From 4ddfb84232c0081f34c45f340e039e206aa5df9f Mon Sep 17 00:00:00 2001 From: Kev Date: Sat, 11 Jul 2026 00:18:01 -0400 Subject: [PATCH] =?UTF-8?q?Session=20A=20(night2):=20AUTONOMY.md=20+=20SNA?= =?UTF-8?q?P=5FTTL=206h=E2=86=9224h=20P0=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overnight cron gap (03→14 UTC) is 11h; a 6h snapshot TTL meant the morning settle pass read an expired snapshot and the accuracy loop silently settled nothing. Ledger (Postgres) settle was immune. Co-Authored-By: Claude Fable 5 --- AUTONOMY.md | 80 +++++++++++++++++++++++++++++++++ src/services/snapshotService.js | 6 ++- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 AUTONOMY.md diff --git a/AUTONOMY.md b/AUTONOMY.md new file mode 100644 index 0000000..2748efe --- /dev/null +++ b/AUTONOMY.md @@ -0,0 +1,80 @@ +# AUTONOMY — "Kev touches nothing for 7 days, what happens" + +Session 60 (night2), traced on d10bb4c + tonight's fixes. Every step cites +its code. Verdict at the bottom. + +## The daily loop (all times ET / UTC) + +**10:00 AM ET (14:00 UTC) — cron slot 1** (`snapshotScheduler.js` HOURS_UTC += 14,19,22,1,3; armed by `SNAPSHOT_CRON=1` in server.js): +1. **Settle pass runs FIRST** (`tick()` order: settleAllOutcomes → + settleAllLedgers → runAllSnapshots): + - `outcomeService.settleAllOutcomes` — settles yesterday's snapshot + grades vs real MLB game logs → `outcomes:{sport}:log` + + `accuracy:{sport}` + `accuracy:overall`. + **P0 FIXED TONIGHT:** `SNAP_TTL` was 6h but the overnight gap + (03:00→14:00 UTC) is 11h — the snapshot EXPIRED before this pass and + the accuracy loop settled nothing. Now 24h (`snapshotService.js`). + - `ledgerService.settleAllLedgers` — settles Supabase `ledger_entries` + (game_date < today ET): outcome + actual_value + CLV vs the captured + closing line. Reads Postgres, immune to Redis TTLs. Idempotent + (`.is('outcome', null)` guard) — re-runs are no-ops. +2. **Snapshot pass** (`snapshotService.runSnapshot` per sport): + - `getOdds` (PropLine free keys, odds-api backup) → date-pinned; a + morning with no lines yet = honest `skipped: no props` (NOT retried — + only thrown/null responses retry once). + - grades → normalize/dedupe → archetype+team resolve (free statsapi) + → `snapshot:{sport}:latest` + `grades:{sport}` (6h TTL — outlives the + 5h max intra-day gap) → ticker events. + - **Ledger write**: `recordPipelineGrades` upserts public rows + (user_id NULL, `ignoreDuplicates` — re-runs never dupe or overwrite + the original lock). `captureClosing` overwrites today's unsettled + rows' closing_line/odds with current feed values. + +**3:00 PM / 6:00 PM / 9:00 PM / 11:00 PM ET — cron slots 2–5**: same tick. +Each run re-captures closing (the LAST write before a game starts is the +real close — after start the prop leaves the feed, so it freezes), computes +line deltas vs the previous snapshot (ticker MOVE), and upserts any newly +posted props as new locked rows. + +**Overnight**: games finish; nothing runs until 10 AM, when the settle pass +closes yesterday's book. Every settled row gets outcome/actual/CLV exactly +once. + +## Self-healing / watchdogs +- Missed-cron watchdog (per-minute, `isSnapshotOverdue`) → ntfy alert + (`vyndr-pipeline-kev2026`) once per missed slot. Snapshot success/empty/ + failure also alert (`opsNotify`, never throws). +- `GET /api/internal/snapshot/status` (internal key) → cron_armed, last + snapshot, redis keys, `overdue` flag. +- Schedule/streaks/hotlist read caches self-heal on miss (cache-aside ESPN, + date-pinned since Session 57). Public snapshot/ledger reads are + cache-only and can never drain quota. + +## What accumulates without touch (day 1 → day 7) +- Day 1: first locked rows; MODEL tab "RECORD BUILDING · N pending". +- Day 2 morning: first settles + CLV (closing captured day 1). AccuracyBadge + leaves "LEARNING" at 8 settled; ledger aggregates render % at n≥20. +- Day ~3–4 (MLB volume ~20-25 graded/day, all settleable): hit% + beat-close% + go live on /ledger MODEL, landing ModelRecord, player strips. +- Day 7: 30d-window aggregates, per-player records accruing. WNBA rows + accumulate PENDING (honest) until Phase 4.5 (ESPN box-score settle, + due ~Jul 24) — the one known credibility clock. + +## Dependencies that must stay true (the only touch-points) +1. Coolify container running with `SNAPSHOT_CRON=1`, PropLine keys, + SUPABASE_URL + service key, REDIS_URL. (One-time, already set.) +2. Redis persistence is server-side config (Session 52) — snapshot keys + vanishing on restart = Coolify Redis setting, not code. The LEDGER is + Postgres and survives anything. +3. ntfy channel monitored casually — alerts are informational; the loop + needs no response. + +## Verdict +**Zero-touch for 7 days: YES for the core record** (grade→lock→close→ +settle→aggregate, all scheduled in-process, idempotent, alert-covered) — +after tonight's SNAP_TTL fix, which was the one broken link (morning +settles silently skipped). Known honest gaps: WNBA settles pending until +4.5; streaks/hot-list data production was EXTERNAL (tank01-prefetch via +n8n/cron, possibly unarmed) — Session B moves log production into the +snapshot pipeline so the aggregator feeds itself. diff --git a/src/services/snapshotService.js b/src/services/snapshotService.js index 67b4f91..5472626 100644 --- a/src/services/snapshotService.js +++ b/src/services/snapshotService.js @@ -17,7 +17,11 @@ * ticker:items — capped array of ticker events (newest first) */ -const SNAP_TTL = 6 * 3600; // 6h — a snapshot is valid until the next run +// Session 60 (AUTONOMY P0) — 24h, NOT 6h. The overnight cron gap is 11 hours +// (03:00 UTC → 14:00 UTC); with a 6h TTL the morning settle pass read an +// EXPIRED snapshot and the accuracy loop silently settled nothing. The +// snapshot must survive until the morning-after settle reads it. +const SNAP_TTL = 24 * 3600; const TICKER_TTL = 24 * 3600; const TICKER_CAP = 50; const DELTA_NOISE = 0.5; // ignore movements smaller than this