215 Commits

Author SHA1 Message Date
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
builtbykev 59b77cdef2 report: proj-v1.1 takeable-edge proof — NOT PROVEN (n=45 MLB overs)
Read-only proof. N-gate passed (overlap 45). proj-v1.1 edge-CLV partial
correlation controlling for price = 0.245 (n.s.); ~half the raw 0.455 is the
shared -fair_prob_lock term (mechanical). Champion out-predicts proj on the
same rows (champ partial-CLV 0.380 sig; champ-edge->hit 0.25 vs 0.12). Unders
contaminated (CLV -9.3); WNBA proj-v1.1 doesn't run. Promotion HELD; the
under-audit is moot since proj loses to the champion first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-29 00:47:56 -04:00
builtbykev b8ee216c62 docs: CLV instrument repair + the straight finding (unders lag the close)
closing_prob 59 -> 406 (MLB 248, WNBA 158). Root cause was attachClosingProb's
truncated read + write-once market_unavailable, not capture or the join. CLV
measured: MLB unders lag the close (mean -9.1 prob-pts, 74% lose), MLB overs
+2.0, WNBA flat -> the +4.57% MLB-C and over/under asymmetry are substantially
stale-line artifacts. Unblocks the proj-v1.1 proof order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-28 19:04:50 -04:00
builtbykev afb56b144b NexaPay purge: VYNDR is Stripe-only — remove all NexaPay traces
NexaPay was cross-project contamination (from another venture) — never a real
VYNDR payment path. Purged; Stripe path untouched.

Removed:
- web/src/services/nexapay.ts (createPaymentLink/getTransaction/HMAC verify)
- web/src/app/api/webhook/nexapay/route.ts (the only importer; Next-registered,
  reachable — now gone)
- NexaPay comments in email.ts + checkout/route.ts
- Active NexaPay entries in docs/SYSTEM-MANIFEST.md (route list, NEXAPAY_* env
  table, service row) + stale claim in wiring-data-train.md
- sw.js precache entry for the deleted webhook chunk

Verified: ZERO NexaPay in code (web/src, src, tests). Full suite 3833 green
(count unchanged — nothing depended on it, confirming it was dead). Web build
exit 0. sw.js parses clean. Stripe checkout untouched (Next→Express→Stripe).

FLAGGED FOR KEV (a repo delete cannot close these):
- Coolify env: remove NEXAPAY_API_KEY / NEXAPAY_WEBHOOK_SECRET / NEXAPAY_API_URL
- Revoke the NexaPay API key + webhook secret at NexaPay's dashboard; de-register
  the webhook if an account was ever configured
- DB column user_profiles.nexapay_customer_id is orphaned (no reader/writer) —
  drop via a follow-up migration (migration 011 left as history)

Cross-project check: ZERO Noctem-Supabase refs; VYNDR references only its own
Supabase (zmdnczhtdxcddsxzttub). NexaPay was the sole contamination found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-27 16:51:59 -04:00
builtbykev 3592aba8d5 docs: log honesty pass in completion matrix + STATE (rows 19/20/24 HONEST fixed)
Records the six live fabrications removed/hidden, keeps media/newsletter/WIRE on
the board as real work, logs the news/line-movement signal as a future model
input, and logs the known honesty gaps (hit-rate-without-ROI, CLV starved).
Honest state: "no KNOWN live fabrications," not "provably none."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-27 06:07:21 -04:00
builtbykev be6b4a2849 inventory: VYNDR-COMPLETION-MATRIX (5-part DONE map of every surface + model)
Read-only inventory order — no code built/wired/fixed/deployed. Maps every
user-facing surface and model component against DESIGNED·BUILT·WIRED·LIVE·HONEST,
re-derived from repo b0a51c8 + prod + design bundle (not STATE.md narrative).
15/26 surfaces fully done. Names the graveyard (BookComparison, ShareCard,
proj_ladder, arch-v1/contact-v1 ledger-only, /intelligence orphan) and the live
honesty gaps (/compare hardcoded, FAQ NexaPay/Brier, MobileEdgeBoard placeholder,
EV fields NULL on served grades). STATE.md now points to the matrix as canonical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-27 05:12:59 -04:00
builtbykev e81c9b8c51 Book Comparison Phase 1-3(backend): fenced per-book store + honest gated crown
Per-book prices existed only transiently (odds cache, ~1h, raw names, grade-path
input); every grade-path persistence point collapses to one book. The
/api/books feature was built+mounted but non-functional (fed FLAT rows to a
GROUPED comparator -> always empty).

Phase 1: bookPriceStore captures per-book prices from `props` BEFORE dedupeProps,
keyed nameKey|stat, into bookprices:{sport} (SNAP_TTL) in snapshotService. Fenced:
reads props, writes its own key, read by nothing on the grade path. Grade proven
byte-identical (test + no-grade-path-reference grep test).

Phase 2: scripts/measure-book-spread.js reports same-line best-vs-worst spread
(cents + implied-prob pts), per sport, never pooled. Pre-registered crown
threshold: median >=8c OR >=2pp. Runs post-deploy on real data.

Phase 3 (backend): compareProp is honest-absent (single-book/flat -> no crown)
and the crown is gated (BOOK_CROWN_ENABLED, default OFF until Phase 2 clears).
/api/books repointed to the snapshot-locked store (fallback odds cache),
nameKey-matched; `source` field is the deploy fingerprint.

HELD unchanged: dedupeProps, snapshot dedup, selector, grade, champion,
challengers, ranking, edge_pct/ev_pct. UI routing of BookComparison + crown
treatment deferred to post-measurement (gated on Phase 2). Full suite 3834 green,
web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
2026-07-27 00:45:01 -04:00
builtbykev d879a10dd3 Design bundle (2): current source of truth — Scanner States + blue-boundary law
Reference material only, ZERO product code. Adds Vyndr Scanner States.dc.html
(S6/S7 spec) + HANDOFF Session 3 blue-boundary-channel law (#8FB2DE = the honesty
channel: priced-out / no-market / line-not-priced). This is the diff baseline for
the design-migration arc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-22 19:10:54 -04:00
builtbykev 9a91837f63 STATE: Session 80 — S7 nudge freshness completed; premise corrected report-first
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-22 12:20:49 -04:00
builtbykev 33d72e38f9 STATE: Session 79 — read-card no-market empty state + priced-line surfacer, live
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-22 11:24:39 -04:00
builtbykev 535a7b70ea STATE: Session 77 — dormant adjusters wired; env non-null proven, ledger row pending
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 21:41:21 -04:00
builtbykev 2adf192d98 STATE: Session 76 — platoon regressed; plumbing gap now four orders old
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 01:58:04 -04:00
builtbykev 9086951852 STATE: Session 75 — weather modulation composed onto park; venue gap still open
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 01:43:41 -04:00
builtbykev 5b4af67d93 STATE: Session 74 — public park base pluggable, game-context capture live
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 01:23:28 -04:00
builtbykev 63e4bce858 STATE: Session 73 — park factors derived + composable; pipeline gap documented
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 01:06:49 -04:00
builtbykev beac1816d2 STATE: Session 72 — Tier-1 live, Tier-2 harness forward-accrual (no historical OOS)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 00:30:46 -04:00
builtbykev 56fee267c9 STATE: Session 71 — challenger deployed; verification gap documented honestly
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-21 00:04:24 -04:00
builtbykev 80f7100fc3 STATE: Session 70 — measurement instrument wired; the baseline accrues forward
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 23:12:27 -04:00
builtbykev 063e9fb3f7 STATE: Session 69 — multi-axis archetypes live, FLEX fabrication deleted
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 22:40:26 -04:00
builtbykev 27aabd078f STATE: Session 68 — Layer 1 mechanism data live (1,354 rows, 100% join)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 21:55:30 -04:00
builtbykev 0264486bf9 STATE: Session 67 — price layer gated at two doors, read card wired
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 19:59:33 -04:00
builtbykev 16697a4b90 STATE: Session 66 — price-layer tokens + the triplet, with induction proof
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 18:33:02 -04:00
builtbykev f549422758 Design bundle: Price Triplet (ACT 01) + Offseason/Intelligence + 83 glyphs
Drops the updated claude.ai/design bundle into specs/design-reference/ as the
build reference. New since the Jul-18 export: Vyndr Price Triplet.dc.html (the
reality-corrected ACT 01 — five honesty states incl. EDGE-NOT-TAKEABLE, and the
projection-then-price read-card hierarchy), Vyndr Offseason.dc.html,
Vyndr Intelligence.dc.html, PNG export masters, and 83 archetype glyphs
(74 display + 9 classifier-legacy).

Reference material only. No product code touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 17:53:23 -04:00
builtbykev 38ee83d0be STATE: Session 65 — CLV un-claim + FORM un-fabricate, with live fingerprint
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
2026-07-20 17:09:01 -04:00
builtbykev f5156dd16d Un-claim CLV on the public profile; un-fabricate player-page FORM
Two Truth-Law fixes found by auditing the product logged-out.

FIX 1 — /u/[handle] claimed a "CLV-verified record" with "closing-line
value included" while ZERO closing-line value renders there. Verified
live: GET /api/profiles/vyndr returns beat_close_pct null (gated behind
CLV_CAPTURE_RELIABLE, unset while C4 is open). Eight instances found —
two of them (the OG + portrait "CLV-VERIFIED RECORD · 30D" eyebrows)
only by the post-removal residual sweep; two more printed the claim in
exactly the no-record branch.

Copy now describes what the page shows. The gated CLV-VERIFIED badge and
the BEAT CLOSE figure are removed from the public profile, OG card and
portrait card. DISPLAY ONLY: beat_close_pct, clvCaptureReliable() and
the whole CLV data path are untouched, and the earned directional badge
stays Analyst+Desk. The claim returns when CLV genuinely renders here.

Also fixes the doubled "· VYNDR · VYNDR" title (layout's '%s · VYNDR'
template already supplies the suffix); verified on composed output by
serving the build and reading the real HTML, not on source.

FIX 2 — the player page's FORM was `70 + 4 × (count of tonight's graded
props)`. Nothing on the HTTP path ever sets stats.form, so that fallback
WAS the live number: Josh Bell's "74" is 70 + 4×1 prop, confirmed
against his live payload. MATCHUP was gradeFromForm(that number), with a
hardcoded 'B' on the no-archetype branch — both fabricated letters with
no opponent input on the path. Systemic: buildIntel is the unconditional
path for every player and sport.

FORM and MATCHUP now render "—" (kind 'plain', so no bar width or colour
is computed off a null). gradeFromForm is deleted and the prop count is
no longer passed into buildIntel. computeFormScore's hardcoded 75 now
returns undefined. Induced across MLB/NBA/WNBA: all render cleanly, and
real values (USAGE 3.6 AB/G, REST B2B) still render.

Neither form value feeds the grade — engine1 reads raw l5_avg/l20_avg
against the line and never a form key; buildIntelFields decorates the
already-graded object. Grade inputs are byte-identical.

Held (needs a per-sport headline-stat design call): a real player-level
form metric + label disambiguation.

Tests 3491 passed / 289 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
2026-07-20 17:05:04 -04:00
builtbykev b1ed675500 STATE: MLB opp_rank_stat live — consumption proven, coverage 8/14, factor inert this slate 2026-07-20 12:53:39 -04:00
builtbykev 2321267346 STATE: harness armed + closing capture started; reverted my own non-close writes 2026-07-20 12:09:12 -04:00
builtbykev f73fb64a43 STATE: heal executed — 64 re-voided, 25+200 quarantined, scopes separated 2026-07-20 05:45:27 -04:00
builtbykev b76e35f575 STATE: grading date binding fixed + blast radius quantified 2026-07-20 03:52:34 -04:00
builtbykev 8e17de4010 STATE: game_date root fixed, 64 voids reverted, grading blast radius flagged 2026-07-20 03:27:42 -04:00
builtbykev e46c88e364 STATE: retention clock proven ticking via induced cron entrypoint 2026-07-20 02:06:43 -04:00
builtbykev c2f6041406 STATE: off-box round trip CLOSED — 645 restored from the box copy
Phase 3 complete. The insurance chain is proven end to end rather than
assumed: dump -> validated -> pushed off-box -> verified on the box ->
pulled back down -> rebuilt into a live database.

Pulled vyndr-20260720-051158.dump FROM the Storage Box (not the local
copy) with the in-session key through the pinned host key, never
bypassing StrictHostKeyChecking. Restored into scratch Postgres 17: 715
archive objects, 42 public tables, ledger_entries with all 27 columns and
real spot-checked rows.

ASSERTION PASSED: ledger_entries restored 645 == live 645 (target >= 645).
model_snapshots restored 100/100, so the retention store shipped yesterday
is covered by backups from day one.

Records the operational gotcha the restore surfaced: the dump is written
by pg_dump 17 (Supabase 17.6) and pg_restore 16 CANNOT read it —
'unsupported version (1.16) in file header'. The first attempt failed on
exactly this. Any DR runbook must use PG17+ tooling. Restoring into
vanilla Postgres also logs 12 ignored errors (Supabase roles/extensions
absent locally) which are harmless.

Scratch DB torn down, pulled copy deleted, both dumps still on the box,
nightly cron untouched. No private key material echoed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-20 01:28:29 -04:00
builtbykev 491e636b7f STATE: off-box backup working + verified on the box
Records off-box as WORKING with the root cause (key was only in Hetzner's
project store, never in the box's authorized_keys — the box previously
offered an EMPTY auth list) and the proof: offbox_ok:true, and the file
independently VERIFIED on the box via rsync --list-only through the pinned
host key (vyndr-20260720-051158.dump, 833,917 bytes, 05:12:28 UTC,
byte-identical to the local dump).

Env truth captured from the run output: key is correctly base64-decoded,
destination has no leading-slash bug.

Hardening recorded: host key statically pinned (accept-new gone, missing
pin refuses the push), remote dir guaranteed, failed required push now
pages at urgent with offbox_ok:false while the exit code still tracks
on-box durability.

Flags the ONE outstanding acceptance item honestly: the round-trip restore
is NOT done, because the dev box cannot authenticate to the Storage Box
(the authorized key is Kev's, not the in-session keypair) and the
container has no Postgres server. Lists both unblocks and the assertion
target (>= 645).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-20 01:16:30 -04:00
builtbykev 7e150f9342 STATE.md: pin header + point to the orientation block 2026-07-20 00:10:15 -04:00
builtbykev 7ea0af2081 STATE.md: CURRENT STATUS + OPEN ITEMS orientation block
Top-of-file ground-truth block for orienting a fresh session.

Records what shipped tonight (probability layer revived 32/32, value
engine arc 1, grade-range work, backup durable on-box at 643/643,
model_snapshots retention live with 100 rows incl 36 refusals, ESPN
parser fix) WITH two honest qualifiers rather than a clean win: A still
does not emit in production so the A-RATED marketing hold stands, and EV
is overconfident (+62%/+61%/+56.9% captured, p_win clamps at 0.95) while
hero v2 already ranks on it.

OFF-BOX BACKUP recorded as NOT WORKING and deferred — never succeeded
once, every dump lives only on the Hetzner volume. Documents the two real
blockers fixed (missing base64 decode; container had rsync but no ssh
binary) and the remaining one: the Storage Box offers an EMPTY auth-method
list, which is an account refusing all auth rather than a wrong key.
Explicitly marks as UNVERIFIED that no Chrome/UI diagnostic was run — no
data on the SSH-support toggle, external reachability, project-vs-box key
scope, or any Hetzner outage — and lists those as untested hypotheses in
likelihood order rather than implying they were checked. The full
scratch-Postgres restore proof is recorded as still OWED.

Open items with status: settlement zero-pushes bug, ~28 props/day
unsettled, permanent model-version contamination (with the hard-cutoff
rule), A-grade unreachable, EV overconfidence, edge_pct broken scale, C4
CLV, consistency CV stopgap. Plus the three live credentials to rotate:
Storage Box password, VYNDR_INTERNAL_KEY (pasted in a transcript), and the
GitHub PAT still in .git/config.

Next queued: backtest harness (needs ~2wk history, currently holds one
night), settlement audit, opponent-strength sourcing (MLB solved via
statsapi pitching splits; NBA/WNBA open) behind the source-adapter
pattern, then the metrics engine gated on the harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-20 00:10:02 -04:00
builtbykev 971f641d12 STATE: retention live + settlement findings + version contamination
Records model_snapshots as LIVE and verified capturing (100 rows over 2
cycles: MLB 14 graded/36 refused, WNBA 50 graded; features, grade_11,
p_win, ev_pct on 100% of graded rows).

First-ever refusal visibility: juiced_no_edge 18, rare_event_over_below_
line 13, insufficient_data 5 — the MLB gate refused 36 of 50 sides (72%),
now measurable for the first time.

Flags EV as OVERCONFIDENT and not fit to surface: first captured values
include +62.1%/+61%/+56.9%, which real markets do not offer. Cause is the
estimator clamping p_win at PROB_CEIL 0.95 off ~10 games. Hero v2 already
ranks on ev_pct, so it will pick the MOST overconfident read — calibration
must gate this before EV drives anything user-facing.

Logs the two settlement-correctness findings Kev asked to track (zero
pushes across 470 settled rows; ~28 props/day never settling) and the
ledger model-version contamination, with the rule that any backtest off
existing history must treat the 2026-07-19 fix boundary as a hard cutoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-19 23:14:58 -04:00
builtbykev 04a09ec1b2 Phase 2 (a) report + (b) retention design — REPORT-FIRST, nothing built
(a) WHAT REPLAYABLE HISTORY EXISTS — the headline is confirmed and worse
than "6 days".

ledger_entries is the ONLY store of model history in the database: 640
public rows, 6 distinct game days (Jul 11/12/16/17/18/19 — 13/14/15 are
missing entirely), 2 sports, 215 players, 470 settled, 465 settled WITH
odds. Every other candidate is 0 rows: grade_history, line_snapshots,
historical_props, closing_lines, resolution_results, accuracy_tracking,
model_predictions_extended, engine1_weights, prediction_registry and ~30
more. A data warehouse was designed and never filled. Redis holds no
history either (latest/previous at 24h TTL; the outcomes log carries no
odds/confidence/projection).

The blocking gap is not the day count, it is that NO MODEL INPUTS ARE
STORED ANYWHERE. No feature vectors, so we can score the grades we
emitted but cannot ask whether a different model would have done better —
which is the only question a harness exists to answer, and the exact gate
the metrics-engine north star requires. Also missing: p_win/ev_pct/
fair_odds (born tonight, on no column), grade_11 (only the 4-letter
collapse is stored, so the entire live C-/C/C+/B- range is unrecoverable),
and any model_version, so pre- and post-fix rows are already silently
mixed in one table. CLV remains unusable (C4). Settlement gaps surfaced
too: Jul 17 MLB 86 graded/57 settled, Jul 18 103/75, and 0 pushes across
470 settled rows — both feed the settlement-correctness audit.

Verdict: we cannot meaningfully backtest yet. Retention is priority zero;
every night without it is history we can never recover.

(b) DESIGN PROPOSAL — model_snapshots in Postgres (not Redis, which is
what lost us history twice). One append-only row per graded prop PER
CYCLE, capturing market values, model output, outcome (stamped later by
the settle pass), and critically a `features` JSONB — the counterfactual
enabler. Carries model_version + code_sha so eras never mix, grade_11 so
resolution is not thrown away, and refused/refusal_reason because
refusals are training data the ledger currently discards entirely.
Written from snapshotService (the existing chokepoint), best-effort so a
retention failure can never break a snapshot.

Volume: ~800 rows/day ~ 292k/year, ~300-600MB/yr of features, which would
exceed the Supabase free tier alone — so the proposal keeps full features
90 days and scalars forever. Three open questions for Kev before building:
the 90-day policy, whether to backfill the 640 existing rows as
scalars-only with explicit null features, and confirming we store
refusals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-19 22:40:43 -04:00
builtbykev 8aafceaa3b STATE.md: pin header to cc8e478 2026-07-19 22:32:59 -04:00
builtbykev cc8e47884d STATE.md: backup is durable on-box and verified by read-back (643==643) 2026-07-19 22:32:59 -04:00
builtbykev 13ca070096 Spec: metrics-engine north star + sourcing scope report (no code)
NORTH STAR (design philosophy, not built): VYNDR measures players by
MODERN FUNCTION, not legacy label — the principle already under the
archetype system, from Rashad Phillips' Basketball Position Metric. The
rule: every proprietary metric is baselined against the player's
functional ARCHETYPE's CURRENT-SEASON behavior, never the position's
inherited standard. The edge is that the market often prices today's
players against yesterday's baselines, so archetype-vs-position baseline
disagreement is a repeatable mispricing. Generalizes across sports. Moat
= proprietary metrics x current-game calibration x our private outcome
data. Metrics ship as VALIDATED FAMILIES: hypothesis, flagged build,
backtest, ship-or-delete with the negative result written down. Nothing
is real until the harness proves it predicts better.

SOURCING SCOPE (report, no code): MLB opponent strength IS derivable from
statsapi, verified live — one free call returns all 30 teams' pitching
splits (era/whip/avg/slg/ops/homeRuns/strikeOuts/HR9), which beats the
ESPN field we were reaching for because it is STAT-SPECIFIC, exactly what
opp_rank_stat wants. NBA/WNBA cannot use ESPN (its team endpoint carries
only a team's own stats, no defensive rating or pace); options are
stats.nba.com dashboards, deriving allowed-points from scoreboard finals
we already fetch, or API-Sports. API-Sports is a fallback tier at best —
100/day will not survive per-team-per-day. ESPN stays last, always behind
an adapter.

Proposed the SOURCE-ADAPTER pattern: one interface per feed, config-driven
primary+fallback per (sport x capability), normalized output so vendor
quirks stay in adapters, fallback announced rather than silent, sources
with zero callers deleted rather than left as corpses, and a health check
that PAGES when a source returns empty or broken — where EMPTY IS A
FAILURE. Tonight's crash (captured 0 / errored 15) and the months-null
opp_rank_stat are both exactly what that check exists to catch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-19 21:43:59 -04:00
builtbykev b742230d94 Phase 1: ship the backup cron as CODE + a manual regrade trigger
FOUNDATION-FIRST re-order, phase 1 (tooling + safety).

BACKUP (highest-severity open item) — INSTALLED, not re-proven.
src/backupScheduler.js runs scripts/backup-db.sh nightly from inside the
API container, armed at boot in server.js. The container already has
SUPABASE_DB_URL, pg_dump and the Supabase route, so deploy == installed:
no host crontab, no Coolify click. Arming is deliberately opt-OUT (armed
whenever SUPABASE_DB_URL exists; BACKUP_CRON=0 kills it) because the S62
design was opt-in and nobody ever opted in — the DB went unbacked every
night for weeks. A failed run pages high-priority ntfy; silence is the
danger with backups.

Durability is the one part still needing a human: the container FS is
ephemeral, so a dump dies on redeploy unless BACKUP_REMOTE (off-box
rsync) or BACKUP_DIR (persistent volume) is set. The scheduler detects
that and pages a WARNING at boot rather than letting an undurable backup
read as "backed up". Runbook rewritten to lead with the code path.

MANUAL REGRADE TRIGGER — scripts/run-snapshot.js, runnable via
docker exec with no VYNDR_INTERNAL_KEY and no new HTTP surface. Runs the
SAME snapshotService.runSnapshot the cron runs (including the team-stats
refresh that powers opp_rank_stat), supports `all` and `--settle`, and
prints the grade/confidence distribution plus p_win/ev_pct presence —
which is the thing you actually want when verifying a grading change.

ACCESS BLOCKER, logged honestly in specs/model-train.md: there is no
VYNDR_INTERNAL_KEY in the local .env and SSH to the box times out from
WSL2, so I can neither curl the internal endpoints (which already exist
from S45) nor docker exec. The trigger is built and correct but only Kev
can run it until a key or SSH access exists. This is the highest-leverage
unblock for phases 2 and 3, which both need on-demand regrade+settle to
verify anything.

Also logged the standing cautions: CLV ledger stays private until
backtest-proven; "self-improving model" is unsupported marketing until
the loop closes; the engine is MLB/WNBA-calibrated and NFL/NBA/soccer
need their own calibration before the hub grades them (scaling gate).

Suite 277/3300 green.

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