carlos / platform Public

Instance key provision: Astra round 1 brief + planner's report

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 3b99911e087cbe6f417aa27a3dc9d6d6e32a5979 parent 151c4d9
2 files changed, +88 −0
  • docs/superpowers/plans/CODEX-PLAN-INSTANCE-KEY-BRIEF-1.md +26 −0
  • docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R0.md +62 −0

CI failed — run details

diff --git a/docs/superpowers/plans/CODEX-PLAN-INSTANCE-KEY-BRIEF-1.md b/docs/superpowers/plans/CODEX-PLAN-INSTANCE-KEY-BRIEF-1.md
new file mode 100644
index 000000000..815ac97f1
--- /dev/null
+++ b/docs/superpowers/plans/CODEX-PLAN-INSTANCE-KEY-BRIEF-1.md
@@ -0,0 +1,26 @@
+# Astra round 1 brief: instance key provision (L21), plan rev 1
+
+**Review:** `docs/superpowers/plans/2026-10-10-instance-key-provision.md` on branch `instance-key-provision`, cut from main at 0d338c14.
+
+**What it decides.** Paul's decision of 2026-10-09: the platform creates each instance's key before the app's first start. The plan has the agent mint `CARLOS_INSTANCE_KEY` (32 CSPRNG bytes, base64) create-only into `<host>.env`, the file that already carries `CARLOS_ADMIN_TOKEN`. The framework contract is rastrillo's `carlos.InstanceKey`, on rastrillo main e3ea08d. Its order is:
+1. the app's own key wins;
+2. otherwise `$CARLOS_INSTANCE_KEY`, verbatim;
+3. on CARLOS without a key, the app refuses to start;
+4. off CARLOS, nothing changes.
+
+Rollout: the key is written on every box, existing instances included, before any app adopts the helper.
+
+**Why it matters.** Region HA (docs/superpowers/specs/2026-10-04-region-ha-box-placement-design.md, H6 and §5.6) restores a dead box's instance from the replicated database and the files backup. A key minted after start and never backed up leaves sealed data unrecoverable.
+
+**Context.** The planner's report is in `docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R0.md`. Its main concern: nothing on main backs up `<host>.env` yet. Durability before the first start depends on the claim C8 files provider, which moves is building, and that obligation is recorded as O1.
+
+**Judge:**
+1. **Create-only.** Is it airtight on every path, given the plan's ruling-8 table? The paths: create, backfill, retries, re-render, hibernation restore, move, relocation install and layout move. Can any path overwrite or rotate a key, or leave two processes of one instance holding different keys?
+2. **Durability before first start.** Is the argument sound on legacy boxes and on claim boxes? Is O1 the right obligation, and is it enough?
+3. **The backfill.** Is it safe, idempotent and complete? Is anything missed: hibernated instances, landing paths, nested layouts?
+4. **Secrecy.** Logs, errors, file modes, the console, CLI listings, move reports.
+5. **Concurrency.** Minting under `adminTokenMu` across `seedChild`, `spawnChildFrom`, nested `prepareAt` and `EnsureClaimAdminToken`.
+6. **Tests.** Does the code compile against main? Are the tests real, so that each fails without its guard?
+7. **The planner's five open questions.** Replicas, a key set in configuration, `report.token`, workers and sidecars, and restarts inside a claim session. Say which ones block the build.
+
+Give findings P1/P2/P3 with file:line. End with a verdict: ready to build, or not.
diff --git a/docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R0.md b/docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R0.md
new file mode 100644
index 000000000..757e57a4a
--- /dev/null
+++ b/docs/superpowers/plans/INSTANCE-KEY-PLAN-REPORT-R0.md
@@ -0,0 +1,62 @@
+# Instance key provision (L21): plan report
+
+**Status:** the plan is written and committed. Nothing is pushed.
+- **Branch:** `instance-key-provision`, worktree `/home/paulca/amadan.net/carlos/platform/.claude/worktrees/instance-key-provision`
+- **Commit:** 151c4d94
+- **Plan:** `docs/superpowers/plans/2026-10-10-instance-key-provision.md`
+- **Tasks:** 5, then a final verification step.
+
+**How the code was checked.** Every code block in the plan was prototyped in a scratch worktree of 0d338c14 and passed these checks:
+- `go vet` on linux and darwin, for activator, host, moves, moves/archive and appconfig;
+- `go build` for windows and darwin, for activator and host;
+- `gofmt` clean.
+
+The tests were **not run**, because the shared-machine rules allowed only build and vet. The scratch worktree is deleted.
+
+## Rulings
+
+1. **Where the key lives.** It goes in `<host>.env`, minted on the box. This is the "overlay" in `mergeEnv`'s sense: the file that already holds `CARLOS_ADMIN_TOKEN`. It is not appconfig's instance overlay (`ApplyInstance`, rendered to `<host>.config.env`). Minting in the console is rejected: that layer is app-editable, and it sits *under* `<host>.env` in `mergeEnv`. Stage B's L21 section pointed at the wrong layer.
+2. **Format.** `base64.StdEncoding` of 32 `crypto/rand` bytes: 44 characters, padded. Every reader yields it verbatim.
+3. **Who mints.** Every launch path mints the key right after the admin token, under `adminTokenMu`: `seedChild`, `spawnChildFrom`, nested `prepareAt`, and the claim's `EnsureClaimAdminToken`. The scheduler tick does not. Creation needs no new step: no instance process starts before `doWake`'s seed or start.
+4. **Create-only.** A key of any shape is never rewritten. An empty value counts as absent. The key is appended, the file is `fchmod`ed to 0600, and the file and its directory are fsynced.
+5. **Configuration wins.** If the rendered `<host>.config.env` already supplies `CARLOS_INSTANCE_KEY`, the platform mints none. Minting would change the app's effective key.
+6. **Durability before the first start.**
+ - Legacy boxes have no publisher on main, and region HA can't run there. The key's durability is the admin token's: disk plus the move archive.
+ - On claim boxes, the launch source mints in `Select`, and `Materialize` calls C8's `Establish` before the app executes. C8 is bound (obligation O1) to publish the env bytes before returning.
+ - If the bucket is unreachable, the wake fails and the app does not start. The key stays on disk, and the retry publishes the same bytes.
+7. **Backfill.**
+ - **Who:** the agent, at startup, on legacy boxes only. It runs after layout recovery and before the layout pass and `svc.Start`.
+ - **Which hosts:** ready activator instances only, and only where `<host>.env` already exists. It never creates the file, so a landing's empty path stays empty.
+ - **Idempotent:** it re-runs on every start.
+ - **Verifiable:** it logs one count line, which the rollout reads.
+ - Claim boxes mint under the guard at each cold admission instead.
+ - The "forced publish" is replaced by ruling 6: no process ever holds a key its generation's manifest lacks.
+8. **Never overwrite.** There is a table with a test per path: create, backfill, existing key, claim retry, re-render, hibernation restore, move, relocation install (obligation O3), and layout move.
+9. **Secrecy.** The key is never logged and never in an error. The file is 0600, and so is the launch file. The console never reads `<host>.env`, and `carlos env list` reads bucket config only. The moves env report uses HMAC only. Once published, the key sits in the bucket beside the database it seals.
+10. **Moves and relocation.** The key travels byte for byte in the sealed archive, and is placed only on an empty path. The env report flags a missing or different key. The moves probe never mints, because its pin would fail. Relocation install writes `EnvItem` verbatim (O3).
+11. **No switch.** An env var nobody reads is harmless. The claim launch identity changes once per host. Rollback is reverting the binary.
+12. **Scope.** Exec-backed activator instances only. Unit tenants, TCP-addr routes, sidecars and workers get no key, and `carlos.Running()` is false for them.
+
+## Obligations on other plans
+
+- **O1, the C8 provider:** `Establish` publishes `<host>.env`'s exact bytes before returning. An error fails the wake.
+- **O2, stage B pinner (ruling 25, `pin.go:250`):** `needKey` must also accept an `EnvItem` that carries `CARLOS_INSTANCE_KEY`. Otherwise every app that adopts the platform key goes `stuck` on every relocation, because it never writes `instance.key`.
+- **O3, stage B Task 8c:** add a test that relocation install keeps the published key.
+- **O4, stage B Task 10 item 9:** satisfied by this plan's rollout plus O1 and O2.
+
+## Open questions for Paul
+
+1. **Replicas** (#162) mirror their home but get their own key. Should a replica get its home's key? That needs a way to copy the key between boxes.
+2. **A key set in configuration.** Defer to it (as planned), or reserve the name? Reserving means refusing it in `carlos env set` / `secrets set` and stripping it at render, and the refusal message needs copy review.
+3. **`report.token`.** The L21 candidate contract included it, but the decision covers the key only. Should the platform provision it too?
+4. **Workers and sidecars.** Should they get their instance's key? Today they get none, and run as if off CARLOS.
+5. **Restarts inside a claim session.** A restart inside a running session (not a cold admission) can mint outside the C8 publish point. This only affects instances admitted before this build. Accept it, or forbid minting outside the launch source on claim boxes?
+
+## Concerns
+
+- **The "always continuous" backup of `<host>.env` does not exist on main.** `hostfiles` is imported by nothing, `EnvItem` exists only on `region-ha-1b-next-build`, and no C8 provider exists, so claim serving wakes are refused. L21's durability on claim boxes depends entirely on O1, in a plan that isn't written yet.
+- **O2 is a real hazard.** Without it, adopting the helper and enabling region HA together leaves every adopted app with a nonempty database stuck.
+- **Copying a database between instances moves data, not keys.** The seeded-instance and run specs (#259-261), and any `db push` between instances, copy a database without its key. Those specs need to say what happens to sealed data.
+- **The key in the data bucket.** Region HA puts the key in the same bucket as the database it seals. That follows from the decision, but is worth stating.
+- **Existing tests may need small updates.** Activator or host tests that compare a child's whole environment, or an env file's exact bytes, after a start may now see one more line. Task 2 says to update such assertions without weakening them, and to list them in the commit.
+- **Line numbers for stage B.** Citations into stage B code refer to `region-ha-1b-next-build` at bb955916, not main.