Commit Graph

155 Commits

Author SHA1 Message Date
builtbykev 1372e6bcf7 Order Zero Phases 1-3: keyed verification, ruler_version boundary, report
PHASE 1 (measured on the live prod feed with the real key):

- WNBA is NOT thin at the feed -- 4.21 books/prop vs MLB's 3.61. It was
  allow-list-starved exactly as MLB was. This removes one candidate
  explanation for its anti-predictive result; it does not explain it, and
  WNBA stays abstaining.
- We cannot see 64.8% of the MLB slate at all (zero admitted books).
- Exchanges are real (smarkets 27%, novig 22%, kalshi 15% on MLB) but
  pinnacle, matchbook and polymarket measured 0% on BOTH sports. There is
  no sharp anchor for player props. The consensus is a MARKET consensus,
  not a SHARP one -- recorded as a permanent limitation, not a milestone.
- DFS is the trap, quantified: prizepicks covers 82% of MLB props, the
  highest in the feed. Admitting it "for breadth" would have looked like
  the biggest available win. Permanently excluded.
- Endpoints: /context WORKS and is FREE (umpire, roof, pitcher handedness,
  lineup confirmation -- richer than what we hand-built). /odds/closing and
  /movement are REDACTED (full structure, zero prices). /results and
  /exports/resolved-props are 403.
- The $19/mo question is answered: soccer IS graded, ~15 competitions in 30
  days (MLS 41k, Liga MX 15k, Brasileirao 12k, UCL/Europa/Conference). Our
  "soccer grades into a void" is a Pro-tier problem, not a data problem.
  NBA is absent because it is July -- seasonal, not inferable either way.

PHASE 2 delta, corrected: MLB mean +1.50 pts, median 0, p90 +10.0, 17.0%
of comparable props move >=5 pts, one-directional (the incumbent prices
the over below the exchange-inclusive consensus). WNBA symmetric and
tight. The median prop does not move -- the change is a right-skewed
minority. That the rulers DIFFER is established; that the new one is
BETTER is not, and that is the re-run.

PHASE 2 item 6: ledger_entries.ruler_version applied to prod, 1,384
existing rows backfilled to v1_first_book (a statement of fact -- every
row to date was produced by the first-book rule). ledgerService stamps
CURRENT_RULER_VERSION on new rows. Never pool edge or CLV across it.

Repo migration numbering lags prod; 025_ledger_ruler_version.sql records
the DDL for review.

PHASE 3: MLB isotonic p_win remains PROVISIONAL -- calibrated against
v1_first_book, does not promote until re-run on the consensus ruler.

NOT LIVE, deliberately: ALLOWED_BOOKS unchanged, served slate
byte-identical, CURRENT_RULER_VERSION still v1_first_book, no live path
calls consensusRuler.

Gates: 4,022 tests passed / 322 suites; next build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 23:52:28 -04:00
builtbykev 293367917c Order Zero: book-breadth test + accrual clock correction (measure-only)
STEP 0 disproved the premise before any request was fired. PropLine's
OpenAPI contract states verbatim that `bookmakers` omitted = ALL books,
so proplineAdapter omitting it is correct and always was. Firing a
guessed param would have RESTRICTED the response and produced exactly
the false negative the order warned about.

The real cause is ours: PropLine sends 18 books; oddsNormalizer
ALLOWED_BOOKS intersects them at exactly 5 -- which is precisely the
"5 MLB books" the 2.18 audit measured. Measured on real public data
(no key, no quota): 4.41 books/prop from the feed, 1.50 after our
filter, and 12 of 34 props go invisible entirely.

Also corrected: "73% single-book" is the long tail of deep props
sole-posted by DraftKings or Bovada. On the core props we grade, the
market is 10-12 books wide. pinnacle appears on 0 of 40 MLB props --
the independent low-vig references present on 100% of core props are
exchanges (novig/smarkets/kalshi). DFS pick'em also covers 100% but is
not a market price and must never enter a consensus.

Verdict is outcome (d) ALREADY OPEN, not (a)/(b)/(c) -- all three
assumed the feed was the constraint. Ruler change scoped (not built):
split one allow-list into takeable/reference/excluded, fair_prob_lock
becomes a median consensus with n>=2 or a labelled fallback. Gated on
exchange price validation + the WNBA measurement, which needs the
PropLine key (prod-only, absent locally). MLB isotonic p_win declared
PROVISIONAL until re-run on the real ruler.

Side finding: we use 1 of 29 endpoints. /odds/closing, /movement,
/odds/history, /best-line, /ev, /results, /exports/resolved-props,
/context (free) map directly onto documented gaps -- and resolution
across 33 sports suggests "no free settled feed for NBA/soccer" may be
a $19/mo problem, not a data problem. Documented, not verified.

Plan edits: §10.1 rewritten, §10.2/§10.5 corrected, and §11 adds the
sequential post-completion accrual clock -- pre-completion data does
not count, no pooling across the completion boundary, two clocks
stated separately, per-sport clocks, verification gate before any
accrual, users onboarded to a complete product only. §9.1's "6-10
weeks out" corrected: that is accrual duration, not distance to the
answer. The ruler change independently forces the same no-pooling
boundary by arithmetic.

No API key was used, printed, or committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 23:19:23 -04:00
builtbykev c98338ef23 plan: add §10 — aggregator + paid-model gaps, and the one root cause behind both
Answers "what makes this the top product, not just a finished one."

THE REFRAME: the aggregator gap and the model gap are the SAME gap in two places.
Our "market" is often ONE book — MLB props are 73% single-book, and
proplineAdapter sends only {apiKey, markets} with NO regions/bookmakers param
(:152), so we take PropLine's default response. That single fact causes four
problems we had been treating as unrelated: no line shopping (the category's #1
free hook), a fair_prob_lock that is a de-vigged single soft book rather than a
consensus (the bent ruler the model is judged against), weak CLV (cannot measure
beat-the-close against one book), and no steam/disagreement detection (needs >=2
books to exist).

So the highest-leverage unblocked action in the whole plan is a cheap API test:
does PropLine return more books with a regions/bookmakers param on our tier? One
request, and if it works it upgrades the free product, the model's denominator and
the CLV instrument simultaneously.

Aggregator gaps catalogued: book breadth, true consensus, historical odds archive
(started — closing_captures 844k rows, lock_lines new, but in-grade history capped
at 24 points, so no full open->close series), market breadth (11 live vs the
category's 50+), ingested alt-line ladders, injury/lineup wire, player news.

Paid-model gaps catalogued: distribution instead of a point (distribution.js
already computes survival probabilities and rungs but is proj-v1.1, ledger-only
and lost to the champion); opportunity/playing-time projected FIRST with its own
uncertainty (the single biggest available modelling gain); per-stat models instead
of one additive index; matchup granularity that actually reaches the grade;
applied calibration; a backtest harness (blocked by the archive gap — you cannot
backtest a price you never stored); CLV as north star.

THE PATTERN: almost every model capability is ALREADY BUILT AND DISCONNECTED.
VYNDR does not have a building problem, it has a connection-and-proof problem plus
one genuine ingestion gap that starves both halves. The expensive part is largely
done, but no new feature fixes it.

Ordering principle recorded: get MLB genuinely good BEFORE replicating across six
sports — a copied-six-times thin model is six times the maintenance for the same
absent edge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 22:35:16 -04:00
builtbykev 37ee952e26 plan: add §9 — what is actually missing for the product to work, not just be built
The phases counted unbuilt code. This section names what is missing for VYNDR to
do what it claims, including the parts that are not builds.

THE CENTRAL GAP: there is no demonstrated edge yet. Every measurement this session
returned null, negative or unproven — served grade r~0.005 and inverted; all three
p_win-vs-fair_prob formulations negative on both sports and both splits; p_win
alone on MLB holdout p~0.07; WNBA negative; CLV null by guard; ROI-by-grade likely
an artifact. The product's core claim is not currently supported by our own data,
and building all 23 orders without closing this leaves a well-built product that
does not do the thing it sells. What closes it is sample and honest iteration, not
code — roughly 6-10 weeks at the current accrual, a clock engineering cannot
shorten and that must not be faked.

Also named: the projection is thin (l5/l20 + opponent rank + rest + usage, with
similarity/archetypes/conditions/Bayesian all built and disconnected, so
connecting them is a hypothesis not a guarantee); it is a one-sport product today
(NBA and soccer do not even settle); there are 3 users and 0 paid so nothing is
validated by usage; there is NO distribution path at all, which appears in no
phase and belongs on the board as its own track; the last mile is unclosed
(push-to-book is a teaser, no affiliate live); and operational fragility remains
(single box, two-sport settlement, three credentials flagged including a Stripe
live key that transited a transcript, no staging).

The honest summary: the truth infrastructure is genuinely well built and this
codebase does not lie about what it knows. What is not yet true is that the model
beats the market — not disproven, unmeasured at adequate n. The finish line is 23
orders PLUS a verdict from accrued data we cannot rush, and the discipline to
report that verdict honestly if it says the edge is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 22:16:11 -04:00
builtbykev e3ca1650d9 plan: specs/MASTER-PLAN.md — single source of truth, 7 phases, ~23 orders, defined END
Consolidation only. Nothing built, wired or promoted.

NOTHING WAS RE-VERIFIED and no query was run — all 22 artifacts produced this
session plus the completion matrix were taken as KNOWN, per the order's own clause.
The verification ledger at the top of the plan lists exactly what was taken as
known and which four items remain genuinely open (sport order, board-reasoning
gating, the CLV flag, team colours) — each open because it needs a decision or a
build, not a query.

The plan captures all six tracks in one document: per-sport models (MLB's 8-layer
stack with each layer marked BUILT/PARTIAL/NOT-WIRED, plus the sport order),
design implementation (61 catalogued items), surfaces, the resolution tail, the
sport boundary, and the Chrome audit.

The through-line it makes visible: MLB's layers 2, 3, 5 and 6 are BUILT AND NOT
CONNECTED, while layer 8 (the grade ladder) is connected and meaningless
(r~0.005, inverted). MLB's fix is connection, not construction.

Phasing is by dependency: MLB model truth -> resolution tail -> surfaces/design
(parallel lane) -> sport boundary -> sport rollout (one order per sport) ->
monetization finish -> Chrome audit and hardening. ~23 orders total, ~11 unblocked
today, so "how many sessions left" now has a real answer.

DEFINITION OF DONE is explicit and countable: MLB layers 1-8 connected with a
monotone held-out-proven ladder; every listed sport finished on the same template
or explicitly abstaining with its reason recorded; all 61 design items built; every
surface reachable and honest; the resolution pipeline firing end-to-end; the sport
boundary a registry; the Chrome audit passed; and the record publishable on its own
terms with no claim outrunning its evidence.

STATE.md now points at the plan and is demoted to history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 22:15:10 -04:00
builtbykev 6d87d7a33c report: p_win recalibration holdout — MLB qualifies on isotonic, WNBA abstains
Measure-only. p_win not flipped live, no grade rebuilt, no calibrator deployed.
Per the doctrine, MLB and WNBA were fitted, selected and judged as SEPARATE
models — and they reach opposite verdicts. No global instrument was fitted.

METHOD: time-forward split per sport (earlier fits, later proves). Both
instruments fitted on TRAIN only — single-parameter Platt and
isotonic-with-pooling. Inputs p_win + outcome only; no market field, no closing
value, no lookahead. Nothing about edge/CLV/beat-the-close enters any pass/fail
line.

MEASUREMENT CORRECTION made mid-run: the first pass reported mean|p - outcome|
(~0.46-0.51), which is NOT calibration — it is noise-dominated individual error
on 0/1 rows and would have made every instrument look identical. Reliability is
only meaningful on BUCKETS (bucket mean predicted vs bucket actual rate,
n-weighted), the metric T0 used. All reported numbers use the corrected metric.

HOLDOUT RELIABILITY (lower better): MLB n=119/4 buckets — raw 0.1038, Platt
0.1120, ISOTONIC 0.0939. WNBA n=93/3 buckets — raw 0.1322, Platt 0.0491,
isotonic 0.0667.
HOLDOUT RESOLUTION: MLB raw 0.1388 -> Platt 0.1284 -> isotonic 0.1225.
WNBA raw -0.1201 -> Platt +0.1269 -> isotonic +0.0322.
Fitted Platt: MLB a=-0.381 b=+0.705; WNBA a=+0.040 b=-0.081.

MLB QUALIFIES, MODESTLY — instrument selected BY HOLDOUT, not assumed: isotonic
beats both raw and Platt, and Platt actually made MLB worse. Reliability improves
0.1038 -> 0.0939 (~10% relative, real but modest) and resolution SURVIVES
(0.1388 -> 0.1225, not crushed). Both Mandate-3 conditions hold.

WNBA ABSTAINS — its Platt result is the best number in the report and is REJECTED
as a fake win. The fitted slope is b = -0.081, negative and near zero, so
sigmoid(0.040 - 0.081*logit p) is nearly constant at ~0.51 for every input: it
"calibrates" by discarding the prediction and emitting the base rate, which is
exactly the failure Mandate 3 pre-registered. Its apparent resolution gain
(-0.120 -> +0.127) is the sign flip, not skill — it would serve the opposite of
its own forecast, fitted on n~96 of anti-signal. Isotonic says the same quietly
(resolution collapses to +0.032).

HONEST CEILING: MLB is a usable-but-unimpressive forecaster (holdout resolution
~0.12, reliability ~0.094, n=119); WNBA has no honest forecast today. Holdout n
and bucket counts (4 and 3) suffice to reject WNBA and prefer isotonic for MLB,
NOT to certify a letter ladder, and the T0 pathology is reduced rather than cured.

CANNOT DETERMINE: per-archetype calibration (Mandate 3d) — bucket n falls below
the reporting floor once split by sport AND archetype on 442 rows.

Queries committed at scripts/pwin-calibration-holdout.sql.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 21:26:21 -04:00
builtbykev 249b3e8235 report: grade diagnostic T0 — p_win is MISCALIBRATED, and it explains the inversion
STOPPED at the T0 gate as instructed. Nothing fixed, no recalibration applied,
no grade touched. T1-T4 deliberately not run.

T0 FIRES ON BOTH PRE-REGISTERED CONDITIONS.

Condition 1 (mean |predicted-actual| > 0.05): MLB ~0.094, WNBA ~0.139.
Condition 2 (monotonic slope): over-confidence GROWS with the prediction —
MLB +0.034 -> +0.043 -> +0.084 -> +0.190 -> +0.189; WNBA +0.044 -> +0.109 ->
+0.349. Worst cases: MLB predicted 0.842 actual 0.652 (n=23), predicted 0.917
actual 0.727 (n=11); WNBA predicted 0.730 actual 0.381 (n=21).

WHY THIS EXPLAINS THE INVERSION, mechanically: p_win is over-stated and the
overstatement SCALES with p_win, so p_win - fair_prob_lock is largest exactly
where p_win is most inflated. Those props hit less than claimed, so the edge
measure correlates negatively. The market was never the problem —
fair_prob_lock is not a bent ruler, the thing subtracted from it is. It also
explains why p_win ALONE still carries signal (+0.23 MLB): rank survives
miscalibration, differences do not.

This independently reconfirms the 2026-07-26 calibration finding (+0.02 at p<.5
-> +0.19 at p>=.8) on a newer, larger population, so it is structural rather
than sampling noise.

PART 0: P0a — only the GRADED side's fair prob is stored (fair_prob_lock;
no opposite-side field), so T1's two-side-sum check cannot run and must use the
stated no-vig recompute fallback. P0b — projection_locked_at exists as a
timestamptz so T2 is potentially runnable, but distinctness from lock time was
NOT verified because T0 gated it.

Two cautions recorded before Part 2 runs: the top MLB buckets where the error is
worst hold n=23 and n=11, so a flexible per-bucket correction would fit noise —
isotonic with pooling or single-parameter Platt is safer; and calibration fixes
magnitudes, so if the market is genuinely better the repaired edge may still land
at ~0, which would be the honest ceiling and gets reported rather than graded
around.

Query committed at scripts/grade-calibration-t0.sql.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 20:12:20 -04:00
builtbykev ea1157d709 report: grade fix Part 1 — the p_win-vs-fair_prob rebuild is REFUTED by the data
STOPPED at the Part 1 gate. Nothing rebuilt, no grade changed, no cutover.

THE FINDING: grading on p_win vs fair_prob does not work. All three candidate
edge formulations correlate NEGATIVELY with outcomes, on both sports, overall,
and in both time splits (n=432 decided rows carrying p_win AND fair_prob_lock):

  ALL  n=432  champ -0.0016  p_win ALONE +0.1221  additive -0.0615  ratio -0.1161  logodds -0.0438
  MLB  n=240  champ +0.0984  p_win ALONE +0.2278  additive -0.0336  ratio -0.1350  logodds -0.0124
  WNBA n=192  champ -0.1143  p_win ALONE -0.0842  additive -0.1326  ratio -0.1281  logodds -0.1243

Subtracting the market's lock-time fair probability destroys and inverts the
signal. The plain reading: props where the model most disagrees with the market
are LESS likely to hit — the market is better than the model, so "edge vs market"
is anti-predictive here, while the raw probability retains some skill alone.

WHAT DOES CARRY SIGNAL: p_win alone, MLB only, and it is modest. Time-forward
split — TRAIN (07-21..07-26, n=120) r=0.2770; HOLDOUT (07-26..07-30, n=120)
r=0.1647, with the additive edge negative in BOTH halves. So p_win survives
forward validation directionally but the holdout is NOT significant (t~1.81,
p~0.07). Suggestive, not proven.

WNBA MUST ABSTAIN: every measure negative including p_win itself (-0.084). Forcing
one threshold across both sports would make a coin-flip sport look sharp, which the
order forbids.

LOOKAHEAD GUARD SATISFIED: fair_prob_lock is the lock-time field, populated on 432
decided rows, range 0.145-0.713. closing_prob (415 rows) is the CLOSE and was NOT
used in any correlation — using it would have manufactured a correlation.

SAMPLE REALITY: 1103 decided rows but only 432 carry both instrument fields, so a
per-sport train/holdout split leaves ~120 per half — enough to show direction, not
to certify a letter ladder.

I did not tune toward a win: three pre-registered candidates were tested and all
three failed; picking a fourth because the first three lost is the overfitting the
order guards against. Recommended instead: grade MLB on p_win alone with WNBA
abstaining and label it modest/accruing (A-RATED hold stays); or wait ~6 weeks for
n~500 MLB; or investigate WHY the market-relative edge inverts, which is the more
valuable question.

Both queries committed at scripts/grade-correlation-proof.sql so no number here
has to be taken on trust. Working settlement untouched; dead resolve endpoint not
wired.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 19:57:21 -04:00
builtbykev 40c61fbb0b 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
2026-07-31 19:38:19 -04:00
builtbykev e970ab1ef3 report: Build 2 Review Zero — G1-G6 + DB verified; two order expectations wrong
No build, no migration, no Stripe object touched. Awaiting Kev on Q1-Q3.

TWO EXPECTATIONS IN THE ORDER ARE WRONG:

1. G5 — users.founder_status is LIVE, not dead. Written by the webhook
   (stripeService.js:163), read and served by routes/stripe.js:95 as is_founder,
   and present in middleware/auth.js:24 PROFILE_COLUMNS so it loads on EVERY
   authenticated request. The guardrail says don't write it unless G5 proves it
   live — G5 proves it live, so A5 must NOT drop it.

2. THE TWO FOUNDER FLAGS ALREADY DISAGREE IN PROD: user_profiles.founder_pricing
   is true on 1 of 3 profiles while users.founder_status is true on 0 of 3. The
   webhook writes both from the same isFounder, so this is a dual-write that has
   already drifted. The build must pick one canonical flag and derive or retire
   the other; two independently-writable founder flags is how a founder loses
   their rate on one code path.

GREPS: G1 founder_pricing has exactly one writer (the webhook mirror) and four
readers (partners MRR attribution, the profile API, the profile badge). G2 the
promo-code bypass is the ONLY founder gate today — getPriceId(tier, founderCode)
against VALID_FOUNDER_CODES, stamped into metadata.is_founder, which the webhook
then trusts, so a code alone mints a founder at any seat number. G3 the webhook
DOES set tier + subscription_status=active + founder_pricing (closing an earlier
CANNOT DETERMINE: a paid sub does flip the Build-1 gate) but stores NO
stripe_subscription_id, confirming A1. G4 nexapay has ZERO code references and
the column is empty, so A5's drop is evidence-supported as its own migration.
G6 price selection is getPriceId -> line_items.

DB VERIFIED: user_profiles has nexapay_customer_id and NO stripe_customer_id /
stripe_subscription_id (A1 needed); users already carries stripe_customer_id;
founder_pricing_seats is a VIEW; 3 profiles, 1 flagged founder.

CANNOT DETERMINE: the four Stripe price IDs — no STRIPE_SECRET_KEY or
STRIPE_PRICE_* in this environment, so I could not independently re-verify that
the IDs in the order are what prod will charge. Since A3 would hardcode them, a
typo becomes a permanent mis-charge; recommend reading them from env (already the
pattern) with a boot assertion that all four resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 17:07:54 -04:00
builtbykev 14f3ce95b9 report: Build 2 Review Zero — payment mechanism BLOCKED, no Stripe credentials
Nothing built. No Stripe object created or changed, no price logic touched.

STOPPED because there is no STRIPE_SECRET_KEY in this environment (.env holds
only ODDS/SUPABASE/INTERNAL keys). The order's standing floor requires
founder/standing/grandfather/race all verified server-side; none of that is
verifiable here, the standing price objects cannot be created, and the
concurrent-checkout race cannot be exercised. On a payment path the failure modes
are permanent and customer-facing — a race bug mis-prices a subscriber forever,
a grandfather bug overcharges one every month — so it must not ship unverified.

VERIFIED ANYWAY:
- Stripe IS live and FOUNDER price objects DO exist. /api/founders/count returns
  {available:true, claimed:0, total:100}, and routes/founders.js returns
  {available:false} whenever countFounderSeats() is null, which it is when
  !STRIPE_SECRET_KEY || founderPrices.length === 0. So available:true proves the
  secret key and at least one founder price ID are configured in prod, and
  claimed:0 is a real count rather than a fallback.
- THE COUNTER IS NOT A GATE. It is a cached (300s) READ, not a claim; founder
  pricing is gated by CODE + EXPIRY, not by the count, so anyone holding
  FOUNDER2026 gets the founder rate at any seat number and the cap is decorative.
  Two simultaneous checkouts at slot 99 would both read 99 and both get founder —
  there is no lock or unique constraint anywhere in the path.
- The gate reads users.tier via config/tiers.js reasoning_visible, so a
  successful subscription must set users.tier for Build 1's gate to open.

CANNOT DETERMINE: whether the STANDING price objects exist (env unreadable, and
getPriceId falls back SILENTLY to a PRICE_UNCONFIGURED sentinel, so a missing
standing object would not surface until the first post-cap checkout 400s in front
of a paying customer); whether the webhook writes users.tier on
checkout.session.completed.

DESIGN IS SETTLED for when it unblocks: a founder_slots table with a unique
constraint on (tier, slot_number) claimed before the Stripe call — the unique
index, not a count read, is what makes the race impossible; price selection from
the claim rather than a code, with the code+expiry bypass retired; grandfathering
by simply never calling Stripe price-migration on a founder sub;
founder-follows-upgrade by claiming on the target tier and releasing the slot on
cancellation; honest display that shows no number when the count is unavailable
(the existing route already sets that precedent).

PREREQUISITES, all needing Kev and none of them code: confirm/create the two
standing price objects and set STRIPE_PRICE_ANALYST / STRIPE_PRICE_DESK; confirm
the webhook sets users.tier; provide a Stripe test-mode key so the race,
grandfather and end-to-end unlock can be exercised rather than asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 08:30:32 -04:00
builtbykev e8b15c705a docs: free proof surface recorded (/record, hollow-preserving, CLV honest-absent)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 07:56:08 -04:00
builtbykev 4c302b5722 report: free proof surface Review Zero — three of four leads have no data source
Nothing built. Docs only. The premise was that this is cheap assembly over
existing aggregates; verified, it is not.

0.1 FILTERABILITY — the endpoints are NOT filterable. Probed live:
    /api/ledger/accuracy?sport=mlb -> total 937
    ?sport=wnba -> total 937
    ?window=7 -> total 937
    identical payloads; the params are ignored (the req.query reads at
    routes/ledger.js:61-63 belong to a different route than /accuracy at :68).
    Sport and tier CAN be sliced client-side from /api/accuracy's sports map and
    /api/ledger/model's by_tier. TIME WINDOW CANNOT — window_days is fixed at 30
    inside getModelAggregate with no param and no stored series, so
    "accuracy over time" has no data source.

0.3 CLV CANNOT LEAD WITH A NUMBER. /api/ledger/model exposes the aggregate, and
    live it returns beat_close_pct = null and clv_distribution = null despite
    clv_sample 937. They are null BY DESIGN: ledgerService publishes them only
    when clvCaptureReliable() passes, and it does not — the capture is still the
    starved instrument the 07-28 repair improved but did not finish. The trap to
    avoid is exact: clv_beat/clv_sample = 34/937 = 3.6% is computable and would
    be WRONG, because the value is null due to instrument distrust, not a missing
    division. Publishing it would be the marketing fabrication this order most
    forbids. CLV can only lead with an honest absence.

0.2 The honest-record laws are ALREADY enforced at source: buckets return
    A pct:null (n=2), B 60% (512), C 57% (413), D pct:null, F pct:null — thin
    tiers already refuse to round. C genuinely sits below B, which is the
    unflattering truth and must be shown as-is.

0.4 CALIBRATION CURVE has no data source — clv_distribution is null and there is
    no claimed-vs-actual endpoint; the 07-26 calibration work was a one-off
    read-only measurement, never wired to a served surface.

BUILDABLE NOW: tier hit-rates by sport with existing hollows preserved,
client-side sport/tier filtering, the capped 3-call sample, and honest state copy
including a CLV not-yet-publishable panel that names the reliability guard.

NEEDS ITS OWN ORDER FIRST: CLV as a leading number (blocked on capture
reliability, not presentation), accuracy over time (needs a param or daily
series), the calibration curve (needs a claimed-vs-actual endpoint).

RECOMMENDS shipping tier-record-forward with an honest CLV building panel rather
than CLV-forward — CLV-forward with a null cannot lead, and with 3.6% would be a
lie. That preserves the premise's strongest claim (a real thin honest record
out-credibilizes a fake fat one) without inventing a number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 07:43:12 -04:00
builtbykev 7cf3892e76 docs: corrected gate recorded + cache-busting verification lesson
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 07:31:45 -04:00
builtbykev 7ddf159e4a docs: Build 1 settled/live gate recorded + live anonymous fingerprint
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 07:21:36 -04:00
builtbykev dbc1416485 docs: tier redesign spec recorded (gate discriminator exists; counter is display-only; base is 3 users)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 07:07:13 -04:00
builtbykev 4a4a3428d8 spec: tier redesign (Option 2, settled-free / live-paid) — design + build order
Report-first. Nothing built; no tier, price, gate or Stripe object changed.

REVIEW ZERO findings that shape the design:

0.2 The ladder is HALF-EXPRESSIBLE already — PRICE_MAP separates founder from
    standing objects, so lifetime grandfathering is native (a sub created against
    a founder price stays on it). BUT founder access is gated by CODE + EXPIRY
    (FOUNDER2026/VYNDR/BETONBLK/EARLYBIRD, expiry 2026-12-31), NOT by seat count:
    anyone with a code gets founder pricing at any seat number. A real
    Stripe-derived counter exists (/api/founders/count, live 0 of 100) but only
    DISPLAYS — and it is cached 300s, so it cannot enforce "slot 100 and 101
    differ permanently". Making the counter the gate, transactionally and
    uncached at checkout-session creation, is a real build.

0.3 The paid->free flip point already exists ON THE SERVED PAYLOAD: settlement
    writes ledger_entries.outcome + settled_at, and /api/snapshot already merges
    per-grade results — live WNBA returns 25 grades, 5 carrying
    outcome {result:'hit', actual:1}. So the gate discriminator (outcome != null)
    is present on the exact object to be gated; no new pipeline needed.

0.4 THE MIGRATION IS NOT WHAT THE ORDER ASSUMES: the users table holds 3 users,
    all free, created Jun 12-19, and ZERO paid. There is no warm mass base — the
    "founder launch to existing users" is a courtesy note to 3 people, and the
    launch's real audience is people who have not signed up yet.

DESIGN: free = full data aggregator + the COMPLETE settled record (letter,
reasoning, edge, outcome — browsable and filterable), which is the proof hook.
Analyst = tonight's live grades + reasoning + edge, unlimited. Desk = + alt
ladder, Kelly, portfolio, engine2. Reasoning/grade/edge are ONE paid unit while
live and become free together at resolution — which also converts today's
unenforced board-reasoning leak into a deliberate rule.

GATE: outcome == null => live => Analyst+; outcome != null => settled => free.
Filter whole grades server-side (not field-strips) so a live grade cannot leak
partially; never infer resolution from time or game status, only from a written
outcome; fail closed to LIVE so a settle failure withholds rather than exposes;
void/unrecoverable are terminal and therefore free.

BUILD ORDER: (1) the settled/live gate, (2) the free settled-record surface —
noted as arguably shipping WITH (1), since gating live grades without it leaves
free users no graded content at all, (3) Stripe ladder + transactional counter +
grandfather rule + retire the code gate, (4) the founder note to the 3,
(5) pricing visuals (already designed in the package).

CANNOT DETERMINE: whether the four Stripe price objects exist in the dashboard
(env not readable here) — flagged as a prerequisite for build 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 07:06:33 -04:00
builtbykev c6ef4cfb2b docs: tier structure recorded — free board uncapped, board reasoning ungated vs config intent
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 06:41:15 -04:00
builtbykev 844ab96f21 report: tier structure pull — the declared free-tier gate is unenforced on the board
Read-only. Nothing changed.

FREE TIER, EXACTLY:
  - Board /api/snapshot: NO count limit. The only gate is
    stripModelPrice(grades, tier) at routes/snapshot.js:106-107 — no slice, no
    volume branch. Live anonymous right now: MLB 5, WNBA 25 = the full board.
    The "3 scans/day" cap rations the SCAN path only.
  - Grade letter: fully visible on every tier (grade_visible: true). Anon also
    receives confidence, edge_pct and VYNDR's own projection.
  - Edge fields: correctly stripped. p_win/ev_pct/model_odds/value/takeable are
    ALL absent from the anonymous payload, with model_price_locked stamped so the
    card shows a lock teaser rather than an absent leg. This half works as designed.

THE HEADLINE — the two paths disagree on reasoning:
  - Scan REDACTS it: tierGating.js lockReasoning + lockKillConditions +
    tier_gated + upgrade hint, driven by free.reasoning_visible = false.
  - Board SERVES IT IN FULL: snapshotGating MODEL_FIELDS is
    [model_odds, p_win, ev_pct, value, takeable] — reasoning is not in the list.
    Verified live anonymously: full reasoning.summary plus a kill condition WITH
    its reason.

Intent: config/tiers.js declares free: { reasoning_visible: false } with the
comment "blurred — frontend renders tier-locked". One of the two paths does not
enforce the product's own declared line, so the evidence reads as oversight
rather than funnel — a funnel would be declared in config, not contradicted by
it. Flagged with the counterweight: board reasoning is good marketing and the
data layer is already free, so closing it is a monetization tightening (Kev's
call), not a fabrication fix.

FREE DATA IS A REAL AGGREGATOR, not just a limited graded view: schedule,
per-book lines, player stats, streaks, hot lists, team hubs, public record — all
public and uncapped (probed live).

PAID (config/tiers.js, checkout.js:4): analyst $14.99 / desk $44.99. Analyst is
unlimited reads; Desk differentiates on capability (alt ladder, Kelly, portfolio,
engine2). africa tier is defined but activation is blocked on a DB CHECK
constraint. api_access is false on every tier. book_odds/fair_odds deliberately
pass through on all tiers — the de-vigged fair number is the hook and is never
the paywall; only model_odds gates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 06:40:43 -04:00
builtbykev 43281bb885 report: D1-close Review Zero — mount not performed, three findings
Nothing changed: no mount, no row edit, no data threading. Docs only.

1. THE RATIONALE DOES NOT REACH THE ROW. StripProp carries stat/line/side/grade/
   gradedAt/delta/awaiting/outcome/movement/revisedFrom/book/bestBook/dead/
   history — no reasoning, no kill_conditions_triggered — and
   buildPlayerStripsFromProps never threads them. Mounting the hover needs a new
   field on the strip contract threaded through the slate adapter: additive, but
   a data-path change rather than a mount.

2. THE 0.3 PREMISE INVERTS — THE RATIONALE IS ALREADY PUBLIC. Verified live and
   anonymously against prod: /api/snapshot/wnba returns reasoning.summary with no
   locked flag plus kill_conditions_triggered. stripModelPrice removes
   model_odds/p_win/ev_pct/value/takeable but NOT reasoning. So the full model
   rationale already ships to every anonymous browser on the main board, while
   the same content IS tier-gated on the scan path (tierGating.js). Mounting the
   hover would leak nothing new, but would surface content that is currently
   shipped-but-unrendered, and the product gates it in one place while serving it
   openly in another. That is a monetization/consistency decision, so it is
   reported with three options rather than resolved unilaterally.

3. ROW-GRAMMAR IS LAW AND LOCKS StatStrip's SOURCE ORDER. rowGrammar.test.js
   asserts element order via src.indexOf on the component source; adding a
   rationale affordance or a team chip moves those offsets, so specs/ROW-GRAMMAR.md
   and the test must be amended in the same commit. That makes this spec-amending
   work needing its own slot decisions, not an additive mount.

Safely mountable with no blockers: reveal.js (wraps the row list, no StatStrip
internals, no new data, no grammar slot). teamChips needs a grammar slot;
rowRationale needs the data threading AND the gating decision AND a slot.

Recommends splitting D1-close into: mount reveal now; a ROW-GRAMMAR amendment
order for the chip + rationale slots; then the rationale mount once the gating
decision is made.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 06:25:05 -04:00
builtbykev a0501f99c0 docs: D1 finish recorded (rationale real-or-absent, reveal once-on-view, 10/80 chip coverage)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 06:18:42 -04:00
builtbykev e34e99c426 docs: D1-A recorded (glyph buckets, boundary channel completed, primitives)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 06:04:50 -04:00
builtbykev 49565b5f02 docs: design-vs-build gap audit recorded (61 items, 6 build waves)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 05:46:32 -04:00
builtbykev e257474cc8 report: design-vs-build gap audit — 61 items enumerated, 18 absent
Package specs/design-reference (Jul 22) audited against the CURRENT repo
(bf7c0a3, ~9 days later). No ~/vyndr_design exists; the in-repo copy is the
package. Every claim is a direct file/grep/count check, not the harness that
returned a silent false in Wave 3.

61 implementable items enumerated: BUILT-TO-SPEC 20, BUILT-BUT-DRIFTED 7,
PARTIAL 16, ABSENT 18 (+1 CANNOT DETERMINE: 19-screen mobile parity needs a
visual pass).

Largest single gap: the glyph library — 38 of 83 designed SVGs are wired (46%),
and the design implies 74 display archetypes against a 41-entry backend
registry, so the archetype system is roughly half the designed scope.

Drift found on surfaces built recently: the book comparison wired 07-29 renders
per-book lines but has NO crown, NO disagreement axis, NO SPLIT chip and NO
movement strip — a simpler version than the S2 design. The mobile tab bar has 5
tabs but not the designed READ-FAB. Calibration gating disagrees with the design
(our n>=20 vs designed N30).

Wave-2 reclassification: Newsletter DESIGN EXISTS (S5 The Report is fully
designed) — the earlier status pull was wrong to call it a design gap. Live
tracking and Slip reader remain genuinely design-missing.

Model linkages named: Price Triplet waits on the EV layer producing
p_win/ev_pct/model_odds; the S4 calibration curve waits on the n-threshold
decision plus accrued buckets, while the CLV chips can build on the repaired
instrument now.

Ordered build list in six dependency waves: self-contained first (glyphs,
primitives, boundary-channel blue), then scanner-nudge-gated, model-gated,
resolution-pipeline-gated (share-card masters cannot ship — the tail has no
generation step and no trigger), licensing-gated (book logos, push-to-book),
then the large surface builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 05:45:56 -04:00
builtbykev 91911cfb1c docs: Wave 3 recorded — /compare live-verified; resolution tail scoped
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 05:35:10 -04:00
builtbykev bf7c0a3c08 Wave 3: /compare built (real head-to-head); resolution tail scoped, not shipped
No grade, ledger or scoring change. Push scoring untouched.

REVIEW ZERO 0.3/0.4 — THE RESOLUTION TAIL DOES NOT FIRE. The resolver is
POST /api/grading/resolve (routes/grading.js:208), and its fanout at :356-371
covers webPush, telegram and discord — but:

  - share-card generation: SPEC'D-NOT-BUILT. Not in the fanout at all (grep
    shareCard in grading.js = 0). shareCards/renderer.js exists with ZERO
    callers, so the component is built but no step would ever invoke it.
  - push notifications: BUILT-NOT-FIRING. In the fanout but gated on
    webPush.configured() (VAPID). push_subscriptions = 0 rows and
    user_notifications = 0 rows — nothing ever subscribed or delivered.
  - Telegram result posts: BUILT-NOT-FIRING (gated on BOT_TOKEN + CHANNEL_ID).
  - Discord result posts: BUILT-NOT-FIRING (gated on webhookFor('results')).
  - recap (all-Final trigger): SPEC'D-NOT-BUILT. No recap file exists in src/.

AND THE WHOLE TAIL IS UNREACHABLE: nothing calls /api/grading/resolve — there is
no ESPN poller in the repo. The live settlement path is the scheduler's
settleAllOutcomes + settleAllLedgers, which fans out to opsNotify only (ops
alerts), with no user-facing output. So even the built channels have no trigger.

Per the order's own rule, ShareCard, /notifications, result posts and recap are
therefore ALL SCOPED, none shipped — no dead shells over a silent pipeline.

BUILT — /compare. Semantics (0.2): a same-market head-to-head, two players with
every row a measure BOTH sides are scored on, aligned via alignRows so the
numbers are comparable — deliberately not two disconnected graded props. Reads
the live /api/stats/player/:name?sport= aggregate. Honest-absent three ways: an
unresolved side reads NO DATA while the other still renders; a measure only one
side has renders a dash, never 0; if neither resolves the page refuses to
compare. NO VERDICT — it shows measures and says the reader draws the call.

Two pre-existing tests (vyndrPhaseE, vyndrParityQA) asserted the in-development
placeholder; both superseded rather than deleted — they now assert the stronger
properties against the real page (live fetch, no sample players, NO VERDICT,
NO DATA, "not a zero").

Floor: 316 suites / 3930 tests green (10 new), web build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 05:31:56 -04:00
builtbykev 831d09bdde docs: Wave 1 wiring recorded + honest fingerprint limitation (nav entries -> Chrome audit)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 05:01:33 -04:00
builtbykev ff7f5d8d2d Wave 1: wire /intelligence, /slip, /parlay + /marketplace honesty pass
Wiring + one copy pass. No grade, ledger, model or scoring change (diff empty
across intelligence/, ledgerService, outcomeService, gradeSlateService).

REVIEW ZERO — each surface proven with real data BEFORE wiring:
  0.1 /intelligence vs /system are NOT duplicates. System.dc.html is a
      multi-surface artboard (TERMINAL + INTELLIGENCE + WIRE sections), not the
      design for a distinct /system route; its INTELLIGENCE section is already
      realised as the live app/intelligence/page.tsx. No /system page exists and
      none should be built as a second copy — the prod 404 is correct.
  0.2 /intelligence renders live and gates SERVER-side, not by blur: the proxy
      requires auth and limits by tier (desk 50 signals / non-desk 8), and
      returns 401 to an anonymous caller (verified live). No leak.
  0.3 /slip parses a real DraftKings slip end to end: 3/3 legs,
      needs_review false, Aaron Judge total_bases over 1.5 @ -115. Honest limit
      recorded: parsers are layout-rigid, an unsupported layout yields ZERO legs
      rather than wrong ones (never-guess), so real-world OCR hit-rate across
      layouts is CANNOT DETERMINE until user slips arrive.
  0.4 /parlay direct route hits the real correlation builder on the same
      ParlayContext the drawer uses.
  0.5 /marketplace advertised four unbuilt things but made NO performance or
      profit claim, and its capture was already real (/api/waitlist upserts to a
      waitlist table). The gap was tense, not fabrication.

WIRED: Nav MORE gains Intelligence, Slip Reader and Marketplace; Parlay Lab
re-pointed from the drawer hash to /parlay (the drawer is unaffected —
ParlayPanel stays mounted with its floating badge).

GATING: /intelligence added to GATED_ROUTES because its feed 401s signed-out, so
an ungated link would land visitors on a permanently empty page. /parlay stays
OPEN deliberately — it is the free parlay funnel and gating it would be a
monetization regression.

/marketplace honesty pass: every item body now opens "Not built yet." /
"Not written yet." / "Not produced yet." with what is planned; the subhead states
it is not a purchase, not a pre-order and not a promise of a ship date; the
playbook item carries "No profit claim, no promised return". The capture stays
real — no fake button. Unit-locked.

Floor: 315 suites / 3920 tests green (12 new), web build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 04:51:42 -04:00
builtbykev 6328a5ce92 docs: boundary not written (no promotion); build triage waves recorded
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 01:50:31 -04:00
builtbykev e70c02aca0 report: incomplete-surface build triage; boundary tag NOT applied (no promotion exists)
Report-only. Nothing built, wired, tagged, or removed.

PART 1 — the recalibration boundary was NOT written. The build order asks to tag
grades pre/post edge-shading at "the true promotion timestamp"; there is no such
timestamp, and writing the marker would insert a fabricated model transition
into an append-only public record — the exact corruption the order exists to
prevent. Three independent production proofs:

  A. model_snapshots.code_sha — every sha that ran the pipeline in the last 5
     days is a documented commit from this session (f3bf300 currently live,
     then f310608, 9b5235c, b8ee216, afb56b1, 3592aba, 914a057), all stamped
     model_version engine1@2026-07-20. No promotion commit exists.
  B. Daily A-family share 07-24..07-30: 0.0, 0.0, 0.0, 0.0, 1.0, 1.6, 0.0 —
     flat at zero, no step change on any date. A 92.9% re-letter under shading
     would have driven the board to ~79-93% A overnight.
  C. efficiencyShading has zero production importers; HEAD d54eca0, clean tree.

Phase 2 delta: neither 92.9% nor 43.6% is attested in any measurement here, and
no re-lettering occurred at any scale, so partial-slate-vs-full-board cannot
explain a gap that does not exist. The only measured numbers, on all 1250 rows
(the full board): 97.4% would change, 79.8% up, 79.0% A-family, and 0 of 1250
rows actually shaded — the hypothetical re-letter would have come entirely from
an unapproved grading-basis switch, which is why the flip was refused.

Measurement integrity needs nothing new right now: model_version already
separates the S64 eras and takeable tags are complete (1245/1250). The boundary
becomes a hard prerequisite the day a promotion actually ships.

PART 2 — build triage for 14 incomplete surfaces, classified from code with each
one's real dependency and honest size, sequenced into five waves: pure wiring
(/intelligence, /slip, /parlay, /marketplace after a copy honesty pass);
design-only gaps (Live tracking, Slip reader, Newsletter artboards);
self-contained builds (/compare, ShareCard host, /notifications); model-gated
(price triplet MODEL leg and the calibration board both need p_win vs
fair_prob — building either on edge_pct would re-ship the retired 620% lie);
and sport-boundary/quota-gated (/soccer blocked on odds-api 0/500, /system,
Offseason).

Matrix corrections: Live tracking, Newsletter and Slip reader are built/live/
honest and marked incomplete only on the DESIGN column; /parlay is
drawer-reachable, not unreachable; /compare is already honest. No live surface
is showing fabricated data today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 01:49:55 -04:00
builtbykev d54eca0afc report: pre-audit status pull — no promotion occurred; matrix re-derived (15/26)
Verified three ways that nothing was promoted and no re-lettering happened:
HEAD is the no-flip commit with a clean tree, efficiencyShading is imported by
zero production files, and live grades carry 0.0% A-family (MLB {B:1,C:4},
WNBA {B:15,C:10}). Neither 92.9% nor 43.6% is a figure measured here - the
challenger's real numbers were 97.4% would-change / 79.8% up / 79.0% A, with
0 of 1250 rows actually shaded.

Takeable tags landed (1245/1250 tagged, takeable_floor on all, one floor -160;
the 5 untagged have no locked price). The model-version boundary is absent and
correctly so - there was no recalibration to mark. Not a pre-audit gap.

Matrix re-derived from live prod probes, importer counts and nav-link counts:
15 of 26 fully done. Book comparison RESOLVED (BookComparisonPanel routed to
the grade card). Grade card honesty improved by the edge_pct retirement.
ShareCard / MobileEdgeBoard / DemoScan still dead code. Seven live routes
remain orphaned with zero nav links; /system and /offseason are 404. No
LIVE-but-not-HONEST surface found.

Design is NOT complete: Live tracking, Slip reader and Newsletter are shipped
with no design artboard - design is the gap, not build. Offseason is the
reverse (designed, never built).

Chrome audit manifest assembled: 11 items with per-item session state, four
requiring an entitled Desk session that only Kev can drive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 01:40:04 -04:00
builtbykev 10aaaeb1da docs: promotion gate not passed (no flip); Order B edge_pct display retired
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 00:58:34 -04:00
builtbykev 0997334f8b Order B: retire edge_pct display. Promotion gate NOT passed — no flip.
THE PROMOTION WAS NOT PERFORMED. Champion grade path byte-identical (diff empty
across intelligence/, gradeSlateService, snapshotService). Projection, p_win and
the CLV instrument untouched.

REVIEW ZERO IS A GATE AND THREE OF FOUR PREREQUISITES FAIL:
  0.1 scores are ESTIMATED priors from the founding spec, not measured. The
      premise's cited values are not in the code either — the module holds
      nba:points .80 and mlb:total_bases .55; there is no NBA 0.72 and no WNBA
      score at all.
  0.2 VERSION-BOUNDARY TAG DID NOT LAND — config/modelEras.js has zero shading
      references. It was deliberately not applied twice (nothing had been
      promoted) and reported both times. The order's own rule says STOP.
  0.3 NO ROLLBACK FLAG EXISTS — zero occurrences of SHADING_ENABLED /
      EDGE_SHADING / shadingEnabled anywhere in src/.
  0.4 takeable tags DID land (migration 034, 1246/1254 rows). PASS.

AND THE APPROVED DELTA DOES NOT MATCH THE MEASURED ONE. Approved: 43.6% of
grades re-letter, efficient markets tighten and soft hold. Measured on all 1250
live rows: 97.4% change (1217), 79.8% move UP, 17.6% down, resulting in 79.0%
A-family (MLB 93.4%) against the champion's 0.2%. And rows_actually_shaded = 0
of 1250 — 96.5% of markets are unscored (f=1) and the one scored market present
is the anchor (f=1.0 by construction). The entire re-letter comes from switching
to edge-vs-fixed-bar grading, NOT from efficiency shading, which is inert on
this board. That is an unapproved grading-basis change riding along, which the
order's own "no new scaling changes riding along" guardrail forbids.

Flipping would re-letter 97.4% of an append-only public record, move 79.8% of
grades UP and mint A's on 79% of the board, on a letter whose measured
correlation with outcomes is r ~ 0.005 — the exact scenario the permanent
founder ruling forbids.

SHIPPED — ORDER B (independent of the promotion, and a live falsehood):
edge_pct display retired from GradeResultCard (confidence strip, EDGE stat cell
now honest-absent, alt-ladder rung) and SoccerGradeResult. DeskShowcase kept
(already honest). Computation and the board's signed-edge sort fallback SURVIVE
— deleting them would re-break the sort fixed on 2026-07-29; a test asserts all
three survive and the sort still orders agrees -> disagrees -> absent.

Fixed two build-breakers the retirement caused (orphaned edgeColor import,
orphaned edge_pct destructure; edge_pct stays on the props contract). Two
pre-existing tests superseded rather than deleted: they asserted the edge figure
is sign-coloured, and now assert the stronger property that no edge percentage
renders at all.

Floor: 314 suites / 3908 tests green (9 new), web build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 00:57:57 -04:00
builtbykev 1e9c808d99 docs: edge-shading challenger measured — flooding persists, input scale is the bug
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 00:39:51 -04:00
builtbykev c2c7abbb65 Edge-shading challenger: built + measured. Flooding NOT fixed — input scale is the bug
Challenger only. Champion grade byte-identical (verified by diff). Nothing
promoted, no live grade re-lettered, no ledger row deleted or re-settled.

BUILT src/services/challengers/efficiencyShading.js (measured-never-served):
  adjusted_edge = raw_edge * f(efficiency); grade = band(adjusted_edge) against
  ONE fixed bar (A+>=10, A>=5, B>=3, C>=1, D>=0, F<0) that never moves.
  f(e) = E_SOFTEST/e bounded to (0,1] — soft markets intact (never amplified),
  sharp shaded toward but not past zero, unscored -> f=1 and FLAGGED.
  A fence test asserts no production grade path imports it.
  Cross-market behaviour is unit-proven: the same raw 6% edge grades A in soft
  mlb:total_bases and B in sharp nba:points.

MEASURED on 1250 live ledger rows — Phase 2.5's answer is NO, the flooding is
not gone: challenger 79.0% A and 80.9% A/B (MLB 93.4% A) vs champion 0.2% A.

TWO findings explain why, and they are the point of the order:

1. The shading is a NO-OP on the live board: rows_actually_shaded = 0 of 1250.
   96.5% of rows are UNSCORED (f=1), and the one scored market present
   (mlb:total_bases) is the anchor so its f is 1.0 by construction.
   mlb:strikeouts and nba:points do not appear in the ledger at all (our
   basketball is wnba, not nba). Challenger vs baseline: 0 rows changed.

2. Placement was never the bug — the INPUT SCALE is. Against a fixed 5% bar the
   RAW edge already clears A on 100% of MLB doubles, 89.6% of hits, before any
   shading. MLB median raw edge is 60%, twelve times the bar. Decisive test:
   apply the sharpest score in the spec (f=0.647) to EVERY row — the maximum
   the design permits — and 75.8% still clear A (MLB 91.7%). Since f is bounded
   <= 1, no achievable shading can close a 12x overshoot. Moving the multiply
   from the threshold to the edge does not change the outcome.

This is edge_pct behaving as the 2026-07-29 diagnosis described: a price-free
(proj-line)/line gap whose scale is a function of line size. It is not a
betting edge, so no fixed betting-edge bar is meaningful against it.

2.6 efficient-market over-suppression: CANNOT DETERMINE — zero live rows are
shaded, so there is no efficient market in the data to over-suppress.

Phase 3: takeable tagging was completed in the previous order (migration 034,
1246/1254 rows) and is not repeated. The model-version boundary is again NOT
applied: nothing promoted, so no boundary exists.

Unblocking needs the input replaced, not the multiply moved: p_win vs
fair_prob (both already computed) instead of edge_pct, plus scores FIT from our
own record for the markets we actually grade.

Floor: 313 suites / 3899 tests green (9 new), web build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 00:39:13 -04:00
builtbykev 896e6e1a00 docs: takeable tagging shipped, efficiency challenger blocked (board + state)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 00:20:18 -04:00
builtbykev 2bfaeff572 Ledger takeable tagging (deferred C2); efficiency challenger BLOCKED
Champion grade UNCHANGED. Push scoring untouched. Additive tags only — nothing
deleted, nothing re-settled.

PART A — THE EFFICIENCY CHALLENGER: BLOCKED, NOT BUILT.
Review Zero came back ABSENT on all three inputs:
  0.1 efficiency scores DO NOT EXIST (zero occurrences of market_efficiency /
      marketEfficiency / efficiency_score in src/ or web/src/).
  0.2 base thresholds DO NOT EXIST (engine1.js has zero `edge` references — the
      grade is not an edge-vs-threshold comparison; grade_thresholds.json holds
      PROBABILITY bands).
  0.3 the +/-0.05 additive efficiency nudge DOES NOT EXIST. The only 0.05s on
      the grade path are featureCache.teammate_absence_bump, a bvp_advantage
      cutoff, and p*0.9+0.05 inside probabilityEstimator (the 0.5*0.1 term of
      the shrink-toward-0.5). There is no additive scaling to replace.

So a challenger differing from the champion in EXACTLY ONE thing cannot be
constructed: there is no additive scaling to swap, no base threshold to
multiply, and engine1.js has zero `sport` references so market cannot reach the
grade. A threshold must exist first — that is R1 of
specs/full-output-grade-mapping.md, an explicitly held separate order. Shipping
R1+R4 together would make the Phase-3 delta report misleading: the re-letter
would be driven mostly by switching to probability grading while being
presented as the efficiency fix.

0.4 coverage: the spec names 5 scores; the live ledger has 11 markets and only
MLB total_bases maps to one. 9 of 11 have no score, so "all scored markets"
cannot be satisfied without inventing 9 numbers.

PART B — LEDGER TAKEABLE TAGGING: BUILT (the deferred C2).
New src/config/takeableStandard.js: floor on the minus side, UNCAPPED plus.
Deliberately NOT valueEngine.isTakeable (the -160..+200 PROMOTION band) — a
+400 prop is not promotable but IS takeable; a test asserts the two diverge on
the plus side and agree at the floor so they can never quietly merge. Absent
price returns null, never false (Number(null) === 0 would tag a missing price
takeable). The floor is POLICY not derived (C1 could not derive one) and is
labelled so; each row records takeable_floor so a re-derivation can re-tag.

Migration 034 (applied + tracked): ledger_entries.takeable boolean +
takeable_floor numeric, nullable, partial index. Forward tagging in
ledgerService at row build; backfill in one statement.
Result: 1254 rows, 1246 tagged (781 takeable / 465 below floor), 8 NULL with
null_despite_price = 0 (the NULLs are genuinely priceless rows). Settled 1163
and graded 1254 unchanged.

PART C — the model-version boundary tag is DELIBERATELY NOT APPLIED: no scaling
change shipped, so no boundary exists, and stamping one would mark a model
transition that never happened. modelEras.js is its home when a real one lands.

Floor: 312 suites / 3890 tests green (8 new), web build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-31 00:19:13 -04:00
builtbykev a6afac43cc report: market-efficiency scaling check — FLAT, mechanism structurally absent
Report-only. No threshold, grade, or efficiency value changed.

VERDICT: FLAT. marketEfficiency.js does not exist (zero occurrences of
market_efficiency / efficiency_score in src/ or web/src/). The spec's
0.85/0.60/0.55 values appear in grade_thresholds.json only as PROBABILITY
BANDS - a coincidental numeric overlap, not efficiency scores.

The base edge thresholds (MLB A:5%, NBA A:7%) do not exist either: engine1.js
has zero `edge` references, so the live grade is not an edge-vs-threshold
comparison at all. The specced rule threshold = base x efficiency has no host.

DISPOSITIVE: engine1.js contains ZERO `sport` references. computeFactors
receives no sport or market, so per-market OR per-sport scaling is structurally
impossible in the live grader - not merely unwired.

Phase 2: the matched-edge test is confounded (edge is not the grading input -
the same market emits both B and C at one edge). The aggregate that
discriminates: mean grade index wnba points 4.71 at mean edge 10.2 vs mlb hits
4.58 at 69.5 vs mlb total_bases 4.32 at 84.9 - the efficient market earns the
highest grades on one-eighth the edge, the opposite of spec.

PREMISE CORRECTION (measured): this order's opening claim that full-output and
collapsed grades "agree 100%" does not hold - on 512 rows carrying both they
agree 17.8%, with 33.8% differing by 3+ tiers. The prior discrimination result
stands (champion r=0.0050 null vs probability r=0.1313; MLB 0.0686 n.s. vs
0.2356 p~0.0004). Repo unchanged between orders. The collapse was not a
phantom and the re-adjudication list stays open.

Scope: flat thresholds are a grade-CALIBRATION gap only - the projection and
the CLV edge (which measured p_win, never the letter) are untouched, so this is
not a third shadow-model alarm. But the fix is NOT independently bounded: with
no threshold step to multiply, efficiency scaling presupposes probability
grading. It is rule R4 of specs/full-output-grade-mapping.md and belongs to
that MLB-first challenger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-30 23:56:48 -04:00
builtbykev 708f0fde5c report: full-output grade mapping spec + collapse cost measured
Report-only. Nothing built, reconnected, or promoted.

Premise corrected again: the three-layer engine is BUILT but NOT WIRED and NOT
DEPLOYED (0 python refs in every grade-path file, 0 python in Dockerfile; there
is no engine1Adapter). So no posterior/CI/similarity prior exists to inventory
or diff. Measured against the collapse that actually exists instead.

THREE collapses, not one: (A) estimateProbability's components discarded at
analyzeViaEngine1:521-524; (B) THE SEVERE ONE - p_win never reaches the grade
at all (engine1.js has zero probability references), so the probability is
excluded from grading rather than collapsed into it; (C) grade_thresholds.json
(probability->grade) read backwards to manufacture confidence.
Market-efficiency scaling is never computed - a gap, not a collapse.

MEASURED on 354 settled rows carrying the served letter and the locked pre-game
p_win (forward, not lookahead). Grade->outcome point-biserial r: champion letter
0.0050 (p~0.93, null) vs probability letter 0.1313 (p~0.013). Per sport: MLB
champ 0.0686 n.s. vs prob 0.2356 (p~0.0004); WNBA champ -0.0986 vs prob -0.1258
- BOTH INVERSE. The served letter is inverted between its only two populated
tiers (B 52.4% n=168 vs C 56.9% n=174).

Verdict: costly on MLB, and un-collapsing does NOT help WNBA -> the challenger
must be MLB-FIRST. Five falsifiable mapping rules specced, incl. R2
(uncertainty grades down) stated explicitly and droppable if it fails.

Hard requirement on the next order: persist per-row n, SE and pre-adjustment p,
or R2/R4 can never be adjudicated (not stored today).

Re-adjudication list flagged incl. proj-v1.1's NOT PROVEN verdict (judged
against the collapsed champion, so not final) and ROI-by-grade (with B/C
inverted, the MLB-C +4.57% segment is likely an artifact of a meaningless
letter).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-30 23:47:11 -04:00
builtbykev dd98b0b614 report: model architecture recovery map — the live grade uses 0 of 3 specced layers
Archaeology only; nothing built, reconnected, or promoted.

The champion is two DISCONNECTED estimates: the letter is engine1's additive
factor index (zero references to p_win or any probability in engine1.js), and
p_win is probabilityEstimator's frequencyOver + 5 heuristic layers, computed
after and merely attached. The live grade path never calls the Python service.

The Python three-layer engine is NOT DEPLOYED — no python/pip in the
Dockerfile; app.js only health-checks it. So Layers 1-2 never shipped.
Layer 3 is wired BACKWARDS: grade_thresholds.json maps PROBABILITY->GRADE and
the live JS reads it in reverse to manufacture confidence from an
already-chosen letter. Per-sport market-efficiency scaling is specced-absent.

Consequence stated plainly: every metric audited to date is on the shadow
model, not the specced engine, which has never been measured.

Sport boundary TESTED not asserted: a new sport on the live path is a ~10-file
core edit with four documented silent-failure modes. Per-sport records DO
exist (sports.mlb n=526/62% vs pooled overall n=937/58%, each n>=20 gated),
but /api/accuracy ignores ?sport= and the pooled overall would absorb a new
sport. Park x weather confirmed challenger-only; xwOBA and leash absent.

Recovery map is dependency-ordered with MLB as the reference module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-30 23:16:34 -04:00
builtbykev f3bf300b36 report: C1 takeable-floor derivation — CANNOT DERIVE (every bucket CI spans zero)
MLB decided overs n=296, 100% with locked_odds. ROI by locked-price bucket
shows every 95% CI containing zero; the curve is NON-MONOTONE and runs opposite
to the premise (deepest buckets positive, the -111..-160 middle most negative);
and price bucket is confounded with market (+200up = doubles/HR longshots).
Rows needed per bucket to resolve a 5-pt edge: 661-2285 vs actual 8-71 (~187
days for one bucket at current accrual). The inherited -160 is neither
confirmed nor refuted. The no-ceiling call is not supported by this data either
(+200up is the worst bucket) though it is not refuted - it stays a design
choice, not a data-backed one.

Recommends C2 proceed with -160 as an explicitly-labelled POLICY floor plus a
re-derivation trigger (any negative bucket n>=300, or end of MLB regular
season; adopt a derived floor only when a bucket CI excludes zero). Enumerates
all 9 takeable sites, incl. the live drift hazard (backend env-tunable,
frontend hardcoded) and the user-visible band copy in PriceTriplet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-30 16:05:09 -04:00
builtbykev f310608ca4 docs: top-graded selector fingerprinted — strip-after-rank boundary proven in prod
The anonymous live order (Brionna Jones edge 29.4 ahead of Rhyne Howard edge
42.9) is only explicable by server-side p_win ranking (.90 vs .745, both
takeable) while the payload carries no paid fields — the free caller got the
paid RANKING without the paid SIGNAL, on live data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-29 22:08:15 -04:00
builtbykev 72a14dc4cd 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
2026-07-29 22:05:30 -04:00
builtbykev 69feab4d25 docs: grade-board sort fingerprinted (deploy boundary captured; ladder induced both directions)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-29 21:18:25 -04:00
builtbykev b85b351993 Grade-board sort: signed signal, takeable-gated p_win, missing sorts LAST
Display ORDERING only. No grade, ledger row, lock_line, scoring, or edge_pct
scale/display change. Push scoring untouched.

Two defects removed from selectTopGrades (wrong at ANY scale, independent of
edge_pct's separate retirement):
  1. edge: Math.abs(numOr(g.edge, -Infinity)) — abs() on an already-
     direction-signed value ranked the model's strongest DISAGREEMENTS level
     with its strongest agreements (177 public ledger rows carry a negative
     edge; positive = the model AGREES with the graded side).
  2. Math.abs(-Infinity) === Infinity, so a row with NO edge sorted FIRST —
     absent data presented as the top pick (the Number(null) class).

New key: grade -> confidence -> takeable-gated p_win (nulls LAST) -> SIGNED
edge (nulls LAST) -> input order. Scales are never mixed in one comparator.
Takeable band = web valueState.isTakeable, asserted byte-equal to the hero's
config/valueEngine.isTakeable (-160..+200) incl. strict-null.

Alt-line ladder (analyzeViaEngine1:506) no longer sorts by edge_pct: ordered
highest-p_win-first derived analytically at zero added compute — P(stat >= k)
is monotone non-increasing in k, so p_win-desc is line-ASC for an over and
line-DESC for an under. base stays marked; no consumer depends on
alt_lines[0]; deskShowcaseService.rungsOf already re-sorted by line.

THREE PREMISE BREAKS found report-first, before code:
  - /api/props/top-graded 404s in prod (absent from src/) so the dashboard
    board renders receipts/empty — the edge sort orders nothing there today.
    The prior order's "97.3% of rows tie" was a LEDGER measurement wrongly
    extrapolated to that board. Fix is correct-in-itself and lands when the
    feed is restored.
  - p_win cannot be a client-side key for all tiers: snapshotGating strips it
    for unentitled tiers ("shipping p_win is shipping the model price").
    Verified live: prod /api/snapshot carries p_win on 0/8 MLB, 0/25 WNBA.
  - Ladder rungs carry no per-rung price, so the takeable gate is inapplicable.

Verified on real data, both sports, both paths: unentitled — WNBA (n=25)
ordering CHANGED, MLB (n=8) unchanged, signed edge non-increasing in every
(grade,confidence) tie group (20 pairs, 0 violations); entitled — 40 real
ledger rows with p_win+locked_odds, p_win-descending, untakeable chalk NOT
promoted (Trea Turner .757 @-275 does not beat Rhyne Howard .745 @-120)
(36 pairs, 0 violations).

Hero consistency, stated honestly: same signal + same gate, different
precedence BY CONTRACT (board = grade-tier-first "top GRADES"; hero =
p_win-first "top read"). Identical within the leading tier (verified); across
tiers the board may lead with an A the hero doesn't pick. Not a contradiction.

Floor: 310 suites / 3864 tests green, web build exit 0. Dashboard + Desk
visuals are auth/feed-gated -> tagged for the Chrome audit, no visual faked.

Held: edge_pct rescale/display retirement (Order B); building the missing
/api/props/top-graded selector; exposing p_win to unentitled tiers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-07-29 21:13:51 -04:00
builtbykev 9b5235cf99 docs: hero ranking fix verified (new code serving — max-p_win takeable, chalk excluded)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-29 04:53:15 -04:00
builtbykev e7ec501054 docs: book-comparison verify note (live-card screenshot auth-blocked; data feed + tests verify)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-29 04:13:47 -04:00
builtbykev 36014c7c30 docs: Book Comparison wired to the prop card (matrix row 18 DONE, 16/26)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-29 04:10:58 -04:00
builtbykev 3a05447f77 docs: lock-line persistence unblocks the staleness audit (migration 033, lock_lines)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-29 02:49:02 -04:00
builtbykev 37261260d1 report: over-side skew audit — champion over-CLV SURVIVES baseline (promising, not confirmed)
Read-only. Takeable MLB overs n=62. Mechanical baseline (no-edge) CLV +1.51pt
(n=20); high-edge +8.64pt (n=37); difference +7.14pt = real edge. Champion
p_win->CLV partial r=0.375 (SIG p~0.003) survives price control. Skew one-sided
(unders -7, over baseline +1.5). De-vig clean (same-book pairing, analyzeViaEngine1:539);
close well-defined (DK/MGM r=0.92). Greenlights building the takeable-edge grade
ON THE CHAMPION, not proj-v1.1. Flagged promising-not-confirmed: thin n, lock-time
multi-book staleness check BLOCKED (not retained), pinnacle ref n=8. No fix, no
promotion — diagnosis only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-29 02:28:21 -04:00