Files
mokosh/.planning/phases/04-harden-clean-up-optional/04-03-SUMMARY.md
Mark 303644f8cc docs(04-03): complete harden-clean-up-optional plan 04-03 — A29 flake fix
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]
2026-05-21 17:01:58 +02:00

26 KiB
Raw Blame History

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
uat-harness
a29-rewrite
cs-injection-world
flake-stabilization
strict-sentinel
rrweb
incremental-source-mutation
charter-d-p4-01
phase-4-wave-2
phase provides
03-spec-10-smoke-verification-dom-event-log-verification 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 provides
03-spec-10-smoke-verification-dom-event-log-verification Plan 03-01 assertA29 + driveA29 page-side-orchestrator scaffold + __mokoshHarness wiring slot (A29 was rewritten in-place; orchestrator wire stays at 33 assertions).
phase provides
02-stabilize-export-pipeline 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 provides
04-01 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 provides
04-02 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.
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)
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.
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).
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.
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).
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.
~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 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 testb341a71 (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

  • tests/uat/extension-page-harness.ts modified: confirmed via git log --stat 73eb9b6 (157 insertions / 53 deletions)
  • tests/uat/lib/harness-page-driver.ts modified: confirmed via git log --stat b341a71 (99 insertions / 29 deletions)
  • Commit 73eb9b6 exists: confirmed via git log --oneline -3 | grep 73eb9b6 (Task 1)
  • Commit b341a71 exists: confirmed via git log --oneline -3 | grep b341a71 (Task 2)
  • vitest 183/183 GREEN: confirmed via npm test 2026-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.ts 13/13 sub-tests PASS
  • tsc clean: confirmed via npx tsc --noEmit exit 0 after each task commit
  • build:test clean: confirmed via npm run build:test exit 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