# 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: 1. **`puppeteer.Page.metrics()`** is reliable ONLY for the page realm it is called on; it returns CDP `Performance.getMetrics`, which exposes `JSHeapUsedSize` for 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 on `Page` does not aggregate across workers/iframes. Scaffolding via `Page.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 via `Page.metrics()` MAY be added but MUST come with explicit diagnostic copy ("page-realm only; SW context excluded") and SHOULD NOT block §10 #9 verification. 2. **rrweb 2.0.0-alpha.4 alpha-pin is safe** for Phase 3 verification: npm registry `latest` tag for `rrweb` still points at 2.0.0-alpha.4 (2023 release; npm dist-tag confirmed by `npm view rrweb dist-tags`). The newer alpha.20 (2026-02-03) introduced a `NodeType` enum move from `rrweb-snapshot` to `@rrweb/types` (breaking import sites) but does not affect what Mokosh actually uses (the `record()` entry point + `EventType` enum 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. 3. **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 `stringifySnapshots` to 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: assert `events.length > 0` + `events[0].type === EventType.Meta` (=4) + at least one `EventType.FullSnapshot` (=2) + at least one `EventType.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"). 4. **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.onCreated` cross-realm capture (Plan 02-04). `chrome.tabs.captureVisibleTab` is 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 | 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` | ## 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.metrics` scaffolding) - 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:82` `if (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 v2 `maskInputFn`, `data-sensitive` HTML 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.Memory` API. ### Claude's Discretion - Harness assertion numbering: A29+ continuing from A24-A28 sequence. - Probe page composition: synthetic HTML inline in `extension-page-harness.ts` vs. 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_modified` overlap audit at plan time. ### Deferred Ideas (OUT OF SCOPE) - rrweb v2 stable upgrade research + implementation (D-P3-03) - Programmatic RAM measurement via `chrome.devtools.Memory` API (D-P3-04 partial defer) - REQ-password-confidentiality v2 candidate — full rrweb v2 `maskInputFn` + `data-sensitive` guards (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 Function` in 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:///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: + 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` 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:** ```typescript // 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 { // 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):** ```typescript // Plan 03-02 driveA30 (illustrative; planner picks A-numbers): // 1. Open a tab on https://example.com (production chrome.tabs.create) // 2. page.type('', '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 ``; assert sentinel is ABSENT from `events.json`. **When to use:** §10 #8 PARTIAL closure (Plan 03-03). **Example:** ```typescript // 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('#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 `>= 1` floors 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 at `tests/background/no-test-hooks-in-prod-bundle.test.ts:108` + UAT A0 mirror at `tests/uat/harness.test.ts:115`). - **Stripping the canonical tokens.css link from harness page** — Per Plan 01-12 Wave 6 + UI-SPEC: `` 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 `` + `` + ``, NOT `