| 1 | # Moves Phase 1 Implementation Plans: Index |
| 2 | |
| 3 | > **For agentic workers:** this file is the map. Each plan it names is a |
| 4 | > self-contained implementation plan with the superpowers header; execute |
| 5 | > them in the order below with superpowers:subagent-driven-development or |
| 6 | > superpowers:executing-plans. Do not start a plan whose dependencies are |
| 7 | > not merged on `main`. |
| 8 | |
| 9 | **Goal:** Build the move-any-app Phase 1 design (spec rev 7.22, e1d7c3f1; |
| 10 | claim rev 6.30, 73b1b9dc) on `main` at d649d8d4 or later (claim Plan 6, the |
| 11 | box wiring for C1 to C4, merged 2026-10-06), as an ordered |
| 12 | series of plans that each produce working, tested, mergeable software. |
| 13 | (The series began against spec rev 7.18 on `main` 67547f5; the revisions |
| 14 | since are in the spec's header and the dispositions below.) |
| 15 | |
| 16 | **Spec:** `docs/superpowers/specs/2026-10-02-move-any-app-design.md` (rev |
| 17 | 7.20: P7 cold-only, region HA L13 to L17 folded in with claim rev 6.28's |
| 18 | fence reference and pre-flip conversion; L18 stated as a limit; one-box |
| 19 | regions only). The executable model is `internal/sim/hostlife/` at 9c417aaa on |
| 20 | the same branch; its witness names are cited in the tests the plans port. |
| 21 | |
| 22 | **Claim library on `main` (built on, never re-implemented):** |
| 23 | `internal/hostclaim` (E, rev 6.27 semantics; 989ee349 §1.1 records the |
| 24 | #236 rulings), `internal/genselect` (F), `internal/hostguard` and |
| 25 | `internal/stopevidence` (claim Plan 3), `internal/park` (claim Plan 4), |
| 26 | `internal/console/apidbclaim.go` (claim Plan 7). |
| 27 | |
| 28 | ## The series |
| 29 | |
| 30 | | # | plan | produces | needs from claim plans 5, 6, 8 | |
| 31 | |---|---|---|---| |
| 32 | | 1 | [`2026-10-06-moves-phase1-1-copy-and-staging.md`](2026-10-06-moves-phase1-1-copy-and-staging.md) | op ids, the record's move journal, the `moving` claim CAS at the request door (operator-only), the per-op staging layout with `move.json`, seal-ready copy rounds, the files archive, the timers snapshot, the launch and readiness reports (T1 to T12, S1, S2), the T11 budget, the protocol floor and `moves_protocol`, the `hold_op` column, the copy gate | nothing | |
| 33 | | 2 | [`2026-10-06-moves-phase1-2-cut-flip-landing.md`](2026-10-06-moves-phase1-2-cut-flip-landing.md) | the journaled cut (requested to flipped), `stopevidence.Reconstruct`, the seal by version, the archive publication, the flip CAS, hold rows and reconcile's participant rules, timers frozen and drained, `RenderHeld`, `MaterializeHeld`, the landing's `checking` to `restored` phases including the generation build (snapshot, anchor, build intent, install manifest), the park queue helpers and `Engine.Parked` | C3 and C8 for two sub-steps of `local`; everything else nothing | |
| 34 | | 3 | [`2026-10-06-moves-phase1-3-probe-commit-open.md`](2026-10-06-moves-phase1-3-probe-commit-open.md) | the probe policy (attempt windows, deadline checks, budgeted attempts), `ProbeWake` as a cold admission into the prepared generation, the commit record CAS and its claim half (obligations, `CoverRegionPredecessors`), the conditional open, the source's finalization park | Tasks 5 to 7 BLOCKED-ON-CLAIM (C1, C2); Tasks 1 to 4 and 8 are not | |
| 35 | | 4 | [`2026-10-06-moves-phase1-4-rollback-return-declare-lost.md`](2026-10-06-moves-phase1-4-rollback-return-declare-lost.md) | `reverting` (pre-flip recovery), the rollback decision, the target's release with the clean/dirty rule (`src_covered`), the dirty return lane, the source's `claimed` CAS (`holder.stamp = source_stamp`, clean `holder.generation = src_gen`), the dirty install on the source, the target's `cleaning`, `stuck`, `--retry`, the record side of declare-lost | Task 9 BLOCKED-ON-CLAIM (C1, C2); Task 10 BLOCKED-ON-CLAIM (C6, C7); the lost-target branch of Task 5 with it | |
| 36 | | 5 | [`2026-10-06-moves-phase1-5-cleanup-and-parks.md`](2026-10-06-moves-phase1-5-cleanup-and-parks.md) | the paging primitive, abort with the target-owned staging park, the staging horizon as a persisted `ParkScope`, the finalization parks and the release to `serving` (K8), the `moves/<host>` sweep scope, the db door's refusal on move kinds, the console's claim-half recovery pass and deadline sweeps | the acknowledgement after the horizon of every chore is C4's runner; the immediate half is built here; the console pass can later be called from claim Plan 5's `Recovery.Pass` | |
| 37 | | 6 | [`2026-10-06-moves-phase1-6-cli-status-observability.md`](2026-10-06-moves-phase1-6-cli-status-observability.md) | `carlos move` and its four sub-verbs, `POST …/move/resolve`, `GET …/move?detail=1`, exit codes 0/3/4/5, the move log, the member stream events, paging rules, the copy gate, the `docs/site/cli.md` heading | nothing (the `--declare-lost` flag calls Plan 4 Task 10's door and is built with it) | |
| 38 | | 7 | [`2026-10-06-moves-phase1-7-rehearsal-runbook.md`](2026-10-06-moves-phase1-7-rehearsal-runbook.md) | the `moveprobe` test app, the cross-version tests, the rollout steps and the rehearsal runbook in M12's order | every plan above, merged and rolled with Paul's go | |
| 39 | | home part 2 | [`2026-10-15-moves-home-region-part2.md`](2026-10-15-moves-home-region-part2.md) | moves into the home region under host claims (#236): T3's home rule, the commit, revert, rollback, abort and loss with a home target, the door's home target (MV-N510, MV-N511), and the rehearsal's Run 1b | claim Plan 13's home naming and the signed designation; nothing new from the claim | |
| 40 | |
| 41 | Plans 1 and 2 have no unmerged dependency and are built first, in order. |
| 42 | Plan 3's pure tasks (1 to 4, 8) and Plan 4's pure tasks (1 to 8) can be |
| 43 | built before claim Plans 6 and 8 land; the index marks every task that |
| 44 | cannot. Plans 5 and 6 depend only on Plans 1 to 4 for code; Plan 5 notes |
| 45 | the runner it hands its chores to. |
| 46 | |
| 47 | ## Dependency order |
| 48 | |
| 49 | ``` |
| 50 | main (67547f5+) ─► Plan 1 ─► Plan 2 ─► Plan 3 (1-4, 8) ─► Plan 4 (1-8) ─► Plan 5 ─► Plan 6 ─► Plan 7 |
| 51 | │ │ |
| 52 | claim Plan 6 (feat/hostclaim-box) ─► Plan 3 (5-7) ─► Plan 4 (9) |
| 53 | claim Plan 8 (feat/hostclaim-leases) ─────────────► Plan 4 (10) |
| 54 | claim Plan 5 (feat/hostclaim-console) ─────────────► Plan 5 (console pass joins Recovery.Pass; adoption of legacy hosts before any rehearsal) |
| 55 | ``` |
| 56 | |
| 57 | ## Exact interfaces needed from claim Plans 5, 6 and 8 (for platform-1e) |
| 58 | |
| 59 | These are the names the moves plans call. Each is quoted from the plan |
| 60 | that defines it; if a plan changes a name, the moves plan that uses it |
| 61 | changes with it. |
| 62 | |
| 63 | **From claim Plan 6 (`feat/hostclaim-box`, package `internal/hostbox`, |
| 64 | `internal/hostwrite`, activator and schedule adapters):** |
| 65 | |
| 66 | - `hostbox.Writer{Claims release.Store; Data genselect.Reader; Bucket, Region, Box string; AdmissionEvidence func(context.Context, hostclaim.Observed, hostclaim.Admission, *genselect.Plan) error; PublicationEvidence func(context.Context, hostclaim.Observed) error; NewID func() string}` with |
| 67 | `Baseline(ctx, observed) (*genselect.Plan, error)`, |
| 68 | `Admit(ctx, observed, in hostclaim.Admission, baseline *genselect.Plan) (hostclaim.Observed, error)`, |
| 69 | `Published(ctx, observed) (hostclaim.Observed, error)`. Plan 3 Task 5 |
| 70 | (`ProbeWake`) admits the probe through `Admit` and publishes through |
| 71 | `Published`; it supplies `AdmissionEvidence` that checks the landing's |
| 72 | journal (the prepared generation's install manifest) and |
| 73 | `PublicationEvidence` that checks `generation.json` by `If-Match`. |
| 74 | - `hostbox.GenerationPrefix(holder hostclaim.Holder) (string, error)`. |
| 75 | - The claim-aware start sequence Plan 6 Task 9 wires into |
| 76 | `Service.wake` (purge, restore of the admitted baseline, start, |
| 77 | readiness, first upload, marker, `PublishGeneration`, open). Plan 3 |
| 78 | Task 6 requires that sequence to be callable for a held row with a |
| 79 | probe policy: moves asks Plan 6 for one exported entry, |
| 80 | `(a *Service) startAdmitted(ctx, e *actEntry, admitted hostclaim.Observed, policy startPolicy) error`, or equivalent, where `startPolicy{MaxStarts int; Window time.Duration; Deadline time.Time; Probe bool}` is the only new input. Until that exists, Plan 3 Tasks 5 to 7 cannot be built. |
| 81 | - `hostbox.Regional.Run` runs `park-replica` chores whose scope is a |
| 82 | `moves/<host>/<op id>` lane or a whole location, and `park-generation` |
| 83 | chores, under a move or rollback op id (platform-1e work order item 1: |
| 84 | "Regional.Run for park-replica moves scopes"); no new kind (D6). |
| 85 | - `spawnClaimed(ctx, enabled bool, manager *cgroup.Manager, host string, limit cgroup.Limit, selected hostwrite.Work, identity hostwrite.Identity, build func() *exec.Cmd) (*exec.Cmd, *cgroup.Prepared, error)` (claim Plan 6 line 878): every spawn under a move is strict (§5.3); `ProbeWake` spawns through it with `CARLOS_MOVE_PROBE=1`, keeps the returned `*cgroup.Prepared` for the process's whole life, and on every exit path (a failed start included) runs `Prepared.KillAndEmpty` then `Prepared.Close` before the attempt is counted. |
| 86 | - The wake admission of Plan 6 Task 9 must honour moves' K6: `doWake` reads the claim fresh and, when `op.exec` does not name this box, kills the host's process tree (`ReconstructStopEvidence`'s step 1, Plan 2 Task 3) before anything else; it admits a wake under `landing/committed` for the target (§7.14 row "landing, committed, not yet finalized") and under `reverting`/`restoring` phases `probed` and `opened` for the source. Its restore writes `<dbPath>.gen` and retires (`park.Local.RetireArtifacts`) a warm database whose `.gen` names another generation before restoring cold (§5.1 start check rule 3, §9.3). |
| 87 | - `schedule.SelectedTimers` (the timer-control adapter): Plan 2 Task 9's |
| 88 | `ReplaceTimers` call on the target runs under the landing's own |
| 89 | selection, so the adapter must accept a held row's write when the |
| 90 | selection names the landing op. |
| 91 | |
| 92 | **From claim Plan 8 (`feat/hostclaim-leases`, package `internal/hostclaim` |
| 93 | additions, `internal/hostexec`, `internal/hostquarantine`, |
| 94 | `internal/hostfence`):** |
| 95 | |
| 96 | - `hostclaim.LostDeclaration{LossID string; ExpectedClaimGen uint64; ExpectedClaimETag string; HolderIncarnation string; HolderStamp, HolderSession uint64; OpID, OpKind, OpPhase, Box string; QuarantineRevision uint64; Enrollment string; FencedAt time.Time; Consequences LossConsequences}`. |
| 97 | - `hostclaim.LossOperationOwner interface{ ValidateDeclareLost(Claim, LostDeclaration) error }`: Plan 4 Task 10 implements it in `internal/moves` with §7.10's legality table (target owes evidence; target released dirty and not returned; destroyed box owes only local chores; refused after the record's commit, for the source, for a non-participant). |
| 98 | - `hostclaim.DeclareLost(ctx, s release.Store, pub ed25519.PublicKey, observed Observed, lossID string, owner LossOperationOwner) (Observed, error)`. |
| 99 | - `hostclaim.LossRecord`, `CreateLossRecord(ctx, s, sg release.Signer, record LossRecord) (ObservedLossRecord, error)`, `ReadLossRecord`, `ReplaceLossRecord`, `LossCloudObservation`, `LossOperatorAttestation`, `TimerLoss`, `LossConsequences`, `ObligationRef`. |
| 100 | - `hostclaim.LossRecoveryOwner interface{ ValidateLossRecovery(Claim, LossRecord, Admission) error }` and `AdmitWriterWithLoss(ctx, s, pub, observed, in Admission, lossID string, validated []ValidatedWriteSource, owner LossRecoveryOwner) (Observed, error)`: the source's re-admission after a declared-lost target (the `writes_lost` for every target session since the cut and the source's cut session when `src_covered` is false; witnesses `wTwoSessionDeclaration`, `wNoDuplicateDeclarationEarly`, `wNoDuplicateDeclarationLate`). |
| 101 | - `hostclaim.Readmit(box string, quarantineRevision uint64) func(*Claim) error` and the `hostquarantine` retirement a returning box runs before `--readmit` (§7.10). |
| 102 | - `hostexec.Open(ctx, cfg hostexec.Config) (*Controller, error)` with `Ready/Lost/Close`: the lease controller every admitted writer runs under; `ProbeWake` runs its spawn under it once Plan 8 lands (Plan 3 Task 6 notes the seam). |
| 103 | - `declareLostTransition`'s effects (claim Plan 8 line 1444): it journals `loss.<id>` (the canonical recovery intent), sets `op.exec = "none"` (the literal string, not `""`) when the lost box was the executor, removes the box from `op.participants`, reassigns its regional chores to `region:<r>`, discharges its local chores, and keeps the phase. Moves' `ReturnToSource` (Plan 4 Task 2) accepts that state: exec `""` or `"none"`, and either `evidence_target` or a `loss.<id>` journal entry. |
| 104 | - **Activation prerequisites are claim Plan 8b's, not Plan 8's** (Plan 8 "Rollout runbook", line 5678): the authenticated release identity behind the floor, the root-signed actor directory and census, and the live quarantine, declare-lost and readmission wiring. Plan 4 Task 10 can be built against Plan 8's library and tests, but `carlos move resolve --declare-lost` stays refused in production (the door answers "declare-lost is not enabled in this deployment yet") until Plan 8b lands and Paul rules on #236's P1 to P3. A direct `park.Local.RetireInstallation` is the build-time stand-in for the returning box's quarantine in tests only; it is not an activation path. |
| 105 | |
| 106 | **From claim Plan 5 (`feat/hostclaim-console`, package `internal/hostops`):** |
| 107 | |
| 108 | - `hostops.Controller.Adopt(ctx, r hostops.Record) (hostclaim.Observed, error)`: a legacy host has no claim until adoption; the move door refuses a claimless host ("the claim not `serving`", §7.1), so every rehearsal host is adopted first. No moves code calls it. |
| 109 | - `Recovery.Pass(ctx) error` and `(s *Server) StartClaimRecoveryPass(ctx)`: Plan 5 Task 7's `movesRecoveryPass` is written to be called from `Recovery.Pass` once it exists; until then the console runs it on its own ticker. |
| 110 | - `hostops.RunOwnedRemote(ctx, e *park.Engine, w park.Work, scope ParkScopeProvider) error` and `ParkScopeProvider func(context.Context, hostclaim.Observed, hostclaim.Obligation) (hostclaim.ParkScope, error)`: the regional runner's scope provider for moves' chores returns the persisted scope the chore carries (Plan 5 Task 3 binds it at the queuing CAS), never a computed one. |
| 111 | |
| 112 | ## BLOCKED-ON-CLAIM contracts (rev 3) |
| 113 | |
| 114 | Platform-1e's work order (2026-10-06): (1) claim Plan 6 Tasks 6 to 11 |
| 115 | built to the signatures below: `startAdmitted`, K6 in the wake |
| 116 | admission, `SelectedTimers` for held rows, `Regional.Run` for |
| 117 | `park-replica` moves scopes; unknown kinds stay refused. (2) Plan 8b: |
| 118 | roster, census, release identity and guard, the termination fence, the |
| 119 | real declare-lost and readmit. (3) Plan 5 Tasks 5 to 9: the move, flip, |
| 120 | commit, rollback and delete transitions, plus the delete's two parks. |
| 121 | Claim rev 6.27 (989ee349) §1.1 records Paul's #236 rulings; P7 |
| 122 | (cold-only) binds every plan here. |
| 123 | |
| 124 | Every task marked **BLOCKED-ON-CLAIM** names one of these contracts and |
| 125 | is not built until the contract is merged on `main`; the moves plans do |
| 126 | not guess the API beyond what is written here. |
| 127 | |
| 128 | Owners (the coordinator, 2026-10-06): **C1 to C4 are claim Plan 6 rev 2, merged to `main` as d649d8d4 on 2026-10-06** (claim Plan 6b, the production assembly, is being drafted; Plan 8b, floor and roster for C6 and C7, is heading to CI), |
| 129 | **C5 is claim Plan 5**, **C6 and C7 are claim Plans 8 and 8b**, **C8 is |
| 130 | region HA's** (plan 1a). Claim rev 6.28 (4ffe8a0d) adopts region HA's L1 |
| 131 | to L20; spec rev 7.20 aligns with it (the fence reference, the pre-flip |
| 132 | source conversion, L13, the one-box scope). |
| 133 | |
| 134 | | id | contract moves needs | from | blocks | |
| 135 | |---|---|---|---| |
| 136 | | C1 | **On `main` at d649d8d4 (claim Plan 6, merged 2026-10-06; inert while `CARLOS_HOST_CLAIM` is unset).** `(a *Service) startAdmitted(ctx, e *actEntry, admitted hostclaim.Observed, policy startPolicy) error` with `startPolicy{MaxStarts int; Window time.Duration; Deadline time.Time; Probe bool; Prepared *moves.InstallManifest; Inputs *provision.MoveInputs}`: the admitted writer's start of claim rev 6.10 §6.4a (purge, restore of the admitted baseline or the prepared generation's pinned snapshot, start, readiness, first upload, `generation.json` by If-Match or If-None-Match, `PublishGeneration`, open), callable for a **held** row, returning `ErrAttemptOver` when the policy's window or deadline passes before readiness and never marking the host failed when `Probe` is true. Cold only (P7): the sequence never consults a certificate while P7 stands. | claim Plan 6 Tasks 7, 9 (work order 1) | Plan 3 Tasks 5, 6, 7; Plan 4 Task 9 | |
| 137 | | C2 | **On `main` at d649d8d4 (claim Plan 6, merged 2026-10-06; inert while `CARLOS_HOST_CLAIM` is unset).** K6 in `doWake`: a fresh claim read; when `op.exec` does not name this box the host's process tree is killed (`stopevidence.Reconstruct` step 1) before anything else; a wake is admitted under `landing/committed` for the target and under `reverting`/`restoring` phases `probed` and `opened` for the source; the restore writes `<dbPath>.gen` and retires a warm database whose `.gen` names another generation. | claim Plan 6 Task 9 | Plan 3 Task 7 (serving after the open); Plan 4 Tasks 5, 6 (serving after `opened`) | |
| 138 | | C3 | **On `main` at d649d8d4 (claim Plan 6, merged 2026-10-06; inert while `CARLOS_HOST_CLAIM` is unset).** `schedule.SelectedTimers` accepts a held row's write when the selection names the hold's op, and gains `ReplaceTimers(host string, rows []registry.Timer) error` delegating to `registry.ReplaceTimers` under that selection. | claim Plan 6 Task 7 | Plan 2 Task 10 `local` (until merged, `local` calls `registry.ReplaceTimers` directly behind the switch D13) | |
| 139 | | C4 | **On `main` at d649d8d4 (claim Plan 6, merged 2026-10-06; inert while `CARLOS_HOST_CLAIM` is unset).** `hostbox.Regional.Run` runs `park-replica` chores scoped to `moves/<host>/<op id>` and to `instances/<host>/<inc>`, and `park-generation` chores, under move and rollback op ids; its `Leases.Acquire(bucket, location)` **shares the activator's held lease for a location this box itself serves** (an aggregate lease adapter, claim Plan 5 D3's wording), else a landing's residue chores and the at-rest sweep on a serving host can never acknowledge (`park/remote.go:404` acquires the location's lease). | claim Plan 6 Task 10, Plan 5 Task 3 | Plan 5 Tasks 1, 3, 5 acknowledgement after the horizon (the immediate half runs without it) | |
| 140 | | C5 | Production callers for `Controller.ReconcileClaims` inside the frozen, signed #232 ownership backfill and for `(s *Server) StartClaimRecoveryPass` (started from serve, callable from the moves console pass, stamps `TargetParkedAt`): a legacy host gets its claim on the next reconcile pass of that backfill, never lazily from the door or the recovery pass (claim §1.1 P4, Plan 5 rev 2 at 2d67ec47). | claim Plan 5 integration (work order 3) | the rehearsal (Plan 7 P-5); the request door's "no host claim yet" refusal is correct meanwhile | |
| 141 | | C6 | The loss sequence, end to end: `CreateLossRecord` of a `prepared` record with `WriteSources` composed as **the latest target session as reason `target` and every earlier target session since the cut and, when `src_covered` is false, the source's cut session as further sources** (`lost.go:149-191` requires the frozen set to equal the admission's primary `WritesLost` plus `AdditionalWritesLost`, unique by `(box, generation, session)`, each with a validated baseline); the transition `prepared → committed` with `ResultClaimGen` after `DeclareLost` (`lost.go:119` admits only a `committed` record); `DestroyedStorage` attestations for `DischargeLocal`; `Enrollment` and `QuarantineRevision` as the roster supplies them (Plan 8b), and a fence form of the declaration with neither (region HA L2 to L4). Moves supplies `moves.LossOwner` (`ValidateDeclareLost`, `ValidateLossRecovery`) and the record-side `lost` phase; the claim owns the record's lifecycle and the exact `LossRecord` composition. | claim Plan 8 + 8b (work order 2) | Plan 4 Task 10; the lost-target branch of Plan 4 Task 5; Plan 6 Task 1 `--declare-lost`; Plan 7 Run 4 | |
| 142 | | C7 | **Settled by claim rev 6.28 §7.3 (spec rev 7.20 §7.10):** `op.journal["fence.<box>"] = {"enrollment","key","sha256"}` names `control/box-fences/<enrollment>.json` and the digest of the bytes verified; a `loss.<id>` intent cites the same reference; consumers accept a fence only through it, re-verifying the record; an automatic declaration needs no quarantine revision and no operator. Still owed by Plans 8/8b: the fence record's writer and the verification helper (`hostclaim.VerifyFence(ctx, s, ref) error` or its real name) and the one-CAS conversion of a pre-flip `moving` op to `relocating` at `pinning` (`hostclaim.ConvertMovingToRelocating(opID, fence FenceRef) func(*Claim) error` or its real name: exec `"none"`, stamp and generation preserved, obligations untouched, `relocating`/`pinning` in the source region; refused once the record is `landing`). Moves' `ReturnToSource` (Plan 4 Task 2) and the console pass (Plan 5 Task 7) consume both. | claim Plans 8 and 8b | Plan 4 Task 2's fence form; Plan 5 Task 7's conversion call; Plan 6 Task 2's `waiting for a box in <region>` state | |
| 143 | | C8 | Region HA's files publisher API in two forms (spec rev 7.22 §7.6, L17, R18-1, R19-1; claim rev 6.30 73b1b9dc). The **prepared-manifest build**, `files.BuildPrepared(ctx, store lease.VersionedStore, location, generation, opID string, stamp uint64, dir, envPath string, wake WakeState) (manifestVersion string, err error)` or its real name: before any session exists, under `op.exec`'s installation authority, create-only content objects plus one create-only manifest bound to `{location, generation, op id, installation stamp}` where `generation` is the installation's **prepared destination generation** (`g-<move id>`, or `g-<rollback id>` for a dirty return), never `holder.generation`, which stays the source's until the probe's admission (R19-1); exact versions, sequence 0, never replaced, retired with its generation. The **session-bound publisher**, `files.Publish(...)` or its real name (claim C3), which starts after the first admission, takes the prepared manifest by exact version as its baseline and publishes from sequence 1, writing origin-qualified references (P6-D3); and the reader a cold admission uses to establish its files manifest from the admitted predecessor chain by exact version. | region HA plan 1a | Plan 2 Task 10 `local`'s manifest step (6) (`.files-gen` alone until then); Plan 3 Task 5's admitted start (files from the manifest once C8 exists) | |
| 144 | |
| 145 | ### Shared contract types (coordination with claim Plan 6 Task 6) |
| 146 | |
| 147 | Claim Plan 6 (merged to `main` as d649d8d4, 2026-10-06) implements C1 to C4 and owns two files our plans also name. **Settled:** the types are on `main`; Plan 1 imports `provision.MoveInputs`, Plan 2 imports `moves.InstallManifest`, Plan 3 imports `moves.ErrAttemptOver`; none of our tasks defines them. Shapes verified identical against bf63e250 and d649d8d4. |
| 148 | |
| 149 | | type | claim Plan 6 file | our definition | shape check | |
| 150 | |---|---|---|---| |
| 151 | | `moves.InstallManifest` | `internal/moves/install_contract.go` | Plan 2 Task 8 | identical: `V int; Op, Host string; N uint64; SnapshotKey, SnapshotVersion string; SnapshotCRC64 uint64; AnchorKey, AnchorVersion string; AnchorCRC64 uint64; IntegrityCheck string; DBSHA256 string; BuiltAt time.Time`; snake_case json tags (final): `v`, `op`, `host`, `snapshot_txid` (Go field `N`), `snapshot_key`, `snapshot_version`, `snapshot_crc64`, `anchor_key`, `anchor_version`, `anchor_crc64`, `integrity_check`, `db_sha256`, `built_at`; Plan 2 Task 8 expects and tests them; spec rev 7.22 §7.6 names them | |
| 152 | | `moves.ErrAttemptOver` | `internal/moves/install_contract.go` | Plan 3 Task 1 | identity only (`errors.Is`); Plan 3 now uses claim Plan 6's message text | |
| 153 | | `provision.MoveInputs` | `internal/provision/moveinputs_contract.go` | Plan 1 Task 2 imports it (its own definition removed in rev 6) | identical fields and json tags: `bundle_etags`, `overlay_etag`, `release`, `files_plain`, `env_report`, all `omitempty` | |
| 154 | |
| 155 | Plans 1, 2 and 3 each carry the import rule at the definition site. |
| 156 | |
| 157 | ## Decisions locked across the series |
| 158 | |
| 159 | - **D1 Journals.** The record's `move` field carries the move's phase |
| 160 | journal (§7.2). The claim's `op.journal` carries what the claim half |
| 161 | must read with the same fresh read as an admission: `seal` (the JSON of |
| 162 | `moves.Seal`), `src_gen`, `source_stamp`, `cut_session`, `rev_phase`, |
| 163 | `evidence_source`, `evidence_target` (deployment-bucket keys, never the |
| 164 | evidence inlined; K13). |
| 165 | - **D2 zstd.** The archive is tar then zstd (§8.1). The repository pins no |
| 166 | zstd library; Plan 1 adds `github.com/klauspost/compress` (pure Go) as |
| 167 | the one new module dependency of the series. |
| 168 | - **D3 Staging-work lease.** `lease.AcquireTTL(ctx, deploymentBucket, "control/staging-leases/"+targetRegion+"/"+host, deploymentRegion, moves.StagingLeaseTTL)` is the claim's shared staging-work lease (§7.3), with `StagingLeaseTTL = 45 * time.Minute` (the ordinary 15-minute `LeaseTTL` cannot leave the signature's 15-minute life plus skew in front of `SafeUntil`). Every staging request goes through `moves.Guarded.Before(fn func(signAt time.Time) error)`, which checks `SafeUntil()` once and hands `fn` the very timestamp it checked; the staging mirror (`lease.NewStagingMirror`, hand-built SigV4 requests, no SDK retries, a 120 s request timeout, no clock of its own) signs each request exactly once at that `signAt`, so a pause of any length between the check and the signing yields a signature dated at the check, which S3 refuses once that date is 15 minutes old; the lane's own retry loop makes a new check for each new signature (Plan 1 rev 5, round-4 finding 3). The residual gap, a pause between the signing and the socket write, is the pause assumption the spec names (§7.3, model 14), bounded by the skew allowance and the request timeout. |
| 169 | - **D4 Seal versions from the copy responses.** `lease.MirrorPrefix` records each copy's destination `versionId` in the receipts through an optional `lease.VersionedCopier` interface that **only the guarded staging adapter implements** (`guardedMirror` over `*lease.StagingMirror`, Plan 1 Tasks 6 and 14; the ordinary `s3Mirror` does not, so the legacy path stays plain, rev 5), so the source never needs to read the target's `moves/*` (M17). The seal is the receipts' versions for the current source keys, and a receipt is trusted only when it matches the current source ETag and size **and** the current destination ETag; anything else is recopied, which yields a fresh version id. |
| 170 | - **D5 The sealed restore is litestream's own planner.** `moves.SealedClient` implements `litestream.ReplicaClient` over the target bucket's versioned store and serves exactly the sealed `(key, versionId)` pairs; `litestream.NewReplicaWithClient(nil, client).Restore` builds the database (the pattern `internal/lease/lease.go:404` uses). Nothing reads the current listing. |
| 171 | - **D6 Park kinds and destinations (rev 3, after platform-1e's interface check).** No new obligation kind and no new destination. A staging lane is a `park-replica` scoped to `moves/<host>/<op id>` (the grammar claim rev 6.23 §2 allows). The source's finalization park and a rolled-back target's cleaning park are `park-replica` scoped to the whole location `instances/<host>/<inc>`: in that region the location is no longer authoritative, so `park.Engine`'s live-set rule (`remote.go:346`) selects every version. A landing's residue from an earlier stay in the target region is parked as one `park-generation` chore per residue generation (`@root` for the origin), the same decision `park.Decide` makes for the at-rest sweep, because a whole-location park there could never acknowledge once the landing's generation is live. The destination is the engine's `parked/<incarnation>/<host>/<retirement id>/`, and F3 selects a move's retirements by the manifest's `Op`, not by path. Spec §6.4's `superseded/<op id>/...` wording is therefore a spec amendment for rev 7.20 ("the destination is the claim engine's, the op is in the manifest"); the invariants it served, one manifest per retirement event (D5), nothing live overwritten, INV-10, hold. Reason no new kind is needed: nothing in a move reads superseded data back (a dirty return seals the target's live installation before `cleaning` parks; a clean return restores from the source's live replica; F3 deletes after M7's hold, by manifest), so the engine's existing kinds and paths carry every property the spec asked of `superseded/`. |
| 172 | - **D7 Reconcile learns holds from the host package.** `provision.ReconcileConfig` gains `Hold func(Record) (status, op string, ok bool)` and `KeepParticipant func(host string) bool`; the host package supplies both from its claim reads. Reconcile itself never reads a claim. |
| 173 | - **D8 The console's move pass.** `movesRecoveryPass` (Plan 5) writes the claim half the record decided (§7.5) and runs the deadline sweeps. It is a plain function so claim Plan 5's `Recovery.Pass` can call it. |
| 174 | - **D9 `provision.MoveStart` joins `provision.MoveRequest`** in Plan 1 (rev 4, under D13): a move without an id, an incarnation and a cut mode has no journal to run, so the new path starts with `MoveStart`; `MoveFlip` and `MoveClose` join `MoveComplete` in Plan 2. The legacy transitions, the legacy worker and the old staging-wins landing (`promoteStagedReplica`) stay reachable and unchanged while the switch is off and are retired together in Plan 6 Task 8; no earlier task deletes or renames them. |
| 175 | - **D10 Flat landing only.** Phase 1 lands flat (§6.2, §21); a nested source is refused at the door and at every box step. |
| 176 | - **D11 The terminal outcome survives the close.** Every close CAS (`done`, `rolled_back`, `aborted`) writes `Record.LastMove = &MoveOutcome{ID, Outcome, At, OfflineS}` as it clears `Move`, so a CLI that missed the intermediate states reads the outcome of the move id it requested from `GET …/move` (`{"state":"none","last":{…}}`) and exits with the right code. |
| 177 | - **D12 Order within the series.** The copy gate (the record file and the ship check) is Plan 1 Task 15, so every later plan's strings are checked from the day they are written; the paging primitive `host.PageOperator` is Plan 5 Task 0, before the console pass that needs it; `carlos move`'s dispatch and its `docs/site/cli.md` heading land in one task (Plan 6 Task 1), so no commit fails the docsite gate. `registry.DeleteIfHold` is Plan 1 Task 12's (the `hold_op` task, built before the floor task that reads it), beside `ReleaseHold`; `hostclaim.ReleaseMove` is Plan 1 Task 3's (Plan 4 widens its legal states); `MoveGuard` is Plan 1 Task 2's; `RenderHeld`, `MaterializeHeld` and the strict spawn are Plan 1 Task 7b's; Plan 1 Task 11's host-side report pass is built inside Task 14 (it needs that task's harness). |
| 178 | - **D13 The operator-off switch.** Every Phase 1 entry point (the new request door and its siblings, the Phase 1 worker, the console's move pass, `carlos move`) is behind `CARLOS_MOVES_PHASE1=on`, read by the console and the agent, **off by default** (Plan 1 Task 0 lands it first). **Off means `main` behaves exactly as today**: the legacy move door (Instance role, `MoveRequest`), the legacy worker (preparing, ready, the staging-wins cut, aborting) and `promoteStagedReplica` stay reachable and unchanged, and every existing test keeps passing unchanged; the new code is unreachable. On: the Phase 1 path handles id'd records and refuses legacy ones. The legacy path is retired in one final task (Plan 6 Task 8) after Paul approves the copy record and the default flips. The switch is spec §15 step 4's dormancy made concrete: the series merges to `main` task by task without exposing a half-move, and the default stays off until every plan is merged and Paul's rehearsal order begins (Plan 7 P-1 turns it on per deployment). The copy gate ties to it: `TestPendingCopyIsNotWired` becomes `TestPendingCopyKeepsTheSwitchOff`: while any row of the copy record is `pending`, `moves.DefaultEnabled` must be `false`, and no task may flip it. Pending strings may therefore be wired as the tasks are built; nothing ships to an operator until Paul approves the record and the switch default changes in one commit. **One exception, by design: the protocol floor (§11) is armed by publishing `control/moves-protocol.json`, an operator action, not by the switch; unpublished, it changes nothing, and once armed it stays armed with the switch off (Plan 1 Task 13, rev 6).** |
| 179 | - **D15 One-box regions only (spec rev 7.20 §7.1, the coordinator's scope ruling).** The request door refuses a move into or out of a region with more than one eligible box (M-C21, pending Paul), computed with main's `eligibleBoxes` and `chooseBox` (`internal/console/boxchoice.go`), which main's tests exercise but no production door calls yet: Plan 1 Task 4's `selectMoveBoxes` is their first production caller and composes every input itself (`liveEdges`, `regionCands` for the AWS region, `boxid.Verifier` over `boxid.Load` with the new `Config.AWSAccount`, `expectedSecretsFP`, `lost` nil, `running` nil); `choose()` therefore names the region's one box, `chooseBox` is unchanged, and its tie-break is unreachable in Phase 1 (region HA owns the hash form, L15). The model stays at one box per region (spec §14.2.6); the two-box model is not built in this series. Plan 1 Task 4 carries the refusal; Plan 7's rehearsals run only in one-box regions. |
| 180 | - **D16 A source terminated before the flip (spec rev 7.20 §7.10).** The fence converts the `moving` op to `relocating` at `pinning` in one claim CAS (C7's claim-side helper); the moves side writes the record's `move.state = "relocating"` with `reason` and, in a one-box region, shows `waiting for a box in <region>` (M-C22) and pages once (Plan 5 Task 7, Plan 6 Tasks 2 and 5); the lane's `park-replica` runs under the target region as for an abort; the move's record clears when the relocation releases to `serving` (the console pass, on `serving` with `rec.Move.ID == op id`). Nothing in Phase 1 builds the relocation itself: that is region HA's. |
| 181 | - **D14 Cold only (P7, claim rev 6.27 §1.1).** No task passes `Mode: "warm"` or `PredecessorCertified: true` to `AdmitWriter`; no task writes `closed/<session>.json`; `KeepStamp` follows the library's rule unchanged. The budget already prices every attempt as a cold admission (Plan 1 Task 10); the per-wake `park-generation` chore is outside the cut window. |
| 182 | |
| 183 | ## Global constraints (every plan repeats these) |
| 184 | |
| 185 | - Gate before every commit: `go build ./...`, `go vet ./...`, `go test ./... -count=1`, `gofmt -l .` clean, `make test`; cross-compile `GOOS=darwin`, `GOOS=windows`, `GOOS=linux GOARCH=arm64` (AGENTS.md "Build & test"). Go uses its default build cache (`~/.cache/go-build`, writable inside the sandbox; never a cache under `/tmp`, the machine's rule); `GOFLAGS=-buildvcs=false` when the worktree's VCS metadata is unreadable from the sandbox. |
| 186 | - Cold only (P7, D14) and the operator-off switch (D13) in every plan. |
| 187 | - CPU policy: tests serial (`-p 1` when the whole tree runs), niced, no deep simulations. No new sweep of `internal/sim/hostlife` runs in these plans. |
| 188 | - Copy: every user-facing string goes through the copy-review gate before it ships; no em dashes anywhere (a full stop and a new sentence instead). Plan 6 Task 7 is the gate. |
| 189 | - Nothing rolls to production without Paul's go. Rehearsal order is M12's: the probe app, a disposable instance, kpmg (Plan 7). |
| 190 | - A new `carlos` command is not done until `docs/site/cli.md` names it (the `internal/docsite` gate). |
| 191 | - Wire shapes are additive: every new record field is `omitempty`, every new registry column has a default, every new claim journal key is a string. |
| 192 | |
| 193 | ## Review round 1 (Astra, codex, on 616f2499): dispositions |
| 194 | |
| 195 | The findings are committed unedited as `CODEX-PLAN-MOVES-PHASE1-R1.md` |
| 196 | beside this index. Verdict then: PLAN: NOT READY. Every finding was |
| 197 | verified against the pinned main checkout and accepted; where each is |
| 198 | fixed: |
| 199 | |
| 200 | | # | finding | fixed in | |
| 201 | |---|---|---| |
| 202 | | 1 | `MoveLanding` type and constant collide | Plan 1 Task 2: the journal type is `MoveLandingJournal` | |
| 203 | | 2 | `activator` importing `stopevidence` is a cycle; `a.cgroups` is no field | Plan 2 Task 3: `stopevidence.Reconstruct(ctx, ReconstructInputs{Cgroups: eb.Cgroups, …})` | |
| 204 | | 3 | `spawnClaimed` returns `(*exec.Cmd, *cgroup.Prepared, error)` | Index interfaces, Plan 3 Task 5: the handle is kept and cleaned on every exit path | |
| 205 | | 4 | a receipt's version trusted on ETag equality alone | Plan 1 Task 5 and D4: a receipt vouches only when it matches source and destination completely; else recopy | |
| 206 | | 5 | a 15-minute lease never passes the guard | Plan 1 Task 6 and D3: `moves.StagingLeaseTTL` 45 min, `lease.AcquireTTL`, a test on real settings | |
| 207 | | 6 | the SDK signs at send and re-signs retries | Plan 1 Task 6 and D3: `lease.NewStagingMirror` with no SDK retries and a 120 s timeout; one request per `Before`; lane retries re-check | |
| 208 | | 7 | `move.json` replay needed a forbidden read | Plan 1 Task 14, Review Focus 2: ledger intent first; `ErrExists` with a pending intent is our own PUT; without, refuse; the target validates | |
| 209 | | 8 | an application file named `env` collides with the overlay | Plan 1 Task 7: `files/` and `env` namespaces, duplicate destinations refused, round-trip test | |
| 210 | | 9 | T9 needs the target box in `touched` before the flip | Plan 1 Task 14: the first `Touch` adds the target region and box | |
| 211 | | 10 | `Put(..., "")` is create-only; reports could not refresh | Plan 1 Task 11 `moves.PutReplace`, Task 12, Plan 6 Task 3 corrected | |
| 212 | | 11 | the floor failed open on a read error; the door ignored completeness | Plan 1 Task 12: the floor is armed locally; unknown refuses a participant; the door needs `readEdgesSnapshot(...).complete()` | |
| 213 | | 12 | stop evidence published before its final proof | Plan 2 Task 5: `stopping` keeps steps 1 to 4 local; the complete report is written at `stopped` before `ExecToNone` | |
| 214 | | 13 | `archived` persisted before the archive was in the seal | Plan 2 Task 5: publication and the seal complete before the phase CAS; the flip checks `seal.Files` | |
| 215 | | 14 | a paused tick could slip the drain | Plan 2 Task 6: the hold check runs after `claim`; `TestDrainRaceCannotAdvanceBookkeeping` | |
| 216 | | 15 | the LTX encoder refuses the lock page | Plan 2 Task 8: `ltx.LockPgno` skipped, `Commit` kept, a >1 GiB sparse test | |
| 217 | | 16 | a replayed build restored over an existing file | Plan 2 Task 8: the scratch database is removed before every restore | |
| 218 | | 17 | `RetireArtifacts` without a persisted chore does nothing | Plan 2 Tasks 9 and 10: `QueueLocalPark` and `RecordInventory` at `checking`; the test asserts the filesystem | |
| 219 | | 18 | the first probe's start check compared against the source generation | Plan 3 Tasks 4 and 5: `PreparedCheck` before admission; `StartCheck` after the CAS inside the admitted sequence | |
| 220 | | 19 | the claim never reached `probing`/`probed` | Plan 2 Task 10 and Plan 3 Task 6: every landing phase mirrors into the claim with `SetMovePhase`; the commit test drives it | |
| 221 | | 20 | declare-lost could override a winning commit | Plan 4 Task 10: the record decides first (`MoveRollBack`, `MoveRollbackTargetLost`), the claim follows | |
| 222 | | 21 | the loss payload did not match claim Plan 8 | Plan 4 Task 10: the exact `LossRecord`, `LossCloudObservation`, `LossOperatorAttestation`, `LossConsequences` fields | |
| 223 | | 22 | recovery could not consume the declaration result | Plan 4 Task 2: `ReturnToSource` accepts exec `"none"` and a `loss.<id>` journal; Task 10 runs the source pass against a real declaration | |
| 224 | | 23 | orphan claims were never visited | Plan 5 Task 7: claim discovery from `control/hosts/`, `ReleaseOrphanMove` | |
| 225 | | 24 | the CLI inferred the outcome from the last state seen | D11 and Plans 1, 3, 4, 5, 6: `Record.LastMove`, `GET …/move` returns `last`, the CLI reads it | |
| 226 | | 25 | the probe app's launch contract differed from the backend | Plan 7 Task 1: `--socket`, `--db`, `TestMoveprobeLaunchContract` | |
| 227 | | 26 | activation depends on claim Plan 8b | Index interfaces: 8b's prerequisites named; declare-lost stays refused until 8b lands | |
| 228 | | 27 | forbidden transitions requested | Plan 4 Task 1: `MoveStuck` from `restoring`/`verified`, `MoveRollbackTargetLost` | |
| 229 | | 28 | reverting had no staging cleanup producer | Plan 2 Task 2 `Revert` queues the park; Plan 4 Task 6 waits; Plan 5 Task 1 dispatches | |
| 230 | | 29 | the MinIO test would never run in CI | Plan 1 Task 11: added to `30-minio-contract`; failing cases for the stubbed hooks | |
| 231 | | 30 | task-order cycle and a failing docsite commit | D12: `PageOperator` is Plan 5 Task 0; dispatch and heading in Plan 6 Task 1; the copy gate is Plan 1 Task 15 | |
| 232 | | 31 | the copy check needed an independent inventory | Plan 1 Task 15: `movesCopyIDs` pinned, negative fixtures | |
| 233 | | 32 | the clean-rollback rehearsal wrote before refusing | Plan 7 Tasks 1 and 3: the before-write mode suppresses every mutation | |
| 234 | |
| 235 | ## Review round 2 (Astra, on da2832aa): dispositions (plan rev 3) |
| 236 | |
| 237 | Findings file: `~/.cache/nearest-region/CODEX-PLAN-MOVES-PHASE1-R2.md` |
| 238 | (the coordinator's record; not re-reviewed, per the coordinator). The |
| 239 | locked design stands; the work split into local fixes (rev 3) and |
| 240 | contracts (the BLOCKED-ON-CLAIM table above). |
| 241 | |
| 242 | | # | finding | rev 3 | |
| 243 | |---|---|---| |
| 244 | | 1 | `complete()` needs `BoxesComplete`, which `readEdgesSnapshot` never sets | Plan 1 Task 4: the door checks `snap.Complete` (every edges file read), the evidence the floor needs; liveness is not the floor's question | |
| 245 | | 2 | `MoveStuck` constant and function collide; a two-argument `Revert` call remained | Plan 4 Task 1: the transition is `MoveMarkStuck`; Plan 2 Task 5's call takes the five arguments | |
| 246 | | 3 | `Claim.Validate` rejects the new kinds | D6: no new kinds; `park-replica` scopes and `park-generation` chores; `Engine.Parked` stays | |
| 247 | | 4 | the dependency graph understated the integration | the BLOCKED-ON-CLAIM table and platform-1e's work order; `DeleteIfHold` moved to Plan 1 Task 13 | |
| 248 | | 5 | intermediate merges exposed half-moves | D13: the operator-off switch `CARLOS_MOVES_PHASE1`; the interim abort releases the claim (`ReleaseMove` in Plan 1 Task 3) | |
| 249 | | 6 | return-lane writes outside the lease and signing contract | Plan 4 Task 4: the lane is written under the source region's staging lease through the guarded staging mirror; the bound is the return seal's | |
| 250 | | 7 | `pending` before the PUT defeats the foreign refusal | Plan 1 Task 14: the target validates `move.json` (its own bucket) in T9 and at `checking`; the source's ledger decides only its own replay; a foreign object is caught by the target, never by the source guessing | |
| 251 | | 8 | inventory registration against the real path/stamp contract | Plan 2 Task 10: relative paths; the old stay's entries retired by their own tag before the new installation registers under F; `.gen` included | |
| 252 | | 9 | the landing held the lease the engine acquires | Plan 2 Task 10: the location lease is taken after `parking`; the acknowledgement half is C4 | |
| 253 | | 10 | a whole-location park could not finish once the new generation was live | D6: residue is parked per generation (`park-generation`) | |
| 254 | | 11 | record/claim phase mirroring wedged on a half write; `ExecToNone` replay | Plan 2 Task 10 `syncClaimPhase` catch-up; Plan 2 Task 2 `ExecToNone` idempotent on a matching journal | |
| 255 | | 12 | the finalization stamp could be lost | Plan 3 Task 8: `row-deleted` in the op journal before the delete; the entry condition reads it | |
| 256 | | 13 | dirty-return probes rejected their prepared generation | Plan 3 Task 5: `Op`, `HoldOp` and `PreparedGeneration` distinguished; `PreparedCheck` for every prepared install | |
| 257 | | 14 | the launch-input pin was never enforced at spawn | Plan 3 Task 5 step 2: render, then compare with `move.inputs` (landing) or record `recovery_inputs` (recovery) | |
| 258 | | 15 | declare-lost rewrote the state its legality check read | Plan 4 Tasks 1, 10: a distinct `lost` target phase the ordinary recovery cannot consume | |
| 259 | | 16 | the loss flow cannot pass the real Plan 8 API | C6; Plan 4 Task 10 BLOCKED-ON-CLAIM | |
| 260 | | 17 | case (c) performed regional retirement | Plan 4 Task 10: a separate consequence path | |
| 261 | | 18 | `TargetParkedAt` had no surviving producer after a loss | Plan 4 Task 10 and Plan 5 Task 7: the console pass stamps it when no chore owned by the lost box or its region remains under the op | |
| 262 | | 19 | the readiness window started before the restore | Plan 3 Tasks 1, 6: `Opened` is the first app start, written by the probe; attempts are accounted separately | |
| 263 | | 20 | randomized sealing broke the replay identity | Plan 2 Task 5: the archive identity (manifest, sealed key, temp file) is journaled before any PUT | |
| 264 | | 21 | the converger could write after the barrier | Plan 2 Task 6: a shared `schedule.HostGate` the runner, the converger and the cut all take | |
| 265 | | 22 | T11 used example rates | Plan 1 Task 11: measured rates with a one-hour freshness; missing measurements fail T11 | |
| 266 | | 23 | log keys gave no sequencing guarantee | Plan 6 Task 3: transition-identity keys, create-only | |
| 267 | | 24 | copy approval and prerequisites after their consumers | D13 (the switch ties approval to enablement); `hostguard.Capture` returns `(Captured, error)`; the lease fixture is `leasetest.NewVersionedBucket` + `NewS3Server`; `ProtocolVersion` stub stays in Task 4 | |
| 268 | | 25 | the loss rehearsal lacked its prerequisites | Plan 7 P-6: Plan 8b, C5, the switch | |
| 269 | | R1-6 | the SDK signs at send | Plan 1 Task 6: the staging mirror signs each S3 REST request by hand with the SDK's SigV4 `Signer.SignHTTP` at the check's clock (`signingTime`), no presigner (S3 v1.105.0 has none for copies), and sends once with no retry | |
| 270 | | R1-8 | `Extract` lacked duplicate-destination rejection | Plan 1 Task 7 | |
| 271 | | R1-11 | unknown-floor participation missed preparing participants | Plan 1 Task 12: a participant is a held row **or** a record whose move names this box | |
| 272 | | R1-27 | Task 7's retry contradicted Task 1 | Plan 4 Task 7 resumes at `StuckAt` | |
| 273 | |
| 274 | ## Plan 1 review (Astra, on d5716bf4): dispositions (Plan 1 rev 4) |
| 275 | |
| 276 | Findings file: `~/.cache/nearest-region/CODEX-PLAN1-REVIEW.md` (the |
| 277 | coordinator's record). All fourteen fixed in Plan 1 rev 4: |
| 278 | |
| 279 | | # | finding | rev 4 | |
| 280 | |---|---|---| |
| 281 | | 1 | the switch did not protect intermediate commits; off changed main's behaviour | Task 0 lands the switch first with `moves.DefaultEnabled`, `Config.MovesEnabled`, `moveCfg.Enabled`; D13 redefined: off = today's legacy path unchanged and reachable; the legacy path is retired in Plan 6 Task 8 | |
| 282 | | 2 | deleted consumers and a duplicate `servingClaim` | Task 2 keeps `MoveRequest` and `MoveComplete` and their tests; the hostclaim fixture is `movingTestClaim` | |
| 283 | | 3 | no copy presigner in S3 v1.105.0 | Task 6 signs each REST request with `v4.Signer.SignHTTP` at the check's clock | |
| 284 | | 4 | Task 5 broke plain mirroring | the plain `MirrorStore` path is unchanged; the receipt rule applies only to a `VersionedCopier` | |
| 285 | | 5 | the source copy was not bound to the claim | Task 14: `moves.ReplicaSource(holder)` and `lease.MirrorPrefixKeep`; legacy root handling; a multi-incarnation test | |
| 286 | | 6 | staging writes bypassed the guard; revalidation before the lease | Task 14: one guarded path for every write class including `move.json` and the `probe/` object; lease first, then the claim read | |
| 287 | | 7 | worker transitions lacked op identity | Task 2's `MoveGuard` and the guarded promote, cut, abort and abort-complete; `TestRoundNeverAdvancesAReplacementMove` | |
| 288 | | 8 | red-after-fix tests | the legacy fixture uses `MoveRequest`; `putSized` and `Changed`; the lease test is arithmetic plus `RenewMargin`, `AcquireTTL` under lease's own S3-gated tests; the signing test uses `leasetest`'s server and the `X-Amz-Date` header; the legacy API test stays, a Phase 1 twin releases the claim between requests | |
| 289 | | 9 | launch composition differed from `Start`'s | Task 9 composes through `agentOwnedVars(rt, fileEnv, haveControl)` and tests removals; `CompareEnv` reports a missing region and layout names absent from both | |
| 290 | | 10 | T5 did not verify the archive | Task 7's `archive.Verify` and its negative tests | |
| 291 | | 11 | extraction changed modes | Task 7 restores exact file and directory modes, deepest directory last | |
| 292 | | 12 | forward dependency and the rejected completeness check | the `hold_op` task is Task 12, before the floor task 13; `snap.Complete` only | |
| 293 | | 13 | target selection ignored rev 7.19 | Task 4 uses `eligibleBoxes` and `chooseBox` with the approved SHA-256 tie-break, refuses multi-box regions (D15), binds T8 and T9 to `op.intent.target` | |
| 294 | | 14 | contradictory Task 14 tests | the source-refusal test is gone; the target validates the lane; the self-review note matches | |
| 295 | |
| 296 | Also from the review: T7 is `release.MaterializeHeld` and S2 is the strict next spawn, both real in Task 7b; S1 wakes a hibernating source through the box's own edge on loopback. |
| 297 | |
| 298 | ## Plan 1 review round 4 (Astra, on d0907747): dispositions (Plan 1 rev 5) |
| 299 | |
| 300 | `CODEX-PLAN1-REVIEW-R4.md` beside this file: PLAN 1 NOT READY TO BUILD, 6 P1, 8 P2, 1 P3. Every finding fixed in Plan 1 rev 5; no second review was run (the coordinator's instruction: fix once and stop). |
| 301 | |
| 302 | | # | finding | disposition | |
| 303 | |---|---|---| |
| 304 | | 1 | P1 Task 4's selection not buildable: `snapshotFor` undefined, `eligibleBoxes` takes seven arguments, no production caller on main, `chooseBox` has no host argument | **Fixed.** `selectMoveBoxes` is a complete adapter: `liveEdges` (the live snapshot `complete()` needs), `boxid.Load` + `boxid.Verifier{Account: cfg.AWSAccount, Certs}` per box, `regionCands` for the AWS region, `HomeRegion`, `expectedSecretsFP`, `lost` nil (C6), `running` nil; `chooseBox` unchanged, its tie-break unreachable under the one-box refusal; tests with real signed `boxid` records. `Config.AWSAccount` (`CARLOS_AWS_ACCOUNT`) is new and named for the roll notes. D15 updated | |
| 305 | | 2 | P1 Task 7b consumes `moves.Check` before Task 11; `Cgroups` is concrete | **Fixed.** `moves.Check` is Task 1's (`check.go`); the strict spawn goes through an unexported `prepareStrict` seam over a `strictProc` interface `*cgroup.Prepared` satisfies; `Cgroups` stays `*cgroup.Manager` | |
| 306 | | 3 | P1 signing time later than the lease check | **Fixed.** `Guarded.Before(fn func(signAt time.Time) error)` hands fn the checked timestamp; every `StagingMirror` mutation is an `At` method taking it; the mirror has no clock; the signing test pauses past the lease between check and signing and asserts the check's `X-Amz-Date`, then a refused third `Before` | |
| 307 | | 4 | P1 generation-source prefix wrong for valid locations | **Fixed.** `ReplicaSource` normalizes `TrimSuffix(Location, "/") + "/"` as `genselect.requestLocation` does; the test's holder comes through `hostclaim.Update`; every fixture location lost its trailing slash | |
| 308 | | 5 | P1 the floor misses the units' direct `carlos.prev` exec | **Fixed.** The agent's hold-prev marker `/data/.moves/hold-prev` (participant and no floor, or `.prev` below the floor); both units' `ExecStart` conditions skip `.prev` under it; unit assertions and a shell-level test | |
| 309 | | 6 | P1 T7 and the floor file write `/opt` from the sandboxed agent | **Fixed.** `MaterializeHeld` returns `ErrStageBlocked`; the pass relays through the existing `stage-requested` marker and `carlos adopt --stage-only` runs `release.ServeHeldStageRequests`; T7 passes on `LiveVersion` only; the floor file moved to `<DataDir>/.moves/floor.json` | |
| 310 | | 7 | P2 `s3Mirror` as `VersionedCopier` changes the legacy path; Task 13 undefined dormancy | **Fixed.** `s3Mirror` implements neither interface (pinned by test); the only producer is the staging mirror through `guardedMirror`; console record and floor arming run only with the switch on, pinned by tests | |
| 311 | | 8 | P2 `Verify` hashes the compressed stream | **Fixed.** `h` hashes the decoded tar, trailer drained; sealed digest checked; pipe drained to EOF so `Open`'s terminal authentication is the verdict; tamper tests for a flipped last byte and a cut final chunk | |
| 312 | | 9 | P2 fixtures fail correct implementations | **Fixed.** `AddBucket("data-uk")`; leasetest's server stores a plain PUT as a new version (its own test); `writeServingClaim` and `movingTestClaim` build valid claims and assert `Validate()` | |
| 313 | | 10 | P2 Task 9 misstates control availability and `CARLOS_PURGE` | **Fixed.** Control is always attempted (`haveControl` true pre-spawn, documented); `CARLOS_PURGE` is an advisory class whose presence difference is a note, never a failure; fixtures corrected; the test builds its `ExecBackend` explicitly | |
| 314 | | 11 | P2 S1 can pass from the cache | **Fixed.** `(*edge.Edge).ServeUncached` with a context marker that disables the cache lookup and store while the hibernating branch still wakes; `ReadyProbe` set from `RunWithActivatorReady`'s callback; the cached-200-beside-failing-wake test | |
| 315 | | 12 | P2 readiness input versions lack producers | **Fixed.** `appconfig.LoadMergedHostVersions` returns the etags of the exact objects merged; `SourceReport.Archive` publishes the archive keys and versions the source's PUT responses returned; T5 reads by those versions | |
| 316 | | 13 | P2 private-storage refusal checks the policy | **Fixed.** The door and the round check the instance's store binding (`objstore.GetIn` under `rec.Environment` or `rec.Slug`, as the create door stamps it); both manifest and store reads fail closed with 503 | |
| 317 | | 14 | P2 inventory rejects M-C21/M-C22; record absent from main | **Fixed.** Inventory `M-C1` to `M-C22`; Task 15's first step copies the record from the design branch by sha | |
| 318 | | 15 | P3 D9, Task 2 and Task 14 contradict D13 | **Fixed.** D9 rewritten; Task 2's file list and commit text keep every legacy caller; Task 14 names only the guarded transitions | |
| 319 | |
| 320 | ## Plan 1 review round 5 (Astra, on 80647eeb): dispositions (Plan 1 rev 6) |
| 321 | |
| 322 | `CODEX-PLAN1-REVIEW-R5.md` beside this file: PLAN 1 NOT READY TO BUILD, 3 P1, 10 P2, 1 P3. Every finding fixed in Plan 1 rev 6 and verified against `origin/main` at 1c43c2ce. |
| 323 | |
| 324 | | # | finding | disposition | |
| 325 | |---|---|---| |
| 326 | | 1 | P1 build failures in the task sequence: undefined `testHolderKey`; `Check` declared twice; `MoveGuardedCut(g, now)` against a one-argument definition; `instanceStoreEnv` console-local but used from `host` | **Fixed.** The fixture uses the record key grammar `create_test.go:14` uses; Task 11's duplicate removed; Task 14 calls `MoveGuardedCut(g)`; `provision.InstanceStoreEnv(*Record)` in Task 2 serves both packages. Also found and fixed: Task 4 read `moves.ProtocolVersion`/`ReadFloor` before Task 13 defined them; `floor.go` is now Task 1's | |
| 327 | | 2 | P1 the root staging relay has no operation-authority check | **Fixed.** `ServeHeldStageRequests(ctx, s, optDir, dataDir, authorize)`: every request component validated before any path join (`Host == file name`, `ValidHostName`, `ValidAccount`, `ValidApp`, `ValidVersion`); `heldStageAuthority` in `host` checks `hold_op`, the claim's op id, kind, target and this box, and the record's pinned release; stale and traversal tests; no `pub` parameter | |
| 328 | | 3 | P1 the hold-prev marker left recovery free to run an old `.prev` after the console committed | **Fixed, redesigned.** The marker is withdrawn. Root writes `/data/.moves/floor.{json,min-release}` before staging and `decidePrev` writes `carlos.prev.version` before the journal; both units select `.prev` only when `prev_ok` (a `sort -V` comparison in the `ExecStart` line) holds; the door admits only when every participant's edges file shows the armed floor and a `prev_release` at or above it, so nothing asynchronous follows admission; shell-level and crash-boundary tests | |
| 329 | | 4 | P2 default-off system apply contradicted the dormant boundary | **Fixed.** The boundary is the floor object's existence, not the switch (D13 names the exception): `floorAllows` reads only the local file, the registry is consulted only by `rollback`'s hold rule, an armed floor stays armed with the switch off; `TestFloorIsDormantWithoutTheObject` covers both cases | |
| 330 | | 5 | P2 `fs.ErrPermission` misses `EROFS` | **Fixed.** Main's `stageBlocked` (`adopt.go:250`) exported and reused; the test injects `EROFS` through the `materializeHeldStage` seam; the `chmod` fixture dropped | |
| 331 | | 6 | P2 Task 4's HTTP fixtures cannot reach their outcomes | **Fixed.** `fleetFixture` writes production-shaped edges files, fresh status snapshots and `boxidtest`-signed identity records, sets `SecretsPub`, `AWSAccount` and `boxCerts`; shared by the door tests and the selector test | |
| 332 | | 7 | P2 selection permits an absent source | **Fixed.** `selectMoveBoxes` returns `Source`; the door requires it and requires `Op.Exec == Source`; the floor requires files in both regions; missing-source and different-executor tests | |
| 333 | | 8 | P2 the multipart receipt-match branch unspecified | **Fixed.** When versioned, the receipt match requires `DstVersionID` and records it; a two-round multipart fixture and an old receipt without a version | |
| 334 | | 9 | P2 `Ready` mixes input generations; T3 loader contradiction; shadow reporting | **Fixed.** `Ready` requires `target.Inputs.FilesPlain == source.FilesPlain` and `Inputs.EnvReport == source.Env.Digest`; T3 uses `LoadMergedHostVersions`; `MergedVersions.Shadowed` → `TargetReport.Notes`, asserted | |
| 335 | | 10 | P2 interim abort's claim-release crash boundary | **Fixed.** `ReleaseMove` leaves `Journal["released_move"]` on the serving op; `RequireReleasedMove(id)` is the replay rule; crash-and-retry test | |
| 336 | | 11 | P2 dir-store lease fallback cannot satisfy the guard | **Fixed.** No deployment bucket → Phase 1 rounds refuse with an S1 reason; tests use the lease fake | |
| 337 | | 12 | P2 `Verify` leaks its producer | **Fixed.** Deferred `pr.CloseWithError` and a wait on the producer's `done`; goroutine-count test on a multi-chunk early mismatch | |
| 338 | | 13 | P2 commit commands omit files | **Fixed.** Tasks 1, 4, 6, 13 and 14 stage every directory they touch | |
| 339 | | 14 | P3 competing instructions | **Fixed.** The goal names spec rev 7.22, claim 6.30 and `main` 1c43c2ce; the door's order paragraph matches the code; Task 9's interface prose lists the advisory class; Task 13 names the live read; `release.LiveVersion` is marked as existing (`versions.go:290`) | |
| 340 | |
| 341 | ## Plan 1 review round 6 (Astra, on 296807a2): dispositions (Plan 1 rev 7) |
| 342 | |
| 343 | `CODEX-PLAN1-REVIEW-R6.md` beside this file: PLAN 1 NOT READY TO BUILD, 4 P1, 4 P2, 1 P3. Every finding fixed in Plan 1 rev 7. This was the one authorised round after rev 6; per the coordinator's instruction the fixes were made once and no further round was run. |
| 344 | |
| 345 | | # | finding | disposition | |
| 346 | |---|---|---| |
| 347 | | 1 | P1 fields consumed before their task: the console reader fields, `hold_op` before Task 12; `ErrOpMismatch`; `fleetBox.FloorArmed` | **Fixed.** Task 4 adds the four reader fields to `edgesFilePlacementView`; the build order (header) puts Task 12 before Task 7b; `ErrStaleOp` (main `ops.go:9`); `fleetBox` declares `FloorUnarmed` | |
| 348 | | 2 | P1 `boxidtest` import cycle | **Fixed.** `boxid_test.go` is left as it is; `boxidtest` is an exported twin, duplication accepted | |
| 349 | | 3 | P1 T7's relay needs state that exists only after readiness | **Fixed.** `heldStageAuthority` accepts an absent registry row during `preparing` and takes the pinned release from `SourceReport.Release` (new: the source's `LiveVersion`, the identity producer; Plan 2's admission copies it into `Inputs.Release`); `TestPreparingRelaysHeldStageOnAHardenedBox` runs the whole cycle from a `MoveStart` record with no target row | |
| 350 | | 4 | P1 `%s` in `ExecStart`, `sort -V` semantics, a test that bypassed systemd | **Fixed, redesigned.** Root writes a verdict file `carlos.prev.allowed` (the floor `.prev` was judged at or above, by `ReleaseAtOrAbove`, with no hold row); the units compare it to the current floor with `cmp -s`, no `%`, no `$`; the test asserts the absence of both expansions, then runs the body | |
| 351 | | 5 | P2 the downgrade prohibition covered only `rollback` | **Fixed.** `activationAllowed` applies both halves of §11 in `install`, `rollback` and `restoreFiles`; the verdict is withheld under a hold; `TestHeldParticipantRefusesEveryDowngrade` | |
| 352 | | 6 | P2 `Open` accepts trailing ciphertext | **Fixed.** The final chunk must end the stream (`ReadFull` must return `io.EOF`); appended-byte and concatenated-archive tests | |
| 353 | | 7 | P2 shadowing reported at the wrong layer | **Fixed.** `LaunchEnvironment` returns the names the carried `<host>.env` overrides in the rendered configuration (`instanceEnvParts`); `MergedVersions.Shadowed` dropped; the test uses no instance overlay | |
| 354 | | 8 | P2 commit commands | **Fixed.** Task 11 commits the pure package alone; Task 14 creates and commits the host report files, the MinIO test and the `30-minio-contract` amendment, and stages `internal/lease` | |
| 355 | | 9 | P3 superseded instructions | **Fixed.** D3 and D4 rewritten; the Go cache line follows the machine's rule (default cache) | |
| 356 | |
| 357 | ## The model's limit (rev 7.20) |
| 358 | |
| 359 | `internal/sim/hostlife` stays at one box per region plus replacement |
| 360 | boxes. It gains rule `fence-converts-moving` and witness |
| 361 | `wFenceConvertsMoving` (a source terminated before the flip in a one-box |
| 362 | region: conversion to `relocating`/`pinning`, no churn and one alert |
| 363 | while no box exists, completion on the replacement box). It does not |
| 364 | model `choose()`, two target replacements, `stuck` after them, or the |
| 365 | files publisher (it takes the fenced source's files directory as |
| 366 | published). The two-box model is region HA's, on its own branch. |
| 367 | |
| 368 | ## What the model proved and the plans port |
| 369 | |
| 370 | Witnesses that become real tests, by plan: Plan 2 ports `wSealByVersion`, |
| 371 | `wMarkersLast`, `wBuildCrashes`, `wEvidenceWholeTree`, `wTimerFreeze`; |
| 372 | Plan 3 ports `wProbeWindowAt` (`TestProbeWindow`), `wCommitBeforeOpen`, |
| 373 | `wCommitCoverage`, `wCommitInterrupted`, `wCommitReplayed`; Plan 4 ports |
| 374 | `wSealReturn`, `wCleanReturnIdentity`, `wRollbackJournal`, |
| 375 | `wRecoveryInputs`, `wDirtyReturn`, `wReturnLostRecord`, |
| 376 | `wRollbackBaselinePreserved`, `wTwoSessionDeclaration`, |
| 377 | `wNoDuplicateDeclarationEarly`, `wNoDuplicateDeclarationLate`, |
| 378 | `wDeclareLostCompute`, `wLostQuarantine`, `wLostReturnPark`; Plan 5 ports |
| 379 | `wStagingScopeDeadline`, `wParkScopeSurvivesRestart`, |
| 380 | `wSweepScopeMembership`, `wStagingQuiet`, `wFreeSweepVsCreate`. Each |
| 381 | cites its witness in the test's doc comment. |
| 382 | |