# VYNDR — CANONICAL STATE FILE Read-only ground-truth audit. Written 2026-07-26 by Claude Code (repo + deployed DB access). Every item tagged **VERIFIED** (file/line or measured number), **CANNOT DETERMINE** (with reason), or **BLOCKED** (with what unblocks it). Where a prior claim conflicts with code/data, the code/data wins. Nothing was built, changed, deployed, or migrated by this audit. --- ## REVIEW ZERO — PREMISE - **0.1 What I can read** — VERIFIED. The **repo** (full source), the **deployed Supabase DB** (read-only via MCP, project `zmdnczhtdxcddsxzttub`), and the **deployed API** (`api.vyndr.app`). - **0.2 Key reachability** — VERIFIED. `PROPLINE_API_KEY_*` are **NOT on the box** (`.env` absent); they were pasted in-session earlier this conversation and are usable for probes. `ODDS_API_KEY` is on the box but **exhausted (0/500)**. `ODDSPAPI_KEY` not on box. DB-dependent items were run against prod directly, so nothing here is BLOCKED on PropLine keys. --- ## PHASE 0 — THE ROI RECONCILIATION (headline) - **0.1 ROI computed in code?** — VERIFIED. **Not for the model ledger.** ROI exists only for **user bet-tracking**: `performanceService.js:42` (`roi: stats.roi`) and `betService.js:172-174` (`profit = payout - amount`). There is **no ROI computation over `ledger_entries`** (the model's public record). That is why the model's ROI has been invisible. - **0.2 Price-at-grade persisted?** — VERIFIED. **YES** — `ledger_entries.locked_odds` (text, American), written by `ledgerService.recordPipelineGrades`. Populated on essentially all settled rows (5 nulls on B). **ROI IS computable for accrued history.** - **0.3 11-point index persisted?** — VERIFIED (nuanced). **NOT in `ledger_entries`** — only the collapsed letter (`grade`); `_grade_11` is explicitly `delete`d at `gradeSlateService.js:97` before the ledger write. **BUT it IS retained in `model_snapshots.grade_11`** (migration `025:51`, `retentionService.js:117`) — 2,888 of 4,600 snapshot rows carry it. So sub-tier is **lost on the settled-outcome table but recoverable** by joining `model_snapshots` to `ledger_entries` on (player, stat, line, date). STATE.md's "grade_11 is stored" and this audit's "deleted before ledger write" are BOTH correct — different tables. - **0.4 Price distribution by grade (American odds, settled)** — VERIFIED: | sport | grade | n(decided) | median | q1 | q3 | |---|---|---|---|---|---| | MLB | B | 285 | −270 | −650 | −155 | | MLB | C | 176 | −169 | −500 | +109 | | WNBA | B | 208 | −124 | −145 | −110 | | WNBA | C | 160 | −120 | −134 | −108 | Blended B median −160, C median −130. Overall range −10000 … +600. MLB grades skew to **deep favorites**; WNBA grades cluster tight around −120. - **0.5 Flat-stake unit ROI by grade (1u/row at `locked_odds`, decided rows hit/miss, voids excluded)** — VERIFIED: | sport | grade | n | hit% | **ROI%** | |---|---|---|---|---| | MLB | B | 285 | 70.2 | **−1.02** | | MLB | C | 176 | 60.8 | **+4.57** | | WNBA | B | 208 | 52.4 | **−4.79** | | WNBA | C | 160 | 51.9 | **−5.26** | | A / D / F | (all) | 1 / 2 / 4 | 0 / 0 / 0 | −100 each (negligible n) | Including voids as net-0 (my first pass) gives blended B −2.31%, C −0.10%. **CONCLUSION — the premise's binary is a false dichotomy; the truth decomposes:** - MLB **B** = high-hit (70%) **break-even favorites** — the premise's "favorites at fair prices, zero edge" hypothesis is CONFIRMED here. - WNBA **B/C** = **losing** (~52% hit at ~−120 where break-even is ~54.5%). - MLB **C** = **genuinely +EV (+4.57% on 176 decided)** — a real edge the blended "no ROI" masked. So: ROI is not uniformly zero-edge. It is **not computed in code**, and when computed here it shows **one profitable segment (MLB-C) hidden under WNBA losses and break-even MLB-B**. - **0.6 64/56 population** — VERIFIED. The displayed `/api/ledger/accuracy` figures are `hits/(hits+misses)`, **excluding voids** (88 void rows, 8.8%) and unsettled (67). Distinct `outcome` values are **{hit, miss, void, null}** — **no `push`** (pushes structurally impossible: half-number lines). The ROI table above uses the SAME decided (hit/miss) population, so hit-rate and ROI are comparable. Blended B hit is 63.1% on `hits/(hit+miss)` but 55.9% on all-settled (the 64 void B rows are the gap). - **0.7 Hit rate in UI/marketing?** — VERIFIED. Displayed on **≥10 surfaces**: `app/page.tsx` (landing), `GradeCard`, `TopSignals`, `vyndr/TierRecord`, `vyndr/ModelRecord`, `vyndr/AccuracyBadge`, `game/[id]/page`, `u/[handle]/portrait`, `ledger/page`, `scan/page`. **ROI / edge is displayed NOWHERE.** Honesty gap: users see "63% / 70% HIT" with no indication MLB-B is −1% and WNBA is −5%. - **0.8 CLV computed / closing persisted?** — VERIFIED (with a broken-ness caveat). `closing_odds` on **962/996** rows, `clv` on **842/996** — so CLV IS computed and closing IS persisted. BUT `closingCapture.js:7-8` documents it as **effectively broken**: `closing_line == locked_line on 92% of rows` (only ~56 rows show real movement), because `captureClosing` overwrites the field with the current feed on every snapshot and most props leave the feed near their lock. CLV exists but is **largely degenerate (≈0)**. --- ## PHASE 1 — THE GRADED LINE - **1.9 Selector location** — VERIFIED. Two stages: (a) `gradeSlateService.dedupeProps` (`gradeSlateService.js:34`, called `:144`); (b) the `snapshotService` dedup at `snapshotService.js:425-434`. - **1.10 Reads over which set?** — VERIFIED. **One provider at a time** — PropLine-normalized rows (primary) via `oddsService` → `recordDownstream` → `gradeAndCacheSlate`. Falls back to odds-api / oddspapi only if PropLine fails (`oddsService` fallback chain). Not all providers merged. - **1.11 dedupeProps exists?** — VERIFIED. **YES, it EXISTS** (`gradeSlateService.js:34`). Logic: Set on key `` `${player}::${stat_type}::${line}` `` — keeps the **FIRST** row per (player, stat, line), discards later rows at the same (player, stat, line) (i.e. other books at the same line), caps at `limit`. Written **Session 32** (`f0c8b4f`, "Grades pipeline + NFL/NHL wiring"). This settles the asserted/un-asserted question: **it is real, not imagined.** - **1.12 snapshotService 411-434** — VERIFIED. Confirmed: dedup by `` `${nameKey}|${stat_type}` ``, keeps the row with the **highest `confidence`**. `confidence` derives from the **grade letter** (band-midpoint from `grade_thresholds.json`, `confidence_basis:'grade_band'`) — so "highest confidence" == "highest grade letter". It carries no information beyond the letter. - **1.13 consensus vs first-book** — VERIFIED. **NO consensus rule exists in code.** The graded line is chosen by **first-book-at-each-line (dedupeProps) then highest-grade-across-lines (snapshotService)**. The "book-agnostic consensus rule" description is a prior order's *proposal*, never built. The "first book" description is CLOSER to correct. **Code wins: no consensus.** - **1.14 5 books at 3 lines → which line?** — VERIFIED (walked). `normalizeProps` emits one row per book → `dedupeProps` keeps the first book at each of the 3 distinct lines (3 survivors) → `gradeAndCacheSlate` grades both sides of each → `snapshotService` keeps the **highest-grade** of the 3. **The graded line = whichever of the 3 lines grades highest** (a best-grade-for-us selection, active now that the feed is 27% MLB / 82% WNBA multi-book). - **1.15 Consumers assuming one book/prop** — VERIFIED (partial list): the grade path (`dedupeProps` collapses book multiplicity), `analyzeViaEngine1` (`book_odds`/`fair_prob` use the single surviving row's odds), the snapshot GameCard overlay, `detectBestBook` (no-ops <2 books). - **1.16 Test pinning graded line vs book-set change?** — VERIFIED: **NONE.** `dedupeProps` and `snapshotService` have unit tests for dedup mechanics, but **no test pins graded-line stability against a change in the book set** (a multi-book different-line scenario). - **1.17 Ledger flag for pre/post book-set change?** — VERIFIED. **No dedicated flag.** `model_version` stamps the model era and challenger-version columns exist, but **nothing distinguishes grades by book-set basis.** A silent book-set change would not be visible on the ledger. --- ## PHASE 2 — THE FEED - **2.18 Books-per-prop TODAY (2026-07-26, live `/api/odds`)** — VERIFIED, and it **CORRECTS the prior "137/140 single-book, DK129/FD14" measurement — the feed has broadened:** - **MLB**: n=322 → `{1 book: 234 (73%), 2: 51, 3: 26, 4: 10, 5: 1}`. Books: **draftkings 273, betmgm 105, betrivers 49, pinnacle 30, fanduel 2.** So 73% single-book (mostly DK), 27% multi-book, **5 books now present** (not DK-only). - **WNBA**: n=163 → `{1 book: 29 (18%), 2 books: 134 (82%)}`. Books: **fanduel 149, draftkings 148.** **WNBA is graded off a genuine 2-book feed (DK + FD)** — better multi-book coverage than MLB. - **2.19 ALLOWED_BOOKS** — VERIFIED. `oddsNormalizer.js:9` = 11 books (draftkings, fanduel, betmgm, caesars, fanatics, bet365, hardrockbet, pointsbet, betrivers, pinnacle, thescore), applied at `:110`/`:198`/`:235`. **Drops nothing today** (every book in the live feed is on the list). - **2.20 PropLine request** — VERIFIED. `proplineAdapter.js:128` `buildUrl = ${BASE}/${sportKey}/odds`; `:151-152` params = `{ apiKey, markets }`. **No `regions`/`bookmakers` param is sent.** The feed broadening (2.18) happens on PropLine's DEFAULT response, not a param we added. - **2.21 PropLine docs / multi-book param** — CANNOT DETERMINE (not re-tested this order). Prior research (WebSearch): PropLine advertises "13 books + 5 exchanges; every payload includes a bookmakers array" and is The-Odds-API-compatible (which uses `regions`/`bookmakers`). Whether a param unlocks the full set on our tier is **unconfirmed** — would need a keyed test with the param. --- ## PHASE 3 — THE CHALLENGER LEDGER - **3.22 Row counts** — VERIFIED: | challenger | total | settled | first row | note | |---|---|---|---|---| | arch-v1 | 164 | 128 | 2026-07-21 | 94 with non-zero delta | | contact-v1 | 122 | 86 | 2026-07-23 | 107 non-null `p_win_contact` (15 abstained) | | proj-v1 + proj-v1.1 | 76 + 46 = 122 | 86 | 2026-07-23 | 119 with `proj_point` | **All MLB** (challengers are statcast-gated → MLB only; the 407 WNBA rows carry none). Public ledger total = **996 rows** (929 settled, 67 unsettled). **The earlier "ZERO settled p_win / measurement not begun" is now STALE — 86-128 settled per challenger.** - **3.23 Population rate** — VERIFIED. 872 rows in the last 14 days across **12 active days** (2 days had no rows). Challengers populate a fraction of rows (arch 164/996) — the rest are pre-deploy or WNBA. - **3.24 Idempotent-lock lag** — VERIFIED, **still structural.** The ledger upsert is `ignoreDuplicates:true`, so a challenger's fields land ONLY on rows first written AFTER that challenger's code deployed (arch 07-21, contact/proj 07-23); re-running a snapshot never backfills challenger fields onto an already-locked row. Any challenger added later inherits the same gap. **Settlement itself is healthy** (`stale_unsettled = 0`). - **3.25 Silent-failure / abstain path** — VERIFIED: **none producing fake accrual.** contact-v1's nulls are honest abstentions (thin/absent Statcast); proj-v1 projects 119/122; arch-v1's non-moved rows are byte-identical-to-champion (no distinctive axis). No caught-throw-writes-null path masquerading as accrual. - **3.26 Void/DNP rate** — VERIFIED. **88 void (8.8%)** + 67 unsettled of 996. Matches the ~9% premise. - **3.27 model_snapshots / archetype / opp_rank** — VERIFIED (partial). `model_snapshots` is LIVE: **4,600 rows, latest 2026-07-26 22:01**, `grade_11` on 2,888. Archetype + `opp_rank_stat` were verified live per-sport (MLB + WNBA) in prior orders; **CANNOT re-confirm per-sport freshness here without additional queries** (not run to keep this pass bounded). --- ## PHASE 4 — WHAT IS ACTUALLY WIRED - **4.28 SportsGameOdds wired?** — VERIFIED. **NOTHING.** No SGO reference in `src/`, `web/src/`, or `.env`. Not wired, configured, committed, or deployed. (The audit that qualified it as a source was report-only.) - **4.29 Design surfaces live vs designed** — VERIFIED: - **S2 (book comparison / crown / disagreement):** `BookComparison.tsx` EXISTS in `web/src/components/` but is **NOT imported/routed anywhere (dead component)**. `BookChip` + `BookWordmark` exist and are used. `MovementStrip`, `CrownBadge` → **NOT FOUND** (never built). (Corrects an earlier order that said "BookComparison doesn't exist" — it exists, just unrouted.) - **THE WIRE:** = the **daily newsletter/content format** (see 4.30). Newsletter engine built (`newsletterService`); send is unscheduled (internal endpoint only). - **S3 article media:** **NOT FOUND** (no article-media generator; `mediaEngine` only has wire/ share text templates). - **S-2 Offseason hub / SeasonBoard:** **NOT FOUND** (never built). - **System / Intelligence:** design files present in `specs/design-reference/`; live components partial. - **4.30 What is THE WIRE?** — VERIFIED. It is **VYNDR's daily newsletter / content voice**, not a UI surface: `newsletterService.js:219-220` (`THE WIRE — {date}`) + `mediaEngine.js:6,121` (`MORNING WIRE`, `SIGNAL` deterministic templates). It's the editorial format for the daily report. - **4.31 detectBestBook + LineSparkline** — VERIFIED. `slateAdapter.detectBestBook` returns a book only when ≥2 books post the SAME line at differing prices (no-ops on 1 book, never marks a lone price "best"). `StatStrip.LineSparkline` (`StatStrip.tsx:130`) renders only at **≥3 history points**, returns `null` below. Both degrade honest-absent on single-book/shallow data. - **4.32 Line history / closing overwrite** — VERIFIED. `history` (`{t,line}`, line-deduped, cap 24) is shallow — most props sit at 1-2 flat points (line moves are rare; books move odds, not the half-point line). Two distinct closing mechanisms: the **`closing_captures` TABLE is INSERTED / appended** (`closingCapture.js:234`), but the **`ledger_entries.closing_line/closing_odds` is OVERWRITTEN every snapshot** (`ledgerService.js:18-21` doc; overwrite is why CLV is degenerate — 0.8). - **4.33 Migration drift** — VERIFIED, **still present.** Repo `supabase/migrations/` has **001-022, 025, 030, 031, 032**. **MISSING: 023, 024, 026, 027, 028, 029** — applied to prod but untracked (they carry the challenger columns `p_win_challenger`/`challenger_*`, `model_version`, `env_*`, `archetype_vector` — confirmed live in prod). The repo does NOT reflect prod schema; check `information_schema.columns` via MCP, not the repo, before schema work. - **4.34 WNBA grading live?** — VERIFIED. **YES.** 407 WNBA ledger rows, **376 settled**, graded off a 2-book (DK+FD) feed. WNBA carries no challenger rows (statcast-gated MLB-only). --- ## PHASE 5 — DOCTRINE AND THE BOARD - **5.35 CLAUDE.md (read in full)** — VERIFIED. Standing rules & findings: - **NO CODE WITHOUT A SPEC**; 5 quality gates; WSL2 heredoc rule (python3 for >10-line files); update BUILD-STATE.md / BLOCKERS.md. - **Data Semantics Rule:** VYNDR never generates lines/odds — market values are REAL captured book numbers; only model_value/grade/edge are model output. `Number(null)===0` is the recurring fabrication bug; use strict null guards. - **Grade internals:** grade is an additive integer index (`engine1`, NEUTRAL_INDEX 3), moved by flat ±1.0/±0.5 deltas; A needs sum ≥+4.5, D ≤−1.51. `confidence` is NOT a probability (grade-band midpoint). `p_win` is the real signal. **NEVER rescale thresholds to mint A's (permanent founder ruling).** **A-RATED marketing on hold** until a prod fingerprint shows real A grades. - **Three stat_type whitelists must stay in sync** (analyze.js, scan.js, validation.py). - **Snapshot pipeline** is the product model (scheduled grade → lock to line → read from cache); on-demand "Read" retired. SNAP_TTL 24h. - **DO-NOT-WIRE / DO-NOT-TOUCH:** Tank01 player props = empty (do not wire); ParlayAPI host dead; `gameLogService.getGameLogs` returns null for MLB (a trap — use `featureCache.getStatRows`); `mlbGrader.js` is dead code; the legacy `--grade-a` token alias block kept until consumers migrate. - **Three separate MLB stat maps on purpose** (featureCache / outcomeService / liveTrackingService) — do not merge. Settlement is MLB-only (WNBA/NBA/soccer never settle via that path — but WNBA IS grading + settling per 4.34, so confirm the settle path). - **5.36 Running task list / open items** — VERIFIED. Primary: **`specs/STATE.md`** (1,886 lines, "STATE OF THE WORLD," CURRENT STATUS + OPEN ITEMS block, last dated 2026-07-22). Also `BUILD-STATE.md`, `BLOCKERS.md`, `DECISIONS.md`, `AUTONOMY.md`, `PROMISE-AUDIT.md`, `ROADMAP.md`. **Open threads I have been tracking across recent orders that a strategist chat may not have:** (a) challenger measurement now HAS settled rows (arch 128 / contact 86 / proj 86) — promotion is a future per-prop-type ledger decision; (b) the design-migration arc (Landing hero migrated; scanner S6/S7 blue-channel reskin shipped; S2/THE WIRE/Offseason/article-media are GAP/unbuilt); (c) multi-book: SGO qualified (report-only), nothing wired; (d) **ROI is uncomputed in code and decomposes to MLB-C +4.57% / MLB-B −1% / WNBA −5%** (this file). - **5.37 Spec / roadmap files** — VERIFIED (in `specs/`): `STATE.md` (running state), `VYNDR-NORTH-STAR.md`, `DESIGN-SPEC.md`, `ROW-GRAMMAR.md` (row grammar law), `LIVE-TRACKING.md`, `VOICE.md`, `model-train.md`, `phase-0-kill-the-lies.md`, `phase-1-truth-infrastructure.md`, `propline-audit.md`, `feature-1-1…4-1` + `a1-s3/s7/s9/s10` feature specs, `combat-intelligence.md`, `design-reference/` (the design bundle). Root docs: `CLAUDE.md`, `DECISIONS.md`, `ARCHITECTURE.md`, `AUTONOMY.md`, `BACKEND_HANDOFF.md`, `BUILD-STATE.md`, `BLOCKERS.md`, `PROMISE-AUDIT.md`. - **5.38 Standing decisions a new session might contradict** — VERIFIED: `DECISIONS.md` (DECISION-001+ architecture log); CLAUDE.md's permanent rulings (never mint A's; A-RATED hold; data-semantics; do-not-wire list); STATE.md open items (A does not emit in prod; EV overconfident/ unvalidated — hero ranks on ev_pct and picks the most overconfident read; grade_11 stored in model_snapshots). `AUTONOMY.md` = the zero-touch loop trace. --- ## CLAIMS I WAS ASKED ABOUT THAT TURNED OUT TO BE FALSE OR UNSUPPORTED 1. **"the ledger reports … 597 settled with 'no ROI'"** — the population is now **929 settled / 996 total** (597 was an earlier snapshot); and **ROI IS computable** (`locked_odds` persisted) — it is simply **not computed in the ledger code**. "No ROI" = no computation, not incomputable. 2. **"Either ROI is not computed, or the model is selecting heavy favorites at fair prices"** — the binary is false; **both are partially true and it decomposes by sport/grade**: MLB-B = high-hit break-even favorites (the hypothesis), WNBA = losing, **MLB-C = genuinely +4.57% EV**. Not uniformly zero-edge. 3. **"the graded line is chosen by a book-agnostic consensus rule"** — **no consensus rule exists in code.** It is first-book-at-line (`dedupeProps`) + highest-grade-across-lines (`snapshotService`). 4. **Prior "137/140 single-book, draftkings 129 / fanduel 14" (~98% single-book)** — **corrected**: today MLB is 73% single-book with **5 books present** (DK/BetMGM/BetRivers/Pinnacle/FanDuel), and **WNBA is 82% two-book (DK+FD)**. The feed broadened. 5. **"the 11-point index is lost / unrecoverable"** — **it is retained in `model_snapshots.grade_11`** (2,888 rows); lost only from `ledger_entries`. Recoverable via join. 6. **"ZERO settled p_win yet / challenger measurement not begun"** (from prior challenger orders) — **stale**: arch-v1 128, contact-v1 86, proj-v1 86 rows are now settled. 7. **"BookComparison doesn't exist, only BookChip"** (an earlier order) — **BookComparison.tsx EXISTS**, it is just unrouted/dead. 8. **CLV framed as "held / not computed"** — **CLV IS computed** (842 rows) and closing IS persisted (962), but it is **largely degenerate** (closing==locked on 92%) due to the overwrite — a different problem than "not computed." 9. **WNBA implicitly treated as not-really-grading** (settlement described as MLB-only in CLAUDE.md) — **WNBA IS grading AND settling** (376 settled). The doctrine note about MLB-only settlement is contradicted by the data; the WNBA settle path should be confirmed. --- *End of canonical state. Regenerate the measured numbers before citing them in a later session — they move as the ledger accrues. Structural facts (file/line, schema, wiring) are stable until code changes.*