carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

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. |