Files
vyndr/tests/unit/calibrationServiceRead.test.js
T
builtbykev f61ec6b391 Read integrity, as-of context, and the shadow matchup resolve (A1-A7)
Seven orders of measurement-first repair. The served grade does not move.

A0/A1 — the unordered page walk returned the right COUNT and the wrong ROWS:
410-617 of 2,490 duplicated with an equal number never returned, while
rows.length matched the server exactly. safePaginate orders on a real unique
key, verifies the tuple at runtime, and THROWS on a query error instead of
treating it as end-of-data. Both hits PROVES are withdrawn: they were drawn
through that reader, and defense_by_direction's distinct-n was likely below
the gate floor all along.

A2/A2b — rolled across every reader: 11 FAIL -> 0. Composite keys pulled from
pg_index (the context tables are dated-composite and had no single unique
column). The unordered helper is deleted, not parked.

A3 — ledgerService and retentionService defaulted the SAME env var to
DIFFERENT versions, so no ledger row ever carried the marker eligibility
requires. One source now. model_snapshots settlement moved onto the cron:
15,484 -> 28,894 settled, repaired-champion 0 -> 7,556.

A4 — hitsFactorContext takes an as-of cutoff. Refusal over reconstruction: no
row at-or-before the date means the factor does not apply, never the nearest
row. Live path unchanged, proven 400/400 on real rows.

A5 — factor_inputs freezes what the factor READ, never the multiplier, so an
audit can recompute and check. It also recorded the finding: the three hits
factors have NEVER fired. prop.opponent and prop.opposing_pitcher are read by
the resolver and written by nothing.

A6/A7 — matchupKeys resolves those keys from the posted lineup plus the
schedule's probable pitchers, and fires the factors into a SHADOW freeze:
248 fires on 308 props, 245 of which would move the grade. The served
forecast is untouched. specs/a8-shadow-factor-gate.md pre-registers the test
that decides whether they ever go live.

Nothing is turned on. CALIBRATION_DEPLOYED stays []. Both verdicts stay
withdrawn. 4,772 tests / 371 suites green, web build exit 0, read-integrity
harness 34/34.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:49:56 -04:00

145 lines
6.4 KiB
JavaScript

'use strict';
/**
* calibrationService.loadSettledRows — the Fix A1 regression.
*
* This reader measured 24.8% corrupt on production (617 of 2,490 rows returned
* twice, an equal number never returned) via two independent defects: an
* unordered page walk, and a query error swallowed as end-of-data. Both are
* locked here against a fake `sb`, so the regression cannot return silently.
*/
const calSvc = require('../../src/services/model/calibrationService');
/**
* A fake PostgREST client for `ledger_entries`. Every filter in the reader's
* chain is accepted and RECORDED, so a dropped filter fails the test rather than
* quietly changing the row set.
*/
function fakeSb(rows, opts = {}) {
const state = { rows, filters: [], selected: null, rangeCalls: 0, ordered: [] };
const q = {
select(cols) { state.selected = cols; return q; },
eq(c, v) { state.filters.push(['eq', c, v]); return q; },
is(c, v) { state.filters.push(['is', c, v]); return q; },
in(c, v) { state.filters.push(['in', c, v]); return q; },
not(c, o, v) { state.filters.push(['not', c, o, v]); return q; },
lt(c, v) { state.filters.push(['lt', c, v]); return q; },
order(c, o) { state.ordered.push([c, o && o.ascending !== false]); return q; },
async range(from, to) {
state.rangeCalls += 1;
if (opts.errorOnRange && opts.errorOnRange(from)) {
return { data: null, error: { message: 'connection reset by peer' } };
}
if (opts.beforeRange) opts.beforeRange(from, state);
const key = state.ordered.length ? state.ordered[state.ordered.length - 1][0] : null;
const out = key
? [...state.rows].sort((a, b) => (a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0))
: [...state.rows];
return { data: out.slice(from, to + 1), error: null };
},
};
return { state, from() { return q; } };
}
const led = (id, p, won, date) => ({
id, p_win: p, outcome: won ? 'hit' : 'miss', game_date: date, quarantine_reason: null,
});
const many = (n) => Array.from({ length: n }, (_, i) => led(i + 1, 0.5 + (i % 40) / 100, i % 2 === 0, '2026-08-01'));
describe('loadSettledRows — orders on a UNIQUE key, every page', () => {
it('returns every row exactly once across a multi-page walk', async () => {
const sb = fakeSb(many(2490));
const rows = await calSvc.loadSettledRows(sb, { sport: 'mlb', stat: 'hits', before: '2026-08-09' });
expect(rows).toHaveLength(2490);
expect(new Set(rows.map((r) => r.id)).size).toBe(2490);
expect(sb.state.rangeCalls).toBe(3);
});
it('orders by id on EVERY page, not just the first', async () => {
const sb = fakeSb(many(2490));
await calSvc.loadSettledRows(sb, { before: '2026-08-09' });
expect(sb.state.ordered).toHaveLength(sb.state.rangeCalls);
for (const [col, asc] of sb.state.ordered) {
expect(col).toBe('id'); // a UNIQUE key — an order on a non-unique
expect(asc).toBe(true); // column still lets ties scramble
}
});
it('selects id — without it the ordering key is not in the payload', async () => {
const sb = fakeSb(many(10));
await calSvc.loadSettledRows(sb, { before: '2026-08-09' });
expect(sb.state.selected).toMatch(/\bid\b/);
});
it('keeps the point-in-time cut and every identity filter', async () => {
const sb = fakeSb(many(10));
await calSvc.loadSettledRows(sb, { sport: 'mlb', stat: 'hits', before: '2026-08-09' });
expect(sb.state.filters).toEqual(expect.arrayContaining([
['eq', 'sport', 'mlb'],
['is', 'user_id', null],
['eq', 'stat', 'hits'],
['in', 'outcome', ['hit', 'miss']],
['not', 'p_win', 'is', null],
['lt', 'game_date', '2026-08-09'], // STRICTLY before — never the answer
]));
});
});
describe('loadSettledRows — a failed read THROWS, never a short fit', () => {
it('throws when a later page errors instead of returning the earlier pages', async () => {
// The :117 defect returned 1,000 rows here and `build()` fitted a map on
// them, indistinguishable from a complete fit.
const sb = fakeSb(many(2490), { errorOnRange: (from) => from === 1000 });
await expect(calSvc.loadSettledRows(sb, { before: '2026-08-09' }))
.rejects.toThrow(/read failed at range 1000-1999/);
});
it('propagates through fromLedger — a read failure is not "thin history"', async () => {
// null means "not enough settled rows to fit"; a throw means "the read
// broke". Collapsing them is what let a fragment look like a fit.
const sb = fakeSb(many(2490), { errorOnRange: (from) => from === 1000 });
await expect(calSvc.fromLedger(sb, { sport: 'mlb', stat: 'hits', before: '2026-08-09' }))
.rejects.toThrow(/read failed/);
});
it('still returns null (not a throw) when there is genuinely no client', async () => {
await expect(calSvc.fromLedger(null, {})).resolves.toBeNull();
});
});
describe('loadSettledRows — concurrent append (the production condition)', () => {
it('returns every pre-existing row exactly once while the cron appends mid-walk', async () => {
// ledger_entries is written at 14/19/22/1/3 UTC. With a stable ascending
// unique key, an appended row lands after the cursor: nothing already
// returned can repeat, nothing pending can be skipped.
const original = many(2500);
let nextId = 90000;
const sb = fakeSb([...original], {
beforeRange: (from, state) => {
if (from > 0) state.rows.push(led(nextId++, 0.6, true, '2026-08-08'));
},
});
const rows = await calSvc.loadSettledRows(sb, { before: '2026-08-09' });
const seen = rows.map((r) => r.id);
expect(new Set(seen).size).toBe(seen.length); // zero duplicates
for (const o of original) expect(seen).toContain(o.id); // zero dropped
});
it('THROWS rather than returning a corrupted set if the walk ever repeats a row', async () => {
// Belt and braces: if the ordering guarantee were ever lost, the reader
// refuses instead of handing a duplicated sample to the fit.
const sb = fakeSb([led(1, 0.5, true, '2026-08-01'), led(1, 0.5, true, '2026-08-01')]);
await expect(calSvc.loadSettledRows(sb, { before: '2026-08-09' }))
.rejects.toThrow(/duplicate key \(id\)=1/);
});
});
describe('Fix A1 does not change what is SERVED', () => {
it('the deploy set is still empty — no stat serves a calibrated number', () => {
const snapshotService = require('../../src/services/snapshotService');
expect(snapshotService.CALIBRATION_DEPLOYED).toEqual([]);
});
});