docs: rehearsal runbook fix round 1 (layout check, roster and fence, evidence capture, return lane, run order, customer steps)
- The flat-layout pre-flight reads carlos status --layout (actual layout), apart from source_report.s1 after a request. - The roster is decoded from its signed envelope; the termination fence is checked at control/box-fences/<enrollment>.json; Run 4's roster checks use both, and the readmit's retirement receipt is checked. - Every run starts a capture loop before its cut, so the rollback journal (dirty, id, loss_ref) and evidence keys are read after close. - Run 3 checks the return lane moves/<host>/<rb>/ is parked in the source bucket after its horizon (R-8). - Run 5 needs dev Runs 1 to 4 (controller ruling; spec:6294). - Runs 6 and 7 get their own customer steps, with <account>/<app>, an owner-approved health check and no probe-only calls; the detail helper and log prefix take ACCT and APP. Sources updated for each changed line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+185
−55
docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md+185 −55
diff --git a/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md b/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md| index 4d12a9359..fd13f8294 100644 |
| --- a/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md |
| +++ b/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md |
| @@ -27,7 +27,8 @@ The Sources section at the end gives file and line for each. |
| |---|---| |
| | `<console>` | the deployment's console origin, `https://...`, as `carlos auth login` stored it | |
| | `<ops>` | the ops account's sqid, as `--account` takes it | |
| -| `<host>` | the run's rehearsal host, `<label>.<instance domain>` | |
| +| `<account>`, `<app>` | a customer run's account sqid and app (Runs 6 and 7) | |
| +| `<host>` | the run's host, `<label>.<instance domain>` | |
| | `<deploy-bucket>` | the deployment bucket (`CARLOS_DEPLOYMENT_BUCKET` in a box's `/etc/carlos/host.env`) | |
| | `<data-bucket-R>` | region R's data bucket: `data_bucket` in that region's box edges file | |
| | `<profile>` | the deployment's named AWS profile. Always name one. There are no default credentials. | |
| @@ -44,34 +45,73 @@ Each check says where to read it. |
| recorded); 5 refused; 1 the console could not be reached or read; 2 a |
| usage error. |
| - **[detail]**: the operator's detail answer. No `carlos` command prints it |
| - yet, so read it with your own login token: |
| + yet, so read it with your own login token. Set `ACCT` and `APP` for the |
| + run: `<ops>` and `moveprobe` for Runs 1 to 5, `<account>` and `<app>` |
| + for Runs 6 and 7. |
| ```sh |
| CONSOLE=<console> |
| + ACCT=<ops> APP=moveprobe |
| TOKEN=$(jq -r --arg c "$CONSOLE" '.credentials[] | select(.console == $c) | .token' ~/.carlos/credentials) |
| - detail() { curl -fsS -H "Authorization: Bearer $TOKEN" "$CONSOLE/api/cli/apps/<ops>/moveprobe/instances/$1/move?detail=1"; } |
| + detail() { curl -fsS -H "Authorization: Bearer $TOKEN" "$CONSOLE/api/cli/apps/$ACCT/$APP/instances/$1/move?detail=1"; } |
| detail <host> | jq . |
| ``` |
| The flat fields (`state`, `phase`, `id`, `last`) sit at the top. The |
| - rest is under `detail`: `move` (the record's journal, absent once the |
| - move closes), `claim`, `target_report`, `source_report`, `evidence`, |
| - `log` (keys only), `timings` (after the close) and `region`. |
| + rest is under `detail`: `move` (the record's journal), `claim`, |
| + `target_report`, `source_report`, `evidence`, `log` (keys only), |
| + `timings` (after the close) and `region`. **`detail.move` and |
| + `detail.evidence` are gone once the move closes**, and a mutation waits |
| + until it closes by default. So every run that needs them starts the |
| + capture loop below before its cut and reads them from the capture file |
| + afterwards. |
| +- **[capture]**: every detail answer of one move, saved as it happens. |
| + Start it in a second terminal once the request has reached ready, before |
| + the cut. It stops by itself once no move is in flight: |
| + |
| + ```sh |
| + capture() { # capture <host> <file> |
| + while :; do |
| + if d=$(detail "$1"); then |
| + printf '%s\n' "$d" >> "$2" |
| + [ "$(printf '%s' "$d" | jq -r .state)" = none ] && break |
| + fi |
| + sleep 2 |
| + done |
| + } |
| + capture <host> run-<n>-detail.jsonl |
| + # later: the last rollback journal the move showed |
| + jq -s '[.[] | .detail.move.rollback | select(. != null)] | last' run-<n>-detail.jsonl |
| + ``` |
| + |
| - **[deploy]**: the deployment bucket, read with `--profile <profile>`. |
| Each move keeps its reports, stop evidence and log under |
| - `moves/<ops>/moveprobe/<host>/<move>/`: `source.json`, `target.json`, |
| + `moves/$ACCT/$APP/<host>/<move>/`: `source.json`, `target.json`, |
| `stopped-source.json`, `stopped-target.json` and `log/<identity>.json`. |
| - The claim is `control/hosts/<host>.json`, the roster |
| - `control/host-actors.json`, each box's edges file |
| + The claim is `control/hosts/<host>.json`, each box's edges file |
| `control/edges/<box>.json`, each box's identity record |
| `control/boxes/<box>.json`, and the floor `control/moves-protocol.json`. |
| ```sh |
| - P="moves/<ops>/moveprobe/<host>/<move>/" |
| + P="moves/$ACCT/$APP/<host>/<move>/" |
| aws s3 ls --profile <profile> "s3://<deploy-bucket>/${P}log/" |
| aws s3 cp --profile <profile> "s3://<deploy-bucket>/${P}log/done.json" - | jq . |
| ``` |
| + The roster, `control/host-actors.json`, is a signed envelope, |
| + `{"payload": <base64 JSON>, "sig": ...}`. Decode the payload to read it |
| + (this does not check the signature; the console does). A box's |
| + termination fence is a separate object, |
| + `control/box-fences/<enrollment>.json`, using the enrollment from the |
| + roster. No object means no fence: |
| + |
| + ```sh |
| + roster() { aws s3 cp --profile <profile> s3://<deploy-bucket>/control/host-actors.json - | jq -r .payload | base64 -d; } |
| + roster | jq '.boxes["<box>"] | {state, enrollment, loss_ids, quarantine_revision}' |
| + aws s3api head-object --profile <profile> --bucket <deploy-bucket> \ |
| + --key "control/box-fences/$(roster | jq -r '.boxes["<box>"].enrollment').json" # 404: no fence |
| + ``` |
| + |
| - **[data]**: a region's data bucket, read with `--profile <profile>`. The |
| instance's replica is under `instances/<host>/` (the box's |
| `CARLOS_HIBERNATE_PREFIX`, `instances` in production), a move's staging |
| @@ -153,9 +193,13 @@ deployment before its first run. |
| `host.env` publishes no `placement` in its edges file: it is a home box. |
| - [ ] **Rehearsal boxes keep instances flat.** Moves refuse a nested |
| instance. Set `CARLOS_LAYOUT_DEFAULT=flat` in each rehearsal box's |
| - `host.env`. Check: `carlos system layout status --console <console>` |
| - shows no nested rehearsal host, and before each run [detail] |
| - `detail.source_report.s1.ok` is `true`. |
| + `host.env`. Pre-flight check, before any request: `carlos status |
| + --layout --console <console>` (the layout each host is actually on, |
| + box by box) shows every rehearsal host's row as `flat`, and no |
| + rehearsal box is marked "(nested by default)". `carlos system layout |
| + status` is not this check: it shows only the recorded intent. Once a |
| + run's request exists, S-2 also checks `detail.source_report.s1.ok`, |
| + the source's own refusal of a nested instance. |
| - [ ] **Never set `CARLOS_MOVES_PHASE1=on` anywhere before P-0 is true.** |
| --- |
| @@ -248,8 +292,9 @@ Paul confirms each of these per deployment before its first run. |
| the console's environment and every box's `host.env`. Check: [box] on |
| each box and the console. #250's activation blockers are closed. |
| - [ ] **Roster.** Each rehearsal box is an active, enrolled entry with no |
| - termination fence. Check: [deploy] `control/host-actors.json` |
| - `boxes.<box>.state` is `active` and `boxes.<box>.enrollment` is set. |
| + termination fence. Check: [deploy] the decoded roster (`roster` above) |
| + has `boxes.<box>.state` `active` and `boxes.<box>.enrollment` set, and |
| + `control/box-fences/<enrollment>.json` does not exist. |
| - [ ] **Box identity records.** `CARLOS_REGION_HA=true` in each rehearsal |
| box's `host.env`. It takes `true`, `1` or `t`; **`on` reads as off**. |
| `CARLOS_AWS_ACCOUNT` is set in the console's environment. Check: [deploy] |
| @@ -330,13 +375,17 @@ Runs refer to these by number. `<from>` and `<to>` are the run's regions. |
| ``` |
| Record the answer's `name` and `due_at`. |
| -- **S-4 Cut.** |
| +- **S-4 Capture, then cut.** In a second terminal, start [capture] for |
| + this run: `capture <host> run-<n>-detail.jsonl`. Then, in the first: |
| ```sh |
| carlos move cut --app moveprobe --account <ops> --host <host> |
| ``` |
| - The expected sentences and exit code are in each run. |
| + The cut waits until the move ends or needs an operator. The expected |
| + sentences and exit code are in each run. Every run captures, so that |
| + whatever the record held while the move was live (the evidence keys, |
| + the rollback journal, a loss) can still be read after it closes. |
| ### Checks after a commit |
| @@ -362,10 +411,9 @@ Runs refer to these by number. `<from>` and `<to>` are the run's regions. |
| `instances/<host>/` returns no versions and no delete markers, and |
| `parked/<inc>/<host>/` holds the retired replica objects with a |
| `manifest.json`. [deploy] the claim's `obligations` is empty. |
| -- **R-6 The source really stopped.** [deploy] `stopped-source.json` (also |
| - named by [detail] `detail.evidence.source` while the move is in flight) |
| - has `steps.tree.ok` and `steps.stray.ok` both `true` and no `stray` |
| - field. [box] on the source box, the journal has no "the instance is not |
| +- **R-6 The source really stopped.** [deploy] `stopped-source.json` (the |
| + key [capture] shows as `detail.evidence.source`) has `steps.tree.ok` |
| + and `steps.stray.ok` both `true` and no `stray` field. [box] on the source box, the journal has no "the instance is not |
| proven stopped yet" line for `<host>` and no M-C20 line ("... is running |
| a process for <host> that this move cannot account for. ..."), and |
| `/data/.moves/.page-outbox/<host>/<move>/stray.json` does not exist. |
| @@ -386,6 +434,20 @@ instance is back) in place of `<to>`. R-5 is replaced: |
| - **R-7 The target's writes came back** (dirty rollbacks only). [report] |
| on the source: `rows` has a `bg` row with `region: <to>`, written on the |
| target while it ran there. |
| +- **R-8 The return lane is parked** (dirty rollbacks only). A dirty |
| + rollback returns the target's writes through its own lane, |
| + `moves/<host>/<rb>/` in the **source's** data bucket, where `<rb>` is |
| + the rollback's id (from [capture]: the last rollback journal's `id`). |
| + The source queues its park when it has probed, with the horizon the |
| + return seal's bound; the park runs only once that horizon plus the |
| + signature age (15 minutes) and the request limit (2 minutes) have |
| + passed. While it is owed, [deploy] the claim's `obligations` has an |
| + entry with `park.location` `moves/<host>/<rb>` and `region` `<from>`; |
| + record its `park.evidence_at`. After that time plus 17 minutes, and |
| + once the obligation is gone: [data] `list-object-versions` on |
| + `<data-bucket-from>` under `moves/<host>/<rb>/` returns no versions and |
| + no delete markers, and `parked/<inc>/<host>/` holds a retirement |
| + directory for it with a `manifest.json`. |
| A clean rollback needs two things: the stopped target's digests equal |
| what the landing installed, and `src_covered` (the source's own replica |
| @@ -465,12 +527,13 @@ nothing and the rollback should be clean. |
| The variable is app-wide. Any moveprobe instance that starts in that |
| region while it is set answers 503 and writes nothing. That is expected. |
| -- [ ] S-1, S-2 with `--to ie`, S-3, S-4. [CLI] M-C8 ("ie could not start |
| - <host>: <reason>. Moving it back to uk."), then M-C9 ("<host> is back in |
| - uk and serving. Nothing was lost."), exit 3. |
| -- [ ] While the move is rolling back and `detail.move.rollback.target_phase` |
| - is `released` or later: [detail] `detail.move.rollback.dirty` is absent |
| - or `false`. Record `detail.evidence.target`. |
| +- [ ] S-1, S-2 with `--to ie`, S-3, S-4 (capture first). [CLI] M-C8 ("ie |
| + could not start <host>: <reason>. Moving it back to uk."), then M-C9 |
| + ("<host> is back in uk and serving. Nothing was lost."), exit 3. |
| +- [ ] From [capture], once the cut has returned and the capture has |
| + stopped: the last rollback journal has `target_phase` `released` or |
| + later, and `dirty` absent or `false`. Record its `id` as `<rb>`, and the |
| + last `detail.evidence.target`. |
| - [ ] Digests: [deploy] `stopped-target.json`'s `db_sha256`, `files_plain` |
| and `timers_sha256` equal `log/rolling_back-target-released.json`'s |
| fields `installed_db_sha256`, `files_plain` and `timers_sha256`. |
| @@ -503,9 +566,11 @@ the rollback is dirty and the target's writes must come back. |
| carlos env set --app moveprobe --account <ops> MOVEPROBE_FAIL_READY_IN=ie,after-write |
| ``` |
| -- [ ] S-1, S-2 with `--to ie`, S-3, S-4. [CLI] M-C8, then M-C9, exit 3. |
| -- [ ] [detail] while rolling back, once `target_phase` is `released` or |
| - later: `detail.move.rollback.dirty` is `true`. |
| +- [ ] S-1, S-2 with `--to ie`, S-3, S-4 (capture first). [CLI] M-C8, then |
| + M-C9, exit 3. |
| +- [ ] From [capture]: the last rollback journal has `target_phase` |
| + `released` or later and `dirty` `true`. Record its `id` as `<rb>` |
| + before anything else; R-8 needs it. |
| - [ ] Digests: [deploy] `stopped-target.json`'s `db_sha256` differs from |
| `log/rolling_back-target-released.json`'s `installed_db_sha256` (the |
| target wrote). |
| @@ -513,7 +578,8 @@ the rollback is dirty and the target's writes must come back. |
| - [ ] [deploy] `log/` has `rolling_back-target-returning` and |
| `rolling_back-target-returned`, then `rolling_back-target-cleaned` and |
| `rolled_back`. |
| -- [ ] R-1 to R-4 and R-6 on `uk`, R-5b on `ie`, and R-7. |
| +- [ ] R-1 to R-4 and R-6 on `uk`, R-5b on `ie`, R-7, and R-8 on `uk` |
| + (the return lane `moves/<host>/<rb>/` in `<data-bucket-uk>`). |
| - [ ] Unset `MOVEPROBE_FAIL_READY_IN` once the checks are recorded. |
| ### Run 4: dev, declare-lost |
| @@ -539,14 +605,16 @@ box cannot be readmitted. |
| Its probe then fails however long the stop takes. If the target finishes |
| its release before the box stops, the run is an ordinary clean rollback: |
| record it and repeat Run 4 on a spare host. |
| -- [ ] S-1, S-2 with `--to uk`, S-3, then |
| - `carlos move cut --app moveprobe --account <ops> --host <host> --no-wait`. |
| -- [ ] When [detail] `state` is `landing` (after `cutting-flipped`), stop |
| - `carlos-dev-uk` through the AWS console (dev only). |
| -- [ ] Wait for `carlos move status --app moveprobe --account <ops> --host |
| - <host> --wait` to say M-C10 ("Waiting for carlos-dev-uk in uk to confirm |
| - it has stopped <host>. <host> is offline."), exit 4. If it is still |
| - landing, the landing deadline has not passed yet. Keep waiting. |
| +- [ ] Before the run, record the box's roster entry: [deploy] |
| + `roster | jq '.boxes["carlos-dev-uk"]'` shows `state` `active`; note |
| + its `enrollment` and `quarantine_revision`. |
| +- [ ] S-1, S-2 with `--to uk`, S-3, S-4 (capture first). The capture runs |
| + until the move closes, through the declaration below. |
| +- [ ] When [capture] shows `state` `landing` (after `cutting-flipped`), |
| + stop `carlos-dev-uk` through the AWS console (dev only). |
| +- [ ] The cut's wait ends with M-C10 ("Waiting for carlos-dev-uk in uk to |
| + confirm it has stopped <host>. <host> is offline."), exit 4. Until the |
| + landing deadline passes, it keeps waiting. |
| - [ ] Declare it lost, with your own AWS credentials for dev in the shell |
| (the command reads EC2 with them and refuses to run as root): |
| @@ -556,20 +624,25 @@ box cannot be readmitted. |
| [CLI] M-C18 ("This declares carlos-dev-uk lost. Anything <host> wrote |
| on carlos-dev-uk after <time> is lost. Type the host name to |
| - confirm."). Type `<host>`. Expect M-C9 and exit 3. |
| + confirm."). Type `<host>`. The command then waits for the outcome: |
| + expect M-C9 and exit 3. |
| - [ ] Expected outcome: **rolled back, with the loss on the record**, not |
| - necessarily clean. [detail] while rolling back: |
| - `detail.move.rollback.loss_ref` and `lost_at` are set. [deploy] |
| + necessarily clean. From [capture]: the last rollback journal has |
| + `loss_ref` and `lost_at` set; record them and its `id`. [deploy] |
| `log/rolling_back-target-lost.json` (event `declared_lost`) and |
| - `log/rolled_back.json` exist. [deploy] `control/host-actors.json`: |
| - `boxes.carlos-dev-uk.state` is `quarantined` and its `loss_ids` holds |
| - the loss. |
| + `log/rolled_back.json` exist. [deploy] the decoded roster: |
| + `boxes.carlos-dev-uk.state` is `quarantined`, `quarantine_revision` is |
| + one more than before, and `loss_ids` holds the loss. |
| - [ ] R-1 to R-4 and R-6 on `ie`. |
| - [ ] Unset `MOVEPROBE_FAIL_READY_IN`. |
| - [ ] Start `carlos-dev-uk` again. It stays quarantined: the deployment's |
| admission gate refuses it for every host until the readmit. Watch [box] |
| its journal from boot, and record anything it does for `<host>` before |
| the readmit. Doing anything for it there is a finding. |
| +- [ ] [deploy] the box retires its local state first and records it: |
| + `control/host-retirement/carlos-dev-uk/<enrollment>/<quarantine_revision>.json` |
| + exists with `"complete": true` and this boot's `boot_id`. Readmit |
| + refuses without it. |
| - [ ] Readmit it. `carlos ops host` exists only with `CARLOS_HOST_CLAIM` |
| set in your shell: |
| @@ -578,8 +651,12 @@ box cannot be readmitted. |
| ``` |
| Answer `readmit` at its prompt. The box must be running and reporting: |
| - readmit refuses a box whose private report is older than 15 minutes. |
| -- [ ] [deploy] `boxes.carlos-dev-uk.state` is `active` again. |
| + readmit refuses a box with a termination fence, and one whose private |
| + report is older than 15 minutes. Before it, check [deploy] |
| + `control/box-fences/<enrollment>.json` does not exist (a stopped box |
| + has none; a terminated one does). |
| +- [ ] [deploy] the decoded roster: `boxes.carlos-dev-uk.state` is `active` |
| + again, with the same `enrollment`. |
| --- |
| @@ -589,8 +666,10 @@ box cannot be readmitted. |
| **Blockers:** P-0; P-1 to P-6 on the flagship; **P-3, the floor, published** |
| (§15 step 6: before any protected production rehearsal); the moves copy |
| -record fully approved (no `pending` row); dev Runs 1 to 3 passed; Paul's |
| -explicit go in the thread, for each of the three. |
| +record fully approved (no `pending` row); **dev Runs 1 to 4 passed** |
| +(each run gates the next, spec §15 step 7; claim Plan 8b is on main, so |
| +the old clause that let Run 5 go ahead with Run 4 skipped no longer |
| +applies); Paul's explicit go in the thread, for each of the three. |
| - **The switch** (Paul, N17): `CARLOS_MOVES_PHASE1=on` in the environment |
| of the console and of the two boxes in play, and nowhere else. The |
| @@ -615,6 +694,47 @@ The runbook names no tenant. The real instances, their owners and the |
| agreed windows live in the flagship's deployment repo and in the amadan |
| thread where Paul gives each go. |
| +These runs move a customer's app, not moveprobe. **Do not call `/ready`, |
| +`/arm-once` or `/report` on a customer host, and do not set |
| +`MOVEPROBE_FAIL_READY_IN`.** Those are moveprobe's own, and S-1, S-3, R-1 |
| +to R-4 and R-7 do not apply. Set `ACCT=<account>` and `APP=<app>` for |
| +[detail], [capture] and the log prefix, and use `--app <app> --account |
| +<account>` on every command. `<from>` and `<to>` are the regions Paul |
| +approves for the run. |
| + |
| +### Customer steps |
| + |
| +- **C-1 Health check, before.** The owner names one health check for the |
| + instance (a URL and the answer that means it is serving) and approves |
| + it for the run. Record it in the deployment repo. Run it and record the |
| + answer. |
| +- **C-2 Request.** |
| + |
| + ```sh |
| + carlos move --app <app> --account <account> --host <host> --to <to> --manual-cut |
| + ``` |
| + |
| + [CLI] M-C1, M-C2, M-C3, then M-C12, exit 4. [detail] at ready, the same |
| + checks as S-2: every target check `ok`, `attempts_admitted` at least 1, |
| + `source_report.s1.ok` and `s2.ok` `true`. Record `<move>` and `<inc>`. |
| +- **C-3 Capture, then cut.** Start [capture] in a second terminal, then, |
| + in the window agreed with the owner: |
| + |
| + ```sh |
| + carlos move cut --app <app> --account <account> --host <host> |
| + ``` |
| + |
| + [CLI] M-C5, M-C6, M-C7, exit 0. |
| +- **C-4 Health check, after.** The owner's check from C-1 gives the |
| + serving answer once M-C7 has printed. The owner confirms it. |
| +- **C-5 Platform checks.** R-5 as written. R-6 as written, except its |
| + last two sentences, which are about moveprobe's worker. [deploy] `log/` |
| + has `cutting-flipped`, `committed`, `committed-opened`, |
| + `committed-source_parked` and `done`. |
| + |
| +If the move rolls back (M-C8 then M-C9, exit 3), the instance is back in |
| +`<from>`: run C-4 there, then R-5b and R-6, and stop the series. |
| + |
| ### Run 6: a disposable customer-shaped instance |
| **Blockers:** P-0; **the home-region parks fix, both parts, merged and |
| @@ -627,9 +747,7 @@ A disposable instance of a customer app with the same shape as moveprobe |
| owner's agreement, before the flagship's claim went on (P-5). |
| - [ ] Instance used: recorded in the deployment repo. |
| -- [ ] Run 1's steps only (a commit), without the moveprobe-specific |
| - [report] checks. The owner checks the instance serves after the open. |
| -- [ ] R-5 and R-6 as written; [CLI] M-C7, exit 0. |
| +- [ ] C-1 to C-5. |
| ### Run 7: the first production customer instance |
| @@ -641,7 +759,8 @@ with its owner. |
| - [ ] The instance was created before the flagship's claim went on, so it |
| is adopted at `instances/<host>` (P-5). One created later cannot move. |
| -- [ ] Run 6's steps. The cut window is the default 120 s. |
| +- [ ] C-1 to C-5, with C-3 inside the agreed window. The cut window is the |
| + default 120 s. |
| - [ ] Report `offline_s` back to the owner: [detail] `last.offline_s`, or |
| `offline_s` in `log/done.json`'s fields. |
| @@ -690,6 +809,8 @@ copy is `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`. |
| | what | where | |
| |---|---| |
| | detail=1 only for operators, only with the switch on | `internal/console/apiinstancemove.go:312-327` | |
| +| `detail.move` is nil once no move is in flight, so the capture loop keeps it | `internal/console/apiinstancemove.go:1291-1292`, `:1416-1423` | |
| +| a mutation waits for the outcome unless `--no-wait` | `move.go:186-188` | |
| | flat fields `state`, `target`, `id`, `last`, `protocol`, `phase` | `internal/console/apiinstancemove.go:160-170` | |
| | `detail.move`, `claim`, `target_report`, `source_report`, `evidence`, `log`, `timings`, `from`, `region` | `internal/console/apiinstancemove.go:1290-1318` | |
| | `evidence.source` and `evidence.target` come from the record in flight | `internal/console/apiinstancemove.go:1367-1370`, `:1416-1423` | |
| @@ -717,6 +838,10 @@ copy is `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`. |
| | parked objects at `parked/<inc>/<host>/<retirement>/`, a `manifest.json`, the source versions deleted | `internal/park/remote.go:230-232`, `:473`, `:497` | |
| | stopped report `steps.tree`, `steps.stray`, `stray` (omitted when empty), digests | `internal/moves/evidence.go:11-20`, `:38-53` | |
| | a clean rollback: `src_covered` and unchanged digests; the "clean ..., src_covered ..." line; `dirty` and `evidence` written at released | `internal/host/movesrollback.go:394-398`, `:521-526` | |
| +| the rollback's `id` | `internal/provision/provision.go:1068` | |
| +| a dirty rollback's return lane is `moves/<host>/<rb>`, parked in the source's region, queued when the source has probed, horizon the return seal's bound | `internal/moves/parkscope.go:66-72`; `internal/host/movesrecover.go:658-675`; `internal/hostclaim/move.go:1093-1108` | |
| +| an obligation's `park.location`, `park.evidence_at`, `region` | `internal/hostclaim/claim.go:116-121`, `:137-147` | |
| +| a park runs only after `evidence_at` plus the signature age (15 min) and the request limit (2 min) | `internal/park/remote.go:21`, `:516-519`; `internal/moves/parkscope.go:23` | |
| **The door and the fleet** |
| @@ -738,11 +863,14 @@ copy is `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`. |
| | the claim at `control/hosts/<host>.json`; `holder.location`, `obligations`, `quiet_after` | `internal/hostclaim/claim.go:56`, `:166`, `:171`, `:182` | |
| | T12 reads `quiet_after`; H is 15 min plus 1 min skew | `internal/host/movesreport.go:756-762`; `internal/moves/lease.go:13`, `:22`; spec:2573 | |
| | the console's claim recovery pass adopts with the claim on | `internal/console/hostclaimbackfill.go:40-48` | |
| -| the roster at `control/host-actors.json`; `boxes`, `state`, `enrollment`, `loss_ids`; a declaration quarantines | `internal/hostactors/roster.go:21`, `:30`, `:38-40`, `:72`, `:265-279` | |
| +| the roster at `control/host-actors.json`; `boxes`, `state`, `enrollment`, `quarantine_revision`, `loss_ids`; a declaration quarantines and adds one to the revision | `internal/hostactors/roster.go:21`, `:30`, `:38-40`, `:72`, `:265-279` | |
| +| the roster is written as a root-signed envelope `{payload, sig}`, the payload base64 JSON | `internal/hostactors/roster.go:249`; `internal/release/rootenvelope.go:29-44`; `internal/release/release.go:377-379` | |
| +| a termination fence is `control/box-fences/<enrollment>.json`; no object, no fence | `internal/hostactors/fence_authority.go:14-34` | |
| | a quarantined box is refused admission | `internal/hostquarantine/quarantine.go:17-25` | |
| | `CARLOS_HOST_CLAIM` is any non-empty value | `cmd/console/main.go:1364-1366`; `hostclaim_cli.go:22` | |
| | `carlos ops host` exists only with `CARLOS_HOST_CLAIM` in the shell; `--readmit`, `--console`, the `readmit` prompt | `hostclaim_cli.go:35-39`, `:61-75`, `:143` | |
| | readmit refuses a fenced box and a private report older than 15 min | `internal/hostresolve/readmit.go:18-40` | |
| +| readmit needs the box's complete retirement receipt for this boot at `control/host-retirement/<box>/<enrollment>/<revision>.json`, then sets the box `active` | `internal/hostresolve/readmit.go:52-66`, `:134`; `internal/hostquarantine/quarantine.go:72-82`, `:91-96` | |
| **The floor, the switch and the boxes** |
| @@ -758,7 +886,8 @@ copy is `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`. |
| | the copy gate keeps the default off while a row is pending | `approved_moves_copy_test.go:811` | |
| | the default cut window, 120 s | `internal/moves/budget.go:17` | |
| | `CARLOS_LAYOUT_DEFAULT=flat` | `internal/host/layoutleg.go:68-70`; `system.go:714` | |
| -| `carlos system status`, `carlos system layout status` | `system.go:75-76`, `:611-641`; `README.md:272` | |
| +| `carlos system status` | `system.go:75-76`; `README.md:272` | |
| +| `carlos status --layout` shows each host's actual layout, box by box, and marks a box "(nested by default)"; `carlos system layout status` shows only the recorded intent | `cli.go:3528`; `status_instances.go:142-163`, `:224`; `system.go:709-716` | |
| | the agent is `carlos-edge.service`, reading `/etc/carlos/host.env`, with a delegated cgroup | `internal/host/units/carlos-edge.service:18-19`, `:101` | |
| | "exec-child accounting armed"; no manager, no stop evidence | `internal/cgroup/cgroup.go:181-184`; `internal/host/movespass.go:376-392` | |
| | "not proven stopped yet ... retrying until the cut's deadline" | `internal/host/movescut.go:422-427` | |
| @@ -800,4 +929,5 @@ copy is `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`. |
| |---|---| |
| | the runs and the pass rule | spec §14.4, spec:6263-6276 | |
| | the rollout order, the floor before any protected production rehearsal | spec §15, spec:6278-6294 | |
| +| each rehearsal gates the next, so Run 5 needs Run 4 | spec:6294; controller ruling, fix round 1 | |
| | the flagship's home is Ireland | spec:1032 | |