From 8c94bd515d0cdafefdb0049a3f81f7845c33b16e Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 May 2026 20:56:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(03-04):=20Task=201=20=E2=80=94=20driveA32?= =?UTF-8?q?=20host-side=20Page.metrics=20scaffolding=20+=20orchestrator=20?= =?UTF-8?q?wiring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A32 ships ~90 lines of best-effort RAM scaffolding per D-P3-04 + RESEARCH Open Question 3 (recommended SHIP). Calls puppeteer.Page.metrics() against the harness page and asserts JSHeapUsedSize is below the SPEC §10 #9 50 MB ceiling. Page-realm scope is the load-bearing caveat (RESEARCH Pitfall 2): the MV3 service worker is a separate Puppeteer target with its own V8 isolate, so Page.metrics() under-reports the operator-facing "extension background RAM" measurement that §10 #9 actually requires. The binding §10 #9 gate stays operator-driven (chrome://memory-internals OR chrome://extensions service-worker memory display) and is recorded in Plan 03-05 VERIFICATION.md human_verification block. Mandatory diagnostic line 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." printAssertionResult prints diagnostics to stdout so the operator sees the caveat in the live UAT trace, never confusing automation GREEN with full §10 #9 closure (T-03-04-01 Repudiation mitigation). Host-side only — no page-side assertA32, no setupFreshRecording, no SAVE, no archive parse. driveA32 takes only `page` (no downloadsDir), so the orchestrator pushes it bare in the drivers array without a wrapped const. Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12 entries (Page.metrics is host-side puppeteer; not bundled). Empirical: UAT harness 32/32 → 33/33 GREEN; A32.1 PASS (JSHeapUsedSize= 1909924 bytes); A32.2 PASS (1.82 MB << 50 MB). Tier-1 unit-gate 13/13 sub-tests GREEN; 12 strings × 0 hits each in dist/. vitest 171/171 GREEN. Closes: - Plan 03-04 must_have 'puppeteer.Page.metrics() returns a JSHeapUsedSize value (>= 0) for the harness page realm' (A32.1) - Plan 03-04 must_have 'JSHeapUsedSize for the harness page realm is below 50 MB' (A32.2) - Plan 03-04 must_have 'Driver emits an explicit diagnostic line: NOTE: page-realm only' (Pitfall 2 gate — leads diagnostics array) - Plan 03-04 must_have 'UAT harness exits 0 with 32 + 1 = 33/33 assertions GREEN' (empirical 33/33) --- tests/uat/harness.test.ts | 11 +++- tests/uat/lib/harness-page-driver.ts | 90 ++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/tests/uat/harness.test.ts b/tests/uat/harness.test.ts index c020ed8..fa7274e 100644 --- a/tests/uat/harness.test.ts +++ b/tests/uat/harness.test.ts @@ -103,6 +103,8 @@ import { driveA30, // Plan 03-03 — password-filter PARTIAL (SPEC §10 #8 PARTIAL per D-P3-02) driveA31, + // Plan 03-04 — RAM scaffolding best-effort (SPEC §10 #9 per D-P3-04) + driveA32, getManifestVersion, } from './lib/harness-page-driver'; import { @@ -271,7 +273,7 @@ async function assertA0_GrepGate(): Promise<{ */ async function main(): Promise { process.stdout.write('\nMokosh Plan 01-13 + 01-14 + 02-04 — UAT harness orchestrator\n'); - process.stdout.write('Architecture: A0 pre-flight + extension-internal page driver (A1..A14, A15..A17, A18..A22, A23, A24, A25, A26, A27, A28, A29, A30, A31)\n'); + process.stdout.write('Architecture: A0 pre-flight + extension-internal page driver (A1..A14, A15..A17, A18..A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32)\n'); process.stdout.write('='.repeat(72) + '\n'); // A0 pre-flight (no Chrome launch needed; runs against built dist/). @@ -475,6 +477,13 @@ async function main(): Promise { // mean the filter actually fired rather than the trivial "no // events at all" tautology). { name: 'A31', drive: driveA31Wrapped }, + // Plan 03-04 A32: RAM scaffolding (SPEC §10 #9 best-effort per D-P3-04). + // NOTE — Page.metrics is page-realm only; SW context is a separate + // Puppeteer target (RESEARCH Pitfall 2). A32 is informational + // scaffolding; the binding §10 #9 gate lives in Plan 03-05 + // VERIFICATION.md `human_verification` block. No wrapped const + // needed — driveA32 takes only `page`. + { name: 'A32', drive: driveA32 }, ]; const buffers = { swConsole: handles.swConsole, offConsole: handles.offConsole }; diff --git a/tests/uat/lib/harness-page-driver.ts b/tests/uat/lib/harness-page-driver.ts index 223c1eb..2f208bf 100644 --- a/tests/uat/lib/harness-page-driver.ts +++ b/tests/uat/lib/harness-page-driver.ts @@ -2314,3 +2314,93 @@ export async function driveA31( error: pageResult.error, }; } + +/* ─── Plan 03-04 — driveA32 (RAM scaffolding best-effort) ──────────── */ + +/** RAM ceiling per SPEC §10 #9 + CON-ram-ceiling. */ +const A32_RAM_CEILING_BYTES = 50 * 1024 * 1024; +/** Bytes-per-MB factor for diagnostic copy. */ +const A32_BYTES_PER_MB = 1024 * 1024; + +/** + * Drive A32 (Plan 03-04 — SPEC §10 #9 RAM best-effort per D-P3-04). + * + * Reads puppeteer.Page.metrics() against the harness page and asserts + * JSHeapUsedSize is below the 50 MB ceiling. This is informational + * scaffolding ONLY: + * + * - RESEARCH Pitfall 2: Page.metrics is page-realm only. The MV3 + * service worker is a separate Puppeteer target with its own V8 + * isolate; page.metrics() does not aggregate across workers/iframes. + * - The page-realm value reported here is NOT the operator-facing + * "extension background RAM" measurement that SPEC §10 #9 requires. + * - The binding §10 #9 gate lives in Plan 03-05 VERIFICATION.md + * `human_verification` block (operator runs chrome://memory-internals + * OR chrome://extensions service-worker memory display). + * + * Why ship this anyway (per RESEARCH Open Question 3): + * - Low cost (~30 lines; single API call; no new bundle surface). + * - Exercises the Page.metrics API end-to-end so Phase 4 (programmatic + * RAM measurement upgrade) inherits a working scaffold. + * - Provides a sanity floor — if the harness page-realm heap ever + * blows past 50 MB, something has gone catastrophically wrong in + * the test infrastructure itself (not necessarily a §10 #9 regression + * in production). + * + * The diagnostic line about page-realm scope MUST be emitted regardless + * of pass/fail per Pitfall 2. + * + * @param page - The harness page from `launchHarnessBrowser`. + * @returns AssertionRecord with 2 checks (heap returned + heap < 50 MB) + * + explicit page-realm-only diagnostic. + */ +export async function driveA32(page: Page): Promise { + const checks: CheckRecord[] = []; + const diagnostics: string[] = []; + + // Pitfall 2 gate: emit the page-realm caveat BEFORE any other diagnostic + // so it leads in the structured output (the operator sees it first). + diagnostics.push( + 'NOTE: page-realm only; SW context measurement requires chrome://memory-internals operator verification per D-P3-04.', + ); + + let metricsErr: string | null = null; + let jsHeapBytes = -1; + let jsHeapTotal = -1; + try { + const metrics = await page.metrics(); + jsHeapBytes = metrics.JSHeapUsedSize ?? -1; + jsHeapTotal = metrics.JSHeapTotalSize ?? -1; + } catch (err) { + metricsErr = err instanceof Error ? err.message : String(err); + } + + const jsHeapMB = jsHeapBytes >= 0 ? jsHeapBytes / A32_BYTES_PER_MB : -1; + diagnostics.push(`A32 JSHeapUsedSize=${jsHeapBytes} bytes (${jsHeapMB.toFixed(2)} MB)`); + diagnostics.push(`A32 JSHeapTotalSize=${jsHeapTotal} bytes`); + if (metricsErr !== null) { + diagnostics.push(`A32 Page.metrics threw: ${metricsErr}`); + } + + checks.push({ + name: 'A32.1: Page.metrics returned a JSHeapUsedSize value >= 0', + expected: '>= 0', + actual: jsHeapBytes, + passed: jsHeapBytes >= 0, + }); + checks.push({ + name: `A32.2: Page-realm JS heap < ${A32_RAM_CEILING_BYTES / A32_BYTES_PER_MB} MB (NOTE: scaffolding only; SW context excluded per D-P3-04)`, + expected: `< ${A32_RAM_CEILING_BYTES / A32_BYTES_PER_MB} MB`, + actual: jsHeapMB >= 0 ? `${jsHeapMB.toFixed(2)} MB` : 'unavailable', + passed: jsHeapBytes >= 0 && jsHeapBytes < A32_RAM_CEILING_BYTES, + }); + + const passed = checks.every((c) => c.passed); + return { + passed, + name: 'A32 — RAM scaffolding (best-effort; page-realm only per D-P3-04 / SPEC §10 #9)', + checks, + diagnostics, + error: metricsErr ?? undefined, + }; +}