carlos / platform Public
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 veton linux and darwin, for activator, host, moves, moves/archive and appconfig;go buildfor windows and darwin, for activator and host;gofmtclean.
The tests were not run, because the shared-machine rules allowed only build and vet. The scratch worktree is deleted.
Rulings
- Where the key lives. It goes in
<host>.env, minted on the box. This is the "overlay" inmergeEnv's sense: the file that already holdsCARLOS_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>.envinmergeEnv. Stage B's L21 section pointed at the wrong layer. - Format.
base64.StdEncodingof 32crypto/randbytes: 44 characters, padded. Every reader yields it verbatim. - Who mints. Every launch path mints the key right after the admin token, under
adminTokenMu:seedChild,spawnChildFrom, nestedprepareAt, and the claim'sEnsureClaimAdminToken. The scheduler tick does not. Creation needs no new step: no instance process starts beforedoWake's seed or start. - Create-only. A key of any shape is never rewritten. An empty value counts as absent. The key is appended, the file is
fchmoded to 0600, and the file and its directory are fsynced. - Configuration wins. If the rendered
<host>.config.envalready suppliesCARLOS_INSTANCE_KEY, the platform mints none. Minting would change the app's effective key. - 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, andMaterializecalls C8'sEstablishbefore 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. - 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>.envalready 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. - 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.
- 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, andcarlos env listreads bucket config only. The moves env report uses HMAC only. Once published, the key sits in the bucket beside the database it seals. - 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
EnvItemverbatim (O3). - No switch. An env var nobody reads is harmless. The claim launch identity changes once per host. Rollback is reverting the binary.
- 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:
Establishpublishes<host>.env's exact bytes before returning. An error fails the wake. - O2, stage B pinner (ruling 25,
pin.go:250):needKeymust also accept anEnvItemthat carriesCARLOS_INSTANCE_KEY. Otherwise every app that adopts the platform key goesstuckon every relocation, because it never writesinstance.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
- 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.
- A key set in configuration. Defer to it (as planned), or reserve the name? Reserving means refusing it in
carlos env set/secrets setand stripping it at render, and the refusal message needs copy review. report.token. The L21 candidate contract included it, but the decision covers the key only. Should the platform provision it too?- Workers and sidecars. Should they get their instance's key? Today they get none, and run as if off CARLOS.
- 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>.envdoes not exist on main.hostfilesis imported by nothing,EnvItemexists only onregion-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 pushbetween 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-buildat bb955916, not main.