'use strict'; /** * THE SHADOW MUST BE GENERATED, PERSISTED, AND HARMLESS. * * A5's whole finding was a factor that was built, correct, and never invoked. * Evidence that is computed but never reaches a row is that same failure one * layer along — so this drives the REAL retention row builder rather than * asserting the merge function in isolation. */ const retention = require('../../src/services/retentionService'); const pc = require('../../src/services/model/probabilityContract'); const ERA = 'engine1@2026-08-07-fullwindow'; const iso = (p) => Math.round((0.42 + 0.26 * p) * 1000) / 1000; const contract = { fit_n: 1798, fitted_through: '2026-08-17', resolve: (read) => pc.resolve({ ...read, sport: 'mlb', stat: 'hits' }, { estimate: iso }), }; const row = (over) => ({ player_key: 'x', stat: 'hits', line: 0.5, side: 'over', model_version: ERA, p_win: 0.65, confidence: 65, grade: 'C+', ev_pct: 12.3, value: true, takeable: true, book_odds: -115, over_odds: -115, under_odds: -105, probability_contract: null, ...over, }); describe('the shadow reaches the row', () => { it('the REAL collector declares the column on every row, refusals included', () => { const collector = retention.createCollector({ snapshotId: 'snap-1', sport: 'mlb', modelVersion: ERA, codeSha: 'test', gameDate: '2026-09-01', gameIdFor: () => 'mlb:2026-09-01:AAA@BBB', }); collector.onGraded( { player: 'A Hitter', stat_type: 'hits', line: 0.5, sport: 'mlb', over_odds: -115, under_odds: -105 }, [{ direction: 'over', grade: 'C+', p_win: 0.65, confidence: 65 }, { direction: 'under', insufficient_data: true }], ); // the collector is the real path; if it collected nothing the assertion // below would vacuously pass, so require the rows first expect(collector.rows.length).toBe(2); for (const r of collector.rows) expect('probability_contract' in r).toBe(true); const merged = retention.mergeProbabilityContract(collector.rows, contract); expect(merged.find((r) => r.side === 'over').probability_contract.probability_state) .toBe(pc.STATE.CERTIFIED_CALIBRATED); }); it('every row in a batch carries the key — a partial shape drops the column for all', () => { const rows = [row(), row({ p_win: 0.91 }), row({ p_win: null, grade: null })]; const out = retention.mergeProbabilityContract(rows, contract); expect(out).toHaveLength(3); for (const r of out) expect('probability_contract' in r).toBe(true); }); it('records the served candidate inside support', () => { const [r] = retention.mergeProbabilityContract([row()], contract); expect(r.probability_contract.probability_state).toBe(pc.STATE.CERTIFIED_CALIBRATED); expect(r.probability_contract.served_probability).toBe(iso(0.65)); expect(r.probability_contract.estimator_type).toBe('isotonic'); expect(r.probability_contract.servable).toBe(false); }); it('records the REFUSAL, and keeps the raw belief on it', () => { const [r] = retention.mergeProbabilityContract([row({ p_win: 0.91 })], contract); expect(r.probability_contract.probability_state).toBe(pc.STATE.UNCERTIFIED); expect(r.probability_contract.served_probability).toBeNull(); expect(r.probability_contract.raw_model_probability).toBe(0.91); expect(r.probability_contract.derived.available).toBe(false); }); it('prices the SIDE, not the opposite bet', () => { const { evPct } = require('../../src/utils/devig'); const [o] = retention.mergeProbabilityContract([row({ side: 'over', book_odds: null })], contract); const [u] = retention.mergeProbabilityContract([row({ side: 'under', book_odds: null })], contract); expect(o.probability_contract.derived.ev_pct).toBe(evPct(iso(0.65), -115)); expect(u.probability_contract.derived.ev_pct).toBe(evPct(iso(0.65), -105)); }); }); describe('the shadow changes NOTHING that is served', () => { it('leaves EVERY key byte-identical except probability_contract', () => { // Diffing a NAMED LIST of served fields only proves the fields someone // thought to list. Diff every key, so a field added later is covered by // this test on the day it appears. const before = [row(), row({ p_win: 0.91, grade: 'B+' }), row({ side: 'under', p_win: 0.55 }), row({ p_win: null, grade: null }), row({ model_version: 'engine1@2026-07-20' })]; const snapshot = JSON.parse(JSON.stringify(before)); const after = retention.mergeProbabilityContract(before, contract); after.forEach((r, i) => { const keys = new Set([...Object.keys(r), ...Object.keys(snapshot[i])]); keys.delete('probability_contract'); expect(keys.size).toBeGreaterThan(8); // the diff must be non-vacuous for (const k of keys) expect(r[k]).toEqual(snapshot[i][k]); }); }); it('adds exactly ONE key and no others', () => { const before = row(); const after = retention.mergeProbabilityContract([before], contract)[0]; const added = Object.keys(after).filter((k) => !(k in before)); const removed = Object.keys(before).filter((k) => !(k in after)); expect(added).toEqual([]); // declared null upfront expect(removed).toEqual([]); expect(after.probability_contract).not.toBeNull(); }); it('does not mutate the input rows in place', () => { const rows = [row()]; retention.mergeProbabilityContract(rows, contract); expect(rows[0].probability_contract).toBeNull(); }); it('a null contract is a no-op, not a raw passthrough', () => { const out = retention.mergeProbabilityContract([row()], null); expect(out[0].probability_contract).toBeNull(); }); it('a throwing contract leaves the row intact rather than losing it', () => { const bad = { resolve: () => { throw new Error('boom'); } }; const out = retention.mergeProbabilityContract([row()], bad); expect(out).toHaveLength(1); expect(out[0].p_win).toBe(0.65); expect(out[0].probability_contract).toBeNull(); }); }); describe('the flag is OFF by default', () => { it('snapshotService reads PROBABILITY_CONTRACT_SHADOW and defaults to off', () => { const src = require('fs').readFileSync( require('path').join(__dirname, '../../src/services/snapshotService.js'), 'utf8'); expect(src).toContain("process.env.PROBABILITY_CONTRACT_SHADOW || '') === '1'"); // and it must not be able to write a served field const block = src.slice(src.indexOf('PROBABILITY CONTRACT SHADOW'), src.indexOf('let lineageIndex')); for (const served of ['g.p_win =', 'g.confidence =', 'g.grade =', 'g.ev_pct =', 'g.value =']) { expect(block).not.toContain(served); } }); it('nothing is added to CALIBRATION_DEPLOYED', () => { const src = require('fs').readFileSync( require('path').join(__dirname, '../../src/services/snapshotService.js'), 'utf8'); expect(src).toMatch(/CALIBRATION_DEPLOYED\s*=\s*Object\.freeze\(\[\s*\]\)/); }); }); describe('the artifact must agree with the contract, not merely accompany it', () => { const cal = require('../../src/services/model/calibration'); const svc = require('../../src/services/model/probabilityContractService'); const map = cal.fitIsotonic(Array.from({ length: 900 }, (_, i) => { const p = Math.round((0.35 + (i % 60) / 100) * 1000) / 1000; return { p, won: ((i * 2654435761) % 1000) / 1000 < (0.5 + 0.45 * (p - 0.5)) ? 1 : 0, date: `d${i % 14}` }; }), { minTotal: 200 }); const build = () => svc.build({}, { calibrationService: { fromLedger: async () => ({ map, fit_n: 900, fitted_through: '2026-08-21', cutoff: '2026-09-02' }) } }); it('the built artifact declares the CERTIFIED era and estimator', async () => { const b = await build(); expect(b.artifact.model_version).toBe(pc.MLB_HITS.model_version); expect(b.artifact.estimator_type).toBe(pc.MLB_HITS.estimator_type); }); it('an artifact fitted for another era serves NOTHING, even on a matching read', () => { const r = pc.resolve({ sport: 'mlb', stat: 'hits', model_version: ERA, p_win: 0.65 }, { estimate: () => 0.6, artifact: { model_version: 'engine1@2026-07-20', estimator_type: 'isotonic' } }); expect(r.probability_state).toBe(pc.STATE.VERSION_MISMATCH); expect(r.served_probability).toBeNull(); }); it('an artifact of the wrong estimator type serves NOTHING', () => { const r = pc.resolve({ sport: 'mlb', stat: 'hits', model_version: ERA, p_win: 0.65 }, { estimate: () => 0.6, artifact: { model_version: ERA, estimator_type: 'low_param' } }); expect(r.probability_state).toBe(pc.STATE.VERSION_MISMATCH); expect(r.served_probability).toBeNull(); }); it('and the row records the mismatch rather than dropping it', () => { const bad = { fit_n: 1, fitted_through: 'x', resolve: (read) => pc.resolve({ ...read, sport: 'mlb', stat: 'hits' }, { estimate: () => 0.6, artifact: { model_version: 'engine1@2026-07-20', estimator_type: 'isotonic' } }) }; const [r] = retention.mergeProbabilityContract([row()], bad); expect(r.probability_contract.probability_state).toBe(pc.STATE.VERSION_MISMATCH); expect(r.probability_contract.served_probability).toBeNull(); expect(r.probability_contract.raw_model_probability).toBe(0.65); }); });