# TAKEABLE ENFORCEMENT — FIXED, TAGGED, AND THE RE-STAMP CALL **Date:** 2026-08-02 · shipped before tonight's settle · served path, champion, ranking and the reference ruler **untouched**. **Gates:** 4,111 tests / 330 suites green · `next build` exit 0 · write path verified on real prod rows · 1,006 rows tagged and excluded. --- ## PART 1 — THE WRITE PATH: TWO LEAKS, NOT ONE The audit found `ledgerService.indexProps`. Tracing the lock price found **`snapshotService.indexOdds` has the same defect** — it also indexed the full props list, so `gradedAt.odds`, the price a grade is *locked* at, could itself be a DFS or exchange price. **Fixing only the ledger would have left contamination flowing in through the lock.** Both now gate on **`TAKEABLE_BOOKS`** — deliberately **not** `MODEL_BOOKS`. `pinnacle` is model-eligible and correctly not takeable; a MODEL gate would re-break this the moment pinnacle's feed recovers. **Two indexes, two roles**, because the row needs different things with different correctness rules: - **PRICE / BOOK / TAKEABLE** → takeable books only. - **GAME FACTS** (`game_time`, `game_date`, team/opponent) → **book-independent**. First pitch is first pitch whichever book listed it; gating these too would drop valid rows for no gain. **Collapsing those roles into one index was the bug.** ### Verified on REAL prod feed rows Real `lock_lines` rows, run through the real `rowsFromSnapshot`: | player | books available (in feed order) | result | |---|---|---| | Carlos Narváez | dabble, kalshi, prizepicks, smarkets — **no takeable book** | **`book=null, price=null, takeable=null`** — honest absent | | Kyle Schwarber (TB) | bovada, dabble, novig, **pinnacle**, draftkings | **draftkings +102**, `takeable=true` | | Shohei Ohtani | dabble, onexbet, draftkings | **draftkings −266**, `takeable=false` | **0 non-takeable anchored rows.** Narváez is the case that matters most: before the fix he would have been stamped `dabble +104, takeable=true`. He is now **honestly absent** — no price beats a price you cannot bet. And **pinnacle was skipped**, which proves the gate is TAKEABLE and not MODEL. **A harness bug worth recording:** my first verification pulled live `/api/odds/mlb`, which returned `{"error": "Odds data temporarily unavailable"}`. My script read that as *0 props* and printed **"all from takeable books? true"** — a **vacuously true pass**. I caught it only because I also printed the book list and it was empty. Sixth-ish instance of this family: *a probe that finds nothing looks identical to a probe that finds nothing wrong.* --- ## PART 2 — TAGGED AND EXCLUDED (never pooled) **1,006 rows tagged** via the purpose-built `quarantine_reason`, at row level with three distinct sub-cases so the re-stamp decision can be made per row: | tag | rows | settled | pending | |---|---:|---:|---:| | `nontakeable_book:recoverable_same_line` | **936** | 47 | 886 | | `nontakeable_book:no_takeable_quote` | 49 | 0 | 48 | | `nontakeable_book:takeable_line_differs` | 21 | 0 | 21 | **`getModelAggregate` already excludes quarantined rows** (`.is('quarantine_reason', null)`) — a pre-existing guard doing exactly the right thing, so the public model record and the n≥20 gate are clean automatically. **All five committed holdout scripts** now carry the same exclusion explicitly. --- ## PART 3 — THE RE-STAMP CALL, ON FACT **The takeable book's LOCK-TIME price is recoverable for 936 of 1,006 (93.0%)** from `lock_lines` — the correct instrument (lock time, not close time). For reference, only 431 appear in `closing_captures`, which is the wrong timing for a lock price anyway. **So re-stamping is a legitimate JOIN for 93%, not a fabrication.** It would pull the captured takeable price onto the row — a value we really recorded, at the right moment. ### And the line question — answered | | rows | verdict | |---|---:|---| | stored line **matches** a takeable book's line | **936 (93.0%)** | line is **CLEAN** | | a takeable book existed but posted a **different** line | **21 (2.1%)** | line **IS** contaminated | | **no takeable book quoted the prop at all** | **49 (4.9%)** | unverifiable — and arguably should never have been graded | **This makes the decision cleanly row-level:** > **OPTION A — re-stamp the 936.** The takeable lock price and the same line both > exist in the archive. Honest join, sample preserved, 886 pending rows recovered > for the holdouts. > > **OPTION B — leave all 1,006 tagged and excluded.** Simplest, loses 886 pending > rows from the accruing verdicts, costs nothing in correctness. > > **Either way, the 21 + 49 stay excluded.** Re-stamping those would invent a lock > price, or a line, we never captured. **I have not re-stamped anything. This is your call, and it is now a fact-based one.** --- ## STACK BEHIND A CLEAN LEDGER **(b) Structural `Number(null) === 0` guard** — a shared unknown-vs-zero helper. **`hits` will re-trigger it**: its 0.5 lines make P(0) the whole game, exactly where a null read as zero is maximally wrong. **(c) `hits` fix** — on both invariants, not before them. **Carry-forward:** tb-v1 verdict (accruing) · the third pre-registered branch (improves-ranking-but-low → promote **and** recalibrate) · **the 100s Cloudflare origin timeout vs a ~115s snapshot** — the work completes server-side, so a **524 is not a failure**; worth a flag before someone reads it as one. ## TAGS **VERIFIED:** both leaks fixed and gated on TAKEABLE (pinnacle skipped, proving it) · 0 non-takeable anchors on real prod rows · no-takeable-book → honest absent · 1,006 rows tagged with three sub-cases · `getModelAggregate` already excluded them · 93.0% recoverable from `lock_lines` · line clean 93.0% / contaminated 2.1% / unverifiable 4.9%. **AWAITING KEV:** re-stamp the 936, or leave all 1,006 excluded.