# PR #22775 proof audit, pass 2

Product head `e8fbc9baea`, PR head `824b0ba8ec` (evidence scripts and knip only), before = merge-base `8acbefefc5`. Audited 2026-10-22 12:25–12:35 UTC on the VM where the capture ran. Every usage limit shown is a SYNTHETIC fixture.

## 1. SHAs and scripts

- `git diff --quiet 866e273265 e8fbc9baea -- <server/packages files changed vs merge-base>` exits 0, so the server files are byte-identical and R1–R5 (audited SUFFICIENT at 866e273265) carry over.
- `git diff --stat e8fbc9baea 824b0ba8ec`: only the 4 evidence scripts and 3 lines of `knip.jsonc`.
- Every environment line in the capture index names `8acbefefc5` (before) or `e8fbc9baea` (after). The burned-in captions in all 10 recordings name the same revisions.
- The capturer's scratch copies in `/tmp/ev-22775/capture/scripts/` differ from the committed scripts, but not in any way that changes the evidence:
  - `stills.mjs`: formatting and import style only.
  - `record.mjs`: formatting, plus an `arm` action in the committed copy. W0a needs that action and was recorded at 12:08, after the scratch copy's mtime (12:02), so the committed copy is the later superset that produced W0a.
  - `seed.ts`: one log-only field (`latestRunId` vs `runtime/latestRun`). Seeding logic is identical.
  - `seed-all.sh`: identical.

## 2. Captions vs recordings (ffmpeg frames at the .srt times)

| Row | Frame(s) | Caption | On screen | OK |
|---|---|---|---|---|
| W4 before | t=20 s | no banner, Resume thread absent | no banner, sidebar W4 plain (Done), composer only | yes |
| W4 after | t=15, 26 s | click Resume thread → no banner, continuation visible | "Continue where you left off.", Working, sidebar W4 Working | yes |
| W4q before | t=30 s | no banner, continuation not visible | queued "Run after recovery." is running (Working 9s), no Queued panel, no continuation | yes |
| W4q after | t=20, 32 s | banner + click → continuation visible | before the click: Queued panel (1) + banner (Resume at reset / Snooze). After: "Continue where you left off." running, Queued "Run after recovery." still held | yes |
| W6 before | t=60 s (12:21:03, after reset) | no banner | no banner, W1 plain | yes |
| W6 after | t=22 s (pre-reset), t=90 s (12:21:33, post-reset) | banner shown | banner both times. After the reset, Snooze until reset is greyed and Resume at reset is still enabled | yes |
| W0a before/after | t=21 s | "Cancel auto-resume" shown | Cancel auto-resume + Snooze in the banner on both revisions (control) | yes |
| W5 before | t=20 s (12:04:19) | no banner (post-reset only) | no continuation, W5 plain | yes |
| W5 after | t=20 s (12:04:20) | no banner (post-reset only) | "Continue where you left off.", Working 4m 15s, sidebar W5 Working | yes |

Stills spot-check:
- W2 1440 light side-by-side: AFTER has the banner with **Cancel auto-resume**, and the W1, W2, W4 and W5 sidebar rows show Limited. BEFORE has no banner and those rows show Done.
- W1 AFTER 1440 light (capturer): banner (Resets 12:20:40 PM), Resume at reset, Snooze until reset, sidebar Limited.

## 3. Rebuild of W1 AFTER from the committed scripts at `824b0ba8ec`

- **Seed.** `seed.ts --home /tmp/ev-22775/audit2/home/.t3 --row W1 --stage init|wake` gives these DB rows (read with `sqlite3 -readonly`):
  - `evidence:W1|1|failed|12:27:55.680Z|12:27:55.699Z`
  - `evidence:W1|2|completed|12:28:01.304Z|12:28:01.395Z`, with `userMessageId=evidence:notification:W1`, i.e. a notification wake that completed before the reset.
  - This has the same shape as the capturer's `db-evidence.txt`. The seed's shell log reports `latestRunId = ordinal:1`.
- **Dev server.** Started with `T3CODE_PORT_OFFSET=42067`, so web is on 47800 and the server on 47801. I ran it under setsid with process group 963164 and stopped it with `kill -TERM -963164`. The ports are free again.
- **Stills.** Taken with `stills.mjs <pairingUrl> /tmp/ev-22775/audit2/stills after W1`. The 1440 dark frame reproduces the capturer's W1 AFTER exactly: banner "Usage limit reached · Resets 10/22/2026, 12:57:55 PM · Resume at reset · Snooze until reset", and the sidebar shows W1 **Limited**.
  - The 1440 **light** frame came out blank: "No projects yet", empty main pane. It is the first navigation in a fresh browser context, and `stills.mjs` waits only for `networkidle` plus a fixed 2.5 s before the screenshot, so the shot caught the cold app load. This is a weakness in the script's robustness, not a product difference. The other 3 variants rendered normally.
  - Fix (optional): wait for `getByText(row title)` before the screenshot.

## 4. Focused tests (at `824b0ba8ec`)

- `vp test run packages/client-runtime/src/state/threadExecution.test.ts`: **11/11 passed**.
- `apps/server`: `vp test run src/orchestration-v2/runtimeLayer.test.ts`: **88/88 passed**.

## 5. Verdict

- **Server: SUFFICIENT.** R1–R5 carry over (byte-identical files, checked above), and the runtimeLayer tests pass. The W5 DB rows independently show one continuation 0.6 s after reset on AFTER and none on BEFORE.
- **Web: SUFFICIENT.** W0, W0a, W1, W2, W4, W4q, W6, W7 and W8 are shown before and after, and captions match the frames. W1 rebuilds from the committed scripts.
- **Other/shared (client-runtime, `packages/shared`): SUFFICIENT.** The threadExecution tests pass, and the client behaviour is exercised end-to-end by W1/W2/W4/W4q.
- **Mobile: N/A by path.** No `apps/mobile` files are touched, though the shared client-runtime change reaches mobile without a mobile-specific UI change.

### Gaps (judged non-blocking)

- **W3 (wake running → completes → banner returns): not needed.** The PR's claim concerns the state after a wake completes. W1 and W2 show that end state on AFTER and its absence on BEFORE. The running-wake intermediate state is the pre-existing "Working" path, and the server tests cover the transition. A stitched capture is possible with the seed's `wake-running`/`finish` stages if a maintainer wants it.
- **W5 pre-reset footage: not needed.** The pre-reset state of W5 is the W2 state (armed, Cancel auto-resume), which is captured. Post-reset, the outcome is in the recording (continuation, Working) and in the DB timestamps (run 3 requested 12:00:05.132Z vs reset 12:00:04.535Z; BEFORE has no run 3).
- **390 expanded composer/sidebar sheet: minor.** The ChatView change is gating logic, not layout. The 390 collapsed frames show the banner, and the 1440 frames show the Limited pill. Closing this would take one 390 capture with the sidebar sheet open for W1 before/after.
- **Script nit:** `stills.mjs` can screenshot before first render. This doesn't affect the existing evidence, because the capturer's frames are populated.
