Implement exact event-name matching plus an explicit per-source alias map, and a dry-run mode that reports old (substring) vs new (exact + alias) pass/fail verdicts on real per-domain configs WITHOUT changing stored results or production validation. Phase ends with a human review checkpoint; Phase 6 is blocked until approved.
Production validateEvents stays substring-based this phase — the cutover is Phase 6. The dry-run diff against real fixtures lets the BI team review every verdict change BEFORE behavior flips.
src/utils/eventMatching.ts.matchExpectedEvent(captured, expected, aliases): boolean
= captured.includes(expected)
|| aliases.some(a => captured.includes(a))
Exact, case-sensitive. The old substring .some(e => e.includes(expected)) at eventUtils.ts:111-113 stays UNCHANGED — additive only.
Original wording was keyed by canonical name; rejected because Gemius view is the expected name for 8 of 9 flows — an alias keyed by view would apply ambiguously to all 8. Flow-keyed avoids that.
{
"<flowName>": {
"dataLayer": ["alt1", "alt2"],
"ga4": [],
"gemius": []
}
}
npm run dry-run:matching against real fixtures.Replays the matcher against captured eventStorage.json fixtures, emits a per-flow verdict diff (old → new) + markdown report at .planning/phases/05-…/dry-run-report.md. Human checkpoint reviews every flipped verdict before Phase 6 cutover.
player_start; rerun shows 0 flips.One canonical name corrected (player → player_start) before dry-run rerun. On the blesk fixture, exact + alias matching produced ZERO verdict flips vs substring. Phase 6 cleared to proceed.
matchExpectedEvent ships and is unit-tested across exact + alias + miss cases.data/event-name-aliases.json shipped with at least one entry exercised (page_prev.dataLayer = ["page_prev"]).validateEvents behavior unchanged this phase (substring still authoritative).npm run dry-run:matching produces a deterministic diff report against the captured blesk fixture.