Files
vyndr/specs/STATE.md
T
2026-07-18 01:56:45 -04:00

26 KiB
Raw Blame History

VYNDR — STATE OF THE WORLD

As of a8e383e (main, deployed + fingerprint-verified live), 2026-07-18. This file opens every future session. Update it when a train ships.

TRUTH-EVERYWHERE PART 2 — COMPLETE (2026-07-18, on main, all 8 items fingerprinted)

Standing law: NO FABRICATED DATA ANYWHERE (UI, marketing, demos, examples). If it renders a number, it's from the DB or it doesn't render. Live data IS the better demo.

  • Item 1 — Verb law (66d52a9): one verb, READ (never SCAN), swept every user-visible surface (FAB/nav/tier/ticker/upgrade). tests/unit/verbLaw.test.js enforces. Ticker "slate read" text flips at the next snapshot cron.
  • Item 2 — Founder counter (41fc2b9): real active-paying-founder count from Stripe-synced user_profiles, cached; HIDES when unavailable. Live: 1/100 (was fabricated 47/100).
  • Item 3/4 — Reports: full fabricated-data inventory + pricing claim audit (LIVE/PARTIAL). Alt-ladder/Kelly/correlation are LIVE in the engine; scratch-cascade + real-time-feed are PARTIAL.
  • Item 5 — Daily hero prop (9b9aab4): live RULE = largest |modelconsensus| gap, A/B only. Shows book line vs VYNDR model + real timestamp. Empty→most-recent-real; nothing→hidden. NO static Jokic fallback. heroPropService + /api/hero-prop. Live: Erica Wheeler 9.5 vs 14.2.
  • Item 6 — Desk showcase (cb3237c): real alt-line ladder from the snapshot (edge guarded on small-line artifact); φ = real same-team correlation for two real legs or hidden; Kelly tile REMOVED (no odds → can't size honestly). deskShowcaseService + /api/desk-showcase. Live: Vandersloot ladder.
  • Item 7 — Accuracy → TRUTH (89a2977): public 30D accuracy (/api/accuracy + /api/ledger/accuracy) now reads the CLEAN Postgres ledger aggregate (getModelAggregate/getAccuracyView, model_value>0), not the Redis outcome log. Number moved 67%→61%. BEAT CLOSE suppressed at source (clvCaptureReliable()) — see C4.
  • Item 8 — Blog (3b7a1f5,a8e383e): wired the 5 REAL articles (content lives in web/content/articles — the runtime content root, NOT repo-root content/) with honest dates + markdown render + OG. Backdated 2026-03-22 orphan deleted + 301. Live: 5 articles serve.

🔴 C4 — CLV CAPTURE BROKEN (open, backend)

captureClosing re-records the LOCKED line as "closing" (closing_line == locked_line on the whole sample) → CLV is flat → BEAT CLOSE = a measured-wrong 0%. Suppressed on all public surfaces until fixed. Full write-up + fix plan: specs/audit-data/clv-capture-broken.md. Flip CLV_CAPTURE_RELIABLE=1 after C4 verifies real closes.

PHONE AUDIT PUNCH LIST — COMPLETE (2026-07-17, on main, deployed, fingerprinted)

Kev's 35-screenshot real-390px audit (signed-out + Desk signed-in). Worked top-down, fingerprinted the batch (compare "on current form" literal live in served chunk off 71e35e9). Test baseline: 268 suites / 3239.

  • P0-1 blue-B/amber-C banished → token-derived grade badges (earlier batch).
  • P0-2 image pipeline (logos/headshots) loading=eager (earlier batch).
  • P0-3 player grouping/ladders across leaders/explore/ledger/breadth (lib/playerGrouping.js).
  • P0-4 mobile app bar collapsed to one line (ticker+heartbeat hidden <768, MobileSyncClock).
  • P1-5 390px overflow containment (breadth rows stack, pitcher line wraps).
  • P1-6 (ff0d3b1) ONE freshness source: Slate "UPDATED" now reads the pipeline refreshed_at (the app-bar clock's field), not the client poll time — no more "0s ago" vs "SYNC 46:03" contradiction.
  • P1-7 (d755b43) flat edge board: an impossible |edge|>40 is treated ABSENT at both data (can't rank a fake +140% over a real +8.4%) and display layers. Chips render now (P0-2). EDGE_BOARD_SANE_MAX.
  • P1-8 (00537eb) Compare verdict truth: no phantom "edge tonight" for July NBA(0) → "on current form"; cites what actually wins (scoring/boards/playmaking, not usage).
  • P2-9 (77e8937) leaderboard stat labels web/src/lib/statAbbrev.js (SB/ER/TB) + FLAGGED the backend grade degradation (below).
  • P2-10 (71e35e9) ledger read-card density (padding/margins tightened; archetype showDesc kept — it's inline, zero height cost).

🔴🔴 BACKEND FINDING — MLB GRADE PIPELINE DEGRADED (needs backend work, NOT frontend)

Diagnosed against live /api/snapshot/mlb (2026-07-17). Full write-up + do-not list: specs/audit-data/mlb-grade-degradation.md. Frontend guards (sane-edge cap, statAbbrev) are damage control, NOT the fix.

  • projection == 0 for 9/25 grades — the MLB feature path feeds 0 instead of refusing (S58 insufficient_data). This also breaks computeEdge → the broken edge_pct.
  • edge_pct on a broken scale {20,60,100,140} — not a market %. Real prop edge is single-digit.
  • grade↔confidence mismatch on 25/25 — B shown at conf 55 (= the B- band per grade_thresholds.json); systematic one-sub-tier gap. The surfaced confidence is not the probability that derived the letter (likely the data-sufficiency penalty applied to display-only). This is the audit's "B at 45%" — NOT a frontend labeling or threshold bug; DO NOT re-letter/re-threshold on the frontend.

DESIGN COMPLETION TRAIN — SUBSTANTIALLY SHIPPED (2026-07-16, on main, deployed) · ONE MASTER CHROME AUDIT PENDING

Goal: the live product matches Design's mockup at EVERY width. HONESTY LAW: I cannot see rendered output (WSL2↔Chrome unreachable) — every visual change is "built to Design's spec, VISUALLY UNVERIFIED." Kev runs ONE master Chrome audit at the END (390px + desktop) against the running unverified list below; fixes propagate through tokens (surfaces inherit, don't fork). Design package (3 revs) authoritative at specs/design-reference/ (HANDOFF.md = entry point; vyndr-system.html desktop, vyndr-mobile.html 19 screens, vyndr-landing.html, assets/glyphs/ 83 SVGs+MANIFEST). SHIPPED + deployed:

  • Palette alignment (e0a26a8,c3d469e,ebaab00): tokens → Design's EXACT ramp. Grade colors corrected: B blue→WHITE #F0F0F0, C amber→GREY #B8BCC8, D #FF4757 (the old blue/amber violated DESIGN-SPEC's own "B neutral-bright, C muted"). Surfaces #0E0E14/#14141E, text #F0F0F0/#B8BCC8/#707080, borders #1E1E2A/#2A2A38. vyndrTokens + tests aligned. ShareCard/OG billboards grade-B white.
  • Glyph system (77d8fd6,771d8b0): 44 archetypes render Design's REAL marks (83-mark set: 74 display + 9 classifier-legacy), front lib/archetypes.js + back archetypeService.js color-synced. ZERO generic placeholders left. Combat keeps its char-glyph namespace (separate test).
  • Entity layer VERIFIED already-complete (DS0/Wave 2A/2B — the audit was stale): TeamLogo (real ESPN CDN, monogram fallback) on game cards/team hub; PlayerAvatar headshots MLB/NBA/WNBA + NFL/NHL resolver ready + soccer-monogram (blocked) + combat-no-photos; books via bookInfo/BookChip everywhere (fixed BookComparison holdout 1558710); line-shopping surfaces (detectBestBook/MarketBreadth) self-deepen. No $1M copy.
  • Rev 3 matchup chips (f24f941,baf977f,bf8bf16): TeamChip primitive (real TeamLogo at 10-12px + abbr, inherits row opacity) on StatStrip board rows, grade reveal, pitcher-vs. Game-card headers already had TeamLogo.
  • Mobile: M1a header collapse (a10227e) · M1b app-bar clock (7c7ab24, Hybrid: wall clock rests, STALE reacts off refreshed_at) · grade-reveal mobile (baf977f, 74px hero) · flat EDGE BOARD (8a24ac9 — Design's core mobile screen: NEW flat edge-ranked IA, slateAdapter.flattenToEdgeBoard+MobileEdgeBoard, shown <768px, cards ≥768px; threaded edge_pct through the pipeline) · M4 structural locks (6bc9093).
  • Key finding: the 19 mobile screens were LARGELY already built (pages built to the SAME DS0-DS5/§7 contracts Design's mobile follows) → they already match structurally + stack on mobile. The genuinely-new work was the flat board (done) + narrow chip/hero polish. REMAINING (all VISUAL, for the master audit): per-screen mobile polish of the ~17 already-built screens (blind, mostly Design-aligned) · M2 billboard crop-ratio verification (OG routes exist, colors fixed). Test baseline: 266 suites / 3214.

🔴 MASTER-AUDIT UNVERIFIED LIST (every visual change this train — Kev's one end-of-session audit)

  1. Palette (biggest): grade B-white/C-grey/red-#FF4757 + full surface/text/border ramp — every surface inherits.
  2. Archetype marks + colors — 44 archetypes render Design's real 83-mark set.
  3. Flat EDGE BOARD at 390px — the core mobile screen (rows/opacity-ramp/chips/hero-edge%).
  4. Grade-reveal mobile (74px hero + team chip) · app-bar clock (Hybrid) · M1a header collapse.
  5. TeamChip on board rows / grade reveal / pitcher-vs (real logo at 10-12px).
  6. OG billboards (ShareCard/player/u) grade-B white + text #F0F0F0.

SHIPPED — HEARTBEAT + CADENCE + QUOTA-GUARD (2026-07-15, on main, deployed)

Three ships, each green + deployed. Standing authority is now PERMANENT full autonomy (three-gate floor: tests green, web build exit 0, post-deploy fingerprint of NEW code).

  • 7712f0a Heartbeat honesty (fingerprint-verified live): the "frozen snapshot / STALE 8h" was a MEASUREMENT bug, not a dead pipeline. updated_at = grade-LOCK time (advances 5×/day by design; grades never change in-game); the SYNC badge measured the 20-min expected_interval_s against it → structurally STALE. refreshed_at (the real 20-min intraday heartbeat) was written to Redis but NEVER serialized. FIX: full snapshot seeds refreshed_at, /api/snapshot/summary + GET /:sport expose it, LiveLayer badge measures from it. Fingerprint PROVED the pipeline was alive all along (refreshed_at advancing 20:20→20:40→21:00→22:00; the 22:00 full slot fired → scheduler healthy, re-armed fine after the 19:37 restart).
  • 4cd933d + 2d413cf Per-sport cadence (Job 1): src/config/sportCadence.js (config-over-constants). REALITY-CORRECTED: mlb/nba/wnba all keep the full grid 14/19/22/1/3 (PropLine 9k/day = cheap; WNBA games span afternoon→late-evening, so 14 UTC/10am ET catches afternoon-game props — the earlier "drop it as waste" was wrong, corrected by observing 2 finished afternoon WNBA games on Jul 15). Soccer alone is trimmed to 14/19 + NO intraday — the genuine win, protecting the scarce odds-api key. Scheduler fires at HOURS_UTC and grades only sportsForHour(h); only intradaySports() refresh.
  • 2d413cf Quota guard (the real finding): odds-api quota was EXHAUSTED 0/500 mid-cycle, unpaged. Root cause: futuresService (the only regular burner) used raw axios, BYPASSING the gateway → never hit recordCall (the ONLY place the WARN/BLOCK pager fires) → the counter reached 100% via silent header-sync. FIX: futures now routes through gateway.fetch('odds-api', …) (counted + blocked); a RESERVE floor (ODDS_API_RESERVE=50) refuses DISCRETIONARY calls (futures/soccer) while remaining ≤ reserve so they can NEVER starve MLB's essential prop-backup; quotaTracker.syncFromHeaders (authoritative) now ALSO fires the once-per-period alert; POST /api/internal/quota/test-alert test-fires the pager end-to-end. Fingerprint caveat: backend-internal, no unauthenticated public surface (/api/internal/* 401s for any path — auth runs before route-match); verify via authenticated test-alert (200=new/404=old) with the internal key.
  • odds-api 0/500 → BOTH soccer props AND futures are blocked on QUOTA, not code. Kev's ruling: HOLD THE LINE (don't pay; zero-out-of-pocket). Soccer stays honest-empty; cadence is ready for the reset. Env: keep API_FOOTBALL_KEY (code reads it), delete inert API_FOOTBALL_KEY2 (Kev handling). See memory odds-api-quota-soccer-blocked. Test baseline: 264 suites / 3195.

SHIPPED — TRUTH-EVERYWHERE + OFFSEASON HUB TRAIN (on main, deployed)

Governed by specs/truth-everywhere-train.md. The reframe that drove it: only MLB GRADED in prod (NBA/WNBA refused their whole slate — offline Python feature source, no fallback; soccer errored pre-grade). So grading, not settlement, was the first domino. Standing deploy authority granted (green + fingerprint → merge + deploy, no ask). Waves DEPLOYED:

  • Wave 0 — NBA/WNBA GRADE (free ESPN gamelogs): espnStatsAdapter.getPlayerGameLog feeds featureCache when the Python source is null → l5/l20 → props grade. Verified live (A'ja Wilson→B, Ionescu→C off real ESPN form). Endpoint site.web.api.espn.com/apis/common/v3/sports/basketball/{nba|wnba}/athletes/{id}/gamelog.
  • Resolver-hardening: roster-index primary resolver (206 WNBA / 544 NBA) + ?season= gamelog retry — the real fix for dual-league (WNBA+NCAA) players who got an empty gamelog (Collier/Brionna Jones now resolve). Broad coverage.
  • Wave 1 — NBA/WNBA SETTLE: defaultGetPlayerStats routes nba/wnba to the ESPN gamelog in BOTH outcomeService + ledgerService; separate NBA_BOX_KEY/NBA_COMBO settle maps; final-honesty guard (never settle in-progress); opsWatch finals-gated zero-settle alarm + per-sport boot announce. accuracy:{sport}+by_tier light up automatically. PROOF is next-day (grades lock tonight → settle tomorrow → accuracy:wnba).
  • Wave 2 — NEVER-DARK HUB: ESPN /news wire (newsService + /api/news/:sport) — verified live (real headlines); NewsWire+FuturesBoard on /explore (offseason-aware, self-hide on empty, futures "TRACKED · NOT GRADED"). KNOWN GAP: /api/futures/:sport returns markets:[] for ALL sports in prod — code path looks correct (odds-api /sports/{FUTURES_KEYS}/odds?markets=outrights); needs a live odds-api probe (ODDS_API_KEY) to finish. Self-hides honestly meanwhile. FUTURES_ENABLED=0 kill-switch; 12h TTL; 1 credit/refresh.
  • Also rode along: espnAthleteIndex cross-sport headshot capture (headshot-coverage) — NBA/WNBA/NFL/NHL real headshots when in-season. DEFERRED (locked decisions): soccer — grade+settle path, needs API_FOOTBALL_KEY (VALIDATED, Free 100/day, active to 2027 — set it in Coolify env API_FOOTBALL_KEY; code dormant until wired) + ESPN-soccer-free-first attempt; MMA moneyline settle (combat not yet in the grade/lock loop); NFL/NHL (dormant, in-season later); futures debug (odds-api probe). Test baseline now 263 suites / 3183.

SHIPPED — WIRING & DATA TRAIN (on main 3b1aa9f, deployed 2026-07-13)

Governed by specs/wiring-data-train.md + specs/combat-intelligence.md + the global visual reference specs/design-reference/vyndr-system.html (build toward it; live wordmark kept). All 6 waves shipped + deployed (fingerprint-verified: /parlay, /fight, /u/vyndr live; house record 88-38/70% real). Real assets verified live (MLB/ESPN headshot CDNs, ESPN-MMA feed, Baseball Savant CSV).

  • Wave 1 — trust bugs: billing renewal honest render (billingDisplay.classifyRenewal — no far-future placeholder); James Wood nameKey-collision fixed (mlbStatsAdapter teamHint disambiguation + streaks join-invariant); DeskShowcase "$1M terminal" → deadpan copy.
  • Wave 2 — sport-agnostic entity layer: real player headshots threaded from ingestion (MLB MLBAM + NBA/WNBA ESPN athlete ids that were fetched-and-discarded) across slate/scan/hotlist/search/grade card; soccer = honest monogram (no free id); 8 self-authored SVG book wordmarks (web/public/books/*.svg, swappable for official art) + all 10 book keys resolve; team-logo abbr aliases. Storage: id on the enriched grade at snapshotService (zero new I/O).
  • Wave 3 — record by grade tier (Addition 2): ONE shared TierRecord (lib/tierRecord.js + component) on dashboard + /u + ledger; per-tier W-L always, hit-% only at n≥20 per tier (gate stays in getModelAggregate).
  • Wave 4 — missing surfaces: Outlook mode (grid never blank → yesterday receipts / tomorrow schedule); Market-Breadth median-consensus-vs-model strip (self-hides <2 books); Parlay Lab /parlay (slate-independent leg source); live Grade-Shift timeline (GradeShift).
  • Wave 5 — /u house-mode + arsenal: house handle vyndr surfaces the real user_id=NULL public model record + per-tier calibration + 1080×1350 portrait/OG (user-handle privacy 404s stay byte-identical); Baseball Savant pitcher-arsenal (savantAdapter, free CSV, verified) → PitcherArsenal card, self-hides on absent.
  • Wave 6 — combat v1 (MMA): ESPN-MMA fight cards + tale-of-the-tape + style-blend archetypes (sport-scoped COMBAT_ARCHETYPES — FINISHER color collided w/ soccer + tripped the ΔE gate, so kept separate) + odds-api ML/round-totals + style-edge verdict. NOT in the snapshot/settle loop; method/round/props + fighter photos + matchup-GRADE engine + ufcstats scraping all DEFERRED, flagged data-limited in-UI. New routes: /fight/[id], /parlay, /u/[handle]/portrait. POST-MERGE TODO: NBA/WNBA headshot coverage + combat depth need prod runtime verification; soccer headshots blocked on API_FOOTBALL_KEY; combat settlement + matchup-grade engine are the next combat sub-wave.

SHIPPED — DESIGN TRAIN v2 (merged to main e9c0a59, deployed & fingerprint-verified live)

Governed by specs/DESIGN-SPEC.md v2 (the raised standard: entities render as themselves, color contract, sub-200ms, screenshot-first billboards). All six sessions DS0DS5 shipped: merged to main, pushed to gitea, Coolify auto-deploy LANDED — verified live via /pricing DeskShowcase ("A $1M terminal") + /u route serving the unified EmptyState. 238 suites / 2885 tests green, next build exit 0. Post-ship TODO: re-run the Chrome design audit against prod v2 to confirm all 22 audit findings closed (env here can't screenshot — WSL2↔Windows-Chrome localhost unreachable).

  • DS0 Entity Layer (24af247): teamMeta.js (real logos+colors for 30 MLB / 30 NBA / 13 WNBA / 48 WC nations via ESPN CDNs), TeamLogo / PlayerAvatar (team-colored monogram fallback, no gray silhouette) / BookWordmark, swapped into GameCard/StatStrip/StreaksPanel/TeamHub.
  • DS1 Speed + Trust Bugs (1c681df): React #418 hydration fix (mounted-flag guard), layout-matched Skeletons replace text-wall loaders, scan→ledger persistence via session.access_token. DS1 follow-up (cf91c04): closed the sb-token trust-bug CLASS — lib/authToken.js currentAccessToken() reads the REAL Supabase session (not the OAuth-only sb-token key) across profile/slip/dashboard/settings/tracker.
  • DS3 Color Contract (49a3323): lib/colorContract.js — green = one meaning (edge/A-tier/CTA), edge/CLV by sign, glow A/A+ only, archetype greens deduped. Enforced by colorContract.test.js (fails on violation).
  • DS4 Billboards (45bafbc): STREAKS row (length as mono hero), grade reveal (sign-colored edge), CLV reframe (flat says so), /u public profile + OG.
  • DS2 Dashboard Slate Rebuild (fe294a5): never-empty hero (falls back to yesterday's A-tier settled receipts), one bold hero per card, pending-filler collapse, ranked tonight-grades. slateAdapter.js engine + 21 tests.
  • DS5 Pricing + Motion + States (a18a3f3): Desk $44.99 as the hero tier (single green CTA, DeskShowcase), ticker → punctuated stillness (≥4.2s hold, one idle motion), unified EmptyState (404 grammar) at TeamHub/game/ledger, archetype glyphs propagated to streaks/ledger. NOTE: WSL2↔Windows-Chrome localhost is unreachable in this env, so visual proof was SSR-render + source-assertion tests + live prod fingerprint curls, not pixel screenshots. The design/v2-train + per-DS worktree branches can be pruned. Stale origin GitHub remote still carries the long-dead leaked PAT — deploy runs off gitea, so it's inert, but rotate it when convenient.

WHERE THINGS STAND

Tests: 2757 / 229 suites, all green. Web build exit 0. Baseline at the A1 board's start was 2398. Governing docs: specs/VYNDR-NORTH-STAR.md (v1.1) + specs/VOICE.md (v1.1) + specs/ROW-GRAMMAR.md — read them before building anything.

The record is live and writing itself. Day one (Jul 11): 24→25 MLB rows, real locks (line/odds/book), closing capture every snapshot + every 20-min intraday refresh, settlement each morning slot with signed CLV. No percentage renders anywhere under n≥20 — enforced in ledgerService.getModelAggregate, the single gate. WNBA/soccer rows pend honestly until Phase 4.5.

THE THREE A1 TRAINS (all merged to main, all pushed)

Train Range Carried
#1 4d2b27d → 219167e S0 verify · S1 promise audit (PROMISE-AUDIT.md: alt ladder + quarter-Kelly BUILT, analyst unlimited, "40+" claims made honest) · S2 compliance (/responsible-gambling rebuilt, /terms /privacy drafts, /methodology, 5 seed articles in content/articles/) · S3 affiliate (BOOK IT deep links organic-until-config-flip, best-price dots, ?ref= partner attribution) · S4 media engine (mediaEngine + executable VOICE lint, /desk arsenal + DATA BRIEF, Ghost DRAFTS-only publisher) · S5 viability (statsapi lineups → CONFIRMED/NOT-IN kills grades visibly; ESPN injury wire; Yesterday/Today/Tomorrow nav + Settle panel) · S7 newsletter (Listmonk capture + THE VYNDR REPORT assembly, env-gated) · S8 ops (settle alarm, 3-slot failure pager, quota/disk/mem, 9AM pulse — test alert delivered to the real ntfy channel) · S9 slip reader (tesseract.js OCR + per-book parsers, /slip page; synthetic-image acceptance passed) · S10 public profiles (/u/{handle}, private-by-default, no-existence-leak) · migrations 021/022
#2 219167e → 14dc9cf S6 display (ROW-GRAMMAR.md + slot-order fixes, line sparklines from intraday history, last-10 ●●○ dots, CLV distribution on MODEL tab, ⌘K SearchModal + mobile search, landing LCP fixes)
#3 14dc9cf → ec5ee3d S11 live tracking (statsapi/ESPN live box lines → ON PACE / NEEDS N / HIT ✓ in the outcome slot, live games float up, "TRACKING — READ LOCKED PRE-GAME"; real-live acceptance: Harper 3/1.5 TB ▼8th → HIT ✓). Grades never change in-game.

Deploy status: ALL LIVE as of 17fb981 (Jul 12 17:43 UTC). Fingerprints confirmed in prod: /api/live/mlb serves JSON with hasLive:true (S11 live tracking working — a real game was tracked at confirm time), /api/health healthy, /api/snapshot/summaryexpected_interval_s:1200 (env var now set).

P0 postmortem (17fb981): the API silently served a 14h-old image (4d2b27d) from Jul 11 ~20:00 to Jul 12 17:43. Root cause: the Dockerfile never copied content/, and mediaEngine.js read content/stark-lines.json with an unguarded module-load readFileSync → ENOENT at require time crashed app.js's eager route chain (app → routes/desk → deskService → mediaEngine) → boot failed → Coolify healthcheck rolled back. Every merge from S6 onward was merged-but-not-running for 14h. Fixed: Dockerfile COPYs content/; mediaEngine treats the file as optional garnish (loadStark try/catch → {}); src/preflight.js now prints [preflight] OK|DEGRADED at boot so a missing file/env is legible instead of masked by a rollback. LESSON: any module-load file read is a boot-crash risk — keep them lazy + try/catch, and the Dockerfile must copy every dir the runtime reads.

LIVE INFRA MAP (verified in prod)

  • VYNDR Web — Next.js :3000, vyndr.app. VYNDR API — Express :3001, api.vyndr.app (/api/internal/* behind VYNDR_INTERNAL_KEY).
  • Scheduler (in-process, API app, SNAPSHOT_CRON=1) — snapshot slots 14,19,22,1,3 UTC; settle pass (outcomes + ledger, idempotent) runs FIRST each slot; 20-min intraday odds refresh during slate hours (INTRADAY_REFRESH=0 kills); desk-ready ping after the day's first slot; daily pulse 13:00 UTC; missed-cron + 3-slot-failure + quota pagers → ntfy vyndr-pipeline-kev2026. Boot logs: [snapshotScheduler] armed + [settlement] armed.
  • Data: Redis (mastermind-cache) for all pipeline caches (SNAP_TTL 24h — do NOT shorten; the 11h overnight gap killed morning settles once). Supabase = the ledger (ledger_entries + public_profiles + user_profiles.partner_ref; RLS everywhere, service-role-only writes; migrations 001022 applied, files in supabase/migrations/).
  • Deploys: gitea git.builtbykev.com/builtbykev/vyndr → Coolify webhook (HMAC secret fixed Jul 11; auto-deploy proven). GitHub origin remote is dead-PAT hygiene debt only.
  • Free feeds (zero out-of-pocket law): PropLine ×3 keys (9k/day capacity; intraday uses ≤144/day), odds-api backup (quota-gated), statsapi.mlb.com, ESPN site API. tesseract.js OCR self-hosted (traineddata caches on first call — needs outbound network once).

ENV VARS STILL PENDING ON KEV'S SIDE (Coolify, API app)

  1. SNAPSHOT_EXPECTED_INTERVAL=1200confirmed absent (fresh restart still served the 18000 default). SYNC badge tracks intraday cadence once set.
  2. DESK_OWNERS=kevdevelops@gmail.com — /desk 403s everyone until set.
  3. LISTMONK_URL / LISTMONK_USER / LISTMONK_TOKEN / LISTMONK_LIST_ID (all four together) — newsletter no-ops politely without them; box-side setup in docs/NEWSLETTER.md.
  4. Optional: GHOST_URL + GHOST_ADMIN_API_KEY (drafts), PULSE_HOUR_UTC.

Entity placeholders (S2, blocked on Kev's values): [ENTITY NAME], [STATE OF FORMATION], [ARBITRATION VENUE], [CONTACT EMAIL] in web/src/app/terms/page.tsx (6×) + web/src/app/privacy/page.tsx (5×). A test keeps unverified entity names out until replaced.

HONEST OPEN ITEMS

  • Phase 4.5 — WNBA settlement via ESPN box scores. DUE ~Jul 24 (hard date). Until then WNBA/soccer/NBA ledger rows pend forever; accuracy = MLB only.
  • The record needs time: no percentage anywhere before n≥20 settles, by design. First percentages expected ~day 34 of MLB volume.
  • Slip reader: acceptance was a clean synthetic image; needs one real phone screenshot (DK) to validate OCR robustness.
  • Landing LCP: S6's fixes are static-analysis-provable but unmeasured — run PageSpeed mobile vs the 3.8s baseline; next suspect is the LiveHeroProp post-fetch swap.
  • Partner report endpoint returns real numbers only for signups AFTER migration 021 (metadata path live; OAuth signups carry no ref — known gap in docs/PARTNERS.md).
  • NBA Oct / NFL Sep readiness + soccer end-to-end: specs/vyndr-roadmap.md.
  • Killed permanently (founder ruling): AI chat assistant, light mode.

OPERATING LAWS (never relax)

Zero out-of-pocket · absent beats wrong, refusal beats hollow · n≥20 before any percentage · nothing auto-posts anywhere, ever · grades never change in-game (revisions are public via revised_from_grade) · one meaning per color, data in mono · no exclamation points, anywhere · the pipeline is the only source of numbers, including in marketing (mediaEngine lint enforces).

— STATE as of ec5ee3d · the record is writing —