Plan-checker iter-1 VERIFICATION PASSED with 1 cosmetic WARNING (Dimension 11 Research Resolution: Open Questions section heading lacked (RESOLVED) suffix convention). Fixed inline: heading now reads "## Open Questions (RESOLVED)". .plan-phase-preferences.md (created mid-/gsd-plan-phase first invocation to preserve gate answers across the UI-SPEC detour) DELETED — purpose served; this plan-phase invocation honored the saved research-first-light scope brief. state.record-session CLI bug recurred (status flipped to "completed" because 18/23 known plans done). Restored: status=ready_to_execute. percent: 78 is correct now (5 Phase 3 plans counted; was 18/18=100 stale). Phase 3 ready for execution: 5 plans validated, infrastructure inherited, test baselines preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 KiB
Phase 3: SPEC §10 smoke verification + DOM/event-log verification - Research
Researched: 2026-05-20 Domain: UAT harness verification (Approach B extension) for rrweb DOM capture + event-log + RAM scaffolding + §10 sweep Confidence: HIGH (Q1, Q2, Q3 verified against registry + official docs + existing harness; Q4 LOW for new chrome.* patterns — no production new surface required for charter)
Summary
Phase 3 is verification-heavy and the Approach B harness pattern (page-side assertA* + host-side driveA* + harness.test.ts orchestrator + Tier-1 FORBIDDEN_HOOK_STRINGS lockstep + pre-checkpoint bundle gates) established in Plans 01-13, 01-14, 02-04 is fully mature. The four scoped research questions resolve as follows:
-
puppeteer.Page.metrics()is reliable ONLY for the page realm it is called on; it returns CDPPerformance.getMetrics, which exposesJSHeapUsedSizefor the page's V8 isolate. It does NOT measure the MV3 service worker context — service workers live in a separate target with its own V8 isolate, and the metrics API onPagedoes not aggregate across workers/iframes. Scaffolding viaPage.metrics()is feasible against the harness extension-internal page (a Page target) but yields a value that under-reports the operator-facing "extension background RAM ≤ 50 MB" claim. Per D-P3-04 best-effort framing, scaffolding viaPage.metrics()MAY be added but MUST come with explicit diagnostic copy ("page-realm only; SW context excluded") and SHOULD NOT block §10 #9 verification. -
rrweb 2.0.0-alpha.4 alpha-pin is safe for Phase 3 verification: npm registry
latesttag forrrwebstill points at 2.0.0-alpha.4 (2023 release; npm dist-tag confirmed bynpm view rrweb dist-tags). The newer alpha.20 (2026-02-03) introduced aNodeTypeenum move fromrrweb-snapshotto@rrweb/types(breaking import sites) but does not affect what Mokosh actually uses (therecord()entry point +EventTypeenum signatures). No known regressions in alpha.4 that would make synthetic-probe-page verification flaky. Stable v2 has NOT shipped (zero non-alpha releases in the dist-tags table). Phase 4 upgrade research is correctly deferred. -
rrweb deterministic verification on synthetic probe pages works via the event-shape assertion pattern, not snapshot-comparison. rrweb's own test suite uses snapshot files filtered through
stringifySnapshotsto normalize non-determinism (mouse positions, timestamps, blob URLs). For Mokosh's "rrweb records DOM events without errors" charter (SPEC §10 #4), the deterministic gate is structural: assertevents.length > 0+events[0].type === EventType.Meta(=4) + at least oneEventType.FullSnapshot(=2) + at least oneEventType.IncrementalSnapshot(=3) emitted during a probe page lifecycle. This is far simpler than snapshot-comparison and matches the charter ("records without errors", not "produces specific DOM snapshot"). -
chrome. surface for §10 verification* is already covered by the existing 29-assertion harness — no new chrome.* patterns required for Phase 3. The Blob URL path is verified empirically by A24 via
chrome.downloads.onCreatedcross-realm capture (Plan 02-04).chrome.tabs.captureVisibleTabis verified transitively by A28 set-equality (screenshot.png present in zip). The chrome-extension://-context Network panel observation is an operator-only surface (Phase 2 VERIFICATION.md T5 cited as override) — no new automation pattern is required by Phase 3 charter.
Primary recommendation: Plan 03-01 through 03-05 follow the Plan 02-04 template verbatim: page-side stubs + host-side driveA* + chained-assertion + findLatestZip + JSZip host-only parse. New assertions stay on production surfaces (rrweb's already-shipped record() + content-script GET_RRWEB_EVENTS handler + chrome.tabs.sendMessage); Tier-1 FORBIDDEN_HOOK_STRINGS stays at 12 entries (no new test-only symbols expected). RAM scaffolding stays optional and best-effort per D-P3-04. VERIFICATION.md aggregator (Plan 03-05) replicates Phase 2 VERIFICATION.md frontmatter shape (overrides_applied + human_verification).
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| REQ-install-clean | Fresh build + load unpacked into Chrome without errors; manifest:name resolves; no remote-font CSP errors; branded icons; en+ru parity | Already COMPLETED Phase 1 Plan 01-12; Phase 3 verifies via §10 #1 aggregator in VERIFICATION.md citing existing tests (no-remote-fonts + manifest-i18n + locale-parity + bundle gates) |
| REQ-rrweb-dom-buffer | rrweb records DOM events via record() over 10-min window, 5000-event cap, oldest-dropped; sensitive fields masked |
Plan 03-01: synthetic form+table+modal probe HTML in extension-page-harness.html; harness assertion grep events.length > 0 + EventType enum values 2/3/4; existing wiring at src/content/index.ts:284-311 already ships maskInputOptions.password=true |
| REQ-user-event-log | Captures 5 event types: click, input (no password), navigation (popstate/hashchange/pushState/replaceState), js_error (error + unhandledrejection), network_error (fetch >= 400 + XHR >= 400 + network failure) | Plan 03-02: Puppeteer page.click + page.type + page.goto + dispatchEvent(ErrorEvent) + fetch(404); harness greps events.json parsed from latest archive for one entry of each type value; existing wiring at src/content/index.ts:60-237 |
| </phase_requirements> |
User Constraints (from CONTEXT.md)
Locked Decisions
-
D-P3-01: Phase 3 = exactly 5 atomic plans:
- 03-01 rrweb DOM verification harness extension (§10 #4)
- 03-02 event-log verification harness extension (§10 #5)
- 03-03 §10 #8 password-filter verification (verify existing minimum; PARTIAL mark)
- 03-04 §10 #9 RAM ceiling best-effort (operator instructions + optional
puppeteer.Page.metricsscaffolding) - 03-05 full §10 sweep VERIFICATION.md aggregating §10 #1-#9 evidence
-
D-P3-02: SPEC §10 #8 = verify existing minimum + PARTIAL mark in VERIFICATION.md. The
src/content/index.ts:82if (target.type === 'password') return;filter is the existing minimum. Plan 03-03 ships a harness assertion that VERIFIES this filter fires (synthetic password input + grep events.json for absence of entered value). NOT in scope: rrweb v2maskInputFn,data-sensitiveHTML attribute guards, full §10 #8 closure. -
D-P3-03: rrweb 2.0.0-alpha.4 stays pinned through Phase 3. Upgrade research + implementation DEFER TO PHASE 4.
-
D-P3-04: SPEC §10 #9 = best-effort + operator instructions in VERIFICATION.md. Optional scaffolding via
puppeteer.Page.metrics()if practical without research budget. NOT in scope:chrome.devtools.MemoryAPI.
Claude's Discretion
- Harness assertion numbering: A29+ continuing from A24-A28 sequence.
- Probe page composition: synthetic HTML inline in
extension-page-harness.tsvs. real-world navigation — planner's call. - Event-log trigger strategy: synthetic Puppeteer events vs. natural lifecycle observation — planner's call.
- Sequencing: Plan 03-05 runs after 03-01..04 (synthesis plan); whether 03-01..04 parallelize within wave 2 depends on
files_modifiedoverlap audit at plan time.
Deferred Ideas (OUT OF SCOPE)
- rrweb v2 stable upgrade research + implementation (D-P3-03)
- Programmatic RAM measurement via
chrome.devtools.MemoryAPI (D-P3-04 partial defer) - REQ-password-confidentiality v2 candidate — full rrweb v2
maskInputFn+data-sensitiveguards (D-P3-02) - Audit P1 #11/#14/#15 polish (fetch Request→[object Request], navigation URL tracking, rrweb timestamp semantics)
- 2 pre-existing ffprobe/ffmpeg vitest flakes
- getDisplayMedia cursor visibility refinement
- Dark-surface logo contrast
- setimmediate polyfill
new Functionin SW chunk - ROADMAP backfill for Plans 01-08..01-13
Project Constraints (from global CLAUDE.md)
The user's global ~/.claude/CLAUDE.md is loaded for this session (no project-local CLAUDE.md discovered). Directives that downstream planner + executor MUST honor:
| Directive | Application to Phase 3 |
|---|---|
No continue statements — use filtering instead |
Already established in Plan 02-04 (Object.keys(zip.files).filter((path) => !zip.files[path].dir) per saved memory). New driveA* / assertA* code follows the filter-pipeline form. |
| Prefer if-else chains over early returns | The if (target.type === 'password') return; at src/content/index.ts:82 is the verification subject for Plan 03-03 (existing code; not modifying it). New verification code follows if-else chains. |
No break inside loops when loop condition handles it |
A11 buffer-continuity loop precedent (for (let i = 0; i < attempts; i++) with no inner break) — follow same shape for any new polling loops. |
| Full-word names; standard acronym exceptions | Existing harness uses assertA29, driveA29 etc — full-word + standard AXX namespacing. |
| TypeScript semantic type aliases over raw types | rrweb EventType enum is the semantic alias (imported from @rrweb/types); avoid magic numbers 2, 3, 4 in assertion code. |
| Type arrow function parameters explicitly | Harness already typed (e.g., (item: chrome.downloads.DownloadItem): void). New listener/predicate callbacks follow same form. |
| Sanitize error messages: log details server-side, return generic messages to clients | N/A for verification phase (no production-surface changes). |
WORKAROUND(org/repo#issue) tags |
Apply if any rrweb behavior requires a known-issue workaround (none anticipated — see Q3 below). |
Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|---|---|---|---|
| rrweb DOM event verification | Test harness (page-realm) | Production content script (read-only) | rrweb runs in the content script per src/content/index.ts:284; the harness verifies via the existing GET_RRWEB_EVENTS round-trip + events.json parse — no new prod surface |
| Event-log capture verification | Test harness (page-realm) + Puppeteer host | Production content script (read-only) | Production wiring at src/content/index.ts:60-237 is unchanged; harness drives synthetic browser events + greps the assembled archive's logs/events.json |
| Password-filter (§10 #8) verification | Test harness (page-realm) | Production content script (read-only at src/content/index.ts:82) |
Negative-assertion grep on events.json for absence of typed-sentinel value |
| RAM ceiling (§10 #9) best-effort | Operator (chrome://memory-internals) | Optional puppeteer.Page.metrics() scaffolding |
SW context is a separate target from Page — metrics API on Page does not reach it; operator-driven measurement is the canonical path per D-P3-04 |
| §10 sweep aggregation | Plan 03-05 VERIFICATION.md (docs tier) | — | Pure docs synthesis aggregating Phase 1 + 2 + 3 evidence |
Standard Stack
Core (existing; no new dependencies)
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| puppeteer | ^25.0.2 | Real-Chrome driver for the UAT harness | Established Plan 01-13 (Approach B); extension-targets API + Page.metrics + service-worker worker.evaluate all live here |
| rrweb | 2.0.0-alpha.4 | Production DOM-event recorder being verified | Pinned per D-P3-03; alpha-pin stable across all phases to date |
| @rrweb/types | (sub-package of rrweb 2.0.0-alpha.4) | EventType enum + eventWithTime type |
Already present transitively at node_modules/@rrweb/types/dist/index.d.ts |
| jszip | ^3.10.1 | Host-side archive parse for events.json + meta.json + zip-layout checks | Established Plan 02-04 (driveA26/A28) |
| tsx | ^4.22.1 | TS runner for harness scripts (tsx tests/uat/harness.test.ts) |
Established Plan 01-13 |
| vitest | ^4 | Unit-test layer (Tier-1 grep gate + meta-json validators) | Established Phase 0/1 |
Alternatives Considered
| Instead of | Could Use | Tradeoff |
|---|---|---|
puppeteer.Page.metrics() |
performance.measureUserAgentSpecificMemory() inside worker.evaluate() |
More accurate (per-SW measurement) BUT requires cross-origin-isolation (COOP+COEP headers) which MV3 extensions do not set — would throw SecurityError. Skip; not viable for D-P3-04. |
puppeteer.Page.metrics() |
CDP Performance.getMetrics directly via page.target().createCDPSession() |
Equivalent data, more plumbing; Page.metrics() IS the wrapper. No win. |
| Manual operator UAT (full Phase 3 closure) | Harness assertions A29+ | Per saved memory feedback-trust-harness-over-manual-uat.md, automation covers what automation can cover. Operator UAT reserved for §10 #9 RAM only. |
New rrweb test utility from packages/rrweb/test/utils.ts (assertDomSnapshot via MHTML) |
Direct grep on events[].type for EventType enum values |
rrweb's test util is for snapshot-comparison (MHTML diffs); Mokosh's charter is "records without errors" — structural assertion is simpler + matches charter literally. |
Installation: No new packages. Phase 3 is verification-only.
Version verification (2026-05-20 via npm view rrweb dist-tags):
{
beta: '1.0.0-beta.2',
latest: '2.0.0-alpha.4', // 2023 release; PHASE-3 PIN
alpha: '2.0.0-alpha.20' // 2026-02-03 release; Phase-4 upgrade-research target
}
latest dist-tag still pointing at alpha.4 (4 years old) confirms (a) no v2 stable shipped, (b) the project actively publishes newer alphas but does not promote them to latest. Phase 4 upgrade research deferral is correctly framed.
Architecture Patterns
System Architecture Diagram
┌─────────────────────────────────────────┐
│ tsx tests/uat/harness.test.ts │ (Puppeteer HOST)
│ - launch.ts → chromium + --load-extension=dist-test
│ - sequential drive: driveA0..A28 + driveA29..A3X
│ - bail-on-first-failure │
└────────────┬────────────────────────────┘
│ Puppeteer CDP
│
┌────────────────────┼────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌────────────────────────┐
│ Page target │ │ Service Worker │ │ Offscreen Document │
│ (chrome-extension://<id>/tests/uat/ │ │ (chrome.offscreen) │
│ extension-page-harness.html) │ │ │
│ │ │ src/background/ │ │ getDisplayMedia │
│ window.__mokosh │ │ index.ts │ │ (synthetic via Canvas) │
│ Harness.assertA* │ │ │ │ MediaRecorder segments │
│ │ │ ↕ │ │ keepalivePort blob URLs│
│ chrome.tabs. │ │ chrome.runtime. │ │ │
│ sendMessage →│←─│ onMessage │←────────│ port: 'video-keepalive'│
│ chrome.downloads.onCreated (cross-realm) │ │
└─────────────────┘ └─────────────────┘ └────────────────────────┘
▲ │ ▲
│ │ chrome.tabs.sendMessage │
│ │ (GET_RRWEB_EVENTS) │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Content Script │ │
│ │ src/content/index.ts │ │
│ │ │ │
│ │ rrweb.record() → rrwebEvents│ │
│ │ click/input/nav/error log → userEvents│
│ └─────────────────────────────┘ │
│ │
└─────────── New for Phase 3 ────────────────────────┘
Plan 03-01: probe HTML (form+table+modal) injected into the harness page tree
Plan 03-02: Puppeteer synthetic event triggers + grep events.json from latest zip
Plan 03-03: <input type=password> + sentinel typed + grep events.json for absence
Plan 03-04: Page.metrics() scaffolding (optional) + operator instructions in VERIFICATION
Plan 03-05: aggregator over Phase 1+2+3 evidence into VERIFICATION.md frontmatter
Component Responsibilities
| File | Responsibility | Phase 3 Touch |
|---|---|---|
src/content/index.ts |
rrweb wiring + event-log wiring | READ-ONLY (verified, not modified) |
src/background/index.ts:GET_RRWEB_EVENTS round-trip |
SW → CS → SW transport of events for archive assembly | READ-ONLY |
src/shared/types.ts:UserEvent |
Event-log shape contract | READ-ONLY (already used in Plan 03-02 grep) |
tests/uat/extension-page-harness.html |
Harness page DOM scaffold; canonical tokens.css load-bearing for A18/A21 | MODIFY (Plan 03-01: add probe HTML below existing scaffold per UI-SPEC) |
tests/uat/extension-page-harness.ts |
Page-side assertA* host (3413 LoC) |
MODIFY (Plan 03-01..04: append assertA29..A3X stubs/handlers) |
tests/uat/lib/harness-page-driver.ts |
Host-side driveA* host (1849 LoC) |
MODIFY (Plan 03-01..04: append driveA29..A3X + JSZip-parse of events.json) |
tests/uat/harness.test.ts |
Orchestrator + FORBIDDEN_HOOK_STRINGS unit-mirror (500 LoC) | MODIFY (Plan 03-01..04: import driveA29+; push to drivers[]; banner) |
tests/background/no-test-hooks-in-prod-bundle.test.ts |
Tier-1 grep gate (12 entries currently) | EXPECTED NO CHANGE — A29+ ride existing prod surfaces |
.planning/phases/03-.../03-05-VERIFICATION.md |
Final phase aggregator | CREATE (Plan 03-05; replicate Phase 2 VERIFICATION.md frontmatter shape) |
Pattern 1: Approach B Harness Extension (rrweb DOM verification)
What: Page-side assertA29(): Promise<AssertionResult> greps events.json from the latest archive by EventType enum value; host-side driveA29(page) chains off A28's already-completed SAVE or runs its own SAVE.
When to use: §10 #4 rrweb DOM verification (Plan 03-01) — synthetic probe HTML triggers rrweb to record meta + full snapshot + at least one incremental snapshot.
Example:
// Source: existing pattern from tests/uat/lib/harness-page-driver.ts:driveA26
// (Plan 02-04; established 2026-05-20)
// EventType enum values from @rrweb/types (verified via
// node_modules/@rrweb/types/dist/index.d.ts grep):
// DomContentLoaded = 0
// Load = 1
// FullSnapshot = 2
// IncrementalSnapshot = 3
// Meta = 4
// Custom = 5
// Plugin = 6
async function driveA29(page: Page, downloadsDir: string): Promise<AssertionResult> {
// Chain off A28's already-completed SAVE — findLatestZip from harness-page-driver.ts
const zipPath = await findLatestZip(downloadsDir);
const buf = await fs.readFile(zipPath);
const zip = await JSZip.loadAsync(buf);
const rrwebRaw = await zip.file('rrweb/session.json')!.async('text');
const events: Array<{ type: number; timestamp: number }> = JSON.parse(rrwebRaw);
// Structural checks (no snapshot-compare; matches charter "records without errors")
const hasMeta = events.some((e) => e.type === 4);
const hasFullSnapshot = events.some((e) => e.type === 2);
const hasIncremental = events.some((e) => e.type === 3);
// ... AssertionResult with 4 checks: length>0 + Meta + FullSnapshot + Incremental
}
Pattern 2: Event-Log Trigger via Puppeteer
What: Use page.click + page.type + page.evaluate(() => window.dispatchEvent(...)) + fetch(...) from inside an opened tab to trigger all 5 UserEvent types; grep logs/events.json from the assembled archive.
When to use: §10 #5 event-log verification (Plan 03-02). All 5 types in one drive.
Example (host-side; production surfaces only):
// Plan 03-02 driveA30 (illustrative; planner picks A-numbers):
// 1. Open a tab on https://example.com (production chrome.tabs.create)
// 2. page.type('<input>', 'hello') → input event
// 3. page.click('a') → click event
// 4. page.goto('https://example.com#frag') → hashchange navigation event
// 5. page.evaluate(() => window.dispatchEvent(new ErrorEvent('error',
// { message: 'probe', filename: 'x', lineno: 1 }))) → js_error
// 6. page.evaluate(() => fetch('https://example.com/404-probe')) → network_error (HTTP 404)
// 7. SAVE_ARCHIVE; grep logs/events.json from latest zip
// 8. Assert: events.some((e) => e.type === 'click') + ... × 5 types
// where UserEvent.type ∈ {'click','input','navigation','js_error','network_error'}
// per src/shared/types.ts:124-131
Pattern 3: Password Negative-Assertion
What: Type a sentinel string into <input type="password">; assert sentinel is ABSENT from events.json.
When to use: §10 #8 PARTIAL closure (Plan 03-03).
Example:
// Plan 03-03 driveA31 (illustrative):
// const SENTINEL = 'secret-do-not-log-123'; // fixed; not a real secret
// await page.evaluate((s: string) => {
// const input = document.querySelector<HTMLInputElement>('#probe-password');
// if (input) { input.value = s; input.dispatchEvent(new Event('input', { bubbles: true })); }
// }, SENTINEL);
// SAVE_ARCHIVE; grep events.json
// const eventsRaw = await zip.file('logs/events.json')!.async('text');
// const userEvents: UserEvent[] = JSON.parse(eventsRaw);
// const containsSentinel = userEvents.some((e) => e.value?.includes(SENTINEL));
// expect(containsSentinel).toBe(false); // ABSENCE proves the password filter fires
Anti-Patterns to Avoid
- Using rrweb's
assertDomSnapshot(MHTML diff) — Mokosh charter is "records without errors", not "matches a specific DOM snapshot". Snapshot-diff is brittle (random ports, timestamps, cursor positions per rrweb test utils). Structural assertion (EventType enum presence) matches charter literally and is order-of-magnitude simpler. - Asserting rrweb event count — rrweb emits a variable number of incremental events depending on probe page interactivity. Use
>= 1floors per type, not exact equality. - Reading SW heap via
puppeteer.Page.metrics()—Page.metrics()is bound to the Page target's V8 isolate. The MV3 service worker is a separate target (target.type() === 'service_worker') with its own isolate. Document this limitation explicitly if scaffolding lands per D-P3-04. - Adding new test-only
__MOKOSH_UAT__-gated symbols — Plan 03-01..04 should ride production surfaces only (rrweb.record() already shipped; GET_RRWEB_EVENTS bridge shipped Phase 1; chrome.tabs/chrome.downloads shipped). If a new test-only symbol becomes necessary, it MUST be added to BOTH FORBIDDEN_HOOK_STRINGS inventories (unit-gate attests/background/no-test-hooks-in-prod-bundle.test.ts:108+ UAT A0 mirror attests/uat/harness.test.ts:115). - Stripping the canonical tokens.css link from harness page — Per Plan 01-12 Wave 6 + UI-SPEC:
<link rel="stylesheet" href="../../src/shared/tokens.css">is load-bearing for A18 + A21. Probe HTML appends below the existing scaffold; does NOT modify the head.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| rrweb event type identification | A hand-coded mapping { 0: 'DomContentLoaded', 2: 'FullSnapshot', ... } |
EventType enum from @rrweb/types/dist/index.d.ts |
Already shipped in node_modules; values pinned by rrweb; import like import { EventType } from '@rrweb/types'; — TypeScript-checked at build time. |
| Archive zip parsing in tests | A custom zip parser | JSZip.loadAsync (same pattern as tests/uat/lib/zip.ts + tests/uat/lib/harness-page-driver.ts:driveA26/A28) |
Host-only (not bundled into harness page); same precedent as Plan 02-04. |
| Latest-zip lookup in downloadsDir | Hand-rolled directory poll | findLatestZip helper at tests/uat/lib/harness-page-driver.ts:1395 |
Mtime-sort chain; race-free because A24/A25/A27 drivers wait for their zip via stable-size protocol. |
| Cross-realm download capture | chrome.downloads.download monkey-patch in harness page realm |
chrome.downloads.onCreated listener (Plan 02-04 A24 pattern at tests/uat/extension-page-harness.ts:2851) |
Production cross-realm API; canonical capture; Tier-1 inventory unaffected. |
| Service worker memory measurement via Page.metrics | Treating Page.metrics().JSHeapUsedSize as the SW heap |
Operator-driven chrome://extensions/ service-worker memory display OR chrome://memory-internals/ |
Page.metrics is page-realm only; SW is a separate target. Per D-P3-04, operator instruction is the canonical path. |
Key insight: Phase 3 is mechanical replication of the Plan 02-04 harness-extension template against a new set of production surfaces (rrweb + event-log + password-filter). Custom infrastructure is anti-pattern — every needed pattern already exists in the harness lib.
Runtime State Inventory
Phase 3 is verification-only — no rename/refactor/migration. This section is included to confirm explicit non-applicability per protocol.
| Category | Items Found | Action Required |
|---|---|---|
| Stored data | None — verified by grep -rn "ChromaDB|Mem0|user_id" src/ returns 0 hits. No long-lived databases used; rrweb + event-log buffers are in-memory only per CON-buffer-storage. |
None |
| Live service config | None — verified by grep -rn "n8n|Datadog|Tailscale|Cloudflare" . returns 0 hits. Extension is local-only per SPEC §9 "Out of Scope". |
None |
| OS-registered state | None — Phase 3 introduces no Windows Task Scheduler / pm2 / launchd / systemd registrations. | None |
| Secrets/env vars | None — no .env or SOPS files in repo; Phase 3 reads no secrets. |
None |
| Build artifacts | None new — Phase 3 ships zero new bundled modules. Existing dist/ + dist-test/ artifacts regenerated by npm run build + npm run build:test are inputs to verification; no Phase-3-owned build artifacts. |
None |
Confirmation: No runtime state to migrate; Phase 3 is verification-only.
Common Pitfalls
Pitfall 1: rrweb event timing race
What goes wrong: assertA29 reads events.json from a zip assembled BEFORE rrweb's IncrementalSnapshot has fired (snapshots fire on mutation; static probe HTML produces only Meta + FullSnapshot).
Why it happens: Synthetic probe page has no DOM mutations between load and SAVE — IncrementalSnapshot is mutation-driven.
How to avoid: Plan 03-01 driveA29 MUST inject at least one DOM mutation (page.evaluate(() => document.body.appendChild(document.createElement('div')))) before SAVE, OR drive the modal trigger button click. Probe HTML modal + table both qualify per UI-SPEC.
Warning signs: A29.3 hasIncrementalSnapshot returns false intermittently.
Pitfall 2: Service worker memory under-reporting via Page.metrics
What goes wrong: Plan 03-04 ships puppeteer.Page.metrics().JSHeapUsedSize and reports e.g. 4 MB; operator interprets this as "extension RAM is well under 50 MB"; real extension SW RAM (separate target) is 35 MB.
Why it happens: Page.metrics() measures only the page realm; SW lives in a different target.
How to avoid: If Plan 03-04 ships scaffolding, gate output behind explicit diagnostic copy: "NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04." Do NOT treat scaffolding value as authoritative; the operator chrome://memory-internals check is the binding §10 #9 gate.
Warning signs: Operator sees a green automation number and skips the chrome://memory-internals step.
Pitfall 3: chrome.tabs permission gap on probe pages
What goes wrong: Plan 03-02 opens a tab on https://example.com via chrome.tabs.create — works because Phase 2 Plan 02-03 added the tabs permission (DEC-011 Amendment 1). However, if the probe-page composition uses URLs that violate the URL_SCHEME_ALLOW regex at src/background/tab-url-tracker.ts:79 (^(https?|chrome-extension):\/\/), meta.urls will be empty and downstream assertions may fail intermittently.
Why it happens: file://, about:, chrome:// URLs are filtered out of urls[] by design (F2 fallback path).
How to avoid: Plan 03-02 probe pages use https://example.com or https://www.iana.org (already established as Plan 02-04 A27 fixtures). Do NOT use about:blank or file://.
Warning signs: A30 passes locally but fails in CI when the test URL is changed to a non-http(s) target.
Pitfall 4: rrweb alpha.4 known issue — genTextAreaValueMutation
What goes wrong: rrweb 2.0.0-alpha.4 has an open issue (rrweb-io/rrweb#1596) where genTextAreaValueMutation doesn't mask the attribute.value of a textarea. If the probe page includes a textarea, the value could leak into incremental snapshots EVEN WHEN maskInputOptions.textarea is set.
Why it happens: Known alpha.4 bug; not yet patched in any 2.0 release.
How to avoid: Plan 03-01 probe HTML uses <input type="text"> + <input type="email"> + <input type="password">, NOT <textarea>. The form-input variety in UI-SPEC Section "Test Fixture Conventions" already excludes textarea. Confirm during planning that no <textarea> is introduced.
Warning signs: A29 passes; a follow-on plan (not Phase 3) introduces a textarea and Plan 03-03 password-filter check passes but textarea leak is unaccounted for.
Pitfall 5: Pre-checkpoint bundle gate breakage from Phase 3 changes
What goes wrong: Plan 03-* adds a new test-only symbol that escapes the Vite __MOKOSH_UAT__ define-token tree-shake, lands in dist/, and breaks Gate 5/6 in the pre-checkpoint bundle gates.
Why it happens: A planner introduces a new debug-only flag or helper without adding it to FORBIDDEN_HOOK_STRINGS.
How to avoid: Per Plan 02-04 SUMMARY decision: "FORBIDDEN_HOOK_STRINGS unchanged at 12. A26/A28 are host-side JSZip; A27 uses chrome.tabs.create/update/remove (production APIs)." Phase 3 plans SHOULD stay at 12 entries. If any plan finds a real need for a new symbol, the planner MUST add it to BOTH FORBIDDEN_HOOK_STRINGS inventories AND __MOKOSH_UAT__-gate it.
Warning signs: Tier-1 grep gate fails after Plan 03-* commit; bundle gate 6 shows new occurrences in dist/.
Pitfall 6: findLatestZip race between parallel A29-A3X drivers
What goes wrong: Plan 03-01..04 plans run in parallel within wave 2 (per "Claude's Discretion" in CONTEXT.md), each does its own SAVE, and findLatestZip returns the WRONG zip to a chained-assertion downstream.
Why it happens: Multiple SAVEs in flight + mtime collision (1s precision on some filesystems).
How to avoid: Per Phase 2 Wave 2 lesson cited in CONTEXT.md: "plan-checker should catch overlaps". Plan-checker validates files_modified overlap audit — if both Plan 03-01 and Plan 03-02 modify the same harness file in different ways, mark wave 2 as SEQUENTIAL. The safer default is: 03-01 → 03-02 → 03-03 → 03-04 sequential within wave 2; 03-05 in wave 3.
Warning signs: Wave 2 commits land in different order than planned; A30 passes locally but fails when chained after A29.
Code Examples
A29: Synthetic probe → rrweb event-shape grep
// Source: pattern from tests/uat/lib/harness-page-driver.ts:driveA26
// (Plan 02-04 host-side; established 2026-05-20)
// Plan: 03-01 (illustrative; planner finalizes)
import { EventType } from '@rrweb/types';
async function driveA29(page: Page, downloadsDir: string): Promise<AssertionResult> {
// Step 1: trigger probe page interactions (form input + modal click)
await page.evaluate(() => {
const input = document.querySelector<HTMLInputElement>('#probe-text');
if (input) {
input.value = 'probe';
input.dispatchEvent(new Event('input', { bubbles: true }));
}
const button = document.querySelector<HTMLButtonElement>('#probe-modal-trigger');
if (button) {
button.click();
}
});
// Step 2: wait for at least one IncrementalSnapshot to land
// (rrweb buffers events; small delay before SAVE is safe)
await new Promise((resolve) => setTimeout(resolve, 500));
// Step 3: SAVE — chain off A28's already-completed flow OR own SAVE
// (planner decides based on files_modified overlap; see Pitfall 6)
// [...] // SAVE_ARCHIVE dispatch via existing pattern
// Step 4: read latest zip + grep rrweb/session.json
const zipPath = await findLatestZip(downloadsDir);
const buf = await fsPromises.readFile(zipPath);
const zip = await JSZip.loadAsync(buf);
const rrwebRaw = await zip.file('rrweb/session.json')!.async('text');
const events: Array<{ type: number; timestamp: number }> = JSON.parse(rrwebRaw);
const checks = [
{
name: 'A29.1: rrweb session.json contains > 0 events',
passed: events.length > 0,
expected: '> 0',
actual: String(events.length),
},
{
name: 'A29.2: rrweb emitted at least one Meta event (EventType=4)',
passed: events.some((e) => e.type === EventType.Meta),
expected: 'has Meta',
actual: String(events.some((e) => e.type === EventType.Meta)),
},
{
name: 'A29.3: rrweb emitted at least one FullSnapshot (EventType=2)',
passed: events.some((e) => e.type === EventType.FullSnapshot),
expected: 'has FullSnapshot',
actual: String(events.some((e) => e.type === EventType.FullSnapshot)),
},
{
name: 'A29.4: rrweb emitted at least one IncrementalSnapshot (EventType=3)',
passed: events.some((e) => e.type === EventType.IncrementalSnapshot),
expected: 'has IncrementalSnapshot',
actual: String(events.some((e) => e.type === EventType.IncrementalSnapshot)),
},
];
return {
passed: checks.every((c) => c.passed),
name: 'A29 — rrweb DOM events recorded without errors (SPEC §10 #4)',
checks,
diagnostics: [],
};
}
A3X: Optional puppeteer.Page.metrics() RAM scaffolding (Plan 03-04)
// Source: pptr.dev/api/puppeteer.page.metrics + existing harness host pattern
// Plan: 03-04 (optional per D-P3-04; planner decides "if practical")
async function driveA3X_OptionalRamScaffold(page: Page): Promise<AssertionResult> {
// Page.metrics is page-realm only — JSHeapUsedSize covers V8 isolate of THIS Page,
// NOT the MV3 service worker (separate target).
const metrics = await page.metrics();
const jsHeapMB = metrics.JSHeapUsedSize !== undefined
? metrics.JSHeapUsedSize / (1024 * 1024)
: -1;
// 50 MB threshold per SPEC §10 #9; treat as best-effort floor for the page realm
// alone. Operator-driven chrome://memory-internals is the binding §10 #9 gate.
const checks = [
{
name: 'A3X.1: Page.metrics returned JSHeapUsedSize',
passed: jsHeapMB >= 0,
expected: '>= 0',
actual: String(jsHeapMB),
},
{
name: 'A3X.2: Page-realm JS heap < 50 MB (NOTE: scaffolding only; SW context excluded)',
passed: jsHeapMB < 50,
expected: '< 50 MB',
actual: `${jsHeapMB.toFixed(2)} MB`,
},
];
return {
passed: checks.every((c) => c.passed),
name: 'A3X — RAM scaffolding (best-effort; page-realm only per D-P3-04)',
checks,
diagnostics: [
'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.',
],
};
}
VERIFICATION.md frontmatter template (Plan 03-05)
---
phase: 03-spec-10-smoke-verification-dom-event-log-verification
verified: 2026-05-20T<HH-MM-SS>Z
status: passed
score: 9/9 SPEC §10 criteria
overrides_applied: <count>
override_notes:
- dimension: "SPEC §10 #8 — Password masking (PARTIAL per D-P3-02 charter)"
initial_status: "PARTIAL"
rationale: |
Full rrweb v2 maskInputFn + data-sensitive guards DEFERRED per 2026-05-20 charter
'we don't care about privacy hardening. At least here.' REQ-password-confidentiality
moved to Out of Scope v1. Existing src/content/index.ts:82 `if (target.type === 'password') return;`
filter VERIFIED by Plan 03-03 A31 (sentinel string typed; greps events.json for absence).
human_verification:
- dimension: "SPEC §10 #9 — Extension RAM ≤ 50 MB"
rationale: |
Per D-P3-04: operator instructions in VERIFICATION below — load extension, idle 5 min,
open chrome://memory-internals OR chrome://extensions service-worker memory display,
verify extension background RAM < 50 MB. Page.metrics scaffolding (if shipped by
Plan 03-04) measures page-realm only and does not cover the SW target.
---
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
| Operator UAT for all functional gates | Approach B harness (Plan 01-13) | 2026-05-19 | Plan 01-09 Task 5 retired; operator UAT reserved for brand judgment + RAM-ceiling-style genuinely-non-automatable surfaces |
| Snapshot-comparison via MHTML (rrweb-io test util) | Structural EventType enum grep | Phase 3 introduction (this plan) | Order-of-magnitude simpler; matches "records without errors" charter literally |
data:application/zip;base64,... download |
blob:chrome-extension://<id>/<uuid> mint |
Plan 02-02 (D-P2-01) | Closes audit P0-6 base64 cap; Plan 03-05 inherits A24 evidence for §10 #6 latency aggregation |
7-field meta.json with url: string |
8-field with urls: string[] + schemaVersion: '2' |
Plan 02-03 (D-P2-02 + D-P2-03) | Plan 03-05 inherits A26 evidence for §10 #7 aggregation |
Deprecated/outdated:
- rrweb 1.x patterns referenced in older blog posts (e.g.,
inlineStylesheet: trueas a separate option) — Mokosh uses 2.0.0-alpha.4 where the option lives insiderecord()config and is implicitly enabled. Not relevant for Phase 3 (verification-only). chrome.tabCapturereferences in pre-D-01 historical commits — fully replaced bychrome.offscreen+getDisplayMedia; Plan 01-05 closed.
Assumptions Log
Claims tagged
[ASSUMED]in this research that the planner / discuss-phase should confirm before locking decisions. Most claims here are[VERIFIED]via npm registry + grep + file reads; the table lists residual unknowns.
| # | Claim | Section | Risk if Wrong |
|---|---|---|---|
| A1 | rrweb 2.0.0-alpha.4 has no NEW regressions since Phase 1 + 2 closure that would make Plan 03-01..03 flaky | Q3 / Pitfall 4 | LOW — alpha.4 is the same artifact that has powered 9 closed plans + 29/29 UAT GREEN end-to-end. Risk is a latent edge case in textarea handling (issue #1596) — mitigated by probe HTML excluding textarea. |
| A2 | The probe-page injection mechanism (synthetic HTML inline in extension-page-harness.ts) does not interfere with existing assertions A18+A21 that depend on canonical tokens.css being loaded |
Pattern 1 / UI-SPEC | LOW — UI-SPEC explicitly bans the probe HTML from importing tokens.css; head element preserves the existing tokens.css link. Confirm during Plan 03-01 plan-checker pass. |
| A3 | Page.metrics() JSHeapUsedSize is available in Puppeteer 25.0.2 (project pin) |
Pattern A3X / Pitfall 2 | LOW — Page.metrics has been stable since Puppeteer 1.x; documented at pptr.dev/api/puppeteer.page.metrics. Verify by quick smoke before committing scaffolding. |
| A4 | EventType.Meta (=4) always fires before EventType.FullSnapshot (=2) on a fresh recording start |
Code Example A29 | LOW — rrweb spec; Meta event is the meta header (URL + width + height + timestamp) and is emitted as the first event of a session. If wrong, A29.2 + A29.3 both fail; both checks have independent grep so the failure mode is visible. |
| A5 | Plans 03-01..04 can complete the harness orchestrator extension without modifying tests/uat/extension-page-harness.html beyond the probe-HTML append |
Component Responsibilities | LOW — probe HTML is below the existing <h1> and <pre id="status">; planner-side confirms the head + tokens.css link remain untouched. |
| A6 | Phase 3 plans can stay at FORBIDDEN_HOOK_STRINGS = 12 entries (no new MOKOSH_UAT symbol required) | Pitfall 5 / Standard Stack | MEDIUM — most likely true (rrweb + chrome.tabs + chrome.downloads are already production surfaces), but if Plan 03-04 scaffolding requires a new bridge op (e.g., get-page-metrics from offscreen → harness), that would add 1-2 entries. Surface in plan-checker pass. |
| A7 | chrome://memory-internals is available on Chrome stable 124+ (operator-facing path) |
RAM operator instructions / D-P3-04 | LOW — chrome://memory-internals is a stable internal URL since Chrome 80+. The chrome://extensions/ service-worker memory display (Inspect views > service worker > Memory) is the canonical alternative. |
| A8 | The harness host can read the assembled zip's rrweb/session.json and logs/events.json via existing findLatestZip + JSZip pattern (Plan 02-04 precedent) |
Don't Hand-Roll / Pattern 1 | VERIFIED — Plan 02-04 driveA26 + driveA28 already do exactly this for meta.json + zip-layout; rrweb/session.json + logs/events.json are sibling entries in the canonical 5-entry layout. |
If the table is mostly LOW risk: confirmed. The only MEDIUM is A6; planner-side flag.
Open Questions (RESOLVED)
-
Wave structure: parallel or sequential for Plans 03-01..04?
- What we know: Plan 02-04 ran A24-A28 sequentially within a single plan (one author). Phase 3's 5-plan structure is finer-grained per D-P3-01.
- What's unclear: Do Plans 03-01, 03-02, 03-03 modify the SAME
tests/uat/extension-page-harness.ts+tests/uat/lib/harness-page-driver.tsfiles in ways that would conflict on parallel execution? CONTEXT.md "Claude's Discretion" defers to plan-timefiles_modifiedoverlap audit. - Recommendation: Default sequential (03-01 → 03-02 → 03-03 → 03-04 → 03-05) unless plan-checker confirms zero overlap. Phase 2 lesson cited in CONTEXT.md is "plan-checker should catch overlaps"; honor it.
-
A29 chaining: own SAVE vs. chain off A28?
- What we know: A26 + A28 chain off A25's already-completed SAVE per Plan 02-04 (no new SAVE). A27 owns its SAVE because the tab tracker needs the multi-tab onActivated events to fire BEFORE dispatch.
- What's unclear: Does the probe-page DOM injection happen before the existing harness page's recorded session, or before a separate session? rrweb starts on
start()(content-script init) — by the time the harness page is loaded, rrweb is already recording. - Recommendation: A29 chains off A28's already-completed SAVE if the DOM mutations happen during the rolling 30s recording window before A25's SAVE. If timing-fragile, A29 owns its SAVE with
setupFreshRecording+ segment-settle, mirroring A27. Plan 03-01 planner picks based on smoke-test.
-
Plan 03-04 Page.metrics scaffolding: include or skip?
- What we know: D-P3-04 says "if practical without research budget". This research budget is exhausted; Page.metrics IS practical (single API call); scaffolding is ~30 lines.
- What's unclear: Is the resulting page-realm-only measurement value worth the diagnostic copy overhead? Operator-facing chrome://memory-internals is unaffected.
- Recommendation: SHIP the scaffolding (low cost). Output is informational only; explicit diagnostic copy ("page-realm only; SW context excluded") avoids over-interpretation. Tier-1 inventory unaffected (Page.metrics is host-side; not bundled).
Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|---|---|---|---|---|
| Node.js + npm | All harness tooling | ✓ | (project pin) | — |
| Puppeteer ^25 | UAT harness driver | ✓ | 25.0.2 (package.json) | — |
| Chrome / Chromium (Puppeteer-bundled) | Real-Chrome target for harness | ✓ | Puppeteer-bundled stable | — |
| rrweb 2.0.0-alpha.4 + @rrweb/types | DOM event verification (Plan 03-01) | ✓ | Pinned (package.json:11) | — |
| JSZip | Archive parsing in driveA29+ | ✓ | 3.10.1 | — |
| tsx | TS runner | ✓ | 4.22.1 | — |
| vitest | Unit-test layer (Tier-1 grep gate) | ✓ | ^4 | — |
| chrome://memory-internals (operator) | §10 #9 RAM check | ✓ (Chrome stable 80+) | — | chrome://extensions service-worker Memory tab |
| ffprobe / ffmpeg | §10 #7 video playback re-verification (already covered in Phase 1 VERIFICATION) | Already verified Phase 1 closure | — | — |
Missing dependencies with no fallback: None. Phase 3 introduces zero new dependencies.
Missing dependencies with fallback: None.
Validation Architecture
Required per
.planning/config.json:workflow.tdd_mode = true(workflow.nyquist_validation absent — treat as enabled).
Test Framework
| Property | Value |
|---|---|
| Framework | vitest ^4 (171/171 GREEN post Phase 2) + Puppeteer ^25 UAT harness (29/29 GREEN) |
| Config file | vite.config.ts + vite.test.config.ts (defines __MOKOSH_UAT__); vitest.config.ts if present (project pins via package.json scripts) |
| Quick run command | npm test (vitest) for unit tier; SKIP_PROD_REBUILD=1 HEADLESS=1 npm run test:uat for harness tier |
| Full suite command | npm test && npm run test:uat (sequential — UAT depends on npm run build:test artifact) |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| REQ-install-clean | §10 #1 fresh build loads unpacked w/o errors | aggregator (existing tests) | npm test covers manifest + i18n + no-remote-fonts |
✅ tests/i18n/manifest-i18n.test.ts + tests/build/no-remote-fonts.test.ts |
| REQ-rrweb-dom-buffer | §10 #4 rrweb records meta+full+incremental on probe page | UAT harness (Plan 03-01 A29) | npm run test:uat (post-Plan 03-01) |
❌ Wave 0 (A29 to be created) |
| REQ-user-event-log | §10 #5 events.json contains 5 UserEvent types | UAT harness (Plan 03-02 A30) | npm run test:uat (post-Plan 03-02) |
❌ Wave 0 (A30 to be created) |
| (§10 #8 PARTIAL per D-P3-02) | Password sentinel absent from events.json | UAT harness (Plan 03-03 A31 negative-assert) | npm run test:uat (post-Plan 03-03) |
❌ Wave 0 (A31 to be created) |
| (§10 #9 best-effort per D-P3-04) | RAM scaffolding via Page.metrics OR operator chrome://memory-internals | UAT harness (Plan 03-04 A3X, optional) + manual-only operator step | npm run test:uat (post-Plan 03-04) + operator |
Optional Wave 0 (A3X may or may not be created) |
Sampling Rate
- Per task commit:
npm test(~30s for full vitest suite) - Per wave merge:
npm run test:uat(~95s; Plan 02-04 baseline) - Phase gate:
npm test && npm run test:uatboth GREEN before/gsd-verify-work 3
Wave 0 Gaps
tests/uat/extension-page-harness.ts— append probe HTML injection helper + assertA29..A31 (+ optional A3X) — Plan 03-01..04tests/uat/lib/harness-page-driver.ts— append driveA29..A31 (+ optional A3X) + EventType import — Plan 03-01..04tests/uat/harness.test.ts— import + drivers[] push + banner — Plan 03-01..04tests/uat/extension-page-harness.html— append probe HTML below existing<h1>+<pre id="status">; preserve tokens.css link — Plan 03-01
(No new test framework install required. No new vitest test files anticipated — all new assertions live in the UAT harness tier.)
Security Domain
security_enforcementis not explicitly set in.planning/config.json— treating as default-enabled.
Applicable ASVS Categories
| ASVS Category | Applies | Standard Control |
|---|---|---|
| V2 Authentication | no | Extension is local-only; no auth surface |
| V3 Session Management | no | Local-only; no remote sessions |
| V4 Access Control | partial | MV3 permissions baseline (DEC-011 + Amendment 1) — verified via existing manifest-i18n.test.ts |
| V5 Input Validation | yes | URL_SCHEME_ALLOW regex at src/background/tab-url-tracker.ts:79 validates URL schemes; probe page inputs are not user input from a security boundary perspective |
| V6 Cryptography | no | No cryptography in Phase 1 v1 surface |
| V7 Error Handling and Logging | partial | Logging via ContentLogger; no PII expected (passwords filtered) |
| V8 Data Protection | partial | Password filter at src/content/index.ts:82 is the v1 minimum per D-P3-02; full masking deferred to Phase 4+ if charter reverses |
Known Threat Patterns for Phase 3
| Pattern | STRIDE | Standard Mitigation |
|---|---|---|
| Test-only hook surface leaking to production bundle | Information Disclosure | Tier-1 FORBIDDEN_HOOK_STRINGS gate (12 entries; unit-mirror + UAT A0 mirror); __MOKOSH_UAT__ Vite define-token tree-shake; verified per Plan 02-04 SUMMARY |
| Probe page sentinel value (e.g., "secret-do-not-log-123") leaking via grep mismatch | Information Disclosure | Sentinel is a fixed test constant, not a real secret; absence-assertion proves filter fires; logged to event log triggers explicit RED |
| New chrome.* permission grant for probe-page composition | Elevation of Privilege | Phase 3 plans explicitly stay at DEC-011 + Amendment 1 baseline (8 permissions); no new manifest changes |
| rrweb data buffer growth past 5000 events / 10min cap during probe | Denial of Service | cleanupOldEvents at src/content/index.ts:27-50 already enforces (oldest-dropped); Plan 03-01 probe HTML interaction is brief (~500ms) and far below the cap |
Sources
Primary (HIGH confidence)
- npm registry verified 2026-05-20 via
npm view rrweb dist-tags:latest=2.0.0-alpha.4, alpha=2.0.0-alpha.20, beta=1.0.0-beta.2 - @rrweb/types EventType enum verified via
node_modules/@rrweb/types/dist/index.d.ts:{DomContentLoaded:0, Load:1, FullSnapshot:2, IncrementalSnapshot:3, Meta:4, Custom:5, Plugin:6} - Plan 02-04 SUMMARY (
.planning/phases/02-stabilize-export-pipeline/02-04-SUMMARY.md) — most recent Approach B harness precedent; 29/29 UAT GREEN - Plan 01-13 SUMMARY — Approach B foundation; FORBIDDEN_HOOK_STRINGS pattern
- Plan 01-14 SUMMARY — single-assertion plan precedent (A23); 7-file atomic-commit shape
src/content/index.ts— production wiring being verified (read-only for Phase 3)src/shared/types.ts:124-131— UserEvent type contracttests/uat/extension-page-harness.ts:2851-3413— assertA24-A28 implementations (latest pattern)tests/uat/lib/harness-page-driver.ts— driveA* + findLatestZip + JSZip patterntests/background/no-test-hooks-in-prod-bundle.test.ts:108-126— Tier-1 inventory (12 entries)- 03-CONTEXT.md (D-P3-01..04 locked decisions + canonical_refs)
- 03-UI-SPEC.md (null-spec; probe-page conventions; FORBIDDEN_HOOK_STRINGS lockstep guidance)
Secondary (MEDIUM confidence)
- Puppeteer official docs: https://pptr.dev/api/puppeteer.page.metrics (Page.metrics + JSHeapUsedSize semantics)
- Puppeteer official docs: https://pptr.dev/guides/chrome-extensions (MV3 SW target acquisition pattern)
- MDN: https://developer.mozilla.org/en-US/docs/Web/API/Performance/measureUserAgentSpecificMemory (SW memory measurement requires COOP+COEP — not viable for MV3 extensions)
- rrweb master/test/utils.ts (reference for assertDomSnapshot pattern — explicitly rejected for charter mismatch)
- W3C Screen Capture spec §6.1 (already cited in Plan 01-14; relevant for §10 #9 if RAM scaffolding research expands)
Tertiary (LOW confidence — flagged for plan-time validation)
- chrome://memory-internals stability across Chrome stable channel (cited at A7 in Assumptions Log; common operator path but not formally guaranteed)
- WebSearch findings on rrweb 2.0 stable release status — cross-verified with npm registry (HIGH); separate WebSearch hit on "rrweb 2.0 production ready" returned an unrelated commercial-fork article, not the canonical rrweb-io repo
Metadata
Confidence breakdown:
- Standard stack: HIGH — all dependencies present in package.json; versions verified via
npm view - Architecture: HIGH — Approach B template from Plan 02-04 is direct precedent; 3 chained-assertion patterns proven
- Pitfalls: HIGH for Pitfalls 1-3 (verified against existing harness + rrweb registry); MEDIUM for Pitfalls 4-6 (research-derived; not yet exercised in this codebase)
- RAM ceiling (§10 #9): MEDIUM — Page.metrics scope verified; SW heap measurement caveat verified; chrome://memory-internals operator instructions are standard but not version-stamped
- rrweb v2 upgrade safety: HIGH — alpha.4 pin status verified directly via npm dist-tags
Research date: 2026-05-20 Valid until: 2026-06-20 (30 days; stable dependencies; if rrweb 2.0 stable ships in the interim, re-check D-P3-03 deferral)