386 lines
20 KiB
Markdown
386 lines
20 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.
|
|
|
|
## Install, restart, verify, and roll back
|
|
|
|
The installer consumes one finalized release-v3 bundle. Its default invocation
|
|
is a dry run: it validates the exact two-file bundle, re-renders the unit from
|
|
the descriptor, binds the installer/package/control-helper source closure to
|
|
the clean Git revision and this checkout's fixed contract root, captures those
|
|
stable bytes once, and prints the proposed target without changing the host:
|
|
|
|
```bash
|
|
python3 ops/install_gcp_leoclean_nosend_release.py \
|
|
--bundle /tmp/leoclean-nosend-release
|
|
```
|
|
|
|
After separate host-restart authorization, the same reviewed revision can be
|
|
executed as root:
|
|
|
|
```bash
|
|
sudo python3 ops/install_gcp_leoclean_nosend_release.py \
|
|
--bundle /tmp/leoclean-nosend-release \
|
|
--execute-restart
|
|
```
|
|
|
|
The executable path never pulls an image. Before touching the service it
|
|
requires the exact digest reference and normalized config digest already to be
|
|
present on the local `unix:///var/run/docker.sock` daemon through an empty,
|
|
service-private Docker config directory. The complete executable transaction,
|
|
including rollback, holds a nonblocking root-owned mode-`0600` lock at
|
|
`/run/leoclean-gcp-nosend-installer.lock`. It installs only the deterministic
|
|
unit, a standalone release-bound container-control helper, the exact release
|
|
descriptor, and a mode-`0600` install receipt. Atomic publications retain the
|
|
published inode identity across durability/readback failure; rollback refuses
|
|
to overwrite a pathname that no longer names that inode. The helper controls a
|
|
container only through a mode-`0600` CID file in the service's mode-`0700`
|
|
runtime directory, then rechecks the full ID, name, immutable image reference,
|
|
config digest, release hash, role label, and running state. After the trusted ID
|
|
disappears it rechecks that the canonical name is still unbound before removing
|
|
the CID file. It never removes a container, uses a mutable name as stop
|
|
authority, or invokes `--force`.
|
|
|
|
An already installed release is verified without a restart. During an upgrade,
|
|
the installer accepts only a stable two-readback active/running or inactive/dead
|
|
prior posture. Before trusting the old `ExecStop`, it exact-binds effective
|
|
`ExecStartPre`, `ExecStart`, `ExecStop`, runtime-directory and systemd security
|
|
properties to the release and verifies the supervising PID's executable,
|
|
command line and cgroup through `/proc`. It then stops the prior service through
|
|
that currently loaded release-bound unit and only afterward replaces and
|
|
reloads the unit. Candidate verification also rejects filesystem or effective
|
|
systemd drop-ins, environment files, forbidden inherited process environment,
|
|
an unexpected fragment, image/config/runtime drift, a non-healthy container,
|
|
or any PID, container-ID, invocation-ID, or restart-counter change across the
|
|
two readbacks. On failure it stops the candidate, restores the exact prior
|
|
files, modes and active/inactive/absent posture, and reloads systemd. An
|
|
incomplete rollback fails closed and is reported as such.
|
|
|
|
This installer does not enable the service, provision IAM, roles or secrets,
|
|
contact GCP or Cloud SQL, change Telegram traffic, touch the VPS, approve a
|
|
proposal, or promote production. Those remain later, separately authorized
|
|
boundaries.
|
|
|
|
## 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 checked by the separate
|
|
`ops/check_gcp_leoclean_nosend_preflight.py` observation receipt. 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.
|
|
|
|
The installer tests use a disposable filesystem root and exact fake
|
|
systemd/Docker readbacks. They prove dry-run non-mutation, local digest/config
|
|
binding, first install, no-restart idempotence, old-unit-first upgrade ordering,
|
|
effective-environment and later-drop-in rejection, PID/restart-race rejection,
|
|
automatic rollback to a running prior release, first-install cleanup, managed
|
|
file posture, contract-root source binding, stale-loaded-unit rejection,
|
|
supervising-process binding, lock contention, post-rename pathname replacement,
|
|
exact inactive-posture rollback, and full-ID/config-digest-only container
|
|
control including name-rebind refusal. They do not constitute a live staging
|
|
restart or a Cloud SQL identity proof.
|
|
|
|
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; an authorized installer/restart execution; 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
|
|
|
|
Release finalization'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. The installer does not delete the bundle, local image, or any
|
|
Artifact Registry object. Its bounded rollback restores the managed prior
|
|
descriptor, helper, unit, receipt, and service state; it must never restart or
|
|
edit the production Leo, Telegram, or VPS services. A live staging execution
|
|
and rollback proof remain separately authorized.
|
|
|
|
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.
|