50 KiB
VYNDR — STATE OF THE WORLD
As of 7ea0af2 (main, DEPLOYED + fingerprinted live), 2026-07-20. This file opens every future session. Start with the CURRENT STATUS + OPEN ITEMS block below.
🧭 CURRENT STATUS + OPEN ITEMS — orientation block (2026-07-20, ~03:20 UTC)
Written to orient a fresh session. Ground truth, not optimism. Everything below was verified live unless explicitly marked UNVERIFIED.
What shipped tonight (all deployed + fingerprinted)
| # | Shipped | Proof |
|---|---|---|
| 1 | Probability layer revived — it was DEAD in prod | p_win/ev_pct/model_odds/value on 32/32 live grades (was 0/8) |
| 2 | Value engine Arc 1 — de-vig, EV, value triplet, hero v2 | live: book −140 · fair −125 · model −109, value:false correctly |
| 3 | Grade-range work — L20 symmetry, refreshTeamStats wired, consistency CV guard, confidence_basis, dead mlbGrader.js removed |
suite 279/3325; D emitted on real WNBA data locally |
| 4 | Backup durable ON-BOX | dump read back: 643 rows in dump == 643 live, 18 MB |
| 5 | model_snapshots retention LIVE (migration 025) |
100 rows captured over 2 cycles, incl. 36 refusals |
| 6 | ESPN team-stats parser fixed (buckets is not iterable, 0/15 → 15/15) |
live refresh |
⚠️ Two honest qualifiers on the above
- A STILL DOES NOT EMIT IN PRODUCTION. The 11-step grade is now stored
(
grade_11) and A/D are arithmetically reachable + locked by tests, but the ±1.0 opponent factor is still dead becauseopp_rank_statis underivable from ESPN (see open item 6). Live boards remain B/C only. → The "A-RATED" marketing hold STANDS (AccuracyBadgecorrectly falls through to "MODEL · X% HIT";TopSignalsself-hides). Do not promote A-rated. - EV IS OVERCONFIDENT AND UNVALIDATED. First captured values include
+62.1% / +61% / +56.9% — real markets don't offer that. Cause:
p_winclamps atPROB_CEIL0.95 off ~10 games. Hero v2 ranks onev_pct, so it picks the MOST overconfident read. Do not surface EV as truth until calibration runs on the harness.
🟢 OFF-BOX BACKUP — CLOSED. FULL ROUND TRIP PROVEN (2026-07-20 05:25 UTC)
Root cause (Kev): the vyndr-backup key was only in Hetzner's PROJECT ssh-key
store, never in the Storage Box's own .ssh/authorized_keys. Installed there →
the box now offers publickey,password (it previously offered an EMPTY auth
list, which is what "SSH not enabled on this box" looks like).
Proven end-to-end:
POST /api/internal/backup/run→ok:true,offbox_ok:true, exit 0, 31s.- Output confirms both env questions:
ssh key: base64-decoded(the key is correct base64, not a mangled raw paste) and destinationu635423@...:vyndr-backups/— no leading-slash bug. - File VERIFIED on the box, not inferred from exit 0:
GET /api/internal/backup/offbox(rsync--list-onlythrough the pinned host key) returnedvyndr-20260720-051158.dump, 833,917 bytes, 05:12:28 UTC — byte-identical size to the local dump, timestamp from that run.
Hardening shipped with it:
- Host key STATICALLY PINNED.
ssh-keyscanmatched the out-of-band fingerprintSHA256:XqONwb1S0zuj5A1CDxpOSuD2hnAArV1A3wKY7Z3sdgM;scripts/storagebox_known_hostsships to the container and the script usesStrictHostKeyChecking=yes+UserKnownHostsFile=<pin>.accept-new(trust-on-first-use) is GONE; a missing pin file REFUSES the push. Tests assert the weakenings never return. - Remote dir guaranteed —
--mkpath, else remotemkdir -p. Without it a push into a missing parent can write the dump AS the directory name: one file, overwritten nightly, reading as "backups exist" while retaining exactly one. - A failed required push now PAGES at urgent (was "low/deferred") and the run
reports
offbox_ok:false. Exit code still tracks ON-BOX durability so a good on-box dump never raises a false total-failure alarm.
✅ ROUND TRIP CLOSED — pulled back off-box and rebuilt into a live DB (05:25 UTC)
The full insurance chain is now PROVEN, not assumed: dump → validated → pushed off-box → verified on the box → pulled back down from the box → restored into a live Postgres.
- Pulled FROM the Storage Box (not the local copy) with the in-session key
through the pinned host key (
StrictHostKeyChecking=yes, never bypassed):vyndr-20260720-051158.dump, 833,917 bytes — byte-identical to what was pushed. - Restored into scratch Postgres 17 (docker): 715 archive objects,
42 public tables,
ledger_entrieswith all 27 columns and real rows (spot-checked: Kelsey Mitchell points o22.5 B −105 hit). - ASSERTION PASSED —
ledger_entriesrestored = 645, live = 645 (target was ≥ 645).model_snapshotsalso restored 100/100, so the new retention store is covered by backups from day one. - Scratch DB torn down; the pulled copy deleted; both dumps still on the box (nothing rotated out, cron untouched).
🔴 OPERATIONAL GOTCHA FOUND DURING THE RESTORE — record this in any DR runbook:
the dump is written by pg_dump 17 (Supabase is PG 17.6), so pg_restore 16
CANNOT read it — it fails with unsupported version (1.16) in file header.
The first restore attempt failed for exactly this reason. Restore with PG 17+
tooling (postgres:17-alpine works). Restoring into vanilla Postgres also logs
errors ignored on restore: 12 — Supabase-specific roles/extensions that do not
exist locally; harmless, the data restores completely.
Open items — with status
| Item | Status | Note |
|---|---|---|
| Settlement: 0 pushes / 470 settled | 🔴 OPEN, unstarted | Implausible — hits/TB land on the number regularly. Exact-number push almost certainly mis-scored as hit or miss. Corrupts every accuracy/ROI number. |
| ~28 props/day never settle | 🔴 OPEN, unstarted | Jul 17 MLB 86 graded/57 settled; Jul 18 103/75. Cause undiagnosed. |
| Model-version contamination | 🟠 PERMANENT, mitigate | ledger_entries mixes pre/post-2026-07-19-fix grades with no marker; eras cannot be separated retroactively. Any backtest/accuracy claim off existing ledger history MUST treat the fix boundary as a hard cutoff. model_snapshots stamps model_version+code_sha so it can't recur. |
| A-grade unreachable in prod | 🔴 OPEN | opp_rank_stat null; ESPN team endpoint has no defensive metric at all. Marketing hold stands. |
| EV overconfident | 🟠 OPEN | Needs calibration before it drives any surface. Hero v2 already ranks on it. |
edge_pct broken scale (U-deg pt 2) |
🔴 OPEN | 51.5% of ledger rows exceed the sane cap; worst 620. 13 frontend surfaces render it; nothing renders ev_pct; it's the free-tier hook; it's written to the append-only edge column every cron. |
| CLV broken (C4) | 🔴 OPEN | closing_line == locked_line on ~95% of rows. BEAT CLOSE suppressed. CLV ledger stays PRIVATE until backtest-proven. |
| Consistency CV floor | 🟠 STOPGAP | CONSISTENCY_MIN_MEAN=4 leaves a ±1.0 dead for MLB low-count stats. Real fix = index-of-dispersion classifier; needs a backtest first. |
🔑 THREE KEYS TO ROTATE (all currently valid)
- Storage Box password — Kev plans a fresh reset; do it before/with the SSH fix.
VYNDR_INTERNAL_KEY— was pasted in plaintext into a chat transcript (2026-07-19) and is in the local.env. It gates snapshot triggers, backup runs, settle passes and the newsletter send. Rotate: Coolify env + local.envtogether.- GitHub PAT in the
originremote — plaintext in.git/config, leaked since ~Session 31. Deploys run offgiteaso it's inert, but it is a live credential. Revoke on GitHub; the remote can simply be deleted.
Next queued work (in order)
- Backtest harness — reads
model_snapshots. Needs ~2 weeks of history; as of now it holds ONE night. Its honest first output is "cannot validate yet" and that is the expected, correct result. Build the instrument now, let data accumulate. - Settlement-correctness audit — the two bugs above. Highest-value correctness work: everything downstream trusts hit/miss.
- A-grade / opponent-strength sourcing — MLB is solved and verified:
statsapiteams/stats?group=pitchingreturns all 30 teams free withera/whip/avg/slg/ops/HR9, and it's stat-specific, which beats what we were fetching. NBA/WNBA needstats.nba.comor derived allowed-points. Implement behind the source-adapter pattern (health check that PAGES on empty — tonight's ESPN crash was invisible for exactly that reason). - Metrics engine — archetype-baselined proprietary metrics
(
specs/model-train.md§2C). Gated on the harness: every metric family ships only if it backtests better than without it.
Governing docs: specs/model-train.md (value engine + phases + north star) ·
specs/snapshot-retention.md (Phase 2 report + schema) ·
specs/audit-data/grade-collapse.md · specs/audit-data/gate-simulation.md.
🟢 RETENTION CLOCK TICKS ON ITS OWN (2026-07-20 06:05 UTC)
Induced the REAL cron entrypoint rather than waiting for 14:00 UTC.
- Cron capture needed NO wiring — verified in code.
snapshotScheduler's tick callsrunAll=snapshotService.runAllSnapshots→runSnapshotper sport →gradeAndCacheSlate→onGraded→ retention. The scheduled and manual paths are the same function. Nothing had been captured only because no slot had fired since retention deployed (~02:55; slots 14/19/22/1/3). - PROVEN by induction:
POST /api/internal/snapshot/all(=runAllSnapshots, the exact function the tick calls) wrote cycles at 06:03:23 mlb and 06:05:09 wnba, 50 rows each,cycle_hour_utc=6, features 50/50. (The HTTP request gateway-timed-out at ~2 min; the job completed server-side — verified in the DB, not from the response.) archetypeFIXED — 50/50 on MLB (was 0/100). Retention still COLLECTS at grade time (only moment features exist) but now PERSISTS after enrichment viaretentionService.mergeEnrichment, which fills ONLY archetype/team/opponent and never rewrites grade-time features or model output (test-locked).- 🔶
team/opponentSTILL NULL — upstream, not the merge. The ENRICHED grades themselves carryteam:null(verified live: 0/25 with team, 25/25 with archetype).resolvePlayerStatsdoesn't returnstats.teamfor MLB, and enrichment never setsopponentat all. Fixing that is an upstream change, deliberately not made under this order. - WNBA archetype is null — honest absence: WNBA stats don't resolve (Python service offline), so there is nothing to classify from.
- ZERO-WRITE ALARM ARMED —
opsWatch.retentionZeroWriteAlarmpages at missed-snapshot severity when a slot GRADED props but retention wrote fewer rows than the slate.runSnapshotnow returnsretentionRowsso the tick can evaluate it. Retention is best-effort by design, so a broken write is silent by construction; this is the counterweight. Verified quiet on a healthy run (gradeCount 25 → retentionRows 50). - INTRADAY DEFERRED (Phase 4).
intradayRefreshServicecallsanalyzeViaEngine1DIRECTLY — it never goes throughgradeAndCacheSlate, so there is noonGradedhook to reuse. Capturing it needs its own ctx + snapshot_id + persist path and would ~5× row volume. Not small; not done. - Outcome stamping still NOT implemented (depends on the settlement fix).
🟢 RETENTION LIVE — model_snapshots capturing (2026-07-20, migration 025 applied)
Phase 2 priority zero. History now compounds from tonight. Spec:
specs/snapshot-retention.md.
- Why: the ONLY model history was
ledger_entries(640 rows / 6 game days) and it stores NO model inputs. All ~30 other warehouse tables are empty. We could score our own grades but could not replay a different model — the only question a backtest answers. model_snapshots(migration 025, APPLIED): append-only, ONE ROW PER GRADED PROP PER SIDE PER CYCLE. Unique on(snapshot_id, player_key, stat, line, side)so a retried cycle can't duplicate. RLS on, service-role writes only.- VERIFIED CAPTURING (2026-07-20 03:02 UTC, 2 triggered cycles): 100 rows.
MLB 50 (14 graded / 36 refused), WNBA 50 (50 graded).
features,grade_11,p_win,ev_pctpopulated on 100% of graded rows; 1 model_version. - Refusals are captured — the ledger drops them entirely. First-ever refusal
breakdown:
juiced_no_edge18,rare_event_over_below_line13,insufficient_data5. The MLB gate refused 36 of 50 sides (72%) — whether that costs edge is now measurable for the first time. - Contract: retention is best-effort + injectable;
persist()returns errors and never throws. A retention failure can never break a snapshot. - 🔶 EV LOOKS OVERCONFIDENT — do not surface it as truth yet. First captured
EVs include +62.1%, +61%, +56.9% — implausible for real markets. Cause is the
quantile estimator clamping
p_winat 0.95 (PROB_CEIL) off ~10 games. Hero v2 already ranks by highestev_pct, so it will select the MOST overconfident read. Calibration (C-cal) must run on the harness before EV drives anything user-facing.
🔶 SETTLEMENT-CORRECTNESS AUDIT — two concrete findings (open, scheduled)
Logged from the Phase 2 history report; do NOT fix inside a retention build.
- ZERO pushes across 470 settled rows. Implausible — hits/TB land exactly on the number regularly. The exact-number push case is almost certainly mis-scored (silently counted as hit or miss).
- ~28 props/day never settle (Jul 17 MLB 86 graded/57 settled; Jul 18 103/75). Cause undiagnosed. Both are correctness bugs in the scoring that accuracy, ROI, CLV and every public record depend on.
🔴 MODEL-VERSION CONTAMINATION in ledger_entries (permanent, mitigate by cutoff)
ledger_entries mixes pre- and post-2026-07-19-fix grades with no version
marker, and the eras cannot be separated retroactively. Any backtest or
accuracy claim computed off existing ledger history must treat the fix boundary
as a hard cutoff. model_snapshots stamps model_version + code_sha on every
row so this can never recur.
🟢 BACKUP — DURABLE ON-BOX, VERIFIED BY READ-BACK (2026-07-20, deployed)
The highest-severity open item is closed on-box. Off-box is a tracked follow-up.
- Runs as CODE, not a cron somebody must install (
src/backupScheduler.js, armed inserver.js). Arming is opt-OUT: armed wheneverSUPABASE_DB_URLexists,BACKUP_CRON=0kills it, nightlyBACKUP_HOUR_UTC:BACKUP_MINUTE_UTC(default 03:10 UTC). The S62 design was opt-in and nobody ever opted in — that is why the DB went unbacked for weeks. Deploy == installed now. - DURABLE:
BACKUP_DIR=/app/backupsis a Coolify persistent volume, so dumps survive redeploys. The container-ephemeral risk is closed. - VERIFIED END-TO-END (2026-07-20 02:31 UTC):
POST /api/internal/backup/run→ exit 0 in 31s →vyndr-20260720-023136.dump, 821,549 bytes, 698 archive objects, ledger_entries present.GET /api/internal/backup/verifyread the rows back OUT of the archive withpg_restore: 643 rows in the dump vs 643 live — exact match, DB 18 MB.- The earlier figure of 604 was a moving target, not a discrepancy: the ledger is append-only and 39 rows were written between the census and the dump (2 manual regrades + the 01:00 UTC cron; newest lock 01:01:45 UTC). 604 + 39 = 643 reconciles exactly.
- Permission bug found and fixed by the real run: the first attempt failed
with
pg_dump: Permission denied— the container runs as non-rootvyndr(uid 100/gid 101) and the mounted volume was root-owned. The Dockerfile now creates + chowns/app/backups; the verify endpoint reportsuid/gid/backup_dir_writableso a mounted-but-unwritable volume is loud, not silent. - 🔶 OFF-BOX IS DEFERRED, NOT DONE. Storage Box SSH auth is not working yet,
so the rsync is gated behind
BACKUP_OFFBOX=1(+BACKUP_REMOTE+BACKUP_SSH_KEY) and currently logs "DEFERRED". A failing push is a LOW priority notice, never a failure — the durable on-box dump succeeded, and crying wolf trains us to ignore backup alerts. Remaining risk: a single Hetzner-side loss takes the dumps with it. To finish: install the generated public key on the Storage Box, setBACKUP_SSH_KEYin Coolify (port 23 — 22 is SFTP-only), flipBACKUP_OFFBOX=1. - Not yet done: a full restore into a scratch Postgres SERVER. The read-back proves the archive CONTAINS the data; a server restore would additionally prove schema/constraint restorability. It needs the dump off the container, which is blocked on the same Storage Box auth.
MODEL TRAIN — ARC 2 PREP: DEAD PROBABILITY LAYER REVIVED (2026-07-19, on main, DEPLOYED + fingerprinted)
Re-sequenced ahead of the gate work after the audit found the ranking signal did
not exist. Reports: specs/audit-data/gate-simulation.md (G-b/C-cal) +
specs/audit-data/grade-collapse.md (the big one).
- p_win/ev_pct/kelly/model_odds/value were absent on 100% of live grades —
gameLogServicereturns null for MLB by construction + the Python service is offline, someta.gameLogswas[]everywhere. EV, the Model Train's whole ranking signal, had never been computed on a live prop; hero v2 always fell through to its fallback; quarter-Kelly (sold on /pricing) never ran. FIX:featureCache.getStatRows. Fingerprinted live: p_win 0.523, ev_pct -10.4, model_odds -109 — the value triplet is whole. - Grade range restored ON MERIT (rescaling permanently rejected):
refreshTeamStatswired intorunSnapshot(it had ZERO callers), L20 made symmetric (no negative branch existed),game_count_in_7dderived. D now emits (WNBA 1/25, earned at p_win 0.365). A-emission awaits the 01 UTC snapshot —opp_rank_statneeds that run. MARKETING HOLD on "A-RATED" copy until then. - Calibration guard: consistency CV is NBA-tuned;
cv ~ 1/sqrt(mean)made every MLB statboom_bust. Floored atCONSISTENCY_MIN_MEAN=4. - Ledger truth (576 rows): only B/C ever emitted, 9 confidence values, ceiling 63; past -400 was -7.7% ROI (the -400 floor was the whole win); flex -161..-250 is our BEST band (+2.2%) so its EV gate ships DISABLED. Suite 276/3286, build 0.
MODEL TRAIN — ARC 1 (ENGINE) SHIPPED (2026-07-19, on main 7a925f4, pushed, NOT yet deploy-fingerprinted)
Spec written retroactively: specs/model-train.md (arc list + what's open). Goal of the
train: make "real opportunities at takeable prices" the ENGINE, not a display filter.
Arc 1 = steps 1–6, backend only. No frontend consumes the new fields yet — the
triplet/VALUE marker/refusal copy are Design's Session-2 surfaces.
- De-vig (
src/utils/devig.js, NEW): two-way multiplicative de-vig → fair prob + fair American price per side +overround. One side unpriced → fair is NULL, never faked. AlsoamericanToImpliedProb/americanToDecimal/impliedProbToAmerican. - EV (
devig.evPct):ev_pct = modelProb × decimal − 1, at the graded side's ACTUAL price. This is the ranking signal now, replacing raw |model − consensus|. - Takeable / value gates (
src/config/valueEngine.js, NEW):TAKEABLE_ODDS_CEILING−160,TAKEABLE_ODDS_MAX+200,VALUE_EV_THRESHOLD2 (%) — all env-tunable.takeable= price in band;value= takeable AND ev ≥ threshold. Strict null guards (Number(null)===0would have made a missing price "takeable"). - Hero v2 (
heroPropService): the hero is the highestev_pctamong takeable A/B reads — a huge model-vs-line gap on a −900 line is trivia, not an opportunity. Falls back to most-recent-real graded read on an empty slate (unchanged). - Value triplet on every graded read:
book_odds·fair_odds·model_odds(+fair_prob,overround,devig_method). Wired insideanalyzeViaEngine1's existing p_win/Kelly block; rides the...resultspread through snapshot / hero / scan. Documented inBACKEND_HANDOFF.md. - Test baseline: 276 suites / 3306 tests green (was 274/3289),
next buildexit 0. New suites:devig.test.js,valueEngine.test.js(+heroPropService.test.jsrewritten). - Knobs NOT in this arc (asked about, confirmed absent from the codebase):
EDGE_FLEX_WALL,HARD_JUICE_WALL,LADDER_ODDS_MAX,MIN_RUNG_PROBABILITY. The only price wall on disk is the flatJUICE_ODDS_FLOOR(−400) from the rare-event work below — it is not edge-aware, and there is no −250 wall.
RARE-EVENT UNDER SUPPRESSION (2026-07-19, on main)
Betting-logic audit: the CONSENSUS-vs-MODEL board flooded with "doubles u0.5 · MODEL 0.2 · +edge" fake reads (juiced rare-event unders). Report: the doubles projection is REAL per-player (MLB_LOG_FIELD doubles→doubles; values varied 0.03/0.16/0.2/0.22) — NOT a flat fallback; the issue is purely structural.
- Config-driven (
src/config/rareEventMarkets.js): RARE_EVENT_STATS = doubles/triples/home_runs/stolen_bases, RARE_EVENT_LINE_MAX = 0.5. - Grade layer (
analyzeViaEngine1): rare-event UNDER at ≤0.5 → always refused (grade null + suppressed); rare-event OVER at ≤0.5 → refused unless projection > line (a 0.2-over-0.5 carries the SAME |edge| as the under, so it'd just take its board rank — refusing it is what actually clears the market). OVER with a genuine projection > line still grades. - Board layer (
marketBreadth.collectBreadth): drops null-model rows — a suppressed/ungraded prop can't rank a "MODEL —" placeholder onto the board. - Suite 274/3289 green. Fingerprint: next MLB snapshot should have no rare-event u0.5 grades.
BACKUP + FOUNDER CHECKOUT (2026-07-18, on main)
- Task A — DB backup (SHIPPED; Kev cron+fingerprint on box)
c2c43cd: Dockerfile now has pg_dump/pg_restore/rsync;backup-db.shvalidates every dump viapg_restore --list(must contain ledger_entries). Runs IN the API container (SUPABASE_DB_URL is there; WSL2 can't reach Supabase). Runbook = host crondocker exec <api> sh /app/scripts/backup-db.sh. Mechanism fingerprint PASSED locally (137 rows → dump → validate → restore → 137 rows). Kev: install the host cron + run the prod restore fingerprint. - Task B — founder checkout SEAT-GATED (SHIPPED)
ccb9668:resolveCheckoutPriceattaches the founder price while seats remain (< FOUNDER_SEATS_TOTAL, samecountFounderSeats()truth as the meter), flips to standard at 100; meter shows SOLD OUT. Fixes "Claim a Founder Desk" charging $44.99 vs advertised $34.99. payment_failed grace 48h→14d (spans Stripe retries; revoke only on real cancel). Tested seat 0/99/100/null. NeedsSTRIPE_PRICE_*_FOUNDERset in prod for founder pricing to activate.
SECURITY + PLUMBING FOLLOW-UP (2026-07-18, on main) — code shipped, migrations authored
Chrome ops session findings (Supabase advisor + Stripe). Code items shipped + fingerprinted; DB migrations authored for Kev to apply (no DB access from the box).
- Item 0 (SHIPPED, fingerprinted)
3b12c6c: founder count = real active Stripe subs (stripeService.countFounderSeats), not DB tier fields. Phantom 1 (a comped profile) gone → prod nowavailable:false(founder price IDs unset in prod env — setSTRIPE_PRICE_*_FOUNDERto show the real 0). - Items 1,3,4,5 (AUTHORED — Kev applies in Supabase)
78c1929:migrations/023_security_hardening.sql(founder_pricing_seats → security_invoker; waitlist anon-INSERT-only; function search_path) +migrations/024_anon_revoke_discoverability.sql(revoke anon SELECT). Fingerprint = re-run Security Advisor. - Item 2 (SHIPPED — Kev sets env + cron)
5f5c004:scripts/backup-db.sh+docs/BACKUP-RUNBOOK.md. NeedsSUPABASE_DB_URL(direct :5432 URI) in Coolify + the cron on the Hetzner box. Fingerprint = restore a dump + count ledger_entries. - Item 6 (SHIPPED — Kev activates portal in Stripe)
889e862: portal proxy + "Manage billing" button. Dunning verified: cancel-on-exhaustion → subscription.deleted → 48h grace →gracePeriod.checkGracePeriodrevokes to free. Nuance: 48h grace on first payment_failed < Stripe's 2-week retry window (self-correcting). - Item 7 (SHIPPED)
ae3cff9: ESPN BET → theScore Bet (PENN) in books.js + ALLOWED_BOOKS; design HANDOFF flagged for the BookChip swap.
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.jsenforces. 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 |model−consensus| 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 inweb/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 pipelinerefreshed_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|>40is 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 labelsweb/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 breakscomputeEdge→ 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 surfacedconfidenceis 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), frontlib/archetypes.js+ backarchetypeService.jscolor-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$1Mcopy. - Rev 3 matchup chips (
f24f941,baf977f,bf8bf16):TeamChipprimitive (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)
- Palette (biggest): grade B-white/C-grey/red-#FF4757 + full surface/text/border ramp — every surface inherits.
- Archetype marks + colors — 44 archetypes render Design's real 83-mark set.
- Flat EDGE BOARD at 390px — the core mobile screen (rows/opacity-ramp/chips/hero-edge%).
- Grade-reveal mobile (74px hero + team chip) · app-bar clock (Hybrid) · M1a header collapse.
- TeamChip on board rows / grade reveal / pitcher-vs (real logo at 10-12px).
- 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).
7712f0aHeartbeat 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-minexpected_interval_sagainst it → structurally STALE.refreshed_at(the real 20-min intraday heartbeat) was written to Redis but NEVER serialized. FIX: full snapshot seedsrefreshed_at,/api/snapshot/summary+ GET/:sportexpose it,LiveLayerbadge measures from it. Fingerprint PROVED the pipeline was alive all along (refreshed_atadvancing 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+2d413cfPer-sport cadence (Job 1):src/config/sportCadence.js(config-over-constants). REALITY-CORRECTED: mlb/nba/wnba all keep the full grid14/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 to14/19+ NO intraday — the genuine win, protecting the scarce odds-api key. Scheduler fires at HOURS_UTC and grades onlysportsForHour(h); onlyintradaySports()refresh.2d413cfQuota 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 hitrecordCall(the ONLY place the WARN/BLOCK pager fires) → the counter reached 100% via silent header-sync. FIX: futures now routes throughgateway.fetch('odds-api', …)(counted + blocked); a RESERVE floor (ODDS_API_RESERVE=50) refuses DISCRETIONARY calls (futures/soccer) whileremaining ≤ reserveso 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-alerttest-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 inertAPI_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.getPlayerGameLogfeedsfeatureCachewhen the Python source is null → l5/l20 → props grade. Verified live (A'ja Wilson→B, Ionescu→C off real ESPN form). Endpointsite.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:
defaultGetPlayerStatsroutes nba/wnba to the ESPN gamelog in BOTHoutcomeService+ledgerService; separateNBA_BOX_KEY/NBA_COMBOsettle maps; final-honesty guard (never settle in-progress);opsWatchfinals-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
/newswire (newsService+/api/news/:sport) — verified live (real headlines);NewsWire+FuturesBoardon/explore(offseason-aware, self-hide on empty, futures "TRACKED · NOT GRADED"). KNOWN GAP:/api/futures/:sportreturnsmarkets:[]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=0kill-switch; 12h TTL; 1 credit/refresh. - Also rode along:
espnAthleteIndexcross-sport headshot capture (headshot-coverage) — NBA/WNBA/NFL/NHL real headshots when in-season. DEFERRED (locked decisions): soccer — grade+settle path, needsAPI_FOOTBALL_KEY(VALIDATED, Free 100/day, active to 2027 — set it in Coolify envAPI_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 (mlbStatsAdapterteamHint 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 theenrichedgrade atsnapshotService(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 ingetModelAggregate). - 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
vyndrsurfaces the realuser_id=NULLpublic model record + per-tier calibration + 1080×1350 portrait/OG (user-handle privacy 404s stay byte-identical); Baseball Savant pitcher-arsenal (savantAdapter, free CSV, verified) →PitcherArsenalcard, 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 onAPI_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 DS0–DS5 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-matchedSkeletons replace text-wall loaders, scan→ledger persistence viasession.access_token. DS1 follow-up (cf91c04): closed the sb-token trust-bug CLASS —lib/authToken.jscurrentAccessToken()reads the REAL Supabase session (not the OAuth-onlysb-tokenkey) 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 bycolorContract.test.js(fails on violation). - DS4 Billboards (
45bafbc): STREAKS row (length as mono hero), grade reveal (sign-colored edge), CLV reframe (flat says so),/upublic 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.jsengine + 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), unifiedEmptyState(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. Thedesign/v2-train+ per-DS worktree branches can be pruned. StaleoriginGitHub remote still carries the long-dead leaked PAT — deploy runs offgitea, 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/summary → expected_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/*behindVYNDR_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=0kills); desk-ready ping after the day's first slot; daily pulse 13:00 UTC; missed-cron + 3-slot-failure + quota pagers → ntfyvyndr-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 001–022 applied, files insupabase/migrations/). - Deploys: gitea
git.builtbykev.com/builtbykev/vyndr→ Coolify webhook (HMAC secret fixed Jul 11; auto-deploy proven). GitHuboriginremote 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)
SNAPSHOT_EXPECTED_INTERVAL=1200— confirmed absent (fresh restart still served the 18000 default). SYNC badge tracks intraday cadence once set.DESK_OWNERS=kevdevelops@gmail.com— /desk 403s everyone until set.LISTMONK_URL/LISTMONK_USER/LISTMONK_TOKEN/LISTMONK_LIST_ID(all four together) — newsletter no-ops politely without them; box-side setup indocs/NEWSLETTER.md.- 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 3–4 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 —