A29 (rrweb DOM verification) rewritten in-place via the canonical cs- injection-world pattern + strict-sentinel filter. Closes ~2/3 flake documented in Plans 03-02 + 03-03 SUMMARYs (A29 was "passing" by reading iana.org leftover DOM events from A27/A28's still-open probe tabs; a real rrweb regression at src/content/index.ts:284 would have been masked). Plan 04-03 task commits (atomic; sequential foreground mode): -73eb9b6: Task 1 — A29 page-side cs-injection-world skeleton + sentinel-bearing <div> injection -b341a71: Task 2 — A29 host-side strict-sentinel filter (RESEARCH Q3 Code Example Pattern 3); IncrementalSource added to @rrweb/types import binding; A29.2 PASS × 5/5 consecutive UAT runs Empirical evidence: - vitest 183/183 GREEN preserved (Plan 04-02 baseline) - UAT harness 33/33 GREEN × 5 consecutive runs - A29 strict-sentinel: mutationEvents=1, sentinelEvents=1 in ALL 5 runs - Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 - SW chunk: 0 new Function, 0 eval (Plan 04-02 baseline held) - Pre-checkpoint bundle gates 5/5 PASS STATE.md + ROADMAP.md updated per sequential workflow: - Plan counter advanced 3 → 4 of 7 - Progress 83% → 87% (26/30 plans complete) - Decision log entry added for Plan 04-03 - ROADMAP Phase 4 04-03 row flipped to [x]
26 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-harden-clean-up-optional | 03 | testing |
|
|
|
|
|
|
|
|
~46 min | 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
IncrementalSourceto the@rrweb/typesimport binding list. Module-localA29_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:uatruns (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 --noEmitexit 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-safetynew 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):
-
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. -
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:
- Mechanical verbatim port of assertA30/assertA31 skeleton — no architectural deviation from RESEARCH Q3's explicit recommendation.
- Strict-sentinel filter (not loose) — only way to close the documented iana.org-leftover flake.
- Defense-in-depth A29.3 + A29.4 preserved — they provide diagnostic differentiation if A29.2 fails.
- Pre-rewrite A29.5 (loose IncrementalSnapshot >=1) retired — strictly subsumed by A29.2 strict-sentinel.
- 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
anytypes in driveA29: the rrweb v2 event shape is polymorphic (thedatafield's structural type depends ondata.source); the driver only reaches a few fields (source, adds, node.textContent). Threeeslint-disable-next-line @typescript-eslint/no-explicit-anycomments + 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 lowercasesentinelsubstring inside the source file; the implementation usesA29.2: rrweb captured the injected mutation containing '${A29_MUTATION_SENTINEL}' ...whereA29_MUTATION_SENTINELis an uppercase template-variable that resolves at runtime to lowercasea29-mutation-sentinel. Case-insensitivegrep -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
tests/uat/extension-page-harness.tsmodified: confirmed viagit log --stat 73eb9b6(157 insertions / 53 deletions)tests/uat/lib/harness-page-driver.tsmodified: confirmed viagit log --stat b341a71(99 insertions / 29 deletions)- Commit
73eb9b6exists: confirmed viagit log --oneline -3 | grep 73eb9b6(Task 1) - Commit
b341a71exists: confirmed viagit log --oneline -3 | grep b341a71(Task 2) - vitest 183/183 GREEN: confirmed via
npm test2026-05-21T14:26:00Z (Plan 04-02 baseline preserved) - UAT 33/33 GREEN × 5/5 runs: confirmed via stress test logs at /tmp/04-03/uat-run-{1..5}.log
- A29.2 strict-sentinel PASS in all 5 runs: confirmed via
grep -c "A29.2.*PASS"per run log - Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12: confirmed via
tests/background/no-test-hooks-in-prod-bundle.test.ts13/13 sub-tests PASS - tsc clean: confirmed via
npx tsc --noEmitexit 0 after each task commit - build:test clean: confirmed via
npm run build:testexit 0 - 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