iter-2 revision of Plan 04-08 (video-file MediaStream methodology reframe)
addressing the 2 BLOCKERs + 5 WARNINGs + 3 advisories from plan-checker
iter-1 (commit 051813e, .planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md).
BLOCKER 1 (Vite ?url asset emission for >=1 MB WebM): pre-decide the
bundling strategy. The 1.9 MB WebM is three orders of magnitude above
Vite's assetsInlineLimit (4096); it follows the extracted-asset path
(dist-test/assets/<hash>.webm), not the data-URI-inline path the
Plan 01-10 SVG precedent uses. The @crxjs/vite-plugin auto-WAR
behavior for extracted media assets in offscreen-document context is
empirically untested in this codebase. Resolution: add an explicit
web_accessible_resources entry for assets/*.webm in manifest.json
alongside the existing src/welcome/welcome.html entry. Production
dist/ has zero *.webm assets so the entry is inert; test dist-test/
has the hashed asset and the entry authorizes chrome-extension://<id>/
assets/<hash>.webm URL access from the offscreen document context.
No executor improvisation; the bundling strategy is locked-in BEFORE
Task 1 begins.
BLOCKER 2 (installFakeDisplayMedia async conversion breaks eager-install
contract): preserve the SYNCHRONOUS function signature. The existing
eager call at src/test-hooks/offscreen-hooks.ts:528-537 + the top-
level await at src/offscreen/recorder.ts:46-48 establish a contract
that navigator.mediaDevices.getDisplayMedia is monkey-patched BEFORE
recorder.bootstrap runs. Converting installFakeDisplayMedia() to
async would create a race window where recorder.startRecording calls
the REAL getDisplayMedia (Chrome screen-share picker hangs in
headless). Resolution: SYNC install (videoEl creation + DOM append +
monkey-patch assignment) + LAZY first-frame closure (await readyState
HAVE_FUTURE_DATA + .play() deferred INTO fakeGetDisplayMedia body).
First getDisplayMedia call may block ~50-500ms while video decodes;
subsequent calls observe the resolved readiness Promise + proceed
immediately. Bridge handler + eager-install try/catch remain sync.
WARNING 1 (autoplay reliability): explicit error class identifier
('autoplay-blocked or codec-unsupported in headless context') in the
.play() reject path; spike surface root cause instead of mysterious
0-frames.
WARNING 2 (patchDisplaySurface compatibility): new sub-gate in Task 1
verify that mints a stream + asserts track.getSettings().displaySurface
=== 'monitor'. Optional executor implementation as a --check-display-
surface-only mode on the spike script; spike re-run is the fallback
high-latency catch.
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 per
debug session-2 baseline.
WARNING 4 (ROADMAP.md edit): pre-specified exact pre-edit string +
replacement + grep gate (CLOSED via Plan 04-08 must appear; STATUS
2026-05-21: OPEN must disappear).
WARNING 5 (synthetic-display-source filename leak): new Tier-2 sub-
invariant in tests/background/no-test-hooks-in-prod-bundle.test.ts;
catches accidental test-hook inlining into production chunk. Tier-1
inventory at 12 entries unchanged.
advisory 1: commit message corrected to reference Task 1 + Task 2
only (not Task 3, which doesn't exist).
advisory 2: src/offscreen/recorder.ts:91 segments invariant added as
grep gate in Task 1 verify block.
advisory 3: dual-location fixture note added to Task 1 Step 1 (the
original tests/fixtures/last_30sec.webm remains in place; the new
tests/uat/fixtures/synthetic-display-source.webm is a SECOND copy
under the UAT subtree).
Plan validates via gsd-sdk frontmatter.validate --schema plan (valid:
true, no missing fields) AND gsd-sdk verify.plan-structure (valid:
true, 0 errors, 0 warnings, 2 tasks with full 4-element shapes).
files_modified updated to include tests/background/no-test-hooks-in-
prod-bundle.test.ts (Tier-2 gate location).
Iter-2 architectural thesis unchanged: HTMLVideoElement.captureStream
bypasses the canvas-throttling root cause per debug session-2 verdict.
The revision is methodology-tightening, not re-architecture.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 KiB
phase, slug, plan, type, wave, depends_on, files_modified, autonomous, requirements, tags, user_setup, revision_history, must_haves
| phase | slug | plan | type | wave | depends_on | files_modified | autonomous | requirements | tags | user_setup | revision_history | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04 | harden-clean-up-optional | 08 | auto | 5.5 |
|
|
true |
|
|
|
- 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. - Step C variant (SPIKE_SKIP_SW_KILL=1; no worker.close()): IDENTICAL 8505-byte failure — Puppeteer CDP
worker.close()is NOT the cause. - 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
?urlimport emits the 1.9 MB WebM asdist-test/assets/<hash>.webm(extracted-asset path; not inlined because the size is three orders of magnitude aboveassetsInlineLimit: 4096). The@crxjs/vite-pluginauto-WAR behavior for extracted media assets in offscreen-document context is empirically untested in this codebase. Pre-emptive remediation: add an explicitweb_accessible_resourcesentry forassets/*.webminmanifest.json. Productiondist/has zero*.webmassets so the entry is inert there (no production attack surface); testdist-test/has the hashed asset and the entry authorizes thechrome-extension://<id>/assets/<hash>.webmURL 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 tonavigator.mediaDevices.getDisplayMediaall execute synchronously. The first-frame readiness await (canplay +.play()) is deferred INTO the fakeGetDisplayMedia closure (lazy first-frame pattern). The first timegetDisplayMedia()is called (which happens insiderecorder.startRecordingAFTER recorder.ts:46-48 top-level await resolves and AFTER recorder.bootstrap), the closure awaitsvideoEl.readyState >= HAVE_FUTURE_DATAif not already ready, then callsvideoEl.captureStream(30), appliespatchDisplaySurface, and returns the MediaStream. SubsequentgetDisplayMediacalls 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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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.mdSource 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
From src/test-hooks/offscreen-hooks.ts:139-264 (CURRENT installFakeDisplayMedia — the methodology failure locus, SYNCHRONOUS):
// 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):
// 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):
// 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):
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):
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):
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):
"web_accessible_resources": [
{
"resources": ["src/welcome/welcome.html"],
"matches": ["<all_urls>"]
}
],
Plan 04-08 iter-2 BLOCKER 1 fix — append a second resources entry:
"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):
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):
// 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):
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):
// 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.webmis 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):
// 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.
Step 2 — Add ambient module declaration for *.webm?url.
- Edit
globals.d.ts. After the existingdeclare module '*.svg?url' { ... }block at lines 34-37, append:// 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):
// 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:
// 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 = trueearly assignment (matches existing contract).- The
lastGetDisplayMediaConstraintscapture (A23 contract). - The
patchDisplaySurface(stream)helper (defined inside the function as in current source; survives the rewrite). - The
mintStream()factory pattern (fresh MediaStream pergetDisplayMediacall). - The fakeGetDisplayMedia closure shape (
asynccallable; 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):
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.
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>):
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:
// 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:
// 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 theweb_accessible_resourcesarray to append a second entry for the WebM assets. Final array (with the existing welcome entry preserved):"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*.webmassets (verified byfind dist -name '*.webm' | wc -lreturning 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 atdist-test/assets/<hash>.webm(verified byfind dist-test -name '*.webm' | wc -lreturning ≥1 post-test-build); the entry authorizeschrome-extension://<id>/assets/<hash>.webmURL access from the offscreen document context. - The
@crxjs/vite-pluginauto-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.jsonshows 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/*.webmURLs to all origins. Production has zero*.webmassets so the exposure surface is null in production. Test bundle is sandboxed in Puppeteer; no user-facing impact.
- Production
Step 5 — Vite config verification (no edits needed; locked in iter-2).
vite.config.ts: production build —?urlimport is reachable only fromsrc/test-hooks/offscreen-hooks.ts, which is gated by__MOKOSH_UAT__insrc/offscreen/recorder.ts:46-48and 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?urlimport 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:testin Step 7):dist-test/assets/<hash>.webmexists after test build;dist/has zero*.webmfiles 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:// 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 (skip the binary asset extensions in IGNORED_EXTENSIONS). // Grep for the literal string 'synthetic-display-source'. Expected: 0 hits. const distFiles = collectDistFiles(); // existing helper from the Tier-1 test const offendingFiles: string[] = []; for (const filePath of distFiles) { const content = readFileSync(filePath, 'utf-8'); if (content.includes('synthetic-display-source')) { offendingFiles.push(filePath); } } expect(offendingFiles).toEqual([]); }); - Note: the executor should READ the existing test file first to identify:
- The exact name of the helper that walks dist/ files (the snippet above calls it
collectDistFiles— verify the actual name and adapt). - The exact location to insert the new test block (after the Tier-1 inventory test, before the file's final close).
- The exact import shape (the snippet uses
readFileSync— verify already imported).
- The exact name of the helper that walks dist/ files (the snippet above calls it
Step 7 — Verify TypeScript + production build clean + test build emits asset + Tier-2 gate passes.
npx tsc --noEmitexits 0.npm run buildexits 0 (production build; test-hooks tree-shake verified —find dist -name '*.webm' | wc -lreturns 0 +grep -r "synthetic-display-source" dist/ 2>/dev/null | wc -lreturns 0).npm run build:testexits 0 (test build; ?url import resolves to hashed asset in dist-test/;find dist-test -name '*.webm' | wc -lreturns ≥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.webmreports ≥ 1_000_000 bytes.grep -c "canvas.captureStream\|fakeCanvas\|fakeAnimationHandle\|fakeDrawInterval" src/test-hooks/offscreen-hooks.ts | head -1returns 0 (all canvas symbols excised — filtered to code only viagrep -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.
- Use
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.tsreturns ≥ 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.tsreturns 0 (verifies the function was NOT converted to async).grep -c "await installFakeDisplayMedia" src/test-hooks/offscreen-hooks.tsreturns 0 (verifies the bridge handler + eager-install were NOT converted to await).- Sub-verify gate for displaySurface monkey-patch compatibility (WARNING 2 remediation): create a one-shot inline node script that loads dist-test (via Puppeteer's
enableExtensions) + executes a snippet in the offscreen document that mints a stream + assertsstream.getVideoTracks()[0].getSettings().displaySurface === 'monitor'. If this fails, the productiondisplaySurface !== 'monitor'gate atsrc/offscreen/recorder.ts:294would tear down the stream + throw'wrong-display-surface'— the spike would fail at Step 2 (assertA2 prime), not at Step 7 (videoSize check). Implementation: append to the existingtests/uat/spike-a33-sw-persistence.tsscript a--check-display-surface-onlymode that runs only Steps 1-2 (prime + 5s post-prime probe), invokes__mokoshOffscreenQueryop='get-display-surface', asserts result is 'monitor', exits. Run viaHEADLESS=1 SKIP_PROD_REBUILD=1 npx tsx tests/uat/spike-a33-sw-persistence.ts --check-display-surface-only 2>&1 | tee /tmp/04-08-display-surface-check.logand grep'DISPLAY-SURFACE-CHECK: PASSED'in the log. The full spike re-run (Step 1 of Task 2) is a superset of this check, but running it as a fast sub-gate in Task 1 catches the WARNING 2 risk in <10s of wall-clock instead of waiting 6+ min into the spike.- Alternative if the executor finds the spike-script-augmentation overhead too high: skip the dedicated sub-gate and rely on the spike re-run in Task 2 Step 1 (the displaySurface failure would manifest as 'wrong-display-surface' Error in the offscreen console capture; the spike's gating condition would still fail). The dedicated sub-gate is the LOW-LATENCY path; the spike re-run is the HIGH-LATENCY path; both gate the same invariant.
- Sub-verify gate for segments invariant (advisory 2 remediation):
grep -cE 'let segments: Blob\[\] = \[\];' src/offscreen/recorder.tsreturns 1 (architectural invariant per debug session-2; Plan 04-08 does NOT modify recorder.ts). 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)' <acceptance_criteria>tests/uat/fixtures/synthetic-display-source.webmexists, is VP9, >= 1 MB.tests/fixtures/last_30sec.webmremains in place (NOT moved).globals.d.tscontains thedeclare module '*.webm?url'block.manifest.jsonweb_accessible_resources contains anassets/*.webmresources entry (iter-2 BLOCKER 1 fix).src/test-hooks/offscreen-hooks.tsimportssyntheticDisplaySourceUrlvia Vite?urlsuffix.installFakeDisplayMedia()remains SYNCHRONOUS (return type: void; noasynckeyword; 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 thefakeGetDisplayMediaclosure.fakeGetDisplayMediaclosure awaitsfakeVideoReadyPromiseon each call (first call may block ~50-500ms; subsequent calls fast-path).mintStream()callsvideoEl.captureStream(30)(NOTcanvas.captureStream).uninstallFakeDisplayMedia()is SYNCHRONOUS (return type: void); pauses + removes the video element + nullsfakeVideoReadyPromise.- All 6 existing bridge ops remain functional + remain in their sync
return falseform (the install-fake-display-media op stays sync — does NOT switch toreturn truebecause the install itself stays sync). - The eager-install try/catch at lines 528-537 is UNCHANGED.
npx tsc --noEmitexits 0.npm run buildexits 0; production bundle has zero*.webmfiles; zero references tosynthetic-display-sourcein any dist/ file.npm run build:testexits 0; test bundle resolves the?urlimport; dist-test/assets/.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
lastGetDisplayMediaConstraintscapture 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.tscontains 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>
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.
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)
**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.
- Verify the displaySurface sub-gate from Task 1 Step 7 PASSED. If skipped, the spike re-run in Step 1 below catches it but with 6+ min latency.
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: PASSEDwithvideoSize > 100_000(typical 1-3 MB). - GATING CONDITION for landing A33:
grep -c 'SPIKE OUTCOME: PASSED' /tmp/04-08-spike-rerun.logreturns ≥ 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=Nlines (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,assertA2triggersrecorder.startRecordingwhich callsnavigator.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. - IF Task 1 Sub-step 3g's sub-gate (
--check-display-surface-onlymode) was implemented, that mode is preserved as a forensic helper. Otherwise the spike script is unchanged.
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
driveA32function definition (the most-recent Phase 3 addition). - Verify the
stopServiceWorkerhelper (lines 68-80) is already in scope — it is, per Plan 04-04 commit3726eee. - Verify
findLatestZipis in scope (exported at line 1434) — it is, per Plan 04-04 commit3726eee. - Verify
JSZip+readFileSyncare 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,:
// 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):
// 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):
// 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 --noEmitexits 0.npm run build:testexits 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— expect34/34GREEN 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— expect34/34GREEN 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:
Confirm zero new entries added (no new MOKOSH_UAT-gated symbols).
# 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) - 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 0grep -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 preserved:
npm test 2>&1 | tail -5— expect 184/184 GREEN (was 183 baseline + 1 new Tier-2 test from Plan 04-08), 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)
- installFakeDisplayMedia diff summary (canvas -> HTMLVideoElement; SYNC install + LAZY first-frame; 6 bridge ops preserved sync; A23 capture preserved; eager-install contract preserved)
- manifest.json WAR entry decision (BLOCKER 1 rationale: pre-emptive explicit entry; production inert; test bundle authorized)
- displaySurface compatibility evidence (sub-gate result; WARNING 2 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 withspike-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.mdentry 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):
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. 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}' <acceptance_criteria>- Spike re-run output contains
SPIKE OUTCOME: PASSEDANDvideoSize > 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=Xwhere X ≥ 3 (5-min idle drove rotation cadence to MAX_SEGMENTS). - WARNING 3 (iter-2): Spike log contains
SPIKE PROBE [POST-KILL]: segments.length=Xwhere X ≥ 3 (architecture preserved across SW kill per debug session-2). npx tsc --noEmitexits 0.npm run build:testexits 0.tests/uat/lib/harness-page-driver.tscontainsdriveA33function with the 4-arg signature(page, browser, extensionId, downloadsDir) => Promise<AssertionRecord>.tests/uat/lib/harness-page-driver.tsdriveA33 dispatches SAVE_ARCHIVE inline viachrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, ...)— verifygrep -c "type: 'SAVE_ARCHIVE'" tests/uat/lib/harness-page-driver.tsreturns ≥ 1.tests/uat/harness.test.tsimportsdriveA33(1 line; grep -c returns ≥ 4 including comment).tests/uat/harness.test.tsdefinesdriveA33Wrappedconst.tests/uat/harness.test.tsdrivers-array contains an{ name: 'A33', drive: ... }entry withSKIP_LONG_UATenv-gate.- Skip-mode UAT:
HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uatreports 34/34 GREEN in ~95s. - Full-mode UAT:
HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uatreports 34/34 GREEN in ~6.5 min; A33.1 + A33.2 + A33.3 all PASS. dispatchSaveArchivesymbol 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.tsreturns ≥ 1; the test PASSES undernpm 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 184 baseline preserved (183 prior + 1 new Tier-2 test) — or 181/184 with the 3 documented pre-existing flakes; pass in isolation.
- WARNING 4 (iter-2):
.planning/ROADMAP.mdcontains'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.mdDecisions list contains a Plan 04-08 entry citing ROADMAP SC #1 CLOSED..planning/phases/04-harden-clean-up-optional/04-08-SUMMARY.mdexists 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> 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).
- Spike re-run output contains
<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' |
| 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> |
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.
<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.fakeGetDisplayMediaclosure performs the lazy first-frame wait — first call awaitsfakeVideoReadyPromise; subsequent calls observe resolved Promise + proceed immediately.- Ambient module decl for
*.webm?urladded toglobals.d.ts. - Explicit web_accessible_resources entry for
assets/*.webmadded tomanifest.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-sourcereturns 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 184 baseline preserved (183 prior + 1 new Tier-2 test).
- Plan 04-04 forensic artifacts (
stopServiceWorkerhelper + 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>
- 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.webmvs 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 (
stopServiceWorkerhelper +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.