---
phase: 03-spec-10-smoke-verification-dom-event-log-verification
plan: 04
subsystem: testing
tags:
  - uat-harness
  - a32
  - ram-ceiling
  - spec-10-9-best-effort
  - approach-b
  - page-metrics
  - charter-d-p3-04
  - phase-3-wave-4
  - host-side-only

requires:
  - phase: 01-stabilize-video-pipeline
    provides: "Plan 01-13 UAT harness Approach B (extension-internal page + synthetic MediaStream; page-side assertA* + host-side driveA* + harness.test.ts orchestrator); FORBIDDEN_HOOK_STRINGS lockstep pattern"
  - phase: 02-stabilize-export-pipeline
    provides: "Plan 02-04 host-side latency precedent (driveA25 mixes host-side timing measurement with page-side assertions); chained-driver pattern (drivers array push + banner comment idiom); puppeteer ^25.0.2 stable Page.metrics surface"
  - plan: 03-01
    provides: "driveA29 host-side JSZip pattern (no analog needed by A32, but the Plan 03-01..03 sequencing keeps wave-4 base aligned)"
  - plan: 03-02
    provides: "driveA30 chained-driver pattern reused — wrapped-const idiom (A32 is the simpler case that does NOT need wrapping because no downloadsDir)"
  - plan: 03-03
    provides: "driveA31 + wrapped const pattern (most-recent precedent; A32 wires AFTER A31 in both import block and drivers array)"

provides:
  - 1 new UAT harness assertion (A32) shipping best-effort RAM scaffolding per D-P3-04 + RESEARCH Open Question 3 (puppeteer.Page.metrics returning JSHeapUsedSize for the harness page-realm V8 isolate)
  - driveA32 host-side scaffolding (~90 lines) at tests/uat/lib/harness-page-driver.ts (NO page-side assertA32 — Page.metrics is a host-only Puppeteer API; no need for a window.__mokoshHarness method; consistent with how the host-side latency portion of A25 is computed)
  - A32_RAM_CEILING_BYTES = 50 * 1024 * 1024 (SPEC §10 #9 + CON-ram-ceiling)
  - A32_BYTES_PER_MB = 1024 * 1024 (diagnostic-copy readability factor)
  - Pitfall 2 mandatory diagnostic line emitted on EVERY run: 'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.' (T-03-04-01 Repudiation mitigation; printed to stdout via printAssertionResult.diagnostics)
  - Orchestrator extension: drivers array 31 → 32 entries (no wrapped-const layer — driveA32 takes only `page`); A32 banner mentioned in the Architecture line
  - Phase 4 inheritance scaffold: any future programmatic RAM measurement upgrade (chrome.devtools.Memory API OR per-target Page.metrics aggregation across SW + offscreen + harness page targets) inherits a working Page.metrics call site to build from

affects:
  - phase-03 plan 05 (Plan 03-05 VERIFICATION.md aggregator records §10 #9 as `human_verification` regardless of A32 status — A32 is informational scaffolding, NOT the binding gate; the binding gate is the operator chrome://memory-internals observation per D-P3-04)
  - phase-04 future programmatic RAM measurement upgrade (D-P3-04 partial defer; A32 establishes the scaffold to build from — extend to SW target via `browser.targets().filter(t => t.type() === 'service_worker')` + `target.createCDPSession()` + `Performance.getMetrics` per RESEARCH §"Code Examples")
  - phase-04 candidate: pre-existing parallel-vitest build-race flake disclosed in 03-03-SUMMARY "Issues Encountered" recurred once during full `npm test` in this plan (1 failure out of 171 tests, race between Tier-1 build-step and parallel test execution); re-run in isolation passed 13/13. NOT caused by Plan 03-04 changes; verified pre-existing across 03-02 + 03-03

tech-stack:
  added: []
  patterns:
    - "Host-side-only assertion (no page-side counterpart): Page.metrics is a puppeteer host API (CDP Performance.getMetrics under the hood). A32 calls it directly without page.evaluate; no window.__mokoshHarness.assertA32 method is added. This matches how the host-side latency portion of A25 is computed and is the simplest possible Approach B variant. Reusable for any future verification that lives entirely in the puppeteer host (e.g., extension targets enumeration, CDP-side network throttling injection)."
    - "Pitfall-leading-diagnostic pattern: when a measurement has a load-bearing scope caveat that would mislead an operator if missed, emit the caveat as the FIRST diagnostic entry (before the actual measurement values). printAssertionResult prints diagnostics in order, so the operator sees the caveat ahead of the green number. Reusable for any best-effort or partial-coverage assertion where automation result must not be confused with full closure."

key-files:
  modified:
    - "tests/uat/lib/harness-page-driver.ts (+90 lines: 2 module constants A32_RAM_CEILING_BYTES=50MiB + A32_BYTES_PER_MB=1MiB; driveA32 export with try/catch around page.metrics(); 2 AssertionRecord checks A32.1 + A32.2; 4 diagnostic lines leading with the Pitfall 2 caveat; full Approach B docstring citing RESEARCH Pitfall 2 + Open Question 3 + D-P3-04)"
    - "tests/uat/harness.test.ts (+10 lines: driveA32 import after driveA31 import block; Architecture banner appended ', A32'; drivers-array entry with Plan 03-04 banner citing D-P3-04 + Pitfall 2 + 'no wrapped const needed — driveA32 takes only page')"
  created: []

key-decisions:
  - "Host-side-only (no page-side assertA32). The plan was explicit on this design (see Plan Anchors: 'NO page-side assertion needed. Page.metrics is a host-side puppeteer API.'). Implementation followed verbatim — no window.__mokoshHarness.assertA32 method, no extension to the declare global Window interface, no page.evaluate call. driveA32 calls page.metrics() directly from the puppeteer host. Simplest possible Approach B variant; reduces test surface."
  - "Mandatory diagnostic line leads the diagnostics array (Pitfall 2 gate). The diagnostics.push('NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.') call is the FIRST line in driveA32's body — emitted BEFORE the try/catch around page.metrics(). This ensures the caveat appears even if Page.metrics throws + the assertion would otherwise RED-fail. T-03-04-01 mitigation: the three layers of operator-visible signal (check name itself includes 'NOTE: scaffolding only; SW context excluded'; mandatory diagnostic line; check expected/actual strings) make it impossible for an operator to glance at 'A32 GREEN' and conclude §10 #9 is closed."
  - "Try/catch wraps the entire page.metrics() call (T-03-04-03 DoS mitigation). Even though Page.metrics has been stable since Puppeteer 1.x (per RESEARCH Assumption A3), the try/catch is defense-in-depth: jsHeapBytes stays -1 on throw + A32.1 REDs cleanly with the error message in diagnostics + the assertion does NOT crash the orchestrator. The metricsErr field is also threaded into AssertionRecord.error so runAssertion + the bail-on-first-failure path see the actual exception text."
  - "No new chrome.* permissions, no manifest changes, no new __MOKOSH_UAT__-gated symbols. Page.metrics is a puppeteer host API (CDP Performance.getMetrics under the hood) — runs entirely in the Node host process. No production-bundle impact. Tier-1 FORBIDDEN_HOOK_STRINGS inventory stays at 12 entries; 13/13 unit-gate sub-tests GREEN; 12 strings × 0 hits each in dist/."
  - "No setupFreshRecording, no SAVE, no archive parse. A32 measures the current heap state of the harness page only. No probe tab, no chrome.scripting.executeScript, no chrome.tabs.create, no chrome.downloads. The simplest possible driver shape — a single API call plus a 50 MB threshold compare. This is the intended scope of the best-effort scaffolding per D-P3-04."
  - "driveA32 takes only `page` (no downloadsDir). The orchestrator pushes it bare in the drivers array without a wrapped const layer (`{ name: 'A32', drive: driveA32 }` not `driveA32Wrapped`). Smaller diff in tests/uat/harness.test.ts; matches the same pattern A24's bare-driver entry uses. Less code to maintain."
  - "Sanity-floor framing for A32.2's 50 MB threshold. The plan-checker passed the plan with this exact threshold (RESEARCH §'Code Example A3X' caveat + Plan Anchors: 'Page-realm typical values: a few MB ... Far below the 50 MB ceiling on any reasonable run.'). Empirical: this run reported 1.82 MB (1909924 bytes JSHeapUsedSize) — well under the ceiling. If A32.2 ever REDs, that signals catastrophic test infrastructure regression (not a §10 #9 production regression), which is the intended floor-signal value of the scaffolding."
  - "A32 GREEN does NOT close §10 #9. Documented in three places: (1) the diagnostic line itself, (2) the check name 'A32.2: ... NOTE: scaffolding only; SW context excluded per D-P3-04', (3) this SUMMARY. Plan 03-05 VERIFICATION.md will mark §10 #9 as `human_verification` (operator chrome://memory-internals or chrome://extensions service-worker memory display) per D-P3-04 + RESEARCH Pitfall 2. Phase 4 candidate: upgrade to programmatic per-target measurement via puppeteer.browser.targets() filtering + per-target createCDPSession + Performance.getMetrics."

patterns-established:
  - "Host-side-only Approach B driver: Page.metrics or any puppeteer-host-only API can be called directly from driveA* without a page-side assertA* counterpart. The orchestrator's `{ name: 'A<NN>', drive: driveA<NN> }` entry needs no wrapped const if the driver takes only `page`. Smaller surface for the same coverage."
  - "Pitfall-leading-diagnostic: when an assertion has a load-bearing scope caveat (e.g., measurement scope, partial coverage, deferred-by-charter), emit the caveat as the FIRST diagnostic entry so it appears before the measurement values in printAssertionResult. Three layers of operator-visible signal (check name + diagnostic line + this SUMMARY) make misinterpretation impossible."

requirements-completed: []
# CON-ram-ceiling / SPEC §10 #9 remains best-effort + operator-driven per
# D-P3-04. A32 is informational scaffolding; the binding §10 #9 gate is
# the operator chrome://memory-internals observation recorded in Plan 03-05
# VERIFICATION.md `human_verification` block.

# Metrics
duration: "~10 min (Phase 3 Wave 4; single-task plan; smallest plan in the phase by code surface — ~90 lines added across 2 files)"
completed: 2026-05-20
---

# Phase 03 Plan 04: A32 RAM scaffolding (best-effort; page-realm only per D-P3-04) Summary

**Single new UAT harness assertion (A32) ships best-effort RAM scaffolding per D-P3-04 + RESEARCH Open Question 3 (~90 lines; host-side only; no page-side counterpart). Calls `puppeteer.Page.metrics()` against the harness page and asserts the V8 isolate's `JSHeapUsedSize` is below the SPEC §10 #9 50 MB ceiling. Mandatory diagnostic line (Pitfall 2 mitigation) emitted on EVERY run regardless of pass/fail: `'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.'` — three layers of operator-visible signal (check name + leading diagnostic + this SUMMARY) prevent any confusion of automation GREEN with full §10 #9 closure. The binding §10 #9 gate stays operator-driven and is recorded as `human_verification` in Plan 03-05 VERIFICATION.md. UAT count 32 → 33 GREEN; vitest 171/171 preserved; Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12.**

## Performance

- **Duration:** ~10 min (Phase 3 Wave 4; single-task plan; smallest by code surface — ~90 lines added across 2 files; smallest by execution time because no probe tabs, no chrome.scripting injection, no chrome.tabs.create, no SAVE, no JSZip parse — just one CDP call)
- **Started:** 2026-05-20T18:51:36Z (worktree spawn after Plan 03-03 landed)
- **Completed:** 2026-05-20T18:56:31Z (SUMMARY drafted)
- **Tasks:** 1 of 1 plan tasks complete (autonomous; no checkpoints)
- **Files modified:** 2 (TypeScript harness wires only; no production code change; no probe HTML; no new dependencies)

## Accomplishments

- **A32 (SPEC §10 #9 best-effort per D-P3-04):** 2 visible checks — A32.1 `Page.metrics returned a JSHeapUsedSize value >= 0` (empirically 1909924 bytes ≈ 1.82 MB) + A32.2 `Page-realm JS heap < 50 MB (NOTE: scaffolding only; SW context excluded per D-P3-04)` (empirically GREEN at 1.82 MB, far below the 50 MB ceiling). Both checks PASS on every run; the diagnostic-leading line about page-realm scope is emitted whether or not the checks pass.
- **Pitfall 2 mitigation operational:** the mandatory `'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.'` line leads the diagnostics array and prints to stdout via `printAssertionResult`. T-03-04-01 Repudiation threat mitigated through three layers of operator-visible signal (check name + diagnostic + SUMMARY).
- **Host-side-only driver pattern established:** no page-side `assertA32`, no `window.__mokoshHarness.assertA32` method, no extension to the `declare global Window` interface. `driveA32(page: Page): Promise<AssertionRecord>` calls `page.metrics()` directly from the puppeteer host. Simplest possible Approach B variant; reusable for any future host-only API check.
- **Phase 4 inheritance scaffold:** the working Page.metrics call site at `tests/uat/lib/harness-page-driver.ts:driveA32` is the foundation for Phase 4's programmatic per-target measurement upgrade (extend to SW target via `browser.targets().filter(t => t.type() === 'service_worker')` + `target.createCDPSession()` + `Performance.getMetrics` per RESEARCH §"Code Examples").
- **Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12:** A32 is host-side only; `puppeteer.Page.metrics()` is a Node-process API, not bundled to the page or SW realms. 13/13 unit-gate sub-tests GREEN; 12 strings × 0 hits each in `dist/`.
- **vitest baseline preserved:** 171/171 GREEN on the isolated re-run. One transient flake on the parallel `npm test` run (Tier-1 build-step race with parallel test execution; identical to the flake disclosed in 03-03 SUMMARY) — confirmed pre-existing across Plan 03-02 + 03-03 + 03-04; not caused by A32 changes; re-run in isolation passed 13/13.
- **tsc clean:** `npx tsc --noEmit` exits 0.
- **No production-code modifications:** `git diff` shows ONLY `tests/uat/lib/harness-page-driver.ts` + `tests/uat/harness.test.ts` modified. No `src/` edits; no manifest changes; no new permissions; no `__MOKOSH_UAT__`-gated symbols.

## Task Commits

Single plan task committed atomically (`--no-verify` per parallel-executor protocol):

1. **Task 1: driveA32 host-side Page.metrics scaffolding + orchestrator wiring** — `8c94bd5` (feat). +90 lines in `tests/uat/lib/harness-page-driver.ts` (2 module constants + driveA32 export with try/catch + AssertionRecord with 2 checks + 4 diagnostic lines leading with the Pitfall 2 caveat); +10 lines in `tests/uat/harness.test.ts` (import block + Architecture banner + drivers-array entry with Plan 03-04 banner comment).

## Files Created/Modified

- `tests/uat/lib/harness-page-driver.ts` — `/* ─── Plan 03-04 — driveA32 (RAM scaffolding best-effort) ──────────── */` section added at end of file. Two module-local constants (`A32_RAM_CEILING_BYTES = 50 * 1024 * 1024` + `A32_BYTES_PER_MB = 1024 * 1024`). One exported function `driveA32(page: Page): Promise<AssertionRecord>` with full Approach B docstring citing RESEARCH Pitfall 2, Open Question 3, and D-P3-04. The diagnostic-leading line `'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.'` is the first push into the diagnostics array, emitted before the try/catch around `page.metrics()`. Two AssertionRecord checks: A32.1 (`JSHeapUsedSize >= 0`) + A32.2 (`page-realm heap < 50 MB`). Two informational diagnostics (`JSHeapUsedSize=<bytes>` + `JSHeapTotalSize=<bytes>`). On `page.metrics()` throw: `metricsErr` is captured, an additional diagnostic `'A32 Page.metrics threw: <message>'` pushes, and the error threads through into `AssertionRecord.error` so runAssertion + bail-on-first-failure see it.
- `tests/uat/harness.test.ts` — `driveA32,` added to the `./lib/harness-page-driver` import block after `driveA31,` and before `getManifestVersion,` with the Plan 03-04 banner comment. Architecture banner string at line 274 appended `, A32` to the assertion list. Drivers-array entry added after the A31 entry with a multi-line banner comment citing D-P3-04, Pitfall 2, the human_verification follow-up in Plan 03-05, and the rationale for the no-wrapped-const pattern: `{ name: 'A32', drive: driveA32 }` (note: bare `driveA32`, not `driveA32Wrapped`, because A32 takes only `page`).

## Decisions Made

- **Host-side-only assertion (no page-side counterpart).** The plan was explicit on this design (Plan Anchors: "NO page-side assertion needed. Page.metrics is a host-side puppeteer API. Unlike A24..A31, A32 does NOT call assertA32 inside page.evaluate — there's no need for a window.__mokoshHarness method."). Implementation followed verbatim — no `window.__mokoshHarness.assertA32` method, no extension to the `declare global Window.__mokoshHarness` interface, no `page.evaluate` call. `driveA32` calls `page.metrics()` directly from the puppeteer host. This is the simplest possible Approach B variant: a single API call plus a 50 MB threshold compare. Reduces test surface and is the cleanest example of host-side-only Approach B driving for future Phase 4 work.

- **Mandatory diagnostic line leads the diagnostics array (RESEARCH Pitfall 2 gate).** The `diagnostics.push('NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.')` call is the FIRST line in driveA32's body — emitted BEFORE the try/catch around `page.metrics()`. This ensures the caveat appears in stdout even if Page.metrics throws (e.g., a future Puppeteer breaking change) + the assertion would otherwise RED-fail; the operator still sees the scope caveat. T-03-04-01 Repudiation mitigation operates through three layers of operator-visible signal: (a) the check name itself includes "NOTE: scaffolding only; SW context excluded per D-P3-04", (b) this mandatory diagnostic line, and (c) the structured `expected`/`actual` strings on A32.2. Operator misinterpretation impossible.

- **Try/catch wraps the entire `page.metrics()` call (T-03-04-03 DoS mitigation).** Page.metrics has been stable since Puppeteer 1.x (per RESEARCH Assumption A3 LOW risk) and the project pins Puppeteer ^25.0.2, so failure is extremely unlikely. The try/catch is defense-in-depth: on throw, `jsHeapBytes` stays `-1` + A32.1 REDs cleanly with the error message in diagnostics + the assertion does NOT crash the orchestrator. The `metricsErr` field is also threaded into `AssertionRecord.error` so `runAssertion`'s console-tail dump and the bail-on-first-failure path see the actual exception text. Empirically the try succeeds (`JSHeapUsedSize=1909924`), but the defensive frame is documented and exercised in code.

- **No new chrome.* permissions, no manifest changes, no new `__MOKOSH_UAT__`-gated symbols.** `puppeteer.Page.metrics()` is a Node-process host API (CDP `Performance.getMetrics` under the hood) — runs entirely in the harness-test host, not in the extension's page/SW/offscreen realms. No production-bundle impact. Tier-1 FORBIDDEN_HOOK_STRINGS inventory stays at 12 entries (verified empirically: 13/13 unit-gate sub-tests GREEN; 12 strings × 0 hits each in dist/). RESEARCH Assumption A6 (MEDIUM risk: "if Plan 03-04 scaffolding requires a new bridge op like `get-page-metrics`, that would add 1-2 entries") was correctly avoided — Page.metrics is read from the host puppeteer object directly without any cross-realm bridge.

- **No setupFreshRecording, no SAVE, no archive parse.** A32 measures the current heap state of the harness page only. No probe tab (no `chrome.tabs.create`), no chrome.scripting.executeScript injection, no SAVE_ARCHIVE dispatch, no JSZip parse, no findLatestZip call. This is the intended scope of the best-effort scaffolding per D-P3-04 — the simplest possible driver shape. Average runtime is dominated by the single `page.metrics()` round-trip (<100 ms).

- **`driveA32` takes only `page` (no `downloadsDir`).** The orchestrator pushes it bare in the drivers array without a wrapped-const layer (`{ name: 'A32', drive: driveA32 }`, not `driveA32Wrapped`). Smaller diff in `tests/uat/harness.test.ts`; matches the same pattern A24's bare-driver entry uses. Less code to maintain; clearer signal that A32 needs nothing from the orchestrator beyond the Page handle.

- **Sanity-floor framing for A32.2's 50 MB threshold.** The plan-checker passed the plan with this exact threshold (RESEARCH §"Code Example A3X" caveat + Plan Anchors: "Page-realm typical values: a few MB ... Far below the 50 MB ceiling on any reasonable run."). Empirically this run reported 1.82 MB (1909924 bytes JSHeapUsedSize) — well under the ceiling. If A32.2 ever REDs, that signals catastrophic test infrastructure regression (not a §10 #9 production regression), which is the intended floor-signal value of the scaffolding.

- **A32 GREEN does NOT close §10 #9.** Documented in three places: (1) the diagnostic line itself ("page-realm only; SW context measurement requires chrome://memory-internals operator verification"), (2) the check name A32.2 ("Page-realm JS heap < 50 MB (NOTE: scaffolding only; SW context excluded per D-P3-04)"), (3) this SUMMARY. Plan 03-05 VERIFICATION.md will mark §10 #9 as `human_verification` (operator chrome://memory-internals OR chrome://extensions service-worker memory display) per D-P3-04 + RESEARCH Pitfall 2. Phase 4 candidate: upgrade to programmatic per-target measurement via `puppeteer.browser.targets()` filtering + per-target `createCDPSession()` + `Performance.getMetrics`.

## Deviations from Plan

**None - plan executed exactly as written.**

The plan-checker passed Plan 03-04 GREEN on iter-1. The plan's `<tasks>/<action>` block included the verbatim driveA32 implementation; the executor copied it into place without modification (the docstring text + the constant names + the diagnostic copy + the check expected/actual values are byte-identical to the plan-spec). The four orchestrator updates (import + Architecture banner + drivers-array entry + no wrapped const) all followed the plan exactly. The empirical UAT run produced 33/33 GREEN on the first attempt, including A32 with both checks PASS + the mandatory diagnostic line in stdout — the contract was met without any deviation, Rule-1 auto-fix, Rule-2 critical addition, or Rule-3 blocking unblock.

This is the cleanest of the four Wave-2/3/4 plans in Phase 3 by deviation count (Plans 03-01, 03-02, 03-03 each had 1-2 architectural Rule-3 adaptations because of the chrome-extension://-no-content-script discovery + page-context-destruction race; Plan 03-04 is host-side only and avoids both problem classes by design).

## Verification

### Automation gates (this run)

- **tsc --noEmit:** Exit 0; clean.
- **`npm test -- --run tests/background/no-test-hooks-in-prod-bundle.test.ts`** (Tier-1 FORBIDDEN_HOOK_STRINGS unit gate): 13/13 sub-tests GREEN; 12 strings × 0 hits each in dist/ (4.61s isolated; 4.62s re-run).
- **`npm test`** (full vitest suite): 171/171 GREEN on the isolated re-run (9.33s; 31 test files). One transient flake on the first run (1/171 failed: Tier-1 build-step race with parallel test execution — `dist/ is empty after npm run build` from a different test file racing the build step); confirmed pre-existing across Plan 03-02 + 03-03 + 03-04 (identical symptom in 03-03 SUMMARY "Issues Encountered"); re-run in isolation passed 13/13; not caused by A32 changes.
- **`HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat`** (UAT harness end-to-end): exit 0; **33/33 GREEN** (A1..A31 baseline preserved + new A32).

### A32 empirical evidence (from the live UAT trace 2026-05-20T18:55:53Z)

```
A32 — RAM scaffolding (best-effort; page-realm only per D-P3-04 / SPEC §10 #9): PASS

Checks:
  [PASS] A32.1: Page.metrics returned a JSHeapUsedSize value >= 0
         expected: ">= 0"
         actual:   1909924
  [PASS] A32.2: Page-realm JS heap < 50 MB (NOTE: scaffolding only; SW context excluded per D-P3-04)
         expected: "< 50 MB"
         actual:   "1.82 MB"

Diagnostics:
  - NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.
  - A32 JSHeapUsedSize=1909924 bytes (1.82 MB)
  - A32 JSHeapTotalSize=10485760 bytes
```

### Plan must-haves coverage (all GREEN)

- `truths[0]` "puppeteer.Page.metrics() returns a JSHeapUsedSize value (>= 0) for the harness page realm" — A32.1 PASS (actual=1909924).
- `truths[1]` "JSHeapUsedSize for the harness page realm is below 50 MB (page-realm only; SW context excluded per RESEARCH Pitfall 2)" — A32.2 PASS (1.82 MB << 50 MB).
- `truths[2]` "Driver emits an explicit diagnostic line: 'NOTE: page-realm only; SW context excluded' (prevents operator misinterpretation)" — PASS (leading entry in diagnostics array; printed to stdout via printAssertionResult).
- `truths[3]` "UAT harness exits 0 with 32 + 1 = 33/33 assertions GREEN (A31 baseline preserved + new A32)" — PASS (exit 0; 33/33 GREEN; A1..A32).
- `artifacts[0]` driveA32 host-side Page.metrics scaffolding (best-effort; explicit page-realm-only diagnostic) — PASS (exported from `tests/uat/lib/harness-page-driver.ts`; visible via `grep -c 'driveA32'`).
- `artifacts[1]` driveA32 import + drivers-array push entry (no wrapped driver — Page.metrics needs only page, not downloadsDir) — PASS (visible in `tests/uat/harness.test.ts`; import after driveA31, drivers entry after A31 entry, no wrapped const).
- `key_links[0]` harness.test.ts → harness-page-driver.ts driveA32 via import + drivers-array push — PASS.
- `key_links[1]` harness-page-driver.ts driveA32 → puppeteer.Page.metrics() CDP Performance.getMetrics via `await page.metrics()` — PASS (verified by `grep -c 'page.metrics()'`).

### Acceptance grep gates

- `npx tsc --noEmit` exit 0 — PASS
- `grep -c 'driveA32' tests/uat/lib/harness-page-driver.ts` returns 2 ≥ 2 — PASS
- `grep -c 'driveA32' tests/uat/harness.test.ts` returns 3 ≥ 2 — PASS (import + 2 drivers-array references; the `driveA32` in `{ name: 'A32', drive: driveA32 }` is the second hit; plus the import = 3)
- `grep -c 'NOTE: page-realm only' tests/uat/lib/harness-page-driver.ts` returns 1 == 1 — PASS
- `grep -c 'page.metrics()' tests/uat/lib/harness-page-driver.ts` returns 2 (1 docstring reference + 1 actual call) — informational; the actual API call is on line 2371 — the docstring reference on line 2334 is required content from the plan's verbatim code block (`page.metrics() does not aggregate across workers/iframes.`). The "exactly 1" wording in plan acceptance reads as "the API is called exactly once"; both readings are satisfied (one call site; one docstring reference).
- `grep -c 'A32_RAM_CEILING_BYTES' tests/uat/lib/harness-page-driver.ts` returns 4 ≥ 2 — PASS (declaration + 1 division in check name + 1 division in expected string + 1 comparison in passed: predicate)
- `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` exit 0 with stdout containing `UAT harness: 33/33 assertions passed` AND the diagnostic line `NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.` (printed by printAssertionResult on A32) — PASS
- `npm test -- --run tests/background/no-test-hooks-in-prod-bundle.test.ts` exit 0 (Tier-1 inventory stays at 12) — PASS (13/13 sub-tests GREEN)

## Threat Flags

None new. The plan's `<threat_model>` (T-03-04-01..T-03-04-04) was analyzed at planner-time; implementation honors all mitigations:

- **T-03-04-01 (Repudiation — Operator interprets A32 GREEN as full §10 #9 closure, skips chrome://memory-internals check):** disposition `mitigate`. Three layers of operator-visible signal in place: (1) the mandatory diagnostic line `'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.'` emitted as the FIRST diagnostics entry on every run (pass or fail); (2) the A32.2 check name itself includes "NOTE: scaffolding only; SW context excluded per D-P3-04"; (3) Plan 03-05 VERIFICATION.md will explicitly list §10 #9 in the `human_verification` block. Operator misinterpretation impossible.
- **T-03-04-02 (Information Disclosure — Test-only hook surface leaking to production bundle):** disposition `mitigate`. A32 is host-side only; `puppeteer.Page.metrics()` is a Node-process host API (CDP Performance.getMetrics) — not bundled to the page or SW realms. FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 entries; 13/13 unit-gate sub-tests GREEN; 12 strings × 0 hits each in dist/.
- **T-03-04-03 (Denial of Service — Page.metrics returns 0 or throws on first call after browser launch):** disposition `mitigate`. The full `page.metrics()` call is wrapped in try/catch; on throw, `jsHeapBytes` stays `-1` + A32.1 REDs cleanly with the error message in diagnostics + the assertion does NOT crash the orchestrator. Per A3 in RESEARCH Assumptions Log, Page.metrics has been stable since Puppeteer 1.x; failure is extremely unlikely on 25.0.2. Empirically the try succeeds (`JSHeapUsedSize=1909924`), but the defensive frame is documented and exercised in code.
- **T-03-04-04 (Elevation of Privilege — New chrome.* permission grant for measurement):** disposition `accept`. A32 uses zero chrome.* APIs. Page.metrics is a CDP call, not an extension API. No manifest delta. Verified empirically: `git diff` shows ONLY `tests/uat/lib/harness-page-driver.ts` + `tests/uat/harness.test.ts` modified; no `src/` edits; no `manifest.json` change.

No new production surface; threat surface unchanged from Plan 03-03. UAT harness extension is test-only and adds no bundle surface (Page.metrics is host-side only).

## Phase 3 Wave Sequencing

Per CONTEXT D-P3-01 + RESEARCH Pitfall 6: Plans 03-01..04 modify the SAME three harness files (extension-page-harness.ts, harness-page-driver.ts, harness.test.ts; A32 modifies only the latter two — A32 has no page-side counterpart). RESEARCH §"Wave Sequencing Note" recommended SEQUENTIAL execution within Wave 2: 03-01 (A29) → 03-02 (A30) → 03-03 (A31) → 03-04 (A32, this plan). Plan 03-04 runs AFTER 03-01 + 03-02 + 03-03 (depends_on: [01, 02, 03]) — Wave 4 sequence honored. Plan 03-05 (VERIFICATION.md aggregator) runs in Wave 5 after 03-01..04 land + UAT harness reaches the empirical 33/33 GREEN baseline.

## Issues Encountered

**One pre-existing parallel-vitest flake recurred (NOT caused by Plan 03-04; verified pre-existing across 03-02 + 03-03):**

- **Tier-1 build-step race during full `npm test`.** First full-suite run during this plan reported `1 failed | 170 passed (171)` with the failing test being `tests/background/no-test-hooks-in-prod-bundle.test.ts` — specifically the sub-test that runs `npm run build` then verifies `dist/ ... at least one chunk`. The failure mode is the same as documented in 03-03 SUMMARY "Issues Encountered": the Tier-1 build-step in this test file races with parallel execution of other test files that may incidentally interact with `dist/`. Empirically reproducible on roughly 1 in 5 full-suite runs; never reproducible in isolation.
- **Empirical pre-existing flake reproduction in this plan:** the failed run was followed by an isolated re-run of `npm test -- --run tests/background/no-test-hooks-in-prod-bundle.test.ts` (13/13 GREEN, 4.62s) and a second full `npm test` run (171/171 GREEN, 9.33s). The flake was non-deterministic; the second full-suite invocation passed cleanly without code changes.
- **Per CLAUDE.md SCOPE BOUNDARY rule:** "Only auto-fix issues DIRECTLY caused by the current task's changes. Pre-existing warnings, linting errors, or failures in unrelated files are out of scope." This flake predates Plan 03-04 (documented as deferred in 03-03 SUMMARY) and is independent of the A32 code path (A32 modifies only `tests/uat/lib/harness-page-driver.ts` + `tests/uat/harness.test.ts`, neither of which interacts with the Tier-1 build-step).
- **Recommended follow-up (per 03-03 + this plan):** Phase 4 hardening pass — isolate the Tier-1 build-step into a serial-execution test file (vitest `pool: 'forks', poolOptions.forks.singleFork: true` for that file) OR move the build-step into a beforeAll hook gated by a file-lock so parallel test files do not race. Plan 03-05 VERIFICATION.md should flag this as a known follow-up for §10 #1 closure rigor.

## Phase 4 Inheritance Path

A32's working Page.metrics call site at `tests/uat/lib/harness-page-driver.ts:driveA32` is the scaffold Phase 4 inherits when programmatic per-target RAM measurement becomes in-scope:

```typescript
// Phase 4 candidate upgrade (not in scope for Plan 03-04):
// Aggregate JSHeapUsedSize across all extension targets (SW + offscreen + harness page).
const swTarget = browser.targets().find(t => t.type() === 'service_worker'
  && t.url().startsWith(`chrome-extension://${extensionId}/`));
const offTarget = browser.targets().find(t => t.type() === 'background_page'  // or 'page'
  && t.url().endsWith('/offscreen.html'));

const swSession = await swTarget!.createCDPSession();
const swMetrics = await swSession.send('Performance.getMetrics');
const swHeap = swMetrics.metrics.find(m => m.name === 'JSHeapUsedSize')?.value ?? -1;
// ... aggregate
```

Phase 4 upgrade unlocks the operator-facing 50 MB ceiling check programmatically — at which point §10 #9 can move from `human_verification` to a binding automation gate. Plan 03-04's scaffolding is the minimum viable foundation.

## Next Plan Readiness

- **Plan 03-05 (§10 sweep VERIFICATION.md aggregator):** Inherits A32 as the informational scaffolding floor for §10 #9 (NOT as the binding gate). Frontmatter shape per RESEARCH §"Code Examples → VERIFICATION.md frontmatter template" includes `human_verification` block citing D-P3-04 + RESEARCH Pitfall 2 + operator chrome://memory-internals as the canonical §10 #9 closure path. Plan 03-05 SHOULD ALSO carry forward the Phase 4 deferred items listed in CONTEXT.md and SHOULD flag the parallel-vitest Tier-1-build-step race under "Forward-Looking Deferred Items".

## Self-Check: PASSED

- A32 driver added: CONFIRMED via git log + grep (`driveA32` 2 hits in `tests/uat/lib/harness-page-driver.ts`; 3 hits in `tests/uat/harness.test.ts` — import line + drivers-array reference + comment banner).
- UAT count: 32 → 33 GREEN: **EMPIRICALLY CONFIRMED** via `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` exit 0; 33/33 assertions PASSED (A1..A32).
- vitest 171/171 GREEN preserved: CONFIRMED on isolated re-run (full suite; 31 test files; 9.33s). Transient parallel-execution flake (1/171) on first run; verified pre-existing across 03-02 + 03-03; not caused by A32 changes; re-run isolated passed 13/13.
- FORBIDDEN_HOOK_STRINGS inventory at 12 (unchanged): CONFIRMED via Tier-1 unit-gate 13/13 sub-tests GREEN; 12 strings × 0 hits each in `dist/`.
- The diagnostic line `NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.` appears in stdout: **EMPIRICALLY CONFIRMED** in the live UAT trace (printed via `printAssertionResult` at line 154-155 of `tests/uat/lib/assertions.ts`).
- The same diagnostic literal appears in source: CONFIRMED via `grep -c 'NOTE: page-realm only' tests/uat/lib/harness-page-driver.ts` returns 1.
- driveA32 signature is `(page: Page) => Promise<AssertionRecord>` (no `downloadsDir` param; no SAVE; no archive parsing): CONFIRMED — orchestrator pushes `{ name: 'A32', drive: driveA32 }` directly without a wrapped const layer.
- tsc clean: CONFIRMED (`npx tsc --noEmit` exit 0).
- No modifications to STATE.md or ROADMAP.md: CONFIRMED (parallel-executor protocol per the prompt; only this plan's SUMMARY.md is added under `.planning/phases/`).
- 1/1 plan tasks committed atomically (`8c94bd5` Task 1).
- SUMMARY.md created and committed (this file).

### File existence verification

```
FOUND: tests/uat/lib/harness-page-driver.ts (driveA32 + 2 module constants + mandatory diagnostic)
FOUND: tests/uat/harness.test.ts (driveA32 import + drivers-array entry + Architecture banner update)
FOUND: .planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-04-SUMMARY.md (this file)
```

### Commit verification

```
FOUND: 8c94bd5 feat(03-04): Task 1 — driveA32 host-side Page.metrics scaffolding + orchestrator wiring
```

---
*Phase: 03-spec-10-smoke-verification-dom-event-log-verification*
*Plan: 04*
*Completed: 2026-05-20*
