313 lines
16 KiB
Markdown
313 lines
16 KiB
Markdown
# GCP leoclean no-send service package
|
|
|
|
## Outcome
|
|
|
|
This slice packages the revision-bound Hermes/leoclean no-send artifact and one
|
|
explicit identity bundle as an immutable OCI image for a separate
|
|
`leoclean-gcp-nosend.service` on `teleo-staging-1`.
|
|
|
|
It is target-architecture staging work, not a production cutover. The image is
|
|
the atomic compatibility boundary because Python, the locked Hermes virtual
|
|
environment, PostgreSQL client, Cloud SQL CA, runtime artifact, identity files,
|
|
and container entrypoint must move and roll back together.
|
|
|
|
## Exact target and authority boundary
|
|
|
|
- project: `teleo-501523`
|
|
- zone: `europe-west6-a`
|
|
- instance: `teleo-staging-1`
|
|
- service account:
|
|
`sa-teleo-staging-vm@teleo-501523.iam.gserviceaccount.com`
|
|
- service: `leoclean-gcp-nosend.service`
|
|
- platform: `linux/amd64`
|
|
- image repository:
|
|
`europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging`
|
|
|
|
The package adds only a deployable, digest-addressed staging service surface.
|
|
It does not add IAM, Secret Manager, database-role, proposal-approval,
|
|
canonical-apply, transport, production, or VPS authority.
|
|
|
|
The service has no provider credential in this slice. It can prove the gateway
|
|
and database-tool boundary, but paid model calls require a separately reviewed
|
|
internal no-send ingress and credential-delivery design during the parity
|
|
slice.
|
|
|
|
## Immutable closure
|
|
|
|
The dedicated Dockerfile uses only digest-pinned base images:
|
|
|
|
- Python `3.11.9`;
|
|
- uv `0.9.30`;
|
|
- PostgreSQL client `16.14` on Bookworm.
|
|
|
|
There is no `apt-get`, floating `pip install`, editable install, or image tag in
|
|
the closure. The final image also binds:
|
|
|
|
- the exact Teleo Git revision and release-pass artifact receipt;
|
|
- Hermes commit and source-tree hash;
|
|
- the artifact content and manifest hashes;
|
|
- the exact uv lock;
|
|
- the synthetic identity manifest, lock, `SOUL.md`, structured `identity.json`,
|
|
original source hashes, and bundle hashes;
|
|
- the exact no-send model, skills, tool registry, proposal-only database policy,
|
|
and runtime-probe receipt that produced the identity runtime contract;
|
|
- the Cloud SQL server CA hash;
|
|
- the Dockerfile, entrypoint, and package-contract hashes.
|
|
|
|
The runtime image starts with a capability-limited bootstrap that creates a
|
|
fresh tmpfs profile, then drops all groups, UID/GID to `65532`, and all Linux
|
|
capability bounding sets before executing Hermes. The gateway readiness receipt
|
|
is written only after the real zero-adapter runner starts and its sealed tool
|
|
registry is rechecked.
|
|
|
|
The PostgreSQL base image's inherited data-volume path is explicitly shadowed
|
|
by a tiny read-only, non-executable, mode-`0000` tmpfs. This prevents Docker
|
|
from exposing an unused writable database directory to the runtime.
|
|
|
|
## Prepare a build context
|
|
|
|
First compile and release-verify the no-send artifact from the exact clean
|
|
revision, retaining it only after verification passes:
|
|
|
|
```bash
|
|
LEOCLEAN_NOSEND_ARTIFACT_OUTPUT=/tmp/leoclean-nosend-artifact \
|
|
scripts/run_leoclean_nosend_runtime_canary.sh
|
|
```
|
|
|
|
Derive the identity runtime contract from that artifact and receipt, generate a
|
|
synthetic/noncanonical manifest from the committed fixture sources, and compile
|
|
the two generated views:
|
|
|
|
```bash
|
|
python3 ops/gcp_leoclean_nosend_package.py identity-contract \
|
|
--artifact /tmp/leoclean-nosend-artifact \
|
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
|
--output /tmp/leoclean-nosend-identity-contract.json
|
|
|
|
python3 ops/gcp_leoclean_nosend_package.py generate-identity-manifest \
|
|
--artifact /tmp/leoclean-nosend-artifact \
|
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
|
--database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \
|
|
--constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \
|
|
--database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \
|
|
--identity-source-root . \
|
|
--output /tmp/leoclean-nosend-identity-manifest.json
|
|
|
|
python3 ops/gcp_leoclean_nosend_package.py compile-identity \
|
|
--artifact /tmp/leoclean-nosend-artifact \
|
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
|
--identity-manifest /tmp/leoclean-nosend-identity-manifest.json \
|
|
--identity-source-root . \
|
|
--output /tmp/leoclean-nosend-identity
|
|
```
|
|
|
|
The compiled identity directory contains exactly:
|
|
|
|
- `SOUL.md`
|
|
- `identity.json`
|
|
- `identity-lock.json`
|
|
- `identity-manifest.json`
|
|
|
|
Then run:
|
|
|
|
```bash
|
|
python3 ops/gcp_leoclean_nosend_package.py prepare \
|
|
--artifact /tmp/leoclean-nosend-artifact \
|
|
--receipt .crabbox-results/leoclean-nosend-runtime.json \
|
|
--identity /tmp/leoclean-nosend-identity \
|
|
--identity-source-root . \
|
|
--output /tmp/leoclean-nosend-image-context
|
|
```
|
|
|
|
The command fails closed on a development receipt, revision drift, content or
|
|
mode drift, unsafe links, an inconsistent identity lock, source or generated
|
|
view drift, a runtime/model/skills/permission mismatch, any canonical-authority
|
|
claim, a changed CA, a non-staging target, or an existing output directory. The
|
|
resulting context is secret-free. Direct canonical and staging-table writes
|
|
remain denied; proposal staging remains available only through
|
|
`kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)`.
|
|
|
|
The Git gate is closure-specific: the Dockerfile, entrypoint, package contract,
|
|
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.
|
|
|
|
## 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 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 \
|
|
--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 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.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.
|
|
|
|
The output parent must already exist as an absolute, non-symlink directory.
|
|
The output is one atomically published directory containing `release.json` and
|
|
`leoclean-gcp-nosend.service`. Both files are rendered, strictly JSON-decoded,
|
|
revalidated, and fsynced in a sibling temporary directory before a
|
|
platform-native no-replace directory rename. An existing or concurrently
|
|
created destination is never overwritten. The staged directory and parent are
|
|
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_*`, `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
|
|
command pulls and records the dynamically observed config ID. It intentionally
|
|
retains the exact repository-digest reference: Docker's image cache belongs to
|
|
the daemon, so a config-directory lock cannot prove that another client did not
|
|
start relying on the same immutable reference after the precheck. Repeated runs
|
|
re-pull and re-inspect the same digest without deleting shared cache state.
|
|
Content-addressable layers and the verified digest reference may remain. A
|
|
failed pull or inspection returns a bounded error without forwarding Docker
|
|
stderr.
|
|
|
|
Tags, alternate repositories, alternate projects/instances/service accounts,
|
|
extra descriptor fields, wrong-architecture images, missing repository-digest
|
|
bindings, mismatched image labels, duplicate JSON keys, and non-finite JSON
|
|
values are rejected.
|
|
|
|
## Offline evidence and live gate
|
|
|
|
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
|
|
built-in zero-adapter/no-`send_message` health response, checks that an
|
|
unsupported `send-message` entrypoint command fails with the exact redacted
|
|
error, and binds the running container to the dynamically resolved image ID.
|
|
Before startup, the smoke also requires the built image to declare the implicit
|
|
root bootstrap user, exact Python entrypoint, `service` command, healthcheck
|
|
timings, and `SIGTERM` stop signal. The successful health command then proves
|
|
PID 1 dropped to UID/GID `65532` with zero Linux capabilities. Finally, the
|
|
smoke removes the candidate container, tag, and visible image ID. The receipt
|
|
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
|
|
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
|
|
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 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 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.
|
|
|
|
The fresh tmpfs profile intentionally has no session continuity. That is
|
|
appropriate for isolated gateway/database and fresh-turn parity proof, but it
|
|
is not the production state design. Before traffic cutover, a separate reviewed
|
|
slice must bind durable noncanonical session state without making it identity or
|
|
knowledge authority. The tmpfs-only state contract sunsets when that cutover
|
|
candidate is proven.
|