Phase 05 · v1.0 · Shipped

Exact matching behind a dry-run gate.

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.

Milestonev1.0 ScopeVAL-02, VAL-03, VAL-04 Depends onPhase 4 CheckpointHuman-review gate
Phase boundary

Build the new matcher; don't flip the switch.

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.

Decisions
Q1 · Exact matcher (VAL-02)

New pure module 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.

Q2 · Alias map (VAL-03) · AMENDED 2026-05-20

Keyed by FLOW NAME, not canonical expected name.

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": []
  }
}
Q3 · Dry-run diff (VAL-04)

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.

Gate resolution · 2026-05-20

Player canonical → player_start; rerun shows 0 flips.

One canonical name corrected (playerplayer_start) before dry-run rerun. On the blesk fixture, exact + alias matching produced ZERO verdict flips vs substring. Phase 6 cleared to proceed.

Success criteria
  1. New pure matcher matchExpectedEvent ships and is unit-tested across exact + alias + miss cases.
  2. Alias map data/event-name-aliases.json shipped with at least one entry exercised (page_prev.dataLayer = ["page_prev"]).
  3. Production validateEvents behavior unchanged this phase (substring still authoritative).
  4. npm run dry-run:matching produces a deterministic diff report against the captured blesk fixture.
  5. Human review checkpoint passes with zero unexplained flips before Phase 6.
Files
newsrc/utils/eventMatching.ts
newdata/event-name-aliases.json
newscripts/dry-run-matching.ts
new.planning/phases/05-…/dry-run-report.{md,json}
newtests/event-matching.test.js
modpackage.jsondry-run:matching script
← PreviousPhase 4 · BI-readable event mapping Next →Phase 6 · Cutover to exact matching