'use strict'; /** * CANONICAL EVENT IDENTITY + PUBLICATION COMMIT TRUTH. * * Two findings drive this suite, both verified against production: * * 1. EVENT LABELS ARE NOT EVENT IDENTITY. `gameIdFor` derives * `sport:date:away@home`, which has no occurrence component, so both halves * of a doubleheader produce a byte-identical string. MLB 2026 through * 2026-08-27: 19 doubleheaders / 38 real games collapse into 19 labels. The * 2026-08-17 St. Louis @ Cincinnati pair (gamePk 824514 / 824478) landed in * ONE id holding 171 ledger rows and FOUR distinct starting pitchers. * * 2. PUBLISHED MUST MEAN COMMITTED. Retention persisted at * snapshotService:1137 while the authoritative slate write is at :1152, and * the winner was marked published earlier still. */ const path = require('path'); const fs = require('fs'); const E = require('../../src/services/event/eventIdentity'); const L = require('../../src/services/read/readLineage'); const retention = require('../../src/services/retentionService'); const ROOT = path.resolve(__dirname, '..', '..'); // The REAL doubleheader, from statsapi. const DH_GAMES = [ { gamePk: 824514, gameNumber: 1, gameDate: '2026-08-17T17:40:00Z', home: { team: 'Cincinnati Reds' }, away: { team: 'St. Louis Cardinals' } }, { gamePk: 824478, gameNumber: 2, gameDate: '2026-08-17T22:40:00Z', home: { team: 'Cincinnati Reds' }, away: { team: 'St. Louis Cardinals' } }, ]; const dhProp = (time, over = {}) => ({ player: 'Andre Pallante', stat_type: 'strikeouts', line: 4.5, book: 'draftkings', home_team: 'Cincinnati Reds', away_team: 'St. Louis Cardinals', game_date: '2026-08-17', game_time: time, ...over, }); describe('EVENT IDENTITY — canonical, sport-neutral contract', () => { test('MLB resolves to a namespaced canonical id from statsapi gamePk', () => { const r = E.resolveEvent('mlb', dhProp('2026-08-17T17:40:00Z'), DH_GAMES); expect(r.canonical_event_id).toBe('mlb:gamepk:824514'); expect(r.event_identity_source).toBe(E.IDENTITY_SOURCE.MLB_STATSAPI_GAMEPK); expect(r.event_identity_method).toBe(E.IDENTITY_METHOD.CANONICAL); expect(r.event_occurrence).toBe(1); }); test('an unsupported sport records UNSUPPORTED_SPORT, never a guessed id', () => { for (const sp of ['wnba', 'nba', 'soccer', 'nfl']) { const r = E.resolveEvent(sp, dhProp('2026-08-17T17:40:00Z'), DH_GAMES); expect(r.event_identity_method).toBe(E.IDENTITY_METHOD.UNSUPPORTED_SPORT); expect(r.canonical_event_id).toBeNull(); } }); test('team resolution is canonical, handling every real spelling', () => { const cases = [ ['Cincinnati Reds', 'CIN'], ['CINReds', 'CIN'], ['CIN', 'CIN'], ['St. Louis Cardinals', 'STL'], ['St.LouisCardinals', 'STL'], ['New York Mets', 'NYM'], ['NYMets', 'NYM'], ['CLEGuardians', 'CLE'], // The trap: both end in "sox". Longest nickname must win. ['Chicago White Sox', 'CWS'], ['ChicagoWhiteSox', 'CWS'], ['Boston Red Sox', 'BOS'], ['BOSRedSox', 'BOS'], ['Toronto Blue Jays', 'TOR'], ['TORBlueJays', 'TOR'], ]; for (const [name, abbr] of cases) expect(E.teamAbbr(name)).toBe(abbr); expect(E.teamAbbr('Not A Team')).toBeNull(); expect(E.teamAbbr('')).toBeNull(); }); test('an unresolvable team name matches NOTHING rather than falling back to text', () => { expect(E.teamsMatch(DH_GAMES[0], 'Cincinnati Reds', 'Some Fake Club')).toBe(false); }); }); describe('DOUBLEHEADER SAFETY — the mandatory proof', () => { test('two games of one doubleheader receive DIFFERENT canonical ids', () => { const g1 = E.resolveEvent('mlb', dhProp('2026-08-17T17:40:00Z'), DH_GAMES); const g2 = E.resolveEvent('mlb', dhProp('2026-08-17T22:40:00Z'), DH_GAMES); expect(g1.canonical_event_id).toBe('mlb:gamepk:824514'); expect(g2.canonical_event_id).toBe('mlb:gamepk:824478'); expect(g1.canonical_event_id).not.toBe(g2.canonical_event_id); expect(g1.event_occurrence).toBe(1); expect(g2.event_occurrence).toBe(2); }); test('both survive dedupe — same player, stat and line, different games', () => { const gs = require('../../src/services/gradeSlateService'); const props = [dhProp('2026-08-17T17:40:00Z'), dhProp('2026-08-17T22:40:00Z')]; E.attachEventIdentity('mlb', props, DH_GAMES); const kept = gs.__internals ? gs.__internals.dedupeProps(props, 100) : null; expect(kept).not.toBeNull(); expect(kept).toHaveLength(2); expect(new Set(kept.map((p) => p.canonical_event_id)).size).toBe(2); }); test('their LEGACY derived label collides, and that no longer matters', () => { const gameIdFor = require('../../src/services/ledgerService').__internals.gameIdFor; const prop = { away_team: 'St. Louis Cardinals', home_team: 'Cincinnati Reds' }; const a = gameIdFor('mlb', prop, '2026-08-17'); const b = gameIdFor('mlb', prop, '2026-08-17'); expect(a).toBe(b); // the label cannot separate them... // ...but canonical identity does, and lineage keys on that. const base = { sport: 'mlb', game_date: '2026-08-17', player_key: 'andre pallante', stat: 'strikeouts', side: 'over', line: 4.5, game_id: a, captured_at: '2026-08-17T14:00:00Z', published: true, grade: 'B', p_win: 0.6, }; const r1 = { ...base, canonical_event_id: 'mlb:gamepk:824514' }; const r2 = { ...base, canonical_event_id: 'mlb:gamepk:824478' }; expect(L.readNaturalKey(r1)).not.toBe(L.readNaturalKey(r2)); }); test('neither game can supersede or overwrite the other', () => { const base = { sport: 'mlb', game_date: '2026-08-17', player_key: 'andre pallante', stat: 'strikeouts', side: 'over', line: 4.5, game_id: 'mlb:2026-08-17:StL@Cin', captured_at: '2026-08-17T14:00:00Z', published: true, grade: 'B', p_win: 0.6, }; const g1 = { ...base, canonical_event_id: 'mlb:gamepk:824514' }; const g2 = { ...base, canonical_event_id: 'mlb:gamepk:824478' }; const r1 = L.resolveLineage({ candidate: g1, existing: [], mintReadId: () => 'RA' }); const row1 = { id: 101, read_id: 'RA', read_natural_key: r1.read_natural_key, game_id: base.game_id, claim_digest: r1.claim_digest, revision_ordinal: 0, lineage_action: 'ORIGIN', supersedes_id: null, captured_at: base.captured_at, claim: g1, }; const r2 = L.resolveLineage({ candidate: g2, existing: [row1], mintReadId: () => 'RB' }); expect(r2.read_id).toBe('RB'); expect(r2.action).toBe(L.LINEAGE_ACTION.ORIGIN); expect(r2.supersedes_id).toBeNull(); }); test('identity is decided by TIME, and refuses when time cannot decide', () => { const noTime = E.resolveEvent('mlb', dhProp(null), DH_GAMES); expect(noTime.event_identity_method).toBe(E.IDENTITY_METHOD.UNRESOLVED); expect(noTime.reason).toBe('ambiguous_no_game_time'); // A time matching neither game is refused rather than snapped to the nearer. const farOff = E.resolveEvent('mlb', dhProp('2026-08-17T05:00:00Z'), DH_GAMES); expect(farOff.event_identity_method).toBe(E.IDENTITY_METHOD.UNRESOLVED); expect(farOff.reason).toBe('ambiguous_time_too_far'); }); test('a single-game day needs no time to resolve', () => { const r = E.resolveEvent('mlb', dhProp(null), [DH_GAMES[0]]); expect(r.canonical_event_id).toBe('mlb:gamepk:824514'); }); }); describe('DEDUPE SEMANTICS — collapse books, never events', () => { const gs = require('../../src/services/gradeSlateService'); const dedupe = (props, n) => gs.__internals.dedupeProps(props, n); test('the same proposition from several books still collapses to one', () => { const props = ['draftkings', 'fanduel', 'betmgm'].map((book) => ({ ...dhProp('2026-08-17T17:40:00Z'), book, canonical_event_id: 'mlb:gamepk:824514' })); expect(dedupe(props, 100)).toHaveLength(1); }); test('the same proposition in two events does NOT collapse', () => { const props = [ { ...dhProp('2026-08-17T17:40:00Z'), canonical_event_id: 'mlb:gamepk:824514' }, { ...dhProp('2026-08-17T22:40:00Z'), canonical_event_id: 'mlb:gamepk:824478' }, ]; expect(dedupe(props, 100)).toHaveLength(2); }); test('without canonical identity behaviour is UNCHANGED from before', () => { // Same teams, same date, no canonical id: the legacy fallback collapses // them exactly as the old key did. The fix does not pretend to distinguish // what it cannot. const props = [dhProp('2026-08-17T17:40:00Z'), dhProp('2026-08-17T22:40:00Z')]; expect(dedupe(props, 100)).toHaveLength(1); }); test('different games on the same date never collapse even without canonical id', () => { const a = { ...dhProp('t'), home_team: 'Cincinnati Reds', away_team: 'St. Louis Cardinals' }; const b = { ...dhProp('t'), home_team: 'Boston Red Sox', away_team: 'Arizona Diamondbacks' }; expect(dedupe([a, b], 100)).toHaveLength(2); }); }); describe('PUBLICATION COMMIT TRUTH', () => { const served = () => ({ sport: 'mlb', game_date: '2026-08-17', player_key: 'x', stat: 'hits', side: 'over', line: 0.5, game_id: 'g', canonical_event_id: 'mlb:gamepk:824514', captured_at: '2026-08-17T14:00:00Z', grade: 'B', p_win: 0.61, published: true, }); const okClient = () => ({ from: () => ({ upsert: async () => ({ error: null }) }) }); test('capture does NOT resolve lineage — persist leaves it null', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/retentionService.js'), 'utf8'); const persistBody = src.slice(src.indexOf('async function persist(')); expect(persistBody).not.toMatch(/attachLineage\(/); }); test('the slate write PRECEDES the publication commit', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8'); const persistAt = src.indexOf('await persistRetention(enriched'); const slateAt = src.indexOf('cacheSet(`snapshot:${sp}:latest`'); const commitAt = src.indexOf('commitPublication({'); expect(persistAt).toBeGreaterThan(-1); expect(slateAt).toBeGreaterThan(persistAt); expect(commitAt).toBeGreaterThan(slateAt); }); test('WINNER SELECTED + REDIS OK -> the exact snapshot becomes published', async () => { const rows = [served()]; const out = await retention.commitPublication( { rows, publicationId: 'slate-1', publishedAt: '2026-08-17T15:00:00Z' }, { getClient: okClient, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(out.published).toBe(1); expect(rows[0].published_at).toBe('2026-08-17T15:00:00Z'); expect(rows[0].publication_id).toBe('slate-1'); expect(rows[0].lineage_action).toBe('ORIGIN'); }); test('WINNER SELECTED + REDIS FAILS -> nothing is published', () => { // The commit is unreachable when the slate write throws: it sits after the // awaited cacheSet in the same function, with no catch between them. const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8'); const slateAt = src.indexOf('cacheSet(`snapshot:${sp}:latest`'); const commitAt = src.indexOf('commitPublication({'); const between = src.slice(slateAt, commitAt); expect(between).not.toMatch(/catch\s*\(/); }); test('a row that was never served is not committed even in a served batch', async () => { const rows = [served(), { ...served(), side: 'under', published: false }]; const out = await retention.commitPublication( { rows, publicationId: 'slate-1', publishedAt: 't' }, { getClient: okClient, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(out.candidates).toBe(1); expect(rows[1].published_at).toBeUndefined(); expect(rows[1].lineage_action).toBeUndefined(); }); test('REDIS OK + PUBLICATION UPDATE FAILS -> understated, never overstated', async () => { const rows = [served()]; const failing = () => ({ from: () => ({ upsert: async () => ({ error: { message: 'db down' } }) }) }); const out = await retention.commitPublication( { rows, publicationId: 'slate-1', publishedAt: 't' }, { getClient: failing, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(out.published).toBe(0); expect(out.error).toBe('db down'); }); test('no database configured skips without throwing', async () => { const out = await retention.commitPublication( { rows: [served()], publicationId: 's', publishedAt: 't' }, { getClient: () => null }, ); expect(out.skipped).toBe(true); expect(out.error).toBeNull(); }); test('retrying the commit does not manufacture a second publication', async () => { const rows = [served()]; const deps = { getClient: okClient, fetchExisting: async () => [], mintReadId: () => 'R1' }; await retention.commitPublication({ rows, publicationId: 's', publishedAt: 't' }, deps); const first = rows[0].claim_digest; // Second run sees the first as existing history. // A COMPLETED lineage action, in the shape production actually persists. // `attachLineage` assigns the action, the state and both version stamps in // one block, and all 1,024 live rows carry all nine fields — a digest with // no schema version is uninterpretable, so the validity predicate requires // them. This fixture previously omitted four of them, describing a row the // writer cannot emit. const existing = [{ id: 1, read_id: rows[0].read_id, read_natural_key: rows[0].read_natural_key, game_id: rows[0].game_id, claim_digest: first, revision_ordinal: 0, lineage_action: 'ORIGIN', supersedes_id: null, captured_at: rows[0].captured_at, lineage_state: 'LIVE', lineage_version: 'lin@1', claim_schema_version: 'claim@1', digest_algorithm_version: 'sha256-json-sorted@1', claim: rows[0], }]; const rows2 = [served()]; const out2 = await retention.commitPublication( { rows: rows2, publicationId: 's', publishedAt: 't' }, { ...deps, fetchExisting: async () => existing }, ); expect(out2.lineage.recaptures).toBe(1); expect(out2.lineage.revisions).toBe(0); }); }); describe('PUBLICATION CLOCK', () => { test('captured_at and published_at are different fields answering different questions', async () => { const rows = [{ sport: 'mlb', game_date: '2026-08-17', player_key: 'x', stat: 'hits', side: 'over', line: 0.5, game_id: 'g', canonical_event_id: 'mlb:gamepk:824514', captured_at: '2026-08-17T14:00:00Z', grade: 'B', p_win: 0.61, published: true, }]; await retention.commitPublication( { rows, publicationId: 's', publishedAt: '2026-08-17T15:07:00Z' }, { getClient: () => ({ from: () => ({ upsert: async () => ({ error: null }) }) }), fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(rows[0].captured_at).toBe('2026-08-17T14:00:00Z'); expect(rows[0].published_at).toBe('2026-08-17T15:07:00Z'); expect(rows[0].published_at).not.toBe(rows[0].captured_at); }); }); describe('CONCURRENCY RECOVERY — bounded', () => { const served = () => ({ sport: 'mlb', game_date: '2026-08-17', player_key: 'x', stat: 'hits', side: 'over', line: 0.5, game_id: 'g', canonical_event_id: 'mlb:gamepk:824514', captured_at: 't', grade: 'B', p_win: 0.61, published: true, }); const conflict = { message: 'duplicate key value violates unique constraint "model_snapshots_supersedes_unique"' }; test('the supersedes conflict is recognised and other errors are not', () => { expect(retention.isSupersedesConflict(conflict)).toBe(true); expect(retention.isSupersedesConflict({ message: 'connection refused' })).toBe(false); expect(retention.isSupersedesConflict(null)).toBe(false); }); test('a losing writer re-resolves against the new head and succeeds', async () => { let n = 0; const sb = { from: () => ({ upsert: async () => { n += 1; return n === 1 ? { error: conflict } : { error: null }; } }) }; const out = await retention.commitPublication( { rows: [served()], publicationId: 's', publishedAt: 't' }, { getClient: () => sb, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(out.conflicts).toBe(1); expect(out.recovered).toBe(1); expect(out.published).toBe(1); }); test('a permanent conflict fails EXPLICITLY and is bounded', async () => { let n = 0; const sb = { from: () => ({ upsert: async () => { n += 1; return { error: conflict }; } }) }; const out = await retention.commitPublication( { rows: [served()], publicationId: 's', publishedAt: 't' }, { getClient: () => sb, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(out.recovery_failed).toBe(1); expect(out.error).toBeTruthy(); // 1 initial + FORK_RETRY_LIMIT attempts. Bounded, never a spin. expect(n).toBe(1 + retention.FORK_RETRY_LIMIT); expect(retention.FORK_RETRY_LIMIT).toBeLessThanOrEqual(3); }); test('a non-conflict error is NOT retried', async () => { let n = 0; const sb = { from: () => ({ upsert: async () => { n += 1; return { error: { message: 'db down' } }; } }) }; const out = await retention.commitPublication( { rows: [served()], publicationId: 's', publishedAt: 't' }, { getClient: () => sb, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(n).toBe(1); expect(out.error).toBe('db down'); expect(out.conflicts).toBeUndefined(); }); }); describe('CHANGE SEMANTICS — identity is not an opinion', () => { test('correcting a derived event label is NOT a BELIEF change', () => { const a = { line: 0.5, side: 'over', p_win: 0.61, grade: 'B', canonical_event_id: null }; const b = { ...a, canonical_event_id: 'mlb:gamepk:824514' }; expect(L.CLAIM_FIELD_CLASSES.canonical_event_id).toBeUndefined(); expect(L.classifyChange(a, b).change_type).toBe(L.CHANGE_TYPE.NO_MATERIAL_PUBLISHED_CHANGE); expect(L.classifyChange(a, b).belief_changed).toBe(false); }); }); describe('SETTLEMENT SEPARATION still holds after propagation', () => { test('canonical event, publication and lineage fields are claim-owned', () => { for (const f of ['published', 'claim_digest', 'revision_ordinal', 'change_type']) { expect(L.settlementUpdateIsSafe({ outcome: 'hit', [f]: 'x' }).violations).toContain(f); } }); test('the real production settle patch still passes', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotSettlementService.js'), 'utf8'); const m = src.match(/\.update\(\{([\s\S]*?)\}\)/); const keys = [...m[1].matchAll(/^\s*([a-z_0-9]+)\s*:/gm)].map((x) => x[1]); expect(L.settlementUpdateIsSafe(Object.fromEntries(keys.map((k) => [k, 1])))).toMatchObject({ safe: true }); }); }); describe('PRODUCTION PRESERVATION', () => { test('no product path imports event identity or lineage', () => { const walk = (dir, out = []) => { for (const e of fs.readdirSync(dir, { withFileTypes: true })) { if (e.name === 'node_modules' || e.name.startsWith('.')) continue; const full = path.join(dir, e.name); if (e.isDirectory()) walk(full, out); else if (/\.(js|jsx|ts|tsx)$/.test(e.name)) out.push(full); } return out; }; const allowed = new Set([ path.join(ROOT, 'src/services/read/readLineage.js'), path.join(ROOT, 'src/services/retentionService.js'), path.join(ROOT, 'src/services/event/eventIdentity.js'), path.join(ROOT, 'src/services/snapshotService.js'), ]); const files = [ ...walk(path.join(ROOT, 'src/routes')), ...(fs.existsSync(path.join(ROOT, 'web/src')) ? walk(path.join(ROOT, 'web/src')) : []), ].filter((f) => !allowed.has(f)); const offenders = files.filter((f) => { const src = fs.readFileSync(f, 'utf8') .replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1'); return /(require\(|from\s+)['"`][^'"`]*(readLineage|eventIdentity)['"`]/.test(src); }).map((f) => path.relative(ROOT, f)); expect(offenders).toEqual([]); }); test('the served envelope still comes from the cache', () => { const src = fs.readFileSync(path.join(ROOT, 'src/routes/snapshot.js'), 'utf8'); expect(src).toMatch(/cacheGet/); expect(src).not.toMatch(/model_snapshots/); expect(src).not.toMatch(/canonical_event_id/); }); }); describe('CANARY SCOPE — bounded and reversible', () => { const load = (val) => { const prev = process.env.LINEAGE_CANARY_SPORTS; if (val === undefined) delete process.env.LINEAGE_CANARY_SPORTS; else process.env.LINEAGE_CANARY_SPORTS = val; jest.resetModules(); // eslint-disable-next-line global-require const m = require('../../src/services/snapshotService'); if (prev === undefined) delete process.env.LINEAGE_CANARY_SPORTS; else process.env.LINEAGE_CANARY_SPORTS = prev; return m; }; test('DARK BY DEFAULT — an unset flag enables lineage for nothing', () => { // This release deploys the integrity repairs with the observer off. const m = load(undefined); expect(m.LINEAGE_CANARY_SPORTS).toEqual([]); for (const sp of ['mlb', 'wnba', 'nba', 'soccer']) { expect(m.lineageCanaryEnabled(sp)).toBe(false); } }); test('MLB is enabled only by an explicit BOUNDED LEASE', () => { // Plain `mlb` is no longer an activation path — it means "forever", which // is precisely the containment defect. Only sport@absolute-UTC-expiry works. const now = new Date('2026-08-30T12:00:00Z'); const legacy = load('mlb'); expect(legacy.lineageCanaryEnabled('mlb', now)).toBe(false); const m = load('mlb@2026-08-30T13:00:00Z'); expect(m.lineageCanaryEnabled('mlb', now)).toBe(true); for (const sp of ['wnba', 'nba', 'soccer']) { expect(m.lineageCanaryEnabled(sp, now)).toBe(false); } // And it turns itself off when the clock crosses, with no restart. expect(m.lineageCanaryEnabled('mlb', new Date('2026-08-30T13:00:01Z'))).toBe(false); }); test('an empty value is a kill switch needing no migration revert', () => { const m = load(''); expect(m.LINEAGE_CANARY_SPORTS).toEqual([]); expect(m.lineageCanaryEnabled('mlb', new Date('2026-08-30T12:00:00Z'))).toBe(false); }); test('a sport without a verified event resolver cannot be canaried into safety', () => { // Two defences now. The lease refuses to widen at all (one sport, and only // a leasable one), AND eventIdentity still refuses to invent identity, so a // widened sport would record UNSUPPORTED_SPORT and stay honest regardless. const now = new Date('2026-08-30T12:00:00Z'); const m = load('mlb@2026-08-30T13:00:00Z,wnba@2026-08-30T13:00:00Z'); expect(m.lineageCanaryEnabled('wnba', now)).toBe(false); expect(m.lineageCanaryEnabled('mlb', now)).toBe(false); // the WHOLE config fails closed const r = E.resolveEvent('wnba', dhProp('2026-08-17T17:40:00Z'), DH_GAMES); expect(r.canonical_event_id).toBeNull(); expect(r.event_identity_method).toBe(E.IDENTITY_METHOD.UNSUPPORTED_SPORT); }); test('the commit is gated on the flag in the live path', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8'); expect(src).toMatch(/commitPublication && persistedRows && lineageCanaryEnabled\(sp\)/); }); }); describe('UNRESOLVED EVENT SAFETY — may lose coverage, may never merge', () => { const gs = require('../../src/services/gradeSlateService'); const dedupe = (props, n) => gs.__internals.dedupeProps(props, n); const base = (o) => ({ player: 'Andre Pallante', stat_type: 'strikeouts', line: 4.5, book: 'draftkings', home_team: 'Cincinnati Reds', away_team: 'St. Louis Cardinals', game_date: '2026-08-17', ...o, }); test('an UNRESOLVED MLB prop never merges with another — it fractures', () => { // Two rows that could be two different games. Merging them would be the // exact defect this change removes, so they must both survive. const props = [ base({ event_identity_method: 'UNRESOLVED' }), base({ event_identity_method: 'UNRESOLVED', book: 'fanduel' }), ]; expect(dedupe(props, 100)).toHaveLength(2); }); test('the unresolved key is NOT date+teams and NOT the legacy label', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/gradeSlateService.js'), 'utf8'); const fn = src.slice(src.indexOf('function propositionEventKey'), src.indexOf('function dedupeProps')); // The legacy label is reachable ONLY for sports with no resolver. expect(fn).toMatch(/expectsCanonical/); expect(fn).toMatch(/unresolved-event:/); // And the fracture branch is returned BEFORE the legacy label is built. expect(fn.indexOf('unresolved-event:')).toBeLessThan(fn.indexOf('legacy:')); }); test('a resolved event still collapses books — the real dedupe job survives', () => { const props = ['draftkings', 'fanduel', 'betmgm'].map((book) => base({ book, canonical_event_id: 'mlb:gamepk:824514' })); expect(dedupe(props, 100)).toHaveLength(1); }); test('a sport with NO resolver keeps legacy behaviour, unchanged', () => { // WNBA never had canonical identity; fracturing it would multiply grading // load for no safety gain, and its lineage is excluded from the canary. const props = [ base({ event_identity_method: 'UNSUPPORTED_SPORT' }), base({ event_identity_method: 'UNSUPPORTED_SPORT', book: 'fanduel' }), ]; expect(dedupe(props, 100)).toHaveLength(1); }); test('unresolved MLB props from two REAL games cannot collapse', () => { // Same player/stat/line/teams/date, identity unknown for both. const g1 = base({ event_identity_method: 'UNRESOLVED', game_time: '2026-08-17T17:40:00Z' }); const g2 = base({ event_identity_method: 'UNRESOLVED', game_time: '2026-08-17T22:40:00Z' }); expect(dedupe([g1, g2], 100)).toHaveLength(2); }); }); describe('EXACT PARITY-GAP IDENTITY', () => { const served = () => ({ sport: 'mlb', snapshot_id: 'snap-1', game_date: '2026-08-17', player_key: 'judge', stat: 'hits', side: 'over', line: 0.5, game_id: 'g', canonical_event_id: 'mlb:gamepk:824514', captured_at: '2026-08-17T14:00:00Z', grade: 'B', p_win: 0.61, published: true, }); test('a failed commit names WHICH served claim is missing', async () => { const failing = () => ({ from: () => ({ upsert: async () => ({ error: { message: 'db down' } }) }) }); const out = await retention.commitPublication( { rows: [served()], publicationId: 'slate-9', publishedAt: '2026-08-17T15:00:00Z' }, { getClient: failing, fetchExisting: async () => [], mintReadId: () => 'R1', now: () => 't5' }, ); expect(out.parity_gap).toBeTruthy(); expect(out.parity_gap.reason).toBe('db down'); expect(out.parity_gap.missing_count).toBe(1); const m = out.parity_gap.missing[0]; // Deterministic recovery, not "lineage failed". for (const k of ['snapshot_id', 'player_key', 'stat', 'line', 'side', 'canonical_event_id', 'captured_at']) { expect(m[k]).not.toBeNull(); } expect(out.parity_gap.publication_id).toBe('slate-9'); expect(out.parity_gap.published_at).toBe('2026-08-17T15:00:00Z'); expect(out.parity_gap.attempted_at).toBe('t5'); }); test('a clean commit records NO parity gap', async () => { const ok = () => ({ from: () => ({ upsert: async () => ({ error: null }) }) }); const out = await retention.commitPublication( { rows: [served()], publicationId: 's', publishedAt: 't' }, { getClient: ok, fetchExisting: async () => [], mintReadId: () => 'R1' }, ); expect(out.parity_gap).toBeUndefined(); expect(out.published).toBe(1); }); }); describe('PROPAGATION — canonical identity survives every hop', () => { test('the graded base carries identity from the prop', () => { // Source assertion on the ONE place the prop→base copy happens. A test that // only built props with the field already set could not catch it being // dropped here, which is exactly what the teeth run exposed. const src = fs.readFileSync(path.join(ROOT, 'src/services/gradeSlateService.js'), 'utf8'); const base = src.slice(src.indexOf('const base = {'), src.indexOf('const sides = await')); expect(base).toMatch(/canonical_event_id:\s*prop\.canonical_event_id/); expect(base).toMatch(/event_identity_method:\s*prop\.event_identity_method/); expect(base).not.toMatch(/canonical_event_id:\s*null\s*,/); }); test('the retention row carries identity from the graded base', () => { const c = retention.createCollector({ snapshotId: 's1', capturedAt: '2026-08-17T14:00:00Z', gameDate: '2026-08-17', gameIdFor: () => 'mlb:2026-08-17:StL@Cin', }); const base = { player: 'Andre Pallante', stat_type: 'strikeouts', line: 4.5, sport: 'mlb', canonical_event_id: 'mlb:gamepk:824514', event_identity_source: 'MLB_STATSAPI_GAMEPK', event_identity_method: 'CANONICAL', event_identity_version: 'evid@1', event_occurrence: 1, }; c.onGraded(base, [{ ...base, direction: 'over', grade: 'B', confidence: 60 }]); expect(c.rows).toHaveLength(1); expect(c.rows[0].canonical_event_id).toBe('mlb:gamepk:824514'); expect(c.rows[0].event_identity_source).toBe('MLB_STATSAPI_GAMEPK'); expect(c.rows[0].event_identity_method).toBe('CANONICAL'); expect(c.rows[0].event_occurrence).toBe(1); }); test('a row with no identity carries NULL, never a fabricated id', () => { const c = retention.createCollector({ snapshotId: 's1', capturedAt: 't', gameDate: '2026-08-17', gameIdFor: () => 'g', }); const base = { player: 'X', stat_type: 'hits', line: 0.5, sport: 'wnba' }; c.onGraded(base, [{ ...base, direction: 'over', grade: 'B' }]); expect(c.rows[0].canonical_event_id).toBeNull(); expect(c.rows[0].event_identity_method).toBeNull(); }); test('lineage keys on the canonical id once it arrives', () => { const row = { sport: 'mlb', game_date: '2026-08-17', player_key: 'andre pallante', stat: 'strikeouts', side: 'over', line: 4.5, game_id: 'mlb:2026-08-17:StL@Cin', captured_at: 't', published: true, grade: 'B', p_win: 0.6, canonical_event_id: 'mlb:gamepk:824514', }; expect(L.eventOccurrence(row)).toBe('mlb:gamepk:824514'); expect(L.readNaturalKey(row)).toContain('mlb:gamepk:824514'); const dropped = { ...row, canonical_event_id: null }; expect(L.readNaturalKey(dropped)).not.toContain('gamepk'); }); }); describe('IMPOSSIBLE-BINDING INVARIANT — the two verified regressions', () => { const { nameKey } = require('../../src/utils/playerName'); const SLATE = [ { gamePk: 823825, gameNumber: 1, gameDate: '2026-08-26T22:40:00Z', home: { team: 'Miami Marlins', teamId: 146 }, away: { team: 'Boston Red Sox', teamId: 111 } }, { gamePk: 824878, gameNumber: 1, gameDate: '2026-08-26T23:15:00Z', home: { team: 'Atlanta Braves', teamId: 144 }, away: { team: 'Los Angeles Dodgers', teamId: 119 } }, { gamePk: 824234, gameNumber: 1, gameDate: '2026-08-26T17:10:00Z', home: { team: 'Detroit Tigers', teamId: 116 }, away: { team: 'Tampa Bay Rays', teamId: 139 } }, { gamePk: 824555, gameNumber: 1, gameDate: '2026-08-26T23:40:00Z', home: { team: 'Chicago White Sox', teamId: 145 }, away: { team: 'Texas Rangers', teamId: 140 } }, ]; // Teams verified from statsapi: Joe Mack id 691788 Marlins, Yandy Diaz id 650490 Rays. const TEAMS = new Map([ [nameKey('Joe Mack'), new Set(['MIA'])], [nameKey('Yandy Diaz'), new Set(['TB'])], ]); test('JOE MACK regression — Marlin bound to Dodgers@Braves is REFUSED', () => { const prop = { player: 'Joe Mack', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_date: '2026-08-26', game_time: '2026-08-26T23:15:00Z', }; const r = E.resolveEvent('mlb', prop, SLATE, TEAMS); expect(r.canonical_event_id).toBeNull(); expect(r.event_identity_method).toBe(E.IDENTITY_METHOD.UNRESOLVED); expect(r.reason).toBe('impossible_binding'); expect(r.evidence).toMatchObject({ violated: true, player_teams: ['MIA'] }); expect(r.evidence.event_teams).toEqual(['LAD', 'ATL']); }); test('YANDY DIAZ regression — Ray bound to Rangers@WhiteSox is REFUSED', () => { const prop = { player: 'Yandy Diaz', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Chicago White Sox', away_team: 'Texas Rangers', game_date: '2026-08-26', game_time: '2026-08-26T23:40:00Z', }; const r = E.resolveEvent('mlb', prop, SLATE, TEAMS); expect(r.reason).toBe('impossible_binding'); expect(r.evidence.player_teams).toEqual(['TB']); }); test('each player STILL binds to his own real game', () => { const mack = E.resolveEvent('mlb', { player: 'Joe Mack', home_team: 'Miami Marlins', away_team: 'Boston Red Sox', game_time: '2026-08-26T22:40:00Z', }, SLATE, TEAMS); expect(mack.canonical_event_id).toBe('mlb:gamepk:823825'); const diaz = E.resolveEvent('mlb', { player: 'Yandy Diaz', home_team: 'Detroit Tigers', away_team: 'Tampa Bay Rays', game_time: '2026-08-26T17:10:00Z', }, SLATE, TEAMS); expect(diaz.canonical_event_id).toBe('mlb:gamepk:824234'); }); test('UNKNOWN team preserves uncertainty — it is not a violation', () => { // Refusing on absent evidence would drop real props for a data gap. const r = E.resolveEvent('mlb', { player: 'Someone Unlisted', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_time: '2026-08-26T23:15:00Z', }, SLATE, TEAMS); expect(r.canonical_event_id).toBe('mlb:gamepk:824878'); }); test('no index at all leaves behaviour exactly as before', () => { const prop = { player: 'Joe Mack', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_time: '2026-08-26T23:15:00Z', }; expect(E.resolveEvent('mlb', prop, SLATE).canonical_event_id).toBe('mlb:gamepk:824878'); expect(E.resolveEvent('mlb', prop, SLATE, null).canonical_event_id).toBe('mlb:gamepk:824878'); }); test('time proximity CANNOT override an impossible team relationship', () => { // The mis-nested event is the closest by start time; it must still refuse. const prop = { player: 'Joe Mack', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_time: '2026-08-26T23:15:00Z', }; expect(E.resolveEvent('mlb', prop, SLATE, TEAMS).reason).toBe('impossible_binding'); }); test('a multi-team player (in-season trade) is not falsely refused', () => { const traded = new Map([[nameKey('Joe Mack'), new Set(['MIA', 'ATL'])]]); const r = E.resolveEvent('mlb', { player: 'Joe Mack', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_time: '2026-08-26T23:15:00Z', }, SLATE, traded); expect(r.canonical_event_id).toBe('mlb:gamepk:824878'); }); test('the roster index keys on nameKey, not the raw name', () => { // nameKey('Joe Mack') is 'joseph mack' — the nickname table. An index keyed // on the raw string silently matches nothing and the invariant never fires. expect(nameKey('Joe Mack')).toBe('joseph mack'); const rawKeyed = new Map([['joe mack', new Set(['MIA'])]]); const r = E.resolveEvent('mlb', { player: 'Joe Mack', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_time: '2026-08-26T23:15:00Z', }, SLATE, rawKeyed); expect(r.canonical_event_id).toBe('mlb:gamepk:824878'); // does NOT fire }); test('buildPlayerTeamIndex keys correctly and survives an unreadable roster', async () => { const built = await E.buildPlayerTeamIndex(SLATE, { getTeamRoster: async (id) => { if (id === 146) return [{ id: 691788, name: 'Joe Mack' }]; if (id === 144) throw new Error('roster unavailable'); return []; }, }); expect(built.index.get(nameKey('Joe Mack'))).toEqual(new Set(['MIA'])); expect(built.stats.failed).toBe(1); expect(built.stats.teams).toBeGreaterThan(0); }); test('no roster source yields an empty index, never a fabricated one', async () => { const built = await E.buildPlayerTeamIndex(SLATE, {}); expect(built.index.size).toBe(0); }); }); describe('EVENT ADMISSION — a fractured identity is not model admission', () => { const gs = require('../../src/services/gradeSlateService'); const admit = (props) => gs.__internals.admitForGrading(props, 'mlb'); const prop = (o) => ({ player: 'P', stat_type: 'hits', line: 0.5, book: 'draftkings', ...o }); test('RESOLVED with a canonical id is admitted', () => { const r = admit([prop({ event_binding_status: 'RESOLVED', canonical_event_id: 'mlb:gamepk:1' })]); expect(r.admitted).toHaveLength(1); expect(r.rejected).toHaveLength(0); }); test('RESOLVED WITHOUT a canonical id is still rejected', () => { // The status alone is not the credential; the id has to be there. const r = admit([prop({ event_binding_status: 'RESOLVED' })]); expect(r.admitted).toHaveLength(0); expect(r.rejected).toHaveLength(1); }); test.each([ ['UNRESOLVED', 'EVENT_UNRESOLVED'], ['AMBIGUOUS', 'EVENT_AMBIGUOUS'], ['CONTRADICTED', 'EVENT_PLAYER_TEAM_CONTRADICTION'], ])('%s does NOT grade and is labelled %s', (status, reason) => { const r = admit([prop({ event_binding_status: status })]); expect(r.admitted).toHaveLength(0); expect(r.rejected[0].event_rejection_reason).toBe(reason); expect(r.rejected[0].event_admission).toBe('REJECTED'); }); test('a legacy derived game_id can never satisfy the gate', () => { const r = admit([prop({ event_binding_status: 'UNRESOLVED', game_id: 'mlb:2026-08-26:A@B' })]); expect(r.admitted).toHaveLength(0); }); test('a sport with no resolver is unchanged', () => { expect(admit([prop({ event_binding_status: 'UNSUPPORTED' })]).admitted).toHaveLength(1); expect(admit([prop({})]).admitted).toHaveLength(1); }); test('rejected props are RETAINED for diagnostics, not discarded', () => { const r = admit([prop({ event_binding_status: 'CONTRADICTED' })]); expect(r.rejected).toHaveLength(1); expect(r.reasons.EVENT_PLAYER_TEAM_CONTRADICTION).toBe(1); }); test('the gate runs BEFORE dedupe and grading, exactly once', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/gradeSlateService.js'), 'utf8'); expect(src.indexOf('admitForGrading(props, sport)')).toBeLessThan(src.indexOf('dedupeProps(gate.admitted')); // One admission seam, not scattered checks. expect((src.match(/admitForGrading\(/g) || []).length).toBe(2); // definition + call }); }); describe('ROSTER TEMPORAL SEMANTICS — evidence must describe the right moment', () => { const { nameKey } = require('../../src/utils/playerName'); const SLATE = [ { gamePk: 823825, gameNumber: 1, gameDate: '2026-08-26T22:40:00Z', home: { team: 'Miami Marlins', teamId: 146 }, away: { team: 'Boston Red Sox', teamId: 111 } }, { gamePk: 824878, gameNumber: 1, gameDate: '2026-08-26T23:15:00Z', home: { team: 'Atlanta Braves', teamId: 144 }, away: { team: 'Los Angeles Dodgers', teamId: 119 } }, ]; const TEAMS = new Map([[nameKey('Joe Mack'), new Set(['MIA'])]]); const misNested = { player: 'Joe Mack', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_date: '2026-08-26', game_time: '2026-08-26T23:15:00Z', }; test('date-valid evidence yields CONTRADICTED', () => { const r = E.resolveEvent('mlb', misNested, SLATE, TEAMS, true); expect(r.event_binding_status).toBe(E.BINDING_STATUS.CONTRADICTED); }); test('evidence that is NOT date-valid yields UNRESOLVED, never an accusation', () => { const r = E.resolveEvent('mlb', misNested, SLATE, TEAMS, false); expect(r.event_binding_status).toBe(E.BINDING_STATUS.UNRESOLVED); expect(r.reason).toBe('participant_evidence_not_date_valid'); // Either way it does not grade — but the two verdicts stay distinct. expect(r.canonical_event_id).toBeNull(); }); test('evidenceIsDateValid requires date-scoping AND a matching date', () => { expect(E.evidenceIsDateValid({ date_scoped: true, as_of_date: '2026-08-26' }, '2026-08-26')).toBe(true); expect(E.evidenceIsDateValid({ date_scoped: true, as_of_date: '2026-08-01' }, '2026-08-26')).toBe(false); expect(E.evidenceIsDateValid({ date_scoped: false, as_of_date: null }, '2026-08-26')).toBe(false); expect(E.evidenceIsDateValid(null, '2026-08-26')).toBe(false); }); test('the roster fetch is scoped to the slate date', async () => { const seen = []; await E.buildPlayerTeamIndex(SLATE, { asOfDate: '2026-08-26', getTeamRoster: async (id, asOf) => { seen.push(asOf); return [{ id, name: 'Joe Mack' }]; }, }); expect(new Set(seen)).toEqual(new Set(['2026-08-26'])); }); test('an unscoped index reports date_scoped false', async () => { const b = await E.buildPlayerTeamIndex(SLATE, { getTeamRoster: async (id) => [{ id, name: 'Joe Mack' }], }); expect(b.stats.date_scoped).toBe(false); expect(E.evidenceIsDateValid(b.stats, '2026-08-26')).toBe(false); }); test('the adapter passes the date through to statsapi', () => { const src = fs.readFileSync(path.join(ROOT, 'src/services/adapters/mlbStatsAdapter.js'), 'utf8'); expect(src).toMatch(/asOfDate \? `&date=/); expect(src).toMatch(/mlbstats:roster:\$\{teamId\}:\$\{season\}:\$\{asOfDate \|\| 'now'\}/); }); }); describe('MIS-NESTED MARKETS ARE REFUSED, NEVER RE-BOUND', () => { const { nameKey } = require('../../src/utils/playerName'); const SLATE = [ { gamePk: 823825, gameNumber: 1, gameDate: '2026-08-26T22:40:00Z', home: { team: 'Miami Marlins', teamId: 146 }, away: { team: 'Boston Red Sox', teamId: 111 } }, { gamePk: 824878, gameNumber: 1, gameDate: '2026-08-26T23:15:00Z', home: { team: 'Atlanta Braves', teamId: 144 }, away: { team: 'Los Angeles Dodgers', teamId: 119 } }, { gamePk: 824234, gameNumber: 1, gameDate: '2026-08-26T17:10:00Z', home: { team: 'Detroit Tigers', teamId: 116 }, away: { team: 'Tampa Bay Rays', teamId: 139 } }, { gamePk: 824555, gameNumber: 1, gameDate: '2026-08-26T23:40:00Z', home: { team: 'Chicago White Sox', teamId: 145 }, away: { team: 'Texas Rangers', teamId: 140 } }, ]; const TEAMS = new Map([ [nameKey('Joe Mack'), new Set(['MIA'])], [nameKey('Yandy Diaz'), new Set(['TB'])], ]); test('JOE MACK: contradicted, not re-bound to the Marlins game', () => { const r = E.resolveEvent('mlb', { player: 'Joe Mack', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_date: '2026-08-26', game_time: '2026-08-26T23:15:00Z', }, SLATE, TEAMS, true); expect(r.event_binding_status).toBe('CONTRADICTED'); // The refusal must NOT quietly relocate the prop to gamePk 823825. The // provider record is invalid; inventing a different event is inventing // provenance. expect(r.canonical_event_id).toBeNull(); expect(r.canonical_event_id).not.toBe('mlb:gamepk:823825'); }); test('YANDY DIAZ: contradicted, not re-bound to the Rays game', () => { const r = E.resolveEvent('mlb', { player: 'Yandy Diaz', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Chicago White Sox', away_team: 'Texas Rangers', game_date: '2026-08-26', game_time: '2026-08-26T23:40:00Z', }, SLATE, TEAMS, true); expect(r.event_binding_status).toBe('CONTRADICTED'); expect(r.canonical_event_id).toBeNull(); expect(r.canonical_event_id).not.toBe('mlb:gamepk:824234'); }); test('each player CORRECTLY associated market remains eligible', () => { const gs = require('../../src/services/gradeSlateService'); const good = [ { player: 'Joe Mack', stat_type: 'total_bases', line: 0.5, book: 'draftkings', home_team: 'Miami Marlins', away_team: 'Boston Red Sox', game_time: '2026-08-26T22:40:00Z' }, { player: 'Yandy Diaz', stat_type: 'total_bases', line: 0.5, book: 'draftkings', home_team: 'Detroit Tigers', away_team: 'Tampa Bay Rays', game_time: '2026-08-26T17:10:00Z' }, ]; E.attachEventIdentity('mlb', good, SLATE, TEAMS, true); for (const p of good) expect(p.event_binding_status).toBe('RESOLVED'); expect(gs.__internals.admitForGrading(good, 'mlb').admitted).toHaveLength(2); }); test('the mis-nested props do not survive admission', () => { const gs = require('../../src/services/gradeSlateService'); const bad = [ { player: 'Joe Mack', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Atlanta Braves', away_team: 'Los Angeles Dodgers', game_time: '2026-08-26T23:15:00Z' }, { player: 'Yandy Diaz', stat_type: 'total_bases', line: 0.5, book: 'pinnacle', home_team: 'Chicago White Sox', away_team: 'Texas Rangers', game_time: '2026-08-26T23:40:00Z' }, ]; E.attachEventIdentity('mlb', bad, SLATE, TEAMS, true); const gate = gs.__internals.admitForGrading(bad, 'mlb'); expect(gate.admitted).toHaveLength(0); expect(gate.rejected).toHaveLength(2); expect(gate.reasons.EVENT_PLAYER_TEAM_CONTRADICTION).toBe(2); }); test('DOUBLEHEADER survives the stricter gate — both games still admitted', () => { const gs = require('../../src/services/gradeSlateService'); const DH = [ { gamePk: 824514, gameNumber: 1, gameDate: '2026-08-17T17:40:00Z', home: { team: 'Cincinnati Reds', teamId: 113 }, away: { team: 'St. Louis Cardinals', teamId: 138 } }, { gamePk: 824478, gameNumber: 2, gameDate: '2026-08-17T22:40:00Z', home: { team: 'Cincinnati Reds', teamId: 113 }, away: { team: 'St. Louis Cardinals', teamId: 138 } }, ]; const teams = new Map([[nameKey('Andre Pallante'), new Set(['STL'])]]); const props = ['2026-08-17T17:40:00Z', '2026-08-17T22:40:00Z'].map((t) => ({ player: 'Andre Pallante', stat_type: 'strikeouts', line: 4.5, book: 'draftkings', home_team: 'Cincinnati Reds', away_team: 'St. Louis Cardinals', game_time: t, })); E.attachEventIdentity('mlb', props, DH, teams, true); expect(props.map((p) => p.event_binding_status)).toEqual(['RESOLVED', 'RESOLVED']); expect(new Set(props.map((p) => p.canonical_event_id)).size).toBe(2); expect(gs.__internals.admitForGrading(props, 'mlb').admitted).toHaveLength(2); }); });