Bind staging image push to a verified receipt
This commit is contained in:
parent
d2ad4f7c96
commit
ac4bf5f01f
4 changed files with 1907 additions and 258 deletions
|
|
@ -132,44 +132,88 @@ Cloud SQL CA, identity validators, and manifest-derived synthetic identity
|
|||
sources must be tracked regular files whose bytes, modes, staged state, and
|
||||
SHA-256 values match the exact `HEAD` commit. Unrelated user worktree changes
|
||||
remain permitted because they cannot enter this image context.
|
||||
Prepared directory paths are derived from those bound files, their modes are
|
||||
normalized and revalidated, and empty or otherwise unmanifested directories
|
||||
are rejected before and after the build. Artifact file modes must equal the
|
||||
artifact manifest; identity, copied source/validator, and top-level metadata
|
||||
files use fixed package modes and are revalidated across the same boundary.
|
||||
|
||||
Use the exact values in `build-args.json` as Docker build arguments. The image
|
||||
build itself revalidates that those values equal `image-input.json`.
|
||||
## Build, push, and receipt one candidate
|
||||
|
||||
This is a live Artifact Registry mutation and requires explicit staging-push
|
||||
authorization. Before running it, verify the staging repository's candidate-tag
|
||||
immutability or retention policy. The command deliberately has no caller-supplied
|
||||
repository, tag, build argument, config digest, or manifest digest:
|
||||
|
||||
```bash
|
||||
install -d -m 0700 /tmp/livingip-leoclean-receipts
|
||||
|
||||
python3 ops/gcp_leoclean_nosend_package.py build-push \
|
||||
--repo-root . \
|
||||
--image-context /tmp/leoclean-nosend-image-context \
|
||||
--docker-binary /usr/bin/docker \
|
||||
--docker-host unix:///var/run/docker.sock \
|
||||
--docker-config /home/operator/.docker \
|
||||
--output-receipt /tmp/livingip-leoclean-receipts/build-push-receipt.json \
|
||||
--execute-staging-push
|
||||
```
|
||||
|
||||
Before contacting Docker, the command revalidates the exact context and binds
|
||||
its source closure to the current clean Git revision. It derives the four build
|
||||
arguments from `image-input.json`, builds one `linux/amd64` image, validates its
|
||||
config/platform/labels/runtime, derives a content-bound candidate tag from the
|
||||
local config digest, and requires zero staging repository digests before push.
|
||||
It does not treat push stdout as authority. A successful push must change the
|
||||
daemon view to exactly one valid staging `RepoDigest` without changing the
|
||||
image configuration.
|
||||
|
||||
The atomically published, no-clobber
|
||||
`livingip.leocleanNoSendBuildPushReceipt.v1` receipt binds the source revision,
|
||||
image-input hash, local config/platform/labels/runtime, fixed staging
|
||||
repository, and daemon-observed manifest digest. Its canonical self-hash is an
|
||||
integrity checksum, not a signature, identity attestation, or independent
|
||||
provenance service. Local build, candidate-tag, and digest references are
|
||||
retained because a Docker-config lock does not establish exclusive ownership of
|
||||
daemon-global cache state; the command never issues `docker image rm` and never
|
||||
uses `--force`. The remote candidate is also never deleted automatically. If
|
||||
receipt publication fails after a push, those objects remain inert and
|
||||
untrusted until a separately authorized operator action resolves them.
|
||||
|
||||
The receipt parent must be an absolute, operator-owned directory without
|
||||
group/other write access. Publication keeps the staged descriptor open, validates
|
||||
the in-memory bytes, hard-links that exact inode without replacement, verifies
|
||||
both names, and fsyncs the directory. The private staged hard link is retained
|
||||
intentionally: portable Unix interfaces cannot conditionally unlink a pathname
|
||||
only if it still names a previously verified inode. Publication therefore never
|
||||
deletes a receipt pathname automatically, and ambiguous failure residue is left
|
||||
for explicit operator inspection. The receipt is mode `0600`, has exactly two
|
||||
links, and is consumed later through the same private directory and stable-inode
|
||||
contract.
|
||||
|
||||
## Finalize one inspected digest-bound release
|
||||
|
||||
After an authorized build and Artifact Registry push returns the immutable
|
||||
image digest:
|
||||
After the authorized build/push command publishes its receipt:
|
||||
|
||||
```bash
|
||||
python3 ops/gcp_leoclean_nosend_package.py finalize \
|
||||
--image-input /tmp/leoclean-nosend-image-context/image-input.json \
|
||||
--image-reference \
|
||||
europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging@sha256:<64-hex-digest> \
|
||||
--expected-revision <reviewed-40-hex-git-revision> \
|
||||
--expected-input-sha256 <reviewed-image-input-sha256> \
|
||||
--expected-config-digest sha256:<reviewed-build-config-digest> \
|
||||
--build-push-receipt /tmp/livingip-leoclean-receipts/build-push-receipt.json \
|
||||
--docker-binary /usr/bin/docker \
|
||||
--docker-host unix:///var/run/docker.sock \
|
||||
--docker-config /home/operator/.docker \
|
||||
--output-bundle /tmp/leoclean-nosend-release
|
||||
```
|
||||
|
||||
This command uses Docker to pull the exact Artifact Registry digest for
|
||||
`linux/amd64`, inspects the immutable local image, and refuses to create either
|
||||
bundle file unless the requested repository digest is present and the platform,
|
||||
config digest, complete required label set, runtime-bearing image configuration,
|
||||
image-input hash, and separately supplied reviewed source revision all match.
|
||||
The config digest is a separate, manually reviewed trust input from the
|
||||
authorized build output; copying it from this command's own registry inspection
|
||||
would defeat the independent binding. This repository does not yet produce a
|
||||
structured build-receipt artifact, so this slice must not be described as
|
||||
proving build-receipt provenance. That producer belongs to the separately
|
||||
reviewed build/push slice before live finalization.
|
||||
This command validates the receipt before Docker, derives the sole
|
||||
`repository@sha256` reference and expected build config from it, independently
|
||||
pulls and inspects that immutable image for `linux/amd64`, and refuses output
|
||||
unless the receipt, registry inspection, image input, complete labels, and
|
||||
runtime-bearing configuration agree.
|
||||
The normalized inspection is embedded in strict release schema
|
||||
`livingip.leocleanNoSendRelease.v2`; unit generation revalidates it. Release v1
|
||||
has no deployed consumer and is intentionally rejected; regenerate any local
|
||||
v1 artifact. For this Linux-only target, inspection normalizes the deprecated
|
||||
`livingip.leocleanNoSendRelease.v3` together with the complete build/push
|
||||
receipt; unit generation revalidates every cross-binding. Release v1 and v2
|
||||
have no deployed consumer and are intentionally rejected; regenerate any local
|
||||
artifact. For this Linux-only target, inspection normalizes the deprecated
|
||||
Windows-only Docker `ArgsEscaped` field while still requiring the exact
|
||||
entrypoint and command arrays.
|
||||
|
||||
|
|
@ -183,7 +227,8 @@ fsynced; a post-publication durability failure removes only the same published
|
|||
inode and re-fsyncs the parent. Any failure leaves no partial final bundle.
|
||||
|
||||
The Docker binary, config directory, and local `unix://` daemon endpoint are
|
||||
explicit inputs; inherited `DOCKER_*` overrides are removed. The pull does not
|
||||
explicit inputs; inherited `DOCKER_*`, `BUILDKIT_*`, and `BUILDX_*` overrides
|
||||
are removed and Docker stdin is closed. The pull does not
|
||||
start a container, install a unit, restart a service, or contact Cloud SQL.
|
||||
Registry authentication and this pull require a separate live-read approval.
|
||||
While holding an advisory lock on the explicit Docker config directory, the
|
||||
|
|
@ -203,10 +248,11 @@ values are rejected.
|
|||
|
||||
## Offline evidence and live gate
|
||||
|
||||
Offline tests prove descriptor and inspection strictness, explicit local Docker
|
||||
authority, non-destructive cache reuse, atomic bundle publication, artifact and
|
||||
synthetic identity binding,
|
||||
digest-only images, pinned runtime versions, a secret-free context, the
|
||||
Offline tests prove strict receipt/descriptor/inspection schemas, exact
|
||||
prepared-context and reviewed-Git binding, mocked pre/post-push digest
|
||||
transitions, explicit local Docker authority, identity-safe cache-reference
|
||||
handling with retained immutable digest references, atomic receipt and bundle publication, artifact and synthetic
|
||||
identity binding, digest-only images, pinned runtime versions, a secret-free context, the
|
||||
capability-drop contract, no published ports, no Docker-socket mount, no
|
||||
environment file, and no production/VPS/Telegram target. CI also builds exactly
|
||||
one `linux/amd64` image, starts it with the reviewed restrictions, requires the
|
||||
|
|
@ -222,9 +268,11 @@ records whether `linux/amd64` ran natively or through cross-platform emulation.
|
|||
Pinned base images and build cache may remain.
|
||||
|
||||
The entrypoint command rejection is not a real Hermes tool invocation, and the
|
||||
bridge-network smoke does not prove general network-egress denial. The offline
|
||||
inspection tests use exact Docker JSON fixtures; they do not prove canonical
|
||||
identity, Cloud SQL permissions, or the live Artifact Registry digest contents,
|
||||
bridge-network smoke does not prove general network-egress denial. The
|
||||
build/push lifecycle tests use mocked Docker state and the offline inspection
|
||||
tests use exact Docker JSON fixtures; they do not prove canonical identity,
|
||||
Cloud SQL permissions, a real Artifact Registry push, the registry's tag
|
||||
policy, or the live Artifact Registry digest contents,
|
||||
or that `teleo-staging-1` currently has a compatible container
|
||||
engine, enough disk, the expected architecture, the expected service account,
|
||||
or an unused unit name. Those are the next read-only live preflight. If the
|
||||
|
|
@ -232,21 +280,28 @@ engine is absent, installation is separate infrastructure work; the deploy must
|
|||
stop instead of falling back to the production sync script or a mutable host
|
||||
virtual environment.
|
||||
|
||||
Publishing the draft branch and stacked PR is only a review handoff. The next
|
||||
deployment-evidence step must execute one authorized remote registry read plus
|
||||
the retained local exact-reference cache mutation against the staging digest.
|
||||
A later installer slice must prove installation and rollback consume the same
|
||||
receipt. Live installation, Artifact Registry publication, service restart,
|
||||
IAM/secret changes, Cloud SQL access, and proposal staging remain separate
|
||||
approvals.
|
||||
Publishing the branch and stacked PR is only a review handoff. After both the
|
||||
digest-inspection package and this receipt producer receive exact-head human
|
||||
review and merge, separate live gates remain: staging repository/tag-policy
|
||||
preflight; authorized build/push receipt production; authorized independent
|
||||
digest finalization; installer/restart/rollback; and Cloud SQL/secret/service
|
||||
deployment proof. CI carries no Artifact Registry credentials and performs no
|
||||
push. IAM/secret changes, Cloud SQL access, proposal staging, production
|
||||
promotion, Telegram traffic, and VPS actions remain separate authority.
|
||||
|
||||
## Rollback and sunset
|
||||
|
||||
This slice's rollback boundary is removal of the unpublished atomic output
|
||||
bundle. The verified repository-digest reference and content-addressable layers
|
||||
may remain in the local daemon cache so the finalizer never deletes shared cache
|
||||
state it cannot exclusively own. It installs no unit and changes no running
|
||||
service. Restoring a prior descriptor, unit, and digest
|
||||
This slice's automatic rollback removes an unpublished bundle only. Receipt
|
||||
pathnames are never removed automatically; the private retained link and any
|
||||
ambiguous publication residue require explicit operator inspection. Tool-owned
|
||||
build and candidate tags, the verified
|
||||
repository-digest reference, and content-addressable layers may remain because
|
||||
a Docker-config lock cannot establish exclusive ownership of daemon-global
|
||||
cache state. A remotely pushed candidate is intentionally not deleted during
|
||||
automatic rollback and cannot be consumed without a valid receipt plus
|
||||
independent finalization. Any later cache cleanup requires separately proven
|
||||
daemon isolation. This slice installs no unit and changes no running service.
|
||||
Restoring a prior descriptor, unit, and digest
|
||||
belongs to the later installer slice, which must never restart or edit the
|
||||
production Leo, Telegram, or VPS services.
|
||||
|
||||
|
|
|
|||
|
|
@ -40,42 +40,61 @@ cut over safely and retire the VPS after soak.
|
|||
synthetic `linux/amd64` OCI lifecycle proved UID/GID `65532`, zero runtime
|
||||
capabilities, no-send health, and complete candidate cleanup on the arm64 Mac
|
||||
Docker daemon. No GCP or live service was contacted.
|
||||
- In progress from exact current-main base
|
||||
`383996a43beebef57cf84a2ced53213e28a41dca`: make release finalization
|
||||
pull and inspect the exact immutable staging digest and refuse release/unit
|
||||
output unless the repository binding, `linux/amd64` platform, config digest,
|
||||
complete labels, runtime configuration, input hash, and source revision all
|
||||
match. Trust-bound JSON is strict, release publication is atomic and
|
||||
no-clobber, and failure rollback re-fsyncs its output parent. This slice does
|
||||
not build, push, install, or deploy an image and has not accessed Artifact
|
||||
Registry or GCP.
|
||||
- Current gate: finish exact-revision offline tests, then stop for separate
|
||||
branch-publication and human-review approval. Remote registry inspection is
|
||||
a later, separately authorized live-read gate.
|
||||
- PR #206 at exact head `e42d635523fcb90ecdd504b0f0290df7015991e7`
|
||||
makes release finalization pull and inspect the exact immutable staging
|
||||
digest. Its five checks pass, but it must not merge unchanged: PR #209's
|
||||
final repair at `d2ad4f7c964ff70f01be65ad57290b1346153f95` is the required
|
||||
corrective layer that prevents finalization from deleting daemon-global
|
||||
digest references a Docker-config lock cannot exclusively own.
|
||||
- Published as draft PR #210: one structured, self-hashed build/push receipt
|
||||
producer. It derives the content-bound candidate tag, manifest digest, and
|
||||
config binding from validated pre/post-push daemon state; finalization
|
||||
consumes that receipt instead of caller-supplied digest/config values. The
|
||||
branch has been rebuilt directly on PR #209's final repair and carries the
|
||||
same retained-digest rule through both build/push and finalization. No
|
||||
daemon-global build tag, candidate tag, immutable digest reference, or
|
||||
receipt pathname is removed automatically. The receipt is published and
|
||||
consumed only through a private operator-owned directory with a retained
|
||||
two-link, stable-inode contract; remote candidates are retained. The exact
|
||||
#210 head belongs in the mutable PR evidence, not recursively inside its own
|
||||
commit.
|
||||
- Current exact-head validation: 206 focused package/OCI tests and the full
|
||||
repository suite at 2,480 passed with 3 expected skips; Ruff, Python compile,
|
||||
and diff checks pass. Two independent advisory exact-head reviews approved
|
||||
the receipt deletion/identity boundary. Tests mock Docker and perform no
|
||||
registry push.
|
||||
- Current gate: publish and review the final #209-based #210 head, allow PR
|
||||
#209 to correct PR #206's feature branch, rerun/review corrected #206, and
|
||||
only then merge/retarget the narrow stack. Registry access remains a later,
|
||||
explicitly authorized live gate.
|
||||
- Not yet live-proven: staging VM/container engine, IAM/Secret Manager, Cloud
|
||||
SQL effective identity, restart behavior, or model/database parity.
|
||||
|
||||
## Remaining order
|
||||
|
||||
1. Finish, publish, and obtain exact-revision human review for immutable image
|
||||
inspection and release finalization.
|
||||
2. Add the structured build/push receipt producer that supplies the independently
|
||||
reviewed manifest and config digests consumed by finalization.
|
||||
3. Add the separate fail-closed installer/restart/verification/rollback slice
|
||||
1. Merge PR #209 into PR #206's feature branch, rerun exact-head checks, obtain
|
||||
human review, and merge the corrected PR #206.
|
||||
2. Update, review, retarget, and merge draft PR #210's structured build/push
|
||||
receipt producer and receipt-bound release-v3 finalizer.
|
||||
3. Under explicit live authorization, verify the staging repository/tag policy,
|
||||
build and push one candidate, publish its receipt, and independently finalize
|
||||
the exact digest.
|
||||
4. Add the separate fail-closed installer/restart/verification/rollback slice
|
||||
that consumes the exact inspection-bound release bundle.
|
||||
4. Run the read-only Artifact Registry and
|
||||
`teleo-staging-1` host/engine/service-account preflight.
|
||||
5. Provision the scoped Cloud SQL role/secret and deploy the separate staging
|
||||
5. Run the read-only `teleo-staging-1`
|
||||
host/engine/service-account preflight.
|
||||
6. Provision the scoped Cloud SQL role/secret and deploy the separate staging
|
||||
service.
|
||||
6. Prove effective identity, reads, function-only proposal staging, denied
|
||||
7. Prove effective identity, reads, function-only proposal staging, denied
|
||||
writes/escalation, receipts, restart stability, and rollback.
|
||||
7. Run behavioral and database parity backtesting.
|
||||
8. Connect the protected Observatory to canonical claims, proposals, pgvector
|
||||
8. Run behavioral and database parity backtesting.
|
||||
9. Connect the protected Observatory to canonical claims, proposals, pgvector
|
||||
projection, and runtime receipts.
|
||||
9. Cut over, soak, restore-test, revoke VPS credentials, and retire the VPS.
|
||||
10. Cut over, soak, restore-test, revoke VPS credentials, and retire the VPS.
|
||||
|
||||
## Current live-action boundary
|
||||
|
||||
No merge, Artifact Registry push, GCP/Cloud SQL/Secret Manager mutation,
|
||||
No merge without explicit human approval, Artifact Registry push,
|
||||
GCP/Cloud SQL/Secret Manager mutation,
|
||||
service deployment/restart, Telegram change, production promotion, canonical
|
||||
proposal apply, or VPS access is part of the current offline corrective slice.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue