# Moves Phase 1, Plan 3: Probe, Commit and Open 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. **Tasks 5, 6 and 7 need claim Plan 6 (`feat/hostclaim-box`) merged**; build Tasks 1 to 4 and 8 first.

**Goal:** Prove the target can run the instance (the probe as a cold admission into the prepared generation, within the budgeted attempts and the cut deadline), commit by one record CAS, let the claim follow with its obligations, open the target row, and park the source.

**Architecture:** The probe policy is pure (`internal/moves/probe.go`): attempt windows, the retry backoff, deadline checks before the start and before readiness. `activator.ProbeWake` runs claim Plan 6's admitted-writer start sequence for a held row with that policy, spawning strictly with `CARLOS_MOVE_PROBE=1` and requiring the readiness answer from the spawned tree. The commit is `provision.MoveCommit` conditional on `now ≤ cut_deadline`, then `hostclaim.Commit` queues a location `park-replica` for the source and a lane `park-replica` for the target and covers the source region's predecessors. The open is `registry.ReleaseHold`. The source's finalization retires its installation under the host guard and records `source_parked_at`.

**Tech Stack:** Go, `internal/hostbox` (claim Plan 6), `internal/hostclaim`, `internal/genselect`, `internal/park`, `internal/hostguard`, unix sockets with `SO_PEERCRED` (`golang.org/x/sys/unix`), cgroup v2.

**Spec:** rev 7.18 §5.1 (the start check, step 9), §7.5, §7.7, §7.9, §7.13, §7.14, §19.2 K6, K8. Index: `2026-10-06-moves-phase1-0-index.md`. Plans 1 and 2 merged.

## Global Constraints

- Gate before every commit (AGENTS.md); cross-compile; sandboxed `GOCACHE`. Tests serial and niced. `SO_PEERCRED` code is `//go:build linux` with a `!linux` stub.
- One attempt is one cold admission; its app may restart under the retry policy (doubling backoff from 1 s, five starts) within the attempt's 30 s readiness window; the deadline is checked before each start and before readiness is accepted (§7.7, R12-2; `TestProbeWindow`).
- A landing has at most three attempts and never more than T11 admitted; a probe never marks the host failed (§7.7).
- The commit is a record CAS conditional on `now ≤ cut_deadline`; the claim follows unconditionally; `holder.generation` is never written by the commit (R10-3).
- The open is the conditional `ReleaseHold(host, moveID)`; the target serves on the session its probe was admitted with; nothing rolls back after a commit.
- Cold only (P7, index D14): every probe and every wake after the open is a cold admission; nothing here reads a certificate. The switch (D13) gates the probe and the open.
- No em dashes.

## Review Focus

1. A probe attempt whose readiness arrives exactly at the window's end must fail; one at 29 s must pass; one whose environment becomes ready only after the window must not start the app (R12-2). Test in Task 1 (`TestProbeWindow`, ported).
2. A commit attempted one second after `cut_deadline` must lose and the move must roll back, even if the probe passed (§7.4, INV-9). Test in Task 2 (`TestCommitRefusedPastTheDeadline`).
3. A target agent that crashes after the commit record CAS and before the claim CAS must, on restart, write the claim half and open, not probe again (witness `wCommitInterrupted`). Test in Task 3 and Task 7 (`TestCommitClaimHalfReplayed`, `TestOpenAfterCrashDoesNotReprobe`).
4. A readiness answer from a process outside the host's cgroup (another local service on the same path) must be refused (§7.7, `SO_PEERCRED`). Test in Task 5 (`TestProbeRefusesForeignPeer`).
5. The source must never park its installation until the record says `committed`, and never while its own `hold_op` is not the move's (a late finalization from an aborted move). Test in Task 8 (`TestSourceParkWaitsForCommit`).

---

## File structure

| path | responsibility |
|---|---|
| `internal/moves/probe.go` | attempt policy and deadlines |
| `internal/moves/startcheck.go` | the start check over the three markers and the claim |
| `internal/provision/provision.go` | `MoveCommit`, `MoveOpened`, `MoveSourceParked`, `MoveProbeAttempt` |
| `internal/hostclaim/move.go` | `Commit` |
| `internal/activator/probewake.go`, `peercred_linux.go`, `peercred_other.go` | `ProbeWake` |
| `internal/host/movesland.go` | `probing`, `probed`, commit, open |
| `internal/host/movesfinal.go` | the source's finalization park |
| `internal/registry/registry.go` | `DeleteIfHold` |

---

### Task 1: The probe policy

**Files:** create `internal/moves/probe.go`, `internal/moves/probe_test.go`.

**Interfaces:**
- `moves.ProbeWindow = 30 * time.Second`, `ProbeSettle = 20 * time.Second`, `ProbeMaxStarts = 5`, `LandingAttempts = 3`, `RecoveryAttempts = 9`; `moves.Backoff(start int) time.Duration` (1 s, 2 s, 4 s, 8 s before starts 2 to 5; 0 before the first); `moves.ProbeAttempt{Index int; Opened time.Time; Starts int}`; `(a *ProbeAttempt) Deadline() time.Time` (`Opened + ProbeWindow`); `(a *ProbeAttempt) MayStart(now, cutDeadline time.Time) error` (`ErrAttemptOver` when `now >= Deadline()` or `now > cutDeadline` or `Starts >= ProbeMaxStarts`); `(a *ProbeAttempt) AcceptReadiness(answeredAt, cutDeadline time.Time) error` (`ErrAttemptOver` when `answeredAt >= Deadline()` or `answeredAt > cutDeadline`); `moves.AttemptsFor(kind string, admitted int) int` (landing: `min(3, admitted)`; recovery: 9).

- [ ] **Step 1: Write the failing test** (port `TestProbeWindow` from `internal/sim/hostlife/witness_test.go:3459` and `wProbeWindowAt`):

```go
// Ported from the model's TestProbeWindow and wProbeWindowAt (R12-2,
// INV-19): readiness k seconds after the attempt's first start: 29 is
// accepted, 30 fails before the start, 31 fails at the deadline with the
// environment still unready.
func TestProbeWindow(t *testing.T) {
	t0 := time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC)
	far := t0.Add(time.Hour)
	for k, want := range map[int]bool{29: true, 30: false, 31: false} {
		a := &ProbeAttempt{Index: 1, Opened: t0}
		if err := a.MayStart(t0, far); err != nil {
			t.Fatal(err)
		}
		a.Starts++
		err := a.AcceptReadiness(t0.Add(time.Duration(k)*time.Second), far)
		if (err == nil) != want {
			t.Fatalf("k=%d: %v", k, err)
		}
	}
	// The environment becomes ready after the window: no start at all.
	a := &ProbeAttempt{Index: 1, Opened: t0}
	if err := a.MayStart(t0.Add(ProbeWindow), far); !errors.Is(err, ErrAttemptOver) {
		t.Fatalf("start after the window: %v", err)
	}
	// Five starts, then the attempt is over whatever the clock says.
	b := &ProbeAttempt{Index: 1, Opened: t0, Starts: 5}
	if err := b.MayStart(t0.Add(time.Second), far); !errors.Is(err, ErrAttemptOver) {
		t.Fatal("sixth start")
	}
	// The absolute cut deadline bounds the whole.
	c := &ProbeAttempt{Index: 1, Opened: t0}
	if err := c.AcceptReadiness(t0.Add(5*time.Second), t0.Add(4*time.Second)); !errors.Is(err, ErrAttemptOver) {
		t.Fatal("readiness past the cut deadline")
	}
	if Backoff(1) != 0 || Backoff(2) != time.Second || Backoff(5) != 8*time.Second {
		t.Fatal("backoff")
	}
	if AttemptsFor("landing", 2) != 2 || AttemptsFor("landing", 7) != 3 || AttemptsFor("recovery", 0) != 9 {
		t.Fatal("attempts")
	}
}
```

- [ ] **Step 2: Run**, **Step 3: implement** per the interface (straightforward arithmetic; `ErrAttemptOver = errors.New("moves: probe attempt window or deadline expired")`, **already on `main` at d649d8d4** in `internal/moves/install_contract.go` (claim Plan 6): this task imports it and does not redefine it; every consumer uses `errors.Is`, so only the identity matters), **Step 4: run** `nice -n 10 go test ./internal/moves -run TestProbeWindow -count=1` → PASS.
- [ ] **Step 5: Commit**: `git commit -m "moves: the probe attempt policy, ported from the model (§7.7, R12-2)" ...`

---

### Task 2: Record transitions for commit, open and the source park

**Files:** modify `internal/provision/provision.go`, test `internal/provision/move_test.go`.

**Interfaces:**
- `MoveProbeAttempt(g MoveGuard, n int)` (state `landing`, phase `probing`; records `Landing.Attempts = n` on the `MoveLandingJournal`, Plan 1 Task 2); `MoveCommit(g, now time.Time) func(*Record) error` (`landing/probed`, `now <= *CutDeadline`, else `ErrMoveConflict` wrapping `ErrCutDeadline`; sets `State = committed`, `CommittedAt`); `MoveOpened(g, now)` (`committed`, `OpenedAt`); `MoveSourceParked(g, now)` (`committed`, `SourceParkedAt`); `MoveClose(g, now time.Time)` (`committed` with `OpenedAt` and `SourceParkedAt` set → `Move = nil`, region already `To`, and `rec.LastMove = &MoveOutcome{ID: g.ID, Outcome: "done", At: now, OfflineS: OpenedAt - CutAt}` in the same write, index D11; Plan 5 calls it when both obligations are gone).

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

```go
func TestCommitRefusedPastTheDeadline(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)
	dl := now.Add(115 * time.Second)
	rec := &Record{Region: "uk", Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, From: "ie", To: "uk", State: MoveLanding, LandingPhase: LandProbed, CutDeadline: &dl}}
	if err := MoveCommit(g, dl.Add(time.Second))(rec); !errors.Is(err, ErrCutDeadline) || rec.Move.State != MoveLanding {
		t.Fatalf("late commit: %v %s", err, rec.Move.State)
	}
	if err := MoveCommit(g, dl)(rec); err != nil || rec.Move.State != MoveCommitted || rec.Move.CommittedAt == nil {
		t.Fatalf("commit at the deadline: %v", err)
	}
	if err := MoveCommit(g, dl)(rec); !errors.Is(err, ErrMoveConflict) {
		t.Fatal("second commit")
	}
	if err := MoveClose(g, now)(rec); !errors.Is(err, ErrMoveConflict) {
		t.Fatal("close before open and park")
	}
	if err := MoveOpened(g, now)(rec); err != nil || rec.Move.OpenedAt == nil {
		t.Fatal(err)
	}
	if err := MoveSourceParked(g, now)(rec); err != nil {
		t.Fatal(err)
	}
	if err := MoveClose(g, now)(rec); err != nil || rec.Move != nil || rec.Region != "uk" || rec.LastMove == nil || rec.LastMove.ID != g.ID || rec.LastMove.Outcome != "done" {
		t.Fatalf("close: %v %+v", err, rec)
	}
}
```

- [ ] **Step 2: Run**, **Step 3: implement**:

```go
// ErrCutDeadline is a commit attempted after cut_deadline (§7.4): the
// record CAS is the decision, so the deadline cannot be passed between the
// record write and the claim write.
var ErrCutDeadline = fmt.Errorf("%w: the cut deadline has passed", ErrMoveConflict)

func MoveCommit(g MoveGuard, now time.Time) 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 != LandProbed {
			return fmt.Errorf("%w: commit follows probed", ErrMoveConflict)
		}
		if rec.Move.CutDeadline == nil || now.After(*rec.Move.CutDeadline) {
			return ErrCutDeadline
		}
		t := now.UTC()
		rec.Move.State, rec.Move.CommittedAt = MoveCommitted, &t
		return nil
	}
}
```

with `MoveOpened`, `MoveSourceParked`, `MoveClose`, `MoveProbeAttempt` in the same style.

- [ ] **Step 4: Run** → PASS. **Step 5: Commit**: `git commit -m "provision: commit conditional on the cut deadline, open, source park, close (§7.9)" ...`

---

### Task 3: `hostclaim.Commit`

**Files:** modify `internal/hostclaim/move.go`, test `internal/hostclaim/move_test.go`.

**Interfaces:**
- `hostclaim.Commit(opID, sourceRegion, sourceBox, targetRegion string, sourceScope, stagingScope ParkScope, newID func() string) func(*Claim) error`: requires `KindLanding` with phase `probing` or `probed` and `Exec == participants[1]`; sets phase `"committed"`; appends location `park-replica` for `sourceRegion` with `sourceScope` (owner `region:<source>`), `park-local` for `sourceBox`, lane `park-replica` for `targetRegion` with `stagingScope`; runs `CoverRegionPredecessors(opID, "committed", sourceRegion)`; idempotent: a claim already at `committed` under this op returns `nil` with no second set of obligations (witness `wCommitReplayed`). `holder.generation` untouched.
- `PhaseCommitted = "committed"`.

- [ ] **Step 1: Write the failing test** (witnesses `wCommitCoverage`, `wCommitInterrupted`, `wCommitReplayed`):

```go
func TestCommitQueuesObligationsAndCoversTheSourceRegion(t *testing.T) {
	c := movingTestClaim()
	op := "mv_abcdefghijklmnopqrstuvwxyz"
	_ = BeginMove(op, "ops", "ie-box", "uk-box", "uk", time.Now())(c)
	_ = ExecToNone(op, "ie-box", "ev", JournalEvidenceSource)(c)
	_ = SetMovePhase(op, KindMoving, PhaseMoving, PhaseSealed)(c)
	c.Gen = 7
	_ = Flip(op, "uk-box", 3, "")(c)
	// The landing mirrors every record phase into the claim (Plan 2 Task
	// 10), so the claim reaches probed through SetMovePhase, never a
	// fixture edit.
	for _, step := range [][2]string{{"checking", "parking"}, {"parking", "building"}, {"building", "published"}, {"published", "local"}, {"local", "configured"}, {"configured", "restored"}, {"restored", "probing"}, {"probing", "probed"}} {
		if err := SetMovePhase(op, KindLanding, step[0], step[1])(c); err != nil {
			t.Fatal(err)
		}
	}
	// The target's probe admission left a record whose predecessor is in ie.
	c.Holder.Generation = op
	c.Holder.Admissions = []AdmissionRecord{{Region: "uk", PredecessorRegion: "ie", Session: 9, Box: "uk-box", Generation: op, Predecessor: "", Mode: "cold", Coverage: "sealed"}}
	ids := 0
	newID := func() string { ids++; return fmt.Sprintf("id%d", ids) }
	src := ParkScope{Location: "instances/a.go.example.com/" + c.Holder.Incarnation, Incarnation: c.Holder.Incarnation, EvidenceAt: time.Now(), RequestLimit: 120 * time.Second}
	stg := ParkScope{Location: "moves/a.go.example.com/" + op, Incarnation: c.Holder.Incarnation, EvidenceAt: time.Now(), RequestLimit: 120 * time.Second}
	if err := Commit(op, "ie", "ie-box", "uk", src, stg, newID)(c); err != nil {
		t.Fatal(err)
	}
	if c.Op.Kind != KindLanding || c.Op.Phase != PhaseCommitted || c.Op.Exec != "uk-box" || c.Holder.Generation != op {
		t.Fatalf("after commit: %+v gen %q", c.Op, c.Holder.Generation)
	}
	kinds := map[string]int{}
	for _, o := range c.Obligations {
		kinds[o.Kind+"/"+o.Region+o.Box]++
	}
	if kinds["park-replica/ie"] != 1 || kinds["park-local/ie-box"] != 1 || kinds["park-replica/uk"] != 1 || len(c.Obligations) != 3 {
		t.Fatalf("obligations: %+v", c.Obligations)
	}
	if !c.Holder.Admissions[0].Covered {
		t.Fatal("the source-region predecessor is not covered (wCommitCoverage)")
	}
	// Replay: nothing doubles (wCommitReplayed).
	if err := Commit(op, "ie", "ie-box", "uk", src, stg, newID)(c); err != nil || len(c.Obligations) != 3 {
		t.Fatalf("replay: %v %d", err, len(c.Obligations))
	}
	// Not from moving.
	d := movingTestClaim()
	if err := Commit(op, "ie", "ie-box", "uk", src, stg, newID)(d); !errors.Is(err, ErrStaleOp) {
		t.Fatalf("commit on a serving claim: %v", err)
	}
}
```

(The exact `Covered` field name and `CoverRegionPredecessors`' semantics are `generation.go`'s; adjust the assertion to the field it sets.)

- [ ] **Step 2: Run**, **Step 3: implement**:

```go
const PhaseCommitted = "committed"

// Commit is the claim half of the record's commit (§7.5, §7.9 step 1):
// phase committed plus, in the same write, a location park-replica for the
// source's location in its region and box, a lane park-replica for the target's
// staging, and CoverRegionPredecessors for the source region. It writes
// nothing into holder.generation (R10-3). Idempotent under the op.
func Commit(opID, sourceRegion, sourceBox, targetRegion string, sourceScope, 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 != KindLanding {
			return fmt.Errorf("%w: kind %q", ErrWrongState, c.Op.Kind)
		}
		if c.Op.Phase == PhaseCommitted {
			return nil
		}
		if c.Op.Phase != "probing" && c.Op.Phase != "probed" {
			return fmt.Errorf("%w: phase %q", ErrWrongState, c.Op.Phase)
		}
		if len(c.Op.Participants) != 2 || c.Op.Exec != c.Op.Participants[1] {
			return fmt.Errorf("%w: exec %q is not the target", ErrWrongState, c.Op.Exec)
		}
		if err := sourceScope.Validate(c.Host); err != nil {
			return err
		}
		if err := stagingScope.Validate(c.Host); err != nil {
			return err
		}
		c.Op.Phase = PhaseCommitted
		s, g := sourceScope, stagingScope
		c.Obligations = append(c.Obligations,
			Obligation{ID: newID(), Op: opID, Kind: "park-replica", Region: sourceRegion, Retirement: newID(), Owner: "region:" + sourceRegion, Park: &s},
			Obligation{ID: newID(), Op: opID, Kind: "park-local", Box: sourceBox, Retirement: newID(), Owner: "box:" + sourceBox},
			Obligation{ID: newID(), Op: opID, Kind: "park-replica", Region: targetRegion, Retirement: newID(), Owner: "region:" + targetRegion, Park: &g})
		return CoverRegionPredecessors(opID, PhaseCommitted, sourceRegion)(c)
	}
}
```

- [ ] **Step 4: Run** → PASS. **Step 5: Commit**: `git commit -m "hostclaim: Commit queues the source and staging parks and covers the source region (§7.9, K8 groundwork)" ...`

---

### Task 4: The start check

**Files:** create `internal/moves/startcheck.go`, `internal/moves/startcheck_test.go`.

**Interfaces:**
- `moves.GenerationMarker{Op, Incarnation, Host, Box string; InstalledAt time.Time; ManifestSHA256 string; SnapshotKey, SnapshotVersion string; SnapshotTXID, SnapshotCRC64 uint64}` (the `generation.json` shape, `genselect.Marker`'s fields); `moves.FilesGen{Op, Incarnation string}`; `moves.DBGen{Op, Incarnation, Location string}`; `moves.PreparedCheck(prepared string, holder hostclaim.Holder, filesGen *FilesGen, dbGen *DBGen) error` (the pre-admission validation of a landing probe's inputs: both markers name `prepared` and the holder's incarnation and location; Plan 3 Task 5 step 3); `moves.StartCheck(c *hostclaim.Claim, marker *GenerationMarker, filesGen *FilesGen, dbGen *DBGen) error` (the §5.1 start check against the claim **after** the admission CAS, Task 5 step 6) (§5.1 the start check: (1) `marker` absent = origin; its `Op` must be `holder.generation` or the predecessor of the latest admission or of a chain of admissions each without a marker (walk `holder.admissions` from the newest: a generation whose `Predecessor` matches is accepted); its `Incarnation` the holder's; (2) `filesGen` when present names the same generation and incarnation as the claim expects (`holder.generation`); (3) `dbGen` when present names the same generation and `holder.location`); errors `ErrStartMarker`, `ErrStartFiles`, `ErrStartDB` with member-safe reasons.

- [ ] **Step 1: Write the failing test**: origin everywhere passes; marker naming the holder's generation passes; marker naming the predecessor of the latest admission passes (lagging); marker naming an unrelated generation refuses `ErrStartMarker`; `.files-gen` with another op refuses `ErrStartFiles`; `<dbPath>.gen` with another location refuses `ErrStartDB`.
- [ ] **Step 2: Run**, **Step 3: implement**, **Step 4: run** → PASS.
- [ ] **Step 5: Commit**: `git commit -m "moves: the start check over the three markers and the claim (§5.1, INV-4)" ...`

---

### Task 5: `ProbeWake` (BLOCKED-ON-CLAIM: C1, C2)

**Files:** create `internal/activator/probewake.go`, `internal/activator/peercred_linux.go`, `internal/activator/peercred_other.go`, `internal/activator/probewake_test.go`.

**Interfaces:**
- Consumes (claim Plan 6): `hostbox.Writer` (`Baseline`, `Admit`, `Published`), `hostbox.GenerationPrefix`, `spawnClaimed(ctx, enabled, manager, host, limit, selected, identity, build) (*exec.Cmd, *cgroup.Prepared, error)` (the `*cgroup.Prepared` is kept for the process's life and, on every exit path including a failed start, `KillAndEmpty` then `Close` run before the attempt is counted), and Plan 6 Task 9's admitted-start sequence exposed as `(a *Service) startAdmitted(ctx, e *actEntry, admitted hostclaim.Observed, policy startPolicy) error` (index: "exact interfaces needed"). Consumes `genselect.SelectPrepared(ctx, s, req, genselect.PreparedPin{Generation, SnapshotKey, SnapshotVersion, SnapshotTXID, SnapshotCRC64})` to plan the prepared generation's restore from the install manifest.
- Produces: `activator.ProbeConfig{Host string; Op string (the claim's op id: the move id, in every kind); HoldOp string (the row's hold_op: the move id for a landing or revert, the rollback id for a rollback's source); PreparedGeneration string (the generation a prepared install produced and the admission opens: the move id for a landing, the rollback id for a dirty return, "" for a clean recovery into the source's own generation); Kind string ("landing" | "recovery"); Prepared *moves.InstallManifest (non-nil exactly when PreparedGeneration != ""); Seal *moves.Seal; Inputs *provision.MoveInputs (the pin a landing probe enforces at spawn; nil for a recovery probe, which records what it rendered); CutDeadline time.Time; Attempt *moves.ProbeAttempt (Index set by the caller; Opened written by the probe at the first app start); RenderHeld func(ctx) (appconfig.HeldRender, error); ReadyPath string; LossID string (empty except for a lost-target return, Plan 4 Task 10, where the admission goes through hostclaim.AdmitWriterWithLoss)}`; `activator.ProbeResult{Session uint64; Generation string; AnsweredAt time.Time}`; `(a *Service) ProbeWake(ctx, cfg ProbeConfig) (ProbeResult, error)`:
  1. the row must be held with `hold_op == cfg.HoldOp` and the claim must name this box `op.exec` under op `cfg.Op` (K1, K6), else `ErrProbeRefused`;
  2. `render, err := cfg.RenderHeld(ctx)` (the caller composes `RenderHeld` + `MaterializeHeld`; every probe kind, model 4); **the launch-input pin, at spawn** (§7.7, round-2 finding 14): for a landing probe (`cfg.Inputs != nil`) the rendered `BundleETags` and `OverlayETag` must equal `cfg.Inputs`', the materialized release must be `cfg.Inputs.Release`, and `moves.ReportEnv(key, b.LaunchEnvironment(rt)).Digest` must equal `cfg.Inputs.EnvReport`; a mismatch is `ErrProbeFailed("configuration changed during the move")` before any admission, and the landing rolls back; a recovery probe records what it rendered into `Rollback.RecoveryInputs` (through the caller's `RecordInputs` hook) and is bound to nothing (J2-R2-5). Plan 2's `configured` check stays as the early refusal; this one is the authoritative one, since the environment can change between the two;
  3. **prepared-input validation**, before any admission, when `cfg.PreparedGeneration != ""` (a landing, or a dirty return): `moves.PreparedCheck(cfg.PreparedGeneration, holder, filesGen, dbGen)`: `.files-gen` and `<dbPath>.gen` name that generation and the holder's incarnation and location, and `cfg.Prepared` exists with `N == cfg.Seal.N`. This is not the §5.1 start check against `holder.generation`: before the first probe's admission `holder.generation` is still the predecessor's (the source's after a flip, the target's after a dirty `ReturnToSource`, which deliberately keeps it, Plan 4 Task 2; only `AdmitWriter` moves it, `generation.go:27`), so a start check here would refuse the install's own files (round-2 finding 13). When `cfg.PreparedGeneration == ""` (a clean recovery or a revert), the markers, when present, must name `holder.generation` as observed;
  4. `cfg.Attempt.MayStart(now, cfg.CutDeadline)` is checked with `Opened` **unset** (the window has not begun); the cut deadline alone applies here;
  5. the admission: `decide` per §7.14: cold; `NewGeneration = cfg.Op` when `holder.generation` is neither `cfg.Op` nor a descendant admitted from it, else a fresh id (`newID()`); `KeepStamp = holder.stamp > holder.session`; `Coverage = "sealed"` when `cfg.Seal != nil && cfg.Seal.FromDB` and the generation is the prepared one, else `""`; `Region`, `PredecessorRegion` per the last admission record; `RemoteMaxTXID` from the baseline plan (`SelectPrepared` for the prepared generation; `Writer.Baseline` for a fresh one); then `Writer.Admit`;
  6. `startAdmitted` with `startPolicy{MaxStarts: moves.ProbeMaxStarts, Window: moves.ProbeWindow, Deadline: cfg.CutDeadline, Probe: true, Prepared: cfg.Prepared}` (C1): the sequence purges and restores **before** the window opens, then sets `cfg.Attempt.Opened = now` at the first app start and journals it (round-2 finding 19: the 30 s window is the app's, the restore is priced separately in T11); **the §5.1 start check runs here, inside the admitted sequence**, with `moves.StartCheck(claimAfterCAS, marker, filesGen, dbGen)` against the generation the admission CAS just named (`holder.generation == cfg.Op` for a first probe; a fresh id for a restarted one, whose `.files-gen` still names `cfg.Op` and is accepted through the admission chain: Task 4's `StartCheck` takes the claim and walks `holder.admissions`); the spawn goes through `spawnClaimed` with `CARLOS_MOVE_PROBE=1` in the child's environment and is strict;
  7. readiness: `GET cfg.ReadyPath` over the row's socket with `Host: <host>`, 200 to 399, the peer's `SO_PEERCRED` pid a member of `carlos-exec@<host>` (`cgroup.Cleanup.Members`), `cfg.Attempt.AcceptReadiness(answeredAt, cfg.CutDeadline)`; a 20 s settle, the process still running, a second request;
  8. the marker and publication are `startAdmitted`'s (first upload, `generation.json` by If-Match or If-None-Match, `Writer.Published`); the entry stays held: `RetireExcept` keeps entries whose row `IsHold`.
  A failure after the admission stops the tree (`FenceAndStop` with a fresh generation) and returns `ErrProbeFailed` with the reason; the attempt is over; the operation decides what next.
- `peercred_linux.go`: `peerPID(conn net.Conn) (int, error)` via `unix.GetsockoptUcred`; `peercred_other.go` returns `ErrStopEvidenceUnsupported`.

Tests: `TestProbeRefusesUnheldRowOrForeignExec`; `TestFirstProbeFromTheSourceGenerationIsAdmitted` (the claim as the flip left it: `holder.generation` is the source's `""`, `.files-gen` and `<dbPath>.gen` name the move id; the probe validates the prepared inputs, admits into the move id, and the start check after the CAS passes; a hand-built claim whose `holder.generation` is already the move id with a `.files-gen` naming another op refuses); `TestProbeRendersBeforeStart` (the `RenderHeld` hook is called before any spawn, model 4); `TestProbeRefusesForeignPeer` (Review Focus 4: a fake listener answering 200 from this test process, whose pid is not in the host cgroup → `ErrProbeFailed` naming the peer); `TestProbeAttemptEndsAtTheWindow` (the fake start answers at 31 s → `ErrAttemptOver`); `TestProbeIsAColdAdmissionIntoThePreparedGeneration` (with Plan 6's fake writer: `Admit` is called once with `NewGeneration == op`, `Mode == "cold"`, `Coverage == "sealed"`, before the spawn hook runs; a second attempt admits a fresh generation, `KeepStamp` false).

- [ ] **Steps 1 to 4**: run `nice -n 10 go test ./internal/activator -run TestProbe -count=1` → PASS. Cross-compile.
- [ ] **Step 5: Commit**: `git commit -m "activator: ProbeWake, a cold admission into the prepared generation with the attempt policy (§7.7, §7.14)" ...`

---

### Task 6: The landing's `probing` and `probed` (BLOCKED-ON-CLAIM: C1)

**Files:** modify `internal/host/movesland.go`, `internal/host/movesland_test.go`.

**Interfaces:**
- `moveCfg.Probe func(ctx, activator.ProbeConfig) (activator.ProbeResult, error)`; `moveCfg.RenderAndMaterialize func(ctx, rt registry.Route, op, version string) (appconfig.HeldRender, error)`.
- `landingPass` phases: `restored → probing` (`MoveLandingPhase`, then the claim's `SetMovePhase(id, KindLanding, "restored", "probing")`, Plan 2 Task 10's mirroring rule, so the admission's `ObservedPhase` is `probing`); in `probing`, attempts from the journal (`Journal.Attempt`): `n = AttemptsFor("landing", targetReport.AttemptsAdmitted)`; for each attempt not yet recorded: `MoveProbeAttempt(g, i)` and journal `Attempt = i` before the call (the attempt is accounted here; its `Opened` is written by the probe at the first app start, Task 5 step 6, so the restore does not eat the window; a crash restarts only the probe, §7.13, and a journalled attempt with an `Opened` whose window passed counts as failed), `cfg.Probe(...)`; success → `MoveLandingPhase(probing → probed)` then `SetMovePhase(id, KindLanding, "probing", "probed")`; `ErrAttemptOver`/`ErrProbeFailed` → next attempt, if any; attempts exhausted or `cut_deadline` passed → the rollback marker (Plan 4 Task 1's `MoveRollBack`; until it lands, `markForRollback`).
- Tests: `TestLandingProbesWithinTheBudget` (two admitted attempts: the first fails, the second passes → `probed`; a third is never attempted); `TestLandingProbeCrashRestartsOnlyTheProbe` (a fresh worker on a journal at `probing` with `Attempt = 1` does not rerun `local` or `restored`: their journal steps are untouched and no second `.files-gen` write happens); `TestLandingExhaustedAttemptsMarkRollback`.

- [ ] **Steps 1 to 5**. Commit: `git commit -m "host: the landing's probing and probed phases (§7.6, §7.7)" ...`

---

### Task 7: Commit and open on the target (BLOCKED-ON-CLAIM: C1, C2; Tasks 5 and 6 first)

**Files:** modify `internal/host/movesland.go`, `internal/host/movesland_test.go`; modify `internal/registry/registry.go` (`DeleteIfHold`).

**Interfaces:**
- In `landingPass`, at `probed`: `provision.SetRecordMove(MoveCommit(g, cfg.Now()))`; `ErrCutDeadline` → the rollback marker; success or an already-`committed` record → the claim half: `hostclaim.Update(Commit(id, rec.Move.From, sourceBox, cfg.Region, sourceScope, stagingScope, newID))` where `sourceScope = {Location: TrimSuffix(holder.location, "/"), Incarnation, EvidenceAt: the cut's stop evidence time (`stopped-source.json`'s `CheckedAt`), RequestLimit: 120 s}` and `stagingScope = {Location: moves.ScopeLocation(host, id), Incarnation, EvidenceAt: seal.Bound(), RequestLimit: 120 s}` (the staging lane's own seal bound, claim rev 6.23; Plan 5 Task 3 proves the deadline); the claim half is written by whoever sees the record ahead (§7.5): this pass, or the console's pass (Plan 5).
- The open: `cfg.ReleaseHold(host, id)` (`registry.ReleaseHold`); `changed == false` with the row already `ready` and `hold_op == ""` is a replay and fine; `MoveOpened(g, now)`; `nudgeSerial`; the activator's entry from the probe is the serving process: no restart.
- `registry.DeleteIfHold` is Plan 1 Task 12's (D12); Task 8 uses it without depending on this task.

Tests: `TestCommitThenOpenInOrder` (witness `wCommitBeforeOpen`: driven from the flip through every landing phase with the real transitions, no fixture phase edits; the row is `ready` only after the record says `committed` and the claim says `committed`); `TestOpenAfterCrashDoesNotReprobe` (Review Focus 3: a worker restarted on a record at `committed` with the claim still `landing/probed` writes the claim half, opens, and never calls `Probe`); `TestLateCommitRollsBack` (Review Focus 2).

- [ ] **Steps 1 to 5**. Commit: `git commit -m "host: commit as a record CAS, the claim half, the conditional open (§7.5, §7.9 steps 1 and 2)" ...`

---

### Task 8: The source's finalization park

**Files:** create `internal/host/movesfinal.go`, `internal/host/movesfinal_test.go`; modify `internal/host/movespass.go` (records in `committed` whose `From == cfg.Region` run `finalizeSource`).

**Interfaces:**
- `finalizeSource(ctx, s, w, rec) error` (§7.9 step 3), on the source box, only when the record is `committed` and **either** this box's row has `hold_op == rec.Move.ID` **or** the op journal (`moves.Journal` for this op) has `Steps["row-deleted"]` (round-2 finding 12: a crash between the row's deletion and `MoveSourceParked` must not lock the close out; a replacement row with another `hold_op` is refused by the first condition and does not match the second); Review Focus 5:
  1. the remote half: `cfg.Park.Run(ctx, park.Work{Host, ID: <the park-replica obligation for cfg.Region under this op, D6>, Op: id})` (the engine takes `Leases.Acquire` itself; the source holds no lease of its own here); `ErrPending` is fine: the copies and version deletes are done when `cfg.Park.Parked` is true; acknowledgement after the horizon is the regional runner's (C4);
  2. the local half under the host guard: `cfg.Local.RetireInstallation(ctx, host, hostguard.Capture(dataDir, host), <park-local obligation id>, id)` parks the database with `<dbPath>.gen`, the files directory, `<host>.env`, `<db>.corrupt` and the op journals into `.parked/superseded-<id>/<host>/<retirement>/` (the destination `park.Local` derives from the obligation's op; if `park.Local` hardcodes `.parked/<inc>/...`, add the `superseded-<op>` form beside Task 9's remote destination); `CompleteObligation(opID, parkLocalID)` through `hostclaim.Update`;
  3. `cfg.ReplaceTimers(host, nil)` (clears the timers rows); journal `Steps["row-deleted"] = "pending"`, then `cfg.DeleteIfHold(host, id)` (the row), then `Steps["row-deleted"] = "done"` (a replay that finds `pending` and no row with this `hold_op` treats the delete as done); the source box's config ack (`appconfig.ConfigAckKey(cfg.Box, host)`, §6.3) is copied to `moves.ReportPrefix(...) + "config-ack-source.json"` and deleted;
  4. `MoveSourceParked(g, now)`; `nudgeSerial`.
  Every step journaled in `moves.Journal`; replayable.

Tests: `TestSourceParkWaitsForCommit` (Review Focus 5: a `landing` record, or a `committed` one whose row's `hold_op` is another op → nothing parked, nothing deleted); `TestSourceParkSurvivesACrashAfterTheRowDelete` (round-2 finding 12: the journal says `row-deleted: pending`, the row is gone; the replay stamps `source_parked_at`); `TestSourceParkRetiresInstallationAndRow` (the database, `.gen`, files dir and env are gone from live paths and present under `.parked/superseded-<id>/`; the row is gone; timers rows gone; `source_parked_at` set; the `park-local` obligation acknowledged); `TestSourceParkReplayIsIdempotent`.

- [ ] **Steps 1 to 5**. Commit: `git commit -m "host: the source's finalization park after the commit (§7.9 step 3)" ...`

---

### Task 9: Gate and copy

- [ ] Full gate, cross-compile (the `peercred_other.go` stub).
- [ ] Copy list for Plan 6: M-C6 "Stopped in <from>. Starting in <to>." (emitted at `probing`), M-C7 (emitted at the open: "Moved. <host> is served from <to>. It was offline for <seconds> seconds.").

## Self-review notes

Coverage: §7.7 every bullet (Tasks 1, 5), §7.14 the landing rows and the admitted writer's start (Task 5, through Plan 6's sequence), §5.1 start check (Task 4), §7.9 steps 1 to 3 (Tasks 2, 3, 7, 8), §7.5 commit row and "any actor writes the claim half" (Tasks 3, 7; Plan 5 adds the console), K6 (Task 5 step 1), §7.13 rows `landing: probing, probed` and `committed, the claim still landing` (Tasks 6, 7). Steps 4 and 5 of §7.9 (the target's staging park and the close) are Plan 5's.
