report: resolution + CLV investigation — Part 1 premise false, Part 2 is an env flag
Nothing built. No poller wired, no capture change, no env flipped.
PART 1 — GRADES ALREADY AUTO-SETTLE. snapshotScheduler resolves settleAllOutcomes
(:64) and settleAllLedgers (:67) and runs them FIRST at every snapshot slot before
grading (its own comment at :393, Session 61). The record is self-populating: 937
settled rows, growing daily (07-24 through 07-30: 20, 25, 44, 26, 98, 62, 91), and
/api/accuracy reads it live at 937 @ 58% (MLB 526 @62%, WNBA 411 @54%).
/api/grading/resolve is a separate unreferenced legacy path, not the settlement
path. Wiring an ESPN poller to it would create a SECOND settlement path racing the
working one and double-count an append-only ledger — so nothing was built.
The DNP/VOID requirement is already satisfied: outcome carries void and
unrecoverable as terminal states, and getModelAggregate excludes both from the
record denominator, so a DNP is never counted as a loss (105 void rows exist).
Idempotency is enforced too — settleLedger guards on .is('outcome', null) and
outcomeService dedupes on nameKey|stat|line|side|date.
THE REAL GAP is smaller and different: settlement covers MLB + WNBA only. NBA and
soccer grade but never settle because no free settled-result feed is wired. That
is a per-sport feed problem, not a missing poller.
PART 2 — clvCaptureReliable() is ONE LINE:
return process.env.CLV_CAPTURE_RELIABLE === '1';
It measures nothing. It fails because the operator has not set the flag, not
because the capture is unreliable. So there is no capture code to repair for the
guard to pass — flipping one env var publishes beat_close_pct immediately, which
makes this a judgement call and precisely the "make a number appear" move the
honesty guard forbids.
The guard itself works: beat_close_pct and clv_distribution publish only when the
flag AND settled>=20 AND clv_sample>0; with it off /record shows NOT PUBLISHED YET
and the computable 34/937 = 3.6% is never the publishing path (clvPanel returns
null and a test forbids the fallback).
CANNOT DETERMINE (Supabase MCP upstream-auth outage): the close-vs-locked
distribution, which is the direct test for the old silent-overwrite bug. The exact
query is in the report. A decision rule is stated BEFORE seeing the number so it
cannot be fitted to it: set the flag only if close_moved is a clear majority of
rows carrying a close AND coverage of settled rows is high enough that the
percentage describes the record rather than the captured subset. 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 — full outstanding board included in the report, covering model work
(A-flood grade fix on p_win vs fair_prob, the collapsed-output re-adjudication
list, calibration/time-series with no honest source, price-triplet MODEL leg),
surfaces (D1 mount, share cards, notifications, Offseason, /system, S3 media,
45 unwired glyphs, /record has no nav link) and infra (NBA/soccer never settle,
three credentials still flagged for rotation, migration drift 023-029).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# 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.**
|
||||
Reference in New Issue
Block a user