# PR #23671 proof audit pass 1: fixture matrix

Head d0186cbb21 (/home/ubuntu/wt/pr-23671), merge-base 60b35a1ee0, red a8cd776ee5.
Paths are relative to `apps/server/` in the head worktree. Re-ran at head: `vp test run src/orchestration-v2/ProviderTurnStartService.test.ts src/orchestration-v2/CheckpointRollbackService.test.ts -t "restore finishes|holds turn starts"` → 2 files, 6 passed.

Legend. PTS = `src/orchestration-v2/ProviderTurnStartService.test.ts`, "does not start provider work in $overlap until the restore finishes" (each row at :968–1001, body :1002–1049). It runs the real `ProviderTurnStartServiceV2.start` through `makeLocalCommandHarness` (:263). The restore side is a bare `withCheckpointRestoreReservation` around a Deferred, not the real `execute`. CRS = `src/orchestration-v2/CheckpointRollbackService.test.ts:350`, "holds turn starts inside the worktree during rollback with restoreFiles $restoreFiles". It runs the real `CheckpointRollbackServiceV2.execute` with mocked session/restore. The turn side is a bare `withTurnStartReservation`, not the real `start`. **No test composes real execute with real start.**

| # | Row | Affected by diff | Proven by | Staging needed |
|---|---|---|---|---|
| 1 | Same dir (turn-start cwd == restore cwd) | Yes (main: worktree threads used the literal-path lease, which already serialized identical literal paths; project-root threads took no lease) | PTS row "a project-root thread during a restore of the project checkout" (:970) is same-dir via workspaceRoot. A same-dir *worktree* thread is not a separate row | Optional unit row (worktreePath == restorePath). It already passes on main via the lease, so it is not a red/green differentiator |
| 2 | Symlink alias | Yes | PTS row "a symlink alias of the restoring checkout" (:977). Red at a8cd776ee5 per PR body | None for unit. realPath is faked (`FileSystem.makeNoop`), so a real-FS symlink only appears in the driver below |
| 3a | Child dir: turn start inside the restoring dir | Yes | PTS row "a directory inside the restoring checkout" (:984); CRS restoreFiles=true (:350, turn start at `<worktree>/packages/app`) | None |
| 3b | Parent dir: turn start at an ancestor of the restoring dir (e.g. project-root thread while a worktree nested under workspaceRoot restores) | Yes (`contains` is checked both ways) | **Not proven.** No row has the turn-start cwd as a strict ancestor | Unit row in PTS: restorePath `/tmp/native-account-project/.t3/wt/x`, worktreePath undefined. Run at head (green); at merge-base it would also race (red) |
| 4 | Project-root thread (worktreePath null) turn start vs a worktree restore of the project checkout | Yes (main took no lease for project-root starts) | PTS row :970 (red per PR body) | None |
| 5 | Missing worktree under a symlinked ancestor | Yes (head commit d0186cbb21 only) | PTS row "a not-yet-recreated worktree under a symlink alias of the restoring one" (:991). Not in the red commit; added with the fix | Optional: also red at a8cd776ee5 (the lease is literal), but it was never run there. The PR body says "fourth overlap case" only for green |
| 6 | Non-overlapping workspaces never wait | Yes (new conflict predicate) | **Not proven** by a dedicated test. Sibling dirs (`/a/b` vs `/a/bc`) also untested; that is the `..${sep}` prefix trap `contains` guards | Unit test at head: restore `/tmp/x/a`, turn start `/tmp/x/ab` and `/tmp/x/b` complete while the restore is held |
| 7 | Two turn starts never wait on each other | Yes | Partial: pre-existing PTS "starts another provider in the same checkout while a turn is pending" (:1145) still passes with both starts under `withTurnStartReservation`. No project-root pair; no pair while both are inside startup | Optional unit: two `withTurnStartReservation` on the same and nested cwd both enter while one is held |
| 8 | Conversation-only rewind (restoreFiles false) takes no reservation | Yes | CRS restoreFiles=false (:350, else-branch joins the turn start before releasing the provider) | None |
| 9 | Project-root rollback (thread.worktreePath null) takes no reservation | Yes (early return in CheckpointRollbackService.ts) | **Not proven** directly. Pre-existing rollback tests use worktree threads | Unit (CRS-style, worktreePath null): a turn start in workspaceRoot completes while execute is parked in `open`. Low risk: it is the same code path as main |
| 10 | Project with no workspaceRoot / project missing (cwd undefined) → no reservation, start runs bare | Yes (new branch in ProviderTurnStartService.ts) | **Not proven.** Harness `getById` always returns workspaceRoot (PTS :705–707) | Unit: mock `getById` → `Option.none()`, assert start completes while a restore reservation on `/` is held |
| 11 | Restore failure releases the reservation | Yes (`Effect.ensuring(release)`) | **Not proven** | Unit: restore effect fails; a later turn start proceeds. Head only |
| 12 | Restore interrupted releases the reservation (no wedge) | Yes | **Not proven** | Unit: interrupt the restore fiber while it is held; a waiting turn start proceeds. Head only |
| 13 | Turn start interrupted while waiting | Yes (`release` of a never-added reservation is a no-op; waiters re-check on the next release) | **Not proven** | Unit: fork a turn start parked behind a restore, interrupt it, release the restore, assert the reservation set is empty (a later restore enters immediately). Head only |
| 14 | Restore vs restore overlapping (serialize) | Yes (implied: two `restore` kinds conflict) | **Not proven** | Unit at head. Low risk |
| 15 | Turn start already held, then a restore arrives → restore waits for startup | Yes (implied: conflicts are symmetric) | **Not proven** | Unit at head |
| 16 | Restore waiting behind a stream of overlapping turn starts (starvation) | Yes (implied: `reserve` checks held reservations only, not waiters, so new turn starts keep slipping in) | Not tested. A design note rather than a bug: turn startups are short | Mention to the maintainer; no staging |
| 17 | File restore phase (not just the provider rollback) is covered | Yes | CRS restoreFiles=true: the second turn start is parked during `restoreEntered` (:350, "duringRestore") | None |
| 18 | Real execute + real start, end to end (the maintainer's runtime ask) | Yes | Not proven by any test | Driver (see recommendation), at both 60b35a1ee0 and d0186cbb21 |

## What a real-server runtime transcript can realistically show

Rows 1–4 (and 5) are in principle observable, but only if a slow provider rollback can be forced. In a real `bin.ts` server, rollback goes through a real provider adapter's session (`ProviderSessionManagerV2.open` plus the adapter's native rollback). No built-in fake or echo provider is selectable from `bin.ts`/`config.ts`. The deterministic stand-ins are replay adapters wired only by `src/orchestration-v2/testkit/ProviderReplayHarness.ts`, which needs a `ProviderReplayGate` from `packages/provider-testing/src/replayGate.ts`. Getting that into a real server would need code changes. A real Codex/Claude rollback is quick and goes through the CLIProxy hub, so the race window is milliseconds: overlap would be nondeterministic, and a "before" transcript would likely show nothing. Rows 6–17 (negative and cleanup cases) cannot be shown meaningfully at runtime. **A real-server transcript is impractical. Do not stage one.**

## Recommendation: labelled "service-layer driver" transcript, both SHAs

Write one throwaway vitest file outside the repo, copied into each worktree at run time, e.g. `/tmp/ev-23671/driver/RestoreRace.driver.test.ts` → `apps/server/src/orchestration-v2/`. Run `vp test run src/orchestration-v2/RestoreRace.driver.test.ts` in `/home/ubuntu/wt/pr-23671-before` (60b35a1ee0) and `/home/ubuntu/wt/pr-23671` (d0186cbb21). Then delete the copy. It reuses:

- **Real** `CheckpointRollbackService.layer`, with the same layer stack as CRS at `CheckpointRollbackService.test.ts:350–437`. That stack is `ThreadCommandExecutor.layer`, `NodeServices.layer`, mocked `ServerConfig` (worktreesDir), `ServerSettings.layerTest()`, `ProjectStoreV2`, `GitWorkflowService`, `EventSinkV2`, `IdAllocator.layer`, `ProjectionStoreV2`, `RuntimePolicyV2`. Keep the injected pauses: `ProviderSessionManagerV2.open` parks on a Deferred (the "slow provider rollback") and `CheckpointServiceV2.restore` parks again (the file-rewrite phase). Optionally make `restore` do a real `git restore`/`git clean` on a temp git repo, so the transcript shows the overwritten file.
- **Real** `ProviderTurnStartServiceV2.start` for the second thread. `makeLocalCommandHarness` is a non-exported function in `ProviderTurnStartService.test.ts:263`, and `ProviderTurnStartService.testkit.ts` exports only a `layer`, so the driver must copy the harness. The simpler option is to write the driver as extra cases appended to a temp copy of `ProviderTurnStartService.test.ts`. Point its thread at a real temp dir: a symlink alias, a child dir, or a project-root thread whose `getById` workspaceRoot is the restoring checkout. Use real `NodeServices` FileSystem so `realPath` follows a real symlink.
- Log a timestamped transcript: `rollback: provider open parked`, `turn start: requested`, `turn start: provider session opened`, `rollback: files restored`. Expected output is "before" = opened before restored for every overlap shape, and "after" = restored before opened. Include restoreFiles=false and a non-overlapping sibling as controls that never wait at either SHA.

Caveat for the merge-base run: `withCheckpointRestoreReservation` doesn't exist there, so the driver must only call the services' public `execute`/`start`, never the new helpers. That is the point anyway: it shows the services themselves, not the primitive. Label it "service-layer driver on real temp git worktree, injected provider pause; not a running server."
