carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

Moves Phase 1 Implementation Plans: Index

> For agentic workers: this file is the map. Each plan it names is a > self-contained implementation plan with the superpowers header; execute > them in the order below with superpowers:subagent-driven-development or > superpowers:executing-plans. Do not start a plan whose dependencies are > not merged on main.

Goal: Build the move-any-app Phase 1 design (spec rev 7.22, e1d7c3f1; claim rev 6.30, 73b1b9dc) on main at d649d8d4 or later (claim Plan 6, the box wiring for C1 to C4, merged 2026-10-06), as an ordered series of plans that each produce working, tested, mergeable software. (The series began against spec rev 7.18 on main 67547f5; the revisions since are in the spec's header and the dispositions below.)

Spec: docs/superpowers/specs/2026-10-02-move-any-app-design.md (rev 7.20: P7 cold-only, region HA L13 to L17 folded in with claim rev 6.28's fence reference and pre-flip conversion; L18 stated as a limit; one-box regions only). The executable model is internal/sim/hostlife/ at 9c417aaa on the same branch; its witness names are cited in the tests the plans port.

Claim library on main (built on, never re-implemented): internal/hostclaim (E, rev 6.27 semantics; 989ee349 §1.1 records the #236 rulings), internal/genselect (F), internal/hostguard and internal/stopevidence (claim Plan 3), internal/park (claim Plan 4), internal/console/apidbclaim.go (claim Plan 7).

The series

| # | plan | produces | needs from claim plans 5, 6, 8 | |---|---|---|---| | 1 | 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 | | 2 | 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 | | 3 | 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 | | 4 | 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 | | 5 | 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 | | 6 | 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) | | 7 | 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 | | home part 2 | 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 |

Plans 1 and 2 have no unmerged dependency and are built first, in order. Plan 3's pure tasks (1 to 4, 8) and Plan 4's pure tasks (1 to 8) can be built before claim Plans 6 and 8 land; the index marks every task that cannot. Plans 5 and 6 depend only on Plans 1 to 4 for code; Plan 5 notes the runner it hands its chores to.

Dependency order

main (67547f5+) ─► Plan 1 ─► Plan 2 ─► Plan 3 (1-4, 8) ─► Plan 4 (1-8) ─► Plan 5 ─► Plan 6 ─► Plan 7
                                        │                   │
          claim Plan 6 (feat/hostclaim-box) ─► Plan 3 (5-7) ─► Plan 4 (9)
          claim Plan 8 (feat/hostclaim-leases) ─────────────► Plan 4 (10)
          claim Plan 5 (feat/hostclaim-console) ─────────────► Plan 5 (console pass joins Recovery.Pass; adoption of legacy hosts before any rehearsal)

Exact interfaces needed from claim Plans 5, 6 and 8 (for platform-1e)

These are the names the moves plans call. Each is quoted from the plan that defines it; if a plan changes a name, the moves plan that uses it changes with it.

From claim Plan 6 (feat/hostclaim-box, package internal/hostbox, internal/hostwrite, activator and schedule adapters):

  • 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 Baseline(ctx, observed) (*genselect.Plan, error), Admit(ctx, observed, in hostclaim.Admission, baseline *genselect.Plan) (hostclaim.Observed, error), Published(ctx, observed) (hostclaim.Observed, error). Plan 3 Task 5 (ProbeWake) admits the probe through Admit and publishes through Published; it supplies AdmissionEvidence that checks the landing's journal (the prepared generation's install manifest) and PublicationEvidence that checks generation.json by If-Match.
  • hostbox.GenerationPrefix(holder hostclaim.Holder) (string, error).
  • The claim-aware start sequence Plan 6 Task 9 wires into Service.wake (purge, restore of the admitted baseline, start, readiness, first upload, marker, PublishGeneration, open). Plan 3 Task 6 requires that sequence to be callable for a held row with a probe policy: moves asks Plan 6 for one exported entry, (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.
  • hostbox.Regional.Run runs park-replica chores whose scope is a moves/<host>/<op id> lane or a whole location, and park-generation chores, under a move or rollback op id (platform-1e work order item 1: "Regional.Run for park-replica moves scopes"); no new kind (D6).
  • 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.
  • 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).
  • schedule.SelectedTimers (the timer-control adapter): Plan 2 Task 9's ReplaceTimers call on the target runs under the landing's own selection, so the adapter must accept a held row's write when the selection names the landing op.

From claim Plan 8 (feat/hostclaim-leases, package internal/hostclaim additions, internal/hostexec, internal/hostquarantine, internal/hostfence):

  • 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}.
  • 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).
  • hostclaim.DeclareLost(ctx, s release.Store, pub ed25519.PublicKey, observed Observed, lossID string, owner LossOperationOwner) (Observed, error).
  • hostclaim.LossRecord, CreateLossRecord(ctx, s, sg release.Signer, record LossRecord) (ObservedLossRecord, error), ReadLossRecord, ReplaceLossRecord, LossCloudObservation, LossOperatorAttestation, TimerLoss, LossConsequences, ObligationRef.
  • 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).
  • hostclaim.Readmit(box string, quarantineRevision uint64) func(*Claim) error and the hostquarantine retirement a returning box runs before --readmit (§7.10).
  • 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).
  • 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.
  • 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.

From claim Plan 5 (feat/hostclaim-console, package internal/hostops):

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

BLOCKED-ON-CLAIM contracts (rev 3)

Platform-1e's work order (2026-10-06): (1) claim Plan 6 Tasks 6 to 11 built to the signatures below: startAdmitted, K6 in the wake admission, SelectedTimers for held rows, Regional.Run for park-replica moves scopes; unknown kinds stay refused. (2) Plan 8b: roster, census, release identity and guard, the termination fence, the real declare-lost and readmit. (3) Plan 5 Tasks 5 to 9: the move, flip, commit, rollback and delete transitions, plus the delete's two parks. Claim rev 6.27 (989ee349) §1.1 records Paul's #236 rulings; P7 (cold-only) binds every plan here.

Every task marked BLOCKED-ON-CLAIM names one of these contracts and is not built until the contract is merged on main; the moves plans do not guess the API beyond what is written here.

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), C5 is claim Plan 5, C6 and C7 are claim Plans 8 and 8b, C8 is region HA's (plan 1a). Claim rev 6.28 (4ffe8a0d) adopts region HA's L1 to L20; spec rev 7.20 aligns with it (the fence reference, the pre-flip source conversion, L13, the one-box scope).

| id | contract moves needs | from | blocks | |---|---|---|---| | 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 | | 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) | | 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) | | 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) | | 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 | | 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 | | 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 | | 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) |

Shared contract types (coordination with claim Plan 6 Task 6)

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.

| type | claim Plan 6 file | our definition | shape check | |---|---|---|---| | 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; snakecase json tags (final): v, op, host, `snapshottxid (Go field N), snapshotkey, snapshotversion, snapshotcrc64, anchorkey, anchorversion, anchorcrc64, integritycheck, dbsha256, builtat; Plan 2 Task 8 expects and tests them; spec rev 7.22 §7.6 names them | | moves.ErrAttemptOver | internal/moves/installcontract.go | Plan 3 Task 1 | identity only (errors.Is); Plan 3 now uses claim Plan 6's message text | | provision.MoveInputs | internal/provision/moveinputscontract.go | Plan 1 Task 2 imports it (its own definition removed in rev 6) | identical fields and json tags: bundleetags, overlayetag, release, filesplain, env_report, all omitempty` |

Plans 1, 2 and 3 each carry the import rule at the definition site.

Decisions locked across the series

  • D1 Journals. The record's move field carries the move's phase journal (§7.2). The claim's op.journal carries what the claim half must read with the same fresh read as an admission: seal (the JSON of moves.Seal), src_gen, source_stamp, cut_session, rev_phase, evidence_source, evidence_target (deployment-bucket keys, never the evidence inlined; K13).
  • D2 zstd. The archive is tar then zstd (§8.1). The repository pins no zstd library; Plan 1 adds github.com/klauspost/compress (pure Go) as the one new module dependency of the series.
  • 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.
  • 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.
  • 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.
  • 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/.
  • 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.
  • 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.
  • 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.
  • D10 Flat landing only. Phase 1 lands flat (§6.2, §21); a nested source is refused at the door and at every box step.
  • 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.
  • 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).
  • 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).
  • 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.
  • 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.
  • 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.

Global constraints (every plan repeats these)

  • 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.
  • Cold only (P7, D14) and the operator-off switch (D13) in every plan.
  • 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.
  • 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.
  • Nothing rolls to production without Paul's go. Rehearsal order is M12's: the probe app, a disposable instance, kpmg (Plan 7).
  • A new carlos command is not done until docs/site/cli.md names it (the internal/docsite gate).
  • 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.

Review round 1 (Astra, codex, on 616f2499): dispositions

The findings are committed unedited as CODEX-PLAN-MOVES-PHASE1-R1.md beside this index. Verdict then: PLAN: NOT READY. Every finding was verified against the pinned main checkout and accepted; where each is fixed:

| # | finding | fixed in | |---|---|---| | 1 | MoveLanding type and constant collide | Plan 1 Task 2: the journal type is MoveLandingJournal | | 2 | activator importing stopevidence is a cycle; a.cgroups is no field | Plan 2 Task 3: stopevidence.Reconstruct(ctx, ReconstructInputs{Cgroups: eb.Cgroups, …}) | | 3 | spawnClaimed returns (*exec.Cmd, *cgroup.Prepared, error) | Index interfaces, Plan 3 Task 5: the handle is kept and cleaned on every exit path | | 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 | | 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 | | 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 | | 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 | | 8 | an application file named env collides with the overlay | Plan 1 Task 7: files/ and env namespaces, duplicate destinations refused, round-trip test | | 9 | T9 needs the target box in touched before the flip | Plan 1 Task 14: the first Touch adds the target region and box | | 10 | Put(..., "") is create-only; reports could not refresh | Plan 1 Task 11 moves.PutReplace, Task 12, Plan 6 Task 3 corrected | | 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() | | 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 | | 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 | | 14 | a paused tick could slip the drain | Plan 2 Task 6: the hold check runs after claim; TestDrainRaceCannotAdvanceBookkeeping | | 15 | the LTX encoder refuses the lock page | Plan 2 Task 8: ltx.LockPgno skipped, Commit kept, a >1 GiB sparse test | | 16 | a replayed build restored over an existing file | Plan 2 Task 8: the scratch database is removed before every restore | | 17 | RetireArtifacts without a persisted chore does nothing | Plan 2 Tasks 9 and 10: QueueLocalPark and RecordInventory at checking; the test asserts the filesystem | | 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 | | 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 | | 20 | declare-lost could override a winning commit | Plan 4 Task 10: the record decides first (MoveRollBack, MoveRollbackTargetLost), the claim follows | | 21 | the loss payload did not match claim Plan 8 | Plan 4 Task 10: the exact LossRecord, LossCloudObservation, LossOperatorAttestation, LossConsequences fields | | 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 | | 23 | orphan claims were never visited | Plan 5 Task 7: claim discovery from control/hosts/, ReleaseOrphanMove | | 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 | | 25 | the probe app's launch contract differed from the backend | Plan 7 Task 1: --socket, --db, TestMoveprobeLaunchContract | | 26 | activation depends on claim Plan 8b | Index interfaces: 8b's prerequisites named; declare-lost stays refused until 8b lands | | 27 | forbidden transitions requested | Plan 4 Task 1: MoveStuck from restoring/verified, MoveRollbackTargetLost | | 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 | | 29 | the MinIO test would never run in CI | Plan 1 Task 11: added to 30-minio-contract; failing cases for the stubbed hooks | | 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 | | 31 | the copy check needed an independent inventory | Plan 1 Task 15: movesCopyIDs pinned, negative fixtures | | 32 | the clean-rollback rehearsal wrote before refusing | Plan 7 Tasks 1 and 3: the before-write mode suppresses every mutation |

Review round 2 (Astra, on da2832aa): dispositions (plan rev 3)

Findings file: ~/.cache/nearest-region/CODEX-PLAN-MOVES-PHASE1-R2.md (the coordinator's record; not re-reviewed, per the coordinator). The locked design stands; the work split into local fixes (rev 3) and contracts (the BLOCKED-ON-CLAIM table above).

| # | finding | rev 3 | |---|---|---| | 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 | | 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 | | 3 | Claim.Validate rejects the new kinds | D6: no new kinds; park-replica scopes and park-generation chores; Engine.Parked stays | | 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 | | 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) | | 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 | | 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 | | 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 | | 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 | | 10 | a whole-location park could not finish once the new generation was live | D6: residue is parked per generation (park-generation) | | 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 | | 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 | | 13 | dirty-return probes rejected their prepared generation | Plan 3 Task 5: Op, HoldOp and PreparedGeneration distinguished; PreparedCheck for every prepared install | | 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) | | 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 | | 16 | the loss flow cannot pass the real Plan 8 API | C6; Plan 4 Task 10 BLOCKED-ON-CLAIM | | 17 | case (c) performed regional retirement | Plan 4 Task 10: a separate consequence path | | 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 | | 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 | | 20 | randomized sealing broke the replay identity | Plan 2 Task 5: the archive identity (manifest, sealed key, temp file) is journaled before any PUT | | 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 | | 22 | T11 used example rates | Plan 1 Task 11: measured rates with a one-hour freshness; missing measurements fail T11 | | 23 | log keys gave no sequencing guarantee | Plan 6 Task 3: transition-identity keys, create-only | | 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 | | 25 | the loss rehearsal lacked its prerequisites | Plan 7 P-6: Plan 8b, C5, the switch | | 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 | | R1-8 | Extract lacked duplicate-destination rejection | Plan 1 Task 7 | | 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 | | R1-27 | Task 7's retry contradicted Task 1 | Plan 4 Task 7 resumes at StuckAt |

Plan 1 review (Astra, on d5716bf4): dispositions (Plan 1 rev 4)

Findings file: ~/.cache/nearest-region/CODEX-PLAN1-REVIEW.md (the coordinator's record). All fourteen fixed in Plan 1 rev 4:

| # | finding | rev 4 | |---|---|---| | 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 | | 2 | deleted consumers and a duplicate servingClaim | Task 2 keeps MoveRequest and MoveComplete and their tests; the hostclaim fixture is movingTestClaim | | 3 | no copy presigner in S3 v1.105.0 | Task 6 signs each REST request with v4.Signer.SignHTTP at the check's clock | | 4 | Task 5 broke plain mirroring | the plain MirrorStore path is unchanged; the receipt rule applies only to a VersionedCopier | | 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 | | 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 | | 7 | worker transitions lacked op identity | Task 2's MoveGuard and the guarded promote, cut, abort and abort-complete; TestRoundNeverAdvancesAReplacementMove | | 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 | | 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 | | 10 | T5 did not verify the archive | Task 7's archive.Verify and its negative tests | | 11 | extraction changed modes | Task 7 restores exact file and directory modes, deepest directory last | | 12 | forward dependency and the rejected completeness check | the hold_op task is Task 12, before the floor task 13; snap.Complete only | | 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 | | 14 | contradictory Task 14 tests | the source-refusal test is gone; the target validates the lane; the self-review note matches |

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.

Plan 1 review round 4 (Astra, on d0907747): dispositions (Plan 1 rev 5)

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

| # | finding | disposition | |---|---|---| | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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() | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 |

Plan 1 review round 5 (Astra, on 80647eeb): dispositions (Plan 1 rev 6)

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.

| # | finding | disposition | |---|---|---| | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 13 | P2 commit commands omit files | Fixed. Tasks 1, 4, 6, 13 and 14 stage every directory they touch | | 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) |

Plan 1 review round 6 (Astra, on 296807a2): dispositions (Plan 1 rev 7)

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.

| # | finding | disposition | |---|---|---| | 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 | | 2 | P1 boxidtest import cycle | Fixed. boxid_test.go is left as it is; boxidtest is an exported twin, duplication accepted | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 9 | P3 superseded instructions | Fixed. D3 and D4 rewritten; the Go cache line follows the machine's rule (default cache) |

The model's limit (rev 7.20)

internal/sim/hostlife stays at one box per region plus replacement boxes. It gains rule fence-converts-moving and witness wFenceConvertsMoving (a source terminated before the flip in a one-box region: conversion to relocating/pinning, no churn and one alert while no box exists, completion on the replacement box). It does not model choose(), two target replacements, stuck after them, or the files publisher (it takes the fenced source's files directory as published). The two-box model is region HA's, on its own branch.

What the model proved and the plans port

Witnesses that become real tests, by plan: Plan 2 ports wSealByVersion, wMarkersLast, wBuildCrashes, wEvidenceWholeTree, wTimerFreeze; Plan 3 ports wProbeWindowAt (TestProbeWindow), wCommitBeforeOpen, wCommitCoverage, wCommitInterrupted, wCommitReplayed; Plan 4 ports wSealReturn, wCleanReturnIdentity, wRollbackJournal, wRecoveryInputs, wDirtyReturn, wReturnLostRecord, wRollbackBaselinePreserved, wTwoSessionDeclaration, wNoDuplicateDeclarationEarly, wNoDuplicateDeclarationLate, wDeclareLostCompute, wLostQuarantine, wLostReturnPark; Plan 5 ports wStagingScopeDeadline, wParkScopeSurvivesRestart, wSweepScopeMembership, wStagingQuiet, wFreeSweepVsCreate. Each cites its witness in the test's doc comment.