Merge pull request #210 from living-ip/feat/leoclean-build-push-receipt
Some checks are pending
CI / lint-and-test (push) Waiting to run

Bind staging image push to a verified receipt
This commit is contained in:
Fawaz 2026-07-21 01:36:27 -07:00 committed by GitHub
commit eb5c60873f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1907 additions and 258 deletions

View file

@ -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 sources must be tracked regular files whose bytes, modes, staged state, and
SHA-256 values match the exact `HEAD` commit. Unrelated user worktree changes SHA-256 values match the exact `HEAD` commit. Unrelated user worktree changes
remain permitted because they cannot enter this image context. 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, push, and receipt one candidate
build itself revalidates that those values equal `image-input.json`.
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 ## Finalize one inspected digest-bound release
After an authorized build and Artifact Registry push returns the immutable After the authorized build/push command publishes its receipt:
image digest:
```bash ```bash
python3 ops/gcp_leoclean_nosend_package.py finalize \ python3 ops/gcp_leoclean_nosend_package.py finalize \
--image-input /tmp/leoclean-nosend-image-context/image-input.json \ --image-input /tmp/leoclean-nosend-image-context/image-input.json \
--image-reference \ --build-push-receipt /tmp/livingip-leoclean-receipts/build-push-receipt.json \
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> \
--docker-binary /usr/bin/docker \ --docker-binary /usr/bin/docker \
--docker-host unix:///var/run/docker.sock \ --docker-host unix:///var/run/docker.sock \
--docker-config /home/operator/.docker \ --docker-config /home/operator/.docker \
--output-bundle /tmp/leoclean-nosend-release --output-bundle /tmp/leoclean-nosend-release
``` ```
This command uses Docker to pull the exact Artifact Registry digest for This command validates the receipt before Docker, derives the sole
`linux/amd64`, inspects the immutable local image, and refuses to create either `repository@sha256` reference and expected build config from it, independently
bundle file unless the requested repository digest is present and the platform, pulls and inspects that immutable image for `linux/amd64`, and refuses output
config digest, complete required label set, runtime-bearing image configuration, unless the receipt, registry inspection, image input, complete labels, and
image-input hash, and separately supplied reviewed source revision all match. runtime-bearing configuration agree.
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.
The normalized inspection is embedded in strict release schema The normalized inspection is embedded in strict release schema
`livingip.leocleanNoSendRelease.v2`; unit generation revalidates it. Release v1 `livingip.leocleanNoSendRelease.v3` together with the complete build/push
has no deployed consumer and is intentionally rejected; regenerate any local receipt; unit generation revalidates every cross-binding. Release v1 and v2
v1 artifact. For this Linux-only target, inspection normalizes the deprecated 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 Windows-only Docker `ArgsEscaped` field while still requiring the exact
entrypoint and command arrays. 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. inode and re-fsyncs the parent. Any failure leaves no partial final bundle.
The Docker binary, config directory, and local `unix://` daemon endpoint are 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. start a container, install a unit, restart a service, or contact Cloud SQL.
Registry authentication and this pull require a separate live-read approval. Registry authentication and this pull require a separate live-read approval.
While holding an advisory lock on the explicit Docker config directory, the 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 evidence and live gate
Offline tests prove descriptor and inspection strictness, explicit local Docker Offline tests prove strict receipt/descriptor/inspection schemas, exact
authority, non-destructive cache reuse, atomic bundle publication, artifact and prepared-context and reviewed-Git binding, mocked pre/post-push digest
synthetic identity binding, transitions, explicit local Docker authority, identity-safe cache-reference
digest-only images, pinned runtime versions, a secret-free context, the 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 capability-drop contract, no published ports, no Docker-socket mount, no
environment file, and no production/VPS/Telegram target. CI also builds exactly environment file, and no production/VPS/Telegram target. CI also builds exactly
one `linux/amd64` image, starts it with the reviewed restrictions, requires the 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. Pinned base images and build cache may remain.
The entrypoint command rejection is not a real Hermes tool invocation, and the 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 bridge-network smoke does not prove general network-egress denial. The
inspection tests use exact Docker JSON fixtures; they do not prove canonical build/push lifecycle tests use mocked Docker state and the offline inspection
identity, Cloud SQL permissions, or the live Artifact Registry digest contents, 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 or that `teleo-staging-1` currently has a compatible container
engine, enough disk, the expected architecture, the expected service account, 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 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 stop instead of falling back to the production sync script or a mutable host
virtual environment. virtual environment.
Publishing the draft branch and stacked PR is only a review handoff. The next Publishing the branch and stacked PR is only a review handoff. After both the
deployment-evidence step must execute one authorized remote registry read plus digest-inspection package and this receipt producer receive exact-head human
the retained local exact-reference cache mutation against the staging digest. review and merge, separate live gates remain: staging repository/tag-policy
A later installer slice must prove installation and rollback consume the same preflight; authorized build/push receipt production; authorized independent
receipt. Live installation, Artifact Registry publication, service restart, digest finalization; installer/restart/rollback; and Cloud SQL/secret/service
IAM/secret changes, Cloud SQL access, and proposal staging remain separate deployment proof. CI carries no Artifact Registry credentials and performs no
approvals. push. IAM/secret changes, Cloud SQL access, proposal staging, production
promotion, Telegram traffic, and VPS actions remain separate authority.
## Rollback and sunset ## Rollback and sunset
This slice's rollback boundary is removal of the unpublished atomic output This slice's automatic rollback removes an unpublished bundle only. Receipt
bundle. The verified repository-digest reference and content-addressable layers pathnames are never removed automatically; the private retained link and any
may remain in the local daemon cache so the finalizer never deletes shared cache ambiguous publication residue require explicit operator inspection. Tool-owned
state it cannot exclusively own. It installs no unit and changes no running build and candidate tags, the verified
service. Restoring a prior descriptor, unit, and digest 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 belongs to the later installer slice, which must never restart or edit the
production Leo, Telegram, or VPS services. production Leo, Telegram, or VPS services.

View file

@ -40,42 +40,61 @@ cut over safely and retire the VPS after soak.
synthetic `linux/amd64` OCI lifecycle proved UID/GID `65532`, zero runtime synthetic `linux/amd64` OCI lifecycle proved UID/GID `65532`, zero runtime
capabilities, no-send health, and complete candidate cleanup on the arm64 Mac capabilities, no-send health, and complete candidate cleanup on the arm64 Mac
Docker daemon. No GCP or live service was contacted. Docker daemon. No GCP or live service was contacted.
- In progress from exact current-main base - PR #206 at exact head `e42d635523fcb90ecdd504b0f0290df7015991e7`
`383996a43beebef57cf84a2ced53213e28a41dca`: make release finalization makes release finalization pull and inspect the exact immutable staging
pull and inspect the exact immutable staging digest and refuse release/unit digest. Its five checks pass, but it must not merge unchanged: PR #209's
output unless the repository binding, `linux/amd64` platform, config digest, final repair at `d2ad4f7c964ff70f01be65ad57290b1346153f95` is the required
complete labels, runtime configuration, input hash, and source revision all corrective layer that prevents finalization from deleting daemon-global
match. Trust-bound JSON is strict, release publication is atomic and digest references a Docker-config lock cannot exclusively own.
no-clobber, and failure rollback re-fsyncs its output parent. This slice does - Published as draft PR #210: one structured, self-hashed build/push receipt
not build, push, install, or deploy an image and has not accessed Artifact producer. It derives the content-bound candidate tag, manifest digest, and
Registry or GCP. config binding from validated pre/post-push daemon state; finalization
- Current gate: finish exact-revision offline tests, then stop for separate consumes that receipt instead of caller-supplied digest/config values. The
branch-publication and human-review approval. Remote registry inspection is branch has been rebuilt directly on PR #209's final repair and carries the
a later, separately authorized live-read gate. 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 - Not yet live-proven: staging VM/container engine, IAM/Secret Manager, Cloud
SQL effective identity, restart behavior, or model/database parity. SQL effective identity, restart behavior, or model/database parity.
## Remaining order ## Remaining order
1. Finish, publish, and obtain exact-revision human review for immutable image 1. Merge PR #209 into PR #206's feature branch, rerun exact-head checks, obtain
inspection and release finalization. human review, and merge the corrected PR #206.
2. Add the structured build/push receipt producer that supplies the independently 2. Update, review, retarget, and merge draft PR #210's structured build/push
reviewed manifest and config digests consumed by finalization. receipt producer and receipt-bound release-v3 finalizer.
3. Add the separate fail-closed installer/restart/verification/rollback slice 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. that consumes the exact inspection-bound release bundle.
4. Run the read-only Artifact Registry and 5. Run the read-only `teleo-staging-1`
`teleo-staging-1` host/engine/service-account preflight. host/engine/service-account preflight.
5. Provision the scoped Cloud SQL role/secret and deploy the separate staging 6. Provision the scoped Cloud SQL role/secret and deploy the separate staging
service. 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. writes/escalation, receipts, restart stability, and rollback.
7. Run behavioral and database parity backtesting. 8. Run behavioral and database parity backtesting.
8. Connect the protected Observatory to canonical claims, proposals, pgvector 9. Connect the protected Observatory to canonical claims, proposals, pgvector
projection, and runtime receipts. 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 ## 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 service deployment/restart, Telegram change, production promotion, canonical
proposal apply, or VPS access is part of the current offline corrective slice. 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