From 8e17de4010d2f1d1dadcac6f0ee78aefadf3223a Mon Sep 17 00:00:00 2001 From: Kev Date: Mon, 20 Jul 2026 03:27:42 -0400 Subject: [PATCH] STATE: game_date root fixed, 64 voids reverted, grading blast radius flagged --- specs/STATE.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/specs/STATE.md b/specs/STATE.md index c753ab1..f4ff8ac 100644 --- a/specs/STATE.md +++ b/specs/STATE.md @@ -133,6 +133,51 @@ exist locally; harmless, the data restores completely. --- +## 🟢 game_date ROOT FIXED + 64 WRONG VOIDS REVERTED (2026-07-20 07:30 UTC) +- **ROOT (not DNP, as first diagnosed):** PropLine emits **no `commence_time`** + (grep-verified), so `ledgerService`'s `dateET(prop.game_time) || + dateET(gradedTs)` always fell through to the **GRADE clock**. A 01:00/03:00 + UTC snapshot is 21:00/23:00 ET the PREVIOUS day → tonight's props filed under + yesterday → settlement correctly found no game → Order 1's void logic + destroyed 64 real results. +- **FIX — `src/services/gameBinder.js`:** binds each prop to a scheduled game by + TEAMS across the ET window (grade date, +1, −1) and attaches the GAME'S own + time/date/id, in `snapshotService` **before** grading and the ledger write, so + ledger + retention + settlement all inherit one correct date. + **PROVEN against the live schedule at the failing hour:** a `2026-07-20T03:00Z` + grade (ET date 07-19, what the old code used) bound Steven Kwan to + **game_date 2026-07-20**, `mlb:2026-07-20:401816188`. +- **HARD CONTRACT:** unbindable → **nothing**. The grade-clock fallback is gone; + ledgerService SKIPS and counts such rows. Verified live: a prop with an + invented matchup returned `unresolved`, never a guessed date. +- **Doubleheaders** are flagged `ambiguous`, never attributed. Real case already + in data: `mlb:2026-07-11:MilwaukeeBrewers@PittsburghPirates(Game1)`. +- **Retention had the same bug** (rows dated by the snapshot clock) — fixed to + the bound game's ET date. +- **64 wrong voids REVERTED** (60 mlb + 4 wnba), scoped exactly to + `settlement_version=2 AND outcome='void' AND settlement_source='player_dnp'`. +- **LIVE PROOF of the safety fix:** the next induced settle pass returned + **`voided: 0, pending: 64`**. The old code voided all 64; the deployed code + refuses to claim DNP on an unconfirmed absence. + +### 🔴 STOP-AND-REPORT — the date bug also reaches GRADING (not just settlement) +`computeFeatures.lookupTodayGame` calls the ESPN scoreboard with **no date +param** — it takes whatever ESPN calls "today". A prop graded at 23:00 ET can +therefore bind its `home_away` / `opponentAbbr` features to the WRONG game +(yesterday's finished one), which feeds `opp_rank_stat` and the home/away factor. +**Not fixed — flagged per the order's stop rule.** Blast radius: +- **Grading: AFFECTED** (feature correctness at late-ET slots — 01:00/03:00 UTC). +- **Settlement: FIXED** (this order). +- **Retention: FIXED** (this order). +- **Display/slate: NOT affected** — the board renders from the date-pinned + schedule and joins grades by player+stat, never by ledger `game_date`. + +### 🔶 STILL OPEN — the reverted 64 cannot settle yet +Their `game_date` is still the OLD wrong value (Jul 17/18); the binder only +corrects rows written from now on. They will retry and age to `unrecoverable` +until their dates are corrected. **Correcting them is the first act of the heal +order** — healing against wrong dates would simply re-fail. + ## 🟢 RETENTION CLOCK TICKS ON ITS OWN (2026-07-20 06:05 UTC) Induced the REAL cron entrypoint rather than waiting for 14:00 UTC. - **Cron capture needed NO wiring — verified in code.** `snapshotScheduler`'s