Files
mokosh/.planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md

24 KiB
Raw Blame History

phase, verified, verifier, goal, status, axes_total, axes_passed, axes_failed, axes_advisory
phase verified verifier goal status axes_total axes_passed axes_failed axes_advisory
01-stabilize-video-pipeline 2026-05-16T09:11:33Z gsd-verifier The video ring buffer captures the most recent 30 s of the active tab's video continuously across tab switches, with a playable WebM header retained — so that on export the assembled `last_30sec.webm` will play. human_needed 10 10 0 2

Phase 1: Stabilize Video Pipeline — Goal-Backward Verification

Phase Goal (verbatim from ROADMAP.md)

The video ring buffer captures the most recent 30 s of the active tab's video continuously across tab switches, with a playable WebM header retained — so that on export the assembled last_30sec.webm will play.

Verdict

DELIVERED — 10 of 10 verification axes PASS against the codebase; phase goal is achieved by the wired implementation, not just by SUMMARY narration. Two ADVISORY notes recorded that do not block phase completion. Three operator-side residue items remain that THIS verifier cannot validate without a Chrome runtime — these MUST be picked up by Phase 4's SPEC §10 smoke pass.

Status is human_needed because the residue items below require a real Chrome operator session (visual picker UX, tab-switch continuity, SW idle survival) — automated codebase verification is exhausted.


Per-Axis Verification Table

# Axis Evidence (file:line or command) Verdict
1 "30 s ring buffer" — constants + rotation src/offscreen/recorder.ts:23-29 (SEGMENT_DURATION_MS=10_000, MAX_SEGMENTS=3, VIDEO_BUFFER_DURATION_MS = 30_000); :86-89 push + shift cap; :352-355 production rotation evict-oldest PASS
2 "Continuous across tab switches" — no re-acquire grep -RIn "onActivated|onUpdated|re-attach|reAttach" src/ returns no matches (exit 1); src/offscreen/recorder.ts:46 comment pins same-MediaStream across rotations; getDisplayMedia is screen/window-scoped per D-01 PASS
3 "Playable WebM" — D-13 per-segment self-contained src/offscreen/recorder.ts:252-302 startNewSegment constructs fresh MediaRecorder per rotation; :349-370 onSegmentStopped finalizes & rotates; tests/offscreen/segment-keyframes.test.ts:195-269 GREEN-pins "each retained segment starts with a keyframe" + 30-s window invariant PASS
4 "D-12 base64 wire transfer" src/shared/binary.ts:42-85 round-trip helpers; src/shared/types.ts:64-68 TransferredVideoSegment.data: string (base64); src/offscreen/recorder.ts:574 await blobToBase64; src/background/index.ts:188 base64ToBlob; tests/offscreen/port-serialization.test.ts 9 tests GREEN PASS
5 "T-1-04 sender-id check both ends" offscreen-side: src/offscreen/recorder.ts:465-467 isFromOwnExtension, :646 enforced on onMessage; SW-side: src/background/index.ts:83-87 rejects port with mismatched sender, :514-517 rejects onMessage with mismatched sender PASS
6 "SW respawn safety" src/background/index.ts:33-35 Promise; :548-552 OFFSCREEN_READY resolves; :576-595 hasDocument() check + early-resolve on detected pre-existing offscreen (CR-03 fix); :608-613 indexedDB.deleteDatabase('VideoRecorderDB') in onInstalled PASS
7 "Manifest aligned" manifest.json:6-13 lists desktopCapture + offscreen; no tabCapture; grep -RIn "tabCapture|chrome.alarms" src/ returns no matches (exit 1); chrome.runtime.connect port replaces alarms keepalive (recorder.ts:606-631 + background/index.ts:78-118) PASS
8 "Fixture validity" tests/fixtures/last_30sec.webm exists (1 633 459 bytes); ffprobe -v error -f matroska -i ... → exit 0, empty stderr; codec=vp9 Profile 0, 1142×1038, bt709 PASS
9 "Acceptance tests aligned" tests/offscreen/webm-playback.test.ts:94-114 decodeDryRunStrict invokes ffmpeg via spawnSync and parses stderr for packet-error + ended-prematurely; npx vitest run shows both empirical gates execute (988 ms + 914 ms) and PASS PASS
10 "Code-review aftermath — no regression" 7 test files / 40 tests all green; tsc exit 0; type-safety grep clean; npm run build exit 0 (60 modules, dist/ produced); sweep commits 08a79a6 (stop-race), 7c91f52 (re-entrance + start-throw + dual-track teardown), 034155b (port-replaced diagnostic) inspected and preserve D-12/D-13 contracts PASS

Score: 10/10 axes verified.


Required Artifacts (3-level check)

Artifact Expected Status Details
src/offscreen/recorder.ts getDisplayMedia + D-13 lifecycle + port + handshake VERIFIED Exists (673 lines, substantive); imported by offscreen/index.html via crxjs; bootstrap() runs on module load; ALL ring-buffer constants, classifier, lifecycle hardening present
src/background/index.ts SW with port-based fetch, T-1-04 sender check, hasDocument check VERIFIED Exists (618 lines); receives port via onConnect; runs chrome.offscreen.hasDocument() on init; CR-03 fix early-resolves offscreenReady on detected pre-existing offscreen
src/shared/binary.ts D-12 base64 ↔ Blob helpers VERIFIED Exists (85 lines); pure, portable (no FileReader); pinned by 9 tests in port-serialization.test.ts
src/shared/types.ts VideoSegment + TransferredVideoSegment wire-format VERIFIED VideoSegment {data: Blob; timestamp} + TransferredVideoSegment {data: string; type; timestamp} both present; Message<T = unknown> post-IN-05
manifest.json desktopCapture + offscreen permissions; NO tabCapture VERIFIED Confirmed line-by-line
tests/offscreen/*.test.ts (7 files) codec-check, handshake, port, port-serialization, segment-keyframes, segment-rotation, webm-playback VERIFIED All 7 present; ring-buffer.test.ts retired (IN-03) leaving 7 file × 40-test surface
tests/fixtures/last_30sec.webm Empirical D-13 regression fixture VERIFIED 1 633 459 bytes; vp9 1142×1038; ffprobe + ffmpeg dry-run both exit 0

From To Via Status Details
offscreen/index.html src/offscreen/recorder.ts <script type="module" src="./recorder.ts"> WIRED Confirmed at src/offscreen/index.html:8; crxjs picks up via rollupOptions.input in vite.config.ts
recorder.ts: encodeAndSendBuffer binary.ts: blobToBase64 import { blobToBase64 } from '../shared/binary' WIRED Imported at recorder.ts:18; called at :574
background/index.ts: getVideoBufferFromOffscreen binary.ts: base64ToBlob import { base64ToBlob, blobToBase64 } from '../shared/binary' WIRED Imported at background/index.ts:2; called at :188; result wrapped in VideoSegment[] returned to saveArchivecreateArchive → archive video/last_30sec.webm entry
recorder.ts: connectPort background/index.ts: onConnect listener chrome.runtime.connect({ name: 'video-keepalive' }) WIRED port name match enforced at background/index.ts:80-82; sender-id at :83-87
recorder.ts: bootstrap → sendMessage background/index.ts: OFFSCREEN_READY case chrome.runtime.sendMessage({ type: 'OFFSCREEN_READY' }) WIRED resolves offscreenReady Promise at background/index.ts:548-552
background/index.ts: startVideoCapture recorder.ts: startRecording chrome.runtime.sendMessage({ type: 'START_RECORDING' }) WIRED recorder.ts dispatches in onMessage switch at :649-654

Data-Flow Trace (Level 4 — Real Data Production)

Artifact Data Variable Source Produces Real Data Status
recorder.ts: segments let segments: Blob[] onSegmentStopped → new Blob(currentChunks) → push driven by MediaRecorder.onstop YES — real ~10 s WebM blobs from MediaRecorder FLOWING
recorder.ts: encodeAndSendBuffer transferred[] await blobToBase64(segment) per segment, sent over port YES — round-trip pinned by tests; empirical fixture proves end-to-end FLOWING
background/index.ts: mergeVideoSegments finalBlob new Blob(sortedSegments.map(s => s.data)) YES — empirical fixture: 1.6 MB VP9 playable file FLOWING
tests/fixtures/last_30sec.webm n/a (empirical fixture) regenerated 2026-05-15 (commit cd61cbc) against D-13 recorder YES — ffprobe exit 0 + ffmpeg dry-run exit 0 + operator-confirmed Chrome playback FLOWING

Behavioral Spot-Checks

Behavior Command Result Status
Vitest suite passes npx vitest run --reporter=dot Test Files 7 passed (7); Tests 40 passed (40); 3.06s PASS
TypeScript type-check passes npx tsc --noEmit exit 0 (no output) PASS
No as any or @ts-ignore in Phase 1 surface grep -RIn "as any|@ts-ignore" src/offscreen/ src/background/index.ts src/shared/ exit 1 (no matches) PASS
No tabCapture / chrome.alarms residue grep -RIn "tabCapture|chrome.alarms" src/ exit 1 (no matches) PASS
No legacy IndexedDB SW plumbing grep -rn "openIndexedDB|VideoRecorderDB" src/ --include='*.ts' Only matches: orphan-cleanup indexedDB.deleteDatabase('VideoRecorderDB') at background/index.ts:609-610 (intentional) PASS
Legacy top-level offscreen/ directory removed ls offscreen/ exit 2 — directory does not exist PASS
Vite inline-plugin removed grep -rn "copy-offscreen|chromeMediaSource" vite.config.ts exit 1 (no matches) PASS
Build produces loadable extension npm run build 60 modules transformed, exit 0; dist/manifest.json, dist/assets/offscreen-*.js, dist/assets/index.ts-*.js (SW) all produced PASS
Fixture decodable by ffprobe (D-12 gate) ffprobe -v error -f matroska -i tests/fixtures/last_30sec.webm exit 0; empty stderr PASS
Fixture passes A3 empirical decode dry-run ffmpeg -nostdin -v warning -i tests/fixtures/last_30sec.webm -f null - exit 0; 0 packet errors; 0 "File ended prematurely"; 298 DTS-monotonicity warnings (expected D-13 trade-off) + 1 "File extends beyond end of segment" (advisory, see below) PASS

Requirements Coverage

Requirement Source Plan Description Status Evidence
REQ-video-ring-buffer Phase 1 (all 7 plans) 30 s in-memory ring buffer via D-13 restart-segments; getDisplayMedia (AMENDED from chrome.tabCapture); vp9 @ 400 000 bps SATISFIED All structural + empirical + wiring evidence above; SPEC §10 #2/#3/#7 functionally green at Phase 1 level

Phase 4 (SPEC §10 smoke) still owns the canonical 9-criterion sweep. Phase 1 only proved #2/#3/#7 against the codebase + fixture; #1 install, #6 < 5 s export latency, #8 password masking, #9 RAM ceiling, and end-to-end Chrome runtime confirmation remain Phase 4 scope. This is by design per REQUIREMENTS.md line 188.


Anti-Patterns Found

File Line Pattern Severity Impact
(none) No TODO/FIXME/XXX/HACK in Phase 1 surface; no as any / @ts-ignore; no console.log-only handlers; no empty => {} rendering paths; no hardcoded empty arrays/objects flowing to user-visible output Clean.

The ffmpeg stderr emits a single File extends beyond end of segment line — this is Matroska-demuxer-level informational, not a decoder error, and is the documented D-13 trade-off (multi-EBML-header concat). Recorded as ADVISORY below, not as an anti-pattern.


Advisory Observations

These do NOT block Phase 1 completion but warrant attention in Phase 4 or Phase 5.

  1. ADV-1: ffmpeg emits one [in#0/matroska,webm] File extends beyond end of segment line. This is the Matroska demuxer noting that the SECOND EBML segment in the concatenated WebM declares a smaller SegmentSize than the remaining bytes (because subsequent segments follow). Chrome's MSE pipeline handles this natively — operator-confirmed playback was clean on 2026-05-15. The webm-playback.test.ts acceptance gate does NOT assert against this signal (only packetErrorCount === 0 and endedPrematurely === false), so it is correctly NOT a failure. Recommend documenting this signal in the Phase 5 cursor-visibility plan's "expected stderr from ffmpeg dry-run" allow-list to prevent future verifiers from being surprised by it.

  2. ADV-2: blobToBase64 per-byte concat is O(n²) for large blobs. WR-06 in REVIEW-FIX.md was pre-approved for Phase 2 deferral with the documented rationale (String.fromCharCode(...subarray) apply-spread argument-length limit ~64 KiB). For the current ~1.6 MB fixture this works fine; if recording quality rises (higher bitrate, longer windows, audio in CAP-01) the encode latency could grow non-linearly. Phase 2 quality-bump review should consider chunked-apply at 32 KiB stride per REVIEW.md's WR-06 recommendation.


Operator-Side Residue (UAT scope)

These behaviors CANNOT be verified by this codebase verifier — they require a real Chrome runtime and an operator. Phase 4 SPEC §10 smoke pass MUST cover them:

  1. OPR-1: getDisplayMedia picker UX appears and grants stream on operator click. Chrome's native screen-share picker dialog cannot be driven from Vitest's Node environment. Verification path: load dist/ unpacked → click extension → observe picker dialog → pick "Entire screen" → confirm "Sharing your screen" indicator appears → SW console shows [Offscreen:Recorder] Stream created. (VALIDATION.md §"Manual-Only Verifications" row 1.)

  2. OPR-2: Continuous recording across tab switches under live getDisplayMedia stream. Codebase verification confirms NO per-tab re-acquisition code exists (no chrome.tabs.onActivated handlers; recorder retains single MediaStream across rotations); this is a SUFFICIENT structural condition. Empirical confirmation that the recording does NOT pause when the operator switches tabs in Chrome requires a live session. Verification path: start recording, switch tabs 3-5 times over 30 s, export, confirm last_30sec.webm covers the full 30 s window. (VALIDATION.md §"Manual-Only Verifications" row 2 analogue, applied to amended D-15 semantics.)

  3. OPR-3: SW idle-unload survival. D-16 + D-17 are structurally satisfied (offscreen owns buffer; long-lived port keeps SW alive; hasDocument() + CR-03 handshake fix on SW respawn). Real MV3 SW lifecycle behavior cannot be driven in Vitest. Verification path: DevTools → Extensions → Service Worker → "Force stop" → wait 60 s → click extension save → confirm exported archive contains video segments from before "Force stop". (VALIDATION.md §"Manual-Only Verifications" row 3.)

These three items are Phase 4's territory per ROADMAP.md (Phase 4 success criteria #2 and #4 in particular). This verifier does NOT classify them as Phase 1 gaps — Phase 1's human_needed status is the correct routing: ship Phase 1 as DELIVERED in code, hand the operator-side checks to Phase 4 smoke.


Gaps Summary

No gaps. Phase 1's goal — "the video ring buffer captures the most recent 30 s of the active tab's video continuously across tab switches, with a playable WebM header retained — so that on export the assembled last_30sec.webm will play" — is achieved by the wired codebase:

  • 30 s buffer: MAX_SEGMENTS=3 × SEGMENT_DURATION_MS=10_000 = 30_000 ms enforced in production (onSegmentStopped:352-355) AND pinned by tests (segment-rotation.test.ts:84-104, "caps at MAX_SEGMENTS — 4th push evicts the oldest", "many pushes never exceed the cap").
  • Continuous across tab switches: Amended D-01/D-15 semantics under getDisplayMedia — no per-tab handlers exist, single MediaStream retained across MediaRecorder rotations (recorder.ts:46 comment + grep evidence).
  • Playable WebM: D-13 restart-segments — each segment self-contained with own EBML header + seed keyframe (startNewSegment:252-302 constructs fresh MediaRecorder per rotation); empirically verified by ffmpeg dry-run exit 0 + 0 packet errors + 0 ended-prematurely.
  • Assembled last_30sec.webm plays: Empirical fixture tests/fixtures/last_30sec.webm (1.6 MB) passes both D-12 (ffprobe) and A3 (ffmpeg) gates, operator-confirmed Chrome playback on 2026-05-15.

The two architectural escalations encountered mid-phase (D-12 binary transfer + A3 keyframe orphan-P-frame) were both anticipated as HIGH-risk in RESEARCH.md and resolved by pre-staged fallbacks (base64 wire format + D-13 restart-segments); both fallbacks are now production code and pinned by tests.


Verified: 2026-05-16T09:11:33Z Verifier: Claude (gsd-verifier)