Files
vyndr/tests/unit/eventPublication.test.js
T
builtbykev 4aca33deb6 One human, one semantic identity — MLB participant convergence
The collision autopsy left two unrepaired defects, running in OPPOSITE
directions, and `outbound_collision_count` can only ever see one of them.

UNDER-COLLAPSE. Dedupe keys on `mlb:<personId>` when the participant is
proven and on the RAW PROVIDER SPELLING when it is not. Mickey Gasper
(681508) is on Boston's 40-man and not on its active roster, so an
active-only index could not identify him and every book's spelling of him
survived dedupe as its own proposition — retention was the first layer to
notice, far too late, and could only discard the loser.

SPLIT. The mirror image, and invisible to the collision metric because it
makes MORE identities, not fewer: Leo Jiménez (677870) is published as both
"Leo Jiménez" and "Leonardo Jimenez", so one human became two semantic
players in one game. Measured across the 15 MLB cohort slices since the
canonical-participant repair, this is a recurring class, not one case:
cam/cameron smith (5 slices), mitch/mitchell bratt, zac/zachary thornton,
leo/leonardo jimenez.

THE REPAIR READS MLB'S OWN RECORD. `hydrate=person` on the roster call the
pipeline already makes returns firstName / useName / useLastName, so the
legitimate name forms for a human come from the league rather than from an
alias table. An alias table is a list of the mistakes we happened to notice.
`nickName` is DELIBERATELY EXCLUDED: over 821 people it produced 14
ambiguous keys, because MLB's nickname field carries bare surnames and
shared clubhouse names — `nameKey('Smitty Smith')` is one string for both
Burch Smith and Will Smith. The four forms kept produce ZERO ambiguity.

Canonical participant reach widens to the 40-man; TEAM EVIDENCE still reads
the ACTIVE roster alone, so event admission and the impossible-binding
refusal are unchanged. Identity still fails closed: a name matching more
than one person in the event resolves to nobody.

CONTINUITY, MEASURED BEFORE WRITING ANY CODE. Over the real 19:00 cohort,
208 of 209 player_keys are unchanged and the one that moves is the defect —
`leonardo jimenez` converging onto `leo jimenez`, a key that already exists.
No new lineage family. The natural key contains game_date, so chains never
span dates and a forward change cannot fork a closed one.

DETERMINISTIC REPRESENTATIVE. Which book's payload survives was decided by
position. It is now decided by the existing MODEL_BOOKS declaration order —
reused, not authored; inventing a sportsbook ranking to settle a tiebreak
would be a market judgement smuggled in as a bug fix — with book name and a
content tiebreak. Stable under every input permutation.

TWO GUARDS, BOTH DIRECTIONS. split (one person, many identities) and merge
(one identity, many people). A merge is refused at the same single admission
seam event identity already uses; a split is counted and alerted but does not
cut the board, because it duplicates an identity rather than asserting a
falsehood.

RETENTION REMAINS AN INDEPENDENT CHECK. The old assertion grepped the source
for `player_key: nameKey(player)`. That expression stood in for a PROPERTY,
and a grep verifies a spelling. Replaced with the property itself, asserted
in both modes: when the producer emits two rows for one human, retention
still files them under one identity and still reports the collision.

Replay of the real cohort through the repair: 3,129 offerings, 100%
participants resolved, every one of 207 participants on exactly ONE semantic
key, collision 0, split 0, merge 0.

Suite 396/5,455/0 · tsc 0 · 15/15 teeth. Tooth 12 came back green first
time and that was a coverage hole, not a safe defect: nothing asserted
retention's append-only upsert. It does now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
2026-08-30 20:37:01 -04:00

946 lines
46 KiB
JavaScript

'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=/);
// The key also carries rosterType now: an 'active' answer must never be
// served to a caller that asked for the 40-man.
expect(src).toMatch(/mlbstats:roster:\$\{teamId\}:\$\{season\}:\$\{rt\}:\$\{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);
});
});