# PR #23579 proof audit, pass 2

Checkouts: before `8d3b061121fe65669d5d43d011b48cd3027a4108`, after `e3c21846c23fc6f412914a3590c73b94e3004e5e`. The after SHA matches `cafe pr view 23579 --json` headOid. Every transcript header records the expected SHA for its side.

## Artifact integrity
- I downloaded all 11 uploaded artifacts into an empty `/tmp/ev-23579/audit2/dl/`. `driver.sh` sha256 is `cd750c4e…eb2ef`, which matches the claim. All 11 files are byte-identical (`cmp`) to the local copies in `/tmp/ev-23579/capture/`.
- `gitwrap/git` is on PATH but never armed: the driver never creates `hold.flag`. The queue therefore comes from the uninstrumented `burst.mjs` race, as SUMMARY says.
- `bin.ts`/cli only defines `--base-dir`. Dev seeding lives only in `scripts/dev-runner.ts`, so a bare `bin.ts` with a fresh base-dir does not copy from `~/.t3`. The claim is confirmed.

## Row by row (each checked against the uploaded transcripts)
| Row | Claim | Backed by log? |
|---|---|---|
| R1 before | Both POSTs 202. At kill: pending empty, 0 threads. After restart+120 s: still `accepted`, 0 threads, task `failed: interrupted by restart` | **Yes.** Kill 12:10:13.541, restart 12:10:13.707, "NOT settled after 120s", run_status '' for both |
| R1 after | At kill: both `dispatching`, 0 threads. After restart: 2 completed, 1 thread each | **Yes.** Kill 12:09:19.448 < restart pid 1748914 at 19.574 < run requested 24.871/24.956; completed 28.8/30.9; total 2; pending emptied |
| R2 before | In-flight run cancelled by recovery; r2-a/b never launched | **Yes.** Hold thread `preparing` at kill → `cancelled` 12:10:23.755; r2-a/b 0 threads after 120 s; total 1 |
| R2 after | Kill rows running/dispatching/dispatching; all 3 completed; 1 thread each; total 3 | **Yes.** Kill 12:10:12.007 < restart 12:10:12.179 < requested 21.569/21.725/21.840. **C1:** in the capture DB, the hold thread `4099e5e7` was created at 12:10:11.992, *before* the kill, and that same thread carries the completed re-run. The same command id re-run reused it, with no second thread |
| R3 both | `pending` row survives the kill → 1 run completed, 1 thread | **Yes,** on both sides |
| R4 | Replays after restart → `duplicate`; before 0 runs, after exactly 1 | **Yes.** before-R1 1 dup and before-R2 2 dups, with thread totals unchanged at 0/1. after-R1 1 dup and after-R2 2 dups, with totals unchanged at 2/3 after +15 s |
| R5 | Unit-test only | **Accepted.** See below |

Real provider: assistant messages in the after-R2 DBs (capture and rerun) carry `creationSource: provider`, `claudeAgent` native items. These are real turns.

### Minor flags (non-blocking)
- The `threads_with_marker` column is 0 everywhere, even though user messages contain the marker. The `'%marker <n>%'` LIKE does not match the rendered/framed payload, so that column carries no evidence. `threads_for_delivery`, which is keyed by command/message id, plus the project totals are the real evidence and are sufficient.
- In after-R1 the in-flight row was still `dispatching` at kill, not `running`. SUMMARY states this openly, and R2 (capture and my rerun) covers the `running` case.
- The race is timing-dependent. The captures went 6/6, and my rerun hit it 1/1.

## R5 reasoning
- `drainPendingWebhooks` (ScheduledTaskService.ts:1647) re-checks on every loop iteration that `webhookPermits.get(taskId) === permit`. `forgetWebhookTask` deletes the permit on task delete (around line 1238), so a recreated task gets a new semaphore, and an old drain that is parked or finishing returns without touching the new incarnation's rows.
- The permit is held only around `runWebhookDelivery`, i.e. the launch command. Stalling it on a real server needs injection, so unit-only is reasonable.
- The test `a replaced task's old drain leaves the new incarnation's delivery alone` (webhook.test.ts:1184):
  - it recreates with the **same id** (`scheduled-task:hook`);
  - it gates the old launch;
  - it asserts the new delivery launches exactly once with the new command id and stays `accepted`, with no extra launch.
- `vp test run apps/server/src/scheduledTasks/ScheduledTaskService.webhook.test.ts` on the PR head gives **52/52 passed**.
- I did not mutation-test whether this test fails without the guard.

## Rerun (after-R2, downloaded driver; only change: `CAP=` points at audit2 helper copies; port 47920; fresh base-dir `/tmp/ev-23579/audit2/home-after-R2-try1`)
Hit rate: 1/1 (queued on the first attempt).
```
[12:15:39.892Z] SIGKILL server pid=1765533 (immediately after the last 202)
# pending at kill: hold running | r2-a dispatching | r2-b dispatching ; project thread total: 1
[12:15:40.007Z] started server pid=1766169 (restart)
[12:15:55.176Z] settled after 10s: all runs terminal
hold e6c57771 completed req 12:15:45.171Z   (thread created 12:15:39.881Z, before kill → reused, C1 ok)
r2-a daa9b67c completed req 12:15:45.236Z
r2-b 4220ef3c completed req 12:15:45.304Z
# project thread total: 3 ; task: succeeded runs=3
R4: replay r2-a, r2-b -> duplicate, duplicate ; total still 3 after +15 s
stopped server pid=1766169 ; PIDs started: 1765533 1766169 (all stopped); ports 47920-29 free
```
This matches the captured after-R2 row exactly.

Verdict: SUFFICIENT
