9fc17a4689
My first attempt did not work in prod -- team stayed 0/323 after deploy.
I resolved the team name AFTER the hint-confirmation check, but the check
itself reads hit.currentTeam.name, which is undefined because
/sports/1/players returns { id, link }. With a FULL-NAME hint (what
snapshotService passes) neither branch of teamRecordMatchesHint could
match: the name branch had no name, and the abbr branch cannot resolve a
full name to an abbr. Confirmation failed, the team was nulled, and my
later backfill ran on an already-null value.
withTeamName() now backfills the name from the cached /teams list BEFORE
any comparison, and is used at all three confirmation sites plus the
return. Verified against the live API on all four cases: no hint, FULL-NAME
hint, abbr hint -> "Philadelphia Phillies"; WRONG hint -> null.
That last case matters most: a wrong hint must still REFUSE. The
confirmation exists so a namesake collision cannot tag a player to a team
he is not on, which would fabricate opponents downstream. Making the match
succeed must not make it succeed wrongly, and a test locks it.
Gates: 4,087 tests / 327 suites green; next build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
103 lines
4.7 KiB
JavaScript
103 lines
4.7 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* S59 SLATE JOIN INVARIANT — re-armed 2026-08-01.
|
||
*
|
||
* The invariant drops a graded prop whose player's REAL team is not a
|
||
* participant in that game. It was written correctly and was silently INERT:
|
||
* `mlbStatsAdapter.searchPlayer` returned `team: null` for every player
|
||
* (`/sports/1/players` gives `currentTeam: { id, link }` with no `name`), so
|
||
* `team` was null on 416/416 stored grades and the guard could never fire.
|
||
*
|
||
* Two properties are locked here:
|
||
* 1. FAIL-SAFE — it drops ONLY on a positive not-in-game. Unknown team, or
|
||
* unknown game participants, ABSTAIN. Dropping a real prop on absent data
|
||
* would be a new fabrication, worse than the bad row it guards against.
|
||
* 2. It must key off the PLAYER'S team, never the prop's own game teams —
|
||
* comparing a game to itself always matches and would re-disarm it while
|
||
* looking armed.
|
||
*/
|
||
|
||
const { buildPlayerStripsFromProps } = require('../../web/src/lib/slateAdapter');
|
||
|
||
const prop = (player, team) => ({ player, stat_type: 'hits', line: 1.5, side: 'O', team });
|
||
const GAME = { home: 'Philadelphia Phillies', away: 'New York Mets' };
|
||
const players = (strips) => strips.map((s) => s.player);
|
||
|
||
describe('join invariant — fail-safe by construction', () => {
|
||
it('DROPS a prop whose player is positively not in this game', () => {
|
||
const out = buildPlayerStripsFromProps(
|
||
[prop('In Game', 'Philadelphia Phillies'), prop('Wrong Game', 'Toronto Blue Jays')],
|
||
{}, {}, Date.now(), GAME,
|
||
);
|
||
expect(players(out)).toEqual(['In Game']);
|
||
});
|
||
|
||
it('ABSTAINS when the player team is unknown — never drops on absent data', () => {
|
||
const out = buildPlayerStripsFromProps(
|
||
[prop('No Team', null), prop('Empty Team', '')],
|
||
{}, {}, Date.now(), GAME,
|
||
);
|
||
expect(players(out)).toEqual(['No Team', 'Empty Team']);
|
||
});
|
||
|
||
it('ABSTAINS when the GAME participants are unknown', () => {
|
||
const rows = [prop('Anyone', 'Toronto Blue Jays')];
|
||
expect(players(buildPlayerStripsFromProps(rows, {}, {}, Date.now(), null))).toEqual(['Anyone']);
|
||
expect(players(buildPlayerStripsFromProps(rows, {}, {}, Date.now(), {}))).toEqual(['Anyone']);
|
||
});
|
||
|
||
it('matches on either participant, and on abbreviation or full name', () => {
|
||
const out = buildPlayerStripsFromProps(
|
||
[prop('Home Guy', 'Philadelphia Phillies'), prop('Away Guy', 'New York Mets')],
|
||
{}, {}, Date.now(), GAME,
|
||
);
|
||
expect(players(out)).toEqual(['Home Guy', 'Away Guy']);
|
||
});
|
||
});
|
||
|
||
describe('the invariant’s input is the PLAYER team, not the prop’s own game', () => {
|
||
it('a grade carrying only home_team/away_team does NOT satisfy the guard', () => {
|
||
// home_team describes the game the prop came from, so using it as the
|
||
// player's team compares the game to itself: always a match, guard dead.
|
||
const row = { player: 'X', stat_type: 'hits', line: 1.5, side: 'O',
|
||
home_team: 'Toronto Blue Jays', away_team: 'Boston Red Sox' };
|
||
const out = buildPlayerStripsFromProps([row], {}, {}, Date.now(), GAME);
|
||
expect(players(out)).toEqual(['X']); // abstained — no player team known
|
||
});
|
||
});
|
||
|
||
describe('mlbStatsAdapter — the team the invariant depends on', () => {
|
||
const { __internals } = require('../../src/services/adapters/mlbStatsAdapter');
|
||
const { withTeamName, teamRecordMatchesHint } = __internals || {};
|
||
const TEAMS = [{ id: 143, abbr: 'PHI', name: 'Philadelphia Phillies' },
|
||
{ id: 141, abbr: 'TOR', name: 'Toronto Blue Jays' }];
|
||
|
||
it('backfills the team NAME from the id — the list returns { id, link } only', () => {
|
||
expect(withTeamName({ id: 143, link: '/api/v1/teams/143' }, TEAMS))
|
||
.toEqual({ id: 143, name: 'Philadelphia Phillies' });
|
||
});
|
||
|
||
it('returns null for an absent team rather than an empty shell', () => {
|
||
expect(withTeamName(null, TEAMS)).toBeNull();
|
||
expect(withTeamName({ link: '/x' }, TEAMS)).toBeNull();
|
||
});
|
||
|
||
it('leaves the name null when the id is unknown — never guessed', () => {
|
||
expect(withTeamName({ id: 999 }, TEAMS)).toEqual({ id: 999, name: null });
|
||
});
|
||
|
||
it('a FULL-NAME hint now matches — this is what was silently failing', () => {
|
||
// Before the backfill, name was undefined, so a full-name hint could match
|
||
// neither branch: confirmation failed and the team was nulled.
|
||
const t = withTeamName({ id: 143 }, TEAMS);
|
||
expect(teamRecordMatchesHint(t, ['Philadelphia Phillies', 'New York Mets'], TEAMS)).toBe(true);
|
||
expect(teamRecordMatchesHint(t, ['PHI'], TEAMS)).toBe(true);
|
||
});
|
||
|
||
it('a WRONG hint still REFUSES — the anti-fabrication guard is preserved', () => {
|
||
const t = withTeamName({ id: 143 }, TEAMS);
|
||
expect(teamRecordMatchesHint(t, ['Toronto Blue Jays'], TEAMS)).toBe(false);
|
||
});
|
||
});
|