runbook: 8.2b backfill-done invocation and checks (26dd5bb1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+19
−8
docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md+19 −8
CI cancelled — superseded by a newer push — run details
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 b6601b25f..b0395816d 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 |
| @@ -126,7 +126,7 @@ Each failure action is the whole action. Nothing is retried in place unless the |
| - [ ] **G3: signing session.** Pass: the signer role has the P-B2 grants (each phase's dry run passes), and `publish-identity` for v0.28.5 wrote its identity create-only. Then roll leo. On failure: tear down, run the CloudTrail check, hold. |
| - [ ] **G4: leo on v0.28.5.** Pass: `carlos system update` completes, the guard is active, `carlos.prev` is v0.28.4, and leo's box report shows v0.28.5 and verifies (`status`). Then block B1. On failure: leo is claims-off, so this is the ordinary failed-roll path: fix forward only (H11). Hold the window. |
| - [ ] **G5: blocks B1 and B2.** Pass: no instance record in the bucket; leo's registry has zero rows of any kind, counted over every table, apex and domain rows included (B1's check prints `total 0`); sophie's has no `wakecheck` row; nothing left under `/data/.instances`, `/data/instances`, `/data/programs` or `/opt/carlos/live` for any B2 host, on any box. Then the window. On failure: hold. Nothing claim-mode has run. |
| -- [ ] **G6: 8.1 and 8.1b.** Pass: 8.1's masks and `install-control --dry-run`; 8.1b's `backfill-collect`: zero hosts (`hosts` `{}`), phase `collecting`, one create-only write. Then 8.2. On failure: case B1 undo (ruling R1): restore units, enablement and barrier. The plan, if written, stays, and is harmless with the console off. |
| +- [ ] **G6: 8.1 and 8.1b.** Pass: 8.1's masks and `install-control --dry-run`; 8.1b's `backfill-collect`: zero hosts (`hosts` empty, not `null`), phase `collecting`, one create-only write. Then 8.2. On failure: case B1 undo (ruling R1): restore units, enablement and barrier. The plan, if written, stays, and is harmless with the console off. |
| - [ ] **G7: 8.2.** Pass: `host.env`, `install-control`, `journal-migrate --window`, the mode checks, `/var/lib/carlos-claims/authority/exec/census.json` present before any restart, instance-control active, and leo's census report lists zero installations. Then 8.2b. On failure: stop and hold from the `host.env` edit (R1). The console stays off, so dev carries on. |
| - [ ] **G8: 8.2b `backfill-done`.** Needs only 8.2's migration census report with zero installations, never the agent's runtime report or `host_claim_enabled` (those come in step 9, G9). Pass: phase `done`, one If-Match write, and sophie (the roster box with no census key) listed as unreported. Then 8.4. On failure: hold. The plan stays `collecting`; with the console off nothing is frozen. |
| - [ ] **G9: 8.4, the go for 9, step 9.** Pass: 8.4's restore; Paul's go for step 9; the edge active through `guard-exec`, one `REFUSING CARLOS_SWAP=overlap (host-claims-on)`, and a fresh verified box report with `host_claim_enabled`. Then block B4. On failure: stop and hold ("Stop and hold", "Steps 9 and 10"). |
| @@ -2814,7 +2814,7 @@ jq '{writes: [.writes[] | {key, before_etag}], backfill}' backfill-collect-dry.j |
| jq -e --arg id "$BACKFILL_ID" '.backfill.id == $id' backfill-collect.json |
| jq -e '.backfill.phase == "collecting"' backfill-collect.json |
| jq -e --argjson rev "$REV" '.backfill.directory_revision == $rev' backfill-collect.json |
| -jq -e '.backfill.hosts == {}' backfill-collect.json |
| +jq -e '.backfill.hosts != null' backfill-collect.json |
| jq -e '(.backfill.hosts | length) == 0' backfill-collect.json |
| jq -e '(.writes | length) == 1' backfill-collect.json |
| jq -e '.writes[0].key == "control/hosts-backfill.json"' backfill-collect.json |
| @@ -2822,7 +2822,7 @@ jq -e '.writes[0].before_etag == ""' backfill-collect.json |
| refresh |
| ``` |
| -Expected: the dry run prints one create-only write of `control/hosts-backfill.json` and no hosts; the apply prints the same plan as read back; every `jq -e` prints `true`; the roster revision is unchanged. `hosts` is the empty map `{}`, not `null`: platform-1e's ruling (2026-10-20) is that collect writes an empty, non-nil host map, pinned by a test on their branch. Refusals, from `claim-step8-readiness`: `backfill refused: the roster must be published with a home region and a verified floor`, `backfill refused: a backfill plan already exists`, `backfill refused: every instance record must carry an acquisition and stay unchanged while the plan is written`. The last means a record appeared after block B1. Any refusal is a STOP before 8.2: case B1 undo (gate G6). A plan already written stays, and with the console off it is harmless. |
| +Expected: the dry run prints one create-only write of `control/hosts-backfill.json` and no hosts; the apply prints the same plan as read back; every `jq -e` prints `true`; the roster revision is unchanged. `hosts` is empty and not `null`: platform-1e's ruling (2026-10-20) is that collect writes an empty, non-nil host map, pinned by a test on their branch. It may serialize as `{}` or `[]`; `length` covers both. Refusals, from `claim-step8-readiness`: `backfill refused: the roster must be published with a home region and a verified floor`, `backfill refused: a backfill plan already exists`, `backfill refused: every instance record must carry an acquisition and stay unchanged while the plan is written`. The last means a record appeared after block B1. Any refusal is a STOP before 8.2: case B1 undo (gate G6). A plan already written stays, and with the console off it is harmless. |
| ### 8.2 Configure, migrate and start root |
| @@ -2899,26 +2899,37 @@ Keep every migration and root record. A failure leaves serving held. |
| The new signer phase from platform-1e's branch `claim-empty-cohort-done` (Fable section 4, T1; the brief calls it `carlos claims backfill-done`). It takes the plan from `collecting` straight to `done`. It refuses unless: the plan's etag is the collecting one; the plan verifies; the phase is `collecting`; the live record listing equals the plan's hosts (none); `control/hosts/` lists nothing; no `apps/*/*/admission.json` exists; and every roster box with a census key has a report for this plan, captured after the plan was made, listing zero installations. Boxes without a key are skipped and listed as unreported: on dev that is sophie. The report it reads is the migration census report `journal-migrate` published in 8.2 (`control/host-census/<BACKFILL_ID>/leo-climbing.json`), nothing else. It does not wait on or read the agent's runtime box report or `host_claim_enabled`: leo's agent is still stopped and masked here, and the claims-on agent starts only in step 9, after 8.4. It then writes the plan as `done` with If-Match and reads it back. There is no adoption: 8.2c is gone, and the files seed (the 2026-10-19 plan's 8.3) with it. |
| -Section 4 fixes the phase, not its plan input or its output fields, so those lines carry a marker until they are checked against platform-1e's branch. In the same signing session, as 8.1b: |
| +The invocation is platform-1e's final one, from `claim-empty-cohort-done` at `26dd5bb1`: a `claims run` plan with phase `backfill-done` and the same `backfill.id` as 8.1b's collect. The census reports it reads live at `control/host-census/<BACKFILL_ID>/<box>.json`. Run the dry run first; it writes nothing. In the same signing session, as 8.1b: |
| ```bash |
| WINDOW_ID=YYYYMMDDTHHMMZ # set at go: the window package's value |
| BACKFILL_ID="cohort1-${WINDOW_ID%%T*}" |
| refresh |
| jq -n --arg b "$B" --argjson rev "$REV" --arg id "$BACKFILL_ID" \ |
| - '{phase:"backfill-done",bucket:$b,expected_revision:$rev,backfill:{id:$id}}' > plan.json # FLAGS: confirm against platform-1e's branch |
| + '{phase:"backfill-done",bucket:$b,expected_revision:$rev,backfill:{id:$id}}' > plan.json |
| "$CARLOS" system claims run --plan /root/claim-trial/plan.json --signing-key "$KEY" --public-key "$PUB" --aws-region "$R" --dry-run > backfill-done-dry.json |
| jq '{writes: [.writes[] | {key, before_etag}], backfill}' backfill-done-dry.json |
| "$CARLOS" system claims run --plan /root/claim-trial/plan.json --signing-key "$KEY" --public-key "$PUB" --aws-region "$R" > backfill-done.json |
| -jq -e '.backfill.phase == "done"' backfill-done.json # FLAGS: confirm against platform-1e's branch |
| +jq -e '.backfill.phase == "done"' backfill-done.json |
| +jq -e --arg id "$BACKFILL_ID" '.backfill.id == $id' backfill-done.json |
| +jq -e --argjson rev "$REV" '.backfill.directory_revision == $rev' backfill-done.json |
| +jq -e '(.backfill.hosts | length) == 0' backfill-done.json |
| +jq -e '.backfill.unreported == ["sophie-knitting"]' backfill-done.json |
| +jq -e '.backfill.reports | tostring | contains("leo-climbing")' backfill-done.json |
| jq -e '(.writes | length) == 1' backfill-done.json |
| jq -e '.writes[0].key == "control/hosts-backfill.json"' backfill-done.json |
| jq -e '(.writes[0].before_etag | length) > 0' backfill-done.json |
| -jq -e '.backfill.unreported == ["sophie-knitting"]' backfill-done.json # FLAGS: confirm against platform-1e's branch |
| +jq '.backfill.reports' backfill-done.json |
| +aws --region "$R" s3 cp "s3://$B/control/host-census/$BACKFILL_ID/leo-climbing.json" - | sha256sum |
| refresh |
| ``` |
| -Expected: the dry run prints one If-Match write of `control/hosts-backfill.json` (its `before_etag` the collecting plan's) and writes nothing; the apply prints the plan read back at `done`; every `jq -e` prints `true`; sophie is the one unreported box; the roster revision is unchanged. The one new refusal is `backfill refused: a census report lists installations; this phase is for an empty cohort` (Fable's suggested text, waiting for Paul's copy review, D7). The others reuse `ErrBackfillPlan` and `ErrBackfillRecords`. Any refusal is gate G8's failure: hold (case B2). The plan stays `collecting`, and with the dev console claims-off nothing is frozen. |
| +Expected: the dry run prints one If-Match write of `control/hosts-backfill.json` (its `before_etag` the collecting plan's) and writes nothing; the apply prints the plan read back at `done`; every `jq -e` prints `true`; the roster revision is unchanged. `hosts` may serialize as `[]` or `{}`; `length` covers both. Sophie is the one unreported box. `.backfill.reports` holds leo's report digest: compare the printed value with the `sha256sum` line, the digest of leo's census report. |
| + |
| +Refusals, and what each means (any of them is gate G8's failure: hold, case B2; the plan stays `collecting`, and with the dev console claims-off nothing is frozen): |
| +- `ErrBackfillInstallations`: a census report lists an installation, so blocks B1 and B2 were not complete. Its text is `backfill refused: a census report lists installations; this phase is for an empty cohort` (Fable's suggested wording, waiting for Paul's copy review, D7). |
| +- `ErrBackfillRecords`: the record set changed since 8.1b's collect. |
| +- `ErrBackfillPlan`: the wrong id, phase, revision or etag. |
| Then close signing session 6 ("Closing the signing session"), with its CloudTrail check. Nothing after 8.2b needs the signer. |