carlos / platform Public

docs: cohort-1 runbook: fence writer applied, alias plan stale, box-record proposal, #302

- Step 3: fence-writer role and box-records bucket policy applied
  (5 added) and merged as platform-infrastructure#46 (beb346b); serving
  unaffected on sophie and leo, no AccessDenied.
- 7a.1: the saved alias plan was refused as stale (the host_buckets
  apply moved the state after it was saved); not applied, #45 unmerged;
  only targeted dev plans until #45 merges.
- Blocker 4: no supported way to publish a box record without region
  HA; proposal for `carlos system box publish-record` with its cost.
- Links amadan #302 for the mutation classifier versus bucket policy
  conflict.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 61e28b03fa88f1a05d929655b1ec208eb6982121 parent 4df3d72
1 file changed, +15 −4
  • docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md +15 −4

CI cancelled — superseded by a newer push — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m

amadan: job cancelled — a newer push superseded it
diff --git a/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md b/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
index 2b7cada44..7503c2388 100644
--- a/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
+++ b/docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md
@@ -193,8 +193,16 @@ Pass: all twelve are root-owned regular files in `/etc/systemd/system`; the edge
1. **Release.** Resolved: v0.28.0, cut from 26065378 (Plan 14 Parts 2 and 3, the activation tools, e8e961dd), is fully rolled on dev (2026-10-18). Step 1.
2. **Empty app segment.** 267 objects under `apps/bab//` (P5). Source: leo's app-less `proof*` routes, removed (2026-10-16). Cleanup waits for the e8e961dd release to reach leo and for Paul's approval. Step 2.
-3. **IAM.** Leo's object-version actions were applied on 2026-10-18 (targeted, `0 to add, 1 to change, 0 to destroy`): the claim's data store lists, reads and deletes object versions in `carlos-dev-ie-640012953448` on the seed and cold-wake paths. The fence-writer role and the deployment bucket policy are planned in carlosframework/platform-infrastructure#46 (`5 to add, 0 to change, 0 to destroy`), not applied. Step 3.
-4. **Box identity records.** Step 5's enrollment needs each box's signed AWS identity record at `control/boxes/<box>.json`. None exists, and none has ever been written: the publisher (`internal/host/boxrecord.go`, `boxRecordLoop`) runs only with `CARLOS_REGION_HA` on, which is off on every dev box. Leo's and sophie's records must be published before step 5, by turning region HA on for them or by a reviewed one-off publish. Open question 4.
+3. **IAM.** Leo's object-version actions were applied on 2026-10-18 (targeted, `0 to add, 1 to change, 0 to destroy`): the claim's data store lists, reads and deletes object versions in `carlos-dev-ie-640012953448` on the seed and cold-wake paths. The fence-writer role (`carlos-dev-ie-b-fence-writer`, bootstrap user `carlos-dev-ie-b-fence-writer-root`, no access key yet) and the deployment bucket policy were applied on 2026-10-18 from the saved targeted plan (`5 added, 0 changed, 0 destroyed`) and merged as platform-infrastructure#46 (`beb346b`). The bucket policy names that role as the only fence writer. After the apply, wakecheck still served on sophie (200) and leo answered `waking elsewhere` (sophie held the lease), with no AccessDenied in either box's journals. Step 3.
+4. **Box identity records.** Step 5's enrollment needs each box's signed AWS identity record at `control/boxes/<box>.json`. None exists, and none has ever been written: the publisher (`internal/host/boxrecord.go`, `boxRecordLoop`) runs only with `CARLOS_REGION_HA` on, which is off on every dev box. Leo's and sophie's records must be published before step 5. Paul ruled a reviewed one-off publish, with region HA staying off.
+
+ There is no supported way today. `carlos system box` only sets roles (`system_roles.go`), and the only caller of `publishBoxRecord` (`internal/host/boxrecord.go:121`) is the agent's `boxRecordLoop`, which returns at once unless `CARLOS_REGION_HA` is on. Records are never hand-written.
+
+ Proposal, the smallest platform change: a verb `carlos system box publish-record` that calls the existing `publishBoxRecord` once with the box's own label (`metricsBoxLabel`) and `readIdentityRecord`, without the region HA gate, and refuses on fleet boxes as the loop does (`shouldSkipPublisherElection`). It writes `control/boxes/<box>.json` create-only through the box's own credentials. The record is `{v: 1, box, identity_document, identity_signature, written_at}`: the instance identity document from IMDS (base64) and AWS's RSA-SHA256 signature over it, checked later against the embedded regional AWS certificates. It is AWS-signed, not signed by the box or the operator, and grants nothing until enrollment reads it. An existing record for the same instance is a no-op; one for another instance is refused. Cost:
+ - about 40 lines plus tests (parse; no-op on the same instance; refusal on a different instance; refusal on a fleet box; no region HA needed), reusing `publishBoxRecord`'s existing tests;
+ - copy: the usage line, the published and already-in-place lines, the refusals, and a `docs/site/cli.md` paragraph, for Paul's copy review;
+ - census: a new root-path unit for the Plan 14 root-read overlay, and a new caller at the existing `boxid.Publish` key-site for the Plan 9 B8 record-write census;
+ - a release and a dev roll before step 5, then one run on leo and one on sophie, each with Paul's go.
## Accepted trial gaps
@@ -615,7 +623,9 @@ Dev's infrastructure is OpenTofu in `platform-infrastructure/dev`, so the key is
Status on 2026-10-18 (Paul's go to apply):
- The first apply created `aws_iam_role_policy.cohort1_fence_read` and the key `ef26a257-fc1d-4fe3-ad9e-77a4b7b789f6`, then failed reading the key back: the provider's refresh calls `DescribeKey`, `GetKeyPolicy`, `GetKeyRotationStatus` and `ListResourceTags` (CloudTrail), and the operator statement lacked `kms:GetKeyRotationStatus`. The key was left tainted and the alias was not created.
- Fix forward, approved: `kms:GetKeyRotationStatus` added to the operator statement (the file below); the live key policy replaced with that exact rendering by `aws kms put-key-policy` without the lockout bypass (equal to the file apart from AWS storing the one-element `Principal` list as a string; the operator keeps `ScheduleKeyDeletion`, `CancelKeyDeletion` and `PutKeyPolicy`); `tofu untaint aws_kms_key.cohort1_fence`.
-- A fresh targeted plan shows `Plan: 1 to add, 0 to change, 0 to destroy.` (the alias only). Not applied; it waits for Paul's go.
+- A fresh targeted plan showed `Plan: 1 to add, 0 to change, 0 to destroy.` (the alias only). On Paul's go to apply it, tofu refused it as stale (`Saved plan is stale`): the `host_buckets` apply in step 3 moved the state after the alias plan was saved. Not applied. The alias needs a new targeted plan and Paul's go; #45 stays unmerged until then.
+- Live today: the key (Enabled, policy as below) and leo's `cohort1-fence-read` grant. Not yet: the alias.
+- Until #45 merges, main's code lacks `cohort1-fence-kms.tf` while the key and grant are in state, so a whole-root plan from main would destroy them. Run only targeted plans on dev until then.
- The alias refresh uses `ListAliases`, an account-level action covered by the operator's IAM policy, not the key policy.
Its content:
@@ -1392,4 +1402,5 @@ For platform-1e:
1. Should `hostops.Apps` skip an empty app segment instead of refusing, so one stray object cannot stop activation on a whole deployment?
2. For a later teardown out of hold: how do writes made under the claim reach the claims-off restore path?
3. Answered 2026-10-18: leo's object-version actions are applied for the trial; the box-records bucket policy goes with the fence-writer role in platform-infrastructure#46.
-4. How should leo's and sophie's box identity records (`control/boxes/<box>.json`) be published for step 5, given `CARLOS_REGION_HA` is off on both?
+4. Box identity records: Paul ruled a reviewed one-off publish with region HA off. No supported verb exists; the proposal in blocker 4 needs a decision, a build, copy review, census rows, a release and a dev roll.
+5. The console's mutation classifier lets it delete under `control/boxes/` and `control/box-volumes/`, which the new box-records bucket policy refuses: amadan carlos/platform#302.