docs: the Phase 1 rehearsal runbook in M12's order (§14.4, §15)
Written against main per the Task 3 rulings: every command from move.go with --app moveprobe --account <ops>, every event from transition.go, and each check naming its read path (the CLI's sentence and exit code, detail=1, the deployment or data bucket with a named profile, the probe's report, or the box). P-0 lists what every run waits for (claim Plan 12, its moves dependants, the cut guard, the home parks fix, all released and rolled); each run states its blockers at its top. Rehearsal hosts are created before the claim and never reused; dev's second region and home ordering, the flagship's env switch on non-home regions, and neutral wording for the customer runs follow Paul's rulings. A Sources appendix cites file:line for each fact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+803
−0
docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md+803 −0
diff --git a/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md b/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md| new file mode 100644 |
| index 000000000..4d12a9359 |
| --- /dev/null |
| +++ b/docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md |
| @@ -0,0 +1,803 @@ |
| +# Moves Phase 1: the rehearsal runbook |
| + |
| +This is the checklist an operator follows to rehearse Phase 1 moves (spec |
| +§14.4, rollout §15). It covers seven runs in the spec's order: four on dev, |
| +one on the flagship with the probe app, one on a disposable customer-shaped |
| +instance, and one on a real customer instance. |
| + |
| +**No run in this runbook can execute today.** Every run waits for P-0 |
| +below. Each run also has its own blockers, listed at its top. Never run one |
| +without Paul's go in the amadan thread. |
| + |
| +The probe app is `moveprobe`. How to build it, ship it and use it is in |
| +[`cmd/moveprobe/README.md`](../../../cmd/moveprobe/README.md). This runbook |
| +does not repeat it. |
| + |
| +Every command, flag, event, field and key below was checked against main, |
| +and moveprobe's against this branch. |
| +The Sources section at the end gives file and line for each. |
| + |
| +--- |
| + |
| +## How to read this runbook |
| + |
| +### Placeholders |
| + |
| +| placeholder | what it is | |
| +|---|---| |
| +| `<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>` | |
| +| `<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. | |
| +| `<move>` | the move id the console gave the request (`id` in the flat view) | |
| +| `<inc>` | the instance incarnation (`detail.move.incarnation`) | |
| + |
| +### Read paths |
| + |
| +Each check says where to read it. |
| + |
| +- **[CLI]**: the sentence `carlos move` prints and its exit code. Exit codes: |
| + 0 done; 3 rolled back or aborted; 4 waiting (ready for a manual cut, |
| + stuck, a release past its deadline, relocating, or an outcome not |
| + 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: |
| + |
| + ```sh |
| + CONSOLE=<console> |
| + 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 <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`. |
| +- **[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`, |
| + `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 |
| + `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>/" |
| + aws s3 ls --profile <profile> "s3://<deploy-bucket>/${P}log/" |
| + aws s3 cp --profile <profile> "s3://<deploy-bucket>/${P}log/done.json" - | jq . |
| + ``` |
| + |
| +- **[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 |
| + lane under `moves/<host>/<move>/`, and parked objects under |
| + `parked/<inc>/<host>/<retirement>/` with a `manifest.json`. These buckets |
| + are versioned, so list versions, not objects: |
| + |
| + ```sh |
| + aws s3api list-object-versions --profile <profile> --bucket <data-bucket-R> --prefix "instances/<host>/" |
| + ``` |
| + |
| +- **[report]**: moveprobe's own report, with the secret from your shell: |
| + |
| + ```sh |
| + curl -fsS -H "Authorization: Bearer $MOVEPROBE_SECRET" https://<host>/report | jq . |
| + ``` |
| + |
| +- **[box]**: on a box, the agent's journal (`journalctl -u carlos-edge`), |
| + `/etc/carlos/host.env`, and files under the data directory (`/data`). |
| + |
| +### Log events |
| + |
| +Each transition writes one log object, keyed by its identity. The ones the |
| +checks use: |
| + |
| +| identity (the key) | event (the `event` field) | when | |
| +|---|---|---| |
| +| `requested` | `requested` | the request was journaled | |
| +| `ready` | `ready` | the target's checks passed | |
| +| `cutting-requested` | `cut_requested` | the cut was asked for | |
| +| `cutting-<phase>` | `<phase>` | `admitted`, `stopping`, `stopped`, `staged`, `archived` | |
| +| `cutting-flipped` | `flipped` | the record now names the target | |
| +| `landing-<phase>` | `<phase>` | the target's landing steps | |
| +| `probe_attempt-<n>` | `probe_attempt` | the landing's n-th readiness probe | |
| +| `committed` | `committed` | the landing committed | |
| +| `committed-opened` | `opened` | the target opened (`offline_s` in its fields) | |
| +| `committed-source_parked` | `source_parked` | the source parked what it held | |
| +| `rolling_back` | `rolling_back` | a rollback started (`reason` in its fields) | |
| +| `rolling_back-target-<phase>` | `<phase>` | `released`, `returning`, `returned` (a dirty rollback only), `cleaning`, `cleaned` | |
| +| `rolling_back-target-lost` | `declared_lost` | the target was declared lost | |
| +| `rolling_back-source-<phase>` | `<phase>` | the source's recovery, up to `recovered` and `opened` | |
| +| `stuck-<epoch>`, `retry-<epoch>` | `stuck`, `retry` | a stuck move and its retry | |
| +| `done`, `rolled_back`, `aborted` | the same | the close; `done` and `rolled_back` carry the timings | |
| + |
| +Every entry's `fields` carry `from`, `to` and, once known, `final_txid`, |
| +`db_sha256`, `timers_sha256`, `files_plain`, `installed_db_sha256`, |
| +`release`, `env_report` and `cut_budget_s`. The close's timings are |
| +`prepare_s`, `cut_s`, `cut_budget_s`, `land_s`, `land_budget_s`, |
| +`offline_s`, `rollback_s` and `release_budget_s`. There is no `stray` |
| +event and no `dirty` field in the log. |
| + |
| +--- |
| + |
| +## P-0: what every run waits for |
| + |
| +Nothing below P-0 runs until every item here is true. Paul confirms P-0 per |
| +deployment before its first run. |
| + |
| +- [ ] **Claim Plan 12 merged.** Without it the landing, the target's half |
| + of a rollback and the source's recovery are unarmed in production, so a |
| + move with the switch on stops the source, flips, and then has nothing to |
| + land it, roll it back or recover it. |
| +- [ ] **Moves Plan 5 Task 9b, Plan 4 Tasks 9 and 10 (the box half), and |
| + Plan 5 Task 8 merged.** They wire moves onto claim Plan 12. Run 4 also |
| + needs claim Plan 12's loss-bound admission. |
| +- [ ] **The moves cut guard merged** (branch `moves-cut-guard`, Paul's |
| + ruling of 2026-10-11). The source refuses a cut unless the target |
| + reports its landing armed. It adds a `T13` check to the target's report |
| + and records why a cut did not start. It is not on main yet. |
| +- [ ] **For any run that touches the home region: the home-region parks fix |
| + part 1 merged** (#236). Part 1 covers moves out of home. Moves into home |
| + need part 2, and the door refuses them until then ("moves to the home |
| + region are not supported yet. the home publishes no staging lane"). |
| +- [ ] **All of the above released and rolled, consoles first, then every |
| + box.** Check every box with `carlos system status --console <console>`: |
| + each reads `current` on the new version. |
| +- [ ] **Each deployment's home region recorded:** `CONSOLE_HOME_REGION` in |
| + the console's environment. A box with no `CARLOS_REGION` in its |
| + `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`. |
| +- [ ] **Never set `CARLOS_MOVES_PHASE1=on` anywhere before P-0 is true.** |
| + |
| +--- |
| + |
| +## Preconditions, once per deployment |
| + |
| +### P-1 The dormant release |
| + |
| +- [ ] The release carrying P-0 is rolled with `CARLOS_MOVES_PHASE1` unset |
| + everywhere: consoles first, then boxes. |
| +- [ ] [deploy] Every box's `control/edges/<box>.json` has |
| + `"moves_protocol": 1`. |
| + |
| +### P-2 Staging grants (§15 step 5, scoped tofu by Paul) |
| + |
| +- [ ] Every rehearsal region's `sibling_staging_arns` lists every other |
| + rehearsal region's data bucket, and each box has the versioned-object |
| + grants (`s3:ListBucketVersions`, `s3:GetObjectVersion`, |
| + `s3:DeleteObjectVersion` on its own bucket; `s3:GetObjectVersion` for |
| + the console). |
| +- [ ] Check after the first request reaches ready: [detail] |
| + `detail.target_report.checks.T10.ok` is `true` (the lane probe). |
| + |
| +### P-3 The protocol floor |
| + |
| +Publish the floor only after **every box and the console run a tagged |
| +`vX.Y.Z` release** at or above `min_release`. Once a floor is armed, any |
| +build that is not `vX.Y.Z` (a SHA build, `dev`, a prerelease) is refused at |
| +activation, and the door treats that box as below the floor. A box never |
| +lowers its local floor on its own. If dev keeps rolling SHA builds, dev's |
| +runs go without a floor, and P-3 is done on the flagship only, before |
| +Run 5 (§15 step 6). |
| + |
| +- [ ] Write it create-only from an operator shell. No `carlos` command |
| + writes it (a gap to note on an amadan discussion). |
| + |
| + ```sh |
| + printf '{"min":1,"min_release":"vX.Y.Z"}' > floor.json |
| + aws s3api put-object --profile <profile> --bucket <deploy-bucket> \ |
| + --key control/moves-protocol.json --body floor.json --if-none-match '*' |
| + aws s3 cp --profile <profile> s3://<deploy-bucket>/control/moves-protocol.json - |
| + ``` |
| + |
| +- [ ] [deploy] Every box's edges file shows `moves_floor_min_release` equal |
| + to the floor's `min_release`, and either `"prev_release": "none"` or |
| + `prev_allowed_for` equal to `min_release`. A box that does not is below |
| + the floor for the door (MV-N5). |
| + |
| +### P-4 Ship moveprobe |
| + |
| +- [ ] Follow `cmd/moveprobe/README.md` ("Build" and "Deploy") under the ops |
| + account: ship, enable instances with private storage off, create the |
| + store with `--env-prefix MOVEPROBE` and no `--environment`, set |
| + `MOVEPROBE_SECRET`, and set the `every-minute` schedule. |
| +- [ ] Keep `MOVEPROBE_SECRET`'s value in your shell. `/report` and |
| + `/arm-once` need it. |
| +- [ ] Leave `moves.ready_path` unset. A move then probes `/`, which answers |
| + as `/ready` does. |
| + |
| +### P-5 Rehearsal hosts, created before the claim |
| + |
| +A host created while `CARLOS_HOST_CLAIM` is on keeps its data under a |
| +per-incarnation path, and the move door refuses it with MV-N167: "the |
| +instance keeps its data under a per-incarnation path, which moves cannot |
| +cut yet". So every rehearsal host is created **before** the claim goes on |
| +for its deployment (Paul, N2(a)). |
| + |
| +- [ ] Before `CARLOS_HOST_CLAIM` goes on, create one moveprobe host per |
| + planned run, in the region that run starts from, plus two spares: |
| + |
| + ```sh |
| + carlos instances create --app moveprobe --account <ops> --host <label>.<instance domain> --region <region> |
| + ``` |
| + |
| + On the flagship, also create Run 6's disposable customer-shaped |
| + instance now. |
| +- [ ] Record the list of hosts, with the run each one is for, in the |
| + deployment's rehearsal record (outside this repo). **Never reuse a |
| + host.** A host that has run once is spent, whatever the outcome. |
| +- [ ] After the claim is on, check each host is adopted: [deploy] |
| + `control/hosts/<host>.json` exists, `holder.location` is |
| + `instances/<host>` (not `instances/<host>/<inc>`), and `op.kind` is |
| + `serving`. The console's claim recovery pass adopts them. |
| + |
| +### P-6 The rest of the fleet's preconditions |
| + |
| +Paul confirms each of these per deployment before its first run. |
| + |
| +- [ ] **Claim on.** `CARLOS_HOST_CLAIM` is set (any non-empty value) in |
| + 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. |
| +- [ ] **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] |
| + `control/boxes/<box>.json` exists for each rehearsal box, and its edges |
| + file has `"capabilities": ["region-ha-1"]`. |
| +- [ ] **One eligible box per rehearsal region.** Any other box in a |
| + rehearsal region runs without `CARLOS_REGION_HA`, so it publishes no |
| + `region-ha-1` and is not eligible. Otherwise the door answers M-C21. |
| +- [ ] **Secrets key.** `CARLOS_SECRETS_KEY` on every rehearsal box, from |
| + the same key pair as the console's. Check: [deploy] each edges file's |
| + `secrets_key_fp` is the same; once the switch is on, it equals |
| + `secrets_key_fp` in `control/moves-console.json`; [detail] |
| + `detail.target_report.checks.T2.ok` is `true`. |
| +- [ ] **Staging bucket.** Each rehearsal box's edges file has |
| + `data_bucket` set. |
| +- [ ] **Alert sinks.** `CARLOS_ALERT_WEBHOOK` or |
| + `CARLOS_ALERT_SNS_TOPIC_ARN` is set on every rehearsal box and on the |
| + console. Without one, pages go nowhere. |
| +- [ ] **Cgroup delegation.** Check: [box] `journalctl -u carlos-edge -b | |
| + grep 'exec-child accounting'` shows `armed (delegated subtree ...)`. A |
| + box without it cannot produce stop evidence and never cuts. |
| +- [ ] **Flat rows and tagged releases**, as P-0 and P-3 say. |
| + |
| +### P-7 The switch |
| + |
| +- [ ] Only after P-0 to P-6: `CARLOS_MOVES_PHASE1=on` in the environment |
| + of the console and of each box in play, then restart them. |
| +- [ ] Check: `carlos move status --app moveprobe --account <ops> --host |
| + <host>` does not say "Moves are not enabled on this console." |
| + |
| +--- |
| + |
| +## Between runs |
| + |
| +Before starting the next run, wait until all of these hold for the host |
| +the last run used: |
| + |
| +- [ ] [detail] `state` is `none` (no move in flight) and `last.outcome` is |
| + recorded. |
| +- [ ] [detail] `detail.claim.kind` is `serving`. |
| +- [ ] [deploy] the claim's `quiet_after` for the region the next move |
| + goes to has passed (T12). It is set when an instance leaves a region and |
| + lasts about 16 minutes. With a fresh host per run it rarely bites, but |
| + check it. |
| +- [ ] `MOVEPROBE_FAIL_READY_IN` is unset: `carlos env list --app moveprobe |
| + --account <ops>` does not show it. |
| + |
| +--- |
| + |
| +## The steps and checks each run uses |
| + |
| +Runs refer to these by number. `<from>` and `<to>` are the run's regions. |
| + |
| +### Steps |
| + |
| +- **S-1 Wake and record.** `curl https://<host>/ready` answers `ready`. |
| + Save [report] as `before-request.json`. |
| +- **S-2 Request.** |
| + |
| + ```sh |
| + carlos move --app moveprobe --account <ops> --host <host> --to <to> --manual-cut |
| + ``` |
| + |
| + [CLI] M-C1 ("Moving <host> from <from> to <to>."), M-C2, M-C3, then |
| + M-C12 ("Ready to cut. ..."), exit 4. A line "<to> cannot take this move |
| + yet: <check>." is M-C4 and names the failing check. |
| + [detail] at ready: every `detail.target_report.checks.T1` to `T12` has |
| + `ok: true` (and `T13` once the cut guard lands); |
| + `detail.target_report.attempts_admitted` is at least 1; |
| + `detail.source_report.s1.ok` and `s2.ok` are `true`. Record `id` as |
| + `<move>` and `detail.move.incarnation` as `<inc>`. |
| +- **S-3 Record and arm.** Save [report] as `before-cut.json`. Arm the |
| + once-timer so it falls due around the cut, then cut within a few |
| + seconds: |
| + |
| + ```sh |
| + curl -fsS -X POST -H "Authorization: Bearer $MOVEPROBE_SECRET" "https://<host>/arm-once?in=30" |
| + ``` |
| + |
| + Record the answer's `name` and `due_at`. |
| +- **S-4 Cut.** |
| + |
| + ```sh |
| + carlos move cut --app moveprobe --account <ops> --host <host> |
| + ``` |
| + |
| + The expected sentences and exit code are in each run. |
| + |
| +### Checks after a commit |
| + |
| +- **R-1 State arrived.** [report] on the target: `region` is `<to>`; |
| + `file_key_sha256` and `platform_key_sha256` equal `before-cut.json`'s; |
| + `object` has no `error` and its `body` names this boot and `<to>`; |
| + `has_secret` is `true`. `curl https://<host>/ready` answers `ready`. |
| +- **R-2 No row lost.** [report] for `bg` and for `worker`: the sum of |
| + `count` over all regions is at least `before-cut.json`'s, and equals the |
| + highest `max_seq` (rows are numbered from 1 in one sequence per kind, so |
| + any gap shows here). |
| +- **R-3 The schedule continues.** [report] `rows` has `kind: tick` with |
| + `region: <to>`, and its `count` rises about once a minute for three |
| + minutes after the open. Every `fired` entry has `count: 1`. A minute |
| + that fell due while the instance was offline fires at most once after |
| + the open, and minutes in between are not replayed, so a short gap |
| + around the cut is expected. A due time that fired twice fails the run. |
| +- **R-4 The once-timer fired once.** [report] `fired` has exactly one |
| + entry with S-3's `name`, its `due_at` equals S-3's, and its `count` is 1. |
| +- **R-5 The source holds nothing live.** After [deploy] |
| + `log/committed-source_parked.json` and `log/done.json` exist: |
| + [data] `list-object-versions` on `<data-bucket-from>` under |
| + `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 |
| + 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. |
| + The moveprobe worker outlives its app on purpose, so a worker that |
| + survived the stop fails the cut's tree step and shows here, then |
| + reverts at the cut's deadline. It does not page. |
| + |
| +### Checks after a rollback (Runs 2, 3 and 4) |
| + |
| +R-1 to R-4 and R-6 apply as written, with `<from>` (the source, where the |
| +instance is back) in place of `<to>`. R-5 is replaced: |
| + |
| +- **R-5b The target holds nothing live.** After [deploy] |
| + `log/rolling_back-target-cleaned.json` and `log/rolled_back.json` exist: |
| + [data] `list-object-versions` on `<data-bucket-to>` under |
| + `instances/<host>/` and under `moves/<host>/<move>/` returns no versions |
| + and no delete markers. |
| +- **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. |
| + |
| +A clean rollback needs two things: the stopped target's digests equal |
| +what the landing installed, and `src_covered` (the source's own replica |
| +reached the stopped position). The target box logs both: |
| +[box] `journalctl -u carlos-edge | grep 'the target of rollback'` shows |
| +"... stopped at TXID <n> (clean <bool>, src_covered <bool>)". |
| + |
| +--- |
| + |
| +## Dev (Runs 1 to 4) |
| + |
| +**Dev topology (Paul, N4).** Dev's second region is applied: `uk`, |
| +`eu-west-2`, box `carlos-dev-uk`, data bucket |
| +`carlos-dev-uk-640012953448` (platform-infrastructure#42). Before Run 1: |
| + |
| +- [ ] Bootstrap `carlos-dev-uk`. |
| +- [ ] Copy the SSM parameters a dev box reads at boot to `eu-west-2`. |
| +- [ ] `CARLOS_REGION_HA=true` on `carlos-dev-ie` and `carlos-dev-uk`, and |
| + `CARLOS_AWS_ACCOUNT` on the dev console. |
| +- [ ] Keep `carlos-dev-ie-b` ineligible: no `CARLOS_REGION_HA` in its |
| + `host.env`, so `ie` has one eligible box (M-C21). |
| +- [ ] Create the dev rehearsal hosts (P-5) after `carlos-dev-uk` serves and |
| + before dev's claim goes on. |
| + |
| +**Dev's home region.** Dev Ireland is probably the home region (its box |
| +has no `CARLOS_REGION`). Confirm it with the dev console's |
| +`CONSOLE_HOME_REGION` and record it here: `____`. If `ie` is home: |
| + |
| +- Run 1 (`ie` to `uk`) moves out of home and needs **home fix part 1**. |
| +- Run 4 (`ie` to `uk`) moves out of home and then rolls back into it. It |
| + needs **part 1**, and part 1 must cover a rollback whose source is home. |
| +- Runs 2 and 3 as written (`uk` to `ie`) move into home and need **home |
| + fix part 2**. The door refuses them until then. |
| +- **Until part 2 lands, only out-of-home moves run.** Run 1 goes ahead on |
| + part 1. Runs 2 and 3 wait. If Paul wants them sooner, they can run out |
| + of home (`ie` to `uk`, with the failure switch on `uk`), which rolls back |
| + into a home source, as Run 4 does. Use that order, and run Run 4, only |
| + if part 1's own tests show it covers a rollback whose source is home. |
| + Part 1 is not built yet, so this is open. |
| +- On the home box, moveprobe sees its region as `home`, not `ie`. Any |
| + `MOVEPROBE_FAIL_READY_IN` aimed at that box names `home`. |
| + |
| +If `ie` is not home, the runs go as written and need no home fix. |
| + |
| +### Run 1: dev, `ie` to `uk`, a commit |
| + |
| +**Blockers:** P-0; home fix part 1 if `ie` is home; dev topology above; P-1 |
| +to P-7 on dev; Paul's go. |
| + |
| +- [ ] Host used: `____` (created in `ie` before the claim). |
| +- [ ] S-1, S-2 with `--to uk`. |
| +- [ ] S-3, S-4. [CLI] M-C5 ("uk is ready. Starting the cut. <host> will be |
| + offline for about <estimate>."), M-C6 ("Stopped in ie. Starting in |
| + uk."), M-C7 ("Moved. <host> is served from uk. It was offline for |
| + <seconds> seconds."), exit 0. |
| +- [ ] R-1 to R-6. |
| +- [ ] [deploy] `log/` has `cutting-flipped`, `committed`, |
| + `committed-opened`, `committed-source_parked` and `done`. [detail] |
| + `last.outcome` is `done`; `detail.timings` holds `offline_s` and |
| + `cut_budget_s`. |
| + |
| +### Run 2: dev, forced clean rollback |
| + |
| +**Blockers:** P-0; home fix **part 2** if `ie` is home (see above); Run 1 |
| +passed; Paul's go. |
| + |
| +The target fails its readiness check in before-write mode, so it writes |
| +nothing and the rollback should be clean. |
| + |
| +- [ ] Host used: `____` (created in `uk` before the claim). |
| +- [ ] Before the request, set the switch for the target's region (`ie`, or |
| + `home` if `ie` is home): |
| + |
| + ```sh |
| + carlos env set --app moveprobe --account <ops> MOVEPROBE_FAIL_READY_IN=ie |
| + ``` |
| + |
| + 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`. |
| +- [ ] 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`. |
| +- [ ] [box] on the `ie` box: "clean true, src_covered true". |
| +- [ ] R-1 to R-4 and R-6 on `uk`, R-5b on `ie`. |
| +- [ ] [deploy] `log/` has `rolling_back`, `rolling_back-target-released`, |
| + `rolling_back-target-cleaned`, `rolling_back-source-opened` and |
| + `rolled_back`, and no `rolling_back-target-returning`. |
| +- [ ] After the checks are recorded: |
| + |
| + ```sh |
| + carlos env unset --app moveprobe --account <ops> MOVEPROBE_FAIL_READY_IN |
| + ``` |
| + |
| +A dirty result here, with moveprobe in before-write mode, is a platform |
| +finding, not a probe failure. Stop the series and report it. |
| + |
| +### Run 3: dev, forced dirty rollback |
| + |
| +**Blockers:** P-0; home fix **part 2** if `ie` is home; Run 2 passed; |
| +Paul's go. |
| + |
| +The target writes one row, starts its worker, and then fails readiness, so |
| +the rollback is dirty and the target's writes must come back. |
| + |
| +- [ ] Host used: `____` (created in `uk` before the claim). |
| +- [ ] Before the request: |
| + |
| + ```sh |
| + 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`. |
| +- [ ] Digests: [deploy] `stopped-target.json`'s `db_sha256` differs from |
| + `log/rolling_back-target-released.json`'s `installed_db_sha256` (the |
| + target wrote). |
| +- [ ] [box] on the `ie` box: "clean false". |
| +- [ ] [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. |
| +- [ ] Unset `MOVEPROBE_FAIL_READY_IN` once the checks are recorded. |
| + |
| +### Run 4: dev, declare-lost |
| + |
| +**Blockers:** P-0, including claim Plan 12's loss-bound admission and Plan |
| +4 Task 10's box half; home fix part 1 if `ie` is home; Run 1 passed (Runs 2 |
| +and 3 too, if part 2 has landed); Paul's go, and his agreement to take |
| +`carlos-dev-uk` out of service for the run. |
| + |
| +The target box is stopped mid-move and declared lost. With only two dev |
| +regions, the target is `carlos-dev-uk`, and `uk` has no eligible box until |
| +the readmit at the end. **Stop the box. Do not terminate it.** A terminated |
| +box cannot be readmitted. |
| + |
| +- [ ] Host used: `____` (created in `ie` before the claim). |
| +- [ ] So the landing cannot commit before the box stops, set the failure |
| + switch on the target first: |
| + |
| + ```sh |
| + carlos env set --app moveprobe --account <ops> MOVEPROBE_FAIL_READY_IN=uk |
| + ``` |
| + |
| + 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. |
| +- [ ] 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): |
| + |
| + ```sh |
| + carlos move resolve --app moveprobe --account <ops> --host <host> --declare-lost carlos-dev-uk |
| + ``` |
| + |
| + [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. |
| +- [ ] 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] |
| + `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. |
| +- [ ] 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. |
| +- [ ] Readmit it. `carlos ops host` exists only with `CARLOS_HOST_CLAIM` |
| + set in your shell: |
| + |
| + ```sh |
| + CARLOS_HOST_CLAIM=1 carlos ops host resolve <host> --readmit carlos-dev-uk --console <console> |
| + ``` |
| + |
| + 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. |
| + |
| +--- |
| + |
| +## Flagship (Run 5) |
| + |
| +### Run 5: the flagship, Runs 1 to 3 again |
| + |
| +**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. |
| + |
| +- **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 |
| + compiled default stays off. It flips later, in its own commit. |
| +- **Regions** (N3): the flagship's home is Ireland, so Run 5 uses two |
| + non-home regions, `uk` and `us`, until the home fix parts land. Run 1's |
| + shape goes `uk` to `us`. Runs 2 and 3 go `us` to `uk`, with |
| + `MOVEPROBE_FAIL_READY_IN=uk` and `uk,after-write`. |
| + |
| +- [ ] Hosts used: Run 1 shape `____` (created in `uk`), Run 2 shape `____` |
| + and Run 3 shape `____` (created in `us`), all before the flagship's |
| + claim went on. |
| +- [ ] Each of the three as on dev, with every check. |
| +- [ ] After the third, remove `CARLOS_MOVES_PHASE1` from the two boxes and |
| + the console unless Paul says to keep it. |
| + |
| +--- |
| + |
| +## Customer instances (Runs 6 and 7) |
| + |
| +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. |
| + |
| +### Run 6: a disposable customer-shaped instance |
| + |
| +**Blockers:** P-0; **the home-region parks fix, both parts, merged and |
| +rolled** (customer instances live in the home region); a dev rehearsal |
| +from home passed once the fix is in; Run 5 passed; the owner's agreement; |
| +Paul's go. |
| + |
| +A disposable instance of a customer app with the same shape as moveprobe |
| +(a key file, a store, a sealed secret), created for the run with the |
| +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. |
| + |
| +### Run 7: the first production customer instance |
| + |
| +**Blockers:** everything Run 6 needs; Run 6 passed; Paul's approval of the |
| +instance; a window agreed with its owner. |
| + |
| +The first production customer instance Paul approves, in a window agreed |
| +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. |
| +- [ ] Report `offline_s` back to the owner: [detail] `last.offline_s`, or |
| + `offline_s` in `log/done.json`'s fields. |
| + |
| +--- |
| + |
| +## After every run |
| + |
| +- [ ] Attach to the amadan thread: the `done` or `rolled_back` log entry |
| + (its timings), the [report] files from before and after, the bucket |
| + listings, and the host used. |
| +- [ ] A failed check stops the series. Fix its cause and repeat the run on |
| + a fresh host before going on. |
| + |
| +--- |
| + |
| +## Sources |
| + |
| +Paths are from the repo root. Line numbers are main's (`bc25674a`, the |
| +base of this branch); `cmd/moveprobe` is this branch's and not on main |
| +yet. Spec is `docs/superpowers/specs/2026-10-02-move-any-app-design.md`; |
| +copy is `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`. |
| + |
| +**`carlos move` (the CLI)** |
| + |
| +| what the runbook says | where | |
| +|---|---| |
| +| verbs `status`, `cut`, `abort`, `resolve`; flags `--host`, `--app`, `--account`, `--console` | `move.go:136-149` | |
| +| `--to`, `--manual-cut`, `--no-wait` on a request; `--wait` on status; `--retry`, `--declare-lost`, `--no-wait` on resolve | `move.go:151-163` | |
| +| exit codes 0, 1, 2, 3, 4, 5 | `move.go:40-45`, `:53-61` | |
| +| "carlos move needs --app" | `move.go:66`, `:230` | |
| +| "Moves are not enabled on this console." | `move.go:65`, `:240-243` | |
| +| refuses to run as root; declare-lost reads EC2 with your own credentials | `move.go:211-217`, `:80`, `:900-903` | |
| +| the request sends `region` and `cut` | `move.go:248-253` | |
| +| M-C2, M-C3, M-C4 | `move.go:703`, `:707`, `:717` | |
| +| M-C12 at ready with a manual cut, exit 4 | `move.go:722-726` | |
| +| M-C10, exit 4 | `move.go:740-754` | |
| +| M-C7 on done, M-C9 on rolled_back, exit 3 | `move.go:788-803` | |
| +| M-C18 and the typed host name | `move.go:860-882` | |
| +| "No move is in flight" | `move.go:595` | |
| +| M-C1, M-C5, M-C6, M-C7, M-C8, M-C9 texts | `internal/moves/operatorcopy.go:11-38`; copy record rows M-C1 to M-C22, `copy:24-45` | |
| +| the detail path, `/api/cli/apps/<acct>/<app>/instances/<host>/move?detail=1` | `move.go:230-232`, `:351-356`; `apimode.go:460-461` | |
| +| login tokens in `~/.carlos/credentials`, keyed by `console` | `auth.go:113-120`, `:139-148` | |
| + |
| +**The detail answer** |
| + |
| +| what | where | |
| +|---|---| |
| +| detail=1 only for operators, only with the switch on | `internal/console/apiinstancemove.go:312-327` | |
| +| 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` | |
| +| `last` fields `id`, `outcome`, `at`, `offline_s` | `internal/provision/provision.go:1059-1064` | |
| +| `move.incarnation` | `internal/provision/provision.go:961` | |
| +| `rollback.dirty`, `target_phase`, `evidence`, `loss_ref`, `lost_at` | `internal/provision/provision.go:1067-1088` | |
| +| `landing.installed_db_sha256` | `internal/provision/provision.go:1052` | |
| +| `target_report.checks` T1 to T12, `attempts_admitted`, `cut_budget_s` | `internal/moves/readiness.go:18-31`, `:58-67` | |
| +| `source_report.s1`, `s2`; S1 refuses a nested instance | `internal/moves/readiness.go:102-109`; `internal/host/movesphase1.go:41`, `:537` | |
| + |
| +**The log and the buckets** |
| + |
| +| what | where | |
| +|---|---| |
| +| identities and events: `requested`, `cutting-requested`/`cut_requested`, `cutting-flipped`/`flipped`, `committed-opened`/`opened`, `committed-source_parked`/`source_parked`, `rolling_back`, `rolling_back-target-<phase>`, `rolling_back-target-lost`/`declared_lost`, `rolling_back-source-<phase>`, `stuck-<epoch>`, the closes; the state as the event for `ready` and `committed` | `internal/moves/transition.go:122-253` | |
| +| `retry-<epoch>` | `internal/host/movesevents.go:109` | |
| +| the fields every entry carries | `internal/moves/transition.go:42-71` | |
| +| the timing keys | `internal/moves/log.go:126-133` | |
| +| target phases `releasing`, `released`, `returning`, `returned`, `cleaning`, `cleaned`, `lost`; source phases up to `recovered`, `opened` | `internal/provision/provision.go:1148-1172` | |
| +| released goes to returning only when dirty, else to cleaning | `internal/host/movesrollback.go:566-575` | |
| +| report prefix `moves/<acct>/<app>/<host>/<op>/`, `source.json`, `target.json`, `stopped-<role>.json`, `log/<identity>.json` | `internal/moves/staging.go:39-50` | |
| +| the staging lane `moves/<host>/<op>/` in the target's data bucket | `internal/moves/staging.go:6` | |
| +| the record's account is the sqid | `internal/console/apiinstances.go:1240` | |
| +| the replica under `<CARLOS_HIBERNATE_PREFIX>/<host>` | `internal/activator/activator.go:119`, `:144-146`; `internal/host/movespass.go:471` | |
| +| 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 door and the fleet** |
| + |
| +| what | where | |
| +|---|---| |
| +| no moves into home, and its sentence | `internal/console/apiinstancemove.go:537-540` | |
| +| a region with no staging bucket is refused | `internal/console/apiinstancemove.go:580-583`; staging from `data_bucket`, `internal/console/instancesplacement.go:366` | |
| +| M-C21, more than one box | `internal/console/apiinstancemove.go:595-600` | |
| +| MV-N5, below the floor | `internal/console/apiinstancemove.go:613-616`; `copy:55` | |
| +| a box below the protocol, or not armed for the floor, or with an unjudged `carlos.prev`, is below the floor | `internal/console/boxselect.go:100-108` | |
| +| MV-N167 for a holder at `instances/<host>/<inc>` | `internal/console/apiinstancemove.go:143`, `:647-651`; `copy:217` | |
| +| a created holder is at `instances/<host>/<inc>`, an adopted one at `instances/<host>` | `internal/hostclaim/create.go:75`; `internal/hostclaim/adopt.go:20` | |
| +| the source must be an active, enrolled roster entry with no termination fence | `internal/console/apiinstancemove.go:657-684`, `:1267-1273` | |
| +| eligibility: fresh, enrolled, `region-ha-1`, the console's secrets fingerprint, not draining | `internal/boxchoice/boxchoice.go:21`, `:109-148` | |
| +| `region-ha-1` and the identity record only with `CARLOS_REGION_HA` | `internal/host/edgesfile.go:156-164`; `internal/host/boxrecord.go:25-28`, `:158-166` | |
| +| `CARLOS_REGION_HA` reads only ParseBool true values, so `on` is off | `internal/host/boxrecord.go:21-28` | |
| +| identity records at `control/boxes/<box>.json` | `internal/boxid/record.go:21`, `:28` | |
| +| `CARLOS_AWS_ACCOUNT` and `CONSOLE_HOME_REGION` on the console | `cmd/console/main.go:318`, `:321` | |
| +| 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` | |
| +| 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` | |
| + |
| +**The floor, the switch and the boxes** |
| + |
| +| what | where | |
| +|---|---| |
| +| the floor key and shape, `{"min","min_release"}` | `internal/moves/floor.go:20`, `:61-64` | |
| +| `vX.Y.Z` only; anything else fails closed | `internal/moves/floor.go:85-99` | |
| +| no code writes the floor; only reads | `internal/moves/floor.go:66-82` | |
| +| a box's floor only rises; lowering it is a manual step per box | `internal/host/movesfloor.go:31-36` | |
| +| `moves_protocol`, `moves_floor_min_release`, `prev_release`, `prev_allowed_for`, `data_bucket`, `secrets_key_fp` in the edges file at `control/edges/<box>.json` | `internal/host/edgesfile.go:218`, `:270`, `:295-304`; `internal/console/instancesplacement.go:22`, `:83-86` | |
| +| `control/moves-console.json` and its `secrets_key_fp` | `internal/moves/floor.go:25-33` | |
| +| the switch, `on`/`1`/`true`, default off | `internal/moves/switch.go:6-23` | |
| +| 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` | |
| +| 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` | |
| +| M-C20 and the page outbox `<DataDir>/.moves/.page-outbox/<host>/<move>/<rule>.json`, rule `stray` | `internal/host/movespaging.go:47-50`, `:87`, `:546-554`, `:586-588` | |
| +| the tree step's "could not be stopped" | `internal/stopevidence/reconstruct_linux.go:185` | |
| +| alert sinks `CARLOS_ALERT_WEBHOOK`, `CARLOS_ALERT_SNS_TOPIC_ARN`; the console arms them only with the claim and the switch on | `internal/alerting/alerting.go:191-209`; `cmd/console/main.go:1352-1362` | |
| +| `CARLOS_SECRETS_KEY` on the box | `internal/host/movespass.go:311-316` | |
| +| `/data` is the data directory | `internal/ops/ops.go:27` | |
| + |
| +**Timers and moveprobe** |
| + |
| +| what | where | |
| +|---|---| |
| +| a held timer row is skipped while the instance moves | `internal/schedule/runner.go:190-197` | |
| +| a periodic schedule's next fire is the next boundary after now (no replay) | `internal/schedule/spec.go:119-125` | |
| +| moveprobe's region is `home` with no `CARLOS_REGION` | `cmd/moveprobe/main.go:94-100` | |
| +| `bg` and `worker` rows each numbered from 1 in one sequence | `cmd/moveprobe/main.go:543-550`, `:158`, `:674` | |
| +| `/report`'s fields, `/arm-once`'s answer, the failure switch's modes | `cmd/moveprobe/README.md`, "Use it", "Make readiness fail on purpose" and "Strings" | |
| + |
| +**Other CLI commands** |
| + |
| +| what | where | |
| +|---|---| |
| +| `carlos env set`, `unset`, `list` with `--app` and `--account` | `appconfig_cli.go:342-350`, `:408-417`, `:462-467`; `cli.go:2045-2051` | |
| +| `carlos instances create --host --region --account` | `instances_cli.go:297-310` | |
| + |
| +**Not on main, cited by name** |
| + |
| +| what | where | |
| +|---|---| |
| +| the cut guard and T13 | branch `moves-cut-guard`, commits `083a6eaa` and `6871d2e3` | |
| +| the home parks fix parts 1 and 2 | branch `moves-home-parks` (no commits yet); #236 | |
| +| claim Plan 12; moves Plan 5 Tasks 9b and 8, Plan 4 Tasks 9 and 10 | plans only; the landing, rollback and recovery are unarmed until then, `internal/host/movespass.go:335-340` | |
| +| dev's second region | platform-infrastructure#42 | |
| + |
| +**Spec** |
| + |
| +| what | where | |
| +|---|---| |
| +| 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 | |
| +| the flagship's home is Ireland | spec:1032 | |