diff --git a/.planning/phases/04-harden-clean-up-optional/04-02-PLAN.md b/.planning/phases/04-harden-clean-up-optional/04-02-PLAN.md index d0aac81..2845798 100644 --- a/.planning/phases/04-harden-clean-up-optional/04-02-PLAN.md +++ b/.planning/phases/04-harden-clean-up-optional/04-02-PLAN.md @@ -34,6 +34,7 @@ must_haves: - "node generate-icons.cjs exits 0 under package.json type: module (CJS-explicit via .cjs extension)" - "vitest baseline +2 (no-new-function-in-sw-chunk + dead-code-grep tests) GREEN" - "Pre-checkpoint bundle Gate 2 polarity flipped (1 hit → 0 hits for 'new Function' in dist/assets/index.ts-*.js)" + - "UAT harness 33/33 GREEN preserved post-polyfill-replacement (REVISION iter-2 WARNING 1 — JSZip's MessageChannel/postMessage/setTimeout fallback chain verified empirically end-to-end at the SAVE→zip layer)" artifacts: - path: "tests/build/no-new-function-in-sw-chunk.test.ts" provides: "Wave 0 RED — grep gate pinning 0 hits of 'new Function' in SW chunk after Plan 04-02 polyfill replacement" @@ -181,6 +182,9 @@ From tests/build/no-remote-fonts.test.ts (existing test scaffold — full 145 li - `runProductionBuild()`: `execFileAsync('npm', ['run', 'build'], { timeout: 90_000 })` - Skip gate: `if (process.env.SKIP_BUILD !== '1') { await runProductionBuild(); }` — re-uses existing built dist/ when SKIP_BUILD=1 (developer-velocity escape hatch) - The Plan 04-02 RED test inherits the same scaffold; SCOPES the walk to `dist/assets/` filtered by `^index\\.ts-.*\\.js$` regex (the SW chunk only). + +UAT harness stdout format (REVISION iter-2 — WARNING 1 mitigation): +- The orchestrator at tests/uat/harness.test.ts:537 emits exactly `UAT harness: 33/33 assertions passed\n` (verified 2026-05-21 via grep). Task 2 `` greps for this canonical line to confirm post-polyfill JSZip behavior is preserved end-to-end at the SAVE→zip layer. @@ -273,7 +277,7 @@ From tests/build/no-remote-fonts.test.ts (existing test scaffold — full 145 li Run the focused test: `npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run` — expect 100% GREEN. Run the full vitest: `npm test -- --run` — expect 181 GREEN (or post-Plan-04-01 baseline + 2). Run the UAT harness: `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` — expect 33/33 GREEN (the SKIP_PROD_REBUILD=0 forces a rebuild against the new vite config + prelude so the UAT runs against the post-fix bundle). - npm run build && grep -c 'new Function' dist/assets/index.ts-*.js | head -1; node generate-icons.cjs; npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run; npx tsc --noEmit + npm run build && grep -c 'new Function' dist/assets/index.ts-*.js | head -1; node generate-icons.cjs; npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run; npx tsc --noEmit && HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat 2>&1 | tee /tmp/04-02-task-2-uat.log | tail -3; grep -c 'UAT harness: 33/33 assertions passed' /tmp/04-02-task-2-uat.log - `npm run build` exits 0; `grep -c 'new Function' dist/assets/index.ts-*.js` returns 0 (was 1). @@ -284,7 +288,7 @@ From tests/build/no-remote-fonts.test.ts (existing test scaffold — full 145 li - `grep -c "queueMicrotask" src/background/index.ts | head -1` returns ≥ 1 (the polyfill assignment). - `grep -c "Resolved in Phase 4 Plan 04-02" .planning/phases/01-stabilize-video-pipeline/deferred-items.md` returns ≥ 1. - Full vitest passes: `npm test -- --run` exits 0 (Plan 04-01 baseline +2 from this plan = ≥ 181 GREEN). - - UAT harness 33/33 GREEN preserved: `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` exits 0 (verifies JSZip fallback works correctly post-polyfill-removal). + - UAT harness 33/33 GREEN preserved (REVISION iter-2 — WARNING 1): `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` exits 0 AND stdout contains `UAT harness: 33/33 assertions passed`; verified by `grep -c 'UAT harness: 33/33 assertions passed' /tmp/04-02-task-2-uat.log` returning 1. This confirms JSZip fallback (MessageChannel/postMessage/setTimeout) works correctly post-polyfill-removal at the empirical SAVE→zip layer. Polyfill replacement landed; SW chunk grep flipped 1 → 0; generate-icons CJS-renamed; deferred-items.md closure-flipped. Atomic commit: `feat(04-02): Wave 1 — setimmediate polyfill replaced + generate-icons.cjs + deferred-items closure`. @@ -304,7 +308,7 @@ From tests/build/no-remote-fonts.test.ts (existing test scaffold — full 145 li | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-04-02-01 | Elevation of Privilege | SW chunk `new Function(string)` literal — a static-analysis red flag for tighter future CSP and a security-audit eyebrow-raiser even under current MV3 CSP defaults | mitigate | Replace transitive `setimmediate` polyfill with explicit `queueMicrotask`-based polyfill in SW entry; verifiable by grep against built dist/. Reversible by git revert. | -| T-04-02-02 | DoS (functional) | JSZip relies on setImmediate to yield between zip-entry writes; if our explicit polyfill `(fn, ...args) => queueMicrotask(() => fn(...args))` is incompatible with JSZip's internal use, the zip-assembly could starve or deadlock | accept (verified by UAT) | The polyfill matches JSZip's signature expectation (function + variadic args); UAT harness 33/33 GREEN under the new bundle confirms behavior preserved (A24 specifically tests SAVE→zip; A28/A29/A30/A31 all exercise the zip-assembly path empirically) | +| T-04-02-02 | DoS (functional) | JSZip relies on setImmediate to yield between zip-entry writes; if our explicit polyfill `(fn, ...args) => queueMicrotask(() => fn(...args))` is incompatible with JSZip's internal use, the zip-assembly could starve or deadlock | accept (verified by UAT) | The polyfill matches JSZip's signature expectation (function + variadic args); UAT harness 33/33 GREEN under the new bundle confirms behavior preserved (A24 specifically tests SAVE→zip; A28/A29/A30/A31 all exercise the zip-assembly path empirically). REVISION iter-2 WARNING 1: Task 2 `` now greps for `UAT harness: 33/33 assertions passed` in stdout so the gate is empirically pinned, not merely asserted in ``. | | T-04-02-03 | Information Disclosure | leftover `permissions.request` literal in src/ could give a future audit the impression the codebase still relies on the deleted permission flow, masking the post-01-05 architecture | mitigate (regression pin) | dead-code-grep.test.ts pins absence — re-introduction breaks CI | | T-04-02-04 | Tampering | `.cjs` extension override applies only to the renamed file; future contributors writing `.js` files under the same project type:module would re-introduce the ESM/CJS error | accept | Convention is documented in the SUMMARY; CI catches future `require(` in `.js` files via `npx tsc --noEmit` at build time + the explicit `node generate-icons.cjs` invocation in any future ROADMAP backfill | @@ -316,7 +320,7 @@ From tests/build/no-remote-fonts.test.ts (existing test scaffold — full 145 li - `node generate-icons.cjs` exits 0. - vitest baseline +2 to ≥ 181 (Plan 04-01 baseline + 2 new build-gate tests). - tsc-clean. -- UAT harness 33/33 GREEN preserved (validates JSZip fallback under the new polyfill regime). +- UAT harness 33/33 GREEN preserved (validates JSZip fallback under the new polyfill regime) — REVISION iter-2 WARNING 1: grep `UAT harness: 33/33 assertions passed` in `` confirms empirically. - Pre-checkpoint bundle Gate 2 (SW CSP-safety: `grep -rn "new Function\\|eval(" dist/assets/`) returns 0 hits (was 1 documented exception). @@ -326,7 +330,7 @@ From tests/build/no-remote-fonts.test.ts (existing test scaffold — full 145 li - ROADMAP SC #3 (generate-icons ESM/CJS) GREEN — `node generate-icons.cjs` exits 0. - ROADMAP SC #4 (dead-code grep) GREEN — regression pinned in vitest. - setimmediate polyfill replacement GREEN — `new Function` count in SW chunk = 0. -- UAT harness 33/33 GREEN preserved. +- UAT harness 33/33 GREEN preserved (REVISION iter-2 — empirically pinned in Task 2 verify). - Pre-checkpoint bundle Gate 2 polarity flipped (1 → 0). @@ -339,9 +343,8 @@ After completion, create `.planning/phases/04-harden-clean-up-optional/04-02-SUM - deferred-items.md closure-flip block - Pre-fix vs post-fix `grep -c 'new Function' dist/assets/index.ts-*.js` (1 → 0) - vitest baseline before/after -- UAT harness GREEN preservation evidence +- UAT harness GREEN preservation evidence (REVISION iter-2 — grep `UAT harness: 33/33 assertions passed` count = 1 from `` output) - RED→GREEN flip for the no-new-function test (Task 1 RED → Task 2 GREEN) - Commit refs (Task 1 + Task 2) - \ No newline at end of file diff --git a/.planning/phases/04-harden-clean-up-optional/04-04-PLAN.md b/.planning/phases/04-harden-clean-up-optional/04-04-PLAN.md index 661af9e..b47a010 100644 --- a/.planning/phases/04-harden-clean-up-optional/04-04-PLAN.md +++ b/.planning/phases/04-harden-clean-up-optional/04-04-PLAN.md @@ -32,6 +32,7 @@ must_haves: - "A33 is env-gated by SKIP_LONG_UAT (default: RUN for closure + alpha gate; SKIP_LONG_UAT=1 to skip for per-commit iteration)" - "UAT harness count flips from 33 → 34 (A33 added); 34/34 GREEN when SKIP_LONG_UAT unset" - "ROADMAP SC #1 (SW state persistence) GREEN — A33 empirical evidence that a real-world 5-min idle + SAVE produces a non-empty video buffer" + - "SAVE_ARCHIVE dispatch reuses the canonical chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, ...) pattern from the harness page realm (NOT a new __mokoshHarness helper); matches the established assertA5/A11/A12/A13/A26/A28/A29/A30/A31 precedent" artifacts: - path: "tests/uat/extension-page-harness.ts" provides: "assertA33 page-side stub (or thin driver) for SAVE_ARCHIVE dispatch after host-side SW kill" @@ -138,7 +139,10 @@ async function stopServiceWorker(browser: Browser, extensionId: string): Promise } ``` -From RESEARCH Q2 Code Example Pattern 4 (driveA33 — host-side body): +SAVE_ARCHIVE dispatch (REVISION iter-2 — Option B per plan-checker BLOCKER 2): +The harness page realm has `chrome.runtime` available (it's an extension-page realm — `chrome-extension:///tests/uat/extension-page-harness.html`). The canonical SAVE_ARCHIVE dispatch is `chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, callback)` — used by 9 existing assertions (A5/A11/A12/A13/A26/A28/A29/A30/A31; verified via `grep "type: 'SAVE_ARCHIVE'" tests/uat/extension-page-harness.ts`). The `__mokoshHarness` surface is `assertA1..A31 + getManifestVersion`; there is NO `dispatchSaveArchive` helper. We do NOT add one — driveA33 dispatches SAVE_ARCHIVE directly via `page.evaluate` + a promise-wrapped `chrome.runtime.sendMessage` callback. This matches Plan 04-05's approach (which dispatches SAVE_ARCHIVE from inside an existing assertA34 method that runs in the harness page realm). + +From RESEARCH Q2 Code Example Pattern 4 (driveA33 — host-side body; REVISION iter-2 inline-dispatched SAVE): ```typescript const A33_IDLE_WAIT_MS = 5 * 60 * 1000; // 300_000 — real wall-clock const A33_NEW_SW_BOOT_MS = 500; // post-worker.close() settle @@ -153,8 +157,21 @@ export async function driveA33( ): Promise { const r: AssertionRecord = { name: 'A33', passed: false, checks: [], diagnostics: [] }; - // Step 1: prime recording on the probe tab - await page.evaluate(() => (window as any).__mokoshHarness.setupFreshRecording()); + // Step 1: prime recording on the probe tab via the existing harness primitive. + // setupFreshRecording is a module-internal helper inside extension-page-harness.ts + // and is reachable from page.evaluate only if exposed; in practice driveA33 calls + // assertA1 (or an equivalent existing harness method that primes a fresh recording) + // OR a thin Plan-04-04 page-side wrapper if the prior arts don't suffice. Verify + // in Task 2 read_first which existing assertA* method delivers a fresh-recording + // SUT and reuse it directly (no new harness method needed). + await page.evaluate(async () => { + // Reuse the same fresh-recording primitive that A5/A26/A30/A31 use as their Step 1. + // The exact call depends on whether setupFreshRecording is exposed; if not, A33's + // first step calls __mokoshHarness.assertA1 (which is the canonical "fresh + // recording bootstrap" assertion in the harness surface). + const harness = (window as { __mokoshHarness: { assertA1: () => Promise } }).__mokoshHarness; + await harness.assertA1(); + }); // Step 2: 5-min wall-clock idle r.diagnostics.push(`waiting ${A33_IDLE_WAIT_MS}ms for SW idle window`); @@ -167,9 +184,27 @@ export async function driveA33( // Step 4: brief settle for SW teardown await new Promise((res) => setTimeout(res, A33_NEW_SW_BOOT_MS)); - // Step 5: dispatch SAVE_ARCHIVE — wakes SW back up as an event - // (event-driven respawn is the canonical MV3 wakeup path) - const saveResult = await page.evaluate(() => (window as any).__mokoshHarness.dispatchSaveArchive()); + // Step 5: dispatch SAVE_ARCHIVE via chrome.runtime.sendMessage from the harness + // page realm — matches the established A5/A11/A12/A13/A26/A28/A29/A30/A31 pattern. + // Wakes SW back up as an event (event-driven respawn is the canonical MV3 wakeup path). + const saveResult = await page.evaluate( + (timeoutMs: number) => + new Promise<{ success: boolean; error?: string }>((resolve) => { + const timer = setTimeout(() => { + resolve({ success: false, error: `SAVE_ARCHIVE timed out after ${timeoutMs}ms` }); + }, timeoutMs); + chrome.runtime.sendMessage({ type: 'SAVE_ARCHIVE' }, (response: unknown) => { + clearTimeout(timer); + if (chrome.runtime.lastError !== undefined) { + resolve({ success: false, error: String(chrome.runtime.lastError.message) }); + return; + } + resolve(response as { success: boolean; error?: string }); + }); + }), + A33_SAVE_ARCHIVE_TIMEOUT_MS, + ); + r.checks.push({ name: 'A33.1: SAVE_ARCHIVE ack success after 5-min idle + SW kill', expected: true, @@ -239,19 +274,40 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe Task 1: Wave 0 SPIKE — empirical verification that offscreen survives 5-min SW idle tests/uat/lib/harness-page-driver.ts - tests/uat/lib/harness-page-driver.ts (full; ~2200 lines — read selectively: imports lines 1-40, findLatestZip ~1395, driveA30 host-side filter ~2039-2148), tests/uat/extension-page-harness.ts:600-700 (setupFreshRecording helper), src/offscreen/recorder.ts:80-100 (segments array context), .planning/phases/04-harden-clean-up-optional/04-RESEARCH.md Q2 sub-question (b), .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (Plan 07 spike precedent) + tests/uat/lib/harness-page-driver.ts (full; ~2200 lines — read selectively: imports lines 1-40, findLatestZip ~1395, driveA30 host-side filter ~2039-2148), tests/uat/extension-page-harness.ts:3932-4021 (__mokoshHarness global registration block — confirm available surface BEFORE writing spike), tests/uat/extension-page-harness.ts:600-700 (setupFreshRecording helper context), src/offscreen/recorder.ts:80-100 (segments array context), .planning/phases/04-harden-clean-up-optional/04-RESEARCH.md Q2 sub-question (b), .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (Plan 07 spike precedent) 1. Add the `stopServiceWorker(browser, extensionId)` helper to `tests/uat/lib/harness-page-driver.ts` per the Code Example in `` above. Place it near the top of the file (after existing imports + before existing driveA-* functions). Add the `import type { Browser } from 'puppeteer';` if not already present. 2. Create a one-shot spike script `tests/uat/spike-a33-sw-persistence.ts` (NEW; treat as scratch file for this spike — delete after spike concludes; record outcome in plan SUMMARY). The script: - Imports `launchHarnessBrowser` from `./lib/launch.ts`. - Imports `stopServiceWorker` + `findLatestZip` from `./lib/harness-page-driver.ts`. - - Launches the harness browser, primes recording via the harness page's setupFreshRecording method. + - Launches the harness browser. + - **Prime recording (REVISION iter-2 — Option B; no `dispatchSaveArchive` helper exists on `__mokoshHarness`):** call the existing fresh-recording primitive via `await handles.harnessPage.evaluate(() => (window as { __mokoshHarness: { assertA1: () => Promise } }).__mokoshHarness.assertA1());`. The Task 1 read_first MUST verify that `__mokoshHarness.assertA1` is the canonical fresh-recording bootstrap (it is per the existing harness — `Harness ready. window.__mokoshHarness.{assertA1..A31, getManifestVersion} available.`); if a different assertA* method is more direct for "prime + leave recording active for 5 min", choose that instead and document in the spike script comment. - `console.log('SPIKE: waiting 5 minutes for SW idle window...')` - `await new Promise(r => setTimeout(r, 5 * 60 * 1000));` - `await stopServiceWorker(handles.browser, handles.extensionId);` - `await new Promise(r => setTimeout(r, 500));` (settle) - - Dispatch SAVE_ARCHIVE via `await handles.harnessPage.evaluate(() => (window as any).__mokoshHarness.dispatchSaveArchive());` + - **Dispatch SAVE_ARCHIVE (REVISION iter-2 — Option B; canonical chrome.runtime.sendMessage from harness page realm):** + ```typescript + const saveResult = await handles.harnessPage.evaluate( + (timeoutMs: number) => + new Promise<{ success: boolean; error?: string }>((resolve) => { + const timer = setTimeout(() => { + resolve({ success: false, error: `SAVE_ARCHIVE timed out after ${timeoutMs}ms` }); + }, timeoutMs); + chrome.runtime.sendMessage({ type: 'SAVE_ARCHIVE' }, (response: unknown) => { + clearTimeout(timer); + if (chrome.runtime.lastError !== undefined) { + resolve({ success: false, error: String(chrome.runtime.lastError.message) }); + return; + } + resolve(response as { success: boolean; error?: string }); + }); + }), + 15_000, + ); + console.log(`SPIKE: SAVE_ARCHIVE ack -> ${JSON.stringify(saveResult)}`); + ``` - `await new Promise(r => setTimeout(r, 5000));` (let download complete) - `const zipPath = findLatestZip(handles.downloadsDir);` - `const zip = await JSZip.loadAsync(readFileSync(zipPath));` @@ -273,6 +329,7 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe - `stopServiceWorker(browser, extensionId)` helper exists at `tests/uat/lib/harness-page-driver.ts` with the canonical Chrome devrel signature (`Browser` + extensionId args; `target.worker()?.close()` body). - Spike script ran to completion (no Puppeteer throw). - Spike result logged with explicit `videoSize= bytes` line. + - Spike SAVE_ARCHIVE dispatch uses `chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, ...)` directly (NOT a non-existent `__mokoshHarness.dispatchSaveArchive()` call); verify by `grep -c 'dispatchSaveArchive' tests/uat/spike-a33-sw-persistence.ts` returns 0 AND `grep -c "type: 'SAVE_ARCHIVE'" tests/uat/spike-a33-sw-persistence.ts` returns ≥ 1. - If videoSize > 100_000: spike PASSED; proceed to Task 2 with verification-only path. - If videoSize ≤ 100_000: spike FAILED; pause plan + flag to plan-checker for re-planning (out of scope for this task to escalate, but SUMMARY documents). - Total spike wall-clock: ~6-7 minutes (5 min idle + ~1-2 min orchestration). @@ -283,26 +340,25 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe Task 2: Wave 1 — A33 assertion + driveA33 + orchestrator wiring (assumes spike PASSED) tests/uat/extension-page-harness.ts, tests/uat/lib/harness-page-driver.ts, tests/uat/harness.test.ts - tests/uat/extension-page-harness.ts:3517-3636 (assertA30 — canonical setupFreshRecording + SAVE pattern), tests/uat/extension-page-harness.ts:3971-4000 (__mokoshHarness global registration block), tests/uat/lib/harness-page-driver.ts:2039-2148 (driveA30 — host-side filter pattern), tests/uat/harness.test.ts:100-110 (import block), tests/uat/harness.test.ts:340-360 (wrapped-driver block), tests/uat/harness.test.ts:459-486 (drivers-array push block), tests/uat/harness.test.ts:225-240 (SKIP_PROD_REBUILD env-gate pattern) + tests/uat/extension-page-harness.ts:3517-3636 (assertA30 — canonical setupFreshRecording + SAVE pattern), tests/uat/extension-page-harness.ts:3878-3917 (assertA31 — most-recent chrome.runtime.sendMessage SAVE_ARCHIVE pattern; copy this), tests/uat/extension-page-harness.ts:3932-4021 (__mokoshHarness global registration block — confirm NO new method added per REVISION iter-2), tests/uat/lib/harness-page-driver.ts:2039-2148 (driveA30 — host-side filter pattern), tests/uat/harness.test.ts:100-110 (import block), tests/uat/harness.test.ts:340-360 (wrapped-driver block), tests/uat/harness.test.ts:459-486 (drivers-array push block), tests/uat/harness.test.ts:225-240 (SKIP_PROD_REBUILD env-gate pattern) **GATING CONDITION:** Task 1 spike produced videoSize > 100_000. (If FAILED, this task is BLOCKED and the plan must be re-planned to add IndexedDB persistence work.) 3-file lockstep update per the Approach B harness extension pattern: **File 1: tests/uat/extension-page-harness.ts** - - Locate the existing `__mokoshHarness` registration block (~line 3971) and the `__mokoshHarness` Window interface declaration (~line 3950). - - Add a thin `dispatchSaveArchive()` helper to `__mokoshHarness` if not already present (it may exist as `dispatchSaveArchiveForA33` or similar; reuse the existing SAVE_ARCHIVE dispatch). If the existing `setupFreshRecording` already covers Step 1 (priming the recording), no new page-side helper is needed — driveA33 calls it directly via `page.evaluate`. - - If a Step-1 page-side helper IS needed for driveA33: add a thin wrapper `setupFreshRecordingForA33` that's a 1-line forwarder to existing `setupFreshRecording`. Per RESEARCH note (FORBIDDEN_HOOK_STRINGS stays at 12): NO new test-only symbol needed — the new helper calls existing production-surface APIs. - - Add `assertA33` ENTRY to the `__mokoshHarness` window interface declaration (`assertA33: () => Promise;`) IF a page-side assertA33 is needed. Per RESEARCH driveA33 pattern: the host-side driveA33 owns the 5-min wait + SW kill + SAVE dispatch via the existing harness methods — likely NO new `assertA33` page-side function is needed; the host-side drives everything via existing primitives. - - If page-side function is NOT needed: just verify orchestrator uses host-only driveA33 (Step 1's setupFreshRecording is already there; Step 5's dispatchSaveArchive call uses existing SAVE_ARCHIVE messaging). - - Decision recorded in plan SUMMARY. + - REVISION iter-2 — Option B per plan-checker BLOCKER 2: the existing `__mokoshHarness` surface is `assertA1..A31 + getManifestVersion`; `dispatchSaveArchive` does NOT exist and we do NOT add it. SAVE_ARCHIVE dispatch happens directly via `chrome.runtime.sendMessage` inside driveA33's `page.evaluate` (matches the established assertA31 pattern at lines 3886-3890). + - Decision: NO new page-side function. driveA33 (host-side) drives Step 1 (prime) by calling an existing `__mokoshHarness.assertA` method that bootstraps a fresh recording (confirm in read_first which existing assertA* is the canonical "prime fresh recording" entrypoint — `assertA1` is the leading candidate; falling back to `assertA5`/`assertA26` if a more direct method matches the spike's actual call site). Step 5 (SAVE) uses inline `chrome.runtime.sendMessage` per the `` block above. + - Verify no edits needed to `__mokoshHarness` registration block (lines 3932-4015): the surface stays at 31 assertA* + getManifestVersion. The Tier-1 FORBIDDEN_HOOK_STRINGS inventory stays at 12 entries (no new test-only symbol). + - If, during read_first, the planner determines that NONE of the existing assertA* methods deliver "prime + leave recording active for ≥5 min", THEN add a thin page-side primer `primeForA33` that calls existing production-surface APIs (REQUEST_PERMISSIONS → START_RECORDING via chrome.runtime.sendMessage); this is a deviation from Option B and must be flagged in the SUMMARY. Per RESEARCH note (FORBIDDEN_HOOK_STRINGS stays at 12): NO new test-only `__MOKOSH_UAT__`-gated symbol; any new page-side helper uses production APIs only. **File 2: tests/uat/lib/harness-page-driver.ts** - - Append `driveA33` function per RESEARCH Code Example Pattern 4 (full body in `` above). + - Append `driveA33` function per RESEARCH Code Example Pattern 4 (full body in `` above; REVISION iter-2 inline-dispatched SAVE). - Place it after the existing driveA32 (which is the most-recent Phase 3 addition). - Verify the `stopServiceWorker` helper from Task 1 is in scope (same file). - Filter-pipeline form; no `continue`; typed function signature `(page, browser, extensionId, downloadsDir) => Promise` per the new 4-arg shape. - Add `import { readFileSync } from 'node:fs';` + `import JSZip from 'jszip';` if not already present (they should be — these are reused from driveA29/30/31). + - The Step-5 SAVE_ARCHIVE inline `page.evaluate` block uses `chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, callback)` per the `` Code Example; verify by `grep -c "type: 'SAVE_ARCHIVE'" tests/uat/lib/harness-page-driver.ts` increases by ≥ 1 vs pre-edit baseline (was 0 in driveA29/30/31 because those call sites are inside extension-page-harness.ts assertA* methods; A33 is unique in dispatching from the host-side via page.evaluate). **File 3: tests/uat/harness.test.ts** - Import: add `driveA33,` to the import block at ~line 101 (alongside `driveA29`-`driveA32`). @@ -339,9 +395,10 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe - Quick UAT: `HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat` exits 0 with 34/34 GREEN (A33 SKIPPED message visible; preserves baseline + adds A33 skip placeholder). - Full UAT: `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat` exits 0 with 34/34 GREEN (A33 actually runs ~6 min wall-clock; A33.1 SAVE ack + A33.2 size > 0 + A33.3 size > 100 KB all PASS). - Tier-1 FORBIDDEN_HOOK_STRINGS check: `grep -c 'FORBIDDEN_HOOK_STRINGS' tests/uat/harness.test.ts tests/background/no-test-hooks-in-prod-bundle.test.ts` — verify the inventory count in both files unchanged (preserves the 12-entry invariant per CONTEXT §"Claude's Discretion"). + - REVISION iter-2 gate: `grep -c 'dispatchSaveArchive' tests/uat/lib/harness-page-driver.ts tests/uat/extension-page-harness.ts tests/uat/harness.test.ts` returns 0 (the non-existent helper is NOT introduced). - npx tsc --noEmit && npm run build:test && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -5 | tee /tmp/04-04-task-2-skip.log; grep -c '34/34' /tmp/04-04-task-2-skip.log + npx tsc --noEmit && npm run build:test && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -5 | tee /tmp/04-04-task-2-skip.log; grep -c '34/34' /tmp/04-04-task-2-skip.log; grep -c 'dispatchSaveArchive' tests/uat/lib/harness-page-driver.ts tests/uat/extension-page-harness.ts tests/uat/harness.test.ts - `npx tsc --noEmit` exits 0. @@ -352,6 +409,7 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe - `grep -c 'A33' tests/uat/harness.test.ts` returns ≥ 4 (import + wrapped + push + comment banner). - `grep -c 'SKIP_LONG_UAT' tests/uat/harness.test.ts` returns ≥ 2 (env-gate + comment). - FORBIDDEN_HOOK_STRINGS count unchanged at 12 (no new test-only symbols introduced per CONTEXT §"Claude's Discretion"; verify by `wc -l` of the inventory arrays). + - REVISION iter-2 gate (Option B): `grep -c 'dispatchSaveArchive' tests/uat/` returns 0 across all harness files; SAVE_ARCHIVE dispatched via `chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, ...)` only. A33 lands; UAT 33→34 GREEN; SW persistence empirically verified at 5-min idle scale. Atomic commit: `feat(04-04): Wave 1 — A33 SW state persistence harness assertion (34/34 GREEN)`. @@ -386,6 +444,7 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe - FORBIDDEN_HOOK_STRINGS count unchanged at 12. - vitest baseline preserved (≥ 181 GREEN from Plans 04-01 + 04-02). - A29 + A30 + A31 + A32 unchanged (no regression to existing assertions). +- REVISION iter-2 invariant: `grep -c 'dispatchSaveArchive' tests/uat/` returns 0 across spike script + harness files. @@ -401,7 +460,7 @@ The spike's job: verify this RAM-only design survives a 5-min SW idle. If it doe After completion, create `.planning/phases/04-harden-clean-up-optional/04-04-SUMMARY.md` capturing: - Spike outcome (videoSize value + interpretation; SPIKE PASSED/FAILED tag) - stopServiceWorker helper diff (full body) -- driveA33 diff (full body) +- driveA33 diff (full body; inline chrome.runtime.sendMessage SAVE per REVISION iter-2 Option B) - Orchestrator wiring diff (3 sites in harness.test.ts) - SKIP_LONG_UAT env-gate decision (default RUN; rationale) - UAT before/after (33/33 → 34/34) @@ -411,4 +470,3 @@ After completion, create `.planning/phases/04-harden-clean-up-optional/04-04-SUM - If spike FAILED: detailed failure mode + flag for re-planning (this branch is unlikely per RESEARCH MEDIUM-confidence; document as ALPHA-PATH-NOT-TAKEN) - \ No newline at end of file diff --git a/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md b/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md index 41efba6..5c635ba 100644 --- a/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md +++ b/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md @@ -253,6 +253,9 @@ No change strictly required; the bare class selector matches both ` UI-SPEC dark-logo contrast strategy: SVG stroke recolor + inline-SVG injection + ambient module decl + harness update + cursor-visibility regression pin + docs back-patch. Pre-checkpoint bundle gates 6/6 PASS: - Gate 1: `npm run build` exit 0 - - Gate 2: `grep -c 'new Function' dist/assets/index.ts-*.js` returns 0 (Plan 04-02 effect preserved) - - Gate 3: SW Node-globals grep clean - - Gate 4: DOM-globals bundled-lib idiom (typeof-guarded) + - Gate 2: `grep -c 'new Function' dist/assets/index.ts-*.js` returns 0 (Plan 04-02 effect preserved; canonical SW chunk glob) + - Gate 3: SW Node-globals grep clean against `dist/assets/index.ts-*.js` + - Gate 4: DOM-globals bundled-lib idiom (typeof-guarded) against `dist/assets/index.ts-*.js` - Gate 5: Tier-1 SW-bundle-import unit gate GREEN - Gate 6: FORBIDDEN_HOOK_STRINGS at 12 (no change) + - Glob-existence pre-gate: `ls dist/assets/index.ts-*.js | wc -l` returns >= 1 (prevents silent 0-hit pass on mis-glob) - Plus pre-checkpoint vitest baseline >=185 GREEN; UAT harness 35/35 GREEN. Auto-confirms: @@ -462,6 +468,7 @@ From tests/uat/extension-page-harness.ts:2249-2294 (current A17.8 — Plan 01-10 | T-04-06-01 | Tampering | a future developer might switch DOMParser to innerHTML for "simplicity" — innerHTML in a content script context is unsafe (CSP risk + script-execution surface) | mitigate | Inline code comment + executor-pattern in the action: "NEVER use innerHTML — DOMParser + appendChild only (MV3 CSP discipline)"; A17.8 harness check verifies the inline-SVG DOM shape (defense in depth) | | T-04-06-02 | Information Disclosure | the inline SVG has no PII or secret; it's the canonical Mokosh brand mark | accept | Static brand asset; out-of-tree threat surface | | T-04-06-03 | Spoofing | cursor visibility in captured frames could leak sensitive UI overlay state (e.g., 2FA OTP digit operator was about to type) — but this is a known and intended diagnostic feature per Plan 01-07 obs; out of scope for password masking per D-P3-02 charter | accept | Operator-side responsibility per CONTEXT charter; v2 candidate per CONTEXT Deferred Ideas | +| T-04-06-04 | Repudiation (REVISION iter-2 — BLOCKER 1) | A mis-globbed `dist/assets/index*-bg.js` matches no files; `grep -c new Function` returns 0; Gate 2 spuriously PASSES even if the setimmediate polyfill never landed | mitigate | Canonical SW chunk glob `dist/assets/index.ts-*.js` verified empirically (file `index.ts-8LkXuqac.js` exists on disk; pinned by RESEARCH Q1 lines 142/180/196); pre-gate `ls dist/assets/index.ts-*.js \| wc -l >= 1` validates the glob matches BEFORE running the grep gates | @@ -474,7 +481,7 @@ From tests/uat/extension-page-harness.ts:2249-2294 (current A17.8 — Plan 01-10 - UAT harness 35/35 GREEN with updated A17.8 (no harness count change; assertion quality changes). - 01-07-SUMMARY.md back-patched. - Operator empirical ack received (Task 4 resume signal "approved"). -- Pre-checkpoint bundle gates 6/6 PASS. +- Pre-checkpoint bundle gates 6/6 PASS (REVISION iter-2 — canonical SW chunk glob `dist/assets/index.ts-*.js` used across Gates 2/3/4; glob-existence pre-gate `ls dist/assets/index.ts-*.js | wc -l >= 1` validates). - PNG toolbar icons byte-identical (no change to scripts/rasterize-icons.sh or icons/*.png). @@ -485,7 +492,7 @@ From tests/uat/extension-page-harness.ts:2249-2294 (current A17.8 — Plan 01-10 - ROADMAP cursor visibility item GREEN. - ROADMAP dark-surface logo contrast item GREEN. - Operator empirical ack received on dark-mode visual aesthetic. -- Pre-checkpoint bundle gates 6/6 PASS preserved. +- Pre-checkpoint bundle gates 6/6 PASS preserved (REVISION iter-2 — canonical glob). - UAT harness 35/35 GREEN preserved. - vitest baseline +4 (Plan 04-05 baseline 181 -> Plan 04-06 baseline >= 185). @@ -499,10 +506,9 @@ After completion, create `.planning/phases/04-harden-clean-up-optional/04-06-SUM - 01-07-SUMMARY.md back-patch (5 stale Phase-5 lines flipped) - 2 new test files (inline-svg + cursor-visibility) with RED->GREEN cycle commits - Operator empirical UAT verbatim ack (e.g., "approved", "all good", or specific issues) -- Pre-checkpoint bundle gates 6/6 PASS evidence (grep outputs) +- Pre-checkpoint bundle gates 6/6 PASS evidence (grep outputs against canonical `dist/assets/index.ts-*.js` glob per REVISION iter-2) - vitest before/after (181 -> >=185) - UAT 35/35 GREEN preservation - Commit refs (Tasks 1 + 2 + 3 + 4 ack) - \ No newline at end of file diff --git a/.planning/phases/04-harden-clean-up-optional/04-07-PLAN.md b/.planning/phases/04-harden-clean-up-optional/04-07-PLAN.md index d3966dd..0db314c 100644 --- a/.planning/phases/04-harden-clean-up-optional/04-07-PLAN.md +++ b/.planning/phases/04-harden-clean-up-optional/04-07-PLAN.md @@ -35,7 +35,7 @@ must_haves: - "All P1 polish items (#11 + #14 + #15) cited as GREEN with Plan 04-01 evidence" - "All Phase 4 hardening items (setimmediate + dead-code + generate-icons + A29 race fix + cursor verification + dark-logo + ROADMAP backfill) cited as GREEN with their plan + commit refs" - "Pre-checkpoint bundle gates 6/6 PASS evidence (per saved memory feedback-pre-checkpoint-bundle-gates.md)" - - "Operator empirical ack from Plan 04-06 cited verbatim with date stamp" + - "Operator empirical ack from Plan 04-06 cited verbatim with date stamp (REVISION iter-2 WARNING 4 — Task 1 verify greps the ack literal from 04-VERIFICATION.md to confirm it's a REAL ack, not a placeholder)" - "Deferred Items table carries forward the v1.1/v2 items: rrweb v2 upgrade + programmatic SW-RAM measurement + REQ-password-confidentiality v2 candidate" - "ROADMAP.md Phase 4 row flipped [x] with closure date; harness count updated from 33 to 35 (Plans 04-04 + 04-05 added A33 + A34)" - "ROADMAP.md Plans 01-08..01-13 rows verified per D-P4-05 backfill (per plan-checker flag #4)" @@ -133,6 +133,9 @@ Per CONTEXT §"Deferred Ideas" — v1.1/v2 items to carry forward in 04-VERIFICA - REQ-password-confidentiality v2 candidate (D-P3-02; only if charter reverses) - Alpha-tester findings integration (D-P4-04; routed via separate maintenance window) +Operator empirical ack format (REVISION iter-2 — WARNING 4 mitigation): +Plan 04-06 Task 4's `` is canonically: `Type "approved" or describe issues (e.g., "stroke too thin", ...)`. The Phase 4 prior-art ack pattern from Plan 01-10 cycle-2 was `"All good" 2026-05-20`. The 04-VERIFICATION.md `human_verification[0].evidence` field MUST contain the verbatim operator ack from the 04-06 Task 4 SUMMARY — matching ONE of: `approved`, `All good`, `APPROVED`, `approved by`, `operator ack`, `all good`. The Task 1 `` greps the file for these patterns to ensure the field is a REAL ack, not a placeholder string like "TBD" or "TODO" or "". + Frontmatter sketch for 04-VERIFICATION.md: ```yaml # (yaml document marker omitted for parser-compat) @@ -166,7 +169,7 @@ human_verification: - test: "dark-mode operator visual aesthetic on welcome hero" expected: "mark legible on OS dark-mode rendering surface" why_human: "Aesthetic contrast judgment — UI-SPEC §'Manual-Only Verifications' acceptance criterion #6; canonical operator-empirical case per feedback-trust-harness-over-manual-uat.md" - evidence: "Plan 04-06 Task 4 operator ack " + evidence: "Plan 04-06 Task 4 operator ack " deferred: - truth: "rrweb 2.0.0-alpha.4 -> stable v2 upgrade" addressed_in: "v1.1 / v2 maintenance milestone" @@ -182,6 +185,7 @@ deferred: evidence: "D-P4-04 charter — operator handles alpha signal out-of-band" # (yaml document marker omitted for parser-compat) ``` +The placeholder `` MUST be replaced with the actual operator response captured in 04-06-SUMMARY.md (e.g., "approved 2026-05-21" or "All good 2026-05-21" or longer verbatim quote). The Task 1 verify-gate greps for the ack literal to ensure the placeholder is NOT left intact. @@ -190,16 +194,16 @@ deferred: Task 1: Write 04-VERIFICATION.md aggregator (NEW) .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md - .planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md, .planning/phases/02-stabilize-export-pipeline/02-VERIFICATION.md, .planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md, all Plan 04-01..04-06 SUMMARY files (.planning/phases/04-harden-clean-up-optional/04-0?-SUMMARY.md — produced by the prior plans) + .planning/phases/01-stabilize-video-pipeline/01-VERIFICATION.md, .planning/phases/02-stabilize-export-pipeline/02-VERIFICATION.md, .planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VERIFICATION.md, all Plan 04-01..04-06 SUMMARY files (.planning/phases/04-harden-clean-up-optional/04-0?-SUMMARY.md — produced by the prior plans), specifically Plan 04-06 SUMMARY for the verbatim operator ack quote (REVISION iter-2 WARNING 4 — required for human_verification[0].evidence) - 1. Read the 3 precedent VERIFICATION.md files (Phase 1 + 2 + 3) ONCE each to extract the canonical frontmatter shape + body section structure. Read each Plan 04-01..04-06 SUMMARY.md once to extract evidence (commit refs, test counts, harness assertion numbers, pre/post measurements). + 1. Read the 3 precedent VERIFICATION.md files (Phase 1 + 2 + 3) ONCE each to extract the canonical frontmatter shape + body section structure. Read each Plan 04-01..04-06 SUMMARY.md once to extract evidence (commit refs, test counts, harness assertion numbers, pre/post measurements). **Specifically extract the verbatim operator ack quote from Plan 04-06 Task 4 SUMMARY** — Task 1 verify-gate requires the ack literal to be present in human_verification[0].evidence. 2. Create `.planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md` with frontmatter per the sketch in `` above. Replace placeholder fields with actual values from the Plan 04-01..04-06 SUMMARYs: - `verified:` — current ISO timestamp (`date -u +%Y-%m-%dT%H:%M:%SZ`) - `score:` — fill in actual counts from SUMMARYs - `re_verification.gaps_closed[*]` — bullet per closed gap with Plan + commit citation - `override_notes` — populate if any T5 override applied to a Phase 4 criterion (e.g., the operator-empirical UAT for dark-logo can be cited as harness-coverage-augmented rather than override; depends on Plan 04-06 outcome). - - `human_verification[0].evidence` — verbatim operator ack quote from Plan 04-06 Task 4 SUMMARY. + - `human_verification[0].evidence` — verbatim operator ack quote from Plan 04-06 Task 4 SUMMARY (REVISION iter-2 WARNING 4 — MUST contain one of: `approved`, `All good`, `APPROVED`, `approved by`, `operator ack`, `all good` — verified by Task 1 verify-gate grep). - `deferred[]` — 4 v1.1/v2 items per CONTEXT Deferred Ideas + the alpha-distribution item per D-P4-04. 3. Body sections (after frontmatter): @@ -208,16 +212,16 @@ deferred: - vitest count: 171 (Phase 3 baseline) -> 171 + new Wave 0 tests from Plans 04-01 + 04-02 + 04-06 = approximately 171 + 8 + 2 + 4 = 185 GREEN (verify exact count via final `npm test -- --run` invocation). - UAT harness count: 33 -> 35 GREEN (Plans 04-04 A33 + 04-05 A34 added; Plan 04-03 rewrote A29 in-place, count unchanged). - Tier-1 FORBIDDEN_HOOK_STRINGS: 12 unchanged (Phase 4 introduced no new __MOKOSH_UAT__-gated symbols per CONTEXT Claude's Discretion). - - Pre-checkpoint bundle gates: 6/6 PASS (Plan 04-02 flipped Gate 2 polarity 1 -> 0 hits `new Function`). + - Pre-checkpoint bundle gates: 6/6 PASS (Plan 04-02 flipped Gate 2 polarity 1 -> 0 hits `new Function` in canonical `dist/assets/index.ts-*.js` glob — REVISION iter-2 BLOCKER 1 mitigation in Plan 04-06). - `## Operator-Empirical Acks (verbatim + commit refs)`: - - Append the Plan 04-06 Task 4 ack from the SUMMARY (verbatim quote + date + commit hash). + - Append the Plan 04-06 Task 4 ack from the SUMMARY (verbatim quote + date + commit hash). REVISION iter-2 WARNING 4: this line MUST contain the actual operator response (e.g., `> approved 2026-05-21` or `> All good 2026-05-21`) — NOT a placeholder. - `## Deferred Items` — table mirroring frontmatter `deferred:`. - `## ROADMAP backfill verification (D-P4-05)` — list of Plans 01-08..01-13 row presence + any newly-added rows. If all rows are already present (per PATTERNS.md noting lines 90-95 exist), this section's content is "Verified — no row additions needed; plan-checker flag #4 closed". 4. Commit: `docs(04-07): Phase 4 closure — 04-VERIFICATION.md aggregator (4/4 ROADMAP SCs + N/N hardening items)`. - test -f .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md && grep -cE '^## ' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md; grep -c 'Plan 04-0' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md + test -f .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md && grep -cE '^## ' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md; grep -c 'Plan 04-0' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md; grep -cE 'approved|All good|APPROVED|approved by|operator ack|all good' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md - File exists at `.planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md`. @@ -226,8 +230,9 @@ deferred: - File >= 120 lines. - `grep -c 'Plan 04-0' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md` returns >= 6 (one per Plan 04-01..04-06 evidence citation). - `grep -c 'commit' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md` returns >= 6. + - REVISION iter-2 WARNING 4 — Operator ack gate: `grep -cE 'approved|All good|APPROVED|approved by|operator ack|all good' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md` returns >= 1. This proves the `human_verification[0].evidence` field contains a real operator ack literal (not the `` placeholder or "TBD"). The pattern set matches both lowercase ("approved", "all good") and titlecase/uppercase ("All good", "APPROVED") variants per the prior-art ack format from Plan 01-10 cycle-2 ("All good" 2026-05-20) AND the canonical 04-06 Task 4 `` expectation ("approved" verbatim). - 04-VERIFICATION.md created with frontmatter + 5+ body sections + 6+ plan citations + deferred items. Commit: `docs(04-07): Phase 4 closure — 04-VERIFICATION.md aggregator`. + 04-VERIFICATION.md created with frontmatter + 5+ body sections + 6+ plan citations + verbatim operator ack + deferred items. Commit: `docs(04-07): Phase 4 closure — 04-VERIFICATION.md aggregator`. @@ -309,6 +314,7 @@ deferred: | T-04-07-01 | Repudiation | aggregator could over-claim closure (e.g., mark ROADMAP SC #2 GREEN when only fetch is empirically verified but XHR is not) | mitigate | Each scorecard row MUST cite specific UAT assertion ID (e.g., "A34.3 XHR 404 -> network_error" or "A34.5 XHR meta.status === 404") + commit hash; absence of an assertion = row marked PARTIAL or YELLOW with explanation | | T-04-07-02 | Tampering | a docs-only commit could in theory be conflated with a code commit; the closure ceremony must be docs-pure | accept | Verification gate `git diff --stat HEAD~1 HEAD` on closure commits should show only `.planning/` files dirty; pre-commit check | | T-04-07-03 | Repudiation | alpha redistribution decision is non-automated (per D-P4-04 user handles out-of-band); 04-VERIFICATION.md is advisory not gating | accept | Documented in scorecard + STATE.md status field clarifies v1 close vs CLOSED-PENDING-ALPHA distinction | +| T-04-07-04 | Repudiation (REVISION iter-2 — WARNING 4) | `human_verification[0].evidence` field could be left as a placeholder (e.g., `` or `TBD`) — the 04-VERIFICATION.md "passes" gates but contains no real operator ack, masking a missing closure step | mitigate | Task 1 `` greps for the ack literal (`approved`, `All good`, `APPROVED`, `approved by`, `operator ack`, `all good`) in the file; count >= 1 required to pass the gate. The pattern set covers both the canonical Plan 04-06 `` expectation ("approved" lowercase) AND the historical Plan 01-10 cycle-2 ack format ("All good" titlecase). If the operator response uses a non-canonical phrasing, the SUMMARY for 04-06 still records it verbatim; the executor extends the grep alternation to match. | @@ -320,6 +326,7 @@ deferred: - PROJECT.md: Validated section updated for Phase 4 hardening closure. - No code or test changes in this plan; `git diff --stat HEAD~1 HEAD` shows only `.planning/` files modified. - All Phase 4 plan SUMMARY files exist (Plans 04-01..04-06; this plan creates its own SUMMARY at completion). +- REVISION iter-2 WARNING 4: 04-VERIFICATION.md contains a verbatim operator ack literal (one of `approved`, `All good`, `APPROVED`, `approved by`, `operator ack`, `all good`); verified by Task 1 `` grep count >= 1. @@ -328,7 +335,7 @@ deferred: - Plans 01-08..01-13 ROADMAP backfill verified (D-P4-05). - v1 milestone status updated: CLOSED-PENDING-ALPHA OR CLOSED. - All Phase 4 success criteria from the ROADMAP cited as GREEN with evidence. -- Operator empirical ack from Plan 04-06 cited verbatim in 04-VERIFICATION.md. +- Operator empirical ack from Plan 04-06 cited verbatim in 04-VERIFICATION.md (REVISION iter-2 WARNING 4 — grep-pinned). - Deferred items table carries forward v1.1/v2 items per CONTEXT. - No code/test changes (docs-only ceremony). @@ -341,7 +348,7 @@ After completion, create `.planning/phases/04-harden-clean-up-optional/04-07-SUM - v1 milestone status flip (CLOSED-PENDING-ALPHA OR CLOSED — depending on alpha redistribution sequencing) - Closure commit refs - Final tally: UAT 35/35 GREEN; vitest >=185 GREEN; pre-checkpoint bundle gates 6/6 PASS; FORBIDDEN_HOOK_STRINGS at 12. +- Verbatim operator ack from Plan 04-06 Task 4 (REVISION iter-2 WARNING 4 — grep-verified at Task 1 closure). - Next steps for the operator: alpha redistribution + v1.0 tag + release notes (separate workstream per D-P4-04). - diff --git a/.planning/phases/04-harden-clean-up-optional/04-VALIDATION.md b/.planning/phases/04-harden-clean-up-optional/04-VALIDATION.md index a25e965..da9be27 100644 --- a/.planning/phases/04-harden-clean-up-optional/04-VALIDATION.md +++ b/.planning/phases/04-harden-clean-up-optional/04-VALIDATION.md @@ -5,6 +5,8 @@ status: draft nyquist_compliant: false wave_0_complete: false created: 2026-05-21 +revised: 2026-05-21 +revision_notes: "iter-2 revision — per-task map updated for 4 revised tasks (04-02 T2 UAT grep gate; 04-04 T1 Option B chrome.runtime.sendMessage; 04-06 T4 canonical SW chunk glob; 04-07 T1 ack-grep gate)" --- # Phase 04 — Validation Strategy @@ -44,24 +46,30 @@ created: 2026-05-21 | 04-01 T1 RED 3 tests | 04-01 | 1 | Audit P1 #11/#14/#15 | T-04-01-01..03 | URL extraction + previousUrl + epoch normalization | unit (vitest jsdom) | `npm test -- tests/content/ --run` | ❌ NEW (Wave 0) | ⬜ pending | | 04-01 T2 GREEN edits | 04-01 | 1 | Audit P1 #11/#14/#15 | T-04-01-01..03 | Same; src/content/index.ts edits | unit (vitest) | `npm test -- tests/content/ --run` (+8 GREEN) + `npx tsc --noEmit` | ✗ EXISTS (modify) | ⬜ pending | | 04-02 T1 RED build gates | 04-02 | 1 | SC #4 dead-code + setimmediate hygiene | T-04-02-01/03 | grep gate | unit (vitest + execFile build) | `npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run` | ❌ NEW (Wave 0) | ⬜ pending | -| 04-02 T2 GREEN polyfill + rename + flip | 04-02 | 1 | SC #3 generate-icons + setimmediate Q1 | T-04-02-01/02/04 | queueMicrotask polyfill; .cjs rename | build-gate + unit | `npm run build && grep -c 'new Function' dist/assets/index.ts-*.js` -> 0 + `node generate-icons.cjs` exit 0 | ✗ EXISTS (modify + rename) | ⬜ pending | +| 04-02 T2 GREEN polyfill + rename + flip (REVISION iter-2 WARNING 1) | 04-02 | 1 | SC #3 generate-icons + setimmediate Q1 + UAT preserved | T-04-02-01/02/04 | queueMicrotask polyfill; .cjs rename; JSZip fallback verified empirically | build-gate + unit + UAT | `npm run build && grep -c 'new Function' dist/assets/index.ts-*.js \| head -1; node generate-icons.cjs; npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run; npx tsc --noEmit && HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat 2>&1 \| tee /tmp/04-02-task-2-uat.log \| tail -3; grep -c 'UAT harness: 33/33 assertions passed' /tmp/04-02-task-2-uat.log` -> 0 hits + cjs exit 0 + tests GREEN + tsc-clean + 1 ack-line | ✗ EXISTS (modify + rename) | ⬜ pending | | 04-03 T1 assertA29 rewrite | 04-03 | 2 | A29 flake stabilization | T-04-03-01/02 | cs-injection-world ISOLATED + sentinel | UAT (page-side) | `npx tsc --noEmit && npm run build:test` | ✗ EXISTS (modify) | ⬜ pending | | 04-03 T2 driveA29 strict-sentinel | 04-03 | 2 | A29 sentinel filter | T-04-03-01 | rrweb IncrementalSource.Mutation filter | UAT (host-side) | `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat` 33/33 GREEN; 5/5 stress | ✗ EXISTS (modify) | ⬜ pending | -| 04-04 T1 SPIKE | 04-04 | 3 | SC #1 SW state persistence empirical | T-04-04-01 | offscreen survives SW idle | spike script | `HEADLESS=1 tsx tests/uat/spike-a33-sw-persistence.ts` -> videoSize > 100_000 | ❌ NEW (Wave 0 spike) | ⬜ pending | -| 04-04 T2 A33 + stopServiceWorker + orchestrator | 04-04 | 3 | SC #1 5-min idle harness | T-04-04-02/03/04 | CDP worker.close() + 5-min wait + SAVE | UAT | `HEADLESS=1 SKIP_LONG_UAT=1 npm run test:uat` 34/34 GREEN (skip-mode); full-mode 34/34 ~6.5 min | ✗ EXISTS (modify) | ⬜ pending | +| 04-04 T1 SPIKE (REVISION iter-2 BLOCKER 2 — Option B chrome.runtime.sendMessage) | 04-04 | 3 | SC #1 SW state persistence empirical | T-04-04-01 | offscreen survives SW idle; SAVE_ARCHIVE via canonical chrome.runtime.sendMessage (NOT non-existent __mokoshHarness.dispatchSaveArchive) | spike script | `npx tsc --noEmit && HEADLESS=1 tsx tests/uat/spike-a33-sw-persistence.ts 2>&1 \| tee /tmp/04-04-spike.log; grep -c 'SPIKE RESULT' /tmp/04-04-spike.log; grep -c "type: 'SAVE_ARCHIVE'" tests/uat/spike-a33-sw-persistence.ts; grep -c 'dispatchSaveArchive' tests/uat/spike-a33-sw-persistence.ts` -> SPIKE RESULT line + SAVE_ARCHIVE >= 1 + dispatchSaveArchive == 0 + videoSize > 100_000 | ❌ NEW (Wave 0 spike) | ⬜ pending | +| 04-04 T2 A33 + stopServiceWorker + orchestrator (REVISION iter-2 BLOCKER 2 — Option B) | 04-04 | 3 | SC #1 5-min idle harness | T-04-04-02/03/04 | CDP worker.close() + 5-min wait + SAVE via chrome.runtime.sendMessage inline page.evaluate | UAT | `npx tsc --noEmit && npm run build:test && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 \| tail -5 \| tee /tmp/04-04-task-2-skip.log; grep -c '34/34' /tmp/04-04-task-2-skip.log; grep -c 'dispatchSaveArchive' tests/uat/lib/harness-page-driver.ts tests/uat/extension-page-harness.ts tests/uat/harness.test.ts` -> 34/34 skip-mode + dispatchSaveArchive == 0 + full-mode 34/34 ~6.5 min | ✗ EXISTS (modify) | ⬜ pending | | 04-05 T1 assertA34 fetch+XHR | 04-05 | 4 | SC #2 fetch+XHR network_error | T-04-05-01 | cs-injection-world dual-trigger | UAT (page-side) | `npx tsc --noEmit && npm run build:test` | ✗ EXISTS (modify) | ⬜ pending | | 04-05 T2 driveA34 + orchestrator | 04-05 | 4 | SC #2 + P1 #11 end-to-end empirical | T-04-05-01 | 2 network_error entries with status===404 | UAT | `HEADLESS=1 SKIP_LONG_UAT=1 npm run test:uat` 35/35 GREEN; full-mode ~7 min | ✗ EXISTS (modify) | ⬜ pending | | 04-06 T1 RED inline-SVG + cursor-pin | 04-06 | 5 | UI-SPEC dark-logo + RESEARCH Finding 4 | T-04-06-01 | DOMParser inline injection (no innerHTML); cursor: 'always' literal | unit (vitest jsdom + build-grep) | `npm test -- tests/welcome/ tests/build/cursor-visibility.test.ts --run` | ❌ NEW (Wave 0) | ⬜ pending | | 04-06 T2 GREEN SVG + welcome.ts + globals | 04-06 | 5 | UI-SPEC stroke recolor + ?raw import | T-04-06-01 | currentColor + DOMParser inline | unit | `npm test -- tests/welcome/inline-svg.test.ts --run` 3/3 GREEN | ✗ EXISTS (modify) | ⬜ pending | | 04-06 T3 A17.8 + 01-07 back-patch | 04-06 | 5 | UI-SPEC harness invariant + docs hygiene | T-04-06-01 | A17.8 raw-source grep | UAT + docs | `HEADLESS=1 SKIP_LONG_UAT=1 npm run test:uat` 35/35 + grep verify | ✗ EXISTS (modify) | ⬜ pending | -| 04-06 T4 Operator empirical | 04-06 | 5 | UI-SPEC AC #6 aesthetic judgment | T-04-06-01 | dark-mode visual contrast | manual | operator returns "approved" or describes issue | n/a | ⬜ pending | -| 04-07 T1 04-VERIFICATION.md | 04-07 | 6 | Phase 4 closure aggregator | T-04-07-01 | scorecard + override notes + deferred items | docs aggregator | `test -f .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md && grep -cE '^## '` >= 5 | ❌ NEW | ⬜ pending | +| 04-06 T4 Operator empirical (REVISION iter-2 BLOCKER 1 — canonical SW chunk glob) | 04-06 | 5 | UI-SPEC AC #6 aesthetic judgment | T-04-06-01/04 | dark-mode visual contrast; canonical `dist/assets/index.ts-*.js` glob (NOT non-matching `index*-bg.js`) | manual + pre-checkpoint bundle gates | `npm run build && ls dist/assets/index.ts-*.js \| wc -l; grep -cE 'new Function\(\|eval\(' dist/assets/index.ts-*.js; HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 \| tail -3` -> glob match >= 1 + 0 hits + UAT 35/35 + operator returns "approved" | n/a (verification + operator) | ⬜ pending | +| 04-07 T1 04-VERIFICATION.md (REVISION iter-2 WARNING 4 — ack-grep gate) | 04-07 | 6 | Phase 4 closure aggregator + verbatim operator ack | T-04-07-01/04 | scorecard + override notes + deferred items + verbatim operator ack from Plan 04-06 | docs aggregator | `test -f .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md && grep -cE '^## ' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md; grep -c 'Plan 04-0' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md; grep -cE 'approved\|All good\|APPROVED\|approved by\|operator ack\|all good' .planning/phases/04-harden-clean-up-optional/04-VERIFICATION.md` -> sections >= 5 + Plan-04 citations >= 6 + ack-literal >= 1 | ❌ NEW | ⬜ pending | | 04-07 T2 Marker flips | 04-07 | 6 | D-P4-05 + ROADMAP/STATE flips | T-04-07-02/03 | Phase 4 [x] + completed_phases: 4 | docs | `grep -c '\[x\] \*\*Phase 4' .planning/ROADMAP.md` >= 1 | ✗ EXISTS (modify) | ⬜ pending | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* **Planner instructions:** Populate one row per task. Per RESEARCH finding 5 (6 new Wave-0 test files anticipated), expect ~6 unit-test rows + ~4 harness-A33+ rows + ~4 bundle-gate rows + ~3 docs rows. Format per Phase 3 03-VALIDATION precedent. +**REVISION iter-2 notes (2026-05-21):** +- **04-02 T2 (WARNING 1):** `` now includes `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` + `grep -c 'UAT harness: 33/33 assertions passed'` to empirically verify JSZip fallback works at the SAVE→zip layer post-polyfill-removal. +- **04-04 T1 + T2 (BLOCKER 2):** Option B applied — SAVE_ARCHIVE dispatched via `chrome.runtime.sendMessage({type: 'SAVE_ARCHIVE'}, ...)` directly from the harness page realm (matches established A5/A11/A12/A13/A26/A28/A29/A30/A31 precedent). The non-existent `window.__mokoshHarness.dispatchSaveArchive` is NOT introduced; gates verify `grep -c 'dispatchSaveArchive'` returns 0 across all harness files + spike script. +- **04-06 T4 (BLOCKER 1):** Canonical SW chunk glob `dist/assets/index.ts-*.js` used for all bundle gates (Gates 2/3/4) — replaces the misquoted `dist/assets/index*-bg.js` which matched no files (silent 0-grep = spurious PASS). New glob-existence pre-gate `ls dist/assets/index.ts-*.js | wc -l >= 1` validates the glob matches BEFORE running grep gates. +- **04-07 T1 (WARNING 4):** Ack-literal grep added — `grep -cE 'approved|All good|APPROVED|approved by|operator ack|all good' 04-VERIFICATION.md >= 1` ensures `human_verification[0].evidence` contains a real operator ack (not a placeholder). + --- ## Wave 0 Requirements @@ -107,5 +115,6 @@ Existing infrastructure already in place (inherited from Phases 1-3): - [ ] No watch-mode flags — verify in planner output (focused commands use `--run`) - [ ] Feedback latency < ~2.5 min default (5-min idle test on dedicated lane) — confirmed by RESEARCH - [ ] `nyquist_compliant: true` set in frontmatter — pending sign-off after planner completes +- [ ] REVISION iter-2 gates applied to 4 revised tasks (04-02 T2, 04-04 T1, 04-04 T2, 04-06 T4, 04-07 T1) **Approval:** pending (planner fills per-task map; checker validates)