fbcb00b7b1
PHASE 0.5 GATE — the three checks, and one correction.
`fairLine` does not exist. Zero hits across src/ and web/src. Option A as
written had no referent, but it resolves better than feared: `fair_odds` is
already a real de-vigged American price on every graded snapshot row, so there
is nothing to derive.
Gate 1 (is it a price): PASS. fair_odds is American odds from
impliedProbToAmerican inside devigTwoWay; fair_prob is the probability. Both
distinct from `line`, the stat threshold.
Gate 2 (numeric match): PASS, 8/8 exact. Recomputed fair_odds and fair_prob
independently from the stored raw over/under prices; every value matched the
stored one to the integer and to 3dp. Same de-vig, same numbers the component
was proven against.
Gate 3 (poison independence): PASS, and proven on the quarantined cohort
itself. devigTwoWay's inputs are (over_odds, under_odds) — market prices
only, no model term is reachable. The 8 rows recomputed above are all
wrong_opponent_grade rows, and their fair prices reproduce exactly from the
market. The poison is in the grade, not the price. Quarantine therefore
suppresses the MODEL leg only; the fair leg stands, as designed.
THE LEAK WAS REAL AND ALREADY LIVE. GET /api/snapshot/:sport is public and
unauthenticated, and it was serving model_odds, p_win, ev_pct, value and
takeable to anonymous callers on every graded row — 25 of 25 on the live wnba
board. The Session-66 gate on /api/analyze was bypassed entirely by this
endpoint.
The strip covers more than model_odds, because model_odds is not the only way
to read the model price: p_win IS the price in another base, and ev_pct is
INVERTIBLE — ev is a function of p_win and book_odds, and book_odds is public,
so leaving ev behind hands the price over. All five model-derived fields go.
book_odds, fair_odds, fair_prob, overround and devig_method stay on every tier:
the fair leg is never the paywall. Rows that keep a book+fair pair are stamped
model_price_locked so a gated price is never mistaken for a missing one.
Tier comes from resolveTierFromRequest, which reads a bearer token when one is
present and otherwise returns 'free'. It FAILS CLOSED on every error path, so a
resolution failure can only ever withhold the price. The response now varies by
entitlement, so the /:sport handler downgrades Cache-Control to private for
authenticated callers and the browser proxy forwards the bearer token —
otherwise a CDN could hand a paid payload to an anonymous viewer, or every
request would look anonymous and paid users would lose the leg.
READ CARD — a manual scan carries no market. The request is {player, stat,
line, direction}, so the engine has no over/under prices to de-vig and
book_odds/fair_odds are legitimately absent from its response; that is why the
triplet was hidden there. lookupSnapshotPrices recovers them from the
pre-graded snapshot via the same cache-only read this route already performs
for locked odds and team. The join is exact on player + stat + line + side
(fair_odds is side-specific), and returns nothing unless book and fair are BOTH
present — a user-chosen line the board never graded has no market attached, so
the triplet stays hidden rather than borrowing another line's price.
FAIR-LEG ABSENCE, measured before shipping: 636 graded rows, 636 with book,
636 with fair, 0 one-sided. Absence rate 0.0%. The hero number is not a
sometimes-number on current data.
Tests 3556 passed / 291 suites, web build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
48 lines
1.6 KiB
JavaScript
48 lines
1.6 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* resolveTierFromRequest (Session 67) — best-effort tier for a PUBLIC endpoint.
|
|
*
|
|
* `requireAuth` is the wrong tool for a route that must keep serving anonymous
|
|
* callers: it 401s. This resolves a tier when a bearer token happens to be
|
|
* present and otherwise returns 'free', so the endpoint stays open while the
|
|
* gated fields stay gated.
|
|
*
|
|
* FAILS CLOSED. Any error — bad token, Supabase unreachable, missing env —
|
|
* returns 'free', which is the LEAST entitled tier. A resolution failure can
|
|
* therefore only ever withhold the model price, never leak it.
|
|
*
|
|
* Deliberately dependency-light and injectable so route tests don't need a
|
|
* Supabase client.
|
|
*/
|
|
|
|
async function resolveTierFromRequest(req, opts = {}) {
|
|
try {
|
|
const header = req && req.headers && req.headers.authorization;
|
|
if (!header || !header.startsWith('Bearer ')) return 'free';
|
|
const token = header.slice(7).trim();
|
|
if (!token) return 'free';
|
|
|
|
const getClient = opts.getSupabaseServiceClient
|
|
|| require('../utils/supabase').getSupabaseServiceClient;
|
|
const supabase = getClient();
|
|
if (!supabase) return 'free';
|
|
|
|
const { data, error } = await supabase.auth.getUser(token);
|
|
if (error || !data || !data.user) return 'free';
|
|
|
|
const { data: profile } = await supabase
|
|
.from('users')
|
|
.select('tier')
|
|
.eq('id', data.user.id)
|
|
.single();
|
|
|
|
const tier = profile && profile.tier;
|
|
return typeof tier === 'string' && tier ? tier : 'free';
|
|
} catch {
|
|
return 'free'; // fail closed — never leak on an error path
|
|
}
|
|
}
|
|
|
|
module.exports = { resolveTierFromRequest };
|