carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

Instance key provision (L21): plan report, rev 3

Status: rev 3 of the plan is written and committed. It answers Astra's round 2 (CODEX-PLAN-INSTANCE-KEY-R2.md) under Paul's rulings R-A to R-F, which are the plan's rulings 19 to 24. Nothing is pushed.

  • Branch: instance-key-build, worktree /home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-key-provision
  • Plan: docs/superpowers/plans/2026-10-10-instance-key-provision.md, rev 3, commit 3947e1d9
  • Tasks: 7, then the final verification. Task 5 is now the adoption verb and its publication, and is mounted nowhere on main. Task 7 lost ConfiguredInstanceKey.

Dispositions (round 2)

| # | Finding | Disposition | |---|---|---| | R2-1 | A configured key still bypasses the refusal (configured A, own B) | Accepted: source (c) dropped; configured keys need an adoption record too | | R2-2 | Configuration evidence neither identifies nor pins the key | Accepted: the record names the key by digest; the pin carries it; the install verifies it | | R2-3 | An adoption record outlives its writer | Accepted: the record binds host, incarnation, session and generation from the agent's launch state; every admission starts with none | | R2-4 | No publication on the running session; O6 not gated | Accepted: hostfiles.PlatformRelay onto the session's publisher; O6 a gate in O4 | | R2-5 | Moves leave the record behind | Accepted: O7 with the exact lists; O6 does not ship before O7 | | note | The record is tenant-forgeable | Answered by ruling 19 (trusted-helper assertion) | | R1-6 | The merge option | Closed: withdrawn by Astra |

Counts: 5 accepted, 0 declined. One note answered, one round 1 finding closed.

What changed in the code

  • instancekey: the record is now version 2 and writer-bound (Binding, ParseAdoption, Binds). New: the sources (SourcePlatform, SourceConfig, SourceOf) and VerifyDelivered for the install.
  • ClaimLaunchSnapshot gains InstanceKeySHA256 and InstanceKeySource, read from the same two files as the env; the identity is unchanged.
  • The adoption verb (MountInstanceKeyAdoption) takes its writer, key digest and source from an AdoptionLaunch that O6 builds per launch. It is no longer mounted on legacy launches.
  • New hostfiles.PlatformRelay stages a platform item on a running publisher without blocking the caller.
  • O2' for stage B is now (a) or (b'), with the pinned key in PinObject.InstanceKey. It replaces stage B's own rev 3 KeyEvidence reader.

How it was checked

  • Every Go block in Tasks 1 to 7 was built in a scratch copy, then rebuilt task by task in a second copy, with go vet and gofmt -l clean after each task. Both copies ended byte-identical. The plan's blocks were then extracted back out of the document and compared with the scratch files, byte for byte.
  • go build for windows, darwin and linux/arm64 of activator, host, hostfiles, instancekey and appconfig, and go vet on darwin: clean.
  • Tests run, niced: - all new tests; - the activator Seed, Start, Prepare, Control, Schedule, Claim and LaunchEnvironment families, with the sandbox off for sockets; - the host claim and env report tests; - all of instancekey, hostfiles, moves and moves/archive.

All passed. The full go test ./internal/host/ -run Claim also passed, in 1112 seconds. It exceeds go test's default 10-minute timeout on this shared box because of an existing 30,000-delivery test (TestClaimCompletedProducersBoundStopAndRetry, 1015 seconds), so the plan's sandbox note now says to pass -timeout 30m.

  • O2' was compiled and run against stage B at 919662d2, in a scratch copy with internal/instancekey added. go build ./internal/... and go vet were clean, and go test ./internal/hostrelocate/ passed: 17 evidence cases, plus every existing pinner test.
  • Scratch copies are deleted. The shared $TMPDIR/gocache is untouched.

The rastrillo addendum

The helper reports on every process start (at most once per process), and the body adds "source": "platform" | "config", copied from a new agent-set $CARLOS_INSTANCE_KEY_SOURCE. It is in the plan's "Ask for rastrillo #66" section, ready to post.

Top concerns

  1. The helper cannot know the source by itself. A platform key and a configured key both arrive as $CARLOS_INSTANCE_KEY. The plan has the agent deliver CARLOS_INSTANCE_KEY_SOURCE beside it (agent-owned, part of O6) for the helper to echo, and the agent checks the echo against its own launch state. If Paul would rather not add a variable, the source field could be dropped from the request, because the agent already knows the source.
  2. Nothing writes a record until O6 and O7 land. The verb is built and tested but mounted nowhere: legacy launches have no writer session to bind to, and claim launches have no control socket. Platform-key and configured-key apps therefore do not relocate until claim Plan 9 Group D (O6) and the moves change (O7) ship, in that order. That is the explicit refusal of ruling 23.
  3. A configured key's digest is published. A config record puts the SHA-256 of a configured secret in the data bucket. That reveals nothing for a random key; a weak, hand-typed key could be guessed against it. It is listed as a follow-up.
  4. Stage B has its own diverging O2' in plan text. Its rev 3 (919662d2) has the KeyEvidence reader, with presence-only config evidence and the export parser. Stage B's planner must replace that step with this plan's O2' and rewrite its ruling 55, as O2' says.
  5. One report lost to box loss inside the publisher's bound. A box lost within that bound (a second by default) after the first adoption loses that report. The pin then refuses until a later session re-reports. That is safe, but worth knowing.

Rev 3.1 (Astra round 3)

Round 3 found no P1. Rev 3.1 fixes the two remaining findings; both are accepted.

  • R3-1 (P2), the replica exclusion at delivery. Ruling 25: fileEnvFor, the composition every launch path uses, drops CARLOS_INSTANCE_KEY for a replica row. That covers Start, Seed, the nested prepare, LaunchEnvironment and the claim snapshot. The helper's step 2 then has nothing to return, so no rastrillo change is needed. It is in the new Task 3b; Task 4's snapshot uses it and names no key for a replica.
  • R3-2 (P3), the dependency order. The follow-ups now say O7 ships before O6.
  • Effect on stage B: O2' and its test are unchanged, and nothing stage B consumes changes. The moves env report for a replica no longer lists the key, on the source and the target alike.

How it was checked:

  • The plan's Go blocks were extracted from the document, rebuilt into a tree and compared byte for byte with the tested scratch tree. They matched.
  • go vet and gofmt were clean. Builds for darwin, windows and linux/arm64 were clean.
  • The new tests were run, sandbox off for sockets: - TestReplicaLaunchIsHandedNoInstanceKey: real replica Start and Seed, with a configured key, a pre-existing env key and both, plus a home-row control case; - TestNestedReplicaPrepareIsHandedNoInstanceKey: run and seed roles; - TestClaimReplicaLaunchIsHandedNoInstanceKey.

They pass with the activator and host launch families. With the strip removed, the replica launch test fails in all three cases.

  • Scratch copies are deleted, and $TMPDIR/gocache is kept.

Residual: a replica on a claim launch before O6 has no control socket. It sees step 4, as before L21, and is still handed no key.

Rev 4 (Astra round 4, Paul's ruling 26)

Round 4 found two P2s in rev 3.1's replica strip; both are accepted. Paul decided that a replica gets its home's key (ruling 26, which replaces rulings 17 and 25).

  • The strip is reverted. A replica's env is composed like any other, so a replica sharing its home's configured key keeps it.
  • replicaKeyGate, an agent-side check, runs on every replica launch (Start, Seed, the nested prepare and the claim launch source): - a delivered key must match the home key's digest; - a keyless replica starts only with a control socket, where the helper refuses at step 3; - with no socket, the agent refuses the launch itself, so the app never reaches the helper's mint fallback.
  • Pre-roll check. Rollout step 1 is new: it compares each replica's key digest with its home's before any box is upgraded, and records matches in CARLOS_REPLICA_HOME_KEYS. Rollout step 6's wording is corrected.
  • New obligation O8 (replica placements, #162). A replica record names no home, the home-to-replica mirror is the app's own, and no box can read another region's data bucket. So handing a keyless replica its home key's value needs a placement change. Until O8 lands, the fallback is the gate's reliable refusal.
  • Effect on stage B: O2' and its test are unchanged. SnapshotClaimLaunch can now return ErrReplicaKey for a replica, and LaunchEnvironment is as in rev 3 again.

How it was checked:

  • Code blocks: the plan's blocks, rebuilt from the document, match the tested scratch tree byte for byte. go vet and gofmt are clean, and builds for darwin, windows and linux/arm64 are clean.
  • Tests through a real launch: the replica tests run the test binary as the app, executing rastrillo e3ea08de's resolution and its documented caller. - With no control socket: a keyless replica is refused and no app runs; the home's key runs and seals with it; a differing or unlisted key is refused. - With a socket (sandbox off): the helper refuses with ErrNoInstanceKey. - With the gate disabled, the app reports "minted". That is Astra's failure, so the test catches it.
  • Other paths tested: the nested prepare (run and seed), the claim launch source, and the env-var parser.
  • Regression runs: the activator launch families, run with the sandbox off, pass. So do the targeted host tests and all of instancekey and hostfiles.
  • Scratch copies are deleted, and $TMPDIR/gocache is kept.

Rev 5 (Astra round 5, Paul's rulings 27 and 28)

All three findings are accepted: two P2s and a P3, all about replicas.

  • R5-1, ruling 27: a configured key is never changed in place. - The procedure. The rollout's operator note gives the coordinated procedure for a configured-key change on an app with replicas: stop the replicas, drop their list entries, change the key, sync, re-check, then start them. - The safety net. A replica's configured key must match its list entry, which only the pre-roll check writes. It must also match what the bucket's configuration renders now (ReplicaConfigKey over appconfig.ConfiguredKeyDigest). - Why the fresh read matters. It is cheap, it needs no O8, and it closes Astra's case: the home updated while the replica's box had not synced yet. With it removed, the test fails. - Residual. A change only on the home (a hand-edited home env, or a key configured per host) is documented by a test and left to the procedure, or to O8.
  • R5-2, ruling 28: O8 delivers platform-minted keys only. A home whose key exists only in configuration is deferred, and its keyless replica is refused (tested).
  • R5-3: ParseHomeKeys errors name the entry's position and the reason, never its text. The agent's logging path is tested.
  • IK-1: unchanged. No IK-1 symbol changes in name, signature or behaviour. Only ParseHomeKeys's error text changed, and stage B does not use it. O2' is unchanged.

How it was checked:

  • Code blocks: the plan's blocks, rebuilt from the document, match the tested scratch tree byte for byte. go vet and gofmt are clean, and builds for darwin, windows and linux/arm64 are clean.
  • Tests: the activator launch families, run with the sandbox off, pass. So do the targeted host tests and all of instancekey, hostfiles, appconfig, moves and moves/archive.
  • Scratch copies are deleted, and $TMPDIR/gocache is kept.

Rev 5.1 (Astra round 6)

Both P2s are accepted. Task 1's code is unchanged; it is built and reviewed on this branch.

  • R6-1, ruling 29: the replica gate decides on the launch's own read. instanceEnvLayer returns the env and its key's layer from one read, every gate call passes that layer, and the gate reads no env file. - The interleaving test (TestReplicaGateDecidesOnTheLaunchesOwnRead) has a hook rewrite <host>.config.env between the launch's read and the gate, as a config sync can. The launch is refused. - Red check: with rev 5's second read restored, the app ran with the stale key.
  • R6-2, ruling 30: read on each launch. The list moves from the agent's environment (never re-read by a running agent) to /etc/carlos/replica-home-keys. - The pre-roll check writes the list whole and renames it into place. The agent reads it at every replica launch and keeps no copy, so no restart is needed. A restart would bounce every instance on the box. - The procedure keeps replicas stopped throughout, and checks the file before the key changes.
  • Coordinator notes: - Task 1's blocks are now the branch's code, from c568c13b to e8487c3a: the canonical-only ParseAdoption, the ParseEnv test, the two doc comments and TestEmptyOwnInstanceKeyShadowsNothing. - Rollout step 3 has the mixed-version moves readiness note: roll every box before any configured key matters.
  • IK-1: unchanged by this revision. O2' and its test were re-run against stage B's tip b9a753f0 with the reviewed instancekey, and pass.

How it was checked:

  • Code blocks: rebuilt from the document on top of the branch, they match the tested tree byte for byte, and Task 1's blocks equal the branch's files. go vet and gofmt are clean, and builds for darwin, windows and linux/arm64 are clean.
  • Tests: the activator launch families, run with the sandbox off, pass. So do the targeted host tests and all of instancekey, hostfiles, appconfig, moves and moves/archive.
  • Scratch copies are deleted, and $TMPDIR/gocache is kept.