791 lines
40 KiB
Markdown
791 lines
40 KiB
Markdown
# Phase 3: SPEC §10 smoke verification + DOM/event-log verification - Pattern Map
|
||
|
||
**Mapped:** 2026-05-20
|
||
**Files analyzed:** 5 anticipated (4 modifications + 1 new VERIFICATION.md) + optional 6th (Page.metrics scaffolding)
|
||
**Analogs found:** 5 / 5 (4 exact, 1 role-match; +1 partial for optional Page.metrics)
|
||
**Phase character:** Verification-heavy; Approach B harness extension (Plan 02-04 is direct precedent for 4 of 5 files).
|
||
|
||
## File Classification
|
||
|
||
| Anticipated File (Plans 03-01..05) | Role | Data Flow | Closest Analog | Match Quality |
|
||
|-------------------------------------|------|-----------|----------------|---------------|
|
||
| `tests/uat/extension-page-harness.ts` (page-side `assertA29..A3X`) | test (page-side assertion host) | request-response (chrome.runtime.sendMessage round-trip) + event-driven (chrome.downloads.onCreated listener for A24 echo, if needed by Plan 03-02) | `tests/uat/extension-page-harness.ts:2851-3413` (assertA24..assertA28 block; Plan 02-04) | EXACT (same file; append-only) |
|
||
| `tests/uat/lib/harness-page-driver.ts` (host-side `driveA29..A3X` + `JSZip` reads) | test (host-side driver + zip inspection) | file-I/O (downloadsDir polling + JSZip-parse) + request-response (page.evaluate wrapper) | `tests/uat/lib/harness-page-driver.ts:1202-1849` (driveA24..driveA28 + `findLatestZip` + `A28_EXPECTED_PATHS`; Plan 02-04) | EXACT (same file; append-only) |
|
||
| `tests/uat/harness.test.ts` (orchestrator wiring: imports + wrapped drivers + drivers array push + banner) | test (orchestrator) | request-response (sequential driver dispatch + bail-on-first-failure) | `tests/uat/harness.test.ts:93-99 + 316-335 + 406-430` (driveA24-A28 imports + wrappers + push; Plan 02-04) | EXACT (same file; append-only) |
|
||
| `tests/uat/extension-page-harness.html` (probe HTML append) | test (DOM scaffold) | static markup | `tests/uat/extension-page-harness.html:17-22` (existing `<h1>` + `<pre id="status">`; Plan 01-12 Wave 6 tokens.css preserved) | EXACT (same file; append-only) |
|
||
| `.planning/phases/03-.../03-VERIFICATION.md` (NEW; aggregator) | docs (verification aggregator with frontmatter) | docs synthesis | `.planning/phases/02-stabilize-export-pipeline/02-VERIFICATION.md` (T5 override pattern; 5/5 must-haves; Plan 02-04 closure) PLUS `.planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md` (per-requirement scorecard; cross-cutting gates table; operator empirical acks; deferred items section) | EXACT (T5 override) + ROLE-MATCH (scorecard structure) |
|
||
| `tests/uat/lib/ramMetrics.ts` OR inline in `harness-page-driver.ts` (OPTIONAL — D-P3-04 best-effort `puppeteer.Page.metrics()` scaffolding) | test (host-side utility) | request-response (CDP `Performance.getMetrics` via `Page.metrics()`) | NONE (research-derived; no existing analog) — closest scaffolding shape lives in `tests/uat/lib/harness-page-driver.ts:1255-1334` (driveA25 host-side latency measurement + diagnostic copy pattern) | NEW PATTERN (cite RESEARCH §"Code Examples" A3X) |
|
||
|
||
## Pattern Assignments
|
||
|
||
### `tests/uat/extension-page-harness.ts` (page-side `assertA29..A3X` for Plans 03-01..04)
|
||
|
||
**Analog:** `tests/uat/extension-page-harness.ts:2851-3413` (Plan 02-04 assertA24-A28)
|
||
|
||
**Existing imports / module context** — already present at top of file; no new imports expected:
|
||
- The harness uses ambient chrome.* types (`chrome.downloads.DownloadItem`, `chrome.tabs.create`, etc.); production surfaces only.
|
||
- Helper utilities `setupFreshRecording`, `sendMessageWithTimeout`, `diag` (file-local) — all reusable for Plan 03-01..04.
|
||
|
||
**Module constants block** — Plan 02-04 precedent for per-assertion tuning constants (lines 2977-2982):
|
||
|
||
```typescript
|
||
/** SAVE_ARCHIVE dispatch timeout for A25 — matches A24's 15s. */
|
||
const A25_SAVE_ARCHIVE_TIMEOUT_MS = 15_000;
|
||
/** Pre-SAVE segment-settle window (10s rotation + 1s slack). */
|
||
const A25_SEGMENT_SETTLE_MS = 11_000;
|
||
/** Hard latency ceiling per SPEC §10 #6 + CON-archive-export-latency. */
|
||
const A25_LATENCY_CEILING_MS = 5_000;
|
||
```
|
||
|
||
Phase 3 plans should follow the same `const A<NN>_<PURPOSE>_MS = NNNN_NNN;` form (`A29_SEGMENT_SETTLE_MS`, `A30_TRIGGER_PAUSE_MS`, `A31_PASSWORD_SENTINEL`, etc.).
|
||
|
||
**Page-side stub pattern (for host-side-only work)** — `assertA26` lines 3164-3173 + `assertA28` lines 3307-3316:
|
||
|
||
```typescript
|
||
/**
|
||
* A26 — D-P2-02 + D-P2-03 empirical (page-side stub).
|
||
*
|
||
* Returns the assertion name + a sentinel diagnostic. All real work
|
||
* happens host-side in driveA26 (JSZip-parse the latest zip + assert
|
||
* meta.json shape). The page-side stub exists purely so the orchestrator's
|
||
* single-method-per-assertion contract (window.__mokoshHarness.assertA26)
|
||
* is uniform across all 29 assertions.
|
||
*
|
||
* Chaining: A26 reads the zip produced by A25 (host-side driver picks
|
||
* the most-recently-modified zip in downloadsDir). No new SAVE dispatch.
|
||
*
|
||
* @returns Stub AssertionResult with empty checks; driveA26 fills them.
|
||
*/
|
||
async function assertA26(): Promise<AssertionResult> {
|
||
return {
|
||
passed: true,
|
||
name: 'A26 — meta.json 8-field shape (D-P2-02 + D-P2-03)',
|
||
checks: [],
|
||
diagnostics: [
|
||
'assertA26 page-side stub; host-side driveA26 inspects latest zip + asserts meta.json shape',
|
||
],
|
||
};
|
||
}
|
||
```
|
||
|
||
**Use for:** Plan 03-01 (rrweb session.json inspection is host-side JSZip), Plan 03-02 (events.json grep is host-side), Plan 03-03 (sentinel grep is host-side). Page-side stubs for A29 / A30 / A31; host-side drivers do the work.
|
||
|
||
**Page-side orchestration pattern (when chrome.* APIs are required pre-SAVE)** — `assertA24` lines 2851-2962 (chrome.downloads.onCreated cross-realm capture) and `assertA27` lines 3202-3292 (chrome.tabs.create + activate + cleanup):
|
||
|
||
```typescript
|
||
async function assertA24(): Promise<AssertionResult> {
|
||
const result: AssertionResult = {
|
||
passed: false,
|
||
name: 'A24 — D-P2-01 empirical: chrome.downloads.download receives blob: URL (closes P0-6)',
|
||
checks: [],
|
||
diagnostics: [],
|
||
};
|
||
|
||
// Capture stored in closure so the onCreated listener can populate it
|
||
// across the async SAVE_ARCHIVE dispatch + post-ack poll.
|
||
let capturedUrl: string | null = null;
|
||
let listenerInstalled = false;
|
||
const onCreatedListener = (item: chrome.downloads.DownloadItem): void => {
|
||
if (capturedUrl === null) {
|
||
capturedUrl = item.url;
|
||
}
|
||
};
|
||
|
||
try {
|
||
diag(result, 'Step 1: setupFreshRecording (A24 owns its recording — onCreated listener installed pre-SAVE)');
|
||
const setupResp = await setupFreshRecording();
|
||
if (!setupResp.ok) {
|
||
throw new Error(`setupFreshRecording failed: ${setupResp.error ?? '(no error)'}`);
|
||
}
|
||
// ... Step 2: settle, Step 3: install listener, Step 4: dispatch SAVE_ARCHIVE, Step 5: poll for capturedUrl
|
||
} catch (err) {
|
||
result.error = err instanceof Error ? err.message : String(err);
|
||
diag(result, `THREW: ${result.error}`);
|
||
} finally {
|
||
// T-02-04-01 mitigation: always remove the listener (idempotent; no-op if never added).
|
||
if (listenerInstalled) {
|
||
try { chrome.downloads.onCreated.removeListener(onCreatedListener); }
|
||
catch (rmErr) { diag(result, `(listener cleanup ignored: ${String(rmErr)})`); }
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
```
|
||
|
||
**Use for:** Plan 03-02 if event-log triggers (page.click / page.type / dispatchEvent) require pre-SAVE setup beyond what RESEARCH §"Code Examples" pattern 2 describes; Plan 03-03 password-sentinel typing pre-SAVE.
|
||
|
||
**Page-side extended-result shape (when host needs page data beyond AssertionResult)** — `A25Result` lines 2997-3002 + `A27Result` lines 3145-3148:
|
||
|
||
```typescript
|
||
interface A25Result extends AssertionResult {
|
||
t0: number; // performance.now() at SAVE dispatch
|
||
tAck: number; // performance.now() at SAVE ack
|
||
t0Wall: number; // Date.now() at SAVE dispatch (cross-realm bracket anchor)
|
||
ackSuccess: boolean;
|
||
}
|
||
|
||
interface A27Result extends AssertionResult {
|
||
tabAUrl: string;
|
||
tabBUrl: string;
|
||
}
|
||
```
|
||
|
||
**Use for:** Plan 03-04 OPTIONAL Page.metrics scaffolding — `A3XResult extends AssertionResult { jsHeapUsedBytes: number; jsHeapTotalBytes: number; }` if Plan 03-04 ships scaffolding (D-P3-04 "if practical"; see RESEARCH §"Code Example A3X").
|
||
|
||
**`__mokoshHarness` registration pattern** — lines 3332-3372 (TypeScript ambient interface) + lines 3374-3404 (object literal):
|
||
|
||
```typescript
|
||
declare global {
|
||
interface Window {
|
||
__mokoshHarness: {
|
||
assertA1: () => Promise<AssertionResult>;
|
||
// ... (24 baseline assertions A1..A23)
|
||
assertA24: () => Promise<AssertionResult>;
|
||
assertA25: () => Promise<A25Result>;
|
||
assertA26: () => Promise<AssertionResult>;
|
||
assertA27: () => Promise<A27Result>;
|
||
assertA28: () => Promise<AssertionResult>;
|
||
getManifestVersion: () => Promise<string>;
|
||
};
|
||
}
|
||
}
|
||
|
||
window.__mokoshHarness = {
|
||
assertA1, assertA2, /* ... */ assertA27, assertA28, getManifestVersion,
|
||
};
|
||
```
|
||
|
||
**Use for:** Plans 03-01..04 each append their own `assertA<NN>` to BOTH the `declare global` interface AND the `window.__mokoshHarness = {...}` object literal. Lockstep is critical; missing one side breaks the orchestrator's `await harness.assertA<NN>()` call.
|
||
|
||
---
|
||
|
||
### `tests/uat/lib/harness-page-driver.ts` (host-side `driveA29..A3X` + `JSZip`)
|
||
|
||
**Analog:** `tests/uat/lib/harness-page-driver.ts:1202-1849` (Plan 02-04 driveA24-A28)
|
||
|
||
**Existing imports (already present in file)** — lines 36-45:
|
||
|
||
```typescript
|
||
import { spawnSync } from 'node:child_process';
|
||
import { existsSync, mkdtempSync, readFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
||
import { tmpdir } from 'node:os';
|
||
import { join, resolve as resolvePath } from 'node:path';
|
||
|
||
import JSZip from 'jszip';
|
||
import type { Page } from 'puppeteer';
|
||
|
||
import type { AssertionRecord, CheckRecord } from './assertions';
|
||
import { assertArchiveShape, extractEntryToFile } from './zip';
|
||
```
|
||
|
||
Plan 03-01 only needs to ADD a single import for the rrweb EventType enum:
|
||
```typescript
|
||
import { EventType } from '@rrweb/types'; // ← NEW for Plan 03-01 (verified at node_modules/@rrweb/types/dist/index.d.ts:186-194)
|
||
```
|
||
|
||
Plan 03-02 / 03-03 may add an import for `UserEvent` from `src/shared/types.ts`:
|
||
```typescript
|
||
import type { UserEvent } from '../../../src/shared/types'; // ← NEW for Plan 03-02 events.json grep
|
||
```
|
||
|
||
**Standard `page.evaluate` wrapper for page-orchestrated assertions** — driveA24 lines 1202-1209:
|
||
|
||
```typescript
|
||
export async function driveA24(page: Page): Promise<AssertionRecord> {
|
||
return await page.evaluate(async () => {
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
|
||
const harness = (window as any).__mokoshHarness;
|
||
const r: AssertionRecord = await harness.assertA24();
|
||
return r;
|
||
}) as AssertionRecord;
|
||
}
|
||
```
|
||
|
||
**Use for:** Plan 03-02 driveA30 (if Plan 03-02 chooses page-side orchestration for event triggers — but RESEARCH recommends Puppeteer-side `page.click` / `page.type` via the page, not chrome.runtime.sendMessage round-trip; planner picks).
|
||
|
||
**Two-phase pattern (page-side stub + host-side JSZip read)** — driveA26 lines 1421-1567 (canonical 6-check meta.json shape gate; chains off A25):
|
||
|
||
```typescript
|
||
export async function driveA26(
|
||
page: Page,
|
||
downloadsDir: string,
|
||
): Promise<AssertionRecord> {
|
||
// Phase 1 — page-side stub (uniform orchestrator shape).
|
||
const pageResult = await page.evaluate(async () => {
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
|
||
const harness = (window as any).__mokoshHarness;
|
||
const r: AssertionRecord = await harness.assertA26();
|
||
return r;
|
||
}) as AssertionRecord;
|
||
|
||
const mergedChecks: CheckRecord[] = pageResult.checks.slice();
|
||
const mergedDiagnostics: string[] = pageResult.diagnostics.slice();
|
||
|
||
// Phase 2 — locate A25's zip.
|
||
const zipPath = findLatestZip(downloadsDir);
|
||
if (zipPath === null) {
|
||
mergedChecks.push({
|
||
name: 'A26.0: at least one zip present in downloadsDir (chain-off A25)',
|
||
expected: '>=1 zip',
|
||
actual: 'no zip in downloadsDir',
|
||
passed: false,
|
||
});
|
||
return { passed: false, name: pageResult.name, checks: mergedChecks,
|
||
diagnostics: mergedDiagnostics, error: pageResult.error };
|
||
}
|
||
mergedDiagnostics.push(`A26 zipPath=${zipPath}`);
|
||
|
||
// Phase 3 — load + inspect target file inside zip.
|
||
const zipBytes = readFileSync(zipPath);
|
||
const zip = await JSZip.loadAsync(zipBytes);
|
||
const metaFile = zip.file('meta.json');
|
||
// ... A26.1..A26.6 structural checks
|
||
const mergedPassed = mergedChecks.every((c) => c.passed);
|
||
return { passed: mergedPassed, name: pageResult.name, checks: mergedChecks,
|
||
diagnostics: mergedDiagnostics, error: pageResult.error };
|
||
}
|
||
```
|
||
|
||
**Use for:** All four Plans 03-01..04 — same 3-phase shape. Plan 03-01 reads `rrweb/session.json`, Plan 03-02 reads `logs/events.json`, Plan 03-03 reads `logs/events.json` for sentinel grep.
|
||
|
||
**`findLatestZip` chain-off helper** — lines 1395-1406 (must be present; if Plan 03-* runs in a fresh wave the helper is already shipped by Plan 02-04):
|
||
|
||
```typescript
|
||
function findLatestZip(downloadsDir: string): string | null {
|
||
const candidates = readdirSync(downloadsDir).filter(isZipFilename);
|
||
if (candidates.length === 0) {
|
||
return null;
|
||
}
|
||
const withMtimes = candidates.map((name) => ({
|
||
name,
|
||
mtimeMs: statSync(resolvePath(downloadsDir, name)).mtimeMs,
|
||
}));
|
||
withMtimes.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
||
return resolvePath(downloadsDir, withMtimes[0].name);
|
||
}
|
||
```
|
||
|
||
**Use for:** Every Plan 03-01..04 driver that reads from downloadsDir. Already present in file (no re-implementation).
|
||
|
||
**EventType enum grep pattern (Plan 03-01)** — derived from RESEARCH §"Code Examples A29" (verified against `@rrweb/types/dist/index.d.ts:186-194`):
|
||
|
||
```typescript
|
||
// Plan 03-01 driveA29 — structural rrweb event-shape grep (RESEARCH-cited pattern; no codebase analog)
|
||
import { EventType } from '@rrweb/types'; // FullSnapshot=2, IncrementalSnapshot=3, Meta=4
|
||
|
||
const rrwebRaw = await zip.file('rrweb/session.json')!.async('text');
|
||
const events: Array<{ type: number; timestamp: number }> = JSON.parse(rrwebRaw);
|
||
|
||
mergedChecks.push({
|
||
name: 'A29.1: rrweb/session.json contains > 0 events',
|
||
expected: '>0',
|
||
actual: events.length,
|
||
passed: events.length > 0,
|
||
});
|
||
mergedChecks.push({
|
||
name: `A29.2: rrweb emitted at least one Meta event (EventType.Meta=${EventType.Meta})`,
|
||
expected: 'has Meta',
|
||
actual: events.some((e) => e.type === EventType.Meta),
|
||
passed: events.some((e) => e.type === EventType.Meta),
|
||
});
|
||
mergedChecks.push({
|
||
name: `A29.3: rrweb emitted at least one FullSnapshot (EventType.FullSnapshot=${EventType.FullSnapshot})`,
|
||
expected: 'has FullSnapshot',
|
||
actual: events.some((e) => e.type === EventType.FullSnapshot),
|
||
passed: events.some((e) => e.type === EventType.FullSnapshot),
|
||
});
|
||
mergedChecks.push({
|
||
name: `A29.4: rrweb emitted at least one IncrementalSnapshot (EventType.IncrementalSnapshot=${EventType.IncrementalSnapshot})`,
|
||
expected: 'has IncrementalSnapshot',
|
||
actual: events.some((e) => e.type === EventType.IncrementalSnapshot),
|
||
passed: events.some((e) => e.type === EventType.IncrementalSnapshot),
|
||
});
|
||
```
|
||
|
||
**Use for:** Plan 03-01 only. RESEARCH §"Pitfall 1" warns: synthetic probe HTML needs at least one DOM mutation between load and SAVE for `IncrementalSnapshot` to fire — Plan 03-01 driver MUST trigger one via `page.evaluate(() => document.body.appendChild(...))` or `page.click('#probe-modal-trigger')`.
|
||
|
||
**UserEvent type-grep pattern (Plan 03-02)** — derived from RESEARCH §"Code Examples" pattern 2 + `src/shared/types.ts:124-131`:
|
||
|
||
```typescript
|
||
// Plan 03-02 driveA30 — events.json grep for 5 UserEvent.type values
|
||
import type { UserEvent } from '../../../src/shared/types';
|
||
|
||
const eventsRaw = await zip.file('logs/events.json')!.async('text');
|
||
const userEvents: UserEvent[] = JSON.parse(eventsRaw);
|
||
|
||
const EXPECTED_TYPES = ['click', 'input', 'navigation', 'js_error', 'network_error'] as const;
|
||
for (const expectedType of EXPECTED_TYPES) {
|
||
mergedChecks.push({
|
||
name: `A30.<i>: logs/events.json contains at least one '${expectedType}' event`,
|
||
expected: `>=1 ${expectedType}`,
|
||
actual: userEvents.filter((e) => e.type === expectedType).length,
|
||
passed: userEvents.some((e) => e.type === expectedType),
|
||
});
|
||
}
|
||
```
|
||
|
||
**Negative-assertion pattern (Plan 03-03 password sentinel grep)** — derived from RESEARCH §"Pattern 3" (no codebase analog; closest analog is driveA27's A27.7/A27.8 absence assertions at harness-page-driver.ts:1724-1737):
|
||
|
||
```typescript
|
||
// Plan 03-03 driveA31 — sentinel absence from logs/events.json
|
||
const SENTINEL = 'secret-do-not-log-123'; // fixed; not a real secret
|
||
const eventsRaw = await zip.file('logs/events.json')!.async('text');
|
||
const userEvents: UserEvent[] = JSON.parse(eventsRaw);
|
||
|
||
const eventsContainingSentinel = userEvents.filter(
|
||
(e) => e.value !== undefined && e.value.includes(SENTINEL),
|
||
);
|
||
mergedChecks.push({
|
||
name: 'A31.1: password sentinel ABSENT from logs/events.json (existing src/content/index.ts:82 filter VERIFIED)',
|
||
expected: 'absent (0 events containing sentinel)',
|
||
actual: `${eventsContainingSentinel.length} events containing sentinel`,
|
||
passed: eventsContainingSentinel.length === 0,
|
||
});
|
||
```
|
||
|
||
**Filter-pipeline form (no `continue`)** — driveA28 lines 1808-1810 (project style; CLAUDE.md "Control Flow" §):
|
||
|
||
```typescript
|
||
const actualPaths: string[] = Object.keys(zip.files)
|
||
.filter((path) => !zip.files[path].dir)
|
||
.sort();
|
||
```
|
||
|
||
**Use for:** Any enumeration loop in Plans 03-01..04. No `for...of` + `if (...) continue;` shape.
|
||
|
||
**Tab-cleanup safety pattern (T-02-04-04)** — assertA27 lines 3271-3289 (try/catch silent-ignore on already-closed):
|
||
|
||
```typescript
|
||
} finally {
|
||
// T-02-04-04 mitigation: cleanup tabs with silent-ignore on already-closed.
|
||
if (tabAId !== undefined) {
|
||
try { await chrome.tabs.remove(tabAId); }
|
||
catch (rmErr) { diag(result, `(tabA cleanup ignored: ${String(rmErr)})`); }
|
||
}
|
||
if (tabBId !== undefined) {
|
||
try { await chrome.tabs.remove(tabBId); }
|
||
catch (rmErr) { diag(result, `(tabB cleanup ignored: ${String(rmErr)})`); }
|
||
}
|
||
}
|
||
```
|
||
|
||
**Use for:** Plan 03-02 if event-log trigger strategy involves opening a probe tab (e.g., `https://example.com` for js_error/network_error triggers).
|
||
|
||
---
|
||
|
||
### `tests/uat/harness.test.ts` (orchestrator wiring)
|
||
|
||
**Analog:** `tests/uat/harness.test.ts:65-130 + 316-335 + 406-430` (Plan 02-04)
|
||
|
||
**Import section pattern** — lines 92-100 (add A24-A28 imports per plan):
|
||
|
||
```typescript
|
||
// Plan 02-04 Task 1 — D-P2-01 empirical Blob URL verification
|
||
driveA24,
|
||
// Plan 02-04 Task 2 — REQ-archive-export-latency (5s ceiling)
|
||
driveA25,
|
||
// Plan 02-04 Task 3 — meta.json 8-field + multi-tab strict + REQ-archive-layout
|
||
driveA26,
|
||
driveA27,
|
||
driveA28,
|
||
getManifestVersion,
|
||
} from './lib/harness-page-driver';
|
||
```
|
||
|
||
**Use for:** Each Plan 03-01..04 appends ONE `driveA<NN>` import + a section banner comment naming the plan and assertion purpose.
|
||
|
||
**Wrapped-driver pattern (when driver needs `downloadsDir` beyond the standard `Page` arg)** — lines 322-335 (Plan 02-04 wrappers):
|
||
|
||
```typescript
|
||
// Plan 02-04 Task 2 — driveA25 needs downloadsDir for the host-side
|
||
// dispatch→file-on-disk latency check (mirrors A5/A12/A13 wrapping).
|
||
const driveA25Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
|
||
(page) => driveA25(page, handles.downloadsDir);
|
||
// Plan 02-04 Task 3 — driveA26/A27/A28 need downloadsDir for host-side
|
||
// zip inspection (JSZip-parse meta.json + zip-layout enumeration). A26
|
||
// chains off A25's zip (no new SAVE); A27 owns its SAVE (multi-tab);
|
||
// A28 chains off A27's zip (no new SAVE).
|
||
const driveA26Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
|
||
(page) => driveA26(page, handles.downloadsDir);
|
||
const driveA27Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
|
||
(page) => driveA27(page, handles.downloadsDir);
|
||
const driveA28Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
|
||
(page) => driveA28(page, handles.downloadsDir);
|
||
```
|
||
|
||
**Use for:** Plans 03-01..03 (all need `downloadsDir` to find the latest zip). Same wrapping pattern; one wrapper per driver.
|
||
|
||
**Drivers-array push pattern with explanatory banner comment** — lines 397-430 (Plan 02-04):
|
||
|
||
```typescript
|
||
// Plan 02-04 Task 1 A24: D-P2-01 empirical Blob URL verification.
|
||
// Installs chrome.downloads.onCreated listener cross-realm, dispatches
|
||
// SAVE_ARCHIVE, captures the download URL, asserts the `blob:` prefix
|
||
// (closes audit P0-6 end-to-end through a real Chrome instance +
|
||
// the offscreen mint round-trip + chrome.downloads platform call).
|
||
{ name: 'A24', drive: driveA24 },
|
||
// Plan 02-04 Task 2 A25: REQ-archive-export-latency / SPEC §10 #6.
|
||
{ name: 'A25', drive: driveA25Wrapped },
|
||
// Plan 02-04 Task 3 A26: D-P2-02 + D-P2-03 meta.json 8-field shape.
|
||
{ name: 'A26', drive: driveA26Wrapped },
|
||
// Plan 02-04 Task 3 A27: STRICT multi-tab urls[] post DEC-011 Amendment 1.
|
||
{ name: 'A27', drive: driveA27Wrapped },
|
||
// Plan 02-04 Task 3 A28: REQ-archive-layout strict 5-entry zip-layout.
|
||
{ name: 'A28', drive: driveA28Wrapped },
|
||
];
|
||
```
|
||
|
||
**Use for:** Plans 03-01..04 each append ONE `{ name: 'A<NN>', drive: driveA<NN>Wrapped }` entry with a banner comment citing the plan, the SPEC §10 # being verified, and the chaining strategy (own SAVE vs chain off prior).
|
||
|
||
**FORBIDDEN_HOOK_STRINGS UAT A0 mirror** — lines 115-130 (12 entries):
|
||
|
||
```typescript
|
||
const FORBIDDEN_HOOK_STRINGS: ReadonlyArray<string> = [
|
||
'__mokoshTest',
|
||
'setCurrentStream',
|
||
'setSegmentCountGetter',
|
||
'installFakeDisplayMedia',
|
||
'uninstallFakeDisplayMedia',
|
||
'dispatchEndedOnTrack',
|
||
'getSegmentCount',
|
||
'__mokoshOffscreenQuery',
|
||
'get-display-surface',
|
||
'get-segment-count',
|
||
// Plan 01-14 A23 surface — lockstep with unit-gate inventory at
|
||
// tests/background/no-test-hooks-in-prod-bundle.test.ts:105.
|
||
'lastGetDisplayMediaConstraints',
|
||
'get-last-getDisplayMedia-constraints',
|
||
];
|
||
```
|
||
|
||
**Lockstep requirement:** ANY new test-only `__MOKOSH_UAT__`-gated symbol introduced by Plan 03-* MUST be added to BOTH this UAT A0 mirror AND the unit-gate at `tests/background/no-test-hooks-in-prod-bundle.test.ts:108-126`. RESEARCH §"Anti-Patterns" + A6 in Assumptions Log: Phase 3 plans are EXPECTED to stay at 12 entries (rrweb + chrome.downloads + chrome.tabs are production surfaces). The MEDIUM-risk exception (A6) is Plan 03-04 Page.metrics scaffolding — Page.metrics is host-side (not bundled into page-realm); no new symbol expected.
|
||
|
||
---
|
||
|
||
### `tests/uat/extension-page-harness.html` (probe HTML append for Plan 03-01)
|
||
|
||
**Analog:** Current state of the file (lines 17-22) — append below, do NOT touch the head.
|
||
|
||
**Existing scaffold** (full file, 24 lines):
|
||
|
||
```html
|
||
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Mokosh UAT Harness (extension-internal page)</title>
|
||
<!--
|
||
Plan 01-12 Wave 6: load the canonical token system on the harness
|
||
page so A18 (Lora WOFF2 reachable via @font-face) and A21
|
||
(--mks-font-display resolves to Lora) both have the @font-face
|
||
rules + CSS custom properties + .mks-display-1 class visible via
|
||
document.styleSheets + getComputedStyle. Vite's crxjs plugin
|
||
handles the asset path rebasing at build:test time so the link
|
||
resolves under dist-test/ even with content-hashed asset filenames.
|
||
-->
|
||
<link rel="stylesheet" href="../../src/shared/tokens.css">
|
||
</head>
|
||
<body>
|
||
<h1>Mokosh UAT — extension-internal page harness</h1>
|
||
<p>This page lives at <code>chrome-extension://<id>/tests/uat/extension-page-harness.html</code>.</p>
|
||
<p>Puppeteer navigates a tab here and drives assertions via <code>window.__mokoshHarness.*</code>.</p>
|
||
<pre id="status">Ready.</pre>
|
||
<script type="module" src="./extension-page-harness.ts"></script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
**Probe-HTML append target:** between `<pre id="status">` (line 21) and `<script type="module">` (line 22). Form + table + modal-trigger HTML per RESEARCH §"Pitfall 4" (NO `<textarea>`; rrweb alpha.4 bug #1596).
|
||
|
||
**Critical constraints (per RESEARCH Assumption A2 + A5):**
|
||
- DO NOT modify the `<head>` block — `<link rel="stylesheet" href="../../src/shared/tokens.css">` is load-bearing for A18 + A21.
|
||
- DO NOT add stylesheet imports for the probe HTML — inline styles via `style="..."` or via the existing tokens.css `--mks-*` vars (defensive).
|
||
- Probe HTML stays minimal: 1 form (`<input type="text">` + `<input type="email">` + `<input type="password">` + `<button>`), 1 small table (2-3 rows), 1 hidden modal `<div>` toggled by a trigger button. Per RESEARCH §"Pitfall 1": at least one DOM mutation is required pre-SAVE for `IncrementalSnapshot` to fire.
|
||
|
||
---
|
||
|
||
### `.planning/phases/03-.../03-VERIFICATION.md` (NEW; aggregator)
|
||
|
||
**Analogs:**
|
||
- `.planning/phases/02-stabilize-export-pipeline/02-VERIFICATION.md` (T5 override pattern + `overrides_applied` field; closest match for Plan 03-05 charter)
|
||
- `.planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md` (per-requirement scorecard + cross-cutting gates + operator empirical acks + deferred items)
|
||
|
||
**Frontmatter shape (from `02-VERIFICATION.md:1-32`):**
|
||
|
||
```yaml
|
||
---
|
||
phase: 02-stabilize-export-pipeline
|
||
verified: 2026-05-20T18:00:00Z
|
||
status: passed
|
||
score: 5/5 must-haves verified
|
||
overrides_applied: 1
|
||
override_notes:
|
||
- dimension: "T5 operator empirical UAT (Plan 02-04 Task 4 Step 2)"
|
||
initial_status: "UNCERTAIN (human_needed)"
|
||
override_to: "VERIFIED"
|
||
rationale: |
|
||
User explicit delegation 2026-05-20: "why do i need to do all of this? It's on you to test..."
|
||
established the operating principle that automation covers what automation can cover.
|
||
Per saved memory feedback-trust-harness-over-manual-uat.md (created same session 2026-05-20),
|
||
operator empirical UAT is reserved for surfaces automation genuinely cannot verify (brand
|
||
judgment, ergonomics). For Phase 2 deep-pipeline work (Blob URL, meta schema, archive layout),
|
||
every operator-checklist surface IS covered by harness assertions:
|
||
- (a) <5s latency → A25 empirical via Puppeteer (real Chrome)
|
||
- ... (5 sub-bullets covering each empirical channel)
|
||
... (citation block ending with "The harness IS the canonical Phase 2 verification per the current memory.")
|
||
human_verification: []
|
||
---
|
||
```
|
||
|
||
**Phase 3 adaptation (per CONTEXT D-P3-02 + D-P3-04):**
|
||
|
||
```yaml
|
||
---
|
||
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: 1
|
||
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.
|
||
---
|
||
```
|
||
|
||
**Per-Requirement Scorecard table** (from `01-VERIFICATION.md:32-46`):
|
||
|
||
```markdown
|
||
## Per-Requirement Scorecard
|
||
|
||
| # | Requirement / Decision | Evidence | Status |
|
||
|---|---|---|---|
|
||
| 1 | REQ-video-ring-buffer | `src/offscreen/recorder.ts:52-58` (`MAX_SEGMENTS=3 × 10s = 30s`); ... | PASS |
|
||
| 2 | REQ-install-clean | `npm run build` exit 0, 9 chunks produced ... | PASS |
|
||
| ... |
|
||
```
|
||
|
||
**Phase 3 adaptation:** §10 #1-#9 row-by-row evidence table with Phase-citation (Phase 1 / Phase 2 / Phase 3) + plan-citation (e.g., "Plan 03-01 A29 GREEN") + commit-citation per criterion.
|
||
|
||
**Cross-Cutting Gates table** (from `01-VERIFICATION.md:48-56`):
|
||
|
||
```markdown
|
||
## Cross-Cutting Gates
|
||
|
||
| Gate | Evidence | Status |
|
||
|---|---|---|
|
||
| vitest | 28 files / **153 tests / 153 GREEN** ... | PASS |
|
||
| UAT harness | 24 drivers (A0 grep gate + A1..A14 + A15..A17 + A18..A22 + A23) ... | PASS |
|
||
| Tier-1 grep gate | 12 FORBIDDEN_HOOK_STRINGS ... | PASS |
|
||
| Pre-checkpoint bundle gates | 0 `googleapis`/`https://fonts` in dist; 0 test-hook leaks ... | PASS |
|
||
| tsc | `npx tsc --noEmit` exit 0 | PASS |
|
||
```
|
||
|
||
**Phase 3 adaptation:** Update UAT harness row (3X drivers post Plan 03-01..04); preserve baseline vitest + Tier-1 + bundle gates; cite Plan 03-05's own re-verification of all 6 pre-checkpoint bundle gates per saved memory `feedback-pre-checkpoint-bundle-gates.md`.
|
||
|
||
**Operator-Empirical Acks table** (from `01-VERIFICATION.md:70-78`):
|
||
|
||
```markdown
|
||
## Operator-Empirical Acks (verbatim + commit refs)
|
||
|
||
| Date | Plan | Operator response | Commit |
|
||
|---|---|---|---|
|
||
| 2026-05-15 | 01-07 (D-12 + A3 closure) | Chrome playback clean, ffmpeg dry-run exit 0 | cd61cbc |
|
||
| ... |
|
||
```
|
||
|
||
**Phase 3 adaptation:** Single row for the §10 #9 RAM operator ack (when received). Empty until ack lands; then verbatim quote + commit.
|
||
|
||
**Deferred Items section** (from `01-VERIFICATION.md:92-104` + `02-VERIFICATION.md:55-58`):
|
||
|
||
```markdown
|
||
## Forward-Looking Deferred Items (NOT gaps)
|
||
|
||
| Item | Owner | Source |
|
||
|---|---|---|
|
||
| rrweb v2 stable upgrade | Phase 4 | D-P3-03 defer rationale |
|
||
| Programmatic RAM measurement (puppeteer.Page.metrics enhancement OR chrome.devtools Memory API) | Phase 4 | D-P3-04 defer rationale |
|
||
| REQ-password-confidentiality v2 candidate (rrweb v2 maskInputFn + data-sensitive guards) | Phase 4 | D-P3-02 defer rationale |
|
||
| ... |
|
||
```
|
||
|
||
**Phase 3 adaptation:** Pull from CONTEXT §"Deferred" verbatim (10 items including the audit P1 polish + setimmediate polyfill + ROADMAP backfill).
|
||
|
||
---
|
||
|
||
### `tests/uat/lib/ramMetrics.ts` (OPTIONAL — Plan 03-04 Page.metrics scaffolding)
|
||
|
||
**Analog:** NONE in codebase. Closest scaffolding shape: `driveA25` host-side latency measurement pattern (`tests/uat/lib/harness-page-driver.ts:1255-1334`). New pattern; cite RESEARCH §"Code Example A3X" + §"Pitfall 2".
|
||
|
||
**Decision per D-P3-04 + RESEARCH Open Question 3 (recommended: SHIP):** Plan 03-04 ships ~30 lines of Page.metrics scaffolding either inline in `harness-page-driver.ts` (preferred per scope-minimization) OR as a new `tests/uat/lib/ramMetrics.ts` helper if planner prefers separation. RESEARCH §"Open Question 3" recommends shipping with explicit diagnostic copy.
|
||
|
||
**Concrete pattern (per RESEARCH §"Code Example A3X"):**
|
||
|
||
```typescript
|
||
// Plan 03-04 optional driveA3X — Page.metrics() scaffolding (D-P3-04 best-effort)
|
||
async function driveA3X_OptionalRamScaffold(page: Page): Promise<AssertionRecord> {
|
||
// 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;
|
||
|
||
const checks: CheckRecord[] = [
|
||
{
|
||
name: 'A3X.1: Page.metrics returned JSHeapUsedSize',
|
||
expected: '>= 0',
|
||
actual: String(jsHeapMB),
|
||
passed: jsHeapMB >= 0,
|
||
},
|
||
{
|
||
name: 'A3X.2: Page-realm JS heap < 50 MB (NOTE: scaffolding only; SW context excluded)',
|
||
expected: '< 50 MB',
|
||
actual: `${jsHeapMB.toFixed(2)} MB`,
|
||
passed: jsHeapMB < 50,
|
||
},
|
||
];
|
||
|
||
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.',
|
||
],
|
||
};
|
||
}
|
||
```
|
||
|
||
**Critical diagnostic copy gate (per RESEARCH §"Pitfall 2"):** The scaffolding MUST emit `'NOTE: page-realm only; SW context excluded'` in diagnostics so an operator does not interpret a green automation number as full §10 #9 closure. The binding §10 #9 gate is the operator chrome://memory-internals check; scaffolding is informational only.
|
||
|
||
---
|
||
|
||
## Shared Patterns
|
||
|
||
### Approach B Harness Extension Lockstep
|
||
|
||
**Source:** Plan 02-04 + Plan 01-13 (Approach B foundation)
|
||
**Apply to:** Plans 03-01, 03-02, 03-03, 03-04 (every Phase 3 plan that ships a new assertion)
|
||
|
||
The 3-file lockstep across each new assertion:
|
||
|
||
1. **`tests/uat/extension-page-harness.ts`** — append `async function assertA<NN>(): Promise<AssertionResult>` (page-side stub OR orchestrator) + extend `declare global` interface + add entry to `window.__mokoshHarness = {...}` object literal.
|
||
2. **`tests/uat/lib/harness-page-driver.ts`** — append `export async function driveA<NN>(page: Page, downloadsDir: string): Promise<AssertionRecord>` (3-phase pattern: page.evaluate stub → findLatestZip → JSZip read).
|
||
3. **`tests/uat/harness.test.ts`** — append `driveA<NN>` import + wrapped-driver const (if `downloadsDir` is needed) + drivers-array push with banner comment.
|
||
|
||
Missing ANY one of the three breaks the orchestrator (either at the `harness.assertA<NN>()` page.evaluate call OR at the `for (const drive of drivers)` loop). Plan-checker MUST validate all three are touched per new A-number.
|
||
|
||
### Pre-Checkpoint Bundle Gates (6/6 Standard Inventory)
|
||
|
||
**Source:** Saved memory `feedback-pre-checkpoint-bundle-gates.md` + `02-04-SUMMARY.md:158-168` (canonical 7-gate table; Phase 2 closure precedent)
|
||
**Apply to:** Plan 03-05 (before VERIFICATION.md is written) — ALL plans before any operator step
|
||
|
||
Standard 6/6 inventory (Plan 02-04 ran 7; Plan 03-05 should run the same):
|
||
|
||
| Gate | Concrete Check |
|
||
|------|----------------|
|
||
| Gate 1 | `npm run build` exit 0 |
|
||
| Gate 2 | SW CSP-safety: `grep -rn "new Function\\|eval(" dist/assets/` — 1 documented exception (setimmediate polyfill) |
|
||
| Gate 3 | SW Node-globals: `grep -rn "Buffer.from\\|Buffer.alloc\\|require(" dist/assets/index.ts-*.js` — 0 hits |
|
||
| Gate 4 | DOM-globals: `grep -rn "window.\\|document." dist/assets/index.ts-*.js` — bundled-lib idiom (typeof-guarded) |
|
||
| Gate 5 | Tier-1 SW-bundle-import gate (`tests/background/sw-bundle-import.test.ts`) GREEN |
|
||
| Gate 6 | FORBIDDEN_HOOK_STRINGS unit gate (`tests/background/no-test-hooks-in-prod-bundle.test.ts`) — 12 strings, 0 hits each |
|
||
| Gate 7 | Manifest validation gates: i18n + locale-parity + build — all GREEN |
|
||
|
||
### T5 Override Pattern (Harness-Coverage-as-Verification)
|
||
|
||
**Source:** `02-VERIFICATION.md:1-31` + saved memory `feedback-trust-harness-over-manual-uat.md`
|
||
**Apply to:** Plan 03-05 VERIFICATION.md — for §10 criteria where harness coverage exists (§10 #4 / #5 / #8) but NOT for §10 #9 RAM (genuine non-automatable)
|
||
|
||
When verifier returns `human_needed` for a criterion AND a harness assertion empirically covers the same surface:
|
||
- Move `human_verification` entry to `overrides_applied` block in frontmatter
|
||
- Include explicit user-delegation citation (date + verbatim quote, if recorded) + saved-memory reference
|
||
- Sub-bullet each harness assertion that covers the surface
|
||
- Mark `status: passed` rather than `status: human_needed`
|
||
|
||
**Counter-example (do NOT override):** §10 #9 RAM measurement — Page.metrics scaffolding is page-realm only (RESEARCH §"Pitfall 2"); the SW context can ONLY be measured via operator-driven chrome://memory-internals. This is the canonical `human_verification` entry per D-P3-04.
|
||
|
||
### Filter-Pipeline Form (No `continue`)
|
||
|
||
**Source:** `~/.claude/CLAUDE.md` "Control Flow" § + Plan 02-04 driveA28 (lines 1808-1810)
|
||
**Apply to:** Any enumeration loop in Plans 03-01..04
|
||
|
||
```typescript
|
||
// PREFERRED — filter pipeline
|
||
const matchingEvents = userEvents.filter((e) => e.type === 'click');
|
||
const realHttpUrls = urls.filter((u) => /^https?:\/\//.test(u));
|
||
|
||
// AVOID — for...of + continue
|
||
// for (const e of userEvents) { if (e.type !== 'click') continue; ... }
|
||
```
|
||
|
||
### Production-Surface-Only New Assertions (FORBIDDEN_HOOK_STRINGS stays at 12)
|
||
|
||
**Source:** `02-04-SUMMARY.md:99` + RESEARCH Assumption A6
|
||
**Apply to:** Plans 03-01..04 — riding production surfaces (rrweb.record + content-script GET_RRWEB_EVENTS + chrome.tabs.create + chrome.downloads.onCreated)
|
||
|
||
Phase 3 assertions ride production surfaces verified by Plans 01-13 + 02-04. NO new `__MOKOSH_UAT__`-gated test-only symbols expected. If Plan 03-04 Page.metrics scaffolding requires a new bridge op (e.g., `get-page-metrics` from offscreen → harness), that scenario adds 1-2 entries to BOTH FORBIDDEN_HOOK_STRINGS inventories AND requires Vite define-token tree-shake gating. Plan-checker MUST validate inventory count.
|
||
|
||
### Anti-Pattern: Re-reading the Same Range
|
||
|
||
**Source:** CLAUDE.md operator guidance
|
||
**Apply to:** All Phase 3 implementation work — read each source file ONCE, extract patterns, then implement
|
||
|
||
---
|
||
|
||
## No Analog Found
|
||
|
||
| File | Role | Data Flow | Reason / Mitigation |
|
||
|------|------|-----------|---------------------|
|
||
| `tests/uat/lib/ramMetrics.ts` (if Plan 03-04 chooses separate-file scaffolding) | test (host-side utility) | request-response (CDP Performance.getMetrics) | Page.metrics() has no codebase precedent. RESEARCH §"Code Example A3X" provides the canonical implementation; planner uses verbatim. Plan 03-04 may inline this in `harness-page-driver.ts` instead of new file (scope-minimization preferred). |
|
||
|
||
All other anticipated files have direct analogs from Plan 02-04 (most recent Approach B precedent).
|
||
|
||
## Per-Plan Anticipated File Touch Inventory
|
||
|
||
| Plan | Files Modified | Files Created | Pattern Source |
|
||
|------|----------------|---------------|----------------|
|
||
| 03-01 (rrweb DOM §10 #4) | `tests/uat/extension-page-harness.ts` + `tests/uat/extension-page-harness.html` (probe HTML) + `tests/uat/lib/harness-page-driver.ts` + `tests/uat/harness.test.ts` | — | Plan 02-04 (assertA26/driveA26 + JSZip + EventType import) |
|
||
| 03-02 (event-log §10 #5) | `tests/uat/extension-page-harness.ts` + `tests/uat/lib/harness-page-driver.ts` + `tests/uat/harness.test.ts` | — | Plan 02-04 (driveA26 chain pattern + UserEvent grep from src/shared/types.ts:124-131) |
|
||
| 03-03 (password-filter §10 #8 PARTIAL) | `tests/uat/extension-page-harness.ts` + `tests/uat/lib/harness-page-driver.ts` + `tests/uat/harness.test.ts` (possibly probe HTML if Plan 03-01 didn't add `<input type="password">`) | — | RESEARCH §"Pattern 3" + driveA27 absence-assertion shape |
|
||
| 03-04 (RAM §10 #9 best-effort) | OPTIONAL: `tests/uat/lib/harness-page-driver.ts` (Page.metrics inline) + `tests/uat/harness.test.ts` (wrapped driver) | OPTIONAL: `tests/uat/lib/ramMetrics.ts` | RESEARCH §"Code Example A3X" (no codebase analog) |
|
||
| 03-05 (§10 sweep aggregator) | — | `.planning/phases/03-.../03-VERIFICATION.md` | `01-VERIFICATION.md` (scorecard structure) + `02-VERIFICATION.md` (T5 override frontmatter) |
|
||
|
||
## Wave Sequencing Note
|
||
|
||
Per CONTEXT §"Claude's Discretion" + RESEARCH Open Question 1 + Pitfall 6: Plans 03-01..04 modify the SAME three harness files (`extension-page-harness.ts`, `harness-page-driver.ts`, `harness.test.ts`). RESEARCH recommends SEQUENTIAL execution within Wave 2: 03-01 → 03-02 → 03-03 → 03-04. Plan-checker validates `files_modified` overlap audit. Plan 03-05 (VERIFICATION.md aggregator) runs in Wave 3 after 03-01..04 land + UAT harness reaches 33/33 (or 32/32 if Plan 03-04 skips scaffolding).
|
||
|
||
## Metadata
|
||
|
||
**Analog search scope:** `tests/uat/`, `.planning/phases/01-stabilize-video-pipeline/`, `.planning/phases/02-stabilize-export-pipeline/`, `src/content/index.ts`, `src/shared/types.ts`, `node_modules/@rrweb/types/dist/index.d.ts`
|
||
|
||
**Files read (each ONCE, non-overlapping ranges):**
|
||
- `tests/uat/extension-page-harness.ts:2840-3409` (assertA24-A28 + __mokoshHarness wire)
|
||
- `tests/uat/lib/harness-page-driver.ts:1-130, 1180-1349, 1360-1599, 1700-1849` (4 non-overlapping ranges; covers driveA24/A25/A26/A27/A28 + findLatestZip + A28_EXPECTED_PATHS + imports)
|
||
- `tests/uat/harness.test.ts:1-100, 100-260, 320-440` (3 non-overlapping ranges; covers orchestrator imports + FORBIDDEN_HOOK_STRINGS + A0 gate + drivers wrap + push)
|
||
- `tests/uat/extension-page-harness.html` (full; 24 lines)
|
||
- `tests/uat/lib/zip.ts` (full; 140 lines)
|
||
- `tests/background/no-test-hooks-in-prod-bundle.test.ts:95-175` (FORBIDDEN_HOOK_STRINGS unit-gate)
|
||
- `.planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md` (full; 123 lines)
|
||
- `.planning/phases/02-stabilize-export-pipeline/02-VERIFICATION.md` (full; 166 lines)
|
||
- `.planning/phases/02-stabilize-export-pipeline/02-04-SUMMARY.md` (full; 259 lines)
|
||
- `src/content/index.ts:75-89` (password filter at line 82) + grep for rrweb wiring anchors
|
||
- `src/shared/types.ts:120-155` (UserEvent + SessionMetadata)
|
||
- `node_modules/@rrweb/types/dist/index.d.ts:186-194` (EventType enum)
|
||
|
||
**Pattern extraction date:** 2026-05-20
|