carlos / platform Public

plan: home part 2 rev 3, Astra round 2 dispositions

- Run 1b and Task 3 accept ie, @home or both in touched and assert
  preservation, never a single spelling.
- The supplied-argument witness is now a static check over every moves
  call site of the transition factories (home_region_args_test.go);
  hostclaim.Update's opaque closure leaves no runtime interception
  point, and an injection point would add production indirection on
  the claim's authority path.
- The clean rollback asserts src_gen as the restore baseline and the
  fresh generation with clean-return coverage after the admission.
- Ruling R2(a) is a five-step procedure: stop, delete the edges file,
  wait out the 15-minute status window, verify complete evidence
  before each run, restart after; it explains the MV-N5 effect of a
  retained file.
Round 2 disposition table added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev bdbfe3fd855430e85d4f0314d6c861c3e6f9501d parent c139b80
1 file changed, +43 −12
  • docs/superpowers/plans/2026-10-15-moves-home-region-part2.md +43 −12

CI cancelled — superseded by a newer push — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	286.166s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.625s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.278s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.711s
ok  	github.com/carlosframework/platform/internal/activator	249.352s
ok  	github.com/carlosframework/platform/internal/agentsock	0.032s
ok  	github.com/carlosframework/platform/internal/alerting	0.008s
ok  	github.com/carlosframework/platform/internal/analytics	0.028s
ok  	github.com/carlosframework/platform/internal/appconfig	2.487s
ok  	github.com/carlosframework/platform/internal/apperr	0.087s
ok  	github.com/carlosframework/platform/internal/applog	0.144s
ok  	github.com/carlosframework/platform/internal/awsinv	0.008s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.013s
ok  	github.com/carlosframework/platform/internal/boxid	0.767s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.016s
ok  	github.com/carlosframework/platform/internal/caps	0.015s
ok  	github.com/carlosframework/platform/internal/certwarm	0.007s
ok  	github.com/carlosframework/platform/internal/cgroup	0.697s
ok  	github.com/carlosframework/platform/internal/changelog	0.327s
ok  	github.com/carlosframework/platform/internal/clamd	0.057s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]

amadan: job cancelled — a newer push superseded it
diff --git a/docs/superpowers/plans/2026-10-15-moves-home-region-part2.md b/docs/superpowers/plans/2026-10-15-moves-home-region-part2.md
index d893cef34..84ab18e09 100644
--- a/docs/superpowers/plans/2026-10-15-moves-home-region-part2.md
+++ b/docs/superpowers/plans/2026-10-15-moves-home-region-part2.md
@@ -2,7 +2,7 @@
> **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 2, 2026-10-15. Paul approved starting part 2 on 2026-10-15. Written against `main` at a5003aba. Tracking thread: amadan #236. Rev 1 (be29ccd0) was reviewed by Astra: NOT READY, four P2s; the dispositions are at the end.
+Rev 3, 2026-10-15. Paul approved starting part 2 on 2026-10-15. Written against `main` at a5003aba. Tracking thread: amadan #236. Rev 1 (be29ccd0) and rev 2 (c139b80b) were reviewed by Astra, each NOT READY; the dispositions of both rounds are at the end.
**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").
@@ -22,7 +22,7 @@ Everything else (the staging lane, the lease key, Touch, Commit, Revert, the rol
- **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. Witnesses assert the first rule on arguments and the second on state, and test both spellings of retained state (Review Focus 1).
+- **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 checked statically on the production call sites (Task 3's `TestMovesHomeRegionArgumentsAreDerived`), because `hostclaim.Update` receives an opaque closure after the arguments are bound and alias-aware replay hides the spelling in the resulting state. 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.
@@ -96,6 +96,7 @@ Everything else (the staging lane, the lease key, Touch, Commit, Revert, the rol
| `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 static check that every moves call site derives its region arguments (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) |
@@ -166,10 +167,10 @@ Fixture work only. No production code changes, and every existing test passes un
### Task 3: The commit into home, through the production owners
-**Files:** extend `internal/host/moveshome_target_test.go`.
+**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.
+- `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;
@@ -179,7 +180,14 @@ Fixture work only. No production code changes, and every existing test passes un
- 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.
-- `TestHomeTargetSuppliedRegionsAreTheHomeToken`: over the three tests above and Task 4's, every region a moves call site **supplied** to the claim for the target side (`Touch`, `QueueResidueParks`, `Commit`'s `targetRegion`, `QueueStagingPark`, `TargetReleased`, `Abort`, `Revert`) is `@home`, recorded by a wrapper around `hostclaim.Update` in the test. Every `op.intent.to` the claim compared was `@home`. This is the first half of Review Focus 1.
+- `TestMovesHomeRegionArgumentsAreDerived` (new file `home_region_args_test.go` at the repository root, package `main`, beside `home_region_comparison_test.go`, reusing its `plan13Expr` and `plan13Placement` helpers). This is the first half of Review Focus 1, checked statically.
+ - **Scope.** Every non-test `.go` file under `internal/host`, `internal/hostops` and `internal/console`. The claim's own package and region HA's relocation code are out of scope: they are not moves call sites, and `ConvertCommittedLanding` already compares through `SameRegion`.
+ - **Calls checked**, with the region parameter each takes: `hostclaim.Touch` (every element of the regions slice literal), `Abort` (2nd), `Revert` (3rd), `Commit` (2nd and 4th), `QueueLocationPark` (2nd), `QueueStagingPark` (2nd), `QueueResidueParks` (2nd), `QueueReturnLanePark` (2nd), `TargetReleased` (3rd), `BeginMove` and `BeginAdmittedMove` (5th), and `moves.StagingLeasePrefix` (1st).
+ - **Allowed argument forms:** `cfg.parkRegion()` and `<x>.cfg().parkRegion()` (a box's own placement code mapped once; `@home` on a home box); `park.PlacementRegion(e)` or `regionname.PlacementRegion(e)` where `e` is a record code by `plan13Placement`'s classification; a variable whose single assignment in the same function is one of those; and `nil` for Touch's regions. `moves.StagingLeasePrefix`'s only allowed caller is `stagingLeaseKey`, whose argument is `park.PlacementRegion` of its parameter.
+ - **Everything else fails** with the call's position and expression, unless it is listed in an explicit allowlist in the test with a one-line reason. Two forms are expected there: the console grants that pass a verified fence or roster binding already mapped by `MoveFenceRegions`, and the plain-path `BeginMove(..., to, ...)`, which Task 5 leaves reachable only for named targets because MV-N510 refuses `to == ""` first. Each entry names its file and function, so a new call site cannot hide behind an old entry.
+ - **Lint witnesses**, as Plan 13's lint has them: a fixture with `hostclaim.Commit(id, from, src, rr.row.Region, ...)` and one with `hostclaim.Touch(id, []string{c.Op.Intent["home_region"]}, nil)` must fail the check; `hostclaim.Commit(id, park.PlacementRegion(l.rec.Move.From), src, cfg.parkRegion(), ...)` must pass.
+ - **The runtime complement.** The values these forms produce for a home target are already pinned: `park.PlacementRegion("") == park.HomeRegion` (`TestHomeRegionIsReserved`), `cfg.parkRegion()` on the `intoHome` fixture is `@home` (asserted in `TestIntoHomeFixtureIsAHomeTarget`), and the staging lease the fake lease records is `control/staging-leases/@home/<host>` (`TestMoveIntoHomeStagesTheHomeLane`). `op.intent.to` is asserted to equal `@home` in the claim after the start CAS, which is the value every exact comparison reads.
+ - **Why static, not an injection point.** `hostclaim.Update` (`store.go:63`) takes an opaque closure built after the arguments are bound, so a store wrapper sees only the resulting claim, and Plan 13's alias-aware replay can make an `ie` argument and an `@home` argument produce identical state. An injection point would mean a moves-side indirection over every transition factory in production code, on the claim's authority path (with census rows in `claim-authority-sites.txt`), to serve a test. The static check adds nothing to production, covers every call site including ones no test reaches, catches a future call site the day it is written, and follows the precedent of Plan 13's comparison lint. Its limit is that it proves the derivation, not the value, which is why the runtime complement above stays.
- `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)"`.
@@ -192,7 +200,7 @@ Fixture work only. No production code changes, and every existing test passes un
**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` with `holder.generation = src_gen`; the record ends `rolled_back`; nothing is parked in `data-ca` that the source still serves (J2/F4 through `restoring`).
+- `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`, its `PredecessorRegion` is `ca`, and its `Predecessor` is `src_gen`. 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.
@@ -247,9 +255,9 @@ The record, the claim and `BeginClaimMove` keep receiving the code `to` (`""`);
**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). On dev, `carlos-dev-ie-b` makes two; Ruling R2's procedure applies to every dev run that touches home, Run 1 included.
+- [ ] **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 a spelling of home (`ie` from Run 1, or `@home`), and only one; 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).
+- [ ] **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".
@@ -282,11 +290,11 @@ Withdrawn in Task 5: MV-N21 ("moves to the home region are not supported yet. th
| # | 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 `TestHomeTargetSuppliedRegionsAreTheHomeToken`; the park view's live-set rule; Ask A1 hardening |
+| 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 static `TestMovesHomeRegionArgumentsAreDerived`; 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 | Ruling R2; runbook P-0 check |
+| 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 |
@@ -320,7 +328,18 @@ 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) Stop `carlos-dev-ie-b` during rehearsal runs.** Stopping its agent alone is **not enough**: its `control/edges/carlos-dev-ie-b.json` stays, and `loadMoveFleet` still counts it. The procedure is: stop the box (the agent or the instance), then delete its edges file with Paul's go, run, and start it again afterwards (its edges pass rewrites the file on boot). 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 step per window, and dev loses its second home box while it lasts.
+ - **(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`. Then confirm with the door: `carlos move status` on a rehearsal host, or the run's own S-1, shows no "could not all be read" and no MV-N5;
+ 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.
@@ -337,7 +356,7 @@ None blocks this plan. Each names the witness that answers it; please review the
**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: `TestHomeTargetSuppliedRegionsAreTheHomeToken` and `TestHomeTargetRetainedChoresKeepTheirSpelling` (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).
+- **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: `TestMovesHomeRegionArgumentsAreDerived` (static, over every moves call site) 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.
@@ -352,6 +371,7 @@ None blocks this plan. Each names the witness that answers it; please review the
- 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 statically, retained state at runtime; no assertion requires exactly one spelling of home in claim state.
- 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)
@@ -366,3 +386,14 @@ Verdict NOT READY, four P2. Astra confirmed the diagnosis (T3 `region-missing`,
| 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. |