runbook: pre-roll emptiness check for the roster-binding fix (design task 9)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 file changed,
+73
−0
docs/superpowers/plans/2026-10-16-claim-activation-dev-cohort1.md+73 −0
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.015s ? 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 d244a002f..0795b0b09 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 |
| @@ -157,6 +157,79 @@ Each failure action is the whole action. Nothing is retried in place unless the |
| - [ ] **G10: block B4.** Pass: the console's claims on (the "Paused state" resume block), `status/consoles/dev-console.json` moved; `apps create claimtrial2`, `ship wc4`, `promote` and the store-mode `instances enable --pool cohort1` done first; then `console-report` and `instances create` back to back, the create succeeding before sophie's next status tick, in at most three attempts. Then acceptance. On failure: a refusal is fail-closed; capture the door's log line. After a third failed attempt, switch the console off again ("Paused state", the pause recipe) and hold leo. |
| - [ ] **G11: acceptance 1 to 4** on `claimtrial2` (launch, a confirmed stop, two cold generations with rising `hits=`, isolation). Then Paul's gos for 10.5 and 10.6. On failure: stop and hold; post the evidence on #250. |
| +#### Pre-roll emptiness check for the roster-binding fix (leo, read-only) |
| + |
| +Source: task 9 of `docs/superpowers/notes/design-roster-revision-holds.md` (section 4), on `claim-empty-cohort-done`. Run it before the roll of the release that carries the binding fix. This runbook does not say which release that is. G4 is its only leo roll, so this section sits right after the gates and applies to whichever roll carries the fix (G4, if it is v0.28.5). If the fix ships in a later release, run the check again before that roll. |
| + |
| +Every item below must be empty or absent, not merely "no running instances". Nothing here writes to leo, the bucket or the roster. The one side effect is `box_inspect`, which stages two scratch files (`roster.json`, `rev`) under `/root/claim-trial` on the box it is given. |
| + |
| +**1. Leo, as root** (an attended root shell, as in "How commands are run"). An absent directory or database counts as 0. `*.lock` files are ignored, as the code ignores them (`claimactivation_preflight.go`, `rootrestart_history.go`). A count that cannot be read counts as a large failure, never as 0. |
| + |
| +```bash |
| +C=/var/lib/carlos-claims |
| +J=$C/journals.db |
| +TOTAL=0 |
| +cnt() { case "$2" in ''|*[!0-9]*) echo "$1 UNREADABLE"; TOTAL=$((TOTAL + 1000000)) ;; *) echo "$1 $2"; TOTAL=$((TOTAL + $2)) ;; esac; } |
| +dbn() { if [ ! -e "$J" ]; then echo 0; elif [ "$(sqlite3 -readonly "$J" "select count(*) from sqlite_schema where type='table' and name='$1'")" = 0 ]; then echo 0; else sqlite3 -readonly "$J" "select count(*) from $1"; fi; } |
| +dirn() { if [ -d "$1" ]; then find "$1" -mindepth 1 ! -name '*.lock' | wc -l; else echo 0; fi; } |
| +command -v sqlite3 jq > /dev/null && echo "tools present" |
| +cnt launches "$(dbn launches)" |
| +cnt stop_inventory "$(dbn stop_inventory)" |
| +cnt journal_inventory_versions "$(dbn journal_inventory_versions)" |
| +cnt journal_requests "$(dbn journal_requests)" |
| +cnt authority/fences "$(dirn $C/authority/fences)" |
| +cnt authority/serving-parks "$(dirn $C/authority/serving-parks)" |
| +cnt retire/serving "$(dirn $C/retire/serving)" |
| +cnt retire/cold "$(dirn $C/retire/cold)" |
| +cnt retire/retired "$(dirn $C/retire/retired)" |
| +cnt retire "$(dirn $C/retire)" |
| +cnt unit-bindings "$(dirn $C/unit-bindings)" |
| +cnt unit-materializations "$(dirn $C/unit-materializations)" |
| +cnt restarts "$(dirn /var/lib/carlos-release/private/restarts)" |
| +cnt migration-items "$(if [ -e $C/authority/migration/migration.json ]; then jq '.Snapshot.Items | length' $C/authority/migration/migration.json; else echo 0; fi)" |
| +test "$TOTAL" -eq 0 && echo "leo: all empty" |
| +``` |
| + |
| +What the lines count, and where the code defines it (`claim-empty-cohort-done`): |
| +- `launches`: the launch journal (`claimjournal_migration.go:121`, `launchjournal.go`). |
| +- `stop_inventory`: root inventory rows (`StopInventoryDB`, `journalservice.go:353`). |
| +- `journal_inventory_versions` and `journal_requests`: the version rows behind `SnapshotRequest` (`inventory_expected.go:59`, `stopinventory_db.go:109`) and the pending inventory requests. |
| +- `authority/fences`: one `<host>.json` record per host, where `PendingInstallations` (`hostguard/root.go:266`) and `PendingStopFences` (`hostguard/recoveryfence.go:14`) look. |
| +- `authority/serving-parks`: serving parks and persisted reboot preparations (`claimservingpark_transport.go:19`). |
| +- `retire/serving`, `retire/cold`, `retire/retired`: the retirement and handover journals. In the code, `retired` is also made under `serving/` and `cold/` (`coldhandover.go:268`, `:396`); the bare `retire` line counts the whole tree, so none can hide. |
| +- `unit-bindings` and `unit-materializations`: `claimunit_production.go:17,21`. |
| +- `restarts`: root restart requests (`rootrollback.go:20`). |
| +- `migration-items`: `migration.json` is the migration record (`rootmigration.go:119`); its `Snapshot.Items` is the list of items. |
| + |
| +**2. DOCS-T1, the bucket** (profile `kass`, dev bucket `carlos-dev-640012953448`). Run it where 5.1's helpers (`ssm_run`, `box_get`, `box_put`, `box_inspect`) are defined. `refresh` does the same read from the signing host; use either. In the bucket, evidence sits under each host: `control/hosts/<host>/evidence/<op>/<box>/` and `control/hosts/<host>/serving-evidence/<box>/` (`claimstop.go:505`). Leo's roster name is `leo-climbing`. A claim names leo if its `holder.writer_box`, `op.exec`, `op.participants` or `holder.admissions[].box` is that name. |
| + |
| +```bash |
| +export AWS_PROFILE=kass |
| +B=carlos-dev-640012953448 |
| +R=eu-west-1 |
| +LEOBOX=leo-climbing |
| +SOPHIE=i-05498eada6f0afd23 |
| +TOTAL=0 |
| +cnt() { case "$2" in ''|*[!0-9]*) echo "$1 UNREADABLE"; TOTAL=$((TOTAL + 1000000)) ;; *) echo "$1 $2"; TOTAL=$((TOTAL + $2)) ;; esac; } |
| +set -o pipefail |
| +HOSTS=$(aws s3 ls "s3://$B/control/hosts/" | awk '$1 != "PRE" && $4 ~ /\.json$/ {print $4}') |
| +echo "host-claim-files $(printf '%s\n' "$HOSTS" | grep -c . || true) # information only; the lines below decide" |
| +for f in $HOSTS; do cnt "claim-names-leo $f" "$(aws s3 cp "s3://$B/control/hosts/$f" - | jq --arg b "$LEOBOX" '[.holder.writer_box?, .op.exec?, (.op.participants[]?), (.holder.admissions[]?.box?)] | map(select(. == $b)) | length')"; done |
| +KEYS=$(aws s3 ls --recursive "s3://$B/control/hosts/" | awk '{print $4}') |
| +cnt evidence-objects-for-leo "$(printf '%s\n' "$KEYS" | grep -cE "^control/hosts/[^/]+/evidence/[^/]+/$LEOBOX/" || true)" |
| +cnt serving-evidence-objects-for-leo "$(printf '%s\n' "$KEYS" | grep -cE "^control/hosts/[^/]+/serving-evidence/$LEOBOX/" || true)" |
| +box_inspect "$SOPHIE" |
| +cnt roster-holds "$(jq '.roster.holds // {} | length' box-inspect.json)" |
| +cnt roster-activations "$(jq '.roster.activations // {} | length' box-inspect.json)" |
| +test "$TOTAL" -eq 0 && echo "bucket and roster: all empty" |
| +``` |
| + |
| +`host-claim-files` only prints how many claim files exist, for the record. Each file that exists is read, and the verdict is the count of names matching leo. A dev bucket that has never run a claim has none. |
| + |
| +**Expected output on real dev leo: all empty.** Every line prints `0`, then `leo: all empty` and `bucket and roster: all empty`. Leo has run claims off since v0.28.4, so `/var/lib/carlos-claims` is absent (P6) and no claim has been written to `control/hosts/`. **Any non-empty or unreadable item STOPS the roll.** Do not delete, move or convert anything to make it pass: paste the output on #250 and wait for Paul's ruling (design section 5, question 2). Paste the full output of both blocks into the fix report. |
| + |
| +**The harness's leo is NOT empty (it holds claimtrial2's creating op and fences from Run 8). The harness re-run starts from post-8.2b, not g10-stuck-run8.** |
| + |
| #### Trial assumptions (Option B): PROPOSED, for Paul to confirm in the morning (D5) |
| Option B gives these up on purpose. Fable recommends accepting all four. None is accepted until Paul says so. |