f61ec6b391
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>
55 lines
2.9 KiB
JavaScript
55 lines
2.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* MODEL VERSION — ONE source, because two were not the same.
|
|
*
|
|
* ── WHAT WENT WRONG ──────────────────────────────────────────────────────
|
|
* `ledgerService` and `retentionService` both read `process.env.MODEL_VERSION`,
|
|
* and both carried a HARDCODED DEFAULT — but different ones:
|
|
*
|
|
* ledgerService.js:57 … || 'engine1@2026-07-20'
|
|
* retentionService.js:59 … || 'engine1@2026-08-07-fullwindow'
|
|
*
|
|
* Production does not set the env var, so the two tables took different
|
|
* defaults. The 2026-08-07 champion repair bumped one and left the other behind.
|
|
* Result: `model_snapshots` carried the repaired marker on 34,128 rows while
|
|
* `ledger_entries` carried the OLD marker on all 15,739 — including rows graded
|
|
* by the repaired champion.
|
|
*
|
|
* That is not a cosmetic mismatch. `reAuditEligibility.isEligible` requires
|
|
* `model_version === REPAIRED_CHAMPION_VERSION`, so applied to the ledger it
|
|
* returned ZERO eligible rows and ZERO eligible dates, permanently, for all four
|
|
* pending measurements. The accrual clock read "blocked" when the real state was
|
|
* "mis-stamped".
|
|
*
|
|
* ── THE RULE ─────────────────────────────────────────────────────────────
|
|
* There is exactly one place a model version may be declared. Anything that
|
|
* stamps a row imports it from here. A default that lives next to its consumer
|
|
* will drift from the other consumer, and the drift is invisible because both
|
|
* sides look locally correct.
|
|
*
|
|
* ── WHAT THIS DOES NOT DO ────────────────────────────────────────────────
|
|
* It does not re-stamp history. Rows already written keep the marker they were
|
|
* written with — rewriting them would destroy the one record of which forecast
|
|
* actually produced them, which is the thing the marker exists to preserve. Old
|
|
* rows stay honestly old; a backfill is a separate, explicit decision.
|
|
*/
|
|
|
|
/** The version every NEW row is stamped with. Override with MODEL_VERSION. */
|
|
const MODEL_VERSION = process.env.MODEL_VERSION || 'engine1@2026-08-07-fullwindow';
|
|
|
|
/**
|
|
* Rows at or after this marker were produced by the repaired champion and are
|
|
* eligible for forward re-audit.
|
|
*
|
|
* Deliberately NOT `MODEL_VERSION`: the eligibility bar is a fixed historical
|
|
* fact about a specific repair, and tying it to "whatever we stamp today" would
|
|
* make every future version silently re-qualify itself.
|
|
*/
|
|
const REPAIRED_CHAMPION_VERSION = 'engine1@2026-08-07-fullwindow';
|
|
|
|
/** The marker that preceded the repair — kept so old rows are nameable. */
|
|
const PRE_REPAIR_VERSION = 'engine1@2026-07-20';
|
|
|
|
module.exports = { MODEL_VERSION, REPAIRED_CHAMPION_VERSION, PRE_REPAIR_VERSION };
|