255 lines
12 KiB
Markdown
255 lines
12 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.
|
|
|
|
Use the exact values in `build-args.json` as Docker build arguments. The image
|
|
build itself revalidates that those values equal `image-input.json`.
|
|
|
|
## Finalize one inspected digest-bound release
|
|
|
|
After an authorized build and Artifact Registry push returns the immutable
|
|
image digest:
|
|
|
|
```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> \
|
|
--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.
|
|
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
|
|
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_*` overrides are removed. 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.
|
|
The exact repository-digest reference must be absent before inspection; use a
|
|
fresh isolated Docker context if it is already cached. While holding an
|
|
advisory lock on the explicit Docker config directory, the command pulls,
|
|
records the dynamically observed config ID, then removes the reference only if
|
|
that ID is still unchanged and proves it absent before publishing the bundle.
|
|
Content-addressable layers may remain. A failed pull, inspection, or cleanup
|
|
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 descriptor and inspection strictness, explicit local Docker
|
|
authority, cache-reference rollback, atomic 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 offline
|
|
inspection tests use exact Docker JSON fixtures; they do not prove canonical
|
|
identity, Cloud SQL permissions, 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 draft branch and stacked PR is only a review handoff. The next
|
|
deployment-evidence step must execute one authorized remote registry read plus
|
|
the reversible 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.
|
|
|
|
## Rollback and sunset
|
|
|
|
This slice's rollback boundary is removal of the unpublished atomic output
|
|
bundle plus removal of the exact repository-digest reference that this command
|
|
introduced into an initially clean, advisory-locked cache; content-addressable
|
|
layers may remain. It 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.
|