# Proof audit, pass 1 — Azure DevOps build policies and PR watches

## Findings first

1. **Blocking evidence gap:** prior R1–R4 stop at a provider plus a directly called evaluator/formatter. No real T3 server, project routing, registered watch, running reactor, committed wake message, or thread rendering was captured. Their in-memory error preservation is implemented by the replay driver itself. These results support the adapter boundary, not delivery. E1–E4 below require real-server recordings.
2. **Contradicted preview claim:** the existing chat hover card calls `pullRequestEnvironment.detail` while open ([caller](https://github.com/pingdotgg/t3code/blob/e13265440a44b51116f9c3427b50c3d0e2c3ca1e/apps/web/src/components/pullRequest/PullRequestLinkPreview.tsx#L52)). Only the guarded click calls `preview` (same file, line 60/74). The new provider preview method skips policy reads, but detail fails on them ([provider](https://github.com/pingdotgg/t3code/blob/e13265440a44b51116f9c3427b50c3d0e2c3ca1e/packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts#L321)). On a cold read with PR show allowed and policy denied, BEFORE can show the hover title/card; AFTER's hover detail fails and the component falls back to a URL tooltip when no fallback prop is supplied. `ChatMarkdown` supplies no fallback. This is a source-confirmed regression path, not a browser observation. The prior matrix's statement that hover uses `preview` is incorrect. E5 must capture this separately; a successful preview RPC cannot pass that row.
3. **Capture timing trap:** there is no public poll-now RPC or environment interval knob. The reactor sweeps every two minutes; without a fingerprint, a quiet complete read can defer the next detail read for ten minutes. Azure supplies neither `getChangeRequestChecks` nor `getChangeRequestWatchFingerprint`. Calling the standalone checks endpoint returns null and does not test this change. A direct call to `evaluatePullRequestWatch` or a separate `reactor.sweep` instance is a labelled harness, not the required running-server proof.

PR [#24568](https://git.cafe/pingdotgg/t3code/pull/24568), branch `takeover/pr-18759`, clean read-only checkout `/home/ubuntu/wt/pr-24568`. AFTER [e13265440a](https://github.com/pingdotgg/t3code/commit/e13265440a44b51116f9c3427b50c3d0e2c3ca1e), BEFORE / verified merge-base [3cc31ed27c](https://github.com/pingdotgg/t3code/commit/3cc31ed27c8956b36cee35a50509fb7c6869592b). Exactly five changed files, all under `packages/source-control-azure-devops/src/server/` (the brief's `apps/server/src/sourceControl/...` is not their location).

This is a specification derived from source, not a new capture. **Live Azure DevOps not exercised.** Prior JSON is synthetic. A real T3 server with executable az fixtures proves application integration, not actual Azure pagination, authentication, or CLI auto-detection against the Azure service.

## Reduced fixture matrix

Status judges the evidence already supplied, not whether the source appears correct. All production rows are affected by the diff. `recording` means an ordered runtime recording (timestamped az/RPC/thread-event JSONL or terminal recording), not an isolated still or one final evaluator result. Screenshots below are supplementary proof that the persisted message appears in the real thread. No new renderer, CSS, responsive or theme implementation is changed; one web viewport/theme suffices. Browser work belongs to the subsequent authorized capturer, not this pass.

| ID / named fixture | Data and drive through real app | BEFORE at merge-base | AFTER at head | Minimum capture / evidence status |
| --- | --- | --- | --- | --- |
| **E1 — failed build delivered to a thread** | Fresh top-level thread; link/watch Azure PR 42 through RPC recipe below. Head A, mergeable, no remarks. Start required Tests policy 12 `running`; after first completed server sweep change to `rejected`, then observe a duplicate rejected read. Can use page0=100 non-build rows and page100=build rows from E2 throughout, folding paging into this run. | No policy subprocess; detail checks empty; watch may record A but no check failure/pass wake message. | Policy 12 becomes `failure`, `required:true`; server reactor commits exactly one monitor wake with `Tests (#12)` and head A. Duplicate produces no second check message. Watch persists failedChecks. | **recording**, paired revisions, AFTER native scheduled read required at least once (not only restarts); BEFORE may skip quiet reads, record that distinction. 1280×900 dark, paired thread views after failed phase; AFTER text must be expanded/readable. **MISSING E2E**; prior R1 shows only decisions/text. |
| **E2 — required pass while advisory still runs** | Continue E1 or separate fresh thread. Page0 exactly 100 well-formed non-build policies, page100 required IDs12/13 (same displayName Tests) plus optional14, and disabled/deleted/notApplicable extras. Observe 12 rejected,13 running,14 running; then 12 approved while13 still running; then13 approved while14 still running; duplicate. Optional14 approval may follow in the same recording. | Checks empty, no policy pages, no check wakes. | No pass while required13 pending. Once both required jobs approve: exactly one `All 2 required checks passed on aaaaaaa` wake although optional14 pending. IDs distinguish equal names. Logs show skip0 then100 despite zero builds in first page. Optional settlement produces one owed `All 3 checks passed` wake; duplicate none. | **recording**, paired, same viewport/theme/thread as E1; paired pass view, ordered phases + persisted watch and message IDs. **MISSING E2E**; prior R2 sufficiently covers paging and evaluator gate only. Required-pending intermediate may be source/existing-watch-test backed; include it here cheaply to prove the negative gate. |
| **E3 — same-head rerun and new head** | Continue passed E2 watch. At A change required12 to `queued` (no pass), then `rejected` (fresh failure despite prior A failure), then `approved` (fresh required pass). Next PR show still advertises A, but latest iteration2 is C: observe C running then C approved, duplicate approved. Carry the same persisted watch; never overwrite watch fields in DB. | Latest iteration C still replaces stale trial A (unchanged behavior); no check failure/pass wakes at any phase. | Queued clears previous failure/pass gate; new failed/successful state can wake again at A. C resets remembered check state; pending C cannot pass; one pass on C then duplicate suppression. If optional14 was settled, count remains2 required; count isn't a head identifier. | **recording**, paired, same viewport/theme; thread/RPC record must show new message IDs and C in wake text/watch. One C-pass still from this recording suffices. **MISSING E2E**; prior R1/R4 support pure transitions/latest-head fallback. |
| **E4 — unreadable later page, recover, preview endpoint** | Use a separate watched thread/PR or isolated run to avoid contamination. Establish required12 failure at A and save persisted state/message IDs. Next page0=100 approved rows, page100=`{}`; require an actual server sweep to finish. Restore same rejected policy: no duplicate failure. Optionally use `running` → rejected to prove a real rerun can wake. In fresh uncached calls, switch policy command to nonzero denial, show remains valid; request `pullRequests.preview` and strict `pullRequests.detail`. | Detail ignores policy fixture entirely and succeeds checks[]; no check wakes. Preview fallback also succeeds (show/iterations/changes), since policy read doesn't exist. | Detail fails typed provider/read error; no partial success from passing page0; no fabricated pass or state advancement. Existing persisted failedChecks/passed/head/watermarks unchanged; same-failure recovery remains silent. Preview RPC returns projectId/repository plus seven summary fields and does not call policy/iterations; full detail really invokes denied policy and fails. | **recording**, paired, same viewport/theme; error warning and completed sweep trace + before/after thread projection/event stream. One thread still after error is supplemental, not proof of absence alone. **MISSING E2E** for error preservation/service dispatch; prior R3 covers malformed/truncated/denied provider reads and provider preview. |
| **E5 — cold chat hover under policy denial** | PR show readable; policy denied before *any* successful detail/cache read for this PR. Seed a normal chat message containing Azure PR URL using message.dispatch and a stub provider; hover in real ChatMarkdown. No fallback prop. Use a fresh environment/browser query scope or new PR number (stub clones fixture number/url), not warm E1 detail. Also exercise guarded plain click if confirmBeforeOpen applies; Ctrl/Meta/Shift/Alt uses native link behavior. | Hover can render repository/number/state/title/author/time from detail. Guarded click preview uses fallback detail and can open target. | **Actual source expectation:** hover detail calls policy and fails; only URL tooltip, no title card. Guarded click preview still succeeds without policy (if its confirmation path is enabled). Desired claim “link previews skip policies/stay readable” is therefore broader than implementation. | **recording**, paired real web hover at 1280×900 dark plus BEFORE card / AFTER tooltip stills. This is the sole extra interaction capture beyond runtime watch evidence. **CONTRADICTED / NOT CAPTURED**; prior R3 proves provider method only. A fix or explicit narrowed claim is needed before calling hover preserved. |
| **H1 — strict decoder, filters, naming, pagination/errors** | Labelled existing harness: reuse prior D1–D4/C1–C4/P1/P2. Approved/rejected/broken/queued/running/unknown + uppercase; build UUID case; nonbuild/disabled/deleted/notApplicable; optional settings/null/blank/trim; IDs and blocking flags; malformed rows including filtered ones; empty/99/exact100+empty/999/1000 raw counts; malformed/truncated later page, nonzero exits/missing az; iterations empty/error/location null. | No policy API requested; full detail checks[]. Latest-head/fallback behavior otherwise unchanged. | Status success/failure/pending; unknown stays pending; only applicable builds; stable policy-ID suffix; required=isBlocking; description/url null. Strict whole-page failure before filtering; raw count drives paging; stop on short page; 10 full pages fail, no skip1000 or partial checks. Fallback iterations don't swallow policy failures. | No new viewport/capture. **REUSE SUFFICIENT at labelled harness scope**: prior runtime/raw argv + focused assertions, all 58 bundle hashes verified in this pass. Generic production rate classification remains excluded; prior process helper lacks it. Do not repeat combinatorics in server recordings. |
| **H2 — empty/all-filtered and nullable/fallback shapes** | Existing prior R4/P2/W5; empty/all-filtered policies on same head after success and on unseen head at logical+16min; no/failed iteration; absent location. Existing generic watch tests cover comments/conflicts/read-failure ceiling and unchanged lifecycle limits. | Empty checks produce no pass or Azure no-checks timeout; head fallback as before. | Empty same-head result preserves remembered state; Azure has no checksAlwaysReport, so +16min cannot invent no-checks wake. Latest iteration determines head when available; fallback doesn't discard populated checks. | **recording, labelled harness, REUSE SUFFICIENT** for these unchanged branches. No real-time16min wait or extra browser/device matrix is required. |

E1–E3 can be a single paired server run with scenario-labelled phases. E4 is a second error/endpoint section of that run (or a second thread); E5 needs a cold scope to avoid held data masking the regression. This is five integration rows, not a full UI regression project.

## Changed units, callers, and branch coverage

| Changed unit | Complete changed behavior / branches | Consumers and matrix coverage |
| --- | --- | --- |
| `azureDevOpsPullRequestJson.ts` | Strict array/status/config schema; integer ID, boolean flags, optional deleted/settings/displayName; build type, enabled, deleted, notApplicable filters; case normalization; approved→success, rejected/broken→failure, all other→pending; name fallback/trim + policy ID; required flag; null description/url; rawCount of all rows. Extra keys tolerated, malformed row fails entire page even if nonbuild. | Only production decoder caller is PR CLI listBuildChecks. H1 carries full table; E1–E4 use representative branches. No render function here. |
| `AzureDevOpsPullRequestCli.ts` | New service method listBuildChecks(cwd,number); az repos pr policy list with detect,id,top100,skip; skip0…900; execute/decode/truncation/cap errors; short-page success; never return partial subset. Stable Effect.fn span. | Only production new-method caller is Azure PR provider getChangeRequest. E2 covers raw-before-filter paging; E4 read failure; H1 boundary variants. No render function. |
| `AzureDevOpsPullRequestProvider.ts` | Full detail now reads show→policies→iterations/changes, returns checks; failures map existing provider reason; preview new optional method returns seven fields from show only. Latest-iteration/null-location/error fallback retained. | PullRequestService.detailUncached copies checks into detail; previewUncached selects optional method; service registry rate-limit wrapper retains optional readers. Reactor detail with allowStale:false plus activity → evaluate → record→orchestrator sync→message.dispatch. RPC detail, review-guide reads, routed client actions also use full detail and inherit policy-read errors. Summary/list stay separate and unchanged; checks/fingerprint endpoints return null for Azure. E1–E5/H1/H2. |
| `AzureDevOpsPullRequestCli.test.ts` | Latest-head mock queue adjusted for extra policy call; added mixed second page and malformed/truncated second-page tests. | Existing focused evidence can be reused. The prior baseline port's extra existing-test failure was queue incompatibility, not a fourth product regression. |
| `AzureDevOpsPullRequestProvider.test.ts` | Two detail mocks add listBuildChecks; new preview-vs-detail policy-denial test. | Proves provider boundary, does not render ChatMarkdown or call PullRequestService/RPC/reactor. Prior baseline preview test fails for missing optional method; runtime fallback preview still succeeds. |

There is no changed component, prop, layout, CSS, width, theme, animation, platform branch, contract schema, or adapter capability. However changed data reaches existing render paths; “no new renderer” does **not** mean “no affected UI.” Inventory at this head:

- Web detail consumers are `usePullRequestDetail`→`PullRequestDetailPanel`/`PullRequestSummaryTab`; `PullRequestChecksPopover` lazy detail (from PR row, panel, ThreadDetailsPrRow); `ThreadDetailsPrRow`; `RightPanelTabs`; `useComposerMenuItems`; and `PullRequestLinkPreview`→ChatMarkdown. Detail checks become empty/pending/failed/passed/mixed counts; null URLs disable/hide details links; labels can truncate/wrap; failed checks expose existing fix actions. These combinations are the data in E1–E4/H1, not new styling work. Existing loading/error/stale/held-data branches require a cold scope for E5; its hover opening is delayed 350 ms and click resolving has a guard. Hover and guarded click read different endpoints.
- Mobile uses the shared detail query in its PR route, `PullRequestChecksRow` disclosure/count/status/fix action, merge/agent controls; desktop wraps web. Notification messages use each client's existing thread rendering. No native rendering or transport code changes. For this server-only claim, one real web thread witness plus shared RPC state is enough; do not require mobile simulators, theme/width Cartesian products, or every existing checks-card layout.
- Local/remote/relay/tunnel all receive the same persisted message/projection; no origin or URL transport change. Other provider adapters are unchanged. Agents and scheduled runs use the same service reads. SourceControlProvider.getChangeRequest in GitManager/SourceControlProviderRegistry is a **different interface** backed by AzureDevOpsSourceControlProvider, not this PR-provider method; do not invent additional policy callers from the shared name.

Relevant sources: [service detail/preview](https://github.com/pingdotgg/t3code/blob/e13265440a44b51116f9c3427b50c3d0e2c3ca1e/apps/server/src/pullRequest/PullRequestService.ts#L1708), [reactor](https://github.com/pingdotgg/t3code/blob/e13265440a44b51116f9c3427b50c3d0e2c3ca1e/apps/server/src/orchestration-v2/PullRequestWatchReactor.ts#L448), [persisted sync and dispatch](https://github.com/pingdotgg/t3code/blob/e13265440a44b51116f9c3427b50c3d0e2c3ca1e/apps/server/src/orchestration-v2/Orchestrator.ts#L2602), [watch MCP handler](https://github.com/pingdotgg/t3code/blob/e13265440a44b51116f9c3427b50c3d0e2c3ca1e/apps/server/src/mcp/toolkits/pullRequests/handlers.ts#L255).

## Concrete real-server seed and driver recipe

These are source-derived instructions for the capturer, **not an assertion that a driver has already run**. Keep source read-only. Each actual attempt gets a new timestamped directory under `/tmp/lead-24568-r1/proof-audit-1/`, with revision subdirectories, fixture manifest/hashes, source identity, command exits, RPC recording, az argv log, server log, trace, and captures. Do not overwrite older runs. Do not open `~/.t3/userdata` read-write, copy credentials, or use the current task's MCP connection to accidentally modify the live environment.

1. **Source and fresh state.** Run exact BEFORE and AFTER code, with independent homes and independent initialized project repos. AFTER is the installed checkout supplied in the brief. Use a lead-owned exact-baseline checkout/export for BEFORE without switching the supplied worktree. If exporting with git archive, set up dependencies so `@t3tools/*` package imports resolve to that revision's own source; blindly symlinking all workspace package node_modules can resolve to AFTER and invalidate the baseline. Record realpaths/source hashes of the three changed production modules loaded by each server. No product-source edits or poll-constant patch.
2. **Synthetic project git repository.** Inside each run create a small real git repo with a local empty initial commit (`git -c user.name=Proof -c user.email=proof@example.invalid commit --allow-empty -m fixture`) and `git remote add origin https://dev.azure.com/acme/platform/_git/web`. Set branch `feat/page`. Do not change the T3 source checkout's origin. Register this repo as a project with auto-pull disabled (`projects.mutate` project.update, autoPull:false); do not fetch the synthetic remote. The full watch identity parsed from the PR URL is host `dev.azure.com`, repository `acme/platform/_git/web`, number42; it is **not** `platform/web` or bare `web`. The provider's CLI repository selector can still be bare web after routing.
3. **az executable stub.** Put it first on server PATH only; never fall back to real az. Route by argv plus a scenario state file, not one global output queue (discovery, detail and activity run concurrently). Log timestamp, PID, cwd, full argv, scenario/generation, skip, chosen fixture and exit. Atomic fixture-state replacements; hold state until a completed server sweep is observed. Reuse prior show/iterations/changes/policy JSON. The old stub needs extension: it does not handle discovery/account/viewer/activity calls and is not directly sufficient for a real server. Required routes:

   | argv | output |
   | --- | --- |
   | `--version` | successful synthetic az version text |
   | `account show --query user.name -o tsv` (discovery) | bare `proof@example.invalid` |
   | `account show --query user ... --output json` (PR CLI getViewer) | `{"name":"proof@example.invalid","type":"user"}` (bare user object, not nested user and not a JSON string) |
   | `repos pr show ... --id N` | prior show fixture with pullRequestId/url adjusted to N, valid Azure location; mergeStatus succeeded, no reviewers/comments; trial A |
   | `repos pr policy list ... --id N --top 100 --skip K` | scenario pageK; denial mode exits1 with safe error; no API/network fallback |
   | `devops invoke ... --resource pullRequestIterations` | iteration1 A / latest iteration2 C as phase specifies |
   | `... --resource pullRequestIterationChanges` | `{ "changeEntries": [] }` |
   | `... --resource pullRequestThreads` | `{ "value": [] }`; no unrelated remarks |
   | background branch PR list if requested | valid show row list, not unrelated PRs; log separately |
   | anything else | fail loudly, retain raw log, extend only to a source-traced legitimate read |

   Canonical policy12: `{"status":"running","configuration":{"id":12,"isEnabled":true,"isBlocking":true,"type":{"id":"0609b952-1397-4640-95ec-e00a01b2c241"},"settings":{"displayName":"Tests"}}}`. Set statuses/IDs/flags as in table. Log policy state at every call; concurrent summary refreshes may add show/list invocations and must not consume progression.
4. **Stub provider for delivered turns.** Wakes call message.dispatch, which can launch a provider. Use a controlled local provider subprocess so synthetic PR watches cannot contact the developer's real account or execute work. One concrete source-backed option is the existing `codexCollabMockPeer.sh`, `.mjs`, `codexMultiAgentWire.json` under `apps/server/src/provider/testFixtures/`, copied into the run directory. Use a wrapper that handles `--version` and otherwise starts the copied peer. Give it a script with `rootThreadId` matching `responses.threadStart.thread.id` from the copied fixture, `notifications:[]`, and unique turnIds per incoming wake; `holdTurnOpen:false` lets turns complete. Extend the external stub to log incoming turn/start text and produce an inert acknowledgement if useful. This is a **stub provider**, not live Codex. No delegation is involved. Use `server.updateSettings` with `patch:{defaultAutoPull:false}` and `providerInstanceMutation:{operation:"upsert",instanceId:"proof_codex",instance:{driver:"codex",enabled:true,environment:[{name:"T3_CODEX_COLLAB_SCRIPT",value:"<run>/provider/script.json",sensitive:false}],config:{enabled:true,binaryPath:"<run>/provider/codex",homePath:"<run>/provider/home"}}}`. Then use that instance for the fixture thread. A provider startup error does not prove the agent consumed the message; distinguish committed message from provider receipt.
5. **Start real dev server.** Launch with tracked child PID/session and redirected run-specific output: `T3CODE_DEV_SEED=0 T3CODE_LOG_LEVEL=Info T3CODE_TRACE_MIN_LEVEL=Debug T3CODE_TRACE_TIMING_ENABLED=true PATH=<run>/stub:<existing PATH> vp run dev --home-dir <run>/<revision>/home --no-seed`. Leave VITE_HTTP_URL/VITE_WS_URL unset. Read actual server/web ports from dev-runner output. Startup log/pairing URL is private until redacted. With an explicit home, DB is `<home>/userdata/statev2.sqlite`, trace `<home>/userdata/logs/server.trace.ndjson`. Check the az log to confirm the server actually resolves the stub. No shared T3 database.
6. **Authenticate RPC and browser.** Bootstrap a browser session by POST `/api/auth/browser-session` with `{credential:<this isolated server's pairing credential>}` and retain its Set-Cookie privately, with matching Origin. Browser uses the complete startup pairing URL once (or its established reusable dev cookie); do not publish that URL/cookie. RPC driver connects to backend `/ws?orchestrationProtocol=2` with that cookie and matching Origin. Use Effect RpcClient/WsRpcGroup with JSON serialization (as client-runtime/rpc/session.ts does), or the equivalent Request frames `{_tag:"Request",id:"unique",tag:"method",payload:{...},headers:[]}`. Match Exit/Chunk by requestId, acknowledge streamed chunks (`Ack`) and answer Ping with Pong. Do not treat this socket as JSON-RPC2.0. Record payloads/results/events after removing credentials, not auth headers.
7. **Register project/thread/watch through RPC** (each commandId/messageId unique, actual UUIDs safest). Methods and payloads:

   ```json
   {"method":"projects.mutate","payload":{"type":"project.create","commandId":"<uuid>","projectId":"<project UUID>","title":"Azure watch proof","workspaceRoot":"<run>/project","defaultModelSelection":null}}
   {"method":"orchestration.dispatchCommand","payload":{"type":"thread.create","commandId":"<uuid>","threadId":"<thread UUID>","projectId":"<project UUID>","createdBy":"user","creationSource":"web","title":"Azure watch proof","modelSelection":{"instanceId":"proof_codex","model":"gpt-5.4"},"runtimeMode":"full-access","interactionMode":"default","branch":"feat/page","worktreePath":null}}
   {"method":"orchestration.subscribeThread","payload":{"threadId":"<thread UUID>","acceptBoundedSnapshot":true,"requestCompletionMarker":true}}
   {"method":"orchestration.dispatchCommand","payload":{"type":"thread.pull-request.watch","commandId":"<uuid>","threadId":"<thread UUID>","host":"dev.azure.com","repository":"acme/platform/_git/web","number":42,"watching":true,"link":{"url":"https://dev.azure.com/acme/platform/_git/web/pullrequest/42","source":"manual"}}}
   ```

   `manual` is the valid user/RPC source (`user` is not in ThreadPullRequestLinkSource). A single watch command links first if necessary and initializes the watch; do not forge ThreadPullRequestWatch fields. Keep thread top-level, unarchived and unsettled. If using actual MCP instead, initialize a scoped connection to **this isolated server** and call `watch_pull_request` with `{threadId:<id>,url:<Azure URL>}`; handler dispatches the same command with source agent. It requires a pull-requests capability and rejects subagents. Plain RPC above avoids minting provider-scoped MCP auth and is sufficient for watch registration.
8. **Drive the real reactor.** Its `start()` runs a sweep then repeats every 2 min. Registration just after initial startup may wait for the next sweep. Keep the fixture phase fixed until the real completed `PullRequestWatchReactor.sweep` trace and corresponding az calls/event stream establish it ran. Pending checks or unknown mergeability cause detail reads on each scheduled sweep; settled checks with complete remarks and no fingerprint use 10 min quiet reread. `allowStale:false` forbids held-data fallback but still uses the 60 s detail cache; changing fixture JSON plus calling detail immediately is not necessarily a fresh read. `pullRequests.invalidate` with `{reference:{projectId,host,repository,number}}` clears service caches but **does not trigger a reactor sweep** or bypass its read plan. To force quiet phases without altering source, stop only your tracked server, update phase, restart the same isolated home: a fresh running reactor has no lastRead map and performs a full startup sweep while preserving the durable watch. Label restart-triggered phases. At least one pending→failure or pending→pass transition must use the normal two-minute scheduler to prove polling itself; restarts alone prove startup sweeps/persistence. Do not unwatch/re-watch for rerun/head rows, since that changes the watch's semantics. No invented env knob, internal sync wake injection, or evaluator calls qualify.
9. **Observe durable result and browser.** Save thread subscription snapshot+events, then `orchestration.getThreadProjection({threadId})`. Inspect `thread.pullRequests[*].watch` and `messages` for IDs starting `message:pr-watch:`, text, `notification.source.kind:"monitor"`, summary, and associated turn items/runs. For failure/success record exact text and count; wait for persisted events/stream milestones, not fixed sleeps. Restart once and re-fetch to prove the same IDs and watch state survive. Provider stub incoming turn/start carrying the wake is additional receipt proof. In the web client select the seeded thread (route `/<environmentId>/<threadId>`); expand the monitor message if condensed so its build/head text is readable. Capture the requested paired thread stills at one 1280×900 dark viewport. Stills alone cannot prove deduplication or absence on error: save completed sweep/event and before/after state recording.
10. **Server logs are corroboration, not the wake payload.** Capture stdout/stderr and server.trace.ndjson. Policy read error produces `pull request watch check failed`; successful sweeps/`PullRequestWatchReactor.record`/orchestration dispatch spans correlate with provider reads and persistence. There is **no successful-wake text log in the reactor**; `woke` only updates an in-memory counter. Do not claim the warning log or “watch ended” summary proves delivery. The full payload is observable through the committed thread message and provider receipt, with the web witness. For no-message phases use the completed sweep span plus stream boundary/projection comparison. A log from a direct harness is not the real server log.
11. **E4 endpoint calls.** On a fresh cache epoch, send `pullRequests.preview` with `{projectId,host:"dev.azure.com",repository:"acme/platform/_git/web",number:42}` while policy denied; save returned summary fields and an az argv interval bounded around that RPC. Then `pullRequests.detail` with same plus `allowStale:false`; require a real denied policy call. In error-wake phase, capture both passing page0 (skip0) and later malformed page1 (skip100) from the actual reactor; don't pre-load detail through RPC and accidentally replace the relevant read. Preserve pre-error watch and message set; fewer than 8 consecutive generic errors so unchanged give-up wake doesn't confuse no-false-check-wake evidence.
12. **E5 hover fixture.** Use a fresh PR number/scope/environment with no saved successful detail. Through message.dispatch, seed text `Preview fixture: https://dev.azure.com/acme/platform/_git/web/pullrequest/42`, createdBy user, creationSource web, attachments[], dispatchMode `{type:"start_immediately"}`, unique command/messageId, into the stub-provider thread. Or use an already-rendered watch wake URL if its detail cache is independently cold. Hover the link and record card/tooltip plus RPC/az calls; no fabricated static HTML preview. The separate preview RPC is not hover proof. Guarded click is only applicable if the actual link has confirmBeforeOpen=true; do not claim it ran otherwise.
13. **Finish cleanly.** RPC watch false on fixture threads (same identity, watching:false) or isolated MCP unwatch; stop every server/driver/provider child you launched using captured PIDs. Retain/upload redacted evidence, fixtures and driver with ghdrop. Never kill by pattern or delete old logs. Polling observation must use the actual server's event/trace recording; don't run an ad-hoc background watch of the real PR. Check disk first/throughout; under 5 GB stop and report infra friction.

## Prior evidence accepted and rejected

Read the supplied [matrix](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9f3106d270736ca7/matrix.md), [transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/745e63c12d909b5e/runtime-transcript.md), [audit](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/194ce3777235bf1e/PASS2-final-audit.md), and [bundle](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/6594af388c1f5073/proof-bundle.tar.gz). Verified all 58 SHA256 index entries locally; inspected actual raw replay/argv logs, fixtures, stub, replay driver and relevant source. All 14 declared source hashes (seven per revision) also match their exact git objects. This pass did not rerun that harness or tests. Reuse labelled narrow results: BEFORE 73/AFTER 140 external assertions, real stub subprocess 96/141, prior focused suites and three isolated regression failures/passes. These counts do not represent server integration tests.

The old audit explicitly excludes persisted record/enqueue, scheduling, delivery, agent startup and full service-cache runtime. Its SUFFICIENT verdict applied to that narrower scope. The prior caller trace about hover is contradicted by exact-head source; the current verdict cannot inherit it.

Coverage completed: full five-file diff, changed runtime branches, current/baseline hover caller, provider/service/reactor/orchestrator/MCP/RPC paths, real-server seeding surfaces, runtime-state matrix, prior bundle integrity and narrow evidence. No worktree changes, ref moves, new server/browser/provider processes, captures, pushes or comments. Disk 29 GB free at inspection; no budget exhaustion.

FRICTION:
- tool | web reader could not retrieve the supplied public artifact URLs; curl retrieved all four successfully | use direct artifact download for public proof bundles.

VERDICT: INSUFFICIENT E1-E4 real-server persisted delivery/error proof missing; E5 hover-preview preservation contradicted and uncaptured
