Files
vyndr/tests/unit/lineageCanaryLease.test.js
T
builtbykev 930d526b01 Lineage productization: a live graph, an observer that doesn't trust it, and a surface that owns only what it knows
The authority review found there was nothing to switch: lineage is a
write-only graph with no living permanent writer and no product consumer.
Authority theater would have been a switch on a consumer that does not exist,
reading a store that is not being written. So: make the graph live, measure it
from outside, and expose the one category it alone owns.

WRITE-PATH FAILURE SEMANTICS, TRACED FIRST. persist(:857) -> the authoritative
cacheSet snapshot:latest(:1404) -> the lineage gate(:1426) -> ledger(:1473).
Lineage failure cannot fail base retention (earlier, separate upsert), cannot
fail publication (the product write precedes the gate), cannot fail the Ledger
(lineageIndex defaults null; the ledger has its own guard), and cannot create a
new partial row (blankLineage() first, atomicity sweep after the catch). The
isolation this tranche needed already existed; only a mode was missing.

THREE MODES, ONE EVALUATOR. `lineageWriteMode` distinguishes OFF /
CANARY_LEASED / PERSISTENT_SHADOW, and the write gate and the status surface
both read it, so they cannot disagree. The canary is CONSULTED, never
converted: its <=4h absolute expiry, its dynamic evaluation and its
fail-closed parse are untouched.

AN AMBIGUOUS CONFIGURATION FAILS CLOSED. If a sport is named by both persistent
mode and an active lease, the two instructions disagree about WHEN WRITING
STOPS — the lease says 22:45, persistent says never. The dangerous reading is
the quiet one: an operator sets a bounded lease believing writing will stop
while persistent keeps it going. We cannot know which they meant, so that sport
writes nothing until the configuration says one thing. The sport allowlist is
the canary's own, so persistent mode can never widen past it.

THE OBSERVER MAY NOT ASK THE WRITER HOW IT DID. settleLedger returned
{settled:0,pending:0} — byte-identical to a healthy "nothing to settle" — while
1,444 rows sat unprocessed, and the watchdog believed it. So `lineageCoverage`
reads durable retained state only, and THE DENOMINATOR MAY NOT CONSULT
lineage_action: eligibility is "the row was published AND a natural key is
derivable from its own identity columns", neither of which the lineage path
writes. If expectation were derived from whether lineage exists, coverage would
be 100% by construction and the metric would be decoration. Zero-expected and
zero-written are kept as different answers.

THE LEGACY BOUNDARY IS OBSERVED, NOT DECLARED. `publication_id` is stamped only
by commitPublication, so the row itself says whether lineage ran. Verified on
production: 5,353 rows carry it — 4,234 complete actions plus exactly the 1,119
historical partial rows — and zero actions exist without one. No epoch constant
is invented; a date would have been a guess about when the writer was on.
publication_id NULL -> LEGACY_UNVERIFIED. Stamped but incomplete ->
LINEAGE_UNAVAILABLE, which is the honest answer for the 1,119 and is never
quietly rewritten as legacy.

THE GRADE-SHIFT BADGE IS UNTOUCHED. revised_from_grade answers "did the letter
change"; lineage answers "which published claim superseded which". Different
questions, and a test now fails if either route learns the word lineage.

Suite 397/5,496/0 · tsc 0 · 15/15 teeth.

TWO OF MY OWN TESTS WERE VACUOUS AND A TOOTH FOUND IT. Tooth 2 came back green
because the isolation tests asserted the slate was published — true whether or
not the exception propagated — while never reaching the lineage gate at all:
the fake grader never fired `onGraded`, so the collector stayed empty and
persistedRows stayed null. Fixed by firing the hook and counting the commit.
A green teeth run means the test is missing.

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

250 lines
12 KiB
JavaScript

// LINEAGE CANARY LEASE — fail-closed, bounded, evaluated at the write gate.
//
// The defect this replaces: `LINEAGE_CANARY_SPORTS=mlb` was parsed once at
// module load and frozen, so an enabled canary stayed writable for the entire
// process lifetime. On 2026-08-29 it was left set and three scheduled snapshots
// wrote lineage overnight unattended. The history was correct by luck; lineage
// is append-only, so a defective canary would have written irreversible wrong
// evidence just as quietly.
const path = require('path');
const fs = require('fs');
const MOD = '../../src/services/lineageCanaryConfig';
const ROOT = path.join(__dirname, '../..');
/** Load the module under a given env value. The parse is at module load, so the
* reload is how a "restart" is simulated. */
function 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(MOD);
if (prev === undefined) delete process.env.LINEAGE_CANARY_SPORTS;
else process.env.LINEAGE_CANARY_SPORTS = prev;
return m;
}
const at = (iso) => new Date(iso);
const T0 = '2026-08-30T12:00:00Z';
const lease = (iso) => `mlb@${iso}`;
describe('the bound is a function of the scheduler, and is proven not asserted', () => {
test('MAX_LEASE cannot enclose three scheduled MLB ticks', () => {
const cfg = load(undefined);
// The scheduler's hours, read from the source of truth rather than retyped.
const sched = fs.readFileSync(path.join(ROOT, 'src/config/sportCadence.js'), 'utf8');
const m = sched.match(/mlb:\s*\{\s*hours:\s*\[([0-9,\s]+)\]/);
expect(m).toBeTruthy();
const HOURS = m[1].split(',').map((n) => parseInt(n.trim(), 10));
expect(HOURS.sort((a, b) => a - b)).toEqual([1, 3, 14, 19, 22]);
// EXHAUSTIVE over a minute grid spanning UTC date boundaries.
const ticks = [];
for (let d = 0; d < 4; d += 1) for (const h of HOURS) ticks.push(d * 1440 + h * 60);
ticks.sort((a, b) => a - b);
const maxTicks = (durMin) => {
let worst = 0;
for (let start = 0; start <= 3 * 1440; start += 1) {
const n = ticks.filter((t) => t >= start && t <= start + durMin).length;
if (n > worst) worst = n;
}
return worst;
};
const leaseMin = cfg.MAX_LEASE_MS / 60000;
expect(leaseMin).toBe(240);
expect(maxTicks(leaseMin)).toBeLessThanOrEqual(2);
// The minimum three-tick span, stated so a scheduler change breaks this test
// rather than silently invalidating the bound.
let minSpan = Infinity;
for (let i = 0; i + 2 < ticks.length; i += 1) minSpan = Math.min(minSpan, ticks[i + 2] - ticks[i]);
expect(minSpan).toBe(300); // 22:00 -> 01:00 -> 03:00
expect(leaseMin).toBeLessThan(minSpan);
// And the falsified claim: six hours DOES enclose three.
expect(maxTicks(6 * 60)).toBe(3);
});
});
describe('parse matrix — every invalid shape fails closed', () => {
const cases = [
['variable missing', undefined, 'OFF', false],
['empty string', '', 'OFF', false],
['whitespace only', ' ', 'OFF', false],
['LEGACY plain mlb', 'mlb', 'INVALID', false],
['legacy comma list', 'mlb,wnba', 'INVALID', false],
['two leases', 'mlb@2026-08-30T13:00:00Z,nba@2026-08-30T13:00:00Z', 'INVALID', false],
['duplicate @', 'mlb@2026-08-30T13:00:00Z@x', 'INVALID', false],
['missing sport', '@2026-08-30T13:00:00Z', 'INVALID', false],
['sport not leasable', 'nba@2026-08-30T13:00:00Z', 'INVALID', false],
['expiry without timezone', 'mlb@2026-08-30T13:00:00', 'INVALID', false],
['expiry with an offset', 'mlb@2026-08-30T13:00:00+00:00', 'INVALID', false],
['malformed expiry', 'mlb@not-a-date', 'INVALID', false],
['expiry is a duration', 'mlb@4h', 'INVALID', false],
['trailing junk', 'mlb@2026-08-30T13:00:00Z junk', 'INVALID', false],
['beyond MAX_LEASE', 'mlb@2026-08-30T17:00:00.001Z', 'INVALID', false],
['already expired', 'mlb@2026-08-30T11:59:59Z', 'EXPIRED', false],
['expiry exactly now', 'mlb@2026-08-30T12:00:00Z', 'EXPIRED', false],
['valid future lease', 'mlb@2026-08-30T13:00:00Z', 'ACTIVE', true],
['valid at the exact bound', 'mlb@2026-08-30T16:00:00Z', 'ACTIVE', true],
];
for (const [name, val, expectState, expectEnabled] of cases) {
test(`${name} -> ${expectState}`, () => {
const cfg = load(val);
const s = cfg.state(at(T0));
expect(s.lease_state).toBe(expectState);
expect(s.effective_enabled).toBe(expectEnabled);
expect(cfg.isEnabled('mlb', at(T0))).toBe(expectEnabled);
if (!expectEnabled) expect(s.effective_active_sports).toEqual([]);
});
}
test('an unreadable clock fails closed rather than defaulting to active', () => {
const cfg = load(lease('2026-08-30T13:00:00Z'));
expect(cfg.isEnabled('mlb', new Date('nonsense'))).toBe(false);
expect(cfg.state(new Date('nonsense')).lease_state).toBe('INVALID');
expect(cfg.state(new Date('nonsense')).invalid_reason).toBe('INVALID_CLOCK');
});
test('a valid lease enables ONLY its own sport', () => {
const cfg = load(lease('2026-08-30T13:00:00Z'));
expect(cfg.isEnabled('mlb', at(T0))).toBe(true);
for (const sp of ['wnba', 'nba', 'soccer', 'nfl']) {
expect(cfg.isEnabled(sp, at(T0))).toBe(false);
}
});
});
describe('expiry is dynamic — no restart required', () => {
test('one process, one parse: ACTIVE then EXPIRED as the clock crosses', () => {
const cfg = load(lease('2026-08-30T13:00:00Z'));
// Same module instance throughout — this is the whole point.
expect(cfg.isEnabled('mlb', at('2026-08-30T12:59:59Z'))).toBe(true);
expect(cfg.state(at('2026-08-30T12:59:59Z')).lease_state).toBe('ACTIVE');
expect(cfg.isEnabled('mlb', at('2026-08-30T13:00:00Z'))).toBe(false);
expect(cfg.state(at('2026-08-30T13:00:00Z')).lease_state).toBe('EXPIRED');
expect(cfg.isEnabled('mlb', at('2026-08-30T14:00:00Z'))).toBe(false);
});
test('remaining_ms counts down and floors at expiry', () => {
const cfg = load(lease('2026-08-30T13:00:00Z'));
expect(cfg.state(at('2026-08-30T12:00:00Z')).remaining_ms).toBe(3600000);
expect(cfg.state(at('2026-08-30T12:30:00Z')).remaining_ms).toBe(1800000);
expect(cfg.state(at('2026-08-30T13:30:00Z')).remaining_ms).toBe(0);
});
});
describe('restart semantics — absolute expiry controls', () => {
test('a restart BEFORE expiry does not reset the lease lifetime', () => {
const val = lease('2026-08-30T13:00:00Z');
const first = load(val);
expect(first.state(at('2026-08-30T12:10:00Z')).remaining_ms).toBe(3000000);
// "restart" — reload the module, same env value.
const second = load(val);
expect(second.state(at('2026-08-30T12:50:00Z')).remaining_ms).toBe(600000);
expect(second.state(at('2026-08-30T12:50:00Z')).configured_expires_at)
.toBe(first.state(at('2026-08-30T12:10:00Z')).configured_expires_at);
});
test('a restart AFTER expiry does NOT reactivate it', () => {
const cfg = load(lease('2026-08-30T11:00:00Z'));
expect(cfg.isEnabled('mlb', at(T0))).toBe(false);
expect(cfg.state(at(T0)).lease_state).toBe('EXPIRED');
});
});
describe('configured-but-expired stays auditable', () => {
test('EXPIRED is distinguishable from never-configured', () => {
const off = load(undefined).state(at(T0));
const exp = load(lease('2026-08-30T11:00:00Z')).state(at(T0));
expect(off.configured).toBe(false);
expect(off.lease_state).toBe('OFF');
expect(exp.configured).toBe(true);
expect(exp.config_valid).toBe(true);
expect(exp.lease_state).toBe('EXPIRED');
expect(exp.configured_sport).toBe('mlb');
expect(exp.configured_expires_at).toBe('2026-08-30T11:00:00.000Z');
expect(exp.effective_enabled).toBe(false);
expect(exp.effective_active_sports).toEqual([]);
});
test('the status never returns the raw environment string', () => {
const raw = lease('2026-08-30T13:00:00Z');
const s = JSON.stringify(load(raw).state(at(T0)));
expect(s).not.toContain('LINEAGE_CANARY_SPORTS');
});
});
describe('ONE evaluator — status and writer cannot disagree', () => {
test('the snapshot write gate delegates and threads the clock', () => {
const src = fs.readFileSync(path.join(ROOT, 'src/services/snapshotService.js'), 'utf8');
expect(src).toMatch(/function lineageCanaryEnabled\(sport, now\)/);
expect(src).toMatch(/lineageCanaryConfig\.isEnabled\(sport, now\)/);
// The gate must not cache the ANSWER at module scope. A frozen boolean is
// the original defect, so this pins the shape rather than one spelling of
// it: `isEnabled` may only be called from inside the function body.
const decl = src.slice(src.indexOf('function lineageCanaryEnabled'));
const body = decl.slice(0, decl.indexOf('\n}') + 2);
expect(body).toMatch(/lineageCanaryConfig\.isEnabled\(sport, now\)/);
// No module-scope evaluation of the gate anywhere outside that function.
const outside = src.replace(body, '');
expect(outside).not.toMatch(/lineageCanaryConfig\.isEnabled\(/);
expect(outside).not.toMatch(/=\s*lineageCanaryEnabled\(/);
// The write gate now asks ONE evaluator (`lineageWriteMode`), which
// consults this lease as one of its two inputs. The lease contract is
// unchanged; what moved is who calls it. The clock is still threaded.
expect(src).toMatch(/function lineageWriteEnabled\(sport, now\)/);
expect(src).toMatch(/lineageWriteMode\.isEnabled\(sport, now\)/);
expect(src).toMatch(/commitPublication && persistedRows && lineageWriteEnabled\(sp, deps\.now && deps\.now\(\)\)/);
});
test('for the same config and instant, writer and status agree', () => {
for (const [val, when] of [
[lease('2026-08-30T13:00:00Z'), '2026-08-30T12:30:00Z'],
[lease('2026-08-30T13:00:00Z'), '2026-08-30T13:30:00Z'],
['mlb', '2026-08-30T12:00:00Z'],
[undefined, '2026-08-30T12:00:00Z'],
]) {
const cfg = load(val);
jest.resetModules();
if (val === undefined) delete process.env.LINEAGE_CANARY_SPORTS;
else process.env.LINEAGE_CANARY_SPORTS = val;
// eslint-disable-next-line global-require
const snap = require('../../src/services/snapshotService');
// eslint-disable-next-line global-require
const live = require(MOD);
delete process.env.LINEAGE_CANARY_SPORTS;
const s = live.state(at(when));
expect(snap.lineageCanaryEnabled('mlb', at(when))).toBe(s.effective_enabled);
expect(live.isEnabled('mlb', at(when))).toBe(s.effective_enabled);
expect(cfg.isEnabled('mlb', at(when))).toBe(s.effective_enabled);
}
});
});
describe('preservation — only the activation gate changed', () => {
const src = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
test('lineage algorithms are untouched', () => {
const rl = src('src/services/read/readLineage.js');
expect(rl).toContain("const LINEAGE_VERSION = 'lin@1'");
expect(rl).toContain("const CLAIM_SCHEMA_VERSION = 'claim@1'");
expect(rl).toContain("const DIGEST_ALGORITHM_VERSION = 'sha256-json-sorted@1'");
const ret = src('src/services/retentionService.js');
expect(ret).toContain('familyScopesFrom');
expect(ret).toContain('isValidLineageAction');
// Pin the IDENTITY ITSELF, not merely the constant's name — a changed tuple
// with an unchanged name is exactly the drift a name check cannot see.
expect(ret).toContain(
"const RETENTION_CONFLICT = 'snapshot_id,game_id,canonical_event_id,player_key,stat,line,side';");
expect(ret).toContain(
"const VALID_LINEAGE_ACTION_FIELDS = Object.freeze([");
});
test('cache-date, participant and intraday repairs are untouched', () => {
expect(src('src/services/oddsService.js')).toContain("ET_DATE_SPORTS = Object.freeze(['mlb'])");
expect(src('src/services/event/eventIdentity.js')).toMatch(/ids\.size !== 1/);
expect(src('src/services/gameBinder.js')).toContain('if (!p.game_date) p.game_date = etFast;');
expect(src('src/services/intradayRefreshService.js')).toContain('BELIEF_FIELDS');
});
});