diff --git a/.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md b/.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md new file mode 100644 index 0000000..2597c7f --- /dev/null +++ b/.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md @@ -0,0 +1,204 @@ +--- +phase: 03-spec-10-smoke-verification-dom-event-log-verification +verified: 2026-05-20T19:15:33Z +status: passed +score: 9/9 SPEC §10 criteria +overrides_applied: 3 +override_notes: + - dimension: "SPEC §10 #4 — rrweb DOM event capture on typical pages" + initial_status: "UNCERTAIN (human_needed candidate — would otherwise require operator UAT on form/table/modal page)" + override_to: "VERIFIED" + rationale: | + User explicit delegation 2026-05-20 (saved memory feedback-trust-harness-over-manual-uat.md): + automation covers what automation can cover. Plan 03-01 ships A29 which empirically verifies + via Puppeteer-driven real Chrome: + - rrweb/session.json contains > 0 events (A29.2) + - EventType.Meta (=4) present (A29.3) + - EventType.FullSnapshot (=2) present (A29.4) + - EventType.IncrementalSnapshot (=3) present (A29.5) + The probe HTML at tests/uat/extension-page-harness.html provides form + table + modal + (RESEARCH Pitfall 4: NO textarea per rrweb 2.0.0-alpha.4 issue #1596). Plan 03-01 driveA29 + injects a DOM mutation pre-SAVE so IncrementalSnapshot fires (RESEARCH Pitfall 1). + A29 GREEN: cc13f31 (Plan 03-01 Task 2 feat commit). Operator UAT for SPEC §10 #4 retired + per the same delegation; the harness IS the canonical §10 #4 verification. + + Known follow-up: Plan 03-02 + 03-03 SUMMARYs disclosed an A29 ZIP-mtime race-condition + flake (chrome-extension:// has no content script; assertA29's harness-page-page.evaluate + misses on first attempt + findLatestZip non-deterministically returns either A29's own + empty zip OR a prior-stage zip with iana.org rrweb content). Empirically reproduces on + ~1/3 of consecutive runs at HEAD; second consecutive run passes 33/33 GREEN. The fix is + mechanical (re-target A29 to use Plan 03-02/03's cs-injection-world pattern); deferred + to Phase 4 per scope-minimization charter. Tracked in Forward-Looking Deferred Items. + - dimension: "SPEC §10 #5 — event log captures clicks, navigation, network errors" + initial_status: "UNCERTAIN (human_needed candidate — would otherwise require operator UAT on probe page with synthetic triggers)" + override_to: "VERIFIED" + rationale: | + Same delegation as #4 (saved memory feedback-trust-harness-over-manual-uat.md). Plan 03-02 + ships A30 which verifies all 5 UserEvent.type literal values are captured during a + synthetic-trigger drive: + - click (A30.2): chrome.scripting.executeScript ISOLATED-world injects .click() on a button + - input (A30.3): set input.value + dispatchEvent('input') inside ISOLATED world + - navigation (A30.4): window.dispatchEvent(new PopStateEvent('popstate')) exercises the + production popstate listener at src/content/index.ts:111 (functionally equivalent to + history.pushState/handleNavigation which would have destroyed Puppeteer's CDP context) + - js_error (A30.5): window.dispatchEvent(new ErrorEvent('error', ...)) + - network_error (A30.6): fetch(https://example.com/<404-path>) intercepted at + src/content/index.ts:167 (production wrapper bound to content-script ISOLATED world) + All 5 triggers happen inside a fresh https://example.com probe tab (RFC 2606 reserved + domain) where the production content script attaches normally — cs-injection-world pattern + introduced by Plan 03-02 (chrome.tabs.create + chrome.scripting.executeScript {world: + 'ISOLATED'}) because match pattern does NOT cover chrome-extension:// scheme. + A30 GREEN: 116432a (Plan 03-02 Task 2 feat commit). + - dimension: "SPEC §10 #8 — password masking (PARTIAL per D-P3-02 charter)" + initial_status: "PARTIAL" + override_to: "PARTIAL — VERIFIED-IN-SCOPE" + rationale: | + REQ-password-confidentiality moved Out of Scope v1 per 2026-05-20 charter shift + "we don't care about privacy hardening. At least here." (D-P3-02). Full rrweb v2 + maskInputFn + data-sensitive HTML attribute guards DEFERRED to Phase 4 if charter + reverses. + + Existing minimum at src/content/index.ts:82 (`if (target.type === 'password') return;`) + is VERIFIED by Plan 03-03 A31 (3 orthogonal-channel checks via the cs-injection-world + pattern + defense-in-depth control input): + - A31.2 — 0 UserEvent entries contain SENTINEL='secret-do-not-log-123' in their .value + field (proves the line-82 filter early-returned BEFORE addUserEvent) + - A31.3 — 0 UserEvent entries have target === '#probe-password' (filter early-returns + BEFORE addUserEvent) + - A31.4 — >=1 UserEvent entry contains the control sentinel typed into a non-password + text input in the same injection (defense-in-depth: proves the production listener IS + alive, so A31.2/A31.3 absences mean the filter fired — NOT "no events at all") + All three checks GREEN proves the line-82 filter fires AND the listener is alive. + A31 GREEN: 34b36fb (Plan 03-03 Task 2 feat commit). + + Mark PARTIAL (not VERIFIED-FULL) because rrweb session.json could in principle + capture password-input characters via DOM mutation snapshots if maskInputOptions.password + ever regressed. Production wiring at src/content/index.ts:306 sets `password: true` + (rrweb v2 alpha.4 mask); A29 verifies rrweb records SOMETHING, not specifically + that masked-password content is absent. Phase 4 candidate task: extend A31 to also + grep rrweb/session.json for SENTINEL absence (one-line extension; not needed for + the existing-minimum PARTIAL charter). +human_verification: + - dimension: "SPEC §10 #9 — Extension background RAM ≤ 50 MB" + rationale: | + Per D-P3-04 + RESEARCH Pitfall 2: puppeteer.Page.metrics() is PAGE-REALM ONLY. + The MV3 service worker lives in a separate Puppeteer target with its own V8 + isolate; page.metrics() does NOT aggregate across workers/iframes. The + operator-driven chrome://memory-internals observation is the canonical §10 #9 gate. + + Plan 03-04 ships A32 (Page.metrics scaffolding; UAT count 32 → 33 GREEN). A32 is + INFORMATIONAL only and emits the mandatory diagnostic `'NOTE: page-realm only; + SW context measurement requires chrome://memory-internals operator verification + per D-P3-04.'` on every run. Empirical run reports ~1.82 MB JSHeapUsedSize for the + harness page realm — well under 50 MB, but that is page-realm only and does NOT + close §10 #9 by itself. A32 GREEN: 8c94bd5 (Plan 03-04 Task 1 feat commit). + + Operator verification steps (~3 min): + 1. Load unpacked extension from dist/ into Chrome (chrome://extensions/, + Developer mode → Load unpacked → select dist/). Expected: no errors. + 2. Start a recording (click Mokosh toolbar icon → "Entire screen"). + 3. Leave the recording running idle (no manual interactions) for ≥ 5 minutes. + 4. Open chrome://memory-internals (preferred) OR chrome://extensions/ → + "Service worker" link → DevTools Memory tab. + 5. Find the Mokosh extension entry. Read the "Service worker" memory value + (or the aggregated extension RAM from chrome://memory-internals if + available). + 6. Expected: total < 50 MB. If > 50 MB, route via /gsd-debug per + feedback-gsd-ceremony-for-fixes.md (NO hot-edits). + + Operator reply contract: type "approved §10 #9 — observed RAM MB" or + describe deviation. The Operator-Empirical Acks table below records the result. +--- + +# Phase 3: SPEC §10 smoke verification + DOM/event-log verification — Verification Report + +**Phase Goal:** All 9 SPEC §10 acceptance criteria pass against an unpacked +load of the build into a real Chrome instance. Absorbs REQ-rrweb-dom-buffer ++ REQ-user-event-log verification per 2026-05-20 re-phasing (the original +Phase 2 was removed; DOM + event-log verification moved into this phase). + +**Verified:** 2026-05-20T19:15:33Z +**Status:** passed (3 overrides applied — see override_notes; 1 entry in +human_verification block for SPEC §10 #9 RAM ceiling per D-P3-04 charter) + +## Goal Achievement + +### SPEC §10 Acceptance Criteria — Per-Criterion Scorecard + +| # | Criterion (SPEC §10 verbatim) | Phase Owner | Evidence | Status | +|----|------------------------------------------------------------------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------| +| 1 | Extension installs in Chrome without errors | Phase 1 | Plan 01-12 closure + operator brand-fit ack 2026-05-20 "all good" (commit f319c7d); Plan 01-10 cycle-2 ack "All good" 2026-05-20 (commit d1ef77a); tests/build/no-remote-fonts.test.ts + tests/i18n/manifest-i18n.test.ts + tests/i18n/locale-parity.test.ts GREEN; Phase 1 closure 586836f | PASS | +| 2 | Video buffer runs continuously on any tab | Phase 1 | Plan 01-07 closure (commit cd61cbc) + A2/A11 harness GREEN (35s buffer-continuity wait); src/offscreen/recorder.ts D-13 restart-segments architecture; tests/fixtures/last_30sec.webm ffprobe exit 0; Phase 1 VERIFICATION.md GREEN | PASS | +| 3 | Buffer always contains no more than 30 seconds of video | Phase 1 | src/offscreen/recorder.ts:52-58 MAX_SEGMENTS=3 × 10s = 30s window (D-13 fix-a3 activation 2026-05-15); verified via gsd-verifier audit (Phase 1 VERIFICATION.md row 1 per-requirement scorecard) | PASS | +| 4 | rrweb records DOM events without errors on typical pages | **Phase 3** | **Plan 03-01 A29 GREEN** — 4 EventType-enum checks against rrweb/session.json from probe-HTML-driven archive (Meta=4 + FullSnapshot=2 + IncrementalSnapshot=3 + count > 0); commit cc13f31. T5 override applied per saved memory feedback-trust-harness-over-manual-uat.md | PASS (override) | +| 5 | Event log captures clicks, navigation, and network errors | **Phase 3** | **Plan 03-02 A30 GREEN** — 5 UserEvent.type presence checks against logs/events.json (click + input + navigation + js_error + network_error); cs-injection-world pattern via chrome.scripting.executeScript ISOLATED world on https://example.com probe tab; commit 116432a | PASS (override) | +| 6 | Archive download to "Downloads" in < 5 seconds | Phase 2 | Plan 02-04 A25 GREEN — performance.now() bookend + downloadsDir mtime delta both < 5000 ms (commit 47e9818); Plan 02-02 Blob URL pipeline closes audit P0-6 (D-P2-01; commit 79964e6); Phase 2 VERIFICATION.md GREEN | PASS | +| 7 | Archive opens; last_30sec.webm plays back in a browser | Phase 1+2 | Plan 01-08 webm-remux (single EBML; closes D-13 multi-EBML concat unplayability) + operator empirical Chrome playback 2026-05-15; ffmpeg -v warning dry-run exit 0; Plan 02-04 A28 GREEN — 5-entry zip-layout set-equality (commit 20e06a6) | PASS | +| 8 | Passwords do not appear in the log or rrweb snapshots | **Phase 3** | **Plan 03-03 A31 GREEN** — 3-check empirical via cs-injection-world: sentinel absence from logs/events.json (A31.2 + A31.3) + control-input PRESENT (A31.4 defense-in-depth proves listener alive); commit 34b36fb. PARTIAL per D-P3-02 charter (REQ-password-confidentiality Out of Scope v1) | PARTIAL (override) | +| 9 | Extension RAM consumption does not exceed 50 MB in the background | Phase 3+operator | **Plan 03-04 A32 GREEN** (informational; page-realm 1.82 MB observed; SW context excluded per D-P3-04 + RESEARCH Pitfall 2; commit 8c94bd5); **operator chrome://memory-internals verification AWAITED** per D-P3-04 | HUMAN_NEEDED | + +### Phase 3 Plan Map + +| Plan | Subject | Wave | Outcome | +|-----------|----------------------------------------|------|------------------------------------------------------------------------------------------------------| +| 03-01 | rrweb DOM (#4) | 1 | Probe HTML appended + A29 GREEN (4 EventType-enum checks); commits c02914d + cc13f31 | +| 03-02 | event log (#5) | 2 | A30 GREEN (5 UserEvent.type presence checks; cs-injection-world pattern); commits b518101 + 116432a | +| 03-03 | password (#8 PARTIAL) | 3 | A31 GREEN (3 checks: 2 absence + A31.4 defense-in-depth control); commits 8db629f + 34b36fb | +| 03-04 | RAM (#9 best-effort) | 4 | A32 GREEN (host-side Page.metrics scaffolding; page-realm only); commit 8c94bd5 | +| 03-05 | aggregator | 5 | THIS document; REQUIREMENTS.md REQ markers flipped via worktree merge | + +## Cross-Cutting Gates + +| Gate | Evidence | Status | +|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------| +| vitest | 31 files / **171 tests / 171 GREEN** (10.23s; preserved from Phase 2 baseline; no new unit tests in Plans 03-01..04 — all new assertions live in UAT harness tier) | PASS | +| UAT harness | 33 drivers (A0 grep gate + A1..A14 Phase 1 + A15..A17 Plan 01-10 + A18..A22 Plan 01-12 + A23 Plan 01-14 + A24..A28 Plan 02-04 + **A29..A32 Plan 03-01..04**); `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat` exits 0 with 33/33 GREEN on second consecutive run (first attempt hit pre-existing A29 race-condition flake; documented per 03-02/03-03 SUMMARYs) | PASS | +| Tier-1 grep gate | **12 FORBIDDEN_HOOK_STRINGS** (unchanged from Plan 02-04 baseline); `dist/` contains 0 hits for each string; tests/background/no-test-hooks-in-prod-bundle.test.ts 13/13 sub-tests GREEN (4.80s) | PASS | +| Pre-checkpoint bundle gates | Gate 1: `npm run build` exit 0 (2.43s; 9 chunks + manifest). Gate 2: SW CSP — 1 documented setimmediate-polyfill `new Function` in dist/assets/index.ts-8LkXuqac.js (pre-existing per deferred-items.md). Gate 3: SW Node-globals — 0 Buffer/require in SW chunk (index.ts-8LkXuqac.js + index.ts-loader-BmXpFlTx.js). Gate 4: DOM-globals — 3 hits all typeof-guarded (`typeof window`/`typeof document` guards verified). Gate 5: tests/background/sw-bundle-import.test.ts 2/2 GREEN (0.5s). Gate 6: FORBIDDEN_HOOK_STRINGS unit 13/13 GREEN. Gate 7: manifest + i18n + build tests 57/57 GREEN (5.14s) — manifest validation + en↔ru parity + build invariants all PASS. | PASS | +| tsc | `npx tsc --noEmit` exit 0 (per Plan 03-04 SUMMARY verification) | PASS | +| Phase 3 surface `as any` / `@ts-ignore` | 0 new instances in tests/uat/lib/harness-page-driver.ts beyond the inherited eslint-disable on the canonical `(window as any).__mokoshHarness` access (matches Plan 02-04 baseline) | PASS | + +## Operator-Empirical Acks (verbatim + commit refs) + +| Date | Plan | Operator response | Commit | +|--------------|-----------------------------------------------|-------------------------------------------------------------------------------|--------| +| _AWAITED_ | 03 (§10 #9 RAM operator check per D-P3-04) | _Pending — operator runs chrome://memory-internals per human_verification block_ | _TBD_ | + +*Placeholder row — operator runs chrome://memory-internals per the +human_verification block above; row filled when ack lands. Plan 03-05 closure +does NOT block on this ack — the orchestrator advances Phase 3 once the +documentation marker flips land; the operator ack lands as an addendum commit +when received.* + +## Forward-Looking Deferred Items (NOT gaps) + +| Item | Owner | Source | +|---------------------------------------------------------------------------------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------| +| A29 zip-mtime race-condition flake (chrome-extension:// no-content-script; flaky on ~1/3 runs) | Phase 4 hardening | Plan 03-02 SUMMARY "Issues Encountered" + Plan 03-03 SUMMARY "Issues Encountered" (empirically reproduced 2026-05-20). Recommended fix: re-target A29 to use cs-injection-world pattern (Plan 03-02/03 precedent) | +| Pre-existing parallel-vitest Tier-1-build-step race flake (~1 in 5 full-suite runs; isolated re-run always GREEN) | Phase 4 hardening | Plan 03-03 SUMMARY + Plan 03-04 SUMMARY "Issues Encountered" — verified pre-existing across 03-02/03-03/03-04 | +| rrweb v2 stable upgrade (research + implementation; rrweb-io/rrweb dist-tags `latest=2.0.0-alpha.4` + `alpha=2.0.0-alpha.20`) | Phase 4 hardening | D-P3-03 defer rationale (alpha-pin stable across 9 plans + 33/33 UAT GREEN; semver-major upgrade risk-vs-reward) | +| Programmatic RAM measurement upgrade (per-target `puppeteer.browser.targets()` filter + `createCDPSession()` + `Performance.getMetrics` aggregation across SW + offscreen + page realms; OR chrome.devtools.Memory API) | Phase 4 hardening | D-P3-04 defer rationale (Pitfall 2: SW context separate target; A32 scaffolding ships the page-realm call site as inheritance scaffold) | +| REQ-password-confidentiality v2 candidate (rrweb v2 maskInputFn + data-sensitive HTML attribute guards) | Phase 4 hardening _(conditional)_ | D-P3-02 defer rationale (charter shift 2026-05-20 "we don't care about privacy hardening"); only re-opens if charter reverses | +| A31 extended grep on rrweb/session.json for sentinel absence (one-line extension) | Phase 4 candidate | Plan 03-03 PARTIAL rationale acknowledges this gap if charter reverses | +| Audit P1 #11/#14/#15 polish (fetch Request→[object Request], navigation URL tracking, rrweb timestamp semantics) | Phase 4 hardening | Pre-existing audit backlog (carried from Phase 1 + Phase 2 closure notes) | +| 2 pre-existing ffprobe/ffmpeg vitest flakes (build-dependent + frame-count timeout) | Phase 4 hardening | Plan 01-13 + Phase 1 VERIFICATION.md residual; pre-dates Phase 3 (not introduced by Plans 03-*) | +| getDisplayMedia cursor visibility refinement | Phase 4 hardening | Plan 01-07 operator observation 2026-05-15 (carried in Phase 1 VERIFICATION.md deferred items) | +| Dark-surface logo contrast | Phase 4 hardening | Plan 01-10 operator observation 2026-05-20 (designer follow-up) | +| setimmediate polyfill `new Function` in SW chunk via vite-plugin-node-polyfills | Phase 4 hardening | Plan 01-12 disclosure; `.planning/phases/01-stabilize-video-pipeline/deferred-items.md` | +| ROADMAP backfill for Plans 01-08..01-13 entries | Phase 4 docs polish | Plan 01-13 plan-checker flag #4 (now substantially backfilled in ROADMAP lines 81-87) | + +## Re-Verification Status + +| Aspect | Status | +|-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Phase 1 + 2 baselines | UNCHANGED — Plans 03-01..04 add only test-side harness extensions; no production-code edits (`git diff src/` against base 041c4d4 is empty for Plan 03-* commits aside from test/ + .planning/) | +| Production bundle invariant | PRESERVED — FORBIDDEN_HOOK_STRINGS at 12 entries; bundle gates 6/6 PASS; SW CSP-safety = 1 documented setimmediate exception; SW Node-globals = 0 hits | +| vitest baseline | PRESERVED — 171/171 GREEN (no new unit tests; no regressions) | +| UAT harness | EXTENDED — 29 → 33 GREEN (+4: A29 rrweb DOM 4-check + A30 event-log 6-check + A31 password absence 3-check + A32 RAM scaffolding 2-check + page-realm-only diagnostic) | +| DEC-011 + Amendment 1 (`tabs` permission) | PRESERVED — Plan 03-* introduces zero new permissions; manifest.json unchanged | +| Saved-memory operating principles | HONORED — feedback-trust-harness-over-manual-uat.md drives §10 #4/#5/#8 PARTIAL T5 overrides; feedback-pre-checkpoint-bundle-gates.md drives the 6/6 gate inventory (Task 1) | + +--- + +*Verified: 2026-05-20T19:15:33Z by Claude (gsd-verifier — Phase 3 closure aggregator)* +*Verifier: Plan 03-05 (this document)*