Build /api/props/top-graded server selector: rank with p_win, serve without it

New READ endpoint. No grade, ledger row, lock_line, or scoring write. Push
scoring untouched.

REVIEW ZERO CORRECTED THE PREMISE: the handler NEVER EXISTED in any commit
(searched git rev-list --all for a /top-graded definition in src/ — zero hits).
Not "removed" — the three axios callers (cheatsheetGenerator, gradeOfTheDay,
widget) and the Next proxy were written against a phantom endpoint, so those
three content generators have silently received [] for their entire life.
Contract recovered from the four consumers, not guessed: {props:[...]},
?sport=UPPERCASE (absent = all sports, which gradeOfTheDay relies on) + ?limit,
rows carrying player/stat/line/direction/sport/grade/confidence? plus the
player_name/stat_type aliases and game_id.

POPULATED-PATH RISK FOUND: the board's populated branch had never run in prod,
and dashboard/page.tsx:463 calls g.stat.replace(/_/g,' ') UNGUARDED (g.player
also feeds the row key, /scan URL and heading; sport must be UPPERCASE for
SportPill). toRow requires non-empty string player+stat and a finite line,
uppercases sport, and DROPS unrenderable rows — a shorter board beats a broken
one.

THE LEAK BOUNDARY (why this is server-side): the browser cannot rank on p_win
for all tiers because stripModelPrice deliberately withholds it from unentitled
tiers. Order of operations is
  read cache -> RANK with p_win (every tier) -> map rows incl. model fields
    -> stripModelPrice(rows, tier) -> serialize
so a free caller receives the paid RANKING without the paid VALUES. Tier comes
from resolveTierFromRequest, which FAILS CLOSED to 'free'. Cache-Control is
private under a bearer token, public otherwise (the /api/snapshot precedent).

ONE SHARED DEFINITION, no drift: new src/utils/gradeRanking.js
(takeablePWin/descNullsLast/rankGrades). heroPropService now imports
takeablePWin instead of its inline copy (behaviour unchanged — it was that
logic verbatim); the selector imports rankGrades; web/src/lib/slateAdapter
keeps its mirror (the browser cannot import src/, S25) and a test cross-checks
the two on identical fixtures (playerName.js precedent). Board is grade-first
("top GRADES"), hero is p_win-first ("top read") — they differ BY DESIGN and
agree within the leading tier.

HONEST LIMIT: the Next proxy (cachedBackendJson) sends no Authorization header
and caches under a shared key, so via the dashboard every viewer gets the
free-tier payload — correct order, no paid values. That is the SAFE behaviour;
forwarding auth into a shared cache is exactly how a paid payload leaks to
anonymous viewers. Per-tier delivery through the proxy needs a tier-keyed cache
and is not done here.

Verified on real prod snapshot data (anonymous path): MLB 8 props, WNBA 10,
0 paid-field leaks, render-contract safe on every row, sport uppercase.

Floor: 311 suites / 3882 tests green (18 new — leak test uses POPULATED p_win,
not today's nulls: entitled gets p_win and it drove the order, unentitled gets
a byte-identical order with all five MODEL_FIELDS absent and no trace in
JSON.stringify, while book/fair market facts survive). Web build exit 0.
Dashboard visual is auth-gated -> tagged for the Chrome audit, not faked.

Held: edge_pct rescale/retirement (Order B); board columns/contract unchanged;
tier-keyed proxy caching.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
Kev
2026-07-29 22:05:30 -04:00
parent 69feab4d25
commit 72a14dc4cd
10 changed files with 672 additions and 8 deletions
+50
View File
@@ -264,6 +264,56 @@
> exposing p_win to unentitled tiers. **Dashboard + Desk visuals are auth/feed-gated → TAGGED FOR
> THE CHROME AUDIT, no visual faked.**
> ## 🟢 `/api/props/top-graded` SERVER SELECTOR BUILT 2026-07-29 (spec `specs/top-graded-selector.md`)
> The dashboard TOP GRADES board finally has a feed. New READ endpoint; no grade/ledger/
> lock_line/scoring write. **0.1 CORRECTS the premise: the handler NEVER EXISTED** — searched
> every commit (`git rev-list --all`) for a `/top-graded` definition in `src/`, **zero hits**. Not
> "removed": the three axios callers (`cheatsheetGenerator`, `gradeOfTheDay`, `widget`) and the Next
> proxy were written against a phantom endpoint, so **those three content generators have silently
> received `[]` for their entire life** — a second, previously-unnoticed casualty now fixed.
> **CONTRACT recovered from consumers, not guessed:** envelope `{props:[...]}`; params `sport`
> (UPPERCASE NBA|MLB|WNBA, absent = all sports, which `gradeOfTheDay` relies on) + `limit`; rows carry
> `player/stat/line/direction/sport/grade/confidence?` plus the `player_name`/`stat_type` aliases and
> `game_id` the other callers read.
> **🔴 0.5 POPULATED-PATH RISK FOUND (the board's populated branch had never run in prod):**
> `dashboard/page.tsx:463` calls **`g.stat.replace(/_/g,' ')` UNGUARDED**, and `g.player` feeds the row
> key + `/scan` URL + heading, and `sport` must be UPPERCASE for `SportPill` (`type Sport =
> 'NBA'|'MLB'|'WNBA'`). A row missing any of those would have CRASHED the board on first populated
> render. `toRow` therefore requires non-empty string `player`+`stat` and a finite `line`, uppercases
> `sport`, and **DROPS** an unrenderable row — a shorter board beats a broken one.
> **THE ORDER OF OPERATIONS (the leak surface):** read cache → **RANK with `p_win` for EVERY tier
> server-side** → map rows *including* model fields → **`stripModelPrice(rows, tier)`** → serialize. So
> a free caller receives the paid RANKING without the paid VALUES. Tier via
> `resolveTierFromRequest` which **FAILS CLOSED to 'free'** (a resolution failure can only withhold,
> never leak). `Cache-Control` = `private` with a bearer token, `public` otherwise (the `/api/snapshot`
> precedent — a CDN must never hand a paid payload to an anonymous viewer).
> **ONE SHARED DEFINITION, no drift:** extracted `src/utils/gradeRanking.js`
> (`takeablePWin`/`descNullsLast`/`rankGrades`). **`heroPropService` now imports `takeablePWin`
> instead of its inline copy** (behaviour unchanged — it was that logic verbatim); the selector imports
> `rankGrades`; `web/src/lib/slateAdapter` keeps its mirror (browser can't import `src/`, S25) and a
> test **cross-checks the two on identical fixtures** — the `playerName.js` precedent.
> **0.3 VERIFIED LIVE that the server HAS p_win:** `/api/hero-prop` returns `available:true` (Brionna
> Jones, B, wnba) and the hero rule REQUIRES non-null p_win + a takeable price. Public
> `/api/snapshot` shows 0/8 MLB + 0/25 WNBA only because it is stripped on the way out.
> **⚠️ HONEST LIMIT — via the dashboard, EVERY viewer gets the free-tier payload.** The Next proxy
> (`cachedBackendJson`) sends only `{Accept}` — **no Authorization header** — and caches under a
> SHARED key (`todayKey(sport,'top_graded')`). That is the SAFE behaviour: forwarding auth into a shared
> cache is exactly how a paid payload leaks to anonymous viewers. So the board shows the correct ORDER
> with no paid values for everyone; entitled payloads are served on a direct authenticated API call
> (proven by route test). Wiring per-tier delivery through the proxy would need a tier-keyed cache — NOT
> this order.
> **VERIFIED ON REAL PROD SNAPSHOT DATA** (anonymous path, what the board will actually render):
> MLB 8 props (B c57 Chandler Simpson edge 140 → …), WNBA 10 (B c69 Rhyne Howard u17.5 points → …),
> **0 paid-field leaks, render-contract safe on every row, sport uppercase**.
> **FLOOR: 311 suites / 3882 tests green (18 new), web build exit 0.** The leak test uses POPULATED
> p_win fixtures (not today's nulls): entitled → p_win present and it drove the order; unentitled →
> **byte-identical order, all five MODEL_FIELDS absent, `JSON.stringify` carries no trace**, while
> market facts (book/fair) SURVIVE — the fair leg is never the paywall. Also locked: chalk (p_win .95
> @300) never tops the board, nulls last but PRESENT, refusals excluded, unrenderable rows dropped,
> thin slate → `200 {props:[]}` never a 404, and board-vs-hero differ by design yet agree within tier.
> **HELD:** edge_pct rescale/retirement (Order B) · board columns/contract unchanged · tier-keyed proxy
> caching. Dashboard visual is auth-gated → TAGGED FOR THE CHROME AUDIT, not faked.
- **Redirect EXISTS + WIRED:** `closingCapture.buildCaptureRows``closing_captures` (append-only,
provenance: captured_at/book/line_type/both-prices/missed_reason) via `intradayRefreshService:221`
+ internal endpoint; `ledgerService.attachClosingProb``closing_prob` (de-vigs both raw sides,