# Moves into the home region (home-region part 2) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

Rev 4.1, 2026-10-15. Paul approved starting part 2 on 2026-10-15. Written against `main` at a5003aba. Tracking thread: amadan #236. Revs 1 to 3 (be29ccd0, c139b80b, bdbfe3fd) were reviewed by Astra, each NOT READY; rev 4 (9c2fbf01) was judged READY with one P3, fixed here. The dispositions of all four rounds are at the end. Next: Paul's rulings (R1 to R7).

**Goal:** Let a Phase 1 move name the home region as its target, under host claims, with the same guarantees a move between two named regions has. That means the commit, a clean or dirty rollback, a pre-flip revert, abort, declare-lost, and the parks in home after the commit. Today the door refuses every such move (MV-N21: "moves to the home region are not supported yet. the home publishes no staging lane").

**Architecture:** Almost all of the box and claim machinery already handles a home target. Part 1 (359e8ac2) maps every record code to an engine region once (`park.PlacementRegion`: the home's `""` is `@home`). Claim Plan 13 (a0806e4c, 2ad5412c) keeps the home box's roster name (for example `ie`) as its claim engine region. It grants the `@home` alias only when the signed roster entry equals the signed fleet home designation. It compares the two names through one rule (`regionname.SameRegion` under `op.intent.home_region`), and it keeps whichever spelling claim state already holds rather than rewriting it. `BeginAdmittedMove` already start-guards both spellings for a home target, `ClaimMoveRosterPlaces` already maps a home roster box to `@home`, and region HA's `ConvertCommittedLanding` already accepts a landing whose `to` is `@home`. What is missing is narrow:

1. **The console door.** It refuses `to == ""` outright. Behind that refusal, its staging capability (`placements()`) skips every edges file without a `placement`, which is exactly the home box, so the home never reads as stageable. Six of its sentences would print the raw code (`""`) for the home. And nothing stops a box whose `CARLOS_REGION` equals the home's name from being picked as a home target, which that box cannot serve.
2. **T3 on the home box.** `moves.CompareEnv` reports `region-missing` whenever the source sets `CARLOS_REGION` and the target does not. A home box never sets it (`agentOwnedVars` adds it only when the box has a region). So every move into home fails T3 and waits at `preparing` for ever. Moves out of home pass only because the loop runs over the source's variables.
3. **Proof through the production owners.** No test runs a move into home through the real box paths. The close harness's probe is a fake (`probeLog.wake`, `movesland_probe_test.go:73`) that calls `AdmitWriter` with a hard-coded `uk-box` and `uk`. It bypasses the production probe owners, their admission evidence and the C8 files publication, which is exactly where the home box's roster name `ie` meets the move's `@home`. The harness's held-env composer also sets `CARLOS_REGION=uk`, which would hide gap 2.

Everything else (the staging lane, the lease key, Touch, Commit, Revert, the rollback halves, the parks, the loss path) is already written region-agnostically and needs executable witnesses, not code.

**Tech Stack:** Go; `internal/console`, `internal/moves`, `internal/host` (tests), `internal/hostclaim` and `internal/hostops` (tests only).

**Spec:** move-any-app design rev 7.22 (`docs/superpowers/specs/2026-10-02-move-any-app-design.md`): §2 and §7.1 (the refusal of "a target of home", F2), §7.3 (staging lane and lease, T1 to T13), §7.6 (landing), §7.9 and §7.10 (revert, rollback, return lane, declare-lost), §16 F2 ("Moves into home": `CARLOS_HOME_PLACEMENT`, home as a target, `carlos move back`). Task 0 amends §2, §7.1 and §16. Claim rev 6.33 (Plan 13's Task I) for the home alias. Index: `2026-10-06-moves-phase1-0-index.md`; D6, D13, D14 and D15 bind every task here. Part 1: `git show 359e8ac2`. Plan 13: `docs/superpowers/plans/2026-10-12-host-lifecycle-claim-13-home-region.md` on `design-replica-ownership`.

## Global Constraints

- **Off means off.** Every change is behind `CARLOS_MOVES_PHASE1` (D13). The legacy door (switch off) keeps its own refusal, em dash and all, unchanged. With the switch on, a home target is admitted **only on a host-claim console** (`s.cfg.HostClaim` with a claim runtime). The plain Phase 1 path keeps refusing it before any write (Ruling R1).
- **One region rule.** A record code becomes an engine region only through `park.PlacementRegion`. Two engine regions are compared only through `park.SameRegion` / `regionname.SameRegion` under the operation's `home_region`, or `park.PlacementMatches` for a code against an engine region. No new `==` between a placement code and an engine region: `TestPlan13NoDirectPlacementEngineComparison` (`home_region_comparison_test.go`) must stay green.
- **Supplied arguments versus retained state.** Every region a moves call site newly hands the claim for a home target is the placement-derived `@home`, never the roster name. Claim state the move finds already written (`touched.regions`, `quiet_after` keys, earlier chores, admissions, predecessor regions) may hold either spelling, and nothing in this plan rewrites, duplicates or canonicalizes it. The first rule is proved at runtime from clean claim state, where a wrong spelling cannot hide (Task 3's `TestHomeTargetFreshStateShowsOnlyTheHomeToken`), and the reviewed set of call sites is pinned by a static inventory that proves nothing about provenance (Task 3's `TestMovesHomeRegionCallSiteInventory`). The second is checked at runtime on claim state, seeding both spellings: whatever spellings the claim held before a step are still there after it, nothing is rewritten, and no chore is duplicated (Review Focus 1).
- **Empty is unset.** `""` stays unset at every engine and claim boundary. Do not make `Validate`, `parkQueueGuard`, `AdmitWriter`, `PublishGeneration` or `Cover*Predecessors` accept it (Plan 13's constraint, kept).
- **No claim or region HA production code changes.** This plan writes tests in `internal/hostclaim` and `internal/hostops` only where they drive moves' own call sites. Anything that would change claim or region HA behaviour is an ask (below), not a task.
- **Cold only (P7, D14); one-box regions only (D15).** Nothing here relaxes either. T8 and S1 keep counting every edges file that publishes the placement. Ruling R2 decides how dev lives with that.
- **Copy.** Every new operator-facing string is a `pending` row in `2026-10-06-moves-phase1-copy.md` in the commit that writes it, and in `movesCopyIDs` (`approved_moves_copy_test.go`). Ids MV-N510 to MV-N529 are reserved for this plan (checked free on every branch on 2026-10-15). An approved template whose fill changes keeps its text and its row; only the argument passed in changes. No em dashes anywhere: a full stop and a new sentence, lower case where the door's sentences are lower case.
- **Gate.** While iterating, targeted runs only: `go test ./internal/moves ./internal/console -run 'Home|MoveDoor|CompareEnv' -count=1` and `go test ./internal/host -run 'Home|Plan13|ProbeOwners|Revert' -count=1`. Never run the whole `internal/host` suite locally; CI's `make test` is the full run (Paul, 2026-10-12). Before handing off, once: `go build ./...`, `go vet ./...`, `gofmt -l .`, the copy gate (`go test . -run 'Moves|Copy' -count=1`) and the cross-compile (`GOOS=darwin`, `GOOS=windows`, `GOOS=linux GOARCH=arm64` `go build ./...`). The Go build cache stays at its default. Use `GOFLAGS=-buildvcs=false` if the worktree's VCS metadata is unreadable from the sandbox. Tests serial and niced; no `internal/sim/hostlife` sweep.
- **Nothing rolls without Paul's go.** Merge only on amadan with `amadan branch merge -squash`, after re-merging `main` and a green CI on the tip.

## What already exists (do not rebuild)

| piece | where | what it already does for a home target |
|---|---|---|
| home's engine region | `internal/regionname`, `park.HomeRegion`, `park.PlacementRegion` | `""` maps to `@home`; `@home` is outside the placement alphabet |
| the alias | `regionname.SameRegion`, `claimRegionBinding` (`internal/host/claimregion.go`), `Roster.MatchesHome` | the roster name and `@home` are one region only under a verified designation |
| alias-aware replay | `QueueResidueParks`, `queueReplicaPark` (`TestPlan13ResidueAliasQueue`, `TestPlan13ReplicaAliasQueue`, `internal/hostclaim/plan13_followups_test.go`) | an `@home` replay of a chore already queued under `ie` changes nothing and adds nothing |
| the start CAS | `hostops.BeginClaimMove`, `ClaimMoveRosterPlaces`, `hostclaim.BeginAdmittedMove` | maps a home roster box to `@home`; writes `op.intent.to = @home` and `home_region`; start-guards both spellings (CC11 over `ie` and `@home`) |
| the home lane's lease | `stagingLeaseKey` (`internal/host/movespass.go`) | `control/staging-leases/@home/<host>` in the deployment bucket, for every caller that passes a record code |
| the source finds home's bucket | `loadMoveFleet` (`internal/host/movesreport.go`) | keeps the placementless box under `""`, so `fleet.targets[""]` is the home box's `data_bucket` |
| Touch | `sourceRound` (`internal/host/movesphase1.go:321-331`) | skips the write when `touched` already holds either spelling of home and the target box; otherwise adds `@home` |
| T8, T9, T12 | `checkOneOwner`, `checkClaim`, `checkQuiet` | name home by the operation's home name; T9 accepts either spelling in `touched`; T12 reads the later of both quiet keys (`TestPlan13HomeTargetReadiness`, `TestPlan13MoveQuietReadsLegacyAndHomeToken`) |
| arming on the home box | `armMovesParks` | arms only when the claim grants the `@home` alias; T13 fails closed otherwise |
| parks under either name | `park.Engine.OwnsRegion`, `hostbox.Regional.Run`, the moves filters (`PlacementMatches(cfg.ParkHomeRegion, cfg.Region, o.Region)`) | `region:@home` chores run on the home box's moves engine and its regional runner |
| production probe owners | `movesProbeOwners` (`internal/host/movesprobeowners.go`) | compare the admission's region with `op.intent.to` and the predecessor's through `SameRegion`; project home history for the baseline (`HolderRegionView`) |
| predecessor cover | `hostclaim.CoverRegionPredecessors` | compares through `SameRegion` under the op's home binding |
| fence and loss | `MoveFenceRegions`, `VerifyMoveTargetFence`, `move_loss_owner.go` | a home target's fence binding maps to `@home` (`TestPlan13FenceRecoveryHomeAndNamed`, "target-home") |
| committed landing on a dead target | `hostclaim.ConvertCommittedLanding` (region HA) | accepts `to = @home` under the op's binding; the relocation's `intent.region` is the roster name |
| authoritative region | `movesAuthoritative` (`internal/host/movessources.go`) | landing and restoring compare through `SameRegion`; the source stays protected until commit or return (J2/F4) |

## Answers to the brief's questions

**Q1. What is missing for a move whose target is home.**

- **Staging lane.** Bucket: the home box's own data bucket, `CARLOS_HIBERNATE_BUCKET`, which its edges file publishes as `data_bucket` with no `placement`. Lease: `control/staging-leases/@home/<host>` in the deployment bucket (part 1's `stagingLeaseKey`). Lane keys: `moves/<host>/<move id>/...` in the home bucket, written by the named source under its `sibling_staging_arns` grant (M17). Who publishes it: the home box's edges-file pass already publishes the coordinates; the console just does not read them (`placements()` skips a file with no `placement`). Task 5 adds a move-only fold. Nothing new is published.
- **How the source finds the bucket and the ARNs.** It already does: `loadMoveFleet` keys the home box under `""`, and every source step looks up `fleet.targets[rec.Move.To]`. IAM is infra: every named region's `sibling_staging_arns` must list the home data bucket's ARN (the source pushes the lane into it), and the home box's must list every named bucket (the home target's T10 probe and its dirty return lane). M17's mesh ("every region lists every other region's data bucket") already says this; P-2 of the runbook has to check that the home bucket is in it (Task 6).
- **T1 to T13 on a home box.** All but T3 already work (table above). T3 is gap 2: `CompareEnv` must accept a home target that sets no `CARLOS_REGION` (Task 1). T8 has a deployment precondition, not a code gap: exactly one edges file in the deployment may lack a `placement` (Ruling R2).
- **Landing on a home box.** Already written: the landing runs on the box the claim names, queues its earlier-stay residue as `park-generation` chores (D6), and commits with `Commit(id, PlacementRegion(From), source, @home, ...)`. The probe's admission, through the production probe owners, records the home box's engine region, its roster name. Task 3 proves it end to end through those owners.
- **Rollback, revert and return lanes (source named, target home).** The home box releases with `TargetReleased(..., @home, ...)`, which matches `op.intent.to` exactly; its cleaning park and the move's lane park are queued with `@home`. A dirty return lane is written by the home box into the named source's bucket under `control/staging-leases/<source>/<host>`, and `QueueReturnLanePark` names the source region. A cut that fails before the flip reverts on the source with `Revert(id, source, @home, &laneScope, ...)`, and the home box parks the lane. Task 4 proves each.
- **Parks in home after the commit.** Two kinds, both owned by home's regional runner: the staging lane's `park-replica` (queued by `Commit`) and the residue `park-generation` chores of an earlier stay (queued at the landing's `parking` step, including the legacy root generation `@root` of a host that was created in home). A new chore is queued with `@home` and owner `region:@home`. A chore the claim already owes under `ie` (from an earlier stay or a sweep) is kept as it is, and the `@home` replay neither duplicates nor rewrites it (Plan 13's alias-aware queue). The home box's moves engine runs the immediate half; `hostbox.Regional` on the home box acknowledges. The source's location park runs in the named source region.
- **The `@home` alias in Touch, Commit and Release.** Touch: when `touched` lacks every spelling of home or lacks the target box, the source adds `PlacementRegion(To)`, which is `@home`, and the target box; when it already holds `ie` (Run 1b, after Run 1 moved the host out of home under the same claim), it writes nothing. Commit: `targetRegion` is the home box's `parkRegion()`, `@home`, which matches `op.intent.to` exactly; the source's quiet key is the named region. Release: `TargetReleased` passes `@home`; `ReleaseMove` and the K8 release name no region. The claim compares `op.intent.to` exactly, and that is safe because `op.intent.to` is itself written as `@home` by the start CAS and moves always supplies `@home`. Task 3 pins the supplied arguments (Review Focus 1, Ask A1).
- **How the door resolves `--to`.** As today: `homeRegionIn` maps the console's home name (`CONSOLE_HOME_REGION`, or the claim-mode fallback `dbClaimHomeRegion`) to `""`. No `home` keyword (Ruling R3): a named region called `home` stays a named region, as Plan 13 rules. With no home name configured the home cannot be named, and the request is `invalid region` as today.

**Q2. Claim-side and region HA changes.** None required in production code. The asks are each backed by an executable witness in this plan, plus one optional hardening (A1).

**Q3. What is shared with part 1, and the failure modes.** Shared: `park.PlacementRegion`, `stagingLeaseKey`, `cfg.parkRegion()`, `moveFleet`'s home key, the engine bindings (`ParkHomeRegion`), the predecessor mapping and the park view's J2/F4 rule. Part 2 adds no new mapping. The failure modes are listed in Review Focus and in "Failure modes" below.

**Q4. Rollout and Run 4.** See "Rollout order" and "Rehearsal changes" below. Run 4 itself does not need moves into home. It needs Runs 2 and 3, which as written move into home.

## Review Focus

1. **One region, two names (Plan 13's dedupe rule).** Two separate properties, each with its own witness:
   - **Supplied arguments are `@home`.** The claim compares `op.intent.to` exactly in `Abort`, `Revert`, `TargetReleased`, the loss and fence paths, and in `requireRetiredRegion` (`internal/hostclaim/move.go:549`). `QueueStagingPark` does not compare it: it validates the scope and queues through the alias-aware `queueReplicaPark` (`move.go:598`), so a wrong spelling there shows as a new chore's region, not a refusal. If a moves call site passed the roster name `ie` instead, most of these refuse (fail closed), but `requireRetiredRegion`'s `region != to` would read `ie` as "not the target" at `landing/committed` and allow a whole-location park in the live home region. The park view still protects the live generation (`movesAuthoritative` answers `@home`, compared through `SameRegion`), so that is defence in depth. The staging lease is the other place: a key spelled with the roster name would split the lane's lease from the source's `@home` key and let two writers stage at once.
   - **Retained state keeps its spelling.** `touched`, `quiet_after`, earlier chores and admissions may already name home as `ie`. A replay must neither add a second chore for the same work under `@home` nor rewrite the `ie` one, and Touch must not write when `ie` is already there. Witnesses seed both spellings.
2. **T3's home rule.** A home target must set no `CARLOS_REGION`; a named target must still set its own, different from the source's. A target in home that does set one is a box that is not the home box, and T3 must fail it (`region-present`), not wave it through (Task 1).
3. **Production owners, not fakes.** The landing, the restarted probe, the C8 files publication, the source's recovery after a rollback and the pre-flip revert must run through `movesProbeOwners`, `movesRecoveryOwners` and the claim runtime, with the home box's engine region `ie` and the op's `to` `@home`. A witness that admits through a fake probe with a literal region proves nothing here (Tasks 2 to 4).
4. **The door's home capability must not leak into create.** `placements()` feeds the create gate's `published` flag and `armedPlacements`; adding `""` there would change create. Task 5 folds the home only in a move-only reader, and `TestCreatePlacementsUnchangedByHomeFold` pins create.
5. **A home-named box is not a home box.** A box with `CARLOS_REGION=<home name>` is home to the console (`homeRegionIn`) but not to itself (no alias; its `cfg.Region` is `ie`, so it never writes a target report, and the source's `fleet.targets[""]` never finds it). The door must refuse it, and say why, before the capability check would give a misleading staging refusal (MV-N511, Task 5).
6. **Parks after the commit, from both engines.** The home box runs home chores from its moves engine (region `@home`) and from `hostbox.Regional` (region = roster name, alias `@home`). Both must acknowledge the same chore once, and a named box must never acknowledge either (`TestPlan13NamedBoxCannotAcknowledgeHomeDelete` covers the named side; Task 4 covers the home side).

---

## File structure

| path | responsibility |
|---|---|
| `docs/superpowers/specs/2026-10-02-move-any-app-design.md` | §2, §7.1, §16 F2 amendment (Task 0) |
| `internal/moves/envreport.go`, `internal/moves/envreport_test.go` | T3's home rule in `CompareEnv` (Task 1) |
| `internal/host/movesreport.go` | `checkEnv` passes the home target to `CompareEnv` (Task 1) |
| `internal/host/movesphase1_harness_test.go` | the `intoHome` option: the source's code, box and bucket, and the target's, parameterized (Task 2) |
| `internal/host/movesland_test.go`, `internal/host/movesland_probe_test.go` | `targetCfg` and `probeLog` take the target from the harness instead of `uk`/`uk-box` (Task 2) |
| `internal/host/movesparks_test.go` | `newCloseHarness` can run its probe through the production owners (Task 2) |
| `internal/host/movesprobeowners_test.go`, `internal/host/movesrecoveryowners_test.go`, `internal/host/moveswakesource_test.go` | their fixtures accept a home target box (Task 2) |
| `internal/host/movesrevert_test.go`, `internal/host/movescut_test.go` | the pre-flip revert fixture accepts a home target (Task 2) |
| `internal/host/moveshome_target_test.go` | new: the into-home witnesses (Tasks 1, 3 and 4) |
| `internal/hostops/move_home_target_test.go` | new: declare-lost and recovery grant witnesses for a home target (Task 4) |
| `home_region_args_test.go` | new, repository root: the inventory of every call site that hands the claim a region on a move's path, each with its runtime witness (Task 3) |
| `internal/console/apiinstancemove.go` | the door's home-target rules and display fills (Task 5) |
| `internal/console/instancesplacement.go` | `movePlacements`, the move-only home fold (Task 5) |
| `internal/console/boxselect.go` | one home name for selection (Task 5) |
| `internal/console/apiinstancemove_home_test.go` | new: the door's home-target tests (Task 5) |
| `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md`, `approved_moves_copy_test.go` | rows MV-N510 to MV-N512, MV-N21 withdrawn |
| `docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md`, `docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md` | P-0, P-2, Run 1b, Runs 2 to 4, Run 6 (Task 6) |

---

### Task 0: Spec amendment

**Files:** modify `docs/superpowers/specs/2026-10-02-move-any-app-design.md`.

- [ ] §2: "between two non-home, non-fleet placements or out of home" becomes "between two non-fleet placements, into home only with host claims on".
- [ ] §7.1's refusal list: "a target of home" becomes "a target of home on a console without host claims, or a home whose box sets a region code of its own".
- [ ] §16 F2: home as a target is delivered by this plan. `CARLOS_HOME_PLACEMENT` is superseded by Plan 13's signed fleet home designation, which names home once for every reader; no environment variable is added. `carlos move back` stays a follow-on (Ruling R3).
- [ ] A spec header line for the revision, citing this plan and #236.
- [ ] Commit: `git commit -m "spec: moves into home (part 2), §2, §7.1, §16 F2"`.

---

### Task 1: T3 accepts a home target that sets no region

**Files:** modify `internal/moves/envreport.go`, `internal/host/movesreport.go`; tests in `internal/moves/envreport_test.go` (extend) and `internal/host/moveshome_target_test.go` (create).

**Interfaces:**
- `moves.CompareEnv(source, target EnvReport, flatLayout, targetHome bool) []EnvDiff`. Every existing caller passes `targetHome` explicitly; there is one production caller (`checkEnv`) plus tests.
- For the region class:
  - `targetHome` false: unchanged (`region-missing` when the target lacks it, `region-same` when equal).
  - `targetHome` true: the target must **not** carry `CARLOS_REGION`. Absent is no diff. Present is `EnvDiff{name, "region-present"}`, checked in both loops (the source may or may not carry it: a move between two home boxes is refused at the door, but T3 does not rely on that).
- `checkEnv` passes `targetHome := rec.Move.To == ""`. That is a record-code test on a record code, which the lint allows. It must not be derived from `cfg.Region`: a second placementless box is a T8 failure, not a T3 pass.
- `region-present` is a fill-in token inside MV-N129's list (`<NAME (kind)>, ...`), so it gets its own row (MV-N512), as MV-N146's fill-in did.

**Tests:**
- `TestCompareEnvHomeTargetSetsNoRegion`: source with `CARLOS_REGION=uk`, target without, `targetHome` true: no diff. The same pair with `targetHome` false: `region-missing` (today's rule kept). Target with `CARLOS_REGION=ie` and `targetHome` true: `region-present`. Source without and target without, `targetHome` true: no diff.
- `TestHomeTargetT3FromTheRealComposition`: build the home box's held environment through the production composer (`agentOwnedVars` with no region, as the box's `ComposeHeldEnv` calls it), not the harness's fixture, so the test fails today and passes after the fix. Assert the target report's T3 is `ok`.
- `TestNamedTargetT3Unchanged`: the existing T3 tests pass with `targetHome` false at every call.

**Copy:** MV-N512 (below), pending.

- [ ] Write the failing tests; implement; targeted runs; commit with the copy row and the inventory line: `git commit -m "moves: T3 accepts a home target that sets no region (home part 2)"`.

---

### Task 2: Fixtures for a home target box

Fixture work only. No production code changes, and every existing test passes unchanged at the end of the task.

**Files:** `internal/host/movesphase1_harness_test.go`, `internal/host/movesland_test.go`, `internal/host/movesland_probe_test.go`, `internal/host/movesparks_test.go`, `internal/host/movesprobeowners_test.go`, `internal/host/movesrecoveryowners_test.go`, `internal/host/moveswakesource_test.go`, `internal/host/movesrevert_test.go`, `internal/host/movescut_test.go`. Read each fixture's current constructor before editing; the list is what the into-home witnesses touch, and an implementer who finds another hard-coded `uk`/`uk-box`/`data-uk` on the target side parameterizes it the same way and adds it to the commit.

**The `intoHome` option.** It mirrors `fromHome` (`h.src = ""`) for the target:
- the source is placed in a named code that is not the home's name (use `ca`), with box `ca-box` and bucket `data-ca`, so no fixture name suggests home and the home's name never collides with the source (today's fixture hard-codes `ie-box` and `data-ie` in several places; parameterize them as `fromHome` parameterized the source's code);
- the target is `home-box`, placement `""`, bucket `data-home`, AWS region `eu-west-1`; its edges file has `data_bucket` and no `placement`;
- the roster names `home-box` in region `ie` and carries the fleet designation `home_region: ie`; the claim's op carries `home_region: ie`; the target's `moveCfg.ParkHomeRegion` is `ie`, as `armMovesParks` would set it;
- the target's held-env composer sets **no** `CARLOS_REGION` (the current fixture's `env["CARLOS_REGION"] = "uk"` would hide Task 1's gap);
- the start uses `BeginAdmittedMove(..., PlacementRegion(""), MoveAdmission{HomeRegion: "ie", ...})`, as the claim-mode door does, not the plain `BeginMove`;
- an option to seed retained claim state under either spelling: `touched.regions` holding `ie` or `@home`, a `quiet_after` key under either, and an earlier residue chore owed under `ie`.

**The production probe for the close harness.** `newCloseHarness` (`movesparks_test.go:38`) installs `probeLog.wake`, which admits with a literal `Box: "uk-box"` and `Region: "uk"`. Add a harness mode that wakes the probe through the production `movesProbeOwners` over the claim runtime, as `TestMovesProbeOwnersThroughTheClaimRuntime` (`movesprobeowners_test.go:814`) does, with the target box's identity: `m.Box = home-box`, `m.Region = ie` (the claim engine's name), `m.HomeRegion = ie`, `m.FleetHomeRegion = ie`. The fake stays for the existing tests.

**The recovery owners and revert fixtures.** `movesRecoveryOwners`' fixture and the cut and revert fixtures accept a home target (the source box is `ca-box`, the target `home-box`), so the source's recovery after a rollback and a pre-flip revert can run through the production owners.

**Tests:** `TestIntoHomeFixtureIsAHomeTarget`: the fixture's record has `Move.To == ""`; the target's edges file has no placement; the claim's `intent.to` is `@home` and `home_region` is `ie`; the target's held env has no `CARLOS_REGION`; the production probe mode admits on `home-box` with region `ie`.

- [ ] Parameterize; run the targeted suites to show nothing existing changed; commit: `git commit -m "moves: fixtures for a home target box (home part 2)"`.

---

### Task 3: The commit into home, through the production owners

**Files:** extend `internal/host/moveshome_target_test.go`; create `home_region_args_test.go` at the repository root.

**Tests (each through the production owners from Task 2, never the fake probe):**
- `TestMoveIntoHomeStagesTheHomeLane`: the source round writes `move.json` and the replica lane into `data-home` under `moves/<host>/<id>/`; it takes the lease `control/staging-leases/@home/<host>` and no other. Touch, with `touched` empty of home: the write adds `@home` and `home-box`. With `touched` already holding `ie` and `home-box` (seeded): no Touch write, and `touched` is byte-identical afterwards. With `touched` holding `ie` but not `home-box`: one write that adds `@home` and `home-box` and leaves `ie` in place, so `touched` then holds both spellings. That is correct, not a defect: Touch is a union and never canonicalizes. Every case asserts preservation (each region and box held before is still held after) and that the only region Touch ever adds is `@home`; none asserts that exactly one spelling is present.
- `TestMoveIntoHomeTargetChecksPass`: on `home-box`, T1 to T13 are all `ok`. T8 names the home by its name. T10 probes `data-ca`. T12 refuses inside a quiet window recorded under `ie`, and again under `@home`.
- `TestMoveIntoHomeCommitsAndCloses` (through the real cut, flip and landing, then the production probe owners and the claim runtime, as `TestMoveFromHomeClosesThroughTheSourcePark` does for part 1 with the fake):
  - an earlier home stay is seeded in `data-home` (a root generation and one named generation); the landing queues one `park-generation` per residue generation with region `@home`, owner `region:@home`, and none for the install's own;
  - the probe's admission, written through `AdmitWriter` with the production `AdmissionEvidence`, records `home-box` with region `ie` and `PredecessorRegion` `ca`; the admission evidence compared `ie` with `op.intent.to = @home` through `SameRegion` and passed;
  - the C8 provider establishes the prepared generation's files manifest under `data-home`'s `<location>/files/g-<id>/` (`moves.GenerationFilesPrefix`), as `TestLandingWakesEstablishThroughC8` does for a named target;
  - the commit queues the source's location park in `ca`, the lane park with `@home`, and covers the source's predecessor; `quiet_after` gains `ca` and never `""`;
  - after the horizon, the home box's engine acknowledges the lane park, the source parks its location, and the record closes with `Region == ""` and `LastMove.Outcome == "done"`;
  - the result can move out of home again: `ClaimMoveRosterPlaces` and the source round accept a second move `"" -> ca` (part 1's path).
- `TestMoveIntoHomeRestartedProbe`: the first probe attempt is admitted and the agent restarts before readiness. The restarted attempt is published fenced (`TestMovesProbeOwnersPublishARestartedAttemptFenced`'s shape) on `home-box`, its admission's predecessor is the first attempt's generation in `ie`, and the commit proceeds as above.
- **The first half of Review Focus 1: supplied regions are `@home`.** Rev 3 proposed a static provenance check. Round 3 showed its classifier could not prove provenance (`to := c.Op.Intent["home_region"]; park.PlacementRegion(to)` passed while supplying `ie`), and a sound version needs dataflow across functions, parameters, struct fields decoded from signed intents, and closures. That is a project of its own, and a heuristic version gives false assurance. Rev 4 uses two narrower pieces instead: a runtime proof, and a static inventory that claims nothing about provenance.
  - **`TestHomeTargetFreshStateShowsOnlyTheHomeToken` (runtime, the proof).** Each into-home witness in Tasks 3 and 4 runs once from **clean** claim state: the start CAS writes `intent.to = @home` and `home_region = ie`, and nothing else in the claim names home (no `touched` entry, no quiet key, no chore). From clean state a wrong spelling cannot hide, because each harmful use shows up:
    - a chore queued with `ie` is a new obligation whose `Region` or `Owner` names `ie` (alias-aware replay absorbs a call only when an equivalent chore already exists, and none does);
    - a Touch with `ie` adds `ie` to `touched`;
    - a staging lease keyed with `ie` shows in the fake lease's recorded keys;
    - an exact comparison against `intent.to` (`Abort`, `Revert`, `TargetReleased`, `ReleaseFencedTarget`, `ReleaseLostTarget`) refuses, and the witness fails;
    - `QueueStagingPark` does not compare with `intent.to`: it validates the scope (`parkQueueGuard`, the lane's location) and calls the alias-aware `queueReplicaPark` (`internal/hostclaim/move.go:598`). A fresh `ie` there would queue a new lane chore named `ie`, which the chore-region assertion below catches.

    After each witness the test asserts that every obligation created for the target side has `Region == "@home"` and `Owner == "region:@home"`, that `touched` gained only `@home` and the target box, and that the lease keys are `control/staging-leases/@home/<host>` for the home lane. A second call with the wrong spelling after a correct first one would be absorbed, but by construction it neither duplicates nor rewrites (that is the retained-state rule below), so it is harmless. The one harmful absorbed case, `requireRetiredRegion` allowing a location park, would create a **new** obligation and is therefore visible.
  - **`TestMovesHomeRegionCallSiteInventory` (static, a pin, not a proof)** in `home_region_args_test.go` at the repository root. It lists every production call site that hands the claim a region on a move's path, and compares the list with a golden inventory in the test. Each inventory row is `{file, enclosing function, callee or composite type and field, argument expression text, witness}`.
    - **Sites:** in every non-test `.go` file under `internal/host`, `internal/hostops` and `internal/console`, every call to `hostclaim.Touch`, `Abort`, `Revert`, `Commit`, `QueueLocationPark`, `QueueStagingPark`, `QueueResidueParks`, `QueueReturnLanePark`, `TargetReleased`, `BeginMove`, `BeginAdmittedMove`, `ReleaseFencedTarget`, `ReleaseLostTarget` and `moves.StagingLeasePrefix`, and every composite literal of `hostclaim.FencedTargetRelease` and `hostclaim.LostTargetRelease` (fields `TargetRegion` and `SourceRegion`), plus each helper that builds the regions they carry: `moveLossRelease` (the `LostTargetRelease` literal), `claimLossIntent` (which copies `TargetRegion` from `op.intent.to`) and `recordLossIntent` (which sets `SourceRegion` from the record), all in `internal/hostops/move_loss_owner.go`. The intent read back from a signed loss record (`lossIntentOf`) is not a new derivation and is not listed.
    - **Witness column:** the runtime test that drives that site with a home target from clean state, or `named-only` with the guard that keeps a home target away. Two rows are expected to be `named-only`: the plain-path `BeginMove(..., to, ...)` (MV-N510 refuses `to == ""` first, Task 5), and any site Task 3 or 4 finds unreachable for a home target. Retained, validated intent is a narrow, named exception: `LostTargetRelease.TargetRegion` is `in.TargetRegion`, which `claimLossIntent` copies from `op.intent.to` and the loss record signs, so its witness is `TestMoveIntoHomeDeclareLost`, and the row says so.
    - **What fails:** a site not in the inventory; an inventoried site whose argument expression text changed; an inventory row whose site no longer exists. The failure message says to add or update the row and name a runtime witness. Fixtures: a new `hostclaim.Commit(id, from, src, rr.row.Region, ...)` site fails as unknown; `to := c.Op.Intent["home_region"]` followed by `hostclaim.QueueStagingPark(id, park.PlacementRegion(to), ...)` at a new site fails as unknown, and at an inventoried site fails as changed; a `FencedTargetRelease{TargetRegion: o.Claim.Op.Intent["home_region"], ...}` literal fails as changed; an unchanged tree passes.
    - **What it does not do:** it classifies nothing as safe by its shape. `plan13Placement` and `plan13Engine` are not used. The inventory's value is that a reviewer saw each site once and a runtime witness covers it, and that no new site appears without both.
  - **Why this trade.** The runtime proof covers exactly the paths a home target takes in the witnesses, with no heuristic. The inventory closes the gap a runtime proof leaves, new or edited call sites, by forcing a review and a witness rather than pretending to verify them. A moves-side injection point over the transition factories remains rejected: it adds production indirection on the claim's authority path to serve a test.
- `TestHomeTargetRetainedChoresKeepTheirSpelling`: with a residue chore and a lane chore already owed under `ie` (seeded), the landing's `QueueResidueParks(@home)` and the commit's `QueueStagingPark(@home)` leave the obligation list byte-identical (no second chore, no rewrite to `@home`); the same with them owed under `@home`. Both are acknowledged once by the home box's engine. This is the second half of Review Focus 1.

- [ ] Write the tests; fix only what they prove broken (expected: nothing outside Task 1); targeted runs; commit: `git commit -m "moves: commit into home through the production owners (home part 2)"`.

---

### Task 4: Revert, rollback, abort, loss and conversion with a home target

**Files:** extend `internal/host/moveshome_target_test.go`; create `internal/hostops/move_home_target_test.go`.

**Tests (production owners throughout):**
- `TestMoveIntoHomePreFlipRevert`: the cut stops the source on `ca-box` and fails before the flip (the shape of `TestCutDeadlinePassedReverts`). `Revert(id, ca-box, @home, &laneScope, ...)` queues the lane park with `@home`, owner `region:@home`; the source's revert probe re-admits on `ca-box` through `movesRecoveryOwners` (`TestRevertProbeAdmitsUncovered`'s shape) in region `ca`; `home-box`'s moves engine runs the lane park on `data-home`, and the home box's `hostbox.Regional` acknowledges it after the horizon. Nothing in `data-ca` is parked.
- `TestMoveIntoHomeCleanRollback`: the probe fails before any write. `TargetReleased` names `@home` and matches `op.intent.to`; the cleaning park of `home-box`'s location and the move's lane park are queued with `@home`; the source reopens on `ca-box` through `movesRecoveryOwners`, asserted in two stages as production recovery works (`movesrecoveryowners.go:300`, and the existing clean-return test at `movesrecoveryowners_test.go:465`). Before the admission: after the clean `ReturnToSource`, `holder.generation` is `src_gen`, and the recovery owners' baseline plan (`r.baseline`) restores from `src_gen`. After the admission: `holder.generation` is the fresh generation the admission named (`fresh1` in that fixture's naming), not `src_gen`; the last admission has `Coverage == "clean-return"`, `WritesLost == nil` and no `AdditionalWritesLost`, and its `Region` is `ca`. Its `Predecessor` and `PredecessorRegion` are the previous last admission's (`hostclaim/generation.go:117` takes both from it, not from the restore baseline): the failed home target's admitted generation and its region in the spelling that admission recorded, which is `ie` in the fixture, not `@home` (the existing named-target shape is `movesrecoveryowners_test.go:446`). The record ends `rolled_back`; nothing is parked in `data-ca` that the source still serves (J2/F4 through `restoring`).
- `TestMoveIntoHomeDirtyReturn`: the probe writes, then fails. `home-box` seals its installation and writes the return lane into `data-ca` under `control/staging-leases/ca/<host>`; `QueueReturnLanePark` names `ca`; the source installs the return through the recovery owners, establishing its files from the return's manifest (C8), and the pre-move generations park after the return (D6), not before.
- `TestMoveIntoHomeAbortParksTheHomeLane`: abort before the cut. `Abort` queues the lane park with `@home`; `home-box`'s moves engine runs it; `hostbox.Regional` on `home-box` (region `ie`, alias `ie`) acknowledges it after the horizon; a named box's runner refuses it; the record clears only after that (Plan 5 Task 1's rule). This is Ask A2's witness.
- `TestHomeRegionalAcknowledgesBothSpellings`: the real `hostbox.Regional` on `home-box` acknowledges a lane `park-replica` and a residue `park-generation` owed under `@home`, and the same owed under `ie`, each exactly once; a `ca` box's runner acknowledges none of the four. Also Ask A2's witness.
- `TestMoveIntoHomeDeclareLost` (hostops): a home target stopped mid-landing and declared lost through Plan 4 Task 10's door. The loss intent's `TargetRegion` is `@home`; the source is re-admitted with the loss through `AdmitWriterWithLoss`; the record shows `loss_ref`. It reuses Plan 13's `TestPlan13FenceRecoveryHomeAndNamed` fixture rather than a new one. `ReleaseLostTarget` must succeed with `TargetRegion == @home` against `intent.to == @home`.
- `TestMoveIntoHomeFencedTargetRelease` (hostops): a home target terminated after it released, from clean claim state. `MoveRecoveryGrant.ReleaseFencedTarget` builds `FencedTargetRelease` with `TargetRegion: park.PlacementRegion(m.To)` (`@home`) and `SourceRegion` `ca`; `hostclaim.ReleaseFencedTarget` accepts it against `intent.to`; the location and lane parks it queues name `@home`. The witness for the inventory's `FencedTargetRelease` row.
- `TestCommittedLandingIntoHomeConvertsOnTargetFence`: after the commit, `home-box` is fenced as terminated. `hostclaim.ConvertCommittedLanding` turns the claim into `relocating` with `intent.region = ie` (the roster name) and `move_to_region = @home`, and keeps the lane park and the source's finalization chores in their retained spelling. This is Ask H1's witness. It changes no region HA code.

- [ ] Write the tests; any production fix they force is in moves' code only, and a failure that points into claim or region HA code becomes an ask on #236, not a patch; targeted runs; commit: `git commit -m "moves: into-home revert, rollback, abort, loss and conversion witnesses (home part 2)"`.

---

### Task 5: The door admits a home target under host claims

**Files:** modify `internal/console/apiinstancemove.go`, `internal/console/instancesplacement.go`, `internal/console/boxselect.go`; create `internal/console/apiinstancemove_home_test.go`; the copy record and `approved_moves_copy_test.go`.

**Interfaces:**
- `movePlacements(ctx, s release.Store, home string) (caps map[string]placementCapability, homeNamed []string, published bool)`: `placements()` plus the home, folded under `""` from every edges file with no `placement`. Same reachability rule: no `eip`, no capability. Staging is `data_bucket != ""`. `homeNamed` is the sorted box ids of edges files whose `placement` equals `home` (when `home != ""`). `published` keeps `placements()`'s meaning, so a home-only fleet still reads "publishes no armed regions". `placements()` and `armedPlacements()` are unchanged.
- `(s *Server) stagingNames(caps) string`: `stagingCodes` with each code passed through `homeRegionOut`, so MV-N20's list names the home by its name.
- `(s *Server) moveHomeName() string`: `dbClaimHomeRegion()` with host claims, `cfg.HomeRegion` without. `selectMoveBoxes`, `verifiedRoster` and `eligibleBoxes` take the home name from it. That is the name `homeRegionIn` already uses, so selection and the door agree when the console's home is the claim-mode fallback.

**The Phase 1 request door, in order (`handleAPIInstanceMoveRequest`):**
1. `to == ""` and not a host-claim console: refuse with MV-N510 (400) before any read or write. This replaces MV-N21 on this path.
2. `to == rec.Region`: unchanged (MV-N16 already fills through `homeRegionOut`).
3. The manifest rule: a home target is allowed when some entry of `m.Regions` maps to `""` through `homeRegionIn`, or the list is empty. Named targets keep `slices.Contains`. MV-N2 fills `<to>` through `homeRegionOut`. Before writing this step, read the create door's rule (`instanceRegionAllowed` at `apiinstances.go:1268` and `regionchoices.go:23-41`). If create treats a home entry differently, follow `regionchoices.go`'s `homeRegionIn` mapping and record the difference in the task's report as a finding for the create door.
4. **New, before the capability check:** `to == ""` and `len(homeNamed) > 0`: refuse with MV-N511 (409), naming the first box. Why here and not after selection: the home fold takes placementless files only, so a deployment whose only home box sets `CARLOS_REGION=<home name>` has no `caps[""]` staging, and the capability check would answer MV-N20 ("publishes no staging bucket"). That is false, since the box may well publish a bucket, and it hides the misconfiguration the operator has to fix. Checking the edges files the capability read already loaded costs nothing extra. Because the read is not liveness-joined, a stale file of a home-named box that has since gone also refuses; that fails closed and is fixed by removing the file, which the refusal names. A deployment with both a placementless home box and a home-named box also gets MV-N511, not M-C21, for the same reason.
5. Staging: `movePlacements` in place of `placements`. MV-N20 fills `<to>` through `homeRegionOut` and the list through `stagingNames`.
6. `selectMoveBoxes` as today. MV-N23 fills `<to>` through `homeRegionOut`. M-C21's `ManyRegion` is already `homeRegionOut`.
7. The floor: MV-N5 (`apiinstancemove.go:633`) fills `<from>` and `<to>` through `homeRegionOut` (today `to` is raw).
8. `startClaimModeMove`: MV-N177 (`:786`, `:797`) fills `<to>` through `homeRegionOut` (today raw); the call to `moveClaimCASFailed` at `:831`, and the plain path's call, pass `homeRegionOut(to)`, so MV-N30 ("the instance has unfinished cleanup in <to>") names the home. `moveClaimCASFailed`'s parameter is renamed `toName` to say it is a display name. Every other step is unchanged: `MatchesHome` and `ClaimMoveRosterPlaces` already refuse a home target without the designation.

The record, the claim and `BeginClaimMove` keep receiving the code `to` (`""`); only display arguments change. The legacy door (switch off) is not touched. Its em-dash refusal stays until Plan 6 Task 8 retires it.

**Tests (`apiinstancemove_home_test.go`, using the claim test fixture of `apiinstancemove_claim_test.go`):**
- `TestMoveDoorClaimModeAcceptsHomeTarget`: `--to ie` on a console whose home is `ie`, a roster with the designation, a placementless home box with a data bucket: 202. The claim is `moving` with `intent.to = @home` and `intent.home_region = ie`; the record's `Move.To` is `""`; the view's `target` is `ie`.
- `TestMoveDoorPlainPathRefusesHomeTarget`: switch on, no host claims: 400 with MV-N510; the claim and the record are byte-identical before and after.
- `TestMoveDoorHomeStagingFromItsEdgesFile`: home box without `data_bucket`: MV-N20 naming `ie`, with a list that names no empty code; with it: passes.
- `TestCreatePlacementsUnchangedByHomeFold`: `placements()` over a fleet with a placementless box returns the same map and `published` as before this task (Review Focus 4).
- `TestMoveDoorHomeTargetMustPublishNoPlacement`: the only `ie` box publishes `placement: ie` and a data bucket: 409 with MV-N511 naming it, not MV-N20; no claim read, no registration. A second case with both a placementless home box and a home-named box: MV-N511 again.
- `TestMoveDoorHomeTargetNeedsTheDesignation`: the roster has no `home_region`: MV-N177 reading "... active boxes in ca and ie"; nothing registered.
- `TestMoveDoorManifestNamesHome`: `regions: ["ie","uk"]` allows `--to ie`; `regions: ["uk"]` refuses with MV-N2 naming `ie`.
- `TestMoveDoorHomeRefusalsNameTheHome`, one case per sentence, each driven to its refusal with a home target: MV-N2, MV-N5 (a home box below the floor: "a box in ca or ie is below the moves protocol floor..."), MV-N16, MV-N20, MV-N23, MV-N30 (a pending chore under `@home` or `ie` makes `BeginAdmittedMove` return `ErrPendingChore`: "the instance has unfinished cleanup in ie..."), MV-N177. Each asserts the exact approved template with `ie` filled, and that no sentence contains `" in  "`, `" or  "` or `" and "` followed by the end of the string.
- `TestLegacyMoveDoorStillRefusesHome`: switch off: the legacy sentence, byte for byte.
- The lint (`TestPlan13NoDirectPlacementEngineComparison`) stays green.

**Copy:** MV-N510 and MV-N511 pending. MV-N21 is withdrawn: its row is removed from the record with a note, as MV-N388's was, and from `movesCopyIDs`, in this commit. MV-N2, MV-N5, MV-N20, MV-N23, MV-N30 and MV-N177 keep their approved text; only their fills change (MV-N16 already names the home).

- [ ] Write the failing tests; implement; targeted runs plus the copy gate; commit: `git commit -m "console: the move door admits a home target under host claims (home part 2)"`.

---

### Task 6: Rehearsal runbook and index

**Files:** modify `docs/superpowers/plans/2026-10-06-moves-phase1-rehearsal.md`, `docs/superpowers/plans/2026-10-06-moves-phase1-0-index.md`.

- [ ] **P-0, the home item.** Part 2 merged and rolled (boxes, then consoles) is required for any run that moves into home. The rest of the item is unchanged: part 1, Plan 13, and Plan 13's order (readers, then the designation, then the flag). Replace the quoted MV-N21 sentence with "the door refuses moves into home without host claims (MV-N510)".
- [ ] **P-0, new: one placementless box.** For each deployment, list `control/edges/` and record that exactly one edges file has no `placement`. S1 and T8 refuse a move into **or out of** home while two do, whether or not the second box is running (an edges file outlives its box), and a retained file below the floor makes the door refuse with MV-N5. On dev, `carlos-dev-ie-b` makes two; Ruling R2's five-step procedure (stop, delete the edges file, wait out the 15-minute status window, verify complete evidence before each run, restart after) applies to every dev run that touches home, Run 1 included. Copy the procedure into the runbook verbatim.
- [ ] **P-2.** Every named region's `sibling_staging_arns` lists the home data bucket's ARN, and the home's lists every named data bucket. Record the home data bucket's name and check it is not the deployment bucket.
- [ ] **Run 1b (new), dev, `uk` to `ie`, a commit into home.** Run 1's host, moved back after Run 1's quiet window (T12). Same checks as Run 1, with `<pr-ie> = home`. Additional checks: the lane is in the home data bucket under `moves/<host>/<move>/`; the claim's `touched.regions` holds at least one spelling of home: `ie`, `@home`, or both (Touch adds `@home` when the target box is new to `touched` and never removes `ie`), and every region it held before the request is still there; after the close, `parked/<inc>/<host>/` in the home bucket holds the residue of Run 1's stay, if any; `quiet_after` names `uk`. Blockers: Run 1 passed; part 2 rolled; the designation published on dev; P-0's one-placementless-box item. Note for the operator: where the console shares the home box, the landing runs on the console's own box (Ruling R7).
- [ ] **Runs 2 and 3.** Once part 2 is rolled, they run as written (`uk` to `ie`, into home). The out-of-home arrangement stays as the fallback while part 2 is not rolled.
- [ ] **Run 4.** Unchanged: `ie` to `uk`, with `carlos-dev-uk` stopped. It never stops the home box. Its blocker becomes "Runs 1, 1b, 2 and 3 passed". It does not need moves into home itself.
- [ ] **Run 6.** "Both parts merged and rolled" stays. The "dev rehearsal from home" blocker becomes "dev Runs 1 and 1b passed".
- [ ] **Sources table.** Replace the MV-N21 line (`apiinstancemove.go:537-540`) and the `moves-home-parks` "parts 1 and 2" line with this plan and its merge commit, and add `internal/host/movesreport.go:113` (`loadMoveFleet` counts every box per placement before the data-bucket check) as the source of the one-placementless-box item.
- [ ] **Index.** Add a row after Plan 7: "Home part 2: `2026-10-15-moves-home-region-part2.md`".
- [ ] Commit: `git commit -m "docs: rehearsal and index for moves into home (home part 2)"`.

---

### Task 7: Gate and review

- [ ] The full local gate once (Global Constraints), excluding the whole `internal/host` suite, which CI runs.
- [ ] Astra (codex, from a committed brief) reviews the built branch against Review Focus 1 to 6. Commit its findings unedited beside this plan as `CODEX-PLAN-MOVES-HOME-PART2-BUILD-R1.md`; fix and record dispositions here.
- [ ] Post the branch, the witnesses for A2, H1 and H3, and the rulings on #236.
- [ ] Re-merge `main`, wait for CI, then Paul's go to merge (`amadan branch merge -squash`).

## Copy rows (pending)

To append to `2026-10-06-moves-phase1-copy.md` under a new heading "Moves into home (part 2)", each in its task's commit:

| id | file | text | status |
|---|---|---|---|
| MV-N510 | internal/console/apiinstancemove.go | moves into the home region need host claims. this console runs without them | pending |
| MV-N511 | internal/console/apiinstancemove.go | <box> sets CARLOS_REGION, so it cannot take a move into the home region | pending |
| MV-N512 | internal/moves/envreport.go (a T3 kind inside MV-N129's list) | region-present | pending |

Withdrawn in Task 5: MV-N21 ("moves to the home region are not supported yet. the home publishes no staging lane"). Fills that change but keep their approved text: MV-N2, MV-N5, MV-N20, MV-N23, MV-N30 and MV-N177 name the home by its name instead of the empty code.

## Failure modes

| # | failure | effect | guard |
|---|---|---|---|
| F1 | a moves call site supplies the home's roster name where `@home` is meant | most exact checks refuse (fail closed); `requireRetiredRegion` would allow a location park in the live home region | Task 3's `TestHomeTargetFreshStateShowsOnlyTheHomeToken` (runtime, from clean state) and `TestMovesHomeRegionCallSiteInventory` (every site reviewed and witnessed); the park view's live-set rule; Ask A1 hardening |
| F2 | the staging lease keyed by the roster name | two writers on one lane | `stagingLeaseKey` takes record codes only; Task 3 asserts the key |
| F3 | a replay canonicalizes or duplicates a chore already owed under `ie` | a second park of the same work, or a rewritten obligation the regional runner no longer matches | Plan 13's alias-aware queue; Task 3's `TestHomeTargetRetainedChoresKeepTheirSpelling` |
| F4 | a box with `CARLOS_REGION=<home name>` | as a home target the move would stall in `preparing` (no target report; the source finds no `targets[""]`) | MV-N511 at the door, before the capability check (Task 5) |
| F5 | two placementless edges files (dev's `carlos-dev-ie-b`; any agent box without `CARLOS_REGION`, running or not) | the door accepts, then S1 or T8 refuses with M-C21 / MV-N166, for moves into **and** out of home; a retained file below the floor makes the door refuse with MV-N5; deleting the file while the box's status is still fresh makes the door refuse every move as incomplete evidence | Ruling R2's five-step procedure; runbook P-0 check |
| F6 | T3 `region-missing` on a home target | every move into home waits at `preparing` | Task 1 |
| F7 | a home box on a build without Task 1 | the same wait, with T3's reason in the target report; the operator aborts | roll boxes before consoles (Ruling R5) |
| F8 | no designation, or the console's home name differs from it | MV-N177 for every claim-mode move; a home box never arms (T13) | Plan 13's rollout order; nothing new |
| F9 | the IAM mesh lacks the home bucket | the source's lane round fails `AccessDenied`, logged; or T10 fails on the home box | runbook P-2 |
| F10 | the home box is also the console's box, and it dies mid-landing | the console's move pass stops with it; box-side recovery and the durable records wait for the console's return | Ruling R7 |
| F11 | the plain Phase 1 path writes `intent.to = ""` | `BeginMove` refuses with `ErrMalformed`, which the door would answer as a 502 | MV-N510 refuses first (Task 5) |
| F12 | a door sentence filled with the raw code | an operator reads "in  " or "in ca and " | Task 5's fills and `TestMoveDoorHomeRefusalsNameTheHome` |

## Rollout order

1. **Plan 13 on every box and console** (on main since 2026-10-12; check each box reads `current` with `carlos system status`). Plan 13's own order still binds: every roster reader on a Plan 13 build, then publish the designation, then turn `CARLOS_HOST_CLAIM` on.
2. **Part 2 merged** (dormant), released, and rolled **boxes first, then consoles**, so no door admits a home target before every home box has Task 1's T3 rule.
3. **IAM:** the M17 mesh including the home bucket (P-2), applied by Paul with a scoped `tofu apply -target`.
4. **Claim activation (#250)**, which publishes the signed designation. Until then a home box never arms and the door refuses claim-mode moves under a configured home name.
5. **`CARLOS_MOVES_PHASE1`** per run, as the runbook says.

Part 2 adds no switch of its own. A home target is reachable only when the designation exists, the console runs host claims, and the switch is on.

## Rehearsal changes

- Run 1 (`ie` to `uk`) is unchanged, but **today it is blocked on dev by `carlos-dev-ie-b`'s edges file** (Ruling R2). That blocker is independent of part 2.
- Run 1b (new): `uk` to `ie`, a commit into home, Run 1's host moved back.
- Runs 2 and 3 return to as written (into home) once part 2 is rolled.
- Run 4 is unchanged and does not move into home. It gates on Runs 1, 1b, 2 and 3. Stopping the home box for a declare-lost would stop dev's console, which is why Run 4 stays out of home (Ruling R6).
- Run 5 (flagship) may keep `uk` and `us`. Adding a home leg is optional and is Paul's call when Run 5 is scheduled.
- Run 6's blockers are met by part 2 merged and rolled plus dev Runs 1 and 1b.

## Rulings needed

Only Paul can decide these. Each has a recommendation.

- **R1. Home targets only under host claims.** The plain Phase 1 path (switch on, claims off) keeps refusing a home target (MV-N510), because it has no designation to resolve home against and its `BeginMove` would write `to = ""`. *Recommend: yes.* Phase 1 cannot land anything in production without claims anyway (its parks arm only with both flags).
- **R2. Dev's second home box blocks every dev run that touches home, today, independent of part 2.** `loadMoveFleet` (`internal/host/movesreport.go:113`) adds every edges file's box to `boxes[placement]` before it checks for a data bucket, and with no liveness check. S1's `oneBoxRefusal` counts `boxes[From]` and `boxes[To]`, and T8 counts `boxes[To]`. `carlos-dev-ie-b` publishes no placement, so `boxes[""]` holds two boxes and Run 1 (`ie` to `uk`) is refused at S1 with "the home region has more than one box" before any copy. The door, which counts eligible boxes (D15), accepts it first. Options:
  - **(a) Take `carlos-dev-ie-b` out for each rehearsal window.** Stopping its agent alone is **not enough**, and neither is deleting its edges file alone. Two readers see the box, by different rules:
    - the box side (`loadMoveFleet`, so S1 and T8) counts every edges file, fresh or not, so a retained `control/edges/carlos-dev-ie-b.json` keeps `boxes[""]` at two;
    - the console's door reads the live snapshot (`ReadLiveSnapshot`, `internal/edgesfile/snapshot.go:168`): a **fresh** status snapshot whose box has no edges file makes the evidence incomplete, and `selectMoveBoxes` (`internal/console/boxselect.go:75`) then refuses every move with "the fleet's edges files could not all be read". A status snapshot is fresh until it is more than 15 minutes old (`status.BoxStale`: 3 × the 5-minute `SnapshotPeriod`).

    A retained edges file also reaches the door's protocol-floor check: `selectMoveBoxes` counts every file of either region, fresh or not, so a stale `carlos-dev-ie-b` file below the floor, or without the armed floor's `moves_floor_min_release`, refuses every move into or out of home with MV-N5. The procedure, with Paul's go for the window:
    1. stop `carlos-dev-ie-b` (the instance, or at least its agent and console units, so it neither writes status nor serves);
    2. delete `control/edges/carlos-dev-ie-b.json` from the deployment bucket;
    3. wait until its last `status/boxes/carlos-dev-ie-b.json` is more than 15 minutes old by its `generated_at`;
    4. before **each** run, verify the fleet's evidence is complete: every box with a status snapshot fresher than 15 minutes has an edges file, every edges file's box has a status snapshot, and exactly one edges file has no `placement`. The door's own confirmation is the run's **S-2**, the move request itself. Only a move request runs `selectMoveBoxes` and the floor check: `carlos move status` (`handleAPIInstanceMoveStatus`) reads the record and claim only, and S-1 only wakes the app, so neither confirms anything here. If S-2 answers "the fleet's edges files could not all be read" or MV-N5, the window is not ready. Both refusals come before any claim read or write, so nothing was started: fix the evidence and repeat S-2;
    5. after the window, start the box again. Its edges pass rewrites the file on boot. No move touching home runs until the window is reopened by steps 1 to 4.

    It also takes #286's two-home-box hazard out of the window, since rehearsal apps then wake on one box only. Cost: an operator procedure with a 15-minute wait per window, and dev loses its second home box while it lasts.
  - **(b) Make S1 and T8 count only eligible boxes**, as the door does. That is a box-side rule change: the box would need the roster, the box identity records and `boxchoice.Eligible`'s inputs, and it would loosen the check that stops a second placementless box from reconciling an unregioned record that has just landed in home (#286). It needs its own task, its own review, and a decision on what "eligible" means to a box that cannot read the console's secrets fingerprint.
  - **(c) Give `carlos-dev-ie-b` a placement of its own** (for example `CARLOS_REGION=ie-b`). That ends the collision for good, but it changes which records the box reconciles: it stops taking unregioned records, so any host woken on it moves back to `carlos-dev-ie`. That is a dev topology change with its own risk, and region HA's two-box testing in dev's home region would need another arrangement.
  - **(d) Retire `carlos-dev-ie-b`** if region HA's dev testing no longer needs it. Cleanest if true; only Paul and region HA know.
  *Recommend (a) for the rehearsal windows, now,* because it unblocks Run 1 without a code change and keeps the box rule that protects against #286. Ask region HA whether (d) is possible; if it is, prefer it over repeating (a). Do not take (b) as part of this plan: if Paul wants it, it is a separate moves task with its own review.
- **R3. `--to` vocabulary and `carlos move back`.** The door accepts the home's configured name only, as every other surface prints it; there is no `home` keyword, because a named region may be called `home` (Plan 13). `carlos move back` stays a follow-on, as spec §16 F2 lists it. *Recommend: yes to both.*
- **R4. Copy.** Approve MV-N510 to MV-N512 and the withdrawal of MV-N21 (copy review). *Recommend: review with the branch, before merge.*
- **R5. No protocol floor bump.** Task 1 changes the box's T3. A home box without it fails T3 and waits (fail closed), and the operator aborts. Bumping `moves.ProtocolVersion` to 2 would make the door refuse up front, but it would also refuse every move, to any region, until every box of both regions rolls. *Recommend: no bump; roll boxes before consoles*, as the release recipe already says.
- **R6. Run 4 stays out of home** (`ie` to `uk`), and the runbook gains Run 1b. *Recommend: yes.*
- **R7. A home target on the console's own box.** Where the console shares the home box, a landing into home puts the console on the move's target. If that box dies mid-landing, the console's move pass dies with it until the box or the console is restored. The records and the claim are durable, so nothing is lost, but recovery waits. *Recommend: accept, and note it in the runbook's Run 1b.* The alternative is refusing a home target whose box runs the console, which no edges field states today.

## Asks

None blocks this plan. Each names the witness that answers it; please review the witness rather than answer in prose. Post them on #236.

**To platform-1e (the claim):**

- **A1. The exact-region contract, and one optional hardening.** Moves' contract: every region moves supplies to `Abort`, `Revert`, `Commit`, `TargetReleased`, `QueueStagingPark`, `QueueResidueParks`, `QueueReturnLanePark` and `Touch` is a placement-derived engine region (`park.PlacementRegion` of a record code, so `@home` for home), never a roster name; retained claim state keeps whatever spelling it has. Witnesses: `TestHomeTargetFreshStateShowsOnlyTheHomeToken` (runtime, from clean state), `TestMovesHomeRegionCallSiteInventory` (every call site, including `ReleaseFencedTarget` and `ReleaseLostTarget`, mapped to its runtime witness) and `TestHomeTargetRetainedChoresKeepTheirSpelling` (runtime, Task 3). Please confirm the claim keeps its exact `intent.to` comparisons on that basis. Optional hardening, recommended: compare `requireRetiredRegion` (`internal/hostclaim/move.go:549`) through `regionname.SameRegion` under `op.intent.home_region`, so a roster-name caller can never read the live home as "not the target" (Review Focus 1).
- **A2. The home regional runner.** Witnesses: `TestMoveIntoHomeAbortParksTheHomeLane`, `TestMoveIntoHomePreFlipRevert` and `TestHomeRegionalAcknowledgesBothSpellings` (Task 4) run the real `hostbox.Regional` on a home box against `@home` and `ie` lane and residue chores. Please review them.
- **A3. No further claim gate.** Witness: `TestMoveDoorClaimModeAcceptsHomeTarget` (Task 5) and `TestMoveIntoHomeCommitsAndCloses` (Task 3) drive a `moving` op with `intent.to = @home` from the start CAS to the close. Please confirm nothing else in the claim should refuse it.

**To region HA (relocation):**

- **H1. The committed-landing conversion into home.** Witness: `TestCommittedLandingIntoHomeConvertsOnTargetFence` (Task 4): `intent.region` is the roster name, `move_to_region` is `@home`, and the lane parks and the source's finalization chores are kept in their spelling. Please review it. No code change is expected.
- **H2. The two-box model.** Moves into home stay one-box (D15, T8 and Ruling R2), so the two-box `internal/sim/hostlife` model needs no new scenario. Confirm, or name the scenario. Separately, for Ruling R2: does region HA still need `carlos-dev-ie-b` in dev's home region (option d)?
- **H3. C8 on a home landing.** Witnesses: `TestMoveIntoHomeCommitsAndCloses` and `TestMoveIntoHomeRestartedProbe` (Task 3) establish the prepared files manifest under `data-home`'s `<location>/files/g-<op>/` through the production provider on a placementless home box, and `TestMoveIntoHomeDirtyReturn` (Task 4) establishes the source's files from the return. Please review them against relocation's env-by-row lookup and prepared-authority reader.

## Self-review

- Every gap named in the brief has a task or a ruling: the lane (Q1, Task 5 fold, P-2), the ARNs (P-2), T1 to T13 (Tasks 1 and 3), the landing (Task 3), revert, rollback and return (Task 4), parks after the commit (Tasks 3 and 4), the alias in Touch, Commit and Release (Task 3's two pins), `--to` (R3).
- Nothing changes claim or region HA code. Anything a test finds there becomes an ask.
- Every witness on a path where `ie` meets `@home` runs through the production owners.
- Supplied arguments are proved at runtime from clean state, and the call sites are pinned by an inventory that claims no provenance; retained state is proved at runtime from seeded state. No assertion requires exactly one spelling of home in claim state, and no heuristic classifier is used as proof.
- Copy rows are pending, have no em dashes, and are in the commit that writes them.

## Review round 1 (Astra, on be29ccd0): dispositions (rev 2)

Verdict NOT READY, four P2. Astra confirmed the diagnosis (T3 `region-missing`, the door and capability gaps, the one-box finding at `loadMoveFleet:113`) and that no claim or region HA production change is needed; it supported R1, R3, R5 and R6, called R2(a) and R7 reasonable, and asked for executable witnesses behind A2, H1 and H3.

| # | finding | rev 2 |
|---|---|---|
| 1 | P2 the witnesses demanded canonical `@home` where Plan 13 keeps existing spellings (`sourceRound:323` skips Touch when `touched` holds `ie`; an `@home` replay keeps an owed `ie` chore, `plan13_followups_test.go:28`) | Global Constraints and Review Focus 1 split supplied arguments (`@home`) from retained state (either spelling). Task 3's Touch test covers empty, `ie` held, and `ie` without the box; `TestHomeTargetSuppliedRegionsAreTheHomeToken` asserts arguments only; new `TestHomeTargetRetainedChoresKeepTheirSpelling` seeds both spellings and asserts no duplicate and no rewrite. Run 1b's check accepts either spelling. |
| 2 | P2 the close harness's fake probe (`movesparks_test.go:38`, `movesland_probe_test.go:73`) bypasses the production owners, admission evidence and files publication | New Task 2 is fixture work only, with the full file list, and adds a production-probe mode over the claim runtime. Tasks 3 and 4 run every witness through `movesProbeOwners`, `movesRecoveryOwners` and the C8 provider, add `TestMoveIntoHomeRestartedProbe` and `TestMoveIntoHomePreFlipRevert`, and the dirty return establishes files through C8. Review Focus 3 is new. |
| 3 | P2 more empty home names in the door: MV-N5 (`:633`), MV-N177 (`:786`), MV-N30 (`:752` via `:831`) | Task 5 steps 7 and 8 pass `homeRegionOut` for every display argument, keep the approved templates and rename `moveClaimCASFailed`'s parameter `toName`; `TestMoveDoorHomeRefusalsNameTheHome` drives each of the six sentences, and MV-N16, to its refusal. |
| 4 | P2 MV-N511's test could not reach its refusal: the home fold takes placementless files only, so MV-N20 fires first at `:599` | The placement-mismatch check moves ahead of the capability check (Task 5 step 4), from `movePlacements`' new `homeNamed` list. Chosen because MV-N20 would tell the operator the region publishes no staging bucket, which is false and hides the real misconfiguration. The post-selection `TargetPlacement` check is dropped as redundant. The test now covers the home-named-only case and the mixed case. |
| also | the one-box finding as a rehearsal blocker independent of part 2 | Ruling R2 rewritten: options (a) to (d), with the correction that stopping the agent alone does not clear the count because the edges file outlives the box; recommendation (a) now, ask region HA about (d), (b) only as its own task. |
| also | A2, H1, H3 need executable witnesses | Every ask now names its witness tests; A2 gains `TestHomeRegionalAcknowledgesBothSpellings`. |

## Review round 2 (Astra, on c139b80b): dispositions (rev 3)

Verdict NOT READY: round 1's #3 and #4 addressed, #1 and #2 partial, two new P2s. All four fixed in rev 3.

| # | finding | rev 3 |
|---|---|---|
| 1 | P2 (round 1 #1, partial) Run 1b required exactly one home spelling, while Task 3 lets Touch leave both `ie` and `@home` | Run 1b accepts `ie`, `@home` or both, and asserts every region held before the request is still held. Task 3's Touch test states that both spellings after a union is correct, asserts preservation and that `@home` is the only region Touch adds, and never asserts a single spelling. Self-review records the rule. |
| 2 | P2 (round 1 #1, partial) the argument-recording test had no interception point: `hostclaim.Update` (`store.go:63`) takes an opaque closure, and alias-aware replay hides the spelling | Replaced by the static `TestMovesHomeRegionArgumentsAreDerived` over every production call site of the transition factories and `moves.StagingLeasePrefix` in `internal/host`, `internal/hostops` and `internal/console`, with allowed argument forms, a named allowlist, and failing and passing fixtures. Static was chosen over a moves-side injection point because an injection point would add production indirection on the claim's authority path to serve a test, while the static check adds nothing to production, covers call sites no test reaches and catches new ones. Its derivation-only limit is covered by the runtime pins listed with it. Global Constraints, F1 and A1 updated. |
| 3 | P2 (new) the clean-rollback assertion `holder.generation = src_gen` conflicted with production recovery, which restores from `src_gen` and admits a fresh generation (`movesrecoveryowners.go:300`; `movesrecoveryowners_test.go:465` expects `fresh1`) | `TestMoveIntoHomeCleanRollback` asserts in two stages: `src_gen` after the clean return and as the restore baseline; after the admission, the fresh generation, `clean-return` coverage, no writes lost, predecessor `src_gen` in `ca`. |
| 4 | P2 (new) R2(a) was incomplete: deleting the edges file while the box's status snapshot is fresh makes `ReadLiveSnapshot` (`snapshot.go:168`) incomplete and `selectMoveBoxes` refuses; a retained file still affects S1, T8 and the floor check | R2(a) is now five steps: stop the box, delete its edges file, wait until its status snapshot is more than 15 minutes old (`status.BoxStale`, 3 × 5 minutes), verify complete fleet evidence and one placementless file before each run, restart after the window. The ruling explains both readers and the MV-N5 effect of a retained file. Task 6's P-0 item, F5 and the runbook copy carry the same procedure. |

## Review round 3 (Astra, on bdbfe3fd): dispositions (rev 4)

Verdict NOT READY: the spelling, interception and generation items addressed; four P2s left. All four fixed in rev 4. On the static check, rev 4 takes the narrower option the coordinator offered rather than attempting provenance.

| # | finding | rev 4 |
|---|---|---|
| 1 | P2 the clean-return predecessor: `AdmitWriter` takes the predecessor and its region from the last admission, the failed home target's (`hostclaim/generation.go:117`; `movesrecoveryowners_test.go:446`), not `src_gen` in `ca` | `TestMoveIntoHomeCleanRollback` keeps the baseline (`src_gen`) and fresh-generation assertions and now asserts `Predecessor` and `PredecessorRegion` are the target's last admitted generation and its region in its recorded spelling (`ie` in the fixture). Round 2's row 3 is superseded on this point. |
| 2 | P2 the static check missed `ReleaseFencedTarget` and `ReleaseLostTarget`, whose regions arrive through `FencedTargetRelease` (`move_fence_release.go:130`) and `LostTargetRelease` (`move_loss_owner.go:732`) and are compared exactly with `intent.to` | Both factories, both composite literals' `TargetRegion` and `SourceRegion` fields, and the helpers that build them (`moveLossRelease`, `claimLossIntent`, `recordLossIntent`) are in the inventory. `LostTargetRelease.TargetRegion` is a named exception for retained, signed intent, with `TestMoveIntoHomeDeclareLost` as its witness. New `TestMoveIntoHomeFencedTargetRelease` witnesses the fenced path. A changed `FencedTargetRelease` literal is a failing fixture. |
| 3 | P2 `plan13Placement` cannot prove provenance: `to := c.Op.Intent["home_region"]; park.PlacementRegion(to)` passed while supplying `ie` | The static provenance check is withdrawn. Sound provenance needs dataflow across functions, parameters, signed-intent fields and closures; a heuristic gives false assurance. Replaced by `TestHomeTargetFreshStateShowsOnlyTheHomeToken`, a runtime proof from clean claim state, where every harmful wrong spelling is observable (a new obligation, a `touched` entry, a lease key, or an exact refusal), plus `TestMovesHomeRegionCallSiteInventory`, a golden inventory of every site with its runtime witness that classifies nothing by shape. The reviewer's counterexample is a failing fixture (unknown site, or changed expression at a known one). No heuristic classifier is used anywhere as proof. |
| 4 | P2 R2(a)'s confirmation: move status (`apiinstancemove.go:325`) runs neither selection nor the floor check, and S-1 only wakes the app | Step 4 keeps the evidence checks and names S-2, the move request, as the door's confirmation; both of its possible refusals precede any claim read or write, so a failed confirmation starts nothing. |

## Review round 4 (Astra, on 9c2fbf01): dispositions (rev 4.1)

Verdict READY: all four round-3 items addressed, and the runtime-witness trade judged sound. One P3, fixed.

| # | finding | rev 4.1 |
|---|---|---|
| 1 | P3 `QueueStagingPark` was listed among the transitions that compare their region exactly with `intent.to`; it does not (`move.go:598` validates the scope and calls the alias-aware `queueReplicaPark`) | Removed from the exact-comparison lists in Review Focus 1 and in `TestHomeTargetFreshStateShowsOnlyTheHomeToken`'s description. Both now say a fresh `ie` there queues a new lane chore named `ie`, which the chore-region assertion catches. |
