carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1# Instance key provision (L21): plan report, rev 3
2
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.
4- **Branch:** `instance-key-build`, worktree `/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-key-provision`
5- **Plan:** `docs/superpowers/plans/2026-10-10-instance-key-provision.md`, rev 3, commit 3947e1d9
6- **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`.
7
8## Dispositions (round 2)
9
10| # | Finding | Disposition |
11|---|---|---|
12| R2-1 | A configured key still bypasses the refusal (configured A, own B) | Accepted: source (c) dropped; configured keys need an adoption record too |
13| 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 |
14| 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 |
15| R2-4 | No publication on the running session; O6 not gated | Accepted: `hostfiles.PlatformRelay` onto the session's publisher; O6 a gate in O4 |
16| R2-5 | Moves leave the record behind | Accepted: O7 with the exact lists; O6 does not ship before O7 |
17| note | The record is tenant-forgeable | Answered by ruling 19 (trusted-helper assertion) |
18| R1-6 | The merge option | Closed: withdrawn by Astra |
19
20**Counts:** 5 accepted, 0 declined. One note answered, one round 1 finding closed.
21
22## What changed in the code
23
24- `instancekey`: the record is now version 2 and writer-bound (`Binding`, `ParseAdoption`, `Binds`). New: the sources (`SourcePlatform`, `SourceConfig`, `SourceOf`) and `VerifyDelivered` for the install.
25- `ClaimLaunchSnapshot` gains `InstanceKeySHA256` and `InstanceKeySource`, read from the same two files as the env; the identity is unchanged.
26- 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.
27- New `hostfiles.PlatformRelay` stages a platform item on a running publisher without blocking the caller.
28- 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.
29
30## How it was checked
31
32- 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.
33- `go build` for windows, darwin and linux/arm64 of activator, host, hostfiles, instancekey and appconfig, and `go vet` on darwin: clean.
34- **Tests run**, niced:
35 - all new tests;
36 - the activator `Seed`, `Start`, `Prepare`, `Control`, `Schedule`, `Claim` and `LaunchEnvironment` families, with the sandbox off for sockets;
37 - the host claim and env report tests;
38 - all of `instancekey`, `hostfiles`, `moves` and `moves/archive`.
39
40 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`.
41- **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.
42- Scratch copies are deleted. The shared `$TMPDIR/gocache` is untouched.
43
44## The rastrillo addendum
45
46The 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.
47
48## Top concerns
49
501. **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.
512. **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.
523. **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.
534. **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.
545. **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.
55
56## Rev 3.1 (Astra round 3)
57
58Round 3 found no P1. Rev 3.1 fixes the two remaining findings; both are accepted.
59
60- **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.
61- **R3-2 (P3), the dependency order.** The follow-ups now say O7 ships before O6.
62- **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.
63
64**How it was checked:**
65- 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.
66- `go vet` and gofmt were clean. Builds for darwin, windows and linux/arm64 were clean.
67- The new tests were run, sandbox off for sockets:
68 - `TestReplicaLaunchIsHandedNoInstanceKey`: real replica Start and Seed, with a configured key, a pre-existing env key and both, plus a home-row control case;
69 - `TestNestedReplicaPrepareIsHandedNoInstanceKey`: run and seed roles;
70 - `TestClaimReplicaLaunchIsHandedNoInstanceKey`.
71
72 They pass with the activator and host launch families. With the strip removed, the replica launch test fails in all three cases.
73- Scratch copies are deleted, and `$TMPDIR/gocache` is kept.
74
75**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.
76
77## Rev 4 (Astra round 4, Paul's ruling 26)
78
79Round 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).
80
81- **The strip is reverted.** A replica's env is composed like any other, so a replica sharing its home's configured key keeps it.
82- **`replicaKeyGate`,** an agent-side check, runs on every replica launch (Start, Seed, the nested prepare and the claim launch source):
83 - a delivered key must match the home key's digest;
84 - a keyless replica starts only with a control socket, where the helper refuses at step 3;
85 - with no socket, the agent refuses the launch itself, so the app never reaches the helper's mint fallback.
86- **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.
87- **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.
88- **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.
89
90**How it was checked:**
91- **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.
92- **Tests through a real launch:** the replica tests run the test binary as the app, executing rastrillo e3ea08de's resolution and its documented caller.
93 - 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.
94 - With a socket (sandbox off): the helper refuses with `ErrNoInstanceKey`.
95 - With the gate disabled, the app reports "minted". That is Astra's failure, so the test catches it.
96- **Other paths tested:** the nested prepare (run and seed), the claim launch source, and the env-var parser.
97- **Regression runs:** the activator launch families, run with the sandbox off, pass. So do the targeted host tests and all of `instancekey` and `hostfiles`.
98- Scratch copies are deleted, and `$TMPDIR/gocache` is kept.
99
100## Rev 5 (Astra round 5, Paul's rulings 27 and 28)
101
102All three findings are accepted: two P2s and a P3, all about replicas.
103
104- **R5-1, ruling 27:** a configured key is never changed in place.
105 - **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.
106 - **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`).
107 - **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.
108 - **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.
109- **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).
110- **R5-3:** `ParseHomeKeys` errors name the entry's position and the reason, never its text. The agent's logging path is tested.
111- **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.
112
113**How it was checked:**
114- **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.
115- **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`.
116- Scratch copies are deleted, and `$TMPDIR/gocache` is kept.
117
118## Rev 5.1 (Astra round 6)
119
120Both P2s are accepted. Task 1's code is unchanged; it is built and reviewed on this branch.
121
122- **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.
123 - **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.
124 - **Red check:** with rev 5's second read restored, the app ran with the stale key.
125- **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`.
126 - 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.
127 - The procedure keeps replicas stopped throughout, and checks the file before the key changes.
128- **Coordinator notes:**
129 - 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`.
130 - Rollout step 3 has the mixed-version moves readiness note: roll every box before any configured key matters.
131- **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.
132
133**How it was checked:**
134- **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.
135- **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`.
136- Scratch copies are deleted, and `$TMPDIR/gocache` is kept.
137