e29ab6fd6a
PART 1 verified by inducing the REAL rowsFromSnapshot over REAL lock_lines
rows from prod. Three cases, 0 non-takeable anchors:
Narvaez (dabble/kalshi/prizepicks/smarkets, NO takeable book)
-> book=null, price=null, takeable=null [honest absent]
Schwarber(bovada/dabble/novig/PINNACLE before draftkings)
-> draftkings +102 [pinnacle SKIPPED, proving TAKEABLE not MODEL]
Ohtani (dabble/onexbet before draftkings) -> draftkings -266
Narvaez is the case that matters: pre-fix he was stamped dabble +104
takeable=true; he is now honestly absent.
A HARNESS BUG RECORDED: my first verification pulled live /api/odds/mlb,
which returned {"error":"Odds data temporarily unavailable"}. The 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. Same family as the silent-false traps: a probe that finds
nothing looks identical to a probe that finds nothing wrong.
PART 2: 1,006 rows tagged via the purpose-built quarantine_reason at ROW
level with three sub-cases (recoverable_same_line 936, no_takeable_quote
49, takeable_line_differs 21). getModelAggregate ALREADY excluded
quarantined rows, so the public record and the n>=20 gate were clean
automatically; all five committed holdout scripts now carry the exclusion
explicitly.
PART 3 -- the re-stamp call is now fact-based. The takeable LOCK-TIME price
is recoverable for 936/1,006 (93.0%) from lock_lines, the correct
instrument. Only 431 appear in closing_captures, which is the wrong timing
for a lock price anyway.
LINE CONTAMINATION ANSWERED (previously unverified): the stored line
MATCHES a takeable book's line on 936 (93.0%), DIFFERS on 21 (2.1%), and is
unverifiable on 49 (4.9%) where no takeable book quoted the prop at all.
That makes it cleanly row-level: re-stamp the 936 as an honest JOIN and
recover 886 pending rows for the holdouts, or leave all 1,006 excluded.
Either way the 21 + 49 stay out -- re-stamping those would invent a lock
price, or a line, we never captured. Nothing re-stamped; Kev's call.
Gates: 4,111 tests / 330 suites green; next build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
133 lines
5.8 KiB
Markdown
133 lines
5.8 KiB
Markdown
# 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.
|