# RESOLUTION PIPELINE + CLV CAPTURE β€” INVESTIGATION (report; nothing built) 2026-07-31. No poller wired, no capture change, no env flipped. --- # PART 1 β€” πŸ”΄ THE PREMISE IS FALSE. GRADES **DO** AUTO-SETTLE. **"Grades never auto-settle" is not the case, and building an ESPN poller onto `/api/grading/resolve` would create a SECOND settlement path racing a working one β€” double-counting an append-only ledger.** That is why nothing was built. **VERIFIED β€” settlement runs on every snapshot slot.** `src/snapshotScheduler.js` resolves `settleAllOutcomes` (`:64`) and `settleAllLedgers` (`:67`) and, per its own comment at `:393`, *"settlement is scheduled IN THIS tick (settleAllOutcomes + settleAllLedgers run FIRST at every snapshot slot, before grading)"* (Session 61). **VERIFIED β€” the record is already self-populating.** Public ledger: **937 settled**, and it grows daily. Settled rows by game_date, measured this session: | 07-24 | 07-25 | 07-26 | 07-27 | 07-28 | 07-29 | 07-30 | |---|---|---|---|---|---|---| | 20 | 25 | 44 | 26 | 98 | 62 | 91 | `/api/accuracy` reads it live: overall **937 @ 58%**, MLB 526 @ 62%, WNBA 411 @ 54%. **So what IS `/api/grading/resolve`?** A separate, unreferenced legacy path (Wave 3 found zero callers). It is not the settlement path and never was. **Wiring a poller to it would settle rows a second time**, and `ledger_entries` is append-only with an `ignoreDuplicates` upsert β€” the failure would be silent and permanent. ### The DNP/VOID requirement β€” ALREADY SATISFIED, and load-bearing The order is right that DNP-as-loss is a downward fabrication. It is already handled: `outcome` values include **`void`** and **`unrecoverable`**, both terminal, and `getModelAggregate` **excludes them from the record denominator** (`.not('outcome','in','("void","unrecoverable")')`) β€” *"void (no bet existed: DNP/postponed) and unrecoverable (truth not fetchable) are TERMINAL but are NOT results. They must never enter a record denominator, exactly as pushes are."* **105 void rows exist in the public ledger.** So DNPs are already excluded, not counted as losses. **Idempotency is already enforced too:** `settleLedger` guards with `.is('outcome', null)` on the update, so a re-poll cannot re-settle. `outcomeService` dedupes on `nameKey|stat|line|side|date`. ### What is ACTUALLY missing (the real gap, much smaller than the order assumes) Settlement covers **MLB + WNBA only** (`outcomeService.SPORTS`). NBA and soccer grade but never settle β€” no free settled-result feed is wired. **That** is the resolution gap worth an order, and it is a per-sport feed problem, not a missing poller. --- # PART 2 β€” πŸ”΄ `clvCaptureReliable()` IS AN ENV FLAG, NOT A DATA CHECK function clvCaptureReliable() { return process.env.CLV_CAPTURE_RELIABLE === '1'; } That is the whole function (`ledgerService.js`). **It does not measure anything.** It fails because the operator has not set `CLV_CAPTURE_RELIABLE=1` β€” not because the capture is computing something unreliable. **This reframes Part 2 entirely.** There is no capture code to "repair" so the guard "legitimately passes": **flipping one env var publishes `beat_close_pct` immediately.** Which makes this a judgement call, not an engineering task β€” and precisely the call the honesty guard says must not be made to "make a number appear." **What the guard gates (VERIFIED):** `beat_close_pct` and `clv_distribution` publish only when `clvCaptureReliable() && settled >= 20 && clv_sample > 0`. With the flag off they are null, `/record` renders **"NOT PUBLISHED YET"**, and the computable-but-untrustworthy `clv_beat/clv_sample` (**34/937 = 3.6%**) is never the publishing path β€” `clvPanel` returns `beatClosePct: null` and a test forbids the fallback. **The honest guard is working exactly as designed.** ### The evidence that must decide the flag β€” and one measurement I could NOT run Known from the 07-28 repair (verified earlier this session): `closing_prob` went **59 β†’ 406** (MLB 248, WNBA 158) against ~1,250 public rows, and the straight finding was **MLB unders lag the close by βˆ’9.1 prob-pts (74% lose)** while **MLB overs are +2.0 and WNBA is flat**. **CANNOT DETERMINE (blocked, Supabase MCP upstream-auth outage):** the current close-vs-locked distribution β€” specifically `count(closing_line = line)` vs `count(closing_line <> line)`. That is the direct test for the old silent-overwrite bug (92% close==locked). **Run exactly this before deciding the flag:** select count(*) filter (where outcome is not null) as settled, count(closing_prob) as with_close, count(*) filter (where closing_line::numeric = line::numeric) as close_equals_locked, count(*) filter (where closing_line::numeric <> line::numeric) as close_moved, count(*) filter (where market_unavailable_reason is not null) as unavailable from ledger_entries where user_id is null; **Recommended decision rule, stated before seeing the number so it cannot be fitted to it:** set `CLV_CAPTURE_RELIABLE=1` **only if** (a) `close_moved` is a clear majority of rows carrying a close β€” i.e. the capture is reading a genuine close, not mirroring the locked line β€” **and** (b) coverage of settled rows is high enough that the published percentage describes the record rather than the subset we happened to capture. **If either fails, leave it off.** A CLV near zero *because close==locked* is the fabrication to avoid, and it would look like success. --- # PART 3 β€” THE OUTSTANDING BOARD (authoritative, from the matrix + STATE) **Shipped this session (closed):** Book Comparison wired Β· hero ranking fix Β· grade-board sort Β· `/api/props/top-graded` selector Β· takeable tagging (migration 034) Β· edge_pct display retirement Β· Wave 1 wiring (`/intelligence`, `/slip`, `/parlay`, `/marketplace` honesty) Β· `/compare` real head-to-head Β· D1-A design implementation Β· D1 row-anatomy modules Β· Build-1 settled/live gate Β· `/record` proof surface Β· Build-2 founder mechanism Phase A + B. **OPEN β€” model (highest leverage):** 1. **A-flood grade fix** β€” rebuild the grade on `p_win` vs `fair_prob`. The served letter correlates **r β‰ˆ 0.005** with outcomes (B 52.4% vs C 56.9% β€” *inverted*), while the probability letter reaches **r = 0.236 on MLB**. This is the single biggest truth gap in the product. 2. **Re-adjudication list** β€” p_winβ†’CLV 0.375, the skew audit, proj-v1.1's "NOT PROVEN", the C1 takeable floor, the calibration curves, and **ROI-by-grade (MLB-C +4.57% is likely an artifact of a meaningless letter)** β€” all measured on the collapsed output. 3. **Calibration curve + accuracy-over-time** β€” no honest source (no claimed-vs-actual endpoint; `window_days` fixed at 30, no series). 4. **Price-triplet MODEL leg** β€” waits on the EV layer producing `p_win`/`ev_pct`. **OPEN β€” surfaces:** D1 reveal/rationale/chips **mount** (modules built, not mounted; blocked on a ROW-GRAMMAR slot amendment + the board-reasoning gating decision) Β· share-card masters + M2 crops (**blocked: the resolve fanout has no generation step and no trigger**) Β· `/notifications` (store exists, no trigger, no consent surface) Β· Offseason hub Β· `/system` Β· S3 article media Β· the 45 unwired glyphs + the 41-vs-74 archetype scope call (D1-B) Β· **`/record` has no nav link yet.** **OPEN β€” infra/ops:** NBA + soccer never settle (Part 1's real gap) Β· **three credentials still flagged for rotation** (Storage Box, `VYNDR_INTERNAL_KEY`, the GitHub PAT in `origin`) Β· migration drift 023–029 untracked in the repo. **NOT previously tracked, surfaced here:** the Stripe **live secret + webhook secret were pasted into a chat transcript** this session. Stored correctly (`.env`, 0600, gitignored, untracked; scan confirms no tracked file holds them) and Kev has declined rotation β€” recorded so the decision is visible, not to relitigate it. --- ## TAGS VERIFIED: settlement runs every slot and the record self-populates (937 settled, daily growth); void/unrecoverable already excluded from the denominator (105 void rows); settlement idempotent; `clvCaptureReliable` is an env flag; the honest CLV guard works and the 3.6% path is blocked by test. **CANNOT DETERMINE: the close-vs-locked distribution (Supabase MCP outage) β€” the one number that should decide the CLV flag.** **BLOCKED: nothing was built β€” Part 1 because the premise is false and a second settlement path would double-count; Part 2 because the "repair" is a one-flag judgement call that needs the blocked measurement first.**