# Moves Phase 1, Plan 2: Cut, Flip and Landing Implementation Plan

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

**Goal:** Cut a prepared move over: stop the source with durable evidence, seal the staged copy by version, publish the archive, flip the record and the claim, and install the generation on the target through `restored`, with every phase journaled and replayable and no process yet started on the target.

**Architecture:** The source's cut phases live in `internal/host/movescut.go` beside `movespass.go`, the target's landing phases in `internal/host/movesland.go`; both drive `provision.MoveState` CASes guarded by move id and incarnation and then the claim CASes `internal/hostclaim/move.go` defines (`Flip`, `ExecToNone`, `SetMovePhase`, `SetJournal`). Stop evidence is `activator.ReconstructStopEvidence`, built on `cgroup.Cleanup` and `stopevidence.Collect`. The generation build is `internal/moves/build.go`: litestream's own planner over `moves.SealedClient` (exactly the sealed versions), then the snapshot and anchor encoded with `ltx.NewEncoder` as `litestreamfacts` does. Holds are registry rows with `hold_op`; reconcile mints and keeps participant rows through two hooks the host package supplies. `appconfig.RenderHeld` and `release.MaterializeHeld` render and stage for a held row. `internal/park` gains `Engine.Parked`; the existing kinds and the `parked/` destination carry move retirements (index D6).

**Tech Stack:** Go, litestream v0.5.14 (`NewReplicaWithClient`, `RestoreOptions.TXID`), superfly/ltx v0.5.1 (`NewEncoder`, `NewDecoder`, `Verify`), modernc sqlite via `lease.CheckDB`, cgroup v2 (`internal/cgroup`), `internal/stopevidence`, `internal/hostguard`, `internal/park`, `internal/genselect`.

**Spec:** `docs/superpowers/specs/2026-10-02-move-any-app-design.md` rev 7.18: §5.1 (steps 1 to 8, markers, the start check), §5.2, §5.3, §6.1, §6.2, §6.4, §7.4, §7.5, §7.6, §7.13, §9.1, §9.2, §9.3, §9.4, §9.6, §19.2 K3, K4, K5, K9, K13. Index: `2026-10-06-moves-phase1-0-index.md`. Plan 1 must be merged.

## Global Constraints

- Gate before every commit: `go build ./...`, `go vet ./...`, `go test ./... -count=1`, `gofmt -l .` clean, `make test`; cross-compile darwin, windows, linux-arm64. Anything that opens a cgroup, scans `/proc` or flocks is behind `//go:build linux` with a `!linux` stub (AGENTS.md cross-compile gate). Sandboxed Go: `export GOCACHE=$TMPDIR/gocache GOFLAGS=-buildvcs=false`.
- Tests serial and niced; the cgroup test (Task 3) skips unless `CARLOS_CGROUP_TEST=1` and a delegated cgroup v2 root is present.
- Every phase is written to the record **before** its side effects (§7.4, §7.6); every box transition is a CAS guarded by `move.id` and `move.incarnation` and revalidates the claim's op id, kind and phase first (K5).
- The required final TXID `N` is the `MaxTXID` of the source's newest level-0 file after the final checkpoint and `SyncAndWait` (§5.1 step 1); the build must reach exactly `N` and pass `PRAGMA integrity_check`.
- Snapshot and anchor are written as the pinned writers write theirs: `HeaderFlagNoChecksum`, checksums zero, trailer CRC-64 present; the anchor's `WALOffset` is `litestream.WALHeaderSize` (facts R5-1c, R5-2).
- A build is two durable states, intent then completed manifest; `generation.json` is never written by the landing (§7.6 `published`; R10-3).
- One absolute deadline: `cut_deadline = cut_at + cut_budget − open_budget`; every phase before the commit checks it (§7.4).
- `promoteStagedReplica` and the staging-wins restore are deleted (§5.1, M15).
- No em dashes in any string.
- Cold only (P7, index D14); the switch (D13) gates the cut and the landing like every other entry point.
- The install routine (Task 8) is shared with region HA's relocation (spec rev 7.19 §7.6, L16): its input is a pinned version set and `N`, never "a staged replica" by name.

## Review Focus

1. A source whose agent restarts between `stopping` and `stopped` must produce the same evidence from cgroup and unit state with no memory, and must not spawn anything meanwhile (INV-3, codex R2-2). Test in Task 5 (`TestCutResumesStoppingFromEvidenceAlone`).
2. A final mirror round that defers an object three times must revert, never flip (§7.4 `staged` failure). Test in Task 5 (`TestCutRevertsAfterThreeDeferredRounds`).
3. A landing whose `building` crashed after the snapshot PUT and before the manifest must rebuild identical bytes and reuse the existing snapshot version, never write a second differing object (R6-5, R7-4; witness `wBuildCrashes`). Test in Task 8 (`TestBuildReplayReusesIdenticalSnapshot`).
4. A `configured` phase whose rendered bundle etags differ from `move.inputs` must mark the move for rollback with "configuration changed during the move", not install (§7.6; J2-R2-5). Test in Task 9 (`TestLandingConfiguredPinsInputs`).
5. Reconcile on the target must never mint the landing row `ready`, and reconcile on the source must never remove the `moving` row while the claim lists the box as a participant, whatever the record's region says (§9.6, K9, codex R2-4). Test in Task 6 (`TestReconcileHoldsParticipantRows`).

---

## File structure

| path | responsibility |
|---|---|
| `internal/provision/provision.go` | cut, landing and reverting phase transitions on the record |
| `internal/hostclaim/move.go` | `SetMovePhase`, `SetJournal`, `ExecToNone`, `Flip`, `Revert`, journal keys |
| `internal/activator/stopreconstruct_linux.go`, `_other.go` | `ReconstructStopEvidence` |
| `internal/activator/backend_exec.go` | strict spawns under a move; the staging-wins fallback deleted |
| `internal/moves/seal.go` | `Seal` and its journal form (data shape from Plan 1's index decision D1) |
| `internal/moves/evidence.go` | `StoppedReport` |
| `internal/moves/journal.go` | the box-local operation journal F12 |
| `internal/moves/ltxbuild.go` | full-file LTX encoding, `BuildIntent`, `InstallManifest` |
| `internal/moves/sealedclient.go` | `SealedClient`, a `litestream.ReplicaClient` over the sealed versions |
| `internal/moves/build.go` | `BuildGeneration` |
| `internal/park/remote.go` | `Engine.Parked` (the existing kinds and `parked/` destination carry move retirements, index D6) |
| `internal/appconfig/renderheld.go` | `RenderHeld` and the move-bound ack |
| `internal/release/materializeheld.go` | `MaterializeHeld` |
| `internal/provision/reconcile.go` | `Hold`, `KeepParticipant` |
| `internal/schedule/runner.go`, `converge.go` | held rows skipped; `Busy`, `Frozen` |
| `internal/host/movescut.go` | the source's cut phases |
| `internal/host/movesland.go` | the target's landing phases through `restored` |
| `internal/host/moveshold.go` | the hold view the host package gives reconcile and schedule |

---

### Task 1: Record transitions for the cut, the flip and the landing

**Files:**
- Modify: `internal/provision/provision.go`
- Test: `internal/provision/move_test.go`

**Interfaces:**
- Consumes: `MoveGuard` and `MoveGuardedCut` from Plan 1 Task 2 (the guard is defined there, with the guarded promote, cut, abort and abort-complete; this task adds the cut phases on top; `MoveGuardedCut` is the `ready → cutting/requested` entry the source uses for `cut == "auto"` and the console's cut door uses too).
- Produces: `MoveCutPhase(g MoveGuard, from, to string, stamp func(*MoveState)) func(*Record) error` (state must be `cutting`; `CutPhase == from`); `MoveAdmit(g, now time.Time, budgetS int, deadline time.Time, inputs MoveInputs)` (`requested → admitted`, stamps `CutAt`, `CutBudgetS`, `CutDeadline`, `Inputs`); `MoveBackToReady(g)` (`cutting/requested|admitted → ready`, clears the cut fields); `MoveRecordSource(g, src MoveSource)` (sets `Source`, legal in `stopped` only); `MoveFlip(g, now time.Time, landingDeadline time.Time)` (`cutting/archived → landing`, `Region = To`, `CutPhase = flipped`, `FlippedAt`, `LandingDeadline`, `LandingPhase = checking`); `MoveLandingPhase(g, from, to string, stamp func(*MoveState))` (state `landing`); `MoveRevert(g, reason string)` (from `cutting` with `CutPhase` in `stopping..archived` → `reverting`, `Reason`).

- [ ] **Step 1: Write the failing test**

```go
func TestCutPhasesAdvanceInOrderUnderTheGuard(t *testing.T) {
	g := MoveGuard{ID: "mv_abcdefghijklmnopqrstuvwxyz", Incarnation: strings.Repeat("ab", 32)}
	now := time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC)
	rec := &Record{Region: "ie", Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, From: "ie", To: "uk", State: MoveReady}}
	if err := MoveGuardedCut(g)(rec); err != nil || rec.Move.State != MoveCutting || rec.Move.CutPhase != CutRequested {
		t.Fatalf("auto cut: %v %+v", err, rec.Move)
	}
	deadline := now.Add(115 * time.Second)
	if err := MoveAdmit(g, now, 120, deadline, MoveInputs{Release: "v1"})(rec); err != nil || rec.Move.CutPhase != CutAdmitted || !rec.Move.CutDeadline.Equal(deadline) || rec.Move.Inputs.Release != "v1" {
		t.Fatalf("admit: %v %+v", err, rec.Move)
	}
	// Wrong incarnation: refused.
	if err := MoveCutPhase(MoveGuard{ID: g.ID, Incarnation: strings.Repeat("cd", 32)}, CutAdmitted, CutStopping, nil)(rec); !errors.Is(err, ErrMoveConflict) {
		t.Fatalf("wrong guard: %v", err)
	}
	// Skipping a phase: refused.
	if err := MoveCutPhase(g, CutStopping, CutStopped, nil)(rec); !errors.Is(err, ErrMoveConflict) {
		t.Fatalf("skip: %v", err)
	}
	for _, step := range [][2]string{{CutAdmitted, CutStopping}, {CutStopping, CutStopped}} {
		if err := MoveCutPhase(g, step[0], step[1], nil)(rec); err != nil {
			t.Fatal(err)
		}
	}
	if err := MoveRecordSource(g, MoveSource{FinalTXID: 42, TimersSHA256: "sha256:t"})(rec); err != nil || rec.Move.Source.FinalTXID != 42 {
		t.Fatalf("source: %v", err)
	}
	for _, step := range [][2]string{{CutStopped, CutStaged}, {CutStaged, CutArchived}} {
		if err := MoveCutPhase(g, step[0], step[1], nil)(rec); err != nil {
			t.Fatal(err)
		}
	}
	// Abort is refused past requested.
	if err := MoveAbort()(rec); !errors.Is(err, ErrMoveConflict) {
		t.Fatal("abort past admitted")
	}
	ld := now.Add(10 * time.Minute)
	if err := MoveFlip(g, now, ld)(rec); err != nil || rec.Region != "uk" || rec.Move.State != MoveLanding || rec.Move.CutPhase != CutFlipped || rec.Move.LandingPhase != LandChecking || !rec.Move.LandingDeadline.Equal(ld) {
		t.Fatalf("flip: %v %+v", err, rec)
	}
	if err := MoveFlip(g, now, ld)(rec); !errors.Is(err, ErrMoveConflict) {
		t.Fatal("a second flip")
	}
	if err := MoveLandingPhase(g, LandChecking, LandParking, nil)(rec); err != nil || rec.Move.LandingPhase != LandParking {
		t.Fatalf("landing phase: %v", err)
	}
}

func TestBackToReadyAndRevert(t *testing.T) {
	g := MoveGuard{ID: "mv_abcdefghijklmnopqrstuvwxyz", Incarnation: strings.Repeat("ab", 32)}
	now := time.Now()
	rec := &Record{Region: "ie", Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, From: "ie", To: "uk", State: MoveCutting, CutPhase: CutAdmitted, CutAt: &now}}
	if err := MoveBackToReady(g)(rec); err != nil || rec.Move.State != MoveReady || rec.Move.CutPhase != "" || rec.Move.CutAt != nil {
		t.Fatalf("back to ready: %v %+v", err, rec.Move)
	}
	rec.Move.State, rec.Move.CutPhase = MoveCutting, CutStopped
	if err := MoveRevert(g, "stop evidence budget")(rec); err != nil || rec.Move.State != MoveReverting || rec.Move.Reason == "" {
		t.Fatalf("revert: %v %+v", err, rec.Move)
	}
	// Not from requested or admitted: nothing ran yet, so back-to-ready is the exit there.
	rec2 := &Record{Region: "ie", Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, State: MoveCutting, CutPhase: CutRequested}}
	if err := MoveRevert(g, "x")(rec2); !errors.Is(err, ErrMoveConflict) {
		t.Fatal("revert from requested")
	}
}
```

- [ ] **Step 2: Run to verify it fails**: `nice -n 10 go test ./internal/provision -run 'TestCutPhases|TestBackToReady' -count=1` → FAIL, undefined.

- [ ] **Step 3: Implement**

```go
// MoveGuard and its check are Plan 1 Task 2's; the cut phases below build on them.

var cutOrder = []string{CutRequested, CutAdmitted, CutStopping, CutStopped, CutStaged, CutArchived, CutFlipped}

func nextIn(order []string, from, to string) bool {
	for i := 0; i+1 < len(order); i++ {
		if order[i] == from && order[i+1] == to {
			return true
		}
	}
	return false
}

// MoveCutPhase advances cut_phase one step, in order, under the guard.
// stamp may fill the fields the phase records; it runs after the check.
func MoveCutPhase(g MoveGuard, from, to string, stamp func(*MoveState)) func(*Record) error {
	return func(rec *Record) error {
		if err := g.check(rec); err != nil {
			return err
		}
		if rec.Move.State != MoveCutting || rec.Move.CutPhase != from || !nextIn(cutOrder, from, to) {
			return fmt.Errorf("%w: cut is %s/%s, step expects %s → %s", ErrMoveConflict, rec.Move.State, rec.Move.CutPhase, from, to)
		}
		rec.Move.CutPhase = to
		if stamp != nil {
			stamp(rec.Move)
		}
		return nil
	}
}

// MoveAdmit is §7.4 admitted: the budget, the absolute deadline and the
// pinned inputs, in one write.
func MoveAdmit(g MoveGuard, now time.Time, budgetS int, deadline time.Time, inputs MoveInputs) func(*Record) error {
	return MoveCutPhase(g, CutRequested, CutAdmitted, func(m *MoveState) {
		t := now.UTC()
		d := deadline.UTC()
		in := inputs
		m.CutAt, m.CutBudgetS, m.CutDeadline, m.Inputs = &t, budgetS, &d, &in
	})
}

// MoveBackToReady undoes an admission whose checks are no longer fresh or
// whose inputs moved (§7.4 admitted, codex R2-8). Legal from requested or
// admitted only: nothing has stopped yet.
func MoveBackToReady(g MoveGuard) func(*Record) error {
	return func(rec *Record) error {
		if err := g.check(rec); err != nil {
			return err
		}
		if rec.Move.State != MoveCutting || (rec.Move.CutPhase != CutRequested && rec.Move.CutPhase != CutAdmitted) {
			return fmt.Errorf("%w: back to ready is legal from requested or admitted only", ErrMoveConflict)
		}
		rec.Move.State, rec.Move.CutPhase, rec.Move.CutAt, rec.Move.CutBudgetS, rec.Move.CutDeadline, rec.Move.Inputs = MoveReady, "", nil, 0, nil, nil
		return nil
	}
}

// MoveRecordSource writes the stopped source's baselines (§7.4 stopped).
func MoveRecordSource(g MoveGuard, src MoveSource) func(*Record) error {
	return func(rec *Record) error {
		if err := g.check(rec); err != nil {
			return err
		}
		if rec.Move.State != MoveCutting || rec.Move.CutPhase != CutStopped {
			return fmt.Errorf("%w: source digests are recorded at stopped", ErrMoveConflict)
		}
		s := src
		rec.Move.Source = &s
		return nil
	}
}

// MoveFlip is the flip (§7.4, §7.5): region = to, state = landing, the
// landing deadline, landing_phase = checking, in one record write. The
// claim follows (hostclaim.Flip).
func MoveFlip(g MoveGuard, now, landingDeadline time.Time) func(*Record) error {
	return func(rec *Record) error {
		if err := g.check(rec); err != nil {
			return err
		}
		if rec.Move.State != MoveCutting || rec.Move.CutPhase != CutArchived {
			return fmt.Errorf("%w: the flip follows archived", ErrMoveConflict)
		}
		t, d := now.UTC(), landingDeadline.UTC()
		rec.Region = rec.Move.To
		rec.Move.State, rec.Move.CutPhase, rec.Move.FlippedAt, rec.Move.LandingDeadline, rec.Move.LandingPhase = MoveLanding, CutFlipped, &t, &d, LandChecking
		return nil
	}
}

var landingOrder = []string{LandChecking, LandParking, LandBuilding, LandPublished, LandLocal, LandConfigured, LandRestored, LandProbing, LandProbed}

// MoveLandingPhase advances landing_phase one step (§7.6).
func MoveLandingPhase(g MoveGuard, from, to string, stamp func(*MoveState)) func(*Record) error {
	return func(rec *Record) error {
		if err := g.check(rec); err != nil {
			return err
		}
		if rec.Move.State != MoveLanding || rec.Move.LandingPhase != from || !nextIn(landingOrder, from, to) {
			return fmt.Errorf("%w: landing is %s/%s, step expects %s → %s", ErrMoveConflict, rec.Move.State, rec.Move.LandingPhase, from, to)
		}
		rec.Move.LandingPhase = to
		if stamp != nil {
			stamp(rec.Move)
		}
		return nil
	}
}

// MoveRevert starts the pre-flip recovery (§7.4 reverting): from any cut
// phase that has stopped the source. Before stopping, back-to-ready is
// the exit.
func MoveRevert(g MoveGuard, reason string) func(*Record) error {
	return func(rec *Record) error {
		if err := g.check(rec); err != nil {
			return err
		}
		switch {
		case rec.Move.State != MoveCutting:
			return fmt.Errorf("%w: revert is a cut's recovery", ErrMoveConflict)
		case rec.Move.CutPhase == CutRequested || rec.Move.CutPhase == CutAdmitted:
			return fmt.Errorf("%w: nothing has stopped yet. back to ready instead", ErrMoveConflict)
		case rec.Move.CutPhase == CutFlipped:
			return fmt.Errorf("%w: flipped. a rollback, not a revert", ErrMoveConflict)
		}
		rec.Move.State, rec.Move.Reason = MoveReverting, reason
		return nil
	}
}
```

- [ ] **Step 4: Run**: `nice -n 10 go test ./internal/provision -count=1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "provision: cut, flip and landing transitions under the move guard (§7.4 to §7.6)" ...`

---

### Task 2: Claim transitions for the cut and the flip

**Files:**
- Modify: `internal/hostclaim/move.go`, `internal/hostclaim/move_test.go`

**Interfaces:**
- Produces: journal keys `JournalSeal = "seal"`, `JournalSrcGen = "src_gen"`, `JournalSourceStamp = "source_stamp"`, `JournalCutSession = "cut_session"`, `JournalRevPhase = "rev_phase"`, `JournalEvidenceSource = "evidence_source"`, `JournalEvidenceTarget = "evidence_target"`; `SetMovePhase(opID, kind, from, to string) func(*Claim) error`; `SetJournal(opID string, kv map[string]string) func(*Claim) error` (op id only; any kind); `ExecToNone(opID, box, evidenceKey, journalKey string) func(*Claim) error` (K3: `c.Op.Exec == box` → `""`, records the evidence key under `journalKey`); `Flip(opID, targetBox string, sourceStamp uint64, srcGen string) func(*Claim) error` (K4: requires `KindMoving`, phase `sealed`, `Exec == ""`, `evidence_source` journalled; sets `Kind = KindLanding`, `Phase = "checking"`, `Exec = targetBox`, `holder.stamp = c.Gen + 1`, the gen this CAS produces: `hostclaim.Update` sets `gen = observed + 1` after `tr` runs (`store.go`, and `create.go:94` writes the serving stamp the same way); journals `source_stamp` and `src_gen`); `Revert(opID, sourceBox, targetRegion string, stagingScope *ParkScope, newID func() string) func(*Claim) error` (from `KindMoving`, `Exec == ""`, `evidence_source` journalled → `KindReverting`, `Exec = sourceBox`, `rev_phase = "evidence"` unless set, plus the lane `park-replica` obligation for the target region's lane with the writers' bound as its scope, which §7.11's "opened waits for the staging obligation" refers to; Plan 4 Task 6 waits on it and Plan 5 Task 1 dispatches it).

- [ ] **Step 1: Write the failing test**

```go
func TestCutClaimCASesFollowK3AndK4(t *testing.T) {
	c := movingTestClaim()
	op := "mv_abcdefghijklmnopqrstuvwxyz"
	_ = BeginMove(op, "ops", "ie-box", "uk-box", "uk", time.Now())(c)
	// Only the executor gives execution up, and only citing evidence.
	if err := ExecToNone(op, "uk-box", "moves/bab/app/h/mv/stopped-source.json", JournalEvidenceSource)(c); !errors.Is(err, ErrWrongState) {
		t.Fatalf("non-exec to none: %v", err)
	}
	if err := ExecToNone(op, "ie-box", "", JournalEvidenceSource)(c); !errors.Is(err, ErrMalformed) {
		t.Fatalf("no evidence: %v", err)
	}
	// The flip needs the seal phase and exec none.
	if err := Flip(op, "uk-box", 3, "")(c); !errors.Is(err, ErrWrongState) {
		t.Fatalf("flip before sealed: %v", err)
	}
	if err := ExecToNone(op, "ie-box", "moves/bab/app/h/mv/stopped-source.json", JournalEvidenceSource)(c); err != nil || c.Op.Exec != "" {
		t.Fatalf("to none: %v %+v", err, c.Op)
	}
	if err := SetJournal(op, map[string]string{JournalSeal: `{"n":7}`, JournalCutSession: "5"})(c); err != nil || c.Op.Journal[JournalSeal] != `{"n":7}` {
		t.Fatalf("journal: %v", err)
	}
	if err := SetMovePhase(op, KindMoving, PhaseMoving, PhaseSealed)(c); err != nil || c.Op.Phase != PhaseSealed {
		t.Fatalf("phase: %v", err)
	}
	if err := SetMovePhase(op, KindMoving, PhaseMoving, PhaseSealed)(c); !errors.Is(err, ErrWrongState) {
		t.Fatal("phase replay")
	}
	before := c.Holder.Stamp
	c.Gen = 9 // what Update will have set
	if err := Flip(op, "uk-box", before, "")(c); err != nil {
		t.Fatal(err)
	}
	if c.Op.Kind != KindLanding || c.Op.Phase != "checking" || c.Op.Exec != "uk-box" || c.Op.Journal[JournalSourceStamp] != "3" || c.Op.Journal[JournalSrcGen] != "" || c.Holder.Stamp == before {
		t.Fatalf("after flip: %+v stamp %d", c.Op, c.Holder.Stamp)
	}
	// holder.generation is untouched by the flip (§7.5).
	if c.Holder.Generation != "" {
		t.Fatal("flip moved the generation")
	}
}

func TestRevertNeedsEvidenceAndReturnsExecToSource(t *testing.T) {
	c := movingTestClaim()
	op := "mv_abcdefghijklmnopqrstuvwxyz"
	_ = BeginMove(op, "ops", "ie-box", "uk-box", "uk", time.Now())(c)
	if err := Revert(op, "ie-box")(c); !errors.Is(err, ErrWrongState) {
		t.Fatalf("revert while exec held: %v", err)
	}
	_ = ExecToNone(op, "ie-box", "moves/bab/app/h/mv/stopped-source.json", JournalEvidenceSource)(c)
	scope := ParkScope{Location: "moves/a.go.example.com/" + op, Incarnation: c.Holder.Incarnation, EvidenceAt: time.Now(), RequestLimit: 120 * time.Second}
	ids := 0
	newID := func() string { ids++; return fmt.Sprintf("id%d", ids) }
	if err := Revert(op, "uk-box", "uk", &scope, newID)(c); !errors.Is(err, ErrWrongState) {
		t.Fatalf("revert to the target: %v", err)
	}
	if err := Revert(op, "ie-box", "uk", &scope, newID)(c); err != nil || c.Op.Kind != KindReverting || c.Op.Exec != "ie-box" || c.Op.Journal[JournalRevPhase] != "evidence" {
		t.Fatalf("revert: %v %+v", err, c.Op)
	}
	if len(c.Obligations) != 1 || c.Obligations[0].Kind != "park-replica" || c.Obligations[0].Region != "uk" || c.Obligations[0].Park == nil {
		t.Fatalf("revert queued no target staging park: %+v", c.Obligations)
	}
}
```

- [ ] **Step 2: Run to verify it fails**, **Step 3: implement**

```go
const (
	JournalSeal           = "seal"
	JournalSrcGen         = "src_gen"
	JournalSourceStamp    = "source_stamp"
	JournalCutSession     = "cut_session"
	JournalRevPhase       = "rev_phase"
	JournalEvidenceSource = "evidence_source"
	JournalEvidenceTarget = "evidence_target"
)

func setJournal(c *Claim, k, v string) {
	if c.Op.Journal == nil {
		c.Op.Journal = map[string]string{}
	}
	c.Op.Journal[k] = v
}

// SetJournal records move journal entries under the op (D1): what the
// claim half must read with the same fresh read as an admission.
func SetJournal(opID string, kv map[string]string) func(*Claim) error {
	return func(c *Claim) error {
		if err := RequireOp(opID)(c); err != nil {
			return err
		}
		for k, v := range kv {
			setJournal(c, k, v)
		}
		return nil
	}
}

// SetMovePhase moves the op's phase one step, under its kind.
func SetMovePhase(opID, kind, from, to string) func(*Claim) error {
	return func(c *Claim) error {
		if err := RequireMoveOp(opID, kind, from)(c); err != nil {
			return err
		}
		c.Op.Phase = to
		return nil
	}
}

// ExecToNone is K3: a participant that has written stop evidence for this
// op gives execution up. Nobody moves exec from one box to another. A
// replay after a crash finds exec already empty and the same evidence
// journalled: that is idempotent, not a conflict.
func ExecToNone(opID, box, evidenceKey, journalKey string) func(*Claim) error {
	return func(c *Claim) error {
		if err := RequireOp(opID)(c); err != nil {
			return err
		}
		if evidenceKey == "" || (journalKey != JournalEvidenceSource && journalKey != JournalEvidenceTarget) {
			return fmt.Errorf("%w: execution is given up only with evidence", ErrMalformed)
		}
		if c.Op.Exec == "" && c.Op.Journal[journalKey] == evidenceKey {
			return nil
		}
		if c.Op.Exec != box {
			return fmt.Errorf("%w: exec is %q, not %q", ErrWrongState, c.Op.Exec, box)
		}
		c.Op.Exec = ""
		setJournal(c, journalKey, evidenceKey)
		return nil
	}
}

// Flip is the claim half of the record's flip (§7.5, K4): moving/sealed →
// landing/checking, exec none → target, citing the source's evidence;
// holder.stamp = the gen this CAS produces (the target's installation
// stamp F, claim rev 6.5 §6.4), with the source's stamp S and generation
// journalled for a rollback's return. holder.generation is not touched.
func Flip(opID, targetBox string, sourceStamp uint64, srcGen string) func(*Claim) error {
	return func(c *Claim) error {
		if err := RequireMoveOp(opID, KindMoving, PhaseSealed)(c); err != nil {
			return err
		}
		if c.Op.Exec != "" || c.Op.Journal[JournalEvidenceSource] == "" {
			return fmt.Errorf("%w: the flip needs exec none and the source's evidence", ErrWrongState)
		}
		if len(c.Op.Participants) != 2 || c.Op.Participants[1] != targetBox {
			return fmt.Errorf("%w: %q is not the move's target", ErrWrongState, targetBox)
		}
		c.Op.Kind, c.Op.Phase, c.Op.Exec = KindLanding, "checking", targetBox
		setJournal(c, JournalSourceStamp, strconv.FormatUint(sourceStamp, 10))
		setJournal(c, JournalSrcGen, srcGen)
		c.Holder.Stamp = c.Gen + 1
		return nil
	}
}

// Revert is §7.4 reverting: the source takes execution back with its own
// evidence, before the flip. The target region's staging park is queued in
// the same write (§7.11: the revert's `opened` waits for it), owned by the
// target region's box, scoped to the lane with the writers' horizon.
func Revert(opID, sourceBox, targetRegion string, stagingScope *ParkScope, newID func() string) func(*Claim) error {
	return func(c *Claim) error {
		if err := RequireOp(opID)(c); err != nil {
			return err
		}
		if c.Op.Kind != KindMoving || c.Op.Exec != "" || c.Op.Journal[JournalEvidenceSource] == "" {
			return fmt.Errorf("%w: revert needs a moving op with exec none and the source's evidence", ErrWrongState)
		}
		if len(c.Op.Participants) == 0 || c.Op.Participants[0] != sourceBox {
			return fmt.Errorf("%w: %q is not the move's source", ErrWrongState, sourceBox)
		}
		if stagingScope == nil || targetRegion == "" {
			return fmt.Errorf("%w: a revert names the staging lane it leaves behind", ErrMalformed)
		}
		if err := stagingScope.Validate(c.Host); err != nil {
			return err
		}
		c.Op.Kind, c.Op.Exec = KindReverting, sourceBox
		if c.Op.Journal[JournalRevPhase] == "" {
			setJournal(c, JournalRevPhase, "evidence")
		}
		for _, o := range c.Obligations {
			if o.Op == opID && o.Kind == "park-replica" && o.Region == targetRegion {
				return nil
			}
		}
		s := *stagingScope
		c.Obligations = append(c.Obligations, Obligation{ID: newID(), Op: opID, Kind: "park-replica", Region: targetRegion, Retirement: newID(), Owner: "region:" + targetRegion, Park: &s})
		return nil
	}
}
```

`c.Gen + 1` presumes `Update` increments `Gen` after the transition; check `store.go`'s `Update` and `create.go`'s `CompleteCreate` (which writes the serving stamp) and use the same expression they use. If `Update` increments before calling `tr`, write `c.Gen`. The test sets `c.Gen` explicitly, so adjust its expectation to the real convention.

- [ ] **Step 4: Run**: `nice -n 10 go test ./internal/hostclaim -count=1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "hostclaim: ExecToNone, Flip, Revert, SetMovePhase, SetJournal (moves K3, K4, §7.5)" ...`

---

### Task 3: `ReconstructStopEvidence`

**Files:**
- Create: `internal/stopevidence/reconstruct_linux.go`, `internal/stopevidence/reconstruct_other.go`, `internal/stopevidence/reconstruct_linux_test.go`
- Create: `internal/moves/evidence.go`, `internal/lease/lockstate.go`, `internal/lease/lockstate_test.go`

**Interfaces:**
- Consumes: `cgroup.Manager.OpenCleanup(host) (*cgroup.Cleanup, error)`, `(*Cleanup).KillAndEmpty(ctx)`, `Populated()`, `Members()`, `Close()`; `stopevidence.Collect`, `stopevidence.FDHolders(procRoot, paths)`, `stopevidence.Cleanup`; `cgroup.ParseSelfCgroup`. The function lives in `internal/stopevidence`, not `internal/activator`: `stopevidence` already imports `activator` (`evidence.go:10`, for `*activator.StopEvidence`), so the activator cannot import it back; `stopevidence` may import `moves` (which imports neither). The cgroup manager is the exec backend's `ExecBackend.Cgroups` (`backend_exec.go`), handed in explicitly by the host package at arm time (`eb.Cgroups`); `activator.Service` has no manager field.
- Produces: `moves.StoppedReport{V int; Role, Box, Incarnation string; ClaimGen uint64; Host, OpID string; Steps map[string]moves.Check; Stray []int; FinalTXID uint64; DBSHA256, FilesPlain, TimersSHA256 string; CheckedAt time.Time}` with step ids `StepTree = "tree"`, `StepStray = "stray"`, `StepFDs = "fds"`, `StepLease = "lease"`, `StepReplica = "replica"`; `(r StoppedReport) Complete() bool` (every step OK, `StepReplica` included); `(r StoppedReport) TreeStopped() bool` (steps 1 to 4 OK: what `stopping` needs before the checkpoint); `lease.LockState(ctx, bucket, prefix, region string) (held bool, owner string, expires time.Time, err error)` (reads `lock.json` as the leaser writes it; absent → `held=false`); `stopevidence.ReconstructInputs{Host string; Cgroups *cgroup.Manager; DBPaths []string; ProcRoot string; LeaseFree func(context.Context) (bool, error)}`; `stopevidence.Reconstruct(ctx, in ReconstructInputs) (moves.StoppedReport, error)` (steps 1 to 4 of §5.3; step 5, the final checkpoint and digests, is the caller's, which then sets `StepReplica`); on `!linux`, returns `ErrUnsupported`.

- [ ] **Step 1: Write the failing tests**

`internal/lease/lockstate_test.go`: with the package's `leasetest` local store (see `internal/lease/leasetest`), acquire a lease then `LockState` → held with an expiry in the future; release → not held; an absent prefix → not held, nil error.

`internal/activator/stopreconstruct_linux_test.go`:

```go
//go:build linux

// §14.1: ReconstructStopEvidence against a real cgroup v2 hierarchy with a
// forked descendant that closes its descriptors and outlives its parent
// (witness wEvidenceWholeTree). Needs a delegated cgroup root:
// CARLOS_CGROUP_TEST=1 and CARLOS_CGROUP_ROOT=<writable cgroup2 dir>.
func TestReconstructStopEvidenceKillsTheWholeTree(t *testing.T) {
	root := os.Getenv("CARLOS_CGROUP_ROOT")
	if os.Getenv("CARLOS_CGROUP_TEST") != "1" || root == "" {
		t.Skip("needs CARLOS_CGROUP_TEST=1 and a delegated cgroup v2 root")
	}
	m, err := cgroup.SetupAt(root, t.Logf)
	if err != nil {
		t.Skip(err)
	}
	host := "ev.go.example.com"
	// A parent that forks a child which detaches and sleeps, then exits.
	cmd, err := m.Start(host, cgroup.Limit{}, func() *exec.Cmd {
		return exec.Command("sh", "-c", "(sleep 300 </dev/null >/dev/null 2>&1 &) ; exit 0")
	})
	if err != nil {
		t.Fatal(err)
	}
	cmd.Wait()
	rep, err := Reconstruct(context.Background(), ReconstructInputs{Host: host, Cgroups: m, ProcRoot: "/proc", LeaseFree: func(context.Context) (bool, error) { return true, nil }})
	if err != nil {
		t.Fatal(err)
	}
	if !rep.Steps[moves.StepTree].OK || !rep.TreeStopped() || rep.Complete() {
		t.Fatalf("tree evidence after the parent exited: %+v (Complete needs the caller's replica step)", rep)
	}
	c, _ := m.OpenCleanup(host)
	pop, _ := c.Populated()
	if pop {
		t.Fatal("the descendant survived")
	}
}

func TestReconstructStopEvidenceRefusesAStrayProcess(t *testing.T) {
	// A fake self-cgroup listing with a second pid refuses with StepStray.
	rep := strayCheck([]int{os.Getpid(), 99999}, os.Getpid())
	if rep.OK {
		t.Fatal("a stray pid passed")
	}
}
```

- [ ] **Step 2: Run to verify they fail**, **Step 3: implement**

`moves/evidence.go` declares the report and steps (as above, with `Complete()` and `TreeStopped()`). `lease/lockstate.go` reads `prefix + "/lock.json"` through the same S3 client constructor `acquireTTL` uses and decodes the leaser's lock body (owner, expires); if the leaser library (`lss3.Leaser`) exposes a read, use it. `reconstruct_linux.go`:

```go
//go:build linux

package stopevidence

// Reconstruct is §5.3's evidence from durable, observable state: it reads
// nothing from the agent's memory, so a restarted agent gives the same
// answer. Steps 1 to 4; the caller does step 5 (the final checkpoint and
// the digests), since only it knows the paths and the replica it closes.
func Reconstruct(ctx context.Context, in ReconstructInputs) (moves.StoppedReport, error) {
	rep := moves.StoppedReport{V: 1, Host: in.Host, Steps: map[string]moves.Check{}, CheckedAt: time.Now().UTC()}
	if in.Cgroups == nil || in.LeaseFree == nil || !controlname.ValidHostShape(in.Host) {
		return rep, ErrInvalidInput
	}
	procRoot := in.ProcRoot
	if procRoot == "" {
		procRoot = "/proc"
	}
	// 1. Kill and drain the tree.
	c, err := in.Cgroups.OpenCleanup(in.Host)
	switch {
	case errors.Is(err, cgroup.ErrNoCgroup): // the group never existed or was removed: an empty tree
		rep.Steps[moves.StepTree] = moves.Check{OK: true, Reason: "no cgroup"}
	case err != nil:
		return rep, err
	default:
		defer c.Close()
		if err := Cleanup(ctx, c); err != nil {
			rep.Steps[moves.StepTree] = moves.Check{Reason: err.Error()}
		} else {
			rep.Steps[moves.StepTree] = moves.Check{OK: true}
		}
	}
	// 2. No stray process in the agent's own leaf.
	self, err := os.ReadFile(filepath.Join(procRoot, "self", "cgroup"))
	if err != nil {
		return rep, err
	}
	leaf, ok := cgroup.ParseSelfCgroup(string(self))
	if !ok {
		return rep, fmt.Errorf("stopevidence: own cgroup unreadable")
	}
	pids, err := readProcs(filepath.Join("/sys/fs/cgroup", leaf, "cgroup.procs"))
	if err != nil {
		return rep, err
	}
	rep.Steps[moves.StepStray] = strayCheck(pids, os.Getpid())
	for _, p := range pids {
		if p != os.Getpid() {
			rep.Stray = append(rep.Stray, p)
		}
	}
	// 3. No open database.
	holders, err := FDHolders(procRoot, dbPathsWithSidecars(in.DBPaths))
	if err != nil {
		return rep, err
	}
	if len(holders) == 0 {
		rep.Steps[moves.StepFDs] = moves.Check{OK: true}
	} else {
		rep.Steps[moves.StepFDs] = moves.Check{Reason: fmt.Sprintf("%d process(es) hold the database open", len(holders))}
	}
	// 4. Lease released.
	free, err := in.LeaseFree(ctx)
	if err != nil {
		return rep, err
	}
	if free {
		rep.Steps[moves.StepLease] = moves.Check{OK: true}
	} else {
		rep.Steps[moves.StepLease] = moves.Check{Reason: "lock.json still held"}
	}
	return rep, nil
}

func strayCheck(pids []int, self int) moves.Check {
	for _, p := range pids {
		if p != self {
			return moves.Check{Reason: fmt.Sprintf("pid %d in the agent's cgroup is not the agent", p)}
		}
	}
	return moves.Check{OK: true}
}

func dbPathsWithSidecars(paths []string) []string {
	var out []string
	for _, p := range paths {
		out = append(out, p, p+"-wal", p+"-shm")
	}
	return out
}
```

(`readProcs` parses one pid per line; `cgroup.ErrNoCgroup` is `cleanup.go:6`'s sentinel for a missing host cgroup; the agent's own leaf is the directory `ParseSelfCgroup` names under `/sys/fs/cgroup`, tested with a fixture `procRoot`.) `reconstruct_other.go` returns `ErrUnsupported`. The cut's step 5 (Task 5) adds `StepReplica` after the checkpoint. The host package wires `moveCfg.Evidence` to `stopevidence.Reconstruct` with `Cgroups: eb.Cgroups` at `armMovesPass` (which gains the `eb *activator.ExecBackend` argument; `agent.go:371` has `eb` in hand).

- [ ] **Step 4: Run**: `nice -n 10 go test ./internal/stopevidence ./internal/lease ./internal/moves -count=1 -p 1; GOOS=darwin go build ./...` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "stopevidence: Reconstruct, stop evidence from cgroup, proc and lease state (§5.3)" ...`

---

### Task 4: The staging-wins restore under the switch (strict spawns moved to Plan 1 Task 7b)

**Files:**
- Modify: `internal/activator/backend_exec.go` (`spawnInCgroup` path at `:210`, `Restore` at `:290-320`, delete `promoteStagedReplica` and `MovesPrefix`/`MoveReceiptsKey` if unused after Plan 1), `internal/activator/backend_exec_test.go`, `internal/host/agent.go:363-369` (the `eb.Mirror` arming becomes unnecessary; keep `Mirror` only if another caller uses it)

**Interfaces:**
- Produces: `ExecBackend.Strict func(host string) bool` (when true, the spawn uses `Cgroups.PrepareStrict(host, limit)` then `Prepared.Start(cmd)`, and a failure refuses the spawn with `ErrStrictRequired`); `(b *ExecBackend) NextSpawnStrict(host string) moves.Check` (S2: OK when `Strict(host)` would be honoured: cgroup v2 delegated and `PrepareStrict` supported).
- The host package sets `eb.Strict = movesStrict` (Task 10's hold view: true for a host whose claim op is a move kind).

- [ ] **Step 1: Write the failing test**: `TestRestoreDoesNotPromoteStagingUnderPhase1` (with `b.Phase1` true, a staged object under `moves/<host>/` and an empty own prefix → `Restore` returns `fresh = true` and the staged object is untouched; with `b.Phase1` false the legacy promotion runs as today and its existing tests pass unchanged).
- [ ] **Step 2: Run**, **Step 3: implement**: `ExecBackend.Phase1 bool` (set by the agent from `moves.Enabled`); `Restore` skips `promoteStagedReplica` when `Phase1`; the legacy code stays until Plan 6 Task 8 retires it (index D13). Strict spawns are Plan 1 Task 7b's.
- [ ] **Step 4: Run**: `nice -n 10 go test ./internal/activator ./internal/host -count=1 -p 1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "activator: strict spawns under a move; the staging-wins restore is gone (§5.3, §9.3, M15)" ...`

---

### Task 5: The cut on the source

**Files:**
- Create: `internal/host/movescut.go`, `internal/host/movescut_test.go`
- Create: `internal/moves/seal.go`, `internal/moves/seal_test.go`
- Modify: `internal/host/movespass.go` (`MoveCutting` arm calls `runCut`; `moveCfg` gains `Evidence`, `TimersDrained`, `NewestL0`, `OwnReplicaPosition`, `SealLeaseTTL`)

**Interfaces:**
- `moves.Seal{V int; N uint64; Replica []SealedObject; Files []SealedObject; Timers []SealedObject; SrcCovered, FromDB bool; SealedAt time.Time; LeaseUntil time.Time}`, `SealedObject{Key, VersionID string; Size int64}`, `(s Seal) Encode() string`, `moves.DecodeSeal(string) (Seal, error)`, `(s Seal) Bound() time.Time` (= `LeaseUntil`: the time after which no staging writer that held the lease can still write; the staging park's `evidence_at`, Plan 5).
- `moveCfg.Evidence func(ctx, host string, dbPaths []string) (moves.StoppedReport, error)` (wired to `svc.ReconstructStopEvidence` with `leaseFree` = `lease.LockState` on the own prefix); `moveCfg.TimersDrained func(host string) bool` (the runner's `Busy`, Task 6); `moveCfg.NewestL0 func(ctx, bucket, prefix, region string) (uint64, error)` (lists the own replica's level-0 files through `lease.VersionedStore` and returns the highest `MaxTXID`; the required final TXID `N`); `moveCfg.LocalPosition func(dbPath string) (uint64, error)` (the local database's TXID from litestream's `-txid` sidecar or its WAL; `src_covered` is `N == LocalPosition` with no pending write).
- `runCut(ctx, s, w, rec, tgt) error`: the phase loop of §7.4:
  - `requested`: fresh target and source reports (`moves.Ready` with the last round's delta) and `move.inputs` to pin from the target report → `MoveAdmit(g, now, budget, moves.CutDeadline(now, budget), inputs)`; stale → `MoveBackToReady`.
  - `admitted`: re-check inputs against current bundle etags (`appconfig.LoadMergedHost` etag), overlay etag, release and launch report digest; mismatch → `MoveBackToReady`; else `MoveCutPhase(admitted → stopping)`.
  - `stopping`: `SetHold(host, "moving", id)`; fence (`cfg.Fence`); wait `TimersDrained`; `cfg.Evidence` (steps 1 to 4); `!TreeStopped()` → retry next pass until `cut_deadline`, then `MoveRevert(g, "stop evidence budget")` and `hostclaim.Revert`; `TreeStopped()` → the four steps are written into the op journal (`Journal.Steps["evidence"]`, local; nothing is published yet, because the report's `StepReplica`, `FinalTXID` and digests do not exist until the checkpoint) → `MoveCutPhase(stopping → stopped)`.
  - `stopped`: `Checkpoint` (final `SyncAndWait`); `N = NewestL0`; `StepReplica` OK with the database's sha256; `Replication`, `NextDue` from the row; timers snapshot → `move-snapshots/<host>/<id>/timers.json` (own bucket, create-only) and `TimersPrefix` (target, `PutVersioned`); `files.plain` via `archive.PlainDigest`; **now** the complete report: `stopped-source.json` create-only (K13: `release.Store.Put(ctx, key, body, "")` is create-only on both the S3 and the dir store, `store.go:38`, and loses with `release.ErrCASConflict`; on a conflict read the existing object, and equal `Host`, `OpID`, `Role` and `FinalTXID` is a replay and fine); `MoveRecordSource`; then `hostclaim.Update(ExecToNone(id, box, stoppedKey, JournalEvidenceSource))` and `SetJournal(cut_session = holder.session)` in the same transition (a crash between any two of these replays the phase: the checkpoint is idempotent on a stopped database, the report is create-only, the CAS is conditional); `MoveCutPhase(stopped → staged)` is written only after the final round below succeeds, so the phase `stopped` repeats the mirror until it does:
  - the final round: `MirrorPrefix` into `replica/` under the lease; `Deferred > 0` → count in the journal (`moveLedger.DeferredRounds`), three → `MoveRevert`; else seal = `round.Versions` for every current key plus the files and timers versions, `SrcCovered = (N == LocalPosition)`, `FromDB = true` when the stopped database file exists; `SetJournal(seal)` and `SetMovePhase(moving, "" → sealed)` in one claim CAS; `MoveCutPhase(stopped → staged)`.
  - `staged`: the final archive from the stopped directory, every step replayable and all of them **before** the phase advances. Sealing is randomized (§8.2), so the archive's identity is fixed **before** any PUT: `archive.Build` + `Seal` into `<JournalDir>/files.sealed` and `files.json` (the manifest with the sealed key and both digests), then the identity (`sealed` digest, `plain` digest, sealed key, byte count) into the op journal (`Journal.Steps["archive"]`); a replay that finds the journal entry and the temp file re-puts those bytes under the same key `files-<sealed>.sealed` (create-only; `ErrExists` is its own earlier PUT); a replay that finds the entry but not the temp file checks the object by key (`ListVersions` of the exact key in the own bucket): present → reuse; absent → rebuild, reseal and **overwrite the journal entry** (nothing was published under the old identity). Then: `PutIfAbsent` to `move-snapshots/<host>/<id>/files-<sealed>.sealed` and the manifest beside it; copy both into `FilesPrefix` with `PutVersioned`; `SetJournal(seal)` with `Files` filled (same `N`); only then `MoveCutPhase(staged → archived, stamp MoveSource.Files digests)`. The phase never says `archived` without the seal naming the archive, and the record's digests are the journaled identity's.
  - `archived`: precondition, re-read from the claim: `seal.Files` non-empty and `seal.N == Source.FinalTXID` (else the phase is a replay of `staged`'s tail: redo the copy and the journal, never flip); `MoveFlip(g, now, now + 15 min)`; then `hostclaim.Update(Flip(id, targetBox, holder.stamp, holder.generation))`; `nudgeSerial`. The claim half may be written by anyone who sees the record ahead (§7.5); the source tries once here and the console pass (Plan 5) finishes it.
  - every phase: `now > cut_deadline` → `MoveRevert` (phases `stopping..archived`) and `hostclaim.Revert(id, cfg.Box, rec.Move.To, &stagingScope, newID)` once exec is none (Task 2's five arguments; `stagingScope` as Plan 4 Task 6 builds it from the receipts ledger's `LeaseUntil`).
- Each phase's local progress is in the op journal F12 (`moves.Journal`, Task 7's shape, written here first as `internal/moves/journal.go`).

Tests (`movescut_test.go`, harness from Plan 1 Task 14 extended with a `fakeEvidence` returning a configurable report, a fake versioned store with a `failAfter(n)` hook, and a claim written into the harness store): `TestCutAdmitsOnlyFreshPassingReports`, `TestCutGoesBackToReadyWhenInputsMove`, `TestCutResumesStoppingFromEvidenceAlone` (Review Focus 1: a fresh worker on the same store with the record at `stopping` calls `Evidence` and advances, with no in-memory state), `TestStoppedReportIsCompleteBeforeExecToNone` (the report object exists with `StepReplica` and `FinalTXID` before the claim's exec is `""`; a crash injected after the checkpoint and before the report leaves exec with the source; the replay publishes one report), `TestCutRevertsAfterThreeDeferredRounds` (Review Focus 2), `TestSealNamesEveryCurrentVersionAndN` (witness `wSealByVersion`: a stale copy landing after the seal creates a version the seal does not name), `TestCutArchiveIsCreateOnlyAndReused` (codex 19, with the real randomized sealer: crashes after the journal entry, after the create-only PUT, after the first copy; every replay publishes the **same** `files-<sealed>` key; a replay with the temp file deleted and no object yet reseals under a new key and the record names the new one; a replay with the temp file deleted and the object present reuses it), `TestArchivedNeverPrecedesTheSealedArchive` (crashes injected after the create-only put, after the first `PutVersioned`, and after the second: in every case the record is still `staged`; the replay completes and the seal names both `files/` versions before `archived`; a record hand-edited to `archived` with an empty `seal.Files` does not flip), `TestFlipWritesRecordThenClaim` (record `landing` with region `uk`; claim `landing/checking`, exec `uk-box`, `source_stamp` journalled; `holder.generation` unchanged), `TestCutDeadlinePassedReverts`.

- [ ] **Step 1 to 4** as above. Run: `nice -n 10 go test ./internal/host ./internal/moves -run 'TestCut|TestSeal|TestFlip' -count=1 -p 1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "host: the journaled cut, requested to flipped, with stop evidence and the seal by version (§7.4, §7.5)" ...`

---

### Task 6: Holds everywhere else: reconcile, schedule, the hold view

**Files:**
- Create: `internal/host/moveshold.go`, `internal/host/moveshold_test.go`
- Modify: `internal/provision/reconcile.go` (`ReconcileConfig.Hold`, `KeepParticipant`; mint and removal), `internal/provision/reconcile_test.go`
- Modify: `internal/schedule/runner.go` (skip held rows; `Busy`), `internal/schedule/converge.go` (`Frozen`), their tests
- Modify: `internal/host/schedule_wiring.go`, `internal/host/agent.go` (wire the hooks)

**Interfaces:**
- `provision.ReconcileConfig.Hold func(rec Record) (status, op string, ok bool)`: when ok, a row minted for `rec` is `Upsert`ed with `Status = status, HoldOp = op` and never `ready`; an existing row under that op is left as is. `KeepParticipant func(host string) bool`: when true, the removal phase skips the row ("participant of a running operation"), and the orphan guard does not act on it.
- `schedule.Runner.Busy(host string) bool` (the per-host claim, `runner.go:276`); the runner's `Tick` takes `claim(host)` **first** and then re-reads the route (`r.Reg.Get`) inside the claim: a held row (`registry.IsHold(rt.Status)`) releases the claim and continues with no bookkeeping. The order matters (the cut's drain): `SetHold` then "wait until `!Busy(host)`" is correct only if every tick that observes the row **after** the hold does so while holding the claim, so a tick paused between its route read and its claim cannot slip past the drain and advance `attempts` (`runner.go:350`). The route read before `claim` at `runner.go:143` stays as a cheap pre-filter; the decision is the second read. `schedule.Converger.Frozen func(host string) bool`: `convergeHost` returns `(false, nil)` for a frozen host, and the prune's `alive` set includes frozen hosts so `PruneTimers` keeps their rows. **One gate for all three writers** (round-2 finding 21): `schedule.HostGate` (`internal/schedule/hostgate.go`, a per-host `sync.RWMutex` map) is shared by the `Runner` (its `claim`/`release` take the host's read lock for the delivery), the `Converger` (`convergeHost` holds the read lock across its check of `Frozen` and its writes) and the cut's drain (`moveCfg.TimersDrained` takes the **write** lock after `SetHold`, which waits for in-flight deliveries and convergences, then releases it; from then on the hold check inside the lock refuses both). The agent's overlapping converge loops (`agent.go:1991`, `:2555`) share the one `HostGate` the schedule wiring creates.
- `movesHoldView` in the host package: built from each moves pass's record list plus `hostclaim.Read` per host with a move in flight; exposes `hold(rec) (status, op, ok)` (target after the flip: `landing`; source in `reverting`/`restoring`: `restoring`; target in a rollback's release: `releasing`; source during `moving` at or after `stopping`: `moving`), `keep(host) bool` (this box in `op.participants` of a move-kind op), `frozen(host) bool` (a move-kind op holds the claim), `strict(host) bool` (same).

Test `TestReconcileHoldsParticipantRows` (Review Focus 5): a record with region `uk` and a `Hold` returning `("landing", id, true)` mints the row held on the uk box and never `ready`; the ie box with `KeepParticipant(host) == true` keeps its `moving` row though the record's region is `uk`; with `KeepParticipant == false` the row is removed as today. Test `TestHeldRowsFreezeTimers` (witness `wTimerFreeze`): a held row's due once-timer is not delivered and its `attempts` does not advance; a frozen host's declared schedule is neither upserted nor pruned by `Converger.Pass`; after the hold lifts both resume. Test `TestDrainRaceCannotAdvanceBookkeeping`: a tick reads the route (ready), pauses on a test hook before `claim`; the cut sets the hold and observes `Busy == false`; the tick resumes, claims, re-reads, sees the hold and releases; `attempts` is unchanged and nothing was delivered. Test `TestConvergerCannotWriteAfterTheBarrier`: a `convergeHost` paused after its `Frozen` check (inside the gate's read lock) blocks the cut's drain until it finishes; a converger that starts after the drain sees `Frozen` and writes nothing; the timers snapshot taken after the drain equals the table.

- [ ] **Steps 1 to 4**. Run: `nice -n 10 go test ./internal/provision ./internal/schedule ./internal/host -count=1 -p 1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "reconcile and schedule honour move holds: participant rows kept, held rows never ready, timers frozen and drained (§5.2, §9.4, §9.6, K9)" ...`

---

### Task 7: The operation journal (`RenderHeld` and `MaterializeHeld` moved to Plan 1 Task 7b)

**Files:**
- Create: `internal/appconfig/renderheld.go`, `internal/appconfig/renderheld_test.go`, `internal/release/materializeheld.go`, `internal/release/materializeheld_test.go`, `internal/moves/journal.go`, `internal/moves/journal_test.go`

**Interfaces:**
- `appconfig.RenderHeld(ctx, s release.Store, priv *ecdh.PrivateKey, dataDir string, rt registry.Route, op string, box string) (HeldRender, error)` with `HeldRender{Path, SHA256 string; BundleETags map[string]string; OverlayETag string}`: loads `LoadMergedHost(ctx, s, acct, app, rt.Environment, rt.Host)`, renders through the same writer `syncExecHost` uses (extract the file-writing half into `renderExecConfig(rt, m, key, priv, dataDir) (path string, changed bool, err error)` and call it from both), writes the file whether or not it changed, and writes `ConfigAck{Box, Host, Schema, Shape, At, InstanceConfigETag, MoveOp: op, FileSHA256, BundleETags}` through `WriteConfigAck` (two additive fields on `ConfigAck`: `MoveOp string \`json:"move_op,omitempty"\``, `FileSHA256 string \`json:"file_sha256,omitempty"\``, `BundleETags map[string]string \`json:"bundle_etags,omitempty"\``). `SyncExec` keeps skipping non-ready rows (`execRouteBase`), so the two never race.
- `release.MaterializeHeld(ctx, s Store, optDir string, rt registry.Route, version string) (*Manifest, error)`: `ensureStaged(ctx, s, optDir, RouteAccount(rt), rt.App, version, rt.Host)` then points `LiveLink(optDir, rt.Host)` at `VersionPath(...)` the way `adoptHost` does (extract that symlink step into `pointLive(optDir, host, target string) error` if it is inline), with no restart and no bake-hold check: a held row runs exactly the pinned release.
- `moves.Journal{V int; Host, Op, Phase string; Steps map[string]string; Attempt int; UpdatedAt time.Time}`; `moves.JournalDir(dataDir, host, op) = <dataDir>/.moves/<host>/<op>/`; `moves.ReadJournal(dataDir, host, op) (*Journal, error)` (absent → empty journal); `(j *Journal) Write(dataDir string) error` (temp file, fsync, rename, dir fsync); `(j *Journal) Done(step string) bool`; `(j *Journal) Mark(step, value string)`.

Tests: `RenderHeld` writes the file for a `landing` row that `SyncExec` would skip and the ack carries `move_op`, the sha256 and the bundle etags; a second call with no change still writes an ack. `MaterializeHeld` stages the pinned version into a temp `optDir` and points `live/<host>` at it, never at the channel head. `Journal` round-trips and `Done` reflects `Mark`.

- [ ] **Steps 1 to 4**. Run: `nice -n 10 go test ./internal/appconfig ./internal/release ./internal/moves -count=1 -p 1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "appconfig.RenderHeld, release.MaterializeHeld, the box-local operation journal (§9.1, §9.2, F12)" ...`

---

### Task 8: Build the generation from the sealed replica

**Files:**
- Create: `internal/moves/ltxbuild.go`, `internal/moves/sealedclient.go`, `internal/moves/build.go`, `internal/moves/build_test.go`

**Interfaces:**
- `moves.ReadDBHeader(path string) (pageSize uint32, pageCount uint32, err error)` (SQLite header bytes 16-17 and 28-31).
- `moves.EncodeFull(db io.ReaderAt, pageSize, pageCount uint32, min, max ltx.TXID, timestamp int64, walOffset int64) (data []byte, crc64 uint64, err error)`: `ltx.NewEncoder` with `Header{Version: ltx.Version, Flags: ltx.HeaderFlagNoChecksum, PageSize, Commit: pageCount, MinTXID: min, MaxTXID: max, Timestamp: timestamp, WALOffset: walOffset}`, every page in order **except SQLite's lock page**, `ltx.LockPgno(pageSize)` (`ltx.go:494`; the encoder refuses it, `encoder.go:219`, and litestream skips it), while `Commit` keeps the full page count; `Close`; the CRC-64 is the trailer's `FileChecksum` (read it back with `ltx.NewDecoder(bytes.NewReader(data)).Verify()` then `Trailer().FileChecksum`).
- `moves.BuildIntent{V int; Op, Host string; N uint64; PageSize, Commit uint32; Timestamp int64}`; `moves.InstallManifest{V int; Op, Host string; N uint64; SnapshotKey, SnapshotVersion string; SnapshotCRC64 uint64; AnchorKey, AnchorVersion string; AnchorCRC64 uint64; IntegrityCheck string; DBSHA256 string; BuiltAt time.Time}` (**shared contract type**, coordination with claim Plan 6 Task 6, `feat/hostclaim-box` bf63e250: claim Plan 6 defines this exact shape in `internal/moves/install_contract.go`, beside `moves.ErrAttemptOver`, with these snake_case json tags (final, from platform-1e): `v`, `op`, `host`, `snapshot_txid` (the Go field stays `N`), `snapshot_key`, `snapshot_version`, `snapshot_crc64`, `anchor_key`, `anchor_version`, `anchor_crc64`, `integrity_check`, `db_sha256`, `built_at`; spec rev 7.22 §7.6 names the same keys. `install_contract.go` **is on `main` at d649d8d4** (claim Plan 6 merged 2026-10-06): **this task imports `moves.InstallManifest` and does not redefine it**; `install.json` therefore carries those snake_case keys; `TestInstallManifestKeys` decodes a built `install.json` into `map[string]any` and asserts exactly that key set, so a drift between the contract and this plan fails here); `moves.GenerationPrefix(location, op string) string` (`<location>g-<op>/`, location with its trailing slash); `moves.SnapshotKey(prefix string, n uint64)` = `prefix + "0009/" + ltx.FormatFilename(1, n)`; `moves.AnchorKey(prefix, n)` = `prefix + "0000/" + ltx.FormatFilename(n, n)`; `BuildIntentKey(prefix) = prefix+"build-intent.json"`, `InstallManifestKey(prefix) = prefix+"install.json"`.
- `moves.NewSealedClient(store lease.VersionedStore, bucket, replicaPrefix string, seal Seal) *SealedClient` implementing `litestream.ReplicaClient`: `Type() "sealed"`, `Init` no-op, `LTXFiles(ctx, level, seek, _)` returns `ltx.NewFileInfoSliceIterator` over the sealed objects at that level parsed from their keys (`ltx.ParseFilename`), `OpenLTXFile` opens exactly the sealed version by `OpenVersion(ctx, bucket, key, versionID)` (ranged through `VersionRangeOpener` when offset/size are set), `WriteLTXFile`, `DeleteLTXFiles`, `DeleteAll` return `ErrSealedReadOnly`, `SetLogger` stores it.
- `moves.BuildGeneration(ctx, in BuildInputs) (InstallManifest, error)` with `BuildInputs{Store lease.VersionedStore; Bucket string; Source []SealedObject (the pinned version set: for a move the seal's replica/ objects, for region HA's relocation the versions a `genselect` plan or a current listing pins, L16); N uint64; GenerationPrefix string; Op, Host string; WorkDir string; Now func() time.Time; Verify func(dbPath string) error}` (`SealedClient` is built over `Source`, so nothing in the routine names a staging lane): (1) read or write the intent (`PutIfAbsent`; on `lease.ErrExists` read it and use its timestamp, page size, commit); (2) restore into a fresh scratch file: remove `<WorkDir>/<host>.db`, `-wal`, `-shm` and `-txid` first (litestream's `Restore` refuses an existing output path, `replica.go:601`; the file is reproducible from the sealed versions, so a replay after any crash starts clean), then `litestream.NewReplicaWithClient(nil, sealedClient).Restore(ctx, opt)` with `opt.OutputPath = <WorkDir>/<host>.db`, `opt.TXID = ltx.TXID(seal.N)`; refuse unless the restored position is `N` (litestream writes `<db>-txid`? Only in follow mode; instead compare the plan's final `MaxTXID` through `litestream.CalcRestorePlan(ctx, client, ltx.TXID(N), time.Time{}, logger)` whose last file's `MaxTXID` must be `N`, and verify every planned file with `ltx.NewDecoder(...).Verify()` before the restore); `Verify(dbPath)` = `lease.CheckDB` (integrity_check); (3) `EncodeFull` snapshot `[1, N]` and `PutIfAbsent(SnapshotKey)`; on `ErrExists`, `OpenVersion` the current one and require equal bytes (identical by construction from the intent; differing bytes refuse with `ErrBuildDiverged`); (4) the anchor `[N, N]` with `WALOffset = litestream.WALHeaderSize`, same rule; (5) sha256 of the database file; (6) `InstallManifest` via `PutIfAbsent` (on exists, read and compare `N`, keys, versions).

Tests (`build_test.go`, using `internal/lease/leasetest`'s local versioned store if it offers one, else a small in-memory `lease.VersionedStore` fake in `moves` tests): `TestSealedClientServesOnlySealedVersions` (a newer version of a sealed key exists; `OpenLTXFile` returns the sealed bytes; an unsealed key is absent); `TestBuildGenerationReachesNAndWritesSnapshotAndAnchor` (drive litestream over a real sqlite file through the file replica client as `litestreamfacts` does to produce a replica, seal its current versions, build, then assert: snapshot header `MinTXID 1, MaxTXID N, Flags NoChecksum`, anchor `[N, N]` with `WALOffset == WALHeaderSize`, both `Verify()`, manifest's `DBSHA256` equals the file's, `IntegrityCheck == "ok"`); `TestBuildReplayReusesIdenticalSnapshot` (Review Focus 3, witness `wBuildCrashes`: run the build with a `Store` that fails after the snapshot PUT, **in the same `WorkDir`**, with the half-restored database file left on disk; run again on the same store and directory; the snapshot key has one version, the manifest exists, bytes identical; a second variant fails during the restore itself, leaving a partial output file, and the replay still completes); `TestBuildRefusesAGapOrAShortRestore` (drop a sealed middle file → `ErrBuildIncomplete`, no snapshot PUT); `TestEncodeFullSkipsTheLockPage` (a sparse database file extended past 1 GiB with `Truncate`, page size 4096: the encoder succeeds, the file has `pageCount - 1` pages, `Verify()` passes, and litestream's own restore of it produces a database whose `PRAGMA page_count` equals the original).

- [ ] **Steps 1 to 4**. Run: `nice -n 10 go test ./internal/moves -run 'TestSealedClient|TestBuild' -count=1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "moves: build the generation from the sealed replica: planner restore, snapshot, anchor, intent and manifest (§5.1 steps 2 and 7, §7.6 building)" ...`

---

### Task 9: Park queue helpers for move retirements (no new kinds)

**Files:**
- Modify: `internal/park/remote.go` (`Engine.Parked` only), `internal/park/remote_test.go`
- Modify: `internal/hostclaim/move.go` (`QueueLocationPark`, `QueueStagingPark`, `QueueLocalPark`, `QueueResidueParks`)

**Interfaces (index D6: the engine's existing kinds and destination):**
- A staging lane is a `park-replica` scoped to `moves/<host>/<op>`; a whole location to retire in a region where it is no longer authoritative (the source after the commit, a rolled-back target at `cleaning`) is a `park-replica` scoped to `instances/<host>/<inc>`; a landing's earlier-stay residue in the target region is one `park-generation` per residue generation (`hostclaim.RootGeneration` for the origin). `Claim.Validate` (`claim.go:266-288`) accepts all three unchanged; the archive lands under `parked/<incarnation>/<host>/<retirement>/`; F3 reads the manifest's `Op`.
- `(e Engine) Parked(ctx, w Work) (bool, error)`: the manifest exists and every version in its deletion set is gone (what the landing's `parking` phase waits for, §7.6; acknowledgement still waits for the horizon in `Run`).
- `hostclaim.QueueLocationPark(opID, region, box string, scope ParkScope, newID func() string) func(*Claim) error` (appends `{Kind: "park-replica", Region, Owner: "region:"+region, Park: &scope, Retirement: newID(), ID: newID()}` and, when `box != ""`, a `park-local` for the box; scope validated with `Validate(host)`; idempotent per `(opID, region)` and `(opID, box)`); `hostclaim.QueueStagingPark(opID, region string, scope ParkScope, newID func() string) func(*Claim) error` (a `park-replica` whose scope location is `moves/<host>/<op>`); `hostclaim.QueueLocalPark(opID, box string, newID func() string) func(*Claim) error` (a `park-local` alone, for the landing's `local` phase: `park.Local.RetireArtifacts` runs only against a persisted `park-local` chore of the claim, `local.go:62`, and treats an unknown id as already completed, so the chore must exist in the claim before the call); `hostclaim.QueueResidueParks(opID, region string, location ParkScope, generations []string, newID func() string) func(*Claim) error` (one `park-generation` per listed generation, each `{Kind: "park-generation", Region, Generation: g, Owner: "region:"+region, Park: &scope}`, deduplicated by `(opID, region, g)`; the landing's `parking` phase feeds it `park.Decide`'s candidates grouped by generation).

Tests: `TestLocationParkIsAParkReplicaTheStoreAccepts` (a real `hostclaim.Update` round trip through `release.NewDirStore`: `Validate` passes, the obligation reads back); `TestStagingParkScopeIsTheLane`; `TestResidueParksArePerGeneration` (two residue generations and the root → three chores, `@root` named); `TestParkedReportsManifestAndDeletions`; `TestEngineArchivesAMoveRetirementUnderParked` (the destination is `parked/<inc>/h/<ret>/instances/h/<inc>/0000/x.ltx@v1` and the manifest's `Op` is the move id).

- [ ] **Steps 1 to 4**. Run: `nice -n 10 go test ./internal/park ./internal/hostclaim -count=1 -p 1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "park: Parked; hostclaim: park queue helpers for move retirements on the existing kinds (§6.4, D6)" ...`

---

### Task 10: The landing, `checking` to `restored`

**Files:**
- Create: `internal/host/movesland.go`, `internal/host/movesland_test.go`
- Modify: `internal/host/movespass.go` (the pass also runs `landingPass` for records whose `Move.To == cfg.Region` and `Move.State == landing`), `internal/host/agent.go` (wiring: `hostguard.Guard{DataDir, LockDir, Claims: hostguard.StoreReader{Store: store}}`, `park.Engine` for the own bucket, `park.Local`, `RenderHeld`, `MaterializeHeld`, `dbverify`)

**Interfaces:**
- `moveCfg` gains: `Guard hostguard.Guard`, `Park park.Engine` (own bucket and region; `View`, `Authorize` from Plan 5's providers until then a view that lists the location and authorizes only the landing's own op), `Local park.Local`, `RenderHeld func(ctx, rt registry.Route, op string) (appconfig.HeldRender, error)`, `MaterializeHeld func(ctx, rt registry.Route, version string) error`, `ReplaceTimers func(host string, rows []registry.Timer) error`, `SetReplication func(host, class string) error`, `SetNextDue func(host string, due int64) error`, `RemoveVerifyEntry func(ctx, host string) error` (loads `dbverify.Load`, deletes `Hosts[host]`, `Publish`), `OwnLease func(ctx, prefix string) (*lease.Held, error)`, `SecretsPriv *ecdh.PrivateKey`.
- `landingPass(ctx, s, w, rec) error` per phase (§7.6), each phase first `MoveLandingPhase(g, from, to)` then its side effects, journaled in `moves.Journal`:
  - every landing phase begins with `syncClaimPhase(ctx, rec, obs)`: the record's `landing_phase` is the decision and the claim's `op.phase` follows it (§7.5); when the claim is **behind** (a crash after the record CAS and before the claim CAS), the function walks `landingOrder` from the claim's phase to the record's, one `SetMovePhase(id, KindLanding, from, to)` per step, before the phase's work; when the claim is **ahead** (a crash after the claim CAS of a later step, impossible by construction since the claim never leads, but checked) it is left alone and logged; equal is the common case. Then the phase does its work, writes the record CAS (`MoveLandingPhase`) and mirrors it (`SetMovePhase`). The console pass (Plan 5 Task 7) runs the same catch-up for a landing it finds behind. `AdmitWriter` checks the phase and never advances it (`generation.go:35`), so the probe's admission observes `probing` only through this mirroring.
  - `checking`: read `stopped-source.json` (must be `Complete()`); claim: `RequireMoveOp(id, KindLanding, "checking")`, `Exec == cfg.Box`; `hostclaim.Update(Touch(id, nil, []string{cfg.Box}))` if absent (Plan 1 Task 14 added it at the first staging write; this is the idempotent K12 re-add); `move.json` (own bucket, readable here) incarnation == `rec.Move.Incarnation` == `holder.incarnation`; before activating anything, read the guard record (`hostguard.Capture(dataDir, host)` returns `(Captured, error)`; `ErrNoRecord` means no earlier stay) and the claim's `Inventory[cfg.Box]`: an earlier stay's paths and their stamp `S'` are what `local` will retire, so they are noted in the op journal (`Journal.Steps["previous"] = S'`) **before** `cfg.Guard.Activate(ctx, host, cfg.Box, nil)` sets `active_stamp = holder.stamp` (the flip's `F`); `hostclaim.Update(QueueLocalPark(id, cfg.Box, newID))` so `local` has a persisted chore. **No lease is taken here** (round-2 finding 9): `park.Engine.Run` acquires the location's lease itself (`remote.go:404`), so the landing acquires `OwnLease` on `holder.location` only after `parking`, at the start of `building`, and keeps it in the worker's per-host map through `published`.
  - `parking`: list `holder.location` in the own bucket (`Store.ListVersions`); nothing there → done; else group the residue by generation as `park.Decide` does (every generation other than the one the landing will build; the origin as `@root`) and `hostclaim.Update(QueueResidueParks(id, cfg.Region, scope{Location: TrimSuffix(location, "/"), Incarnation, EvidenceAt: rec.Move.FlippedAt, RequestLimit: 120 s}, generations, newID))` once (journal step); `cfg.Park.Run(ctx, Work{Host, ID, Op})` per chore (ErrPending expected), then `cfg.Park.Parked` must be true for each before advancing. Per-generation chores are what let the acknowledgement finish after the probe makes the new generation live in this location (D6; a whole-location park would meet `remote.go:346`'s live-set refusal forever); the acknowledgement half is C4's runner.
  - `building`: `moves.BuildGeneration` with the seal from the claim journal (`DecodeSeal(obs.Claim.Op.Journal[JournalSeal])`), `ReplicaPrefix` in the own bucket, `GenerationPrefix(holder.location, id)`, `WorkDir = JournalDir`.
  - `published`: journal only (no `generation.json`, R10-3).
  - `local`, in this order (round-2 finding 8; `park/local.go:202` requires paths **relative to `DataDir`**, tags them with the captured stamp and refuses a path already tagged with another stamp): (1) `captured, err := hostguard.Capture(dataDir, host)` (now `F`); (2) when the journal's `previous` names `S'` and `Inventory[cfg.Box]` has paths tagged `S'`: `cfg.Local.RetireArtifacts(ctx, host, captured, <the park-local chore id>, id, S')` parks every artifact tagged `S'` (the old `<host>.db`, `<host>.db.gen`, `<host>/`, `<host>.env`) into `.parked/<inc>/<host>/<retirement>/artifacts/`, which clears their inventory entries; when there is no `S'`, `AcknowledgeEmpty`; the test asserts on the filesystem that the old files are gone from the live paths and present under `.parked/`, not on the call returning nil; (3) `archive.Open` + `Extract` the newest archive from `FilesPrefix` (by the seal's files version) into `<DataDir>/<host>/` and `<DataDir>/<host>.env`; (4) `cfg.Local.RecordInventory(ctx, host, captured, "flat", []string{host + ".db", host + ".db.gen", host, host + ".env"}, check)` registers the new installation under `F`; (5) `ReplaceTimers(host, snapshot.Rows)` from the sealed timers version (through `SelectedTimers.ReplaceTimers` once C3 lands; `registry.ReplaceTimers` directly behind the switch until then); `SetReplication(host, Source.Replication)`, `SetNextDue(host, Source.NextDue)`; `RemoveVerifyEntry`; (6) the **prepared files manifest** of spec rev 7.21 §7.6 (L17, R18-1; claim rev 6.30 73b1b9dc) under the installation's authority, never the session-bound publisher (no target session exists yet): `op.exec` writes the installed files, `<host>.env` and the wake state as create-only content objects under `<location>files/<P>/objects/<seq>-<sha256>` and then the one create-only manifest `<location>files/<P>/manifest.json`, bound to `{location, P, op id, installation stamp}`, where **`P` is the installation's prepared destination generation, `"g-" + src.GenerationOp`** (the move id here; the rollback id for Plan 4's dirty return, `installSource.GenerationOp`), **never `holder.generation`**, which still names the source's pre-move generation until the probe's admission (spec rev 7.22 §7.6, R19-1: a manifest bound to or filed under the source's generation could never be the new generation's baseline, and the first landing admission would block); `TestLocalBindsThePreparedManifestToTheNewGeneration` asserts the manifest key is `files/g-<op>/manifest.json` and its binding names `g-<op>` while the claim's `holder.generation` is still the source's; naming exact versions, at sequence 0, never replaced and retired with its generation, through C8's prepared-manifest build (region HA :809; `files.BuildPrepared(ctx, store, location, generation, opID string, stamp uint64, dir, envPath string, wake WakeState) (manifestVersion string, err error)` or its real name, called with `generation = P`), journalled like the install manifest; a replay reuses the objects by digest and the manifest by key; the session-bound publisher starts at the probe's admission (Plan 3 Task 5), takes this manifest by exact version as its baseline and publishes from sequence 1 (a journal step, skipped with a log line until C8 exists); (7) fsync the directory; write `.files-gen` `{"op": id, "incarnation": inc}` last.
  - `configured`: `RenderHeld`; `MaterializeHeld(rt, rec.Move.Inputs.Release)`; the ack's `BundleETags` must equal `rec.Move.Inputs.BundleETags` and its overlay etag `Inputs.OverlayETag`, else `MoveRollBack` (Plan 4 Task 1's transition; until it exists, write `Reason` and stop: `markForRollback(rec, "configuration changed during the move")` is the one call site Plan 4 replaces).
  - `restored`: copy the built database to the row's `DBPath` (flat), sha256 == manifest's, `lease.CheckDB`, write `<dbPath>.gen` `{"op": id, "incarnation": inc, "location": holder.location}`, `MoveLandingPhase(configured → restored, stamp Landing.InstalledDBSHA256)`.
  - `landing_deadline` passed before `restored` → the rollback marker as above.
- `armMovesPass` wiring in `agent.go` for the new hooks; the hold view (Task 6) is updated from both passes.

Tests (`movesland_test.go`, the harness with `cfg.Region = "uk"`, a flipped record and a `landing/checking` claim with `exec = uk-box`, the sealed replica staged in the harness's versioned store from a real small sqlite replica built as in Task 8's test, and a fake `park.Leases` that **refuses a second acquisition of a held location**, so a lease the landing still held would fail the test): `TestLandingCheckingRequiresClaimEvidenceAndTouch`, `TestLandingParkingWaitsForTheManifest` (an earlier stay's objects under `holder.location` in two generations are archived to `parked/<inc>/<host>/<ret>/...` before `building`, by two `park-generation` chores; after the probe publishes the new generation, `Engine.Run` on those chores past the horizon acknowledges rather than refusing), `TestLandingPhaseCatchUp` (round-2 finding 11: a crash after the record's `parking` and before the claim's; the restarted worker advances the claim to `parking` and continues; a claim left at `checking` with the record at `restored` is walked forward step by step), `TestLandingInventoryOrder` (an earlier stay's inventory tagged `S'`: `local` parks it, then registers the new paths under `F`; registering first is refused by the real `RecordInventory`), `TestLandingBuildsInstallsAndRestores` (end to end through `restored`: `.files-gen` last, timers replaced with registrations preserved, `<dbPath>.gen` written, `installed_db_sha256` recorded), `TestLandingMarkersAreWrittenLast` (witness `wMarkersLast`: a crash injected before the final sub-step of `local` leaves no `.files-gen`; the replay finishes it), `TestLandingConfiguredPinsInputs` (Review Focus 4), `TestLandingDeadlineMarksRollback`, `TestLandingReplaysArePhaseIdempotent` (every phase run twice yields one object per key).

- [ ] **Steps 1 to 4**. Run: `nice -n 10 go test ./internal/host -run 'TestLanding' -count=1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "host: the landing from checking to restored: park, build, local install, held render and materialization (§7.6)" ...`

---

### Task 11: Gate, copy list and hand-off

- [ ] Full gate serial and niced; cross-compile darwin, windows, linux-arm64 (`stopreconstruct_other.go`, `fds_other.go` paths).
- [ ] Add this plan's strings to `docs/superpowers/plans/2026-10-06-moves-phase1-copy.md` under "Plan 2": "configuration changed during the move" (reason), "stop evidence budget" (reason), M-C20's text for a stray process alert.
- [ ] Hand-off note for Plan 3 in the commit message: `restored` is the last phase this plan writes; `probing` begins with `ProbeWake`.

## Self-review notes

Spec coverage: §7.4 every phase and its recovery exit (Task 5), §7.5 record then claim (Tasks 1, 2, 5), §5.3 steps 1 to 4 (Task 3) and step 5 (Task 5), §5.1 steps 1, 2, 7 and the build's two durable states (Task 8), §5.2 holds (Plan 1 Task 13, Task 6), §9.6 (Task 6), §9.4 drained, frozen, replaced (Tasks 6, 10), §9.1, §9.2 (Task 7), §9.3 strict spawns and the deleted fallback (Task 4), §6.4 destinations (Task 9), §7.6 through `restored` (Task 10), §7.13 rows for `cutting`, `flipped`, `landing: checking to restored` (Tasks 5, 10 replay tests). The start check of §5.1 and `ProbeWake` are Plan 3's; `reverting`'s phases are Plan 4's (this plan writes the `reverting` record and claim transitions only).
