Files
mokosh/.planning/phases/04-harden-clean-up-optional/04-08-PLAN.md
Mark 17e55ddbb9 docs(04-08): polish per iter-2 advisories — symbol mismatch + display-surface mode lock-in + cosmetic clarifications
iter-3 polish pass on Plan 04-08 per checker iter-2 verdict PASSED
(commit 9c334b7). Five cosmetic-level fixes; no thesis/scope changes;
BLOCKER fixes from iter-2 + WARNING fixes from iter-2 preserved verbatim.

Remediations:
- WARNING 1 (low-severity; displaySurface sub-gate scope ambiguity):
  HIGH-LATENCY catch path locked in; the under-specified
  `--check-display-surface-only` spike-script mode is dropped (would have
  required 5-10 LOC of executor improvisation for no meaningful latency
  win over the canonical spike re-run's assertA2 fast-fail at <30s).
- Advisory 1 (symbol-name mismatch): Tier-2 snippet's `collectDistFiles`
  replaced with the actual helper `listAllFilesRecursive(DIST_DIR)` from
  tests/background/no-test-hooks-in-prod-bundle.test.ts:152; also uses
  the existing `countOccurrencesInFile` for binary-extension-aware grep.
- Advisory 2 (SUMMARY-write practice for WARNING 1): explicit note added
  that 04-08-SUMMARY.md documents the WARNING 1 closure path (no Plan B
  fallback; explicit error-class identifier; observable via offscreen
  console capture).
- Advisory 3 (vitest math): 183 -> 184 clarified consistently across
  must_haves truth + Step 5 body + acceptance_criteria + verification +
  success_criteria (+1 from the new Tier-2 `test(...)` block).
- Advisory 4 (duration-N/A rationale): moved out of the PLAN body into
  the SUMMARY content list (Step 6); PLAN keeps only the load-bearing
  size gate + loop-attr behavioral assertion + spike-re-run empirical
  catch; 1.9 MB / ~400 kbps / ~38s decoded-timeline reasoning lands at
  SUMMARY-time.

Frontmatter:
- Added iter-2 entry to revision_history (iter-3 polish closure).
- Tag flipped planner-iter-2-revision -> planner-iter-3-revision.

Plan validates via gsd-sdk frontmatter.validate + verify.plan-structure:
- valid: true; 0 errors; 0 warnings; 2 tasks; all 4 task elements present.

Diff: +51/-22 lines (5640 chars net). Branch ready for plan-checker
iter-3 (cosmetic-only polish review expected to PASS without further
findings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:05:38 +02:00

1337 lines
107 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 04
slug: harden-clean-up-optional
plan: 08
type: auto
wave: 5.5
depends_on:
- 01
- 02
- 03
- 04
- 05
- 06
files_modified:
- tests/uat/fixtures/synthetic-display-source.webm
- src/test-hooks/offscreen-hooks.ts
- globals.d.ts
- manifest.json
- vite.config.ts
- vite.test.config.ts
- tests/uat/extension-page-harness.ts
- tests/uat/lib/harness-page-driver.ts
- tests/uat/harness.test.ts
- tests/uat/spike-a33-sw-persistence.ts
- tests/background/no-test-hooks-in-prod-bundle.test.ts
autonomous: true
requirements: []
tags:
- uat-harness
- a33
- methodology-reframe
- video-file-source
- htmlvideoelement-capturestream
- canvas-throttling-fix
- chrome-bug-653548
- charter-d-p4-01
- roadmap-sc-1-closes
- post-debug-session-2
- planner-iter-3-revision
user_setup: []
revision_history:
- iter: 1
plan_commit: 504d9dc
checker_commit: 051813e
verdict: ITERATE-NEEDED
blockers_addressed:
- "BLOCKER 1: Vite ?url -> dist-test/assets/<hash>.webm web_accessible_resources strategy now pre-decided (per checker Option B + user objective Option A) — explicit WAR entry for assets/*.webm in manifest.json + chrome.runtime.getURL() not needed because Vite's ?url import resolves to the chrome-extension://<id>/assets/<hash>.webm form already (verified by Plan 01-10 mokosh-mark.svg precedent for SVG; differs only by extension). The WAR entry covers production (zero *.webm assets — entry is inert) AND test bundle (offscreen reaches the asset via direct ?url import; the WAR entry just authorizes chrome-extension:// scheme access from the offscreen document context). No executor improvisation."
- "BLOCKER 2: installFakeDisplayMedia() remains SYNCHRONOUS at module load — preserves eager-install contract at lines 533-537. The HTMLVideoElement creation + DOM append + monkey-patch installation happen synchronously. The canplay wait + .play() + first captureStream call are deferred INTO the fakeGetDisplayMedia closure (lazy first-frame pattern). First getDisplayMedia call awaits readyState >= HAVE_FUTURE_DATA; subsequent calls reuse the now-ready video element. Zero race window with recorder.ts:46-48 top-level await chain."
warnings_addressed:
- "WARNING 1: headless autoplay fallback documented — if videoEl.play() rejects with NotAllowedError, the closure throws with explicit 'autoplay-blocked' error class so the spike surfaces the precise root cause (not a mysterious 0-frames)."
- "WARNING 2: patchDisplaySurface compatibility check — added explicit sub-verify gate that tests track.getSettings().displaySurface === 'monitor' on a fresh mintStream() call BEFORE the spike re-run."
- "WARNING 3: spike probe-value asserts surfaced as explicit grep gates in Task 2 <verify> block (POST-PRIME=0, PRE-KILL≥3, POST-KILL≥3)."
- "WARNING 4: ROADMAP.md edit pre-specified — exact sed-replace target documented; grep gate enforces post-edit invariant."
- "WARNING 5: synthetic-display-source filename leak gate added as Tier-2 invariant in tests/background/no-test-hooks-in-prod-bundle.test.ts (dist/ grep for 'synthetic-display-source' returns 0)."
cosmetics_addressed:
- "advisory 1: Task 3 mis-reference in commit message corrected (Task 1 + Task 2 only)."
- "advisory 2: recorder.ts:91 segments invariant gated by grep in Task 1 verify."
- "advisory 3: dual-fixture-location note added to Task 1 Step 1."
- iter: 2
plan_commit: 1f2eb2e
checker_commit: 9c334b7
verdict: PASSED
warnings_addressed:
- "WARNING 1 (iter-3 polish; checker iter-2 NEW WARNING): displaySurface sub-gate scope ambiguity resolved by locking in the HIGH-LATENCY catch path — the dedicated `--check-display-surface-only` spike-script mode is dropped (it was under-specified and would have required 5-10 LOC of executor improvisation for no meaningful latency win). The displaySurface invariant is now gated by the spike re-run's assertA2 fast-fail (Task 2 Step 1); the production gate at src/offscreen/recorder.ts:294 throws 'wrong-display-surface' within ~30s of spike start if the patchDisplaySurface helper is broken. SUMMARY documents the chosen closure path explicitly."
cosmetics_addressed:
- "advisory 1 (iter-3 polish; checker iter-2 NEW cosmetic-advisory 1): symbol-name mismatch in the WARNING 5 Tier-2 snippet — `collectDistFiles` replaced with the actual symbol `listAllFilesRecursive(DIST_DIR)` from tests/background/no-test-hooks-in-prod-bundle.test.ts:152; also uses the existing `countOccurrencesInFile` helper for binary-extension-aware grep (matches the Tier-1 entries' pattern verbatim)."
- "advisory 2 (iter-3 polish; checker iter-2 NEW cosmetic-advisory 2): WARNING 1 SUMMARY-write practice note added — the SUMMARY explicitly documents the chosen WARNING 1 closure path (no Plan B fallback; explicit error-class identifier on autoplay/codec reject; observable via offscreen console capture)."
- "advisory 3 (iter-3 polish; checker iter-2 NEW cosmetic-advisory 3): vitest math clarified throughout — must_haves truth + Step 5 body + acceptance_criteria + verification + success_criteria all consistently state '183 -> 184 GREEN; +1 from the new Tier-2 test(...) block in tests/background/no-test-hooks-in-prod-bundle.test.ts'."
- "advisory 4 (iter-3 polish; checker iter-2 NEW cosmetic-advisory 4): duration=N/A rationale moved out of the PLAN body into the SUMMARY content list (Step 6) — the PLAN keeps only the load-bearing >=1 MB size gate + loop-attr behavioral assertion + spike-re-run empirical catch; the 1.9 MB / ~400 kbps / ~38s decoded-timeline reasoning chain is documented at SUMMARY-time per checker iter-2 advisory 4."
must_haves:
truths:
- "installFakeDisplayMedia() at src/test-hooks/offscreen-hooks.ts is SYNCHRONOUS at module load — preserves the eager-install contract at lines 533-537 (the monkey-patch on navigator.mediaDevices.getDisplayMedia is in place BEFORE recorder.ts:46-48 top-level await resolves)"
- "fakeGetDisplayMedia closure performs the lazy first-frame wait — first invocation awaits videoEl.readyState >= 3 (HAVE_FUTURE_DATA) + .play(); subsequent invocations reuse the cached ready video element; mint a fresh MediaStream each call (matching the existing canvas.captureStream-per-call contract)"
- "installFakeDisplayMedia() at src/test-hooks/offscreen-hooks.ts mints its MediaStream from an HTMLVideoElement playing a bundled WebM (NOT from canvas.captureStream); the video element loops a >=30s VP9 source so the MediaRecorder sees real frame data across the full 5-min spike window"
- "tests/uat/fixtures/synthetic-display-source.webm exists as a CC0/internal-owned >=1MB VP9 WebM with duration >=30s; bundled into the test-config build via Vite ?url import (per Plan 01-10 mokosh-mark.svg precedent); chrome-extension:// scheme access from offscreen document is authorized via the explicit web_accessible_resources entry for assets/*.webm in manifest.json"
- "manifest.json web_accessible_resources block contains an explicit entry for assets/*.webm — pre-decided per planner iter-2 (no executor improvisation; the entry is inert in production where dist/ has zero *.webm assets, and load-bearing in test build where dist-test/assets/<hash>.webm needs chrome-extension:// authorization)"
- "tests/uat/spike-a33-sw-persistence.ts re-runs end-to-end and produces videoSize > 100_000 (canonical run with worker.close()) — empirically refutes the previous 8505-byte 0-frames methodology failure and confirms ROADMAP SC #1 architectural integrity per debug session-2 verdict; spike probe values shift from (POST-PRIME=0, PRE-KILL=3, POST-KILL=3 but 0 frames) -> (POST-PRIME=0, PRE-KILL>=3, POST-KILL>=3 with NON-ZERO frames; videoSize > 100_000)"
- "assertA33 / driveA33 / orchestrator wiring land per the original Plan 04-04 Pattern 4 verbatim (cs-injection-world precedent NOT applicable — A33 is host-side CDP-driven, not page-side rrweb-driven)"
- "stopServiceWorker(browser, extensionId) helper at tests/uat/lib/harness-page-driver.ts is reused verbatim from Plan 04-04 (already committed at 3726eee); no new helper symbol needed"
- "A33 env-gated by SKIP_LONG_UAT (default RUN for closure + alpha gate; SKIP_LONG_UAT=1 to skip per-commit iteration); 95s baseline preserved when skipped"
- "UAT harness count flips 33 -> 34 (A33 added); 34/34 GREEN when SKIP_LONG_UAT unset; vitest goes 183/183 -> 184/184 GREEN (Plan 04-08 adds exactly one new vitest `test(...)` block — the Tier-2 filename-leak gate in tests/background/no-test-hooks-in-prod-bundle.test.ts; cf. WARNING 5 remediation + iter-3 polish per checker iter-2 cosmetic-advisory 3)"
- "ROADMAP SC #1 (SW state persistence) GREEN — A33 empirical evidence that a 5-min idle + Puppeteer worker.close() + SAVE_ARCHIVE produces video/last_30sec.webm with size > 100 KB; ROADMAP.md row flipped from 'STATUS 2026-05-21: OPEN' to 'STATUS 2026-05-22: CLOSED via Plan 04-08'"
- "Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 entries (lockstep across tests/uat/harness.test.ts + tests/background/no-test-hooks-in-prod-bundle.test.ts) — Plan 04-08 introduces NO new __MOKOSH_UAT__-gated symbols on production bundle; the bundled WebM ships only in test build per Vite ?url + test-config gating"
- "Tier-2 production-bundle filename leak gate added: 'synthetic-display-source' string returns 0 hits across dist/ — codified in tests/background/no-test-hooks-in-prod-bundle.test.ts as a new sub-invariant (canary for future regressions that might accidentally inline test-hooks into the production chunk)"
- "Pre-checkpoint bundle gates 6/6 PASS unchanged from Plan 04-03 baseline (new Function=0 + eval=0 + Buffer.=1 pre-existing JSZip + window./document.=0 in SW + Tier-1 inventory=12 + en/ru parity preserved) — production source delta is zero (only src/test-hooks/* changes; tree-shaken in production per __MOKOSH_UAT__ gate)"
- "Architecture integrity preserved per debug session-2 verdict — src/offscreen/recorder.ts:91 `let segments: Blob[] = []` RAM-only architecture is canonically correct (verified by grep gate); no IndexedDB persistence work, no chrome.storage migration, no offscreen-document lifecycle changes"
- "patchDisplaySurface monkey-patch survives HTMLVideoElement.captureStream — verified by an explicit sub-test in Task 1 verify block (a synthetic mintStream() call + track.getSettings().displaySurface === 'monitor' assertion BEFORE the spike re-run)"
artifacts:
- path: "tests/uat/fixtures/synthetic-display-source.webm"
provides: "Bundled CC0/internal VP9 WebM source for HTMLVideoElement.captureStream — replaces canvas-captureStream invisible-source throttling. Size 1-3 MB; duration >=30s; loopable (autoplay+loop+muted). License: internal capture by this project's MediaRecorder pipeline (CC0-equivalent project-owned). Note: tests/fixtures/last_30sec.webm (the original Plan 01-07 regression fixture) remains in place; this is a SECOND copy at tests/uat/fixtures/ for the Vite ?url import to resolve cleanly without crossing the test-vs-fixtures dir boundary."
min_size_bytes: 1000000
- path: "src/test-hooks/offscreen-hooks.ts"
provides: "installFakeDisplayMedia() rewritten — SYNCHRONOUS module-load function; mints MediaStream from HTMLVideoElement.captureStream(30) on first getDisplayMedia call (lazy first-frame wait inside fakeGetDisplayMedia closure); displaySurface monkey-patch preserved; lifecycle (idempotent install + idempotent uninstall) preserved; A23 constraints capture preserved; all 6 existing bridge ops (install-fake-display-media + dispatch-ended + has-stream + get-display-surface + get-segment-count + get-last-getDisplayMedia-constraints) preserved with their original sync return contract"
contains: "videoEl.captureStream"
- path: "globals.d.ts"
provides: "Ambient declaration for `*.webm?url` Vite asset import (mirrors existing `*.svg?url` block at lines 34-37)"
contains: "webm?url"
- path: "manifest.json"
provides: "Explicit web_accessible_resources entry for assets/*.webm — pre-decided per planner iter-2 BLOCKER 1 remediation; production dist/ has zero *.webm assets so the entry is inert there; test dist-test/ has the hashed asset and the entry authorizes chrome-extension://<id>/assets/<hash>.webm access from the offscreen document context."
contains: "assets/*.webm"
- path: "tests/background/no-test-hooks-in-prod-bundle.test.ts"
provides: "Tier-2 invariant: 'synthetic-display-source' filename returns 0 hits across dist/ — canary for accidental test-hook inlining into production chunk. Tier-1 inventory at 12 entries UNCHANGED; this is a NEW Tier-2 entry alongside (does not count against the 12 lockstep)."
contains: "synthetic-display-source"
- path: "tests/uat/extension-page-harness.ts"
provides: "NO EDIT NEEDED per Plan 04-04 REVISION iter-2 Option B — assertA2 is reused as the canonical prime entrypoint; A33 logic lives entirely in driveA33 (host-side) + the inline page.evaluate dispatch. The Plan 04-04 SUMMARY post-debug amendment confirms this is the canonical pattern."
contains: "__mokoshHarness"
- path: "tests/uat/lib/harness-page-driver.ts"
provides: "driveA33(page, browser, extensionId, downloadsDir) host-side driver implementing the 5-min idle + stopServiceWorker + SAVE_ARCHIVE + JSZip video-size check (Plan 04-04 Pattern 4 verbatim; REVISION iter-2 Option B inline SAVE_ARCHIVE dispatch)"
contains: "driveA33"
- path: "tests/uat/harness.test.ts"
provides: "driveA33 import + driveA33Wrapped wrapped-driver const + drivers-array push entry with SKIP_LONG_UAT env-gate (per Plan 04-04 Wave 1 spec verbatim)"
contains: "driveA33Wrapped"
key_links:
- from: "src/test-hooks/offscreen-hooks.ts installFakeDisplayMedia (sync top-level + lazy closure)"
to: "tests/uat/fixtures/synthetic-display-source.webm via Vite ?url import"
via: "import syntheticDisplaySourceUrl from '../../tests/uat/fixtures/synthetic-display-source.webm?url' (resolves to chrome-extension://<id>/assets/<hash>.webm at test bundle time; chrome-extension:// scheme authorized via manifest.json web_accessible_resources assets/*.webm entry)"
pattern: "synthetic-display-source\\.webm\\?url"
- from: "manifest.json web_accessible_resources"
to: "dist-test/assets/<hash>.webm (test bundle only; production has zero *.webm assets)"
via: "explicit resources: ['assets/*.webm'] entry alongside the existing src/welcome/welcome.html entry"
pattern: "assets/\\*\\.webm"
- from: "src/test-hooks/offscreen-hooks.ts fakeGetDisplayMedia closure"
to: "videoEl readiness gate (readyState >= HAVE_FUTURE_DATA + .play()) + videoEl.captureStream(30) + displaySurface monkey-patch"
via: "first-call lazy path awaits canplay/error + .play(); subsequent calls fast-path the cached-ready videoEl; mintStream(); patchDisplaySurface(stream); return stream"
pattern: "readyState.*HAVE_FUTURE_DATA|videoEl\\.captureStream"
- from: "tests/uat/harness.test.ts driveA33Wrapped"
to: "tests/uat/lib/harness-page-driver.ts driveA33(page, browser, extensionId, downloadsDir)"
via: "(page) => driveA33(page, handles.browser, handles.extensionId, handles.downloadsDir)"
pattern: "handles\\.browser.*handles\\.extensionId"
- from: "tests/uat/lib/harness-page-driver.ts driveA33 video-size check"
to: "zip.file('video/last_30sec.webm') -> byteLength > 100_000"
via: "JSZip.loadAsync + entry.async('uint8array')"
pattern: "video/last_30sec\\.webm"
---
<objective>
**Methodology reframe for ROADMAP SC #1.** Per debug session-2 (`.planning/debug/sw-offscreen-persistence-investigation-session-2.md`, commit `4ea1bbb`), the Plan 04-04 SPIKE FAILED outcome (8505 bytes) is empirically REFUTED as an architectural failure. Three independent probes converged on the canonical NO answer:
1. **Segment-count probes** at POST-PRIME/PRE-KILL/POST-KILL: count=0 / count=3 / count=3 — segments accumulated correctly AND survived the SW kill. The `let segments: Blob[] = []` RAM-only architecture (src/offscreen/recorder.ts:91) is NOT broken.
2. **Step C variant** (SPIKE_SKIP_SW_KILL=1; no worker.close()): IDENTICAL 8505-byte failure — Puppeteer CDP `worker.close()` is NOT the cause.
3. **Direct Remux logs** (visible because SW respawn was skipped in Step C): `Segment ts=1..3: 0 frames, duration=0ms, trackInfo=320x180`; `Remux complete: 0 frames, total timeline=0ms, output=8505 bytes`.
**Root cause:** `installFakeDisplayMedia()` at `src/test-hooks/offscreen-hooks.ts:139-264` mints a `canvas.captureStream(30)` from a hidden -9999px-offset 320x180 canvas. Despite the existing `setInterval(drawFrame, 33ms)` belt-and-suspenders against RAF throttling, **headless-Chromium aggressively throttles MediaRecorder on invisible-canvas sources** (Chrome bug 653548; chromium auto-throttled-screen-capture design doc; sendrec.eu "Why Canvas Breaks Your Screen Recorder"). The MediaRecorder emits structurally-valid WebM with valid V_VP9 track metadata (320x180) but **zero VP9 frames per segment** across the 5-min idle window.
**Fix:** Replace the canvas.captureStream source with an `HTMLVideoElement` playing a bundled WebM (Option 2 from session-2 recommendations). Video-file-backed `videoEl.captureStream(30)` is not subject to invisible-canvas throttling — the source is a real media element with a real decoded frame timeline. The displaySurface monkey-patch (production code's monitor-only gate) is preserved verbatim; the lifecycle (idempotent install + uninstall) is preserved; the existing bridge ops (install-fake-display-media + dispatch-ended + has-stream + get-display-surface + get-segment-count + get-last-getDisplayMedia-constraints) are preserved.
**Iter-2 revision (per checker iter-1 findings at `.planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md`, commit `051813e`):**
- **BLOCKER 1 resolution — bundling strategy pre-decided.** Vite `?url` import emits the 1.9 MB WebM as `dist-test/assets/<hash>.webm` (extracted-asset path; not inlined because the size is three orders of magnitude above `assetsInlineLimit: 4096`). The `@crxjs/vite-plugin` auto-WAR behavior for extracted media assets in offscreen-document context is empirically untested in this codebase. **Pre-emptive remediation: add an explicit `web_accessible_resources` entry for `assets/*.webm`** in `manifest.json`. Production `dist/` has zero `*.webm` assets so the entry is inert there (no production attack surface); test `dist-test/` has the hashed asset and the entry authorizes the `chrome-extension://<id>/assets/<hash>.webm` URL form from the offscreen document context. No executor improvisation; the bundling strategy is locked-in BEFORE Task 1 begins.
- **BLOCKER 2 resolution — eager-install contract preserved.** `installFakeDisplayMedia()` remains **SYNCHRONOUS** at module load (matching the existing eager call at lines 533-537). The video element creation + `document.body.appendChild(videoEl)` + monkey-patch assignment to `navigator.mediaDevices.getDisplayMedia` all execute synchronously. The first-frame readiness await (canplay + `.play()`) is **deferred INTO the fakeGetDisplayMedia closure** (lazy first-frame pattern). The first time `getDisplayMedia()` is called (which happens inside `recorder.startRecording` AFTER recorder.ts:46-48 top-level await resolves and AFTER recorder.bootstrap), the closure awaits `videoEl.readyState >= HAVE_FUTURE_DATA` if not already ready, then calls `videoEl.captureStream(30)`, applies `patchDisplaySurface`, and returns the MediaStream. Subsequent `getDisplayMedia` calls fast-path the cached-ready video element. Zero race window with the eager-install contract.
**Revival path:** Once methodology produces real frames, re-run `tests/uat/spike-a33-sw-persistence.ts` — it MUST exit 0 with `videoSize > 100_000`. Then land the A33 harness assertion per the original Plan 04-04 Pattern 4 verbatim: `driveA33` host-side driver + orchestrator wiring + `SKIP_LONG_UAT` env-gate. The `stopServiceWorker(browser, extensionId)` helper from Plan 04-04 (committed at `3726eee`) is REUSED verbatim — no new test-only symbols introduced; FORBIDDEN_HOOK_STRINGS inventory unchanged at 12. A new Tier-2 sub-invariant ('synthetic-display-source' filename = 0 hits in dist/) lands alongside, codifying the production-bundle leak canary.
Purpose: Closes ROADMAP SC #1 ("After running the extension idle for >5 minutes, then exporting, the archive still contains a non-empty video buffer") within v1 by reframing the verification methodology (NOT the architecture). Honors the spike-FAILED-but-architecture-OK verdict from debug session-2; rejects the previously-proposed IndexedDB persistence plan-fix (which would not have closed SC #1 — the spike would STILL produce 8505 bytes after IDB lands because the failure is in the test's fake stream, not in segment persistence).
Output: 1 NEW bundled fixture (`tests/uat/fixtures/synthetic-display-source.webm`); rewrite of `installFakeDisplayMedia()` (canvas -> HTMLVideoElement; SYNC install + LAZY first-frame); 1 ambient module declaration for `*.webm?url`; 1 explicit `web_accessible_resources` entry for `assets/*.webm`; 1 NEW harness assertion (A33; harness count 33->34); driveA33 + orchestrator wiring; spike script re-runs PASSED; 1 NEW Tier-2 filename-leak gate. ROADMAP SC #1 flips OPEN -> GREEN.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-harden-clean-up-optional/04-CONTEXT.md
@.planning/phases/04-harden-clean-up-optional/04-PATTERNS.md
@.planning/phases/04-harden-clean-up-optional/04-04-PLAN.md
@.planning/phases/04-harden-clean-up-optional/04-04-SUMMARY.md
@.planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md
@.planning/debug/sw-offscreen-persistence-investigation-session-2.md
# Source files — locus of the methodology reframe
@src/test-hooks/offscreen-hooks.ts
@src/offscreen/recorder.ts
@tests/uat/extension-page-harness.ts
@tests/uat/lib/harness-page-driver.ts
@tests/uat/harness.test.ts
@tests/uat/lib/launch.ts
@tests/uat/spike-a33-sw-persistence.ts
@tests/background/no-test-hooks-in-prod-bundle.test.ts
# Vite ?url import precedent (Plan 01-10 mokosh-mark.svg pattern)
@src/welcome/welcome.ts
@globals.d.ts
@manifest.json
@vite.config.ts
@vite.test.config.ts
# Prior plan SUMMARYs — direct ancestors
@.planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md
@.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-04-SUMMARY.md
<interfaces>
<!-- Key shapes the executor consumes directly. Extracted from codebase + debug session-2 + checker iter-1 2026-05-22. -->
From src/test-hooks/offscreen-hooks.ts:139-264 (CURRENT installFakeDisplayMedia — the methodology failure locus, SYNCHRONOUS):
```typescript
// CURRENT (broken under 5-min headless idle per debug session-2 verdict):
// CRITICAL: this is SYNCHRONOUS — the monkey-patch on
// navigator.mediaDevices.getDisplayMedia is in place BEFORE the
// recorder.ts:46-48 await import('../test-hooks/offscreen-hooks')
// resolves. This is the eager-install contract.
export function installFakeDisplayMedia(): void { // SYNC — no Promise return
if (fakeInstalled) return;
fakeInstalled = true;
const canvas = document.createElement('canvas');
canvas.width = 320; canvas.height = 180;
// ... -9999px style + appendChild + drawFrame + setInterval ...
const mintStream = (): MediaStream => {
const stream = canvas.captureStream(30); // <- THE THROTTLING SOURCE
patchDisplaySurface(stream);
return stream;
};
const fakeGetDisplayMedia = async (
constraints?: DisplayMediaStreamOptions,
): Promise<MediaStream> => {
lastGetDisplayMediaConstraints = constraints ?? null;
return mintStream(); // SYNC body; async only for the contract shape
};
(navigator.mediaDevices as unknown as {
getDisplayMedia: typeof fakeGetDisplayMedia;
}).getDisplayMedia = fakeGetDisplayMedia; // SYNC monkey-patch — pre-resolve point
}
```
REPLACEMENT pattern (HTMLVideoElement.captureStream — Plan 04-08, SYNC install + LAZY first-frame closure):
```typescript
// Plan 04-08 methodology reframe — video-file-backed MediaStream.
// canvas.captureStream(30) on a hidden -9999px canvas is throttled to
// near-zero frames per second under headless Chromium 5-min idle
// (Chrome bug 653548; debug session-2 2026-05-22). An HTMLVideoElement
// playing a real WebM source is NOT subject to invisible-canvas
// throttling because the source has a real decoded frame timeline
// independent of canvas pixel mutations.
//
// CRITICAL CONTRACT (iter-2 BLOCKER 2 fix): installFakeDisplayMedia()
// remains SYNCHRONOUS. The video element creation + DOM append +
// monkey-patch on navigator.mediaDevices.getDisplayMedia execute
// synchronously at function call time. The canplay wait + .play() are
// deferred INTO the fakeGetDisplayMedia closure so the eager module-
// load call at lines 533-537 still installs the monkey-patch BEFORE
// recorder.ts:46-48 top-level await resolves. No race window with the
// recorder.startRecording call.
//
// References:
// - debug session-2 verdict at .planning/debug/sw-offscreen-persistence-investigation-session-2.md
// - HTMLVideoElement.captureStream:
// https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/captureStream
// - Chrome bug 653548 (auto-throttled canvas captureStream):
// https://bugs.chromium.org/p/chromium/issues/detail?id=653548
// - HTMLMediaElement.readyState (HAVE_FUTURE_DATA == 3):
// https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/readyState
// - iter-2 BLOCKER 2 remediation: .planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md
import syntheticDisplaySourceUrl from '../../tests/uat/fixtures/synthetic-display-source.webm?url';
// Module-level cell — populated by the SYNC install; consumed by the
// LAZY closure. Mutable because the closure may need to await readiness
// across multiple calls before the cached state stabilizes.
let fakeVideoEl: HTMLVideoElement | null = null;
let fakeVideoReadyPromise: Promise<void> | null = null;
export function installFakeDisplayMedia(): void { // SYNC — return type unchanged from current
if (fakeInstalled) return;
fakeInstalled = true;
// SYNC: create + append + style — these complete on the same tick.
const videoEl = document.createElement('video');
videoEl.src = syntheticDisplaySourceUrl;
videoEl.loop = true; // loop indefinitely so 5-min idle has source frames
videoEl.muted = true; // muted required for autoplay per Chrome policy
videoEl.autoplay = true; // ensure playback starts without user gesture
videoEl.playsInline = true;
videoEl.preload = 'auto'; // start fetching/decoding immediately
videoEl.style.position = 'fixed';
videoEl.style.top = '-9999px';
videoEl.style.left = '-9999px';
document.body.appendChild(videoEl);
fakeVideoEl = videoEl;
// SYNC: start the readiness Promise NOW so it has the maximum head
// start before the first getDisplayMedia call. The Promise resolves
// when readyState reaches HAVE_FUTURE_DATA (canplay event). If the
// video is already ready by the time fakeGetDisplayMedia is invoked,
// the closure's `await fakeVideoReadyPromise` resolves immediately.
//
// The .play() call is fire-and-forget here — we don't await it at
// install time (that would block the SYNC contract). We DO chain it
// off the canplay resolution inside the Promise body so the closure
// can rely on "ready AND playing" once the Promise resolves.
fakeVideoReadyPromise = new Promise<void>((resolve, reject) => {
const onCanPlay = (): void => {
videoEl.removeEventListener('canplay', onCanPlay);
videoEl.removeEventListener('error', onError);
// Chain .play() — required because autoplay attr is best-effort.
videoEl.play().then(() => resolve()).catch((err) => {
reject(new Error(
`synthetic-display-source.webm play() rejected: ${
err instanceof Error ? err.message : String(err)
} (autoplay-blocked or codec-unsupported in headless context)`
));
});
};
const onError = (): void => {
videoEl.removeEventListener('canplay', onCanPlay);
videoEl.removeEventListener('error', onError);
reject(new Error(
'synthetic-display-source.webm failed to load (media error; verify WAR entry + dist-test/assets/<hash>.webm reachable)'
));
};
videoEl.addEventListener('canplay', onCanPlay);
videoEl.addEventListener('error', onError);
});
// SYNC: monkey-patch — the eager-install contract requires this assignment
// to happen synchronously at module load (before recorder.ts:46-48 top-
// level await resolves). The closure body is async (matches the real
// getDisplayMedia signature) but the assignment itself is synchronous.
const fakeGetDisplayMedia = async (
constraints?: DisplayMediaStreamOptions,
): Promise<MediaStream> => {
// Plan 01-14 A23 capture (preserved verbatim).
lastGetDisplayMediaConstraints = constraints ?? null;
// LAZY FIRST-FRAME: await the readiness Promise. First call may
// block ~50-500ms while the video decodes its first frame. Cached
// resolution thereafter — subsequent calls observe the resolved
// Promise and proceed immediately.
if (fakeVideoReadyPromise !== null) {
await fakeVideoReadyPromise;
}
if (fakeVideoEl === null) {
// Defensive — module-level state was nulled by uninstall. Reject
// explicitly so callers see a clear error rather than NPE.
throw new Error('fake getDisplayMedia called after uninstall');
}
return mintStream();
};
(navigator.mediaDevices as unknown as {
getDisplayMedia: typeof fakeGetDisplayMedia;
}).getDisplayMedia = fakeGetDisplayMedia;
}
const mintStream = (): MediaStream => {
if (fakeVideoEl === null) {
throw new Error('mintStream called before fakeVideoEl initialized');
}
// captureStream(30) — request 30 fps; the actual cadence is driven
// by the source video's frame timeline.
const stream = (fakeVideoEl as HTMLVideoElement & {
captureStream: (fps?: number) => MediaStream;
}).captureStream(30);
patchDisplaySurface(stream); // preserve existing monkey-patch
return stream;
};
```
From src/test-hooks/offscreen-hooks.ts:528-537 (CURRENT eager-install at module load — UNCHANGED in iter-2):
```typescript
// PROTOTYPE: install the fake getDisplayMedia eagerly so production
// recorder.startRecording will use the synthetic stream on its first
// call — no chicken-and-egg with the bridge install op. Wrap in a
// try so any DOM-not-ready edge case does not block module init.
try {
installFakeDisplayMedia(); // STILL SYNC — eager-install contract preserved
} catch (e) {
console.warn("[offscreen-hooks] eager installFakeDisplayMedia failed:", e);
}
```
From src/offscreen/recorder.ts:46-48 (the top-level await resume point — eager-install contract dependent):
```typescript
let testHooks: typeof import('../test-hooks/offscreen-hooks') | null = null;
if (__MOKOSH_UAT__) {
testHooks = await import('../test-hooks/offscreen-hooks');
}
// By the time the await resolves, offscreen-hooks's module-load eager
// call has already executed installFakeDisplayMedia() SYNCHRONOUSLY,
// which means navigator.mediaDevices.getDisplayMedia is ALREADY patched
// to the fakeGetDisplayMedia closure. recorder.startRecording's
// `await navigator.mediaDevices.getDisplayMedia(...)` resolves with the
// synthetic stream — the lazy first-frame wait happens inside the closure,
// not at install time.
```
From src/welcome/welcome.ts:46 (Plan 01-10 Vite ?url precedent — direct analog for SVG):
```typescript
import markUrl from '../shared/brand/mokosh-mark.svg?url';
```
From globals.d.ts:34-37 (Plan 01-10 ambient module decl — direct analog to copy):
```typescript
declare module '*.svg?url' {
const url: string;
export default url;
}
// Plan 04-08 adds the parallel block:
declare module '*.webm?url' {
const url: string;
export default url;
}
```
From manifest.json:20-25 (CURRENT web_accessible_resources block — Plan 04-08 appends one entry):
```json
"web_accessible_resources": [
{
"resources": ["src/welcome/welcome.html"],
"matches": ["<all_urls>"]
}
],
```
Plan 04-08 iter-2 BLOCKER 1 fix — append a second resources entry:
```json
"web_accessible_resources": [
{
"resources": ["src/welcome/welcome.html"],
"matches": ["<all_urls>"]
},
{
"resources": ["assets/*.webm"],
"matches": ["<all_urls>"]
}
],
```
From src/test-hooks/offscreen-hooks.ts:271-291 (uninstallFakeDisplayMedia — preserve idempotent teardown; adapted for videoEl):
```typescript
export function uninstallFakeDisplayMedia(): void {
if (!fakeInstalled) return;
fakeInstalled = false;
if (fakeVideoEl !== null) {
try { fakeVideoEl.pause(); } catch { /* ignore */ }
fakeVideoEl.remove();
fakeVideoEl = null;
}
fakeVideoReadyPromise = null; // drop the Promise reference
// We deliberately do NOT restore the original getDisplayMedia — the
// offscreen document is throwaway and gets a fresh navigator on the
// next createDocument() anyway.
}
```
From tests/uat/lib/harness-page-driver.ts:68-80 (stopServiceWorker helper — REUSED verbatim from Plan 04-04):
```typescript
// Already committed at 3726eee; Plan 04-08 reuses without modification.
export async function stopServiceWorker(browser: Browser, extensionId: string): Promise<void> {
const host = `chrome-extension://${extensionId}`;
const target = await browser.waitForTarget(
(t) => t.type() === 'service_worker' && t.url().startsWith(host),
);
const worker = await target.worker();
if (worker !== null) {
await worker.close();
}
}
```
From Plan 04-04 PLAN.md Pattern 4 (driveA33 host-side body — REVIVED VERBATIM under valid methodology):
```typescript
const A33_IDLE_WAIT_MS = 5 * 60 * 1000;
const A33_NEW_SW_BOOT_MS = 500;
const A33_OVERALL_TIMEOUT_MS = A33_IDLE_WAIT_MS + 60_000;
const A33_SAVE_ARCHIVE_TIMEOUT_MS = 15_000;
const A33_DOWNLOAD_SETTLE_MS = 5_000;
const A33_VIDEO_SIZE_FLOOR_BYTES = 100_000;
export async function driveA33(
page: Page,
browser: Browser,
extensionId: string,
downloadsDir: string,
): Promise<AssertionRecord> {
const r: AssertionRecord = { name: 'A33', passed: false, checks: [], diagnostics: [] };
// Step 1: prime via __mokoshHarness.assertA2 (canonical fresh-recording bootstrap;
// Plan 04-04 REVISION iter-2 Option B; verified via existing harness surface).
await page.evaluate(async () => {
const harness = (window as unknown as {
__mokoshHarness: { assertA2: () => Promise<{ passed: boolean; error?: string }> };
}).__mokoshHarness;
const a2 = await harness.assertA2();
if (!a2.passed) {
throw new Error(`assertA2 priming failed: ${a2.error ?? '(no error)'}`);
}
});
r.diagnostics.push('Step 1 OK: assertA2 prime -> REC');
// Step 2: 5-min wall-clock idle.
r.diagnostics.push(`Step 2: waiting ${A33_IDLE_WAIT_MS}ms for SW idle window`);
await new Promise((res) => setTimeout(res, A33_IDLE_WAIT_MS));
// Step 3: force SW termination via CDP.
await stopServiceWorker(browser, extensionId);
r.diagnostics.push('Step 3 OK: SW terminated via worker.close()');
// Step 4: brief settle for SW teardown.
await new Promise((res) => setTimeout(res, A33_NEW_SW_BOOT_MS));
// Step 5: SAVE_ARCHIVE inline dispatch from harness-page realm
// (Plan 04-04 REVISION iter-2 Option B; wakes SW event-driven).
const saveResult = await page.evaluate(
(timeoutMs: number) =>
new Promise<{ success: boolean; error?: string }>((resolve) => {
const timer = setTimeout(() => {
resolve({ success: false, error: `SAVE_ARCHIVE timed out after ${timeoutMs}ms` });
}, timeoutMs);
chrome.runtime.sendMessage({ type: 'SAVE_ARCHIVE' }, (response: unknown) => {
clearTimeout(timer);
if (chrome.runtime.lastError !== undefined) {
resolve({ success: false, error: String(chrome.runtime.lastError.message) });
return;
}
resolve(response as { success: boolean; error?: string });
});
}),
A33_SAVE_ARCHIVE_TIMEOUT_MS,
);
r.checks.push({
name: 'A33.1: SAVE_ARCHIVE ack success after 5-min idle + SW kill',
expected: true,
actual: saveResult.success,
passed: saveResult.success === true,
});
// Step 6: settle for chrome.downloads to finish.
await new Promise((res) => setTimeout(res, A33_DOWNLOAD_SETTLE_MS));
// Step 7: locate zip + measure video entry.
const zipPath = findLatestZip(downloadsDir);
if (zipPath === null) {
r.checks.push({ name: 'A33.0: zip present', expected: '>=1 zip', actual: 'none', passed: false });
r.passed = false;
return r;
}
const zip = await JSZip.loadAsync(readFileSync(zipPath));
const videoEntry = zip.file('video/last_30sec.webm');
const videoSize = videoEntry !== null
? (await videoEntry.async('uint8array')).byteLength
: 0;
r.checks.push({
name: 'A33.2: video/last_30sec.webm size > 0 (buffer survived SW eviction)',
expected: '>0',
actual: String(videoSize),
passed: videoSize > 0,
});
r.checks.push({
name: 'A33.3: video size > 100 KB (sanity floor; real archives 1-3 MB)',
expected: `>${A33_VIDEO_SIZE_FLOOR_BYTES}`,
actual: String(videoSize),
passed: videoSize > A33_VIDEO_SIZE_FLOOR_BYTES,
});
r.passed = r.checks.every((c) => c.passed);
return r;
}
```
From tests/uat/harness.test.ts:486 (orchestrator drivers-array — append site for A33):
```typescript
// Plan 04-08 — driveA33 lands HERE, after the existing A32 entry at line 486.
{
name: 'A33',
drive: process.env.SKIP_LONG_UAT === '1'
? async (): Promise<AssertionRecord> => ({
name: 'A33',
passed: true,
checks: [],
diagnostics: ['A33 SKIPPED (SKIP_LONG_UAT=1; unset to run 5-min idle test)'],
})
: driveA33Wrapped,
},
```
From tests/fixtures/last_30sec.webm (existing internal project artifact — REMAINS IN PLACE per advisory 3; the new tests/uat/fixtures/synthetic-display-source.webm is a SECOND copy at a different location):
- size: 1.9 MB (1888636 bytes verified via ls)
- codec: VP9
- dimensions: 1142x1044
- license: internal project capture (CC0-equivalent project-owned)
- A direct copy to `tests/uat/fixtures/synthetic-display-source.webm` is the baseline (zero-cost; already proven to play in Chrome via Plan 01-07).
From tests/background/no-test-hooks-in-prod-bundle.test.ts (CURRENT Tier-1 inventory at 12 entries; iter-2 adds 1 NEW Tier-2 sub-invariant):
```typescript
// Tier-1: 12-entry FORBIDDEN_HOOK_STRINGS lockstep with tests/uat/harness.test.ts.
// Plan 04-08 leaves this UNCHANGED.
//
// Tier-2 NEW (Plan 04-08 iter-2 — WARNING 5 remediation): single canary
// gate that 'synthetic-display-source' does not leak into dist/. Test-only
// fixture filename — the gated import in offscreen-hooks.ts is tree-shaken
// in production per __MOKOSH_UAT__; this gate catches any future regression
// that accidentally inlines test-hooks into the production chunk.
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Bundle WebM fixture + pre-emptive WAR entry + replace canvas.captureStream with HTMLVideoElement.captureStream (SYNC install + LAZY first-frame)</name>
<files>tests/uat/fixtures/synthetic-display-source.webm, src/test-hooks/offscreen-hooks.ts, globals.d.ts, manifest.json, vite.config.ts, vite.test.config.ts, tests/background/no-test-hooks-in-prod-bundle.test.ts</files>
<read_first>src/test-hooks/offscreen-hooks.ts (full file — installFakeDisplayMedia at lines 139-264 + uninstallFakeDisplayMedia at 271-291 + eager-install at 528-537), src/offscreen/recorder.ts:40-60 (the top-level await import resume point that depends on eager-install contract), src/welcome/welcome.ts:30-50 (Vite ?url import precedent), globals.d.ts (full — ambient module declaration pattern), manifest.json (full — web_accessible_resources block at line 20), vite.config.ts (full — rollupOptions.input + nodePolyfills config), vite.test.config.ts (full — test-build entry mirror), tests/background/no-test-hooks-in-prod-bundle.test.ts (full — Tier-1 invariant + the Tier-2 add site), tests/fixtures/last_30sec.webm (verify file exists + VP9 codec + ~1.9 MB), tests/uat/spike-a33-sw-persistence.ts (full — to understand the exact prime + SAVE flow the new methodology will be tested against), .planning/debug/sw-offscreen-persistence-investigation-session-2.md (Resolution section — fix recommendations), .planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md (BLOCKER 1 + BLOCKER 2 + WARNING 1/2/5 — the specific remediation guidance)</read_first>
<action>
**Step 1 — Bundle the WebM fixture (advisory 3 dual-location note).**
- Create directory: `mkdir -p tests/uat/fixtures/`.
- Copy `tests/fixtures/last_30sec.webm` to `tests/uat/fixtures/synthetic-display-source.webm`. The existing artifact is a 1.9 MB VP9 WebM internally captured by this project's MediaRecorder pipeline — CC0-equivalent project-owned per the "internal capture" provenance documented in `.planning/STATE.md` Phase 1 Closure Notes. Verify codec via `ffprobe -v error -show_entries stream=codec_name,width,height -of default=nw=1 tests/uat/fixtures/synthetic-display-source.webm` reports `codec_name=vp9`.
- The fixture must be >=1 MB (size gate verified by `wc -c` >= 1_000_000 in the Task 1 verify block) AND must loop cleanly under `videoEl.loop = true` across the 5-min spike window (loop behavior verified empirically by the spike re-run in Task 2 Step 1 — if the fixture's decoded timeline is too short OR the loop seam produces a decoder hiccup, the spike's `videoSize > 100_000` floor would catch it). The 1.9 MB capture from `tests/fixtures/last_30sec.webm` is the COPY baseline; if the executor finds the copy fails the spike re-run for any duration/loop-seam reason, the OPTIONAL fallback is to regenerate via `ffmpeg -f lavfi -i testsrc=duration=35:size=320x180:rate=30 -c:v libvpx-vp9 -b:v 400k tests/uat/fixtures/synthetic-display-source.webm`. The full reasoning chain (ffprobe duration=N/A is acceptable because the 1.9 MB / ~400 kbps VP9 yields ~38s of decoded timeline; the `videoEl.loop = true` attr handles indefinite playback) is documented in 04-08-SUMMARY.md's "Bundled WebM fixture decision" section so future maintainers understand the rationale at SUMMARY-time (cf. iter-3 polish per checker iter-2 cosmetic-advisory 4).
- **advisory 3 note**: `tests/fixtures/last_30sec.webm` remains in place (Plan 01-07 regression fixture; not affected). The new path at `tests/uat/fixtures/synthetic-display-source.webm` is a SECOND copy under the UAT subtree — this is intentional because the Vite ?url import in `src/test-hooks/offscreen-hooks.ts` resolves cleanly from `../../tests/uat/fixtures/...` without crossing a non-bundled subtree (the `tests/fixtures/` directory is excluded from the test bundle's input set; the `tests/uat/fixtures/` directory is implicitly included as a transitive import of the offscreen-hooks module).
- Decision (per `feedback-no-unilateral-scope-reduction.md`): the executor's default is the COPY path (zero-cost, project-owned, already proven to play in Chrome per Plan 01-07). Regeneration is an OPTIONAL upgrade if the baseline fails to loop cleanly in headless captureStream.
**Step 2 — Add ambient module declaration for `*.webm?url`.**
- Edit `globals.d.ts`. After the existing `declare module '*.svg?url' { ... }` block at lines 34-37, append:
```typescript
// Plan 04-08 — Vite `?url` import for bundled test-only WebM fixture
// (tests/uat/fixtures/synthetic-display-source.webm). Mirrors the
// Plan 01-10 mokosh-mark.svg precedent; only gated test builds resolve
// the import (offscreen-hooks.ts is tree-shaken in production per
// `__MOKOSH_UAT__`).
declare module '*.webm?url' {
const url: string;
export default url;
}
```
**Step 3 — Rewrite `installFakeDisplayMedia()` to use HTMLVideoElement.captureStream — SYNCHRONOUS install + LAZY first-frame closure (iter-2 BLOCKER 2 fix).**
**Critical contract:** `installFakeDisplayMedia()` MUST remain SYNCHRONOUS (no `async`/`await` in the function declaration). The eager-install at `src/test-hooks/offscreen-hooks.ts:528-537` calls it synchronously at module load; the `await import('../test-hooks/offscreen-hooks')` chain at `src/offscreen/recorder.ts:46-48` depends on the monkey-patch being in place BEFORE the await resolves. Converting `installFakeDisplayMedia()` to async would introduce a race window where `recorder.startRecording` could call the REAL `navigator.mediaDevices.getDisplayMedia` (the picker would hang in headless mode). DO NOT convert to async. DO NOT change the function's return type.
**Sub-step 3a — Add top-of-module import (after existing imports at line 49):**
```typescript
// Plan 04-08 — bundled WebM source for HTMLVideoElement-backed MediaStream.
// Replaces the prior canvas.captureStream(30) source which was throttled
// to 0 frames/segment under 5-min headless Chromium idle (Chrome bug
// 653548; debug session-2 verdict 2026-05-22). The video file's real
// decoded frame timeline is not subject to invisible-canvas throttling.
//
// The `?url` import resolves to a Vite-emitted hashed asset URL only in
// test builds (offscreen-hooks.ts is gated by `__MOKOSH_UAT__` in
// src/offscreen/recorder.ts and tree-shaken in production). The asset
// emission path is dist-test/assets/<hash>.webm; the chrome-extension://
// scheme access from the offscreen document is authorized by the explicit
// web_accessible_resources entry for assets/*.webm in manifest.json
// (iter-2 BLOCKER 1 remediation; pre-decided to avoid executor improvisation).
//
// References:
// - .planning/debug/sw-offscreen-persistence-investigation-session-2.md
// - .planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md
// - https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/captureStream
// - Chrome bug 653548: https://bugs.chromium.org/p/chromium/issues/detail?id=653548
// - HTMLMediaElement.readyState: https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/readyState
import syntheticDisplaySourceUrl from '../../tests/uat/fixtures/synthetic-display-source.webm?url';
```
**Sub-step 3b — Update module-level state declarations (lines ~97-100). Replace canvas-specific cells with video-specific cells:**
```typescript
// REPLACE:
// let fakeCanvas: HTMLCanvasElement | null = null;
// let fakeAnimationHandle: number | null = null;
// let fakeDrawInterval: ReturnType<typeof setInterval> | null = null;
//
// WITH:
let fakeVideoEl: HTMLVideoElement | null = null;
// Plan 04-08 BLOCKER 2 (iter-2): lazy first-frame Promise. SYNC install
// kicks off the readiness chain (canplay + .play()); the Promise resolves
// when the video element is ready to captureStream. The fakeGetDisplayMedia
// closure awaits this Promise on each call — first call may block ~50-500ms;
// subsequent calls observe the already-resolved Promise and proceed
// immediately. Nulled by uninstallFakeDisplayMedia.
let fakeVideoReadyPromise: Promise<void> | null = null;
```
**Sub-step 3c — Replace the `installFakeDisplayMedia` body (lines 139-264). Keep the function signature SYNCHRONOUS — `export function installFakeDisplayMedia(): void`. Preserve:**
- Idempotency guard (`if (fakeInstalled) return;`).
- `fakeInstalled = true` early assignment (matches existing contract).
- The `lastGetDisplayMediaConstraints` capture (A23 contract).
- The `patchDisplaySurface(stream)` helper (defined inside the function as in current source; survives the rewrite).
- The `mintStream()` factory pattern (fresh MediaStream per `getDisplayMedia` call).
- The fakeGetDisplayMedia closure shape (`async` callable; cast-through-unknown for navigator.mediaDevices.getDisplayMedia assignment).
- The eager-install try/catch at lines 528-537 unchanged.
**New body (SYNC install + LAZY first-frame, code-snippet form per checker iter-1 BLOCKER 2 Option A):**
```typescript
export function installFakeDisplayMedia(): void {
if (fakeInstalled) {
return;
}
fakeInstalled = true;
// SYNC PHASE — execute synchronously so the monkey-patch on
// navigator.mediaDevices.getDisplayMedia is in place BEFORE
// recorder.ts:46-48 top-level await resolves. Per debug session-2 +
// iter-2 BLOCKER 2 — must NOT block on canplay or .play() here.
// Build a hidden HTMLVideoElement playing the bundled WebM source.
// The element stays off-screen (-9999px) so it has zero visual side
// effect; loop=true ensures source frames never run out across the
// 5-min spike window; muted=true is required for autoplay per the
// Chrome autoplay policy (audio policy doesn't gate muted video).
const videoEl = document.createElement('video');
videoEl.src = syntheticDisplaySourceUrl;
videoEl.loop = true;
videoEl.muted = true;
videoEl.autoplay = true;
videoEl.playsInline = true;
videoEl.preload = 'auto';
videoEl.style.position = 'fixed';
videoEl.style.top = '-9999px';
videoEl.style.left = '-9999px';
document.body.appendChild(videoEl);
fakeVideoEl = videoEl;
// Start the readiness Promise NOW so it has the maximum head start
// before the first getDisplayMedia call. The Promise resolves when
// canplay fires AND .play() resolves; rejects on error or play() reject
// (autoplay blocked / codec unsupported). Stored in the module-level
// cell so the closure can await it.
//
// WARNING 1 remediation (iter-2): autoplay reject path produces an
// explicit error class identifier ('autoplay-blocked or codec-unsupported
// in headless context') so a misconfigured fixture / hostile autoplay
// policy surfaces as a clear failure signal — NOT a mysterious 0-frames
// downstream. The spike's gating check on `videoSize > 100_000` will
// fail with this error visible in the offscreen console capture (or in
// the harness diagnostics), making the root cause unambiguous.
//
// WARNING 1 SUMMARY-write practice (iter-3 polish): the executor writing
// 04-08-SUMMARY.md MUST document the chosen failure path explicitly —
// 'no Plan B fallback; explicit error-class identifier on autoplay/codec
// reject is the chosen WARNING 1 closure path; downstream observability
// via the offscreen console capture is the diagnostic surface.' The
// error class is observable in the spike re-run's offscreen-console log
// capture, so the SUMMARY's evidence section should cite this path
// (cf. cosmetic-advisory 2 from checker iter-2).
fakeVideoReadyPromise = new Promise<void>((resolve, reject) => {
const onCanPlay = (): void => {
videoEl.removeEventListener('canplay', onCanPlay);
videoEl.removeEventListener('error', onError);
// Chain .play() — required because autoplay attr is best-effort.
videoEl.play().then(() => resolve()).catch((err: unknown) => {
reject(new Error(
`synthetic-display-source.webm play() rejected: ${
err instanceof Error ? err.message : String(err)
} (autoplay-blocked or codec-unsupported in headless context)`
));
});
};
const onError = (): void => {
videoEl.removeEventListener('canplay', onCanPlay);
videoEl.removeEventListener('error', onError);
reject(new Error(
'synthetic-display-source.webm failed to load (media error; '
+ 'verify WAR entry for assets/*.webm in manifest.json + '
+ 'dist-test/assets/<hash>.webm reachable via chrome-extension://)'
));
};
videoEl.addEventListener('canplay', onCanPlay);
videoEl.addEventListener('error', onError);
});
/**
* Apply the displaySurface monkey-patch to a freshly-minted stream's
* video track. Production code's post-grant validation reads
* `getSettings().displaySurface` and tears down + throws
* 'wrong-display-surface' on anything but 'monitor' — the patch makes
* the synthetic video stream satisfy that gate. WARNING 2 (iter-2):
* this patch is verified to work with HTMLVideoElement.captureStream
* tracks via the explicit sub-test in Task 1 verify block; the MDN-
* documented contract on the returned track is the same writable
* `getSettings` reference (per HTMLMediaElement.captureStream §Return
* Value: "A new MediaStream object").
*/
const patchDisplaySurface = (stream: MediaStream): void => {
const videoTrack = stream.getVideoTracks()[0];
if (videoTrack !== undefined) {
const originalGetSettings = videoTrack.getSettings.bind(videoTrack);
videoTrack.getSettings = ((): MediaTrackSettings => {
const real = originalGetSettings();
return {
...real,
displaySurface: 'monitor',
};
}) as typeof videoTrack.getSettings;
}
};
/**
* Mint a FRESH MediaStream from the HTMLVideoElement. Each invocation
* generates new tracks in 'live' state — required for the multi-
* recording-lifecycle pattern (A6 stops the first stream's tracks via
* dispatchEvent('ended'); A7 starts a new recording which calls
* getDisplayMedia → must get a live stream, NOT the dead one A6
* teardown left behind). The videoEl persists across calls; track refs
* do not. This contract is preserved verbatim from the canvas variant.
*/
const mintStream = (): MediaStream => {
if (fakeVideoEl === null) {
throw new Error('mintStream called before fakeVideoEl initialized — should be unreachable');
}
// captureStream(30) — request 30 fps; the actual cadence is driven
// by the source video's frame timeline (HTMLMediaElement.captureStream
// spec; non-standard but widely-implemented in Chrome since 2017).
//
// TypeScript cast through `&` intersection because HTMLMediaElement.
// captureStream is non-standard and not in default lib.dom.d.ts at
// all TS versions. The runtime surface is stable in Chrome MV3 target
// (≥88 per manifest).
const stream = (fakeVideoEl as HTMLVideoElement & {
captureStream: (fps?: number) => MediaStream;
}).captureStream(30);
patchDisplaySurface(stream);
return stream;
};
// LAZY ASYNC PHASE — fakeGetDisplayMedia awaits readiness on first call.
// Subsequent calls observe the already-resolved Promise and proceed
// immediately. The closure is async-shaped to match the real
// getDisplayMedia signature; recorder.startRecording's
// `await navigator.mediaDevices.getDisplayMedia(...)` is fully compatible.
const fakeGetDisplayMedia = async (
constraints?: DisplayMediaStreamOptions,
): Promise<MediaStream> => {
// Plan 01-14 A23: capture the production call's constraints object
// so the harness can verify `monitorTypeSurfaces: 'include'` reached
// the call site. Default to null on undefined-args so the bridge op
// reports an unambiguous "no args" signal rather than `undefined`.
lastGetDisplayMediaConstraints = constraints ?? null;
// LAZY FIRST-FRAME WAIT (iter-2 BLOCKER 2). The Promise was kicked
// off synchronously at install time; by the time the production
// recorder.startRecording reaches this call site (after recorder.ts
// bootstrap + START_RECORDING handler dispatch), the video has had
// a head start of ~50ms+ on its decode. Most calls observe a
// resolved Promise here and proceed immediately. First call on a
// cold offscreen may block ~50-500ms while canplay fires.
if (fakeVideoReadyPromise !== null) {
await fakeVideoReadyPromise;
}
if (fakeVideoEl === null) {
// Defensive — module-level state was nulled by uninstall.
throw new Error('fake getDisplayMedia called after uninstall');
}
return mintStream();
};
// SYNC MONKEY-PATCH — the eager-install contract requires this assignment
// to happen synchronously at function call time. Cast through `unknown`
// because MediaDevices.getDisplayMedia has multiple overloads.
(navigator.mediaDevices as unknown as {
getDisplayMedia: typeof fakeGetDisplayMedia;
}).getDisplayMedia = fakeGetDisplayMedia;
}
```
**Sub-step 3d — Delete the canvas-specific code block (current lines ~160-199 in source: `const canvas = document.createElement('canvas')` + drawFrame + requestAnimationFrame + setInterval). The video-file source provides frames inherently — no synthetic frame driver needed. Remove all references to: `fakeCanvas`, `fakeAnimationHandle`, `fakeDrawInterval`, `drawFrame`, `frameCount`, `ctx`, `canvas.captureStream`.**
**Sub-step 3e — Update `uninstallFakeDisplayMedia()` (current lines 271-291) to teardown the video element instead of canvas. Keep function SYNCHRONOUS (return `void`, not `Promise<void>`):**
```typescript
export function uninstallFakeDisplayMedia(): void {
if (!fakeInstalled) {
return;
}
fakeInstalled = false;
if (fakeVideoEl !== null) {
try {
fakeVideoEl.pause();
} catch {
// ignore pause errors during teardown (e.g., already paused)
}
fakeVideoEl.remove();
fakeVideoEl = null;
}
// Drop the Promise reference so a subsequent install creates a fresh one.
fakeVideoReadyPromise = null;
// We deliberately do NOT restore the original getDisplayMedia — the
// offscreen document is throwaway and gets a fresh navigator on the
// next createDocument() anyway.
}
```
**Sub-step 3f — Verify the eager-install at lines 528-537 is UNCHANGED. The existing sync void call still works because `installFakeDisplayMedia()` remains a sync void function:**
```typescript
// UNCHANGED — preserves the eager-install contract per iter-2 BLOCKER 2 fix.
try {
installFakeDisplayMedia(); // SYNC — monkey-patch installed before recorder.ts await resolves
} catch (e) {
console.warn("[offscreen-hooks] eager installFakeDisplayMedia failed:", e);
}
```
**Sub-step 3g — Verify the bridge handler at lines 408-418 is UNCHANGED. The existing `try { installFakeDisplayMedia(); sendResponse({ok: true}); } ... return false;` pattern still works because the function remains sync. The `return false` (sync sendResponse signal) stays — no async dispatcher conversion needed:**
```typescript
// UNCHANGED from current source — the bridge op stays sync because
// installFakeDisplayMedia() stays sync. The async first-frame wait is
// inside fakeGetDisplayMedia, not at install time, so the bridge
// dispatcher doesn't need to await anything.
if (op === 'install-fake-display-media') {
try {
installFakeDisplayMedia(); // SYNC; returns immediately
sendResponse({ ok: true });
} catch (err) {
sendResponse({
ok: false,
error: err instanceof Error ? err.message : String(err),
});
}
return false; // sync sendResponse signal — unchanged
}
```
**Step 4 — Add the explicit web_accessible_resources entry (iter-2 BLOCKER 1 fix).**
- Edit `manifest.json`. Modify the `web_accessible_resources` array to append a second entry for the WebM assets. Final array (with the existing welcome entry preserved):
```json
"web_accessible_resources": [
{
"resources": ["src/welcome/welcome.html"],
"matches": ["<all_urls>"]
},
{
"resources": ["assets/*.webm"],
"matches": ["<all_urls>"]
}
],
```
- **Rationale (locked in iter-2; no executor decision):**
- Production `dist/` has zero `*.webm` assets (verified by `find dist -name '*.webm' | wc -l` returning 0 post-prod-build); the entry is **inert** in production — costs only the diff in the production manifest, costs nothing at runtime.
- Test `dist-test/` has the hashed asset at `dist-test/assets/<hash>.webm` (verified by `find dist-test -name '*.webm' | wc -l` returning ≥1 post-test-build); the entry **authorizes** `chrome-extension://<id>/assets/<hash>.webm` URL access from the offscreen document context.
- The `@crxjs/vite-plugin` auto-WAR behavior for extracted media assets in offscreen-document context is empirically untested in this codebase (per checker iter-1 BLOCKER 1 — Plan 01-10 mokosh-mark.svg was 877 bytes and was inlined as data URI; `dist/manifest.json` shows it was NEVER auto-WAR'd because the SVG was never emitted as a file). Pre-emptive explicit entry sidesteps the question entirely.
- Threat surface: the entry exposes `assets/*.webm` URLs to all origins. Production has zero `*.webm` assets so the exposure surface is null in production. Test bundle is sandboxed in Puppeteer; no user-facing impact.
**Step 5 — Vite config verification (no edits needed; locked in iter-2).**
- `vite.config.ts`: production build — `?url` import is reachable only from `src/test-hooks/offscreen-hooks.ts`, which is gated by `__MOKOSH_UAT__` in `src/offscreen/recorder.ts:46-48` and tree-shaken to dead branch in production builds. NO config changes needed.
- `vite.test.config.ts`: test build — the offscreen entry is already present per Plan 01-13; the `?url` import resolves as a side-effect of the offscreen-hooks.ts module being included via the gated dynamic import. NO config changes needed.
- **assertion (verified by `npm run build:test` in Step 7):** `dist-test/assets/<hash>.webm` exists after test build; `dist/` has zero `*.webm` files after production build.
**Step 6 — Add Tier-2 production-bundle filename leak gate (iter-2 WARNING 5 remediation).**
- Edit `tests/background/no-test-hooks-in-prod-bundle.test.ts`. After the existing Tier-1 FORBIDDEN_HOOK_STRINGS test block, add a new sub-test:
```typescript
// Plan 04-08 iter-2 WARNING 5 — Tier-2 production-bundle filename leak canary.
//
// The test-only WebM fixture filename ('synthetic-display-source')
// appears in the TEST bundle as the resolved Vite hash URL but MUST
// NOT appear in the PRODUCTION dist/ bundle. The offscreen-hooks
// module that imports it is tree-shaken in production per
// __MOKOSH_UAT__; this gate catches any future regression that
// accidentally inlines test-hooks into the production chunk.
//
// The Tier-1 FORBIDDEN_HOOK_STRINGS inventory above tests __mokoshTest-
// family symbols; this Tier-2 gate tests the orthogonal axis of
// test-only ASSET filenames. Total inventory:
// Tier-1 (symbols): 12 entries (unchanged from Plan 01-14)
// Tier-2 (asset filenames): 1 entry (Plan 04-08 — synthetic-display-source)
test('Tier-2: synthetic-display-source filename does not leak into production dist/', () => {
// Walk dist/ files via the existing recursive walker (which skips
// symlinks; the existing countOccurrencesInFile helper handles
// binary-extension skipping). Grep for the literal string
// 'synthetic-display-source'. Expected: 0 hits.
const distFiles = listAllFilesRecursive(DIST_DIR); // existing helper at line ~152 of this file
const offendingFiles: string[] = [];
for (const filePath of distFiles) {
if (countOccurrencesInFile(filePath, 'synthetic-display-source') > 0) {
offendingFiles.push(filePath);
}
}
expect(offendingFiles).toEqual([]);
});
```
- **Note**: the snippet above uses the actual symbols from the live file
(`listAllFilesRecursive(DIST_DIR)` at line ~152 + `countOccurrencesInFile`
at line ~185 + the `DIST_DIR` constant at line ~133). The executor still
reads the existing test file first to confirm:
- The exact insertion site for the new `test(...)` block (after the
closing `}` of the `for (const needle of FORBIDDEN_HOOK_STRINGS)` loop
inside the existing `describe(...)` block — same describe scope keeps
the gates colocated under one suite name).
- Whether the new test should also be wrapped in the same
`it.skipIf(SKIP_BUILD === '1' && !existsSync(DIST_DIR))` style as the
Tier-1 entries (cross-check the existing pattern when reading).
**Step 7 — Verify TypeScript + production build clean + test build emits asset + Tier-2 gate passes.**
- `npx tsc --noEmit` exits 0.
- `npm run build` exits 0 (production build; test-hooks tree-shake verified — `find dist -name '*.webm' | wc -l` returns 0 + `grep -r "synthetic-display-source" dist/ 2>/dev/null | wc -l` returns 0).
- `npm run build:test` exits 0 (test build; ?url import resolves to hashed asset in dist-test/; `find dist-test -name '*.webm' | wc -l` returns ≥1; the chunk that contains the offscreen-hooks module includes a string match for the synthetic-display-source filename or its hashed URL).
- `wc -c tests/uat/fixtures/synthetic-display-source.webm` reports ≥ 1_000_000 bytes.
- `grep -c "canvas.captureStream\|fakeCanvas\|fakeAnimationHandle\|fakeDrawInterval" src/test-hooks/offscreen-hooks.ts | head -1` returns 0 (all canvas symbols excised — filtered to code only via `grep -v '^//'` if needed to skip comment refs):
- Use `grep -v '^[[:space:]]*\(//\|\*\)' src/test-hooks/offscreen-hooks.ts | grep -c "canvas\.captureStream\|fakeCanvas\b\|fakeAnimationHandle\|fakeDrawInterval"` to filter out comment matches; expected count = 0.
- `grep -v '^[[:space:]]*\(//\|\*\)' src/test-hooks/offscreen-hooks.ts | grep -c "fakeVideoEl\|videoEl\.captureStream\|fakeVideoReadyPromise"` returns ≥ 3 (replacement symbols present in code, not just comments).
- `grep -c "installFakeDisplayMedia(): void" src/test-hooks/offscreen-hooks.ts` returns ≥ 1 (sync signature preserved; iter-2 BLOCKER 2 verification — function does NOT return Promise).
- `grep -c "export function installFakeDisplayMedia(): Promise" src/test-hooks/offscreen-hooks.ts` returns 0 (verifies the function was NOT converted to async).
- `grep -c "await installFakeDisplayMedia" src/test-hooks/offscreen-hooks.ts` returns 0 (verifies the bridge handler + eager-install were NOT converted to await).
- **Sub-verify gate for displaySurface monkey-patch compatibility (WARNING 2 remediation — iter-3 polish: HIGH-LATENCY catch path locked in).** The displaySurface contract is gated by the spike re-run in Task 2 Step 1 — NOT by a dedicated low-latency sub-gate. Rationale: the spike's Step 1 calls `__mokoshHarness.assertA2` which executes `recorder.startRecording` -> `await navigator.mediaDevices.getDisplayMedia(...)` (the patched closure). If `track.getSettings().displaySurface !== 'monitor'` after the `patchDisplaySurface(stream)` call, the production gate at `src/offscreen/recorder.ts:294` tears down the stream + throws `'wrong-display-surface'` during assertA2 — surfacing the regression within seconds of the spike's Step 1, NOT after the 5-min idle elapses. The high-latency framing was overstated in iter-2; in practice the failure surfaces in <30s of the spike (assertA2 prime takes 5-15s; the displaySurface check fires inside the production handler immediately on track availability). The dedicated `--check-display-surface-only` mode is dropped — it would have required 5-10 LOC of under-specified spike-script augmentation (env-var hook, log emit, exit-code semantics) for no meaningful latency win over the canonical spike re-run.
- **SUMMARY documents the WARNING 2 closure path:** the executor's 04-08-SUMMARY.md MUST state explicitly that the WARNING 2 closure relied on the spike re-run's assertA2 fast-fail (NOT a dedicated sub-gate); cite the wall-clock-to-failure (<30s for displaySurface; full 6+ min only if assertA2 prime succeeds and the failure is in the videoSize floor).
- **Sub-verify gate for segments invariant (advisory 2 remediation):** `grep -cE 'let segments: Blob\[\] = \[\];' src/offscreen/recorder.ts` returns 1 (architectural invariant per debug session-2; Plan 04-08 does NOT modify recorder.ts).
</action>
<verify>
<automated>mkdir -p tests/uat/fixtures && test -s tests/uat/fixtures/synthetic-display-source.webm && wc -c tests/uat/fixtures/synthetic-display-source.webm | awk '$1 > 1000000 {exit 0} {exit 1}' && npx tsc --noEmit && npm run build 2>&1 | tail -5 && npm run build:test 2>&1 | tail -5 && find dist -name '*.webm' | wc -l | awk '$1 == 0 {exit 0} {exit 1}' && find dist-test -name '*.webm' | wc -l | awk '$1 >= 1 {exit 0} {exit 1}' && grep -r "synthetic-display-source" dist/ 2>/dev/null | wc -l | awk '$1 == 0 {exit 0} {exit 1}' && grep -v '^[[:space:]]*\(//\|\*\)' src/test-hooks/offscreen-hooks.ts | grep -c "canvas\.captureStream\|fakeCanvas\b\|fakeAnimationHandle\|fakeDrawInterval" | awk '$1 == 0 {exit 0} {exit 1}' && grep -v '^[[:space:]]*\(//\|\*\)' src/test-hooks/offscreen-hooks.ts | grep -c "fakeVideoEl\|videoEl\.captureStream\|fakeVideoReadyPromise" | awk '$1 >= 3 {exit 0} {exit 1}' && grep -c "installFakeDisplayMedia(): void" src/test-hooks/offscreen-hooks.ts | awk '$1 >= 1 {exit 0} {exit 1}' && grep -c "export function installFakeDisplayMedia(): Promise" src/test-hooks/offscreen-hooks.ts | awk '$1 == 0 {exit 0} {exit 1}' && grep -c "await installFakeDisplayMedia" src/test-hooks/offscreen-hooks.ts | awk '$1 == 0 {exit 0} {exit 1}' && grep -cE 'let segments: Blob\[\] = \[\];' src/offscreen/recorder.ts | awk '$1 == 1 {exit 0} {exit 1}' && grep -c '"assets/\*\.webm"' manifest.json | awk '$1 >= 1 {exit 0} {exit 1}' && grep -c "synthetic-display-source" tests/background/no-test-hooks-in-prod-bundle.test.ts | awk '$1 >= 1 {exit 0} {exit 1}' && npm test -- tests/background/no-test-hooks-in-prod-bundle.test.ts 2>&1 | tail -10 | grep -E '(passed|PASS)'</automated>
</verify>
<acceptance_criteria>
- `tests/uat/fixtures/synthetic-display-source.webm` exists, is VP9, >= 1 MB.
- `tests/fixtures/last_30sec.webm` remains in place (NOT moved).
- `globals.d.ts` contains the `declare module '*.webm?url'` block.
- `manifest.json` web_accessible_resources contains an `assets/*.webm` resources entry (iter-2 BLOCKER 1 fix).
- `src/test-hooks/offscreen-hooks.ts` imports `syntheticDisplaySourceUrl` via Vite `?url` suffix.
- **`installFakeDisplayMedia()` remains SYNCHRONOUS** (return type `: void`; no `async` keyword; iter-2 BLOCKER 2 verification — function signature preserved).
- `installFakeDisplayMedia()` body creates videoEl + appendChild + monkey-patch ASSIGNMENT all synchronously; the canplay wait + .play() are deferred INTO the `fakeGetDisplayMedia` closure.
- `fakeGetDisplayMedia` closure awaits `fakeVideoReadyPromise` on each call (first call may block ~50-500ms; subsequent calls fast-path).
- `mintStream()` calls `videoEl.captureStream(30)` (NOT `canvas.captureStream`).
- `uninstallFakeDisplayMedia()` is SYNCHRONOUS (return type `: void`); pauses + removes the video element + nulls `fakeVideoReadyPromise`.
- All 6 existing bridge ops remain functional + remain in their sync `return false` form (the install-fake-display-media op stays sync — does NOT switch to `return true` because the install itself stays sync).
- The eager-install try/catch at lines 528-537 is UNCHANGED.
- `npx tsc --noEmit` exits 0.
- `npm run build` exits 0; production bundle has zero `*.webm` files; zero references to `synthetic-display-source` in any dist/ file.
- `npm run build:test` exits 0; test bundle resolves the `?url` import; dist-test/assets/<hash>.webm exists.
- `grep -c "canvas.captureStream" src/test-hooks/offscreen-hooks.ts` (code only, comments stripped) returns 0.
- The displaySurface monkey-patch (`patchDisplaySurface(stream)` call) is preserved.
- The A23 `lastGetDisplayMediaConstraints` capture is preserved.
- The idempotent install/uninstall contract is preserved.
- **Architectural invariant preserved:** `src/offscreen/recorder.ts:91 let segments: Blob[] = []` is UNCHANGED (advisory 2 fix; grep gate enforces).
- **Tier-2 gate (WARNING 5 fix):** `tests/background/no-test-hooks-in-prod-bundle.test.ts` contains a new test that greps dist/ for 'synthetic-display-source' and asserts 0 hits; the test PASSES.
- **Tier-1 inventory UNCHANGED at 12** (verified by line count of both inventories — Plan 04-08 introduces zero new __mokoshTest-family symbols).
</acceptance_criteria>
<done>Methodology fix landed; bundled WebM source replaces canvas.captureStream throttling; eager-install contract preserved; explicit WAR entry locks in chrome-extension:// scheme access. Atomic commit: `feat(04-08): video-file MediaStream + sync-install/lazy-first-frame + explicit WAR — methodology reframe per debug session-2 + iter-2 BLOCKER fixes`.</done>
</task>
<task type="auto">
<name>Task 2: Re-run spike-a33-sw-persistence.ts + land A33 harness assertion + orchestrator wiring + ROADMAP SC #1 flip</name>
<files>tests/uat/extension-page-harness.ts, tests/uat/lib/harness-page-driver.ts, tests/uat/harness.test.ts, tests/uat/spike-a33-sw-persistence.ts, .planning/ROADMAP.md, .planning/STATE.md, .planning/phases/04-harden-clean-up-optional/04-08-SUMMARY.md</files>
<read_first>tests/uat/spike-a33-sw-persistence.ts (full — re-run target), tests/uat/extension-page-harness.ts:3517-3700 (assertA30 — cs-injection-world precedent for reference; A33 does NOT use this pattern but reading clarifies the harness assertion shape), tests/uat/extension-page-harness.ts:3878-3917 (assertA31 — most-recent chrome.runtime.sendMessage SAVE_ARCHIVE pattern; copy this), tests/uat/extension-page-harness.ts:3932-4021 (__mokoshHarness global registration block — verify assertA2 is the canonical "prime fresh recording" entrypoint per Plan 04-04 REVISION iter-2 Option B), tests/uat/lib/harness-page-driver.ts:48-80 (stopServiceWorker helper — REUSED from Plan 04-04 3726eee), tests/uat/lib/harness-page-driver.ts:2039-2148 (driveA30 — host-side filter pattern for shape reference), tests/uat/lib/harness-page-driver.ts:1434-1480 (findLatestZip exported function — REUSED from Plan 04-04), tests/uat/harness.test.ts:100-110 (import block), tests/uat/harness.test.ts:340-360 (wrapped-driver block), tests/uat/harness.test.ts:459-487 (drivers-array push block; A32 is the most-recent entry), tests/uat/harness.test.ts:225-240 (SKIP_PROD_REBUILD env-gate pattern — analog for SKIP_LONG_UAT), .planning/ROADMAP.md:250-262 (SC #1 row text — exact pre-edit string), .planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md (WARNING 3 + WARNING 4 — specific grep gates for spike probe values + ROADMAP edit)</read_first>
<action>
**Step 0 — GATING CONDITION (Task 1 complete + methodology valid).**
- Verify Task 1 acceptance criteria all met. If any fail (TS error, missing fixture, sync signature violated, etc.), STOP and fix before proceeding.
- displaySurface compatibility is gated by Step 1 below (the spike re-run's assertA2 fast-fails on `'wrong-display-surface'` if the patch is broken; surfaces within ~30s of spike start, NOT after the 5-min idle). No separate Task 1 sub-gate exists — the dedicated `--check-display-surface-only` mode was dropped in iter-3 polish per WARNING 1 closure (the high-latency catch path is locked in).
**Step 1 — Re-run the spike script.**
- Run: `HEADLESS=1 SKIP_PROD_REBUILD=0 npx tsx tests/uat/spike-a33-sw-persistence.ts 2>&1 | tee /tmp/04-08-spike-rerun.log`
- Total wall-clock: ~6-7 min (5 min idle + ~1-2 min orchestration).
- **Expected outcome:** `SPIKE OUTCOME: PASSED` with `videoSize > 100_000` (typical 1-3 MB).
- **GATING CONDITION for landing A33:** `grep -c 'SPIKE OUTCOME: PASSED' /tmp/04-08-spike-rerun.log` returns ≥ 1 AND extracted videoSize > 100_000.
- **WARNING 3 remediation (iter-2): probe-value grep gates surfaced as explicit verify-block commands** — the spike script already emits `SPIKE PROBE [POST-PRIME]: segments.length=N`, `SPIKE PROBE [PRE-KILL]: segments.length=N`, `SPIKE PROBE [POST-KILL]: segments.length=N` lines (per debug session-2 Step B addition). The verify block below greps each value to confirm the methodology is healthy:
- `POST-PRIME=0` — no segment rotation has happened yet (the prime just established REC state).
- `PRE-KILL >= 3` — 5-min idle drove the rotation cadence to MAX_SEGMENTS (3 × 10s self-contained segments accumulated).
- `POST-KILL >= 3` — segments structurally survived the SW kill (per debug session-2 verdict: architecture is sound; this should match PRE-KILL).
- If the re-run still FAILS (videoSize ≤ 100_000): STOP execution; document failure in SUMMARY; flag for further debug session. This branch is unlikely per session-2 verdict (canvas throttling is the only identified root cause; HTMLVideoElement source bypasses it definitively per MDN + Chrome bug 653548 design doc) but the spike-first contract requires explicit acknowledgement of the FAIL branch.
**Step 2 — Update the spike script if needed (sync-install/lazy-first-frame compatibility).**
- The spike script's prime step uses `__mokoshHarness.assertA2` (canonical fresh-recording bootstrap; Plan 04-04 REVISION iter-2 Option B). Under the new methodology, `assertA2` triggers `recorder.startRecording` which calls `navigator.mediaDevices.getDisplayMedia` — this is now the patched closure that lazy-waits for video readiness. First call may take an extra ~50-500ms; the existing assertA2 timeout (per Plan 01-13 default ~15s) absorbs this comfortably. NO edit to the spike script's logic is needed.
- No spike-script edit is needed. The dedicated `--check-display-surface-only` mode was dropped in iter-3 polish (WARNING 1 closure — high-latency catch path locked in); the canonical spike covers the displaySurface invariant via assertA2's fast-fail path.
**Step 3 — Land A33 in the harness (3-file lockstep per Plan 04-04 Wave 1 spec verbatim).**
**File A: `tests/uat/extension-page-harness.ts`** — no edits needed (per Plan 04-04 REVISION iter-2 Option B). The `__mokoshHarness` surface stays at `assertA1..A31 + getManifestVersion`. SAVE_ARCHIVE dispatch happens inline in driveA33 via `page.evaluate` + `chrome.runtime.sendMessage`. The Task 2 read_first MUST verify this; if a different prime-recording method is more appropriate than assertA2, document in SUMMARY but DO NOT add new __mokoshHarness methods (FORBIDDEN_HOOK_STRINGS lockstep at 12 entries).
**File B: `tests/uat/lib/harness-page-driver.ts`** — append `driveA33` per the body in `<interfaces>` above.
- Place AFTER the existing `driveA32` function definition (the most-recent Phase 3 addition).
- Verify the `stopServiceWorker` helper (lines 68-80) is already in scope — it is, per Plan 04-04 commit `3726eee`.
- Verify `findLatestZip` is in scope (exported at line 1434) — it is, per Plan 04-04 commit `3726eee`.
- Verify `JSZip` + `readFileSync` are imported (lines 37 + 41) — they are.
- Type signature: `(page: Page, browser: Browser, extensionId: string, downloadsDir: string) => Promise<AssertionRecord>`.
- Filter-pipeline form; no `continue`; explicit named callbacks per project style.
**File C: `tests/uat/harness.test.ts`** — 3 sites edit:
(1) Import block at line 107 — add `driveA33,` after `driveA32,`:
```typescript
// Plan 04-08 — driveA33 SW state persistence (ROADMAP SC #1; methodology
// reframe per debug session-2 verdict; needs Browser + extensionId for
// CDP-based SW kill + downloadsDir for host-side JSZip parse).
driveA33,
```
(2) Wrapped-driver block after line 357 (after `driveA31Wrapped`):
```typescript
// Plan 04-08 — driveA33 needs Browser + extensionId for CDP-based SW kill
// AND downloadsDir for host-side JSZip parse of post-restart zip.
const driveA33Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
(page) => driveA33(page, handles.browser, handles.extensionId, handles.downloadsDir);
```
(3) Drivers-array push at line 487 (after the existing A32 entry):
```typescript
// Plan 04-08 A33: SW state persistence 5-min idle (ROADMAP SC #1).
// Methodology reframe per debug session-2 — video-file MediaStream
// replaces the canvas.captureStream invisible-source throttling that
// produced 8505-byte 0-frames archives under the previous Plan 04-04
// spike methodology. Architecture (offscreen-RAM segments: Blob[]) is
// unchanged and canonically correct per debug session-2 segment-count
// probe evidence (POST-KILL count=3 confirms structural persistence).
// Forces SW eviction via Puppeteer CDP worker.close() per the canonical
// Chrome devrel pattern (stopServiceWorker helper from Plan 04-04).
// Env-gated by SKIP_LONG_UAT for fast per-commit iteration; defaults
// to RUN for Phase 4 closure + alpha gate.
{
name: 'A33',
drive: process.env.SKIP_LONG_UAT === '1'
? async (): Promise<AssertionRecord> => ({
name: 'A33',
passed: true,
checks: [],
diagnostics: ['A33 SKIPPED (SKIP_LONG_UAT=1; unset to run 5-min idle test)'],
})
: driveA33Wrapped,
},
```
**Step 4 — Run the UAT harness verification gates.**
- `npx tsc --noEmit` exits 0.
- `npm run build:test` exits 0.
- **Skip-mode UAT (fast preserve):** `HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -5 | tee /tmp/04-08-uat-skip.log` — expect `34/34` GREEN in ~95s; A33 SKIPPED placeholder counts as PASS.
- **Full-mode UAT (closure gate):** `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat 2>&1 | tail -5 | tee /tmp/04-08-uat-full.log` — expect `34/34` GREEN in ~6.5 min; A33 actually runs and passes A33.1 + A33.2 + A33.3.
**Step 5 — Lockstep + bundle-gate invariant verification.**
- Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 — verify by counting code-only entries (comment-stripped) in both arrays:
```bash
# tests/uat/harness.test.ts FORBIDDEN_HOOK_STRINGS array entries (12 expected)
# tests/background/no-test-hooks-in-prod-bundle.test.ts the same array (12 expected, lockstep)
```
Confirm zero new entries added (no new __MOKOSH_UAT__-gated symbols).
- Tier-2 sub-invariant (Plan 04-08 iter-2): `npm test -- tests/background/no-test-hooks-in-prod-bundle.test.ts 2>&1 | grep -E '(passed|PASS)'` — both the Tier-1 inventory test AND the new Tier-2 filename-leak test pass.
- Pre-checkpoint bundle gates (per `feedback-pre-checkpoint-bundle-gates.md`) — 6/6 PASS unchanged from Plan 04-03 baseline:
- `grep -v '^#\|^//' dist/assets/index-*.js | grep -c 'new Function'` returns 0 (Plan 04-02 polarity preserved)
- `grep -v '^#\|^//' dist/assets/index-*.js | grep -c '\beval\b'` returns 0
- `grep -v '^#\|^//' dist/assets/index-*.js | grep -c 'Buffer\.'` returns 1 (pre-existing JSZip polyfill; Plan 04-02 deferred)
- SW chunk `window.`/`document.` references = 0
- Manifest validation + en/ru parity preserved (unchanged from Plan 04-03)
- vitest baseline flips 183 -> 184: `npm test 2>&1 | tail -5` — expect 184/184 GREEN (prior baseline 183 + exactly 1 new `test(...)` block added in Task 1 Step 6 — the Tier-2 'synthetic-display-source filename does not leak' gate in tests/background/no-test-hooks-in-prod-bundle.test.ts; cf. WARNING 5 + iter-3 polish per checker iter-2 cosmetic-advisory 3). Or 181/184 with the 3 documented pre-existing parallel-vitest flakes per 04-CONTEXT items 9-10; flakes pass in isolation.
**Step 6 — Write Plan 04-08 SUMMARY.**
- Create `.planning/phases/04-harden-clean-up-optional/04-08-SUMMARY.md`:
- Methodology reframe rationale (cite debug session-2 verdict verbatim)
- **iter-2 revision summary** (cite checker iter-1 + BLOCKER 1 + BLOCKER 2 + WARNING resolutions)
- Bundled WebM fixture decision (copy vs regenerate; size + codec evidence; dual-location note; **duration=N/A rationale** — 1.9 MB / ~400 kbps VP9 = ~38s decoded timeline + `videoEl.loop = true` for indefinite playback across the 5-min spike; cf. iter-3 polish per checker iter-2 cosmetic-advisory 4)
- installFakeDisplayMedia diff summary (canvas -> HTMLVideoElement; SYNC install + LAZY first-frame; 6 bridge ops preserved sync; A23 capture preserved; eager-install contract preserved)
- **WARNING 1 closure path** (autoplay reject fallback) — document explicitly: no Plan B fallback; explicit error-class identifier on autoplay/codec reject; the error class string is observable in the spike's offscreen-console capture; cite the wall-clock-to-failure surface (per iter-3 polish + checker iter-2 cosmetic-advisory 2)
- manifest.json WAR entry decision (BLOCKER 1 rationale: pre-emptive explicit entry; production inert; test bundle authorized)
- **WARNING 2 closure path** (displaySurface compat) — document explicitly: HIGH-LATENCY catch via spike re-run's assertA2 fast-fail (NOT a dedicated sub-gate); cite the wall-clock-to-failure (<30s for displaySurface check inside assertA2 prime); the dedicated `--check-display-surface-only` mode was dropped in iter-3 polish per WARNING 1 closure
- Spike re-run evidence (videoSize before/after; segment-count probe values; elapsed time)
- A33 land evidence (driveA33 + orchestrator wiring; FORBIDDEN_HOOK_STRINGS at 12; pre-checkpoint gates 6/6; Tier-2 gate added)
- UAT before/after (33/33 -> 34/34 GREEN)
- ROADMAP SC #1 closure with commit ref
- Plan 04-04 SUMMARY post-debug amendment cross-reference
- Persisting artifacts (stopServiceWorker + spike script + canonical methodology pattern + Tier-2 invariant)
- Architectural integrity statement (offscreen-RAM segments: Blob[] is UNCHANGED and canonically correct; grep gate enforces)
**Step 7 — Update STATE.md + ROADMAP.md markers (Phase 4 partial closure) — WARNING 4 remediation: exact edit specified + grep gate.**
**STATE.md edit:**
- Append to "Decisions" section: `[Phase 04-08]: Methodology reframe complete — video-file MediaStream replaces canvas.captureStream throttling per debug session-2 verdict; A33 harness assertion landed; UAT 33 -> 34 GREEN; ROADMAP SC #1 (SW state persistence) CLOSED 2026-05-22.`
**ROADMAP.md edit (exact pre-edit string + replacement; WARNING 4 fix):**
- Open `.planning/ROADMAP.md`. Locate the SC #1 prose at lines 250-262 (the existing OPEN status block).
- The current text (verified during planning) contains:
```
**STATUS 2026-05-21: OPEN.** Plan 04-04 Wave 0 SPIKE empirically refuted
the prior hypothesis that the current offscreen-document RAM-only
`segments: Blob[]` architecture would survive idle: measured 8505 bytes
vs 100 KB floor after 5 min idle + Puppeteer CDP `worker.close()`. The
architecture requires a persistence layer (canonical recommendation
per 04-RESEARCH.md Q2 sub-question b Option C: IndexedDB persistence
in offscreen). Plan-fix ceremony queued ahead of Plans 04-05/04-06/
04-07. Reproducible verification gate: tests/uat/spike-a33-sw-persistence.ts.
```
- Replace the entire block (from `**STATUS 2026-05-21: OPEN.**` through the line ending with `spike-a33-sw-persistence.ts.`) with:
```
**STATUS 2026-05-22: CLOSED via Plan 04-08 — see .planning/phases/04-harden-clean-up-optional/04-08-SUMMARY.md.**
The prior Plan 04-04 SPIKE FAILED outcome (8505 bytes; 2026-05-21) was
empirically REFUTED by debug session-2 (commit `4ea1bbb`): the
offscreen-RAM `segments: Blob[]` architecture is sound (POST-KILL probe
count=3 confirms structural persistence); the failure was test
methodology (canvas.captureStream invisible-source throttling per
Chrome bug 653548). Plan 04-08 replaced the canvas source with
HTMLVideoElement.captureStream backed by a bundled WebM (preserving
eager-install contract via SYNC-install + LAZY first-frame pattern);
spike re-run produces videoSize > 100_000 (typical 1-3 MB); A33
harness assertion lands per Plan 04-04 Pattern 4 verbatim under
SKIP_LONG_UAT env-gate. Reproducible verification gate:
tests/uat/spike-a33-sw-persistence.ts (now PASSES under valid
methodology).
```
- Update the Plans list block at line ~277 — find `04-04-PLAN.md` entry and append a sibling line after it for Plan 04-08:
```
- [x] 04-08-PLAN.md — A33 methodology reframe + harness assertion: **CLOSED 2026-05-22** via debug session-2 verdict (canvas-captureStream invisible-source throttling root cause); HTMLVideoElement.captureStream replaces canvas.captureStream in installFakeDisplayMedia() with SYNC install + LAZY first-frame contract; spike re-run produces videoSize > 100_000 (vs 8505 baseline); A33 lands per original Plan 04-04 Wave 1 spec under SKIP_LONG_UAT env-gate; UAT 33 -> 34 GREEN. ROADMAP SC #1 CLOSED.
```
- Update the phase tracker table at line ~292: find the row for Phase 4 and update completion count + status note:
```
| 4. Harden + clean up (optional) | 5/8 | In Progress (Plan 04-08 closed ROADMAP SC #1 via methodology reframe; Plans 04-05/04-06/04-07 remain for build hygiene + visual polish + closure aggregator) | |
```
- **WARNING 4 grep gate (post-edit verify):**
```bash
grep -c 'CLOSED via Plan 04-08' .planning/ROADMAP.md # MUST return >= 1
grep -c 'STATUS 2026-05-21: OPEN' .planning/ROADMAP.md # MUST return 0 (the old status removed)
grep -c 'STATUS 2026-05-22: CLOSED' .planning/ROADMAP.md # MUST return >= 1
```
**Step 8 — Atomic commit (advisory 1 — corrected commit message; Task 1 + Task 2 only, no Task 3).**
- Single atomic commit for Task 2 with all Task 2 artifacts + SUMMARY + STATE/ROADMAP markers: `feat(04-08): A33 SW state persistence harness assertion — methodology reframe (34/34 GREEN; ROADMAP SC #1 CLOSED)`.
- Task 1 was committed separately at the close of Task 1 per its own done line: `feat(04-08): video-file MediaStream + sync-install/lazy-first-frame + explicit WAR — methodology reframe per debug session-2 + iter-2 BLOCKER fixes`.
</action>
<verify>
<automated>npx tsc --noEmit && npm run build:test && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -5 | tee /tmp/04-08-uat-skip.log; grep -c '34/34\|34 passed' /tmp/04-08-uat-skip.log | awk '$1 >= 1 {exit 0} {exit 1}' && grep -c 'driveA33' tests/uat/harness.test.ts | awk '$1 >= 3 {exit 0} {exit 1}' && grep -c 'SKIP_LONG_UAT' tests/uat/harness.test.ts | awk '$1 >= 2 {exit 0} {exit 1}' && grep -c 'dispatchSaveArchive' tests/uat/lib/harness-page-driver.ts tests/uat/extension-page-harness.ts tests/uat/harness.test.ts 2>/dev/null | grep -v ":0$" | wc -l | awk '$1 == 0 {exit 0} {exit 1}' && grep -cE 'SPIKE PROBE \[POST-PRIME\]: segments\.length=0' /tmp/04-08-spike-rerun.log 2>/dev/null | awk '$1 >= 1 {exit 0} {exit 1}' && grep -cE 'SPIKE PROBE \[PRE-KILL\]: segments\.length=([3-9]|[1-9][0-9]+)' /tmp/04-08-spike-rerun.log 2>/dev/null | awk '$1 >= 1 {exit 0} {exit 1}' && grep -cE 'SPIKE PROBE \[POST-KILL\]: segments\.length=([3-9]|[1-9][0-9]+)' /tmp/04-08-spike-rerun.log 2>/dev/null | awk '$1 >= 1 {exit 0} {exit 1}' && grep -c 'SPIKE OUTCOME: PASSED' /tmp/04-08-spike-rerun.log 2>/dev/null | awk '$1 >= 1 {exit 0} {exit 1}' && grep -c 'CLOSED via Plan 04-08' .planning/ROADMAP.md | awk '$1 >= 1 {exit 0} {exit 1}' && grep -c 'STATUS 2026-05-21: OPEN' .planning/ROADMAP.md | awk '$1 == 0 {exit 0} {exit 1}'</automated>
</verify>
<acceptance_criteria>
- Spike re-run output contains `SPIKE OUTCOME: PASSED` AND `videoSize > 100_000`. (If FAILED branch fires, STOP and document; A33 land is BLOCKED.)
- **WARNING 3 (iter-2):** Spike log contains `SPIKE PROBE [POST-PRIME]: segments.length=0` (post-prime baseline; no rotations yet).
- **WARNING 3 (iter-2):** Spike log contains `SPIKE PROBE [PRE-KILL]: segments.length=X` where X ≥ 3 (5-min idle drove rotation cadence to MAX_SEGMENTS).
- **WARNING 3 (iter-2):** Spike log contains `SPIKE PROBE [POST-KILL]: segments.length=X` where X ≥ 3 (architecture preserved across SW kill per debug session-2).
- `npx tsc --noEmit` exits 0.
- `npm run build:test` exits 0.
- `tests/uat/lib/harness-page-driver.ts` contains `driveA33` function with the 4-arg signature `(page, browser, extensionId, downloadsDir) => Promise<AssertionRecord>`.
- `tests/uat/lib/harness-page-driver.ts` driveA33 dispatches SAVE_ARCHIVE inline via `chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, ...)` — verify `grep -c "type: 'SAVE_ARCHIVE'" tests/uat/lib/harness-page-driver.ts` returns ≥ 1.
- `tests/uat/harness.test.ts` imports `driveA33` (1 line; grep -c returns ≥ 4 including comment).
- `tests/uat/harness.test.ts` defines `driveA33Wrapped` const.
- `tests/uat/harness.test.ts` drivers-array contains an `{ name: 'A33', drive: ... }` entry with `SKIP_LONG_UAT` env-gate.
- Skip-mode UAT: `HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat` reports 34/34 GREEN in ~95s.
- Full-mode UAT: `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat` reports 34/34 GREEN in ~6.5 min; A33.1 + A33.2 + A33.3 all PASS.
- `dispatchSaveArchive` symbol is NOT introduced anywhere — `grep -c 'dispatchSaveArchive' tests/uat/` returns 0.
- FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 entries — verify by line count of both arrays.
- **Tier-2 gate (Plan 04-08 iter-2):** `grep -c 'synthetic-display-source' tests/background/no-test-hooks-in-prod-bundle.test.ts` returns ≥ 1; the test PASSES under `npm test -- tests/background/no-test-hooks-in-prod-bundle.test.ts`.
- Pre-checkpoint bundle gates 6/6 PASS unchanged from Plan 04-03 baseline.
- vitest baseline flips 183 -> 184 (prior 183 GREEN + exactly 1 new `test(...)` block — the Tier-2 'synthetic-display-source filename does not leak' gate in tests/background/no-test-hooks-in-prod-bundle.test.ts) — or 181/184 with the 3 documented pre-existing flakes; pass in isolation.
- **WARNING 4 (iter-2):** `.planning/ROADMAP.md` contains `'CLOSED via Plan 04-08'` (grep returns ≥ 1); contains zero hits on `'STATUS 2026-05-21: OPEN'` (the old status was removed); contains `'STATUS 2026-05-22: CLOSED'`.
- `.planning/STATE.md` Decisions list contains a Plan 04-08 entry citing ROADMAP SC #1 CLOSED.
- `.planning/phases/04-harden-clean-up-optional/04-08-SUMMARY.md` exists with: methodology rationale + iter-2 revision summary + spike re-run evidence + probe values + A33 land evidence + UAT before/after + ROADMAP SC #1 closure + cross-reference to Plan 04-04 post-debug amendment.
</acceptance_criteria>
<done>A33 lands; UAT 33->34 GREEN; ROADMAP SC #1 CLOSED. Atomic commit: `feat(04-08): A33 SW state persistence harness assertion — methodology reframe (34/34 GREEN; ROADMAP SC #1 CLOSED)`.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Bundled WebM fixture -> offscreen test bundle | the `tests/uat/fixtures/synthetic-display-source.webm` is project-owned (CC0-equivalent internal capture) and loaded via Vite's `?url` asset pipeline only in test builds. Production builds tree-shake the entire offscreen-hooks module per `__MOKOSH_UAT__`; the fixture has zero attack surface in production (manifest.json WAR entry for assets/*.webm is inert because production dist/ has zero *.webm files). |
| HTMLVideoElement.captureStream -> MediaRecorder | the video element source is bundled at build time (not network-fetched); no remote-origin attack vector. The captured stream feeds the production MediaRecorder code path verbatim — same boundary as canvas.captureStream had. |
| Puppeteer CDP -> Chrome MV3 SW | unchanged from Plan 04-04 (stopServiceWorker helper reused verbatim). |
| manifest.json web_accessible_resources -> chrome-extension:// URL access | new explicit entry for `assets/*.webm` extends the existing `src/welcome/welcome.html` WAR pattern. Matches `<all_urls>`; in production this is null-surface (no *.webm files); in test this authorizes the bundled fixture. |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-04-08-01 | Tampering | future change to `installFakeDisplayMedia()` reintroduces canvas.captureStream silently | mitigate | `grep -c "canvas.captureStream" src/test-hooks/offscreen-hooks.ts` (code-only, comment-filtered) invariant = 0 codified in Task 1 verify. A33 acts as a regression-catching gate; if a future PR moves source back to canvas, the spike fails fast (videoSize=8505). |
| T-04-08-02 | Tampering | bundled `synthetic-display-source.webm` is renamed/deleted/replaced with a tiny or invalid WebM | mitigate | Task 1 verify checks `wc -c` >= 1_000_000 bytes; Task 2 spike re-run catches any content-level corruption (videoSize-floor gate). |
| T-04-08-03 | Information disclosure | the bundled WebM contains operator content (it's a project-internal capture of the developer's screen) | accept | The existing `tests/fixtures/last_30sec.webm` has been in the repo since 2026-05-15 (Plan 01-07 closure); the new copy at `tests/uat/fixtures/synthetic-display-source.webm` is the same project-owned artifact. Tradeoff: rebuilding a fresh WebM from `ffmpeg -f lavfi -i testsrc` is OPTIONAL per Task 1 Step 1. If the developer prefers content-free fixtures, the regeneration path is the documented escape hatch. |
| T-04-08-04 | DoS (test runtime) | A33's 5-min idle adds ~5 min to harness wall-clock (95s -> 395s); per-commit CI lanes would suffer | mitigate | Env-gated by `SKIP_LONG_UAT=1` (default RUN for closure + alpha; documented per-commit fast-skip path). Same gate Plan 04-04 specified. |
| T-04-08-05 | Repudiation | HTMLVideoElement.captureStream is non-standard (not in lib.dom.d.ts at all TS versions); a TS strict-mode bump could break the cast-through-intersection idiom | accept | The cast is pinned in the code at Task 1 Step 3; project's TS pin is documented; if a TS upgrade ever breaks the cast, the build-test gate (Task 1 verify) catches it. The runtime surface itself (HTMLMediaElement.captureStream) is implemented in Chrome since 2017 and stable per MDN. |
| T-04-08-06 | Spoofing | a malicious replacement `synthetic-display-source.webm` could inject malicious VP9 codec data | accept | the fixture is committed to git; any tamper is visible via `git diff`. Plan 04-08's threat surface is test-only and the test build runs in a sandboxed Puppeteer browser instance. No production user is exposed. |
| T-04-08-07 | Elevation of Privilege | manifest.json's new web_accessible_resources entry for `assets/*.webm` could expose unintended content if a future plan accidentally bundles a sensitive WebM into production dist/ | mitigate | Production dist/ has zero *.webm assets (verified by Task 1 Step 7 grep gate: `find dist -name '*.webm' | wc -l == 0`); Tier-2 filename-leak gate at tests/background/no-test-hooks-in-prod-bundle.test.ts catches any regression. The threat is constructed (no current attack surface) but the gate makes it self-extinguishing. |
| T-04-08-08 | Tampering | future refactor converts `installFakeDisplayMedia()` to async (breaking the eager-install contract — recurrence of iter-2 BLOCKER 2) | mitigate | Grep gate codified in Task 1 verify: `grep -c "export function installFakeDisplayMedia(): Promise" src/test-hooks/offscreen-hooks.ts == 0` AND `grep -c "installFakeDisplayMedia(): void" >= 1`. Any future PR that flips the signature trips the build-test pipeline. Threat is high-impact (silent re-introduction of the 8505-byte race) but the gate is deterministic. |
| T-04-08-09 | Tampering | future refactor removes the explicit `assets/*.webm` WAR entry (breaking the chrome-extension:// access path for the bundled fixture) | mitigate | Task 1 verify gate: `grep -c '"assets/\*\.webm"' manifest.json >= 1`. The fixture would still load via Vite hash URL in test build but the chrome-extension:// scheme access would fail; the spike re-run would catch the regression. |
</threat_model>
<verification>
- `npx tsc --noEmit` exits 0.
- `npm run build` exits 0 (production; test-hooks tree-shake verified).
- `npm run build:test` exits 0 (test; ?url import resolves; dist-test/assets/<hash>.webm emitted).
- `wc -c tests/uat/fixtures/synthetic-display-source.webm` reports >= 1_000_000 bytes; codec=vp9 per ffprobe.
- `find dist -name '*.webm' | wc -l` returns 0 (production has zero WebM assets — WAR entry is inert there).
- `find dist-test -name '*.webm' | wc -l` returns ≥ 1 (test bundle emits the asset).
- **Code-only grep gates (filtering comments via `grep -v '^[[:space:]]*\(//\|\*\)'`):**
- `canvas.captureStream` references: 0
- `fakeVideoEl|videoEl.captureStream|fakeVideoReadyPromise` references: ≥ 3
- `grep -c "installFakeDisplayMedia(): void" src/test-hooks/offscreen-hooks.ts` returns ≥ 1 (sync signature preserved).
- `grep -c "export function installFakeDisplayMedia(): Promise" src/test-hooks/offscreen-hooks.ts` returns 0 (NOT async; iter-2 BLOCKER 2 fix verified).
- `grep -c "await installFakeDisplayMedia" src/test-hooks/offscreen-hooks.ts` returns 0 (eager-install + bridge handler preserve sync calls).
- `grep -c '"assets/\*\.webm"' manifest.json` returns ≥ 1 (iter-2 BLOCKER 1 fix verified).
- `grep -c "synthetic-display-source" tests/background/no-test-hooks-in-prod-bundle.test.ts` returns ≥ 1 (Tier-2 gate present; WARNING 5).
- `grep -r "synthetic-display-source" dist/ 2>/dev/null | wc -l` returns 0 (production tree-shake).
- `grep -cE 'let segments: Blob\[\] = \[\];' src/offscreen/recorder.ts` returns 1 (advisory 2; architecture invariant).
- Spike re-run: `HEADLESS=1 npx tsx tests/uat/spike-a33-sw-persistence.ts` exits 0 with `videoSize > 100_000` (typical 1-3 MB).
- **WARNING 3 (iter-2):** spike log contains:
- `SPIKE PROBE [POST-PRIME]: segments.length=0`
- `SPIKE PROBE [PRE-KILL]: segments.length=X` where X ≥ 3
- `SPIKE PROBE [POST-KILL]: segments.length=X` where X ≥ 3
- `SPIKE OUTCOME: PASSED`
- UAT harness count 33 -> 34.
- Skip-mode UAT: `HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat` GREEN 34/34 in ~95s.
- Full-mode UAT: `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat` GREEN 34/34 in ~6.5 min.
- **WARNING 4 (iter-2):** ROADMAP edits:
- `grep -c 'CLOSED via Plan 04-08' .planning/ROADMAP.md` returns ≥ 1
- `grep -c 'STATUS 2026-05-21: OPEN' .planning/ROADMAP.md` returns 0 (old status removed)
- `grep -c 'STATUS 2026-05-22: CLOSED' .planning/ROADMAP.md` returns ≥ 1
- Tier-1 FORBIDDEN_HOOK_STRINGS lockstep at 12 entries (unchanged).
- Tier-2 gate (Plan 04-08): `npm test -- tests/background/no-test-hooks-in-prod-bundle.test.ts` passes.
- Pre-checkpoint bundle gates 6/6 PASS (new Function=0 + eval=0 + Buffer.=1 + window.=0 + document.=0 in SW + Tier-1=12 + en/ru parity).
- vitest baseline flips 183 -> 184 GREEN (prior 183 + exactly 1 new `test(...)` block — the Tier-2 filename-leak gate in tests/background/no-test-hooks-in-prod-bundle.test.ts; cf. WARNING 5 + iter-3 polish per checker iter-2 cosmetic-advisory 3); or 181/184 with 3 documented pre-existing flakes; pass in isolation.
- A29 + A30 + A31 + A32 unchanged (no regression to existing assertions).
- Architecture invariant preserved: `src/offscreen/recorder.ts:91 let segments: Blob[] = []` UNCHANGED — debug session-2 verdict is honored (NO IndexedDB persistence work).
- spike-FAILED forensic-evidence preservation: `tests/uat/spike-a33-sw-persistence.ts` retained (Plan 04-04 commit 3726eee + debug session-2 Step B + C additions); now functions as a PASSING regression test under the new methodology.
- `dispatchSaveArchive` symbol NOT introduced anywhere — `grep -c` across spike script + harness files returns 0.
- **iter-2 BLOCKER 1 verify:** WAR entry pre-decided AND the @crxjs/vite-plugin auto-WAR question is sidestepped (no executor improvisation needed).
- **iter-2 BLOCKER 2 verify:** eager-install contract preserved — sync monkey-patch in `installFakeDisplayMedia()`; lazy first-frame inside `fakeGetDisplayMedia` closure; recorder.ts:46-48 await chain race window eliminated.
</verification>
<success_criteria>
- Bundled WebM fixture lands at `tests/uat/fixtures/synthetic-display-source.webm` (>=1 MB VP9; project-owned).
- `installFakeDisplayMedia()` rewritten — canvas.captureStream replaced by HTMLVideoElement.captureStream; **function signature remains SYNCHRONOUS** (iter-2 BLOCKER 2); displaySurface monkey-patch + A23 capture + idempotency contract preserved.
- `fakeGetDisplayMedia` closure performs the lazy first-frame wait — first call awaits `fakeVideoReadyPromise`; subsequent calls observe resolved Promise + proceed immediately.
- Ambient module decl for `*.webm?url` added to `globals.d.ts`.
- Explicit web_accessible_resources entry for `assets/*.webm` added to `manifest.json` (iter-2 BLOCKER 1).
- Spike re-run produces `videoSize > 100_000` (typical 1-3 MB) — methodology reframe empirically validated.
- Spike probe values confirm methodology health: POST-PRIME=0, PRE-KILL≥3, POST-KILL≥3 (WARNING 3).
- A33 harness assertion lands per Plan 04-04 Pattern 4 verbatim: driveA33 + driveA33Wrapped + orchestrator entry + SKIP_LONG_UAT env-gate.
- UAT harness count 33 -> 34 GREEN (skip-mode in ~95s; full-mode in ~6.5 min).
- ROADMAP SC #1 (SW state persistence) flipped CLOSED in ROADMAP.md with exact pre-edit/post-edit grep gates verified (WARNING 4).
- Plan 04-04 SUMMARY post-debug amendment cross-referenced in Plan 04-08 SUMMARY.
- Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 entries.
- Tier-2 production-bundle filename-leak gate added: `synthetic-display-source` returns 0 hits in dist/ (WARNING 5).
- Pre-checkpoint bundle gates 6/6 PASS.
- Architectural integrity preserved (no IndexedDB persistence; no chrome.storage migration; src/offscreen/recorder.ts:91 segments array unchanged; grep gate enforces).
- vitest baseline flips 183 -> 184 (prior 183 + exactly 1 new `test(...)` block — the Tier-2 filename-leak gate added in Task 1 Step 6).
- Plan 04-04 forensic artifacts (`stopServiceWorker` helper + spike script) repurposed as PASSING regression tests under valid methodology.
- iter-2 BLOCKER 1 + BLOCKER 2 + WARNINGs 1-5 + advisories 1-3 all addressed per checker iter-1 remediation guidance.
</success_criteria>
<output>
After completion, create `.planning/phases/04-harden-clean-up-optional/04-08-SUMMARY.md` capturing:
- **Methodology reframe rationale** — verbatim cite of debug session-2 verdict (REFUTED-architecture; canvas-captureStream issue); explicit rejection of the previously-proposed IndexedDB persistence plan-fix with the empirical reasoning (the spike would STILL produce 8505 bytes after IDB lands because the failure is in the fake stream, not in segment persistence).
- **iter-2 revision summary** — cite checker iter-1 BLOCKER 1 (Vite asset-emission path) + BLOCKER 2 (eager-install contract) resolutions; explain SYNC install + LAZY first-frame contract; document the explicit WAR entry decision (production inert / test authorized); enumerate WARNING resolutions (autoplay fallback; displaySurface compat sub-gate; spike probe-value grep gates; ROADMAP edit pre-spec; Tier-2 filename-leak gate).
- **Bundled WebM fixture decision** — copy from `tests/fixtures/last_30sec.webm` vs regenerate via ffmpeg; size + codec + duration evidence; license/provenance attestation (CC0-equivalent project-owned internal capture); dual-location note (original fixture remains in place at tests/fixtures/).
- **installFakeDisplayMedia diff summary** — before (canvas.captureStream + RAF + setInterval; sync but throttled) vs after (HTMLVideoElement + sync install + lazy first-frame closure; sync signature + async first-call wait); preserved invariants (displaySurface monkey-patch, A23 capture, idempotency, 6 bridge ops all sync); explicit verification that the function did NOT become async.
- **manifest.json WAR entry decision** — iter-2 BLOCKER 1 rationale; production inert + test authorized; alternative options considered (Option A probe-then-decide; Option C blob URL from ?raw); chosen path = explicit pre-emptive entry.
- **displaySurface compatibility evidence** — WARNING 2 sub-gate result OR spike re-run high-latency catch evidence.
- **Spike re-run evidence** — videoSize before (8505) vs after (>100_000; typical 1-3 MB); segment-count probe values (POST-PRIME=0, PRE-KILL≥3, POST-KILL≥3); elapsed time (~308s baseline preserved); SPIKE OUTCOME: PASSED.
- **A33 land evidence** — driveA33 (4-arg signature); 3-file lockstep diff (extension-page-harness.ts unchanged per Option B; harness-page-driver.ts +driveA33; harness.test.ts +import +wrapped +array push); FORBIDDEN_HOOK_STRINGS at 12 entries (unchanged); Tier-2 gate added; pre-checkpoint gates 6/6 PASS.
- **UAT before/after** — 33/33 -> 34/34 GREEN; skip-mode wall-clock ~95s; full-mode wall-clock ~6.5 min.
- **ROADMAP SC #1 closure** — flipped OPEN -> CLOSED with Plan 04-08 cite + 2026-05-22 date; exact ROADMAP.md edit pre/post snapshots; grep gate verifications.
- **Plan 04-04 cross-reference** — debug session-2 verdict honored; spike-FAILED forensic artifacts (`stopServiceWorker` helper + `spike-a33-sw-persistence.ts`) repurposed as PASSING regression tests.
- **Architectural integrity statement** — `let segments: Blob[] = []` at src/offscreen/recorder.ts:91 is UNCHANGED and canonically correct per debug session-2 segment-count probe evidence; grep gate enforces.
- **Commit refs** — Task 1 atomic commit (methodology fix + WAR entry + Tier-2 gate) + Task 2 atomic commit (A33 land + SUMMARY + STATE/ROADMAP markers).
- **Next plan handoff** — Plan 04-07 (closure aggregator) can now reference ROADMAP SC #1 as GREEN; v1 milestone close prep unblocked from the SW persistence side.
</output>