Four binary in/out fixes, each tied to a downstream phase. Codex review forced a scope correction: two original items (GA4 allow-list removal, per-section catch-and-continue) already shipped in v1.0. The actual remaining work is a DataLayer-side allow-list the audit missed, plus three smaller items.
Not a full audit. The v1.0 AUDIT.md and BI-USER-FIT-AUDIT.md still own broader cleanup. Each in-scope item below has an explicit binary rationale tied to which downstream phase it unblocks.
Where: src/utils/eventUtils.ts:80-86
if ( event.includes("gallery") || event.includes("player") || event.includes("page") || event.includes("article") ) { processEventInMemory(mediaName, "dataLayer", event, functionName); }
Why in: Phase 10's unclassified bucket requires the matcher to see every captured event. The GA4 path was fixed earlier; this DataLayer path still pre-filters by 4 hardcoded substrings. Any DataLayer event without gallery|player|page|article in its name (e.g. subscription_click, consent_*, cta_*, paywall_view) is silently dropped before storage, then later surfaces as a Phase 10 tracking-broken false-positive.
Fix: drop the inner if — capture every event value. Matching decides relevance. Add length / control-char hygiene (256-char cap, strip non-printable).
Where: src/bi-measurement.ts:145-224 (pre-section setup) and the fatal IIFE catch at :430-434.
Why in: Section flow execution is already wrapped (Phase 7 work, verified at :220-340). But pre-section setup — browser.newContext(...), CncSite.load(...), the consent click + cleanup — is still outside the section-aborted boundary. If site.load() throws because a domain is down, the whole run aborts with no per-section rows, contradicting Phase 2's "no silent gaps" promise.
Fix: wrap each setup step in its own try; on failure, emit one section-aborted row per planned flow on that media with outcome:"section-aborted", failureKind:classifyError(err), and proceed to the next media.
Where: src/lib/uniweb-site.ts (8 raw throw new Error(...) sites listed in BI-FIT-AUDIT §4)
Why in: Phase 10's robot-broken triage needs to distinguish "modal was up" from "page layout changed". Raw Error doesn't carry that context.
Fix:
TestError extends Error { uiEvidence?, selectorAttempted?, cause? }.cause.classifyError: TestError.uiEvidence checked first; if modalBlocking or !consentAccepted → robot-broken. Then fall through to existing rules (timeout, network, default). Otherwise the strongest Phase 10 signal gets shadowed.classifyError to read err.cause recursively for Playwright detection inside wrapped TestError.Where: src/utils/eventUtils.ts:73-79 (DataLayer scraping) — root cause re-identified by codex.
Why in: EVENT-COVERAGE-AUDIT documents gallery_previous.dataLayer containing leftover gallery_open / gallery_next from prior steps. Original draft suspected attachRequestHandler reset ordering. Real cause: processDataLayerEventsInMemory rereads the entire cumulative window.dataLayer array every call. resetFunctionEventStorage clears the bucket, but the next scrape re-ingests every prior event.
Fix:
dataLayerCursor: Map<Page, number>.dataLayer.slice(cursor); update cursor to dataLayer.length.handlerUtils.ts:64-68 stores any en.Tests tests/capture-integrity.test.js:76-99 already cover stream_start / cta_click outside the old allow-list. Out — verify with a quick regression assertion only.
bi-measurement.ts:220-340.Section flow execution already produces section-aborted rows via Phase 7's classifier. The narrowed Z2 above handles only the remaining setup gap.
| Item | Why out |
|---|---|
Remove mariadb dep | Pure cleanup. Doesn't impact v1.1. |
| Translate Czech log messages | Cosmetic. Old strings stay; new code uses English. |
Resurrect src/tools/generateConfig.ts | Authoring tooling is v1.2+ scope. |
| Replace JSON configs with YAML / TOML | No actual blocker. |
| Robot signatures → options object | CLAUDE.md hard-no. |
| Add ESLint | CLAUDE.md hard-no. |
| Refactor mutable singletons | CLAUDE.md preserves the drain/reset contract. |
event:"subscription_click" appears in eventStorage.<media>.<fn>.dataLayer. Today it doesn't.logger.warn; matcher contract unchanged.site.load() to throw on media A still allows media B to run; each planned flow on A produces a section-aborted row.throw new Error(...) in uniweb-site.ts replaced with TestError; grep returns zero. TestError.cause set where original error exists.TestError with uiEvidence.modalBlocking=true AND wrapped Playwright timeout classifies as robot-broken (evidence wins). Same test without modal flag → timeout.gallery_previous.dataLayer contains exactly the events from the second flow's window. Test fails without cursor; passes with it.npm run check + npm run test:logic pass; no new npm deps.Z1 (GA4 allow-list) and Z2-flow (per-section catch) had already shipped in v1.0. Codex verified line-by-line. Phase 12 rewritten around the actual remaining work.
The audit had documented the GA4 allow-list but missed the equivalent filter in processDataLayerEventsInMemory at eventUtils.ts:80-86. Promoted to new Z1.
Without reordering, the existing timeout rule in classifyError would shadow TestError.uiEvidence.modalBlocking — defeating the strongest Phase 10 signal. uiEvidence now checked first; .cause recursion ensures wrapped errors still classify correctly.
Leakage is from rereading the cumulative window.dataLayer on every scrape, not from handler reset ordering. Fix is a per-page cursor.