carlos / platform Public

Instance key provision: rev 2 planner's report

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 7fa74e51ac7dce8a9bb6caed8c099f1769515172 parent f2c8149
1 file changed, +51 −0
  • docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R2.md +51 −0
diff --git a/docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R2.md b/docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R2.md
new file mode 100644
index 000000000..ba695849e
--- /dev/null
+++ b/docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R2.md
@@ -0,0 +1,51 @@
+# Instance key provision (L21): plan report, rev 2
+
+**Status:** rev 2 of the plan is written and committed. It answers Astra's round 1 (`CODEX-PLAN-INSTANCE-KEY-R1.md`). Nothing is pushed.
+- **Branch:** `instance-key-build` (cut from origin/main 0901e835, with Astra's review cherry-picked as 55b22cb5), worktree `/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-key-provision`
+- **Plan:** `docs/superpowers/plans/2026-10-10-instance-key-provision.md`, rev 2, commit f2c8149c
+- **Tasks:** 7, then a final verification step. Rev 1 had 5. The new ones are the shared rule (`internal/instancekey`, Task 1) and the adoption verb (Task 5).
+
+## Dispositions
+
+| # | Finding | Disposition |
+|---|---|---|
+| 1 | O2 accepts a platform key the app may not use | Accepted: key-source evidence (a), (b), (c); new adoption verb and record; O2' |
+| 2 | A failed sync becomes success on retry | Accepted: every ensure syncs, present key included; the claim early return syncs too |
+| 3 | Nested backfill fails with `EINVAL` | Accepted: mode 0 without `O_CREAT`; nested backfill test |
+| 4 | An empty override defeats a configured key | Accepted: one effective-key rule, applied by the mint and by `mergeEnv`; tested through `Start` |
+| 5 | O2 refuses configured keys | Accepted: `appconfig.ConfiguredInstanceKey`, O2' source (c), relocation test |
+| 6 | The rollout's merge option | Declined: squash on the hub is Paul's rule; merging is Paul's call |
+| 7 | Question 5's premise | Accepted: corrected (ruling 16) and pinned by a test |
+| 8 | Coverage gaps (flat seed, nested seed, real restore) | Accepted: three tests |
+| 9 | O1's fake proves ordering only | Accepted, moved to O1''s provider tests |
+| 10 | Legacy durability is disk plus archives | Acknowledged, no change |
+| Q1-Q4 | Paul's open questions | Provisional defaults, marked as not yet confirmed by Paul |
+
+**Counts:** 8 accepted, 1 declined, 1 acknowledged with no change; 4 provisional defaults.
+
+## How the code was checked
+
+Every Go block in Tasks 1 to 7 was built in a scratch copy of this branch, and then rebuilt task by task in a second copy, with `go vet` and `gofmt -l` clean after each task. Both copies ended byte-identical.
+- `go vet` on linux and darwin for activator, host, appconfig and instancekey; `go build` for windows, darwin and linux/arm64. (`GOOS=windows go vet` of activator fails on an existing test's `unix.Mkfifo`, which this plan does not touch.)
+- **The tests were run**, targeted and niced: every new test, plus the activator `Seed`, `Start`, `Prepare`, `Control`, `Schedule`, `Claim` and `LaunchEnvironment` families, the host claim and env report tests, and the whole appconfig, moves, moves/archive and instancekey packages. All pass.
+- **The red states were run too**, for Tasks 3 and 4. Each test the plan says fails does fail. Doing that caught one test, the hibernation restore, that passed with no key at all; it now asserts a key was minted first.
+- **O2' was compiled and run against stage B.** A scratch copy of `origin/region-ha-1b-next-build` (1651ff7d) had `internal/instancekey` added and the O2' diff applied. `go vet` was clean and `go test ./internal/hostrelocate/` passed, every existing pinner test included.
+- Only the O2' wiring sketch (`relocationConfigKey`) is uncompiled, because `relocationRecord` exists only in stage B's plan text.
+- The sandbox refuses unix sockets, so tests that start children or bind control sockets were run with the sandbox off.
+- Scratch copies are deleted.
+
+## The rastrillo ask
+
+**Needed.** Rastrillo's control socket client calls only `PUT` and `DELETE /schedule/{name}`. Nothing on the agent's socket lets an app say which key it uses.
+
+**One line:** when `carlos.InstanceKey` returns `$CARLOS_INSTANCE_KEY` (step 2), call `PUT /instance-key/adoption` once per process, with the key's SHA-256, bearer `CARLOS_ADMIN_TOKEN`, a short timeout and no effect on the result. The full text is the plan's "Ask for rastrillo #66" section.
+
+## Top concerns
+
+1. **Claim boxes cannot record adoption yet.** Claim launches strip `CARLOS_CONTROL_SOCKET` (claim_snapshot.go:116), so an app that first adopts the platform key on a claim box has no record, and its relocation refuses (stuck). Region HA runs only on claim boxes, so source (b) works there only for records written while the box was legacy, until claim launches get a control socket (O6, claim Plan 9 Group D).
+2. **Source (b) waits on rastrillo.** Apps on e3ea08d, which lacks the call, run fine but never relocate. Paul needs to post the ask and get a release before telling owners to adopt.
+3. **Source (c) has a residual.** An app that keeps its own `instance.key`, never published, and also has `CARLOS_INSTANCE_KEY` in its configuration passes (c), though it seals with its own key. Requiring adoption records for configured keys would close it, at the cost of publishing a digest of a configuration secret. The plan follows the ruling and lists this as a follow-up.
+4. **Stage B's O2 parser was wrong.** It accepted `export CARLOS_INSTANCE_KEY="k1"`, which the activator never delivers as the key. O2' parses with the activator's own parser, and its test pins the exported line as no key.
+5. **Moves leave a stale adoption record on the source.** The record is not in moves' journalled artifact lists, so a moved host's flat record stays behind. It vouches only for the same key, but an empty-installation census on that box would refuse it. The fix is a follow-up, with a journal version step.
+6. **Ruling 18 treats fsync as not a host write.** The claim early return fsyncs the env file outside the claim's guard. If the claim's owners disagree, that step moves under `authorize`, which re-reads the claim on every selection.
+7. **A small cost on every launch.** Every launch now does an open, an fchmod and two fsyncs on `<host>.env`. Rev 1 did this only on the mint.