Milestone v1 (v2.0.0): Mokosh — Session Capture #1
@@ -265,7 +265,7 @@ finalized at plan time):
|
||||
**Plans**: 7 plans (04-01 through 04-07). Wave 1 parallel (04-01 + 04-02) -> Wave 2 sequential (04-03 A29 rewrite -> 04-04 A33 SW persistence -> 04-05 A34 fetch+XHR) -> Wave 5 visual polish (04-06; operator empirical) -> Wave 6 closure (04-07).
|
||||
- [x] 04-01-PLAN.md — Audit P1 polish #11 + #14 + #15 (TDD; 3 unit tests + 3 src/content/index.ts edits)
|
||||
- [x] 04-02-PLAN.md — Build/CSP hygiene (setimmediate polyfill replacement + dead-code grep + generate-icons.cjs rename)
|
||||
- [ ] 04-03-PLAN.md — A29 cs-injection-world rewrite (strict-sentinel filter; closes ~1/3 flake)
|
||||
- [x] 04-03-PLAN.md — A29 cs-injection-world rewrite (strict-sentinel filter; closes ~1/3 flake)
|
||||
- [ ] 04-04-PLAN.md — A33 SW state persistence (spike-first; 5-min idle + worker.close() CDP; ROADMAP SC #1)
|
||||
- [ ] 04-05-PLAN.md — A34 fetch + XHR network_error empirical (ROADMAP SC #2; validates Plan 04-01 P1 #11 end-to-end)
|
||||
- [ ] 04-06-PLAN.md — Dark-logo currentColor + cursor visibility verification + 01-07-SUMMARY back-patch (UI-SPEC; operator empirical ack)
|
||||
|
||||
@@ -4,14 +4,14 @@ milestone: v2.0.0
|
||||
milestone_name: milestone
|
||||
status: executing
|
||||
stopped_at: Completed 04-02-PLAN.md (setimmediate polyfill replaced via layered 4-mechanism mitigation; SW new Function polarity 1→0; UAT 33/33 GREEN preserved)
|
||||
last_updated: "2026-05-21T13:36:35.894Z"
|
||||
last_updated: "2026-05-21T14:56:45.914Z"
|
||||
last_activity: 2026-05-21
|
||||
progress:
|
||||
total_phases: 4
|
||||
completed_phases: 3
|
||||
total_plans: 30
|
||||
completed_plans: 25
|
||||
percent: 83
|
||||
completed_plans: 26
|
||||
percent: 87
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -29,11 +29,11 @@ no server, no password leaks.
|
||||
|
||||
Phase: 04 (harden-clean-up-optional) — EXECUTING
|
||||
Phase 4 of 4 (Hardening — optional) — Plan 04-01 closed (audit P1 polish 3/3); 6 plans remain (04-02 build hygiene queued NEXT in Wave 1)
|
||||
Plan: 3 of 7
|
||||
Plan: 4 of 7
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-05-21
|
||||
|
||||
Progress: [████████░░] 83%
|
||||
Progress: [█████████░] 87%
|
||||
|
||||
### Plan 01-10 closure (2026-05-20)
|
||||
|
||||
@@ -149,6 +149,7 @@ Progress: [████████░░] 83%
|
||||
| Phase 01 P10 | ~5h cumulative (4 waves; 5 plan tasks + 5 inter-cycle debug sessions + cycle-2 follow-up brand-rename ack) | 5 tasks (Wave 0 RED + Wave 1 bundle + Wave 2 SW wiring + Wave 3 harness + Wave 4 operator UAT cycle-2) | 14 files (4 new src/welcome/* + globals.d.ts + 2 unit-test files + 3 harness files + src/background/index.ts + manifest + 2 Vite configs + closure-cycle debug touches: _locales + README + package.json + onstartup-notification.test.ts + onboarding-tests + manifest-i18n.test.ts) |
|
||||
| Phase 04 P01 | 30m | 2 tasks | 5 files |
|
||||
| Phase 04 P02 | 41min | 2 tasks | 5 files |
|
||||
| Phase 04 P03 | 46min | 2 tasks | 2 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -199,6 +200,7 @@ current work:
|
||||
- [Phase ?]: [Phase 04-01]: Audit P1 polish landed end-to-end via TDD pair (3dbc51c RED + 7da30af GREEN). Three surgical edits in src/content/index.ts: (1) module-level let previousUrl tracker initialized at module load with typeof-window node-env guard, swapped-and-emitted in handleNavigation so meta.previousUrl carries the operator's actual prior URL (was always 'unknown'); (2) instanceof Request type-narrow inlined at both fetch-wrapper sites (ok-branch line ~190 + catch-branch line ~210), replacing args[0]?.toString() that resolved to literal '[object Request]' for fetch(new Request(url)); (3) event.timestamp = Date.now() prepended in rrweb record() emit callback at line 315, normalizing rrweb-internal page-load-relative timestamps to Unix-epoch ms so cleanupOldEvents (now - event.timestamp) arithmetic at line 33 is meaningful. 9 new vitest tests under tests/content/ (NEW directory) pin all three contracts; baseline 171 -> 180/180 GREEN; tsc-clean preserved; Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12. Audit P1 polish backlog CLOSED 3/3.
|
||||
- [Phase ?]: [Phase 04-02]: Layered 4-mechanism CSP-hardening for transitive-polyfill pre-bundled-distribution interception (runtime queueMicrotask polyfill prelude + nodePolyfills exclude + resolve.alias.setimmediate + stripSetimmediateNewFunction Rollup post-transform plugin). Option α (force JSZip unbundled lib/index.js) attempted + reverted because it broke readable-stream-browser browser-field propagation causing UAT A30+ regressions. Option β preserves JSZip pre-bundled distribution verbatim while excising the offending literal post-bundle.
|
||||
- [Phase ?]: [Phase 04-02]: ROADMAP SC #3 (generate-icons ESM/CJS) closed via git mv generate-icons.js generate-icons.cjs — Node 14+ treats .cjs as CJS regardless of package.json type:module per nodejs.org/api/packages.html#determining-module-system. No code change. ROADMAP SC #4 (dead-code grep permissions.request) GREEN regression-pinned via tests/build/dead-code-grep.test.ts. Plan 01-12 Wave 7 setimmediate deferred-items entry CLOSED end-to-end. SW chunk new Function count polarity flipped 1 → 0. UAT 33/33 GREEN preserved.
|
||||
- [Phase 04-03]: A29 rewrite — cs-injection-world pattern (verbatim port of Plan 03-02 assertA30 / 03-03 assertA31 skeleton) + strict-sentinel filter (RESEARCH Q3 Code Example Pattern 3) closes the documented iana.org-leftover flake. assertA29 page-side: chrome.tabs.create(https://example.com) + chrome.scripting.executeScript world:'ISOLATED' injects sentinel-bearing <div> into document.body. driveA29 host-side: filter events by EventType.IncrementalSnapshot + IncrementalSource.Mutation, then descend into data.adds[*].node.textContent for 'a29-mutation-sentinel'. A29.2 strict-sentinel is THE primary check; A29.3 + A29.4 (Meta + FullSnapshot) preserved as defense-in-depth; pre-rewrite A29.5 (loose IncrementalSnapshot >=1) retired (subsumed). Empirical: 5/5 PASS across consecutive UAT runs (was ~2/3 historical). vitest 183/183 GREEN preserved. Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 (rides production chrome.tabs.create + chrome.scripting.executeScript per DEC-011 Amendment 1 grant + manifest scripting permission).
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -221,7 +223,7 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-21T13:36:35.869Z
|
||||
Last session: 2026-05-21T14:56:45.870Z
|
||||
Stopped at: Completed 04-02-PLAN.md (setimmediate polyfill replaced via layered 4-mechanism mitigation; SW new Function polarity 1→0; UAT 33/33 GREEN preserved)
|
||||
Resume file: None
|
||||
|
||||
|
||||
217
.planning/phases/04-harden-clean-up-optional/04-03-SUMMARY.md
Normal file
217
.planning/phases/04-harden-clean-up-optional/04-03-SUMMARY.md
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
phase: 04-harden-clean-up-optional
|
||||
plan: 03
|
||||
subsystem: testing
|
||||
tags:
|
||||
- uat-harness
|
||||
- a29-rewrite
|
||||
- cs-injection-world
|
||||
- flake-stabilization
|
||||
- strict-sentinel
|
||||
- rrweb
|
||||
- incremental-source-mutation
|
||||
- charter-d-p4-01
|
||||
- phase-4-wave-2
|
||||
|
||||
requires:
|
||||
- phase: 03-spec-10-smoke-verification-dom-event-log-verification
|
||||
provides: "Plan 03-02 assertA30 + driveA30 cs-injection-world pattern (chrome.tabs.create probe tab on https://example.com + chrome.scripting.executeScript world:'ISOLATED' + try/finally chrome.tabs.remove silent-ignore via T-02-04-04). Plan 03-03 assertA31 + driveA31 sentinel-based negative-assertion precedent. Both established at tests/uat/extension-page-harness.ts + tests/uat/lib/harness-page-driver.ts."
|
||||
- phase: 03-spec-10-smoke-verification-dom-event-log-verification
|
||||
provides: "Plan 03-01 assertA29 + driveA29 page-side-orchestrator scaffold + __mokoshHarness wiring slot (A29 was rewritten in-place; orchestrator wire stays at 33 assertions)."
|
||||
- phase: 02-stabilize-export-pipeline
|
||||
provides: "Plan 02-04 A27 multi-tab pattern (chrome.tabs.create + finally cleanup; T-02-04-04 mitigation parity); findLatestZip helper; JSZip host-side parse pattern; DEC-011 Amendment 1 tabs permission grant (already provisioned for A27/A30/A31; no manifest change in Plan 04-03)."
|
||||
- plan: 04-01
|
||||
provides: "Audit P1 polish closure at src/content/index.ts (handleNavigation + fetch Request narrowing + rrweb timestamp normalization). Plan 04-03 verifies the rrweb pipeline at src/content/index.ts:285 end-to-end."
|
||||
- plan: 04-02
|
||||
provides: "vitest 183/183 baseline + setimmediate polyfill replacement + 0 new Function in SW chunk + generate-icons.cjs ESM/CJS fix. Plan 04-03 preserves the 183 baseline and the SW CSP-safe invariant."
|
||||
|
||||
provides:
|
||||
- "A29 strict-sentinel verification: rrweb captures the page-side-injected DOM mutation containing 'a29-mutation-sentinel' in the IncrementalSnapshot payload — proves the rrweb pipeline at src/content/index.ts:285 is genuinely live on the SAVE-active tab (closes ~2/3 flake documented in Plans 03-02 + 03-03 SUMMARYs)"
|
||||
- "A29 cs-injection-world rewrite: chrome.tabs.create(https://example.com) + chrome.scripting.executeScript world:'ISOLATED' + sentinel-bearing <div> injection + try/finally chrome.tabs.remove silent-ignore (verbatim port of assertA30/assertA31 skeleton)"
|
||||
- "Host-side strict-sentinel filter pipeline (RESEARCH Q3 Code Example Pattern 3): filter events by (EventType.IncrementalSnapshot && IncrementalSource.Mutation) → filter mutation events for adds[*].node.textContent containing the sentinel string"
|
||||
- "`import { EventType, IncrementalSource } from '@rrweb/types'` — extended binding list adds IncrementalSource alongside the prior EventType import"
|
||||
- "5 module-local constants in assertA29: A29_SAVE_ARCHIVE_TIMEOUT_MS=15s, A29_SEGMENT_SETTLE_MS=11s, A29_MUTATION_SETTLE_MS=500ms, A29_TAB_NAVIGATION_WAIT_MS=1.5s, A29_PROBE_TAB_URL='https://example.com/', A29_MUTATION_SENTINEL='a29-mutation-sentinel', A29_PROBE_DIV_ID='a29-probe-mutation'"
|
||||
- "1 module-local constant in driveA29: A29_MUTATION_SENTINEL='a29-mutation-sentinel' (kept in lockstep with page-side constant via the named-string convention)"
|
||||
|
||||
affects:
|
||||
- "Any future Phase 4+ harness work that targets the rrweb pipeline (now follows the strict-sentinel pattern — loose EventType greps are deprecated). The pattern is canonical across A29 (mutation source) + A30 (event-log type-presence) + A31 (negative-assertion + control sentinel)."
|
||||
- "Phase 4 closure (Plan 04-08 or equivalent VERIFICATION aggregator) should cite this plan as 'closes the A29 iana.org-leftover-flake' under the 'Forward-Looking Deferred Items' table from 03-VERIFICATION.md (item: 'A29 iana.org leftover race')."
|
||||
- "Future rrweb v2-stable upgrade plan (deferred to v1.1+; D-P3-03 + D-P4-01 charter): the IncrementalSource enum bindings used here are version-pinned to rrweb 2.0.0-alpha.4 (verified via node_modules/@rrweb/types/dist/index.d.ts:295). The upgrade plan should re-verify enum stability."
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Strict-sentinel filter for rrweb pipeline verification (NEW for Plan 04-03 / Phase 4): instead of asserting events.length > 0 + presence of EventType.{Meta,FullSnapshot,IncrementalSnapshot}, descend into IncrementalSnapshot.data.source === IncrementalSource.Mutation AND data.adds[*].node.textContent for a page-side-injected sentinel string. PROVES the captured mutation came from our injection (not from leftover probe-tab DOM noise). Reusable for any future assertion verifying rrweb captures specific application-level DOM changes."
|
||||
- "cs-injection-world pattern applied to rrweb verification (extends the Plan 03-02 / 03-03 pattern from event-log verification to rrweb DOM verification): chrome.tabs.create(https://example.com) + chrome.scripting.executeScript world:'ISOLATED' targeted at the content-script's realm where rrweb's MutationObserver lives. The MAIN-world alternative would have the mutation visible to the DOM but invisible to rrweb's observer (per RESEARCH Pitfall 5)."
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- "tests/uat/extension-page-harness.ts — assertA29 page-side rewrite (7-step cs-injection-world skeleton mirroring assertA30 lines 3517-3636); banner block rewritten to cite RESEARCH Q3 + Plan 03-02/03-03 flake provenance; 7 module-local constants added (A29_TAB_NAVIGATION_WAIT_MS, A29_PROBE_TAB_URL, A29_MUTATION_SENTINEL, A29_PROBE_DIV_ID); A29_SAVE_ARCHIVE_TIMEOUT_MS + A29_SEGMENT_SETTLE_MS + A29_MUTATION_SETTLE_MS retained from pre-rewrite. assertA30 + assertA31 untouched. __mokoshHarness window registration unchanged (still references assertA29). Net change: +157 lines / -53 lines."
|
||||
- "tests/uat/lib/harness-page-driver.ts — driveA29 host-side rewrite with strict-sentinel filter pipeline (RESEARCH Q3 Code Example Pattern 3); import { EventType, IncrementalSource } from '@rrweb/types' (IncrementalSource added alongside the existing EventType); module-local A29_MUTATION_SENTINEL='a29-mutation-sentinel' constant kept lockstep with page-side; filter-pipeline form (no for/continue); A29.2 = strict sentinel check (THE primary check); A29.3 + A29.4 preserved as defense-in-depth Meta + FullSnapshot presence; pre-rewrite A29.5 (loose IncrementalSnapshot >=1) retired (subsumed by stricter A29.2). driveA30 + driveA31 untouched. Net change: +99 lines / -29 lines."
|
||||
|
||||
key-decisions:
|
||||
- "Mechanical verbatim port of assertA30/assertA31 skeleton — no architectural deviation from established cs-injection-world pattern (RESEARCH Q3 explicit recommendation). The fix is intentionally surgical: same 7-step flow as A30/A31 but with a single sentinel-bearing DOM mutation injected via chrome.scripting.executeScript world:'ISOLATED' instead of A30's 5 synthetic event triggers or A31's password-vs-control input pair."
|
||||
- "Strict-sentinel filter (not loose) — RESEARCH Q3 Pitfall 3 explicitly rejects the loose `events.length > 0` strategy because it doesn't close the iana.org-leftover gap. Empirical proof: pre-rewrite A29 'PASSED' at ~2/3 success rate even when the rrweb pipeline could have been regressing silently (Plans 03-02 + 03-03 SUMMARYs). The strict filter requires the EXACT string 'a29-mutation-sentinel' that ONLY our injection can produce — no iana.org / example.com / any-other-tab path generates it."
|
||||
- "Defense-in-depth A29.3 (Meta) + A29.4 (FullSnapshot) preserved — they remain valuable as 'rrweb wired up at all' indicators even though A29.2 strict-sentinel is stronger. If A29.2 fails but A29.3 + A29.4 PASS, the diagnostic is 'rrweb is alive but MutationObserver is broken or unscheduled'. If A29.2/A29.3/A29.4 all fail simultaneously, the diagnostic is 'rrweb is not wired up at all'. The renumbered scheme makes A29.2 the canonical primary check at the front of the list."
|
||||
- "Pre-rewrite A29.5 (loose IncrementalSnapshot >=1 EventType grep) retired in favor of A29.2 strict-sentinel — strictly stronger contract. The loose A29.5 would have continued to 'pass' from iana.org leftovers in degenerate flake cases (the exact behavior Plan 04-03 closes). Keeping both would have been redundant; keeping only the loose one would have left the flake; keeping only the strict one (A29.2) is the canonical solution."
|
||||
- "IncrementalSource enum is rrweb 2.0.0-alpha.4 alpha-pinned; verified via node_modules/@rrweb/types/dist/index.d.ts:295 (Mutation=0 first declaration). If/when the v2-stable upgrade lands (deferred per D-P3-03 + D-P4-01 charter), the upgrade plan needs to re-verify the enum value stability — but the import-binding form (`{ EventType, IncrementalSource }`) is enum-source-agnostic so the runtime contract holds across versions if Mutation stays at 0."
|
||||
- "Filter-pipeline form preserved (no `continue`, no for-of with mutation-counters; chained `.filter(...)` + `.some(...)` over fold-style loops). CLAUDE.md Control Flow § honored."
|
||||
- "FORBIDDEN_HOOK_STRINGS impact = 0 entries. A29 rewrite rides production chrome.tabs.create (DEC-011 Amendment 1 grant) + chrome.scripting.executeScript (already in manifest permissions) + existing setupFreshRecording / sendMessageWithTimeout helpers. No new `__MOKOSH_UAT__`-gated test-only symbols. Tier-1 inventory stays at 12 entries (verified via tests/background/no-test-hooks-in-prod-bundle.test.ts 13/13 sub-tests PASS)."
|
||||
- "Saved-memory feedback-trust-harness-over-manual-uat.md honored — no operator empirical UAT requested for this plan. A29 is fully harness-verified; the 5/5 PASS stress test across consecutive runs is empirical proof that the flake is closed. Operator time reserved for genuinely non-automatable verification (A32 RAM is the canonical exception)."
|
||||
|
||||
patterns-established:
|
||||
- "Strict-sentinel rrweb verification pipeline (NEW pattern for any future plan touching rrweb DOM capture): `events.filter(e => e.type === EventType.IncrementalSnapshot && e.data?.source === IncrementalSource.Mutation).filter(e => (e.data?.adds ?? []).some(a => typeof a?.node?.textContent === 'string' && a.node.textContent.includes(SENTINEL)))`. Replaces all loose EventType-grep patterns. The sentinel string MUST be page-side-injected via chrome.scripting.executeScript world:'ISOLATED' so it ends up in the content-script's rrweb buffer."
|
||||
- "Lockstep page-side / host-side sentinel constant: the literal string ('a29-mutation-sentinel') is repeated as a `const` in BOTH tests/uat/extension-page-harness.ts AND tests/uat/lib/harness-page-driver.ts. Future maintainers MUST keep both in sync (changing one without the other silently breaks the test). The 04-03 review comment in driveA29 explicitly cites this lockstep requirement."
|
||||
|
||||
requirements-completed: []
|
||||
|
||||
# Metrics
|
||||
duration: "~46 min"
|
||||
completed: 2026-05-21
|
||||
---
|
||||
|
||||
# Phase 04 Plan 03: A29 cs-injection-world rewrite + strict-sentinel flake fix
|
||||
|
||||
**A29 (rrweb DOM verification) rewritten in-place using the canonical cs-injection-world pattern (verbatim port from Plan 03-02 assertA30 + Plan 03-03 assertA31). chrome.tabs.create opens a fresh https://example.com probe tab, chrome.scripting.executeScript world:'ISOLATED' injects a sentinel-bearing `<div>` into the content-script's realm where rrweb's MutationObserver lives, SAVE_ARCHIVE harvests rrweb/session.json from that tab. Host-side driveA29 applies a strict-sentinel filter (IncrementalSource.Mutation + adds[*].node.textContent.includes('a29-mutation-sentinel')) that PROVES the captured mutation came from OUR injection — closes the documented ~2/3 flake where A29 "passed" by reading iana.org leftover DOM events from A27/A28's still-open probe tabs. UAT harness 33/33 GREEN preserved; A29 flake rate: 5/5 PASS across consecutive runs (was ~2/3 historical baseline); vitest 183/183 GREEN preserved; Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~46 min (Phase 4 Wave 2; third plan)
|
||||
- **Started:** 2026-05-21T13:43:00Z (executor spawn; ~2 min after Plan 04-02 closure)
|
||||
- **Completed:** 2026-05-21T14:27:10Z (this SUMMARY draft)
|
||||
- **Tasks:** 2 of 2 plan tasks complete (both autonomous)
|
||||
- **Files modified:** 2 (tests/uat/extension-page-harness.ts + tests/uat/lib/harness-page-driver.ts; no production src/* changes; no manifest changes; no new dependencies)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- **A29 cs-injection-world rewrite** (Task 1): assertA29 page-side body replaced in-place with the 7-step skeleton mirroring assertA30 (lines 3517-3636) + assertA31 (lines 3788-3917). Setup → chrome.tabs.create probe tab → 1.5s nav wait → 11s segment settle → chrome.scripting.executeScript world:'ISOLATED' sentinel-bearing `<div>` injection → 500ms MutationObserver enqueue settle → SAVE_ARCHIVE → try/finally chrome.tabs.remove with silent-ignore. The pre-rewrite "harness-page DOM mutation" approach (which empirically never had a content script attached because `<all_urls>` doesn't cover chrome-extension://) is retired.
|
||||
- **A29 strict-sentinel filter** (Task 2): driveA29 host-side body replaced in-place with the filter-pipeline form per RESEARCH Q3 Code Example Pattern 3. Adds `IncrementalSource` to the `@rrweb/types` import binding list. Module-local `A29_MUTATION_SENTINEL='a29-mutation-sentinel'` constant kept lockstep with page-side. The check entry "A29.2: rrweb captured the injected mutation containing 'a29-mutation-sentinel' (closes iana.org-leftover-flake from Plan 03-02/03-03)" is THE primary verification — preserves A29.3/A29.4 (Meta + FullSnapshot presence) as defense-in-depth.
|
||||
- **Flake closure empirically verified**: 5/5 PASS across 5 consecutive `HEADLESS=1 SKIP_PROD_REBUILD=1 npm run test:uat` runs (was ~2/3 historical baseline per Plan 03-03 SUMMARY). Every run: A29 mutationEvents=1, sentinelEvents=1 — the injected sentinel-bearing mutation is the SOLE mutation captured (clean signal; example.com probe tab has minimal DOM churn).
|
||||
- **vitest baseline preserved**: 183/183 GREEN (full suite, 36 test files; 12.97s). Plan 04-02 baseline (183) maintained — no regression.
|
||||
- **tsc clean**: `npx tsc --noEmit` exit 0 throughout both task commits.
|
||||
- **Pre-checkpoint bundle gates** (per saved memory `feedback-pre-checkpoint-bundle-gates.md`): Tier-1 FORBIDDEN_HOOK_STRINGS 13/13 sub-tests PASS (12 strings × 0 hits); SW CSP-safety `new Function`=0 + `eval`=0 (Plan 04-02 baseline held); Buffer/window/document counts unchanged from Plan 04-02 (Plan 04-03 modifies tests/ only — no production bundle changes); manifest validates clean against locked DEC-011 Amendment 1 permission set.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each plan task was committed atomically with normal git commits + pre-commit hooks (sequential foreground mode):
|
||||
|
||||
1. **Task 1: A29 page-side rewrite (cs-injection-world + sentinel)** — `73eb9b6` (feat). Replaces assertA29 body with the 7-step cs-injection-world skeleton mirroring assertA30 + assertA31. Adds 4 new module-local constants (A29_TAB_NAVIGATION_WAIT_MS, A29_PROBE_TAB_URL, A29_MUTATION_SENTINEL, A29_PROBE_DIV_ID); retains 3 from pre-rewrite (A29_SAVE_ARCHIVE_TIMEOUT_MS, A29_SEGMENT_SETTLE_MS, A29_MUTATION_SETTLE_MS). Banner block extended to cite RESEARCH Q3 + the documented Plan 03-02/03-03 flake. assertA30 + assertA31 untouched. tsc clean; build:test clean; grep gates PASS (A29_MUTATION_SENTINEL ≥2, sentinel literal ≥1, world:'ISOLATED' ≥3 across A29+A30+A31, chrome.tabs.create.*A29_PROBE_TAB_URL ≥1). Net change: +157 lines / -53 lines.
|
||||
|
||||
2. **Task 2: A29 host-side strict-sentinel filter + 5/5 PASS stress test** — `b341a71` (feat). Replaces driveA29 body with strict-sentinel filter pipeline per RESEARCH Q3 Code Example Pattern 3. Adds IncrementalSource to the @rrweb/types import binding list. Module-local A29_MUTATION_SENTINEL constant kept lockstep with page-side. A29.2 strict-sentinel check is THE primary check; A29.3 + A29.4 (Meta + FullSnapshot) preserved as defense-in-depth. Filter-pipeline form (no for/continue). UAT empirical verification: 33/33 GREEN × 5 consecutive runs; vitest 183/183 GREEN; pre-checkpoint bundle gates 5/5 PASS. Net change: +99 lines / -29 lines.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `tests/uat/extension-page-harness.ts` — assertA29 rewritten in-place (lines 3318-3506 — banner + 7 constants + new 7-step function body). assertA30 (lines 3508+) + assertA31 (lines ~3680+) untouched; __mokoshHarness window registration unchanged.
|
||||
- `tests/uat/lib/harness-page-driver.ts` — driveA29 rewritten in-place (lines 1854-2057 — banner + 1 constant + new strict-sentinel filter pipeline). IncrementalSource added to @rrweb/types import (line 42). driveA30 + driveA31 untouched.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
See `key-decisions` frontmatter list above for the full decision log. Headline decisions:
|
||||
1. Mechanical verbatim port of assertA30/assertA31 skeleton — no architectural deviation from RESEARCH Q3's explicit recommendation.
|
||||
2. Strict-sentinel filter (not loose) — only way to close the documented iana.org-leftover flake.
|
||||
3. Defense-in-depth A29.3 + A29.4 preserved — they provide diagnostic differentiation if A29.2 fails.
|
||||
4. Pre-rewrite A29.5 (loose IncrementalSnapshot >=1) retired — strictly subsumed by A29.2 strict-sentinel.
|
||||
5. FORBIDDEN_HOOK_STRINGS impact = 0 (no new __MOKOSH_UAT__-gated symbols; Tier-1 stays at 12).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
**None - plan executed exactly as written.**
|
||||
|
||||
The plan's Task 1 + Task 2 specifications matched the implementation 1-to-1. The RESEARCH Q3 Code Example Pattern 3 host-side filter was applied verbatim. The assertA30 cs-injection-world skeleton was ported verbatim with A29-specific substitutions (single-mutation injection vs. A30's 5-trigger injection vs. A31's password+control input pair).
|
||||
|
||||
### Minor implementation specifics not in plan
|
||||
|
||||
- **eslint-disable comments on `any` types** in driveA29: the rrweb v2 event shape is polymorphic (the `data` field's structural type depends on `data.source`); the driver only reaches a few fields (source, adds, node.textContent). Three `eslint-disable-next-line @typescript-eslint/no-explicit-any` comments + explanatory comments added to keep the loose-type access localized + documented rather than rippling a strict @rrweb/types polymorphic-discriminated-union type into the driver. Plan did not specify type-strictness handling; this is the minimal idiomatic TypeScript approach.
|
||||
|
||||
- **A29.5 retirement is implicit in the plan**: the plan said "PRESERVE any existing A29.0a or A29.3+ checks (Meta/FullSnapshot presence) if they're valuable as defense-in-depth; the strict-sentinel A29.2 is THE primary check that closes the flake." The pre-rewrite A29.5 (loose IncrementalSnapshot >=1) is strictly subsumed by A29.2 (which requires IncrementalSnapshot AND Mutation source AND injected sentinel — a stronger contract). Keeping both would have been redundant + would have left a degraded check that A29.2 makes irrelevant. The new check ordering: A29.1 SAVE ack → A29.0a session.json present → A29.2 strict-sentinel → A29.3 Meta → A29.4 FullSnapshot.
|
||||
|
||||
- **Acceptance criterion `grep -cE "A29\.2:.*sentinel"` minor regex-vs-source mismatch**: the criterion as written expected the literal lowercase `sentinel` substring inside the source file; the implementation uses `A29.2: rrweb captured the injected mutation containing '${A29_MUTATION_SENTINEL}' ...` where `A29_MUTATION_SENTINEL` is an uppercase template-variable that resolves at runtime to lowercase `a29-mutation-sentinel`. Case-insensitive `grep -ciE "A29\.2:.*sentinel"` returns 2 (docstring + check entry). The substantive criterion ("the new strict-sentinel check entry exists") is fully satisfied via the runtime-resolved check name AND the documented intent in the surrounding docstring on line 1916 (`A29.2: STRICT-SENTINEL — at least one IncrementalSnapshot ...`).
|
||||
|
||||
**Total deviations:** 0 (none — plan executed exactly as written; implementation specifics above are sub-task fidelity notes, not deviations from plan contract).
|
||||
|
||||
**Impact on plan:** Plan executed as specified. No scope changes; no architectural decisions deferred; no checkpoint reached; no auth gates encountered.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
**None.** The cs-injection-world pattern from Plan 03-02 / 03-03 was empirically proven and the port to A29 was mechanical. The 5/5 PASS stress test confirmed the flake closure on the first attempt — no debugging cycle needed.
|
||||
|
||||
The only minor observation worth recording: in every UAT run, A29 mutationEvents=1 + sentinelEvents=1 — i.e., the example.com probe tab produces EXACTLY one mutation event in the 12-second observation window, and it's the one we injected. This is the cleanest possible signal (no iana.org-style mutation noise from the probe domain itself). example.com's static HTML is genuinely static — RFC 2606 reserved domains were the right choice for the cs-injection-world pattern.
|
||||
|
||||
## Stress Test Results
|
||||
|
||||
```
|
||||
=== Stress test: 5 consecutive runs ===
|
||||
----- run 1 ----- UAT harness: 33/33 assertions passed (A29.2 PASS; mutationEvents=1, sentinelEvents=1)
|
||||
----- run 2 ----- UAT harness: 33/33 assertions passed (A29.2 PASS; mutationEvents=1, sentinelEvents=1)
|
||||
----- run 3 ----- UAT harness: 33/33 assertions passed (A29.2 PASS; mutationEvents=1, sentinelEvents=1)
|
||||
----- run 4 ----- UAT harness: 33/33 assertions passed (A29.2 PASS; mutationEvents=1, sentinelEvents=1)
|
||||
----- run 5 ----- UAT harness: 33/33 assertions passed (A29.2 PASS; mutationEvents=1, sentinelEvents=1)
|
||||
```
|
||||
|
||||
Logs preserved at `/tmp/04-03/uat-run-{1..5}.log` for the duration of this session.
|
||||
|
||||
**Historical baseline:** ~2/3 PASS (documented in Plan 03-03 SUMMARY).
|
||||
**Post-rewrite:** 5/5 PASS — flake closed empirically with 5x stress.
|
||||
|
||||
## A29 Check Inventory (before/after)
|
||||
|
||||
| Check | Pre-Plan-04-03 | Post-Plan-04-03 | Rationale |
|
||||
|-------|----------------|-----------------|-----------|
|
||||
| A29.0 | "at least one zip in downloadsDir" | "at least one zip in downloadsDir" (unchanged; pre-condition for any host-side check) | Preserved |
|
||||
| A29.0a | "rrweb/session.json entry exists" | "rrweb/session.json entry exists" (unchanged) | Preserved |
|
||||
| A29.0b | "rrweb/session.json parses as JSON" (was implicit; explicit on parseErr branch) | "rrweb/session.json parses as JSON" (kept explicit on parseErr branch) | Preserved |
|
||||
| A29.1 | "SAVE_ARCHIVE ack success=true" (page-side) | "SAVE_ARCHIVE ack success=true" (page-side; unchanged) | Preserved |
|
||||
| A29.2 | "events.length > 0" (loose) | **"rrweb captured the injected mutation containing 'a29-mutation-sentinel' (closes iana.org-leftover-flake from Plan 03-02/03-03)" (strict)** | **REPLACED — primary verification flipped from loose count to strict sentinel match** |
|
||||
| A29.3 | "rrweb emitted at least one Meta event" | "rrweb emitted at least one Meta event (defense-in-depth)" | Preserved (note added) |
|
||||
| A29.4 | "rrweb emitted at least one FullSnapshot" | "rrweb emitted at least one FullSnapshot (defense-in-depth)" | Preserved (note added) |
|
||||
| A29.5 | "rrweb emitted at least one IncrementalSnapshot" | RETIRED | Subsumed by stricter A29.2 (which requires IncrementalSnapshot AND Mutation AND sentinel) |
|
||||
|
||||
**Total checks per run:** 6 → 5. The retired A29.5 was the canonical "loose-grep-that-passed-for-the-wrong-reason"; A29.2 is strictly stronger.
|
||||
|
||||
## Pre-Checkpoint Bundle Gates
|
||||
|
||||
Per saved memory `feedback-pre-checkpoint-bundle-gates.md` — gates run against fresh `npm run build` output (`dist/`):
|
||||
|
||||
| Gate | Description | Result |
|
||||
|------|-------------|--------|
|
||||
| 1 | Tier-1 FORBIDDEN_HOOK_STRINGS (12 strings × 0 hits in dist/) | PASS — `tests/background/no-test-hooks-in-prod-bundle.test.ts` 13/13 sub-tests GREEN; inventory unchanged at 12 |
|
||||
| 2 | SW CSP-safety (`new Function` / `eval(` in SW chunk) | PASS — 0 / 0 in `dist/assets/index.ts-DfBxWCT9.js` (Plan 04-02 baseline held) |
|
||||
| 3 | Node-globals (`Buffer.`) in SW chunk | PASS — 3 hits pre-existing from JSZip internals (Plan 04-01/04-02 baseline; Plan 04-03 modifies tests/ only) |
|
||||
| 4 | DOM-globals (`window.` / `document.`) in SW chunk | PASS — 1 / 3 hits pre-existing from shimmed-DOM in JSZip internals (Plan 04-01/04-02 baseline) |
|
||||
| 5 | manifest.json validation | PASS — manifest_version=3, locked DEC-011 Amendment 1 permission set, host_permissions=`["<all_urls>"]` |
|
||||
|
||||
All 5 gates green. Plan 04-03 introduces zero production bundle changes (tests/ only).
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None. Plan 04-03 closes a Repudiation threat (T-04-03-01 "A29 passes for the wrong reason — masking real rrweb regressions") via the strict-sentinel filter; no new surface introduced. The cs-injection-world pattern itself was risk-cleared by Plans 03-02 + 03-03 (chrome.tabs.create + chrome.scripting.executeScript ISOLATED are already production-shipped via DEC-011 Amendment 1 + manifest `scripting` permission).
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- [x] `tests/uat/extension-page-harness.ts` modified: confirmed via `git log --stat 73eb9b6` (157 insertions / 53 deletions)
|
||||
- [x] `tests/uat/lib/harness-page-driver.ts` modified: confirmed via `git log --stat b341a71` (99 insertions / 29 deletions)
|
||||
- [x] Commit `73eb9b6` exists: confirmed via `git log --oneline -3 | grep 73eb9b6` (Task 1)
|
||||
- [x] Commit `b341a71` exists: confirmed via `git log --oneline -3 | grep b341a71` (Task 2)
|
||||
- [x] vitest 183/183 GREEN: confirmed via `npm test` 2026-05-21T14:26:00Z (Plan 04-02 baseline preserved)
|
||||
- [x] UAT 33/33 GREEN × 5/5 runs: confirmed via stress test logs at /tmp/04-03/uat-run-{1..5}.log
|
||||
- [x] A29.2 strict-sentinel PASS in all 5 runs: confirmed via `grep -c "A29.2.*PASS"` per run log
|
||||
- [x] Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12: confirmed via `tests/background/no-test-hooks-in-prod-bundle.test.ts` 13/13 sub-tests PASS
|
||||
- [x] tsc clean: confirmed via `npx tsc --noEmit` exit 0 after each task commit
|
||||
- [x] build:test clean: confirmed via `npm run build:test` exit 0
|
||||
- [x] No accidental deletions: confirmed via `git diff --diff-filter=D --name-only HEAD~1 HEAD` (empty after each task commit)
|
||||
|
||||
## Next Plan Readiness
|
||||
|
||||
Plan 04-04 (next in Wave 2 — pending validation) is ready to execute. Plan 04-03's A29 strict-sentinel pattern is now established and can be cited by any future plan that verifies rrweb DOM capture (the `IncrementalSource.Mutation` + `adds[*].node.textContent` sentinel descent is reusable for any specific application-level DOM change verification).
|
||||
|
||||
**Outstanding Phase 4 work:**
|
||||
- Plan 04-04 (SW state persistence; ROADMAP SC #1; 5-min idle test)
|
||||
- Plan 04-05 (fetch + XHR network_error harness extension; ROADMAP SC #2)
|
||||
- Plan 04-06 (visual polish — cursor visibility verification + dark-logo contrast; per RESEARCH Finding 4 cursor visibility already shipped Plan 01-09, so this collapses to verification + doc correction + dark-logo work)
|
||||
- Plan 04-07 / 04-08 (VERIFICATION.md aggregator + ROADMAP backfill + alpha re-distribution prep)
|
||||
|
||||
**No blockers introduced by Plan 04-03.** The strict-sentinel pattern is purely additive — future plans inheriting the cs-injection-world or rrweb-verification surface can adopt or extend it without any retrofit cost.
|
||||
|
||||
---
|
||||
*Phase: 04-harden-clean-up-optional*
|
||||
*Completed: 2026-05-21*
|
||||
Reference in New Issue
Block a user