carlos / platform Public

1b-next plan: L21 decided — the platform pre-provisions instance.key (rastrillo #66 owns the app contract)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev b346819704df502b17c42955149d69067ff34990 parent fe144ba
1 file changed, +1 −1
  • docs/superpowers/plans/2026-10-07-region-ha-1b-next-relocation.md +1 −1

CI passed — run details

diff --git a/docs/superpowers/plans/2026-10-07-region-ha-1b-next-relocation.md b/docs/superpowers/plans/2026-10-07-region-ha-1b-next-relocation.md
index 42a0b7d80..874422318 100644
--- a/docs/superpowers/plans/2026-10-07-region-ha-1b-next-relocation.md
+++ b/docs/superpowers/plans/2026-10-07-region-ha-1b-next-relocation.md
@@ -354,7 +354,7 @@ Interfaces this plan needs that no merged or declared plan supplies. Each gated
| R8 | claim 6b | A hook on `ClaimAssembly`, `Extend func(*ClaimAssembly, hostbox.Actor)`, called by `Build` after `a.Identity.Verify` and before the `WorkSource` captures `Policy.Check` (6b's Build at the line that sets `source := hostbox.WorkSource{…Policy: a.Policy.Check…}`), so the verified box identity is available. Task 10's `applyRegionHA` is its body: with `CARLOS_REGION_HA` on it sets `a.Policy.Relocation = hostbox.RelocationPolicy(actor.Box)` and `a.Reconcile.Reconciler.FenceClosed` (over `Guard.AdmissionFenced`); off, it touches nothing. (Rev 4's insertion after `OpenProductionClaimAssembly` was before the box was known.) | Task 10 |
| R9 | moves Plan 1 Task 7b | A relocation-aware held-release relay: `release.ServeHeldStageRequests`' authorizer accepts a request bound to a `relocating` operation (`Op.Kind == relocating`, `Op.Exec == req.Box`, `op.journal.install_stamp` equal to the request's stamp, and the pinned release equal to the version the local install recorded), beside the `moving`/`landing` requests it accepts today. Without it `MaterializeHeld` returns `ErrStageBlocked` under the edge unit's `ProtectSystem=strict` and the local install cannot complete. | Task 8c |
| R10 | claim 6b (and the activator's owner) | The reservation-bearing extension of main's isolated root launch protocol, which 6b's own external completion gate names as required "before any tenant shape can run with claims enabled" and as "not supplied by the external owner adapters" (6b rev 5, "External completion gates"). Until it exists, `ClaimRoleAssembly.Preflight` returns `activator.ErrClaimIsolationUnavailable` for every launch (6b, `Preflight`), and the runtime's `Prerequisites` calls `Roles.Preflight` before any dispatch (6b, `runtime.Prerequisites`). Even with R1 every production probe would therefore be refused. The ask: the protocol binds each supported role launch to the durable claim reservation and preserves main's namespace isolation; `Preflight` admits a relocation probe's `"probe"` work through it, bound to `op.exec`, the operation and `install_stamp`; without the protocol, `Preflight` refuses before any probe attempt is counted. | Task 8c, Task 10 |
-| L21 | region HA spec (Paul) | **Open: the durability of the always-continuous items between an app minting them and their first publish.** §5.6 has every cold admission copy its manifest before the app starts, and H6 makes `instance.key`, `report.token` and the env files continuous. Continuous is not synchronous: an app can mint `instance.key`, write database rows sealed with it, and replicate them before the publisher's first manifest with the key lands. A box terminated in that window leaves a nonempty database whose key exists nowhere. Rev 18's pinner refuses that case and the operation goes `stuck` for an operator (ruling 25), but the database is not recoverable by any relocation. Closing the window needs an upstream durability contract, and there are two candidates, neither chosen here: the platform pre-provisions `instance.key` (and `report.token`) into the files directory and publishes them before the app's first start, or the replicator holds its first upload until the first manifest carrying the key has landed. The choice is Paul's. | ruling 25 |
+| L21 | region HA spec (Paul) | **Open: the durability of the always-continuous items between an app minting them and their first publish.** §5.6 has every cold admission copy its manifest before the app starts, and H6 makes `instance.key`, `report.token` and the env files continuous. Continuous is not synchronous: an app can mint `instance.key`, write database rows sealed with it, and replicate them before the publisher's first manifest with the key lands. A box terminated in that window leaves a nonempty database whose key exists nowhere. Rev 18's pinner refuses that case and the operation goes `stuck` for an operator (ruling 25), but the database is not recoverable by any relocation. Closing the window needs an upstream durability contract, and there are two candidates, neither chosen here: the platform pre-provisions `instance.key` (and `report.token`) into the files directory and publishes them before the app's first start, or the replicator holds its first upload until the first manifest carrying the key has landed. **Decided 2026-10-09 (Paul): the platform pre-provisions the key.** At creation the platform generates it on the box (CSPRNG), writes it create-only (never overwriting) into the instance's overlay env file as `CARLOS_INSTANCE_KEY`, and publishes it before the app's first start. That file is platform-managed and always continuous. Existing instances get one forced publish before region HA is enabled. The app side is a framework contract, requested as rastrillo/rastrillo #66: detect the platform, use the app's own existing key first and the platform's only when it has none, never mint on the platform, and keep today's behaviour off it. The platform side is built only once #66 agrees the contract. The pinner's refusal (ruling 25) stays as the backstop. Residual: an app that later replaces its key reopens the window for the new key. | ruling 25 |
| R7 | moves Plan 2 | Task 8's `moves.InstallManifestKey`, `SnapshotKey`, `AnchorKey` as declared, and `BuildInputs.Source` taking the pinned generation's full keys as `genselect` lists them (level and TXID range parsed from the key's tail by `SealedClient`). | Task 8b |
## Rulings made while planning