feat(01-13): wave-3A — add get-display-surface bridge op (A3 prereq) + extend Tier-1 grep gate
Scope: prerequisite step for Wave 3A's A3 assertion (displaySurface=monitor
verification). The page→offscreen bridge gains a new op so the harness can
query the active stream's `getSettings().displaySurface` without needing
direct offscreen.evaluate access (impossible by-construction; the only
cross-isolate path is chrome.runtime.sendMessage).
Bridge op contract (`src/test-hooks/offscreen-hooks.ts`):
- Protocol: { type: '__mokoshOffscreenQuery', op: 'get-display-surface' }
- Response: { displaySurface: string|null }
• null when no current stream (recording not active)
• 'monitor' when installFakeDisplayMedia's monkey-patched
getSettings() reports it (production code in
src/offscreen/recorder.ts enforces this same value — tears down
stream + throws 'wrong-display-surface' otherwise).
- Failure: { ok: false, error: <message> } only on getSettings throw.
Tier-1 grep gate extension (`tests/background/no-test-hooks-in-prod-bundle.test.ts`):
- FORBIDDEN_HOOK_STRINGS: 8 → 9 entries.
- Added: 'get-display-surface' (the literal bridge-op string;
matches the production-bundle absence invariant — the offscreen-hooks
module is tree-shaken in production builds by the Vite mode gate in
src/offscreen/recorder.ts top-of-module).
Verification:
- npx tsc: clean
- npm run build: clean (dist/ 4 chunks; no offscreen-hooks artifact)
- npm run build:test: clean (dist-test/ adds offscreen-hooks-DfWtG71P.js, 2.38kB)
- SKIP_BUILD=1 vitest run no-test-hooks-in-prod-bundle.test.ts → 10/10 GREEN
(1 build-sanity + 9 forbidden-string checks; production bundle hook-free)
- SKIP_BUILD=1 vitest run (full) → 93/93 GREEN
(Wave 0+1+2 baseline 92 + 1 from the 9th grep-gate string)
- npx tsx tests/uat/a6.test.ts → A6 5/5 GREEN
(lib-driven path preserved; bridge op addition does not interfere)
Wave 3A continuation: assertA1/A2/A3/A4 land in the next commit which
wires the harness-page surface + driver wrappers + harness.test.ts
orchestrator. This commit is the bridge prerequisite — keeping the
bridge-op extension atomic + the grep-gate extension atomic so the
'production bundle hook-free' invariant is provable BEFORE the page-side
surface lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,10 @@ globalThis.__mokoshTest = {
|
|||||||
// op='install-fake-display-media' → { ok: true } OR { ok: false, error }
|
// op='install-fake-display-media' → { ok: true } OR { ok: false, error }
|
||||||
// op='dispatch-ended' → { ok: true } OR { ok: false, error: 'no stream' }
|
// op='dispatch-ended' → { ok: true } OR { ok: false, error: 'no stream' }
|
||||||
// op='has-stream' → { hasStream: boolean }
|
// op='has-stream' → { hasStream: boolean }
|
||||||
|
// op='get-display-surface' → { displaySurface: string|null } OR { ok: false, error }
|
||||||
|
// — Plan 01-13 Wave 3A A3 contract. Returns the active track's
|
||||||
|
// `getSettings().displaySurface` value (monkey-patched to 'monitor'
|
||||||
|
// by `installFakeDisplayMedia`); returns null when no stream is live.
|
||||||
// Unknown ops respond { ok: false, error: 'unknown-op' }.
|
// Unknown ops respond { ok: false, error: 'unknown-op' }.
|
||||||
//
|
//
|
||||||
// The bridge handler MUST run BEFORE the production offscreen bridge
|
// The bridge handler MUST run BEFORE the production offscreen bridge
|
||||||
@@ -349,6 +353,35 @@ chrome.runtime.onMessage.addListener((rawMessage, _sender, sendResponse) => {
|
|||||||
sendResponse({ hasStream: currentStream !== null });
|
sendResponse({ hasStream: currentStream !== null });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (op === 'get-display-surface') {
|
||||||
|
// Plan 01-13 Wave 3A A3 contract — return the active track's
|
||||||
|
// displaySurface so the harness can verify the offscreen-hooks
|
||||||
|
// monkey-patched getSettings() correctly reports 'monitor'.
|
||||||
|
// Production code in src/offscreen/recorder.ts enforces this same
|
||||||
|
// invariant (tears down + throws 'wrong-display-surface' otherwise),
|
||||||
|
// so if recording is live the value is guaranteed monitor — the
|
||||||
|
// harness asserts === 'monitor' for explicit empirical verification.
|
||||||
|
try {
|
||||||
|
if (currentStream === null) {
|
||||||
|
sendResponse({ displaySurface: null });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const track = currentStream.getVideoTracks()[0];
|
||||||
|
if (track === undefined) {
|
||||||
|
sendResponse({ displaySurface: null });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const settings = track.getSettings();
|
||||||
|
const displaySurface = settings.displaySurface ?? null;
|
||||||
|
sendResponse({ displaySurface });
|
||||||
|
} catch (err) {
|
||||||
|
sendResponse({
|
||||||
|
ok: false,
|
||||||
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
sendResponse({ ok: false, error: 'unknown-op' });
|
sendResponse({ ok: false, error: 'unknown-op' });
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,8 +58,9 @@
|
|||||||
// (replaces Approach-A `simulateUserStop`)
|
// (replaces Approach-A `simulateUserStop`)
|
||||||
// - `getSegmentCount` — Plan 01-11 Task 7 segments-count getter (retained)
|
// - `getSegmentCount` — Plan 01-11 Task 7 segments-count getter (retained)
|
||||||
// - `__mokoshOffscreenQuery` — 01-13 page→offscreen bridge message type
|
// - `__mokoshOffscreenQuery` — 01-13 page→offscreen bridge message type
|
||||||
|
// - `get-display-surface` — 01-13 Wave 3A bridge op string (A3 contract)
|
||||||
//
|
//
|
||||||
// Total: 8 surface strings. Each MUST be absent from EVERY file under
|
// Total: 9 surface strings. Each MUST be absent from EVERY file under
|
||||||
// `dist/` post-build. The list is mirrored by the harness's A0
|
// `dist/` post-build. The list is mirrored by the harness's A0
|
||||||
// assertion (tests/uat/harness.test.ts in Wave 3A) so the same
|
// assertion (tests/uat/harness.test.ts in Wave 3A) so the same
|
||||||
// invariant is enforced at unit-test time (fast, every CI run) AND
|
// invariant is enforced at unit-test time (fast, every CI run) AND
|
||||||
@@ -109,6 +110,7 @@ const FORBIDDEN_HOOK_STRINGS: ReadonlyArray<string> = [
|
|||||||
'dispatchEndedOnTrack',
|
'dispatchEndedOnTrack',
|
||||||
'getSegmentCount',
|
'getSegmentCount',
|
||||||
'__mokoshOffscreenQuery',
|
'__mokoshOffscreenQuery',
|
||||||
|
'get-display-surface',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** How long the build child has to finish (`npm run build` is ~10s).
|
/** How long the build child has to finish (`npm run build` is ~10s).
|
||||||
|
|||||||
Reference in New Issue
Block a user