Files
vyndr/supabase/migrations/033_lock_lines.sql
T
builtbykev c7067c80c4 Persist lock-time multi-book lines to lock_lines (unblocks the staleness audit)
The over-side skew audit's confirming check — was our locked line stale-high vs
consensus AT LOCK — was BLOCKED because multi-book lines at lock were never
persisted (bookprices is Redis current-only). This persists them.

- migration 033: lock_lines table (tracked + applied to prod). One row per
  (graded prop × book) with both odds + a lock timestamp. RLS enabled, NO
  policies -> service-role only (fence). UNIQUE key -> idempotent re-runs.
- lockLineCapture.js: buildLockRows (pure, graded-props only, honest-absent
  single-book) + idempotent upsert persist. Built from the in-memory props at
  the LOCK moment (ts) -> no Redis re-read, no TTL race.
- snapshotService: persist right after `enriched` (the lock moment; gradedAt
  uses the same ts). Best-effort + fenced.

FENCE (measurement-only): lock_lines is read by NOTHING on the grade path
(gradeSlateService, snapshot dedup/indexOdds, challengers, selector, ledger) —
a grep test asserts it, and RLS locks it to the service role. Grade byte-
identical proven: runSnapshot grades are identical with persist on/off (test).

Volume ~1.5-3k rows/day (graded props x books x 5 snapshots); weeks retained,
no pruning needed short-term. Does NOT retroactively fix the existing 62 rows —
future accrual only; confirmation still needs weeks of settled rows. Full suite
3842 green, web build exit 0. No grade/locked_odds/outcome/served surface changed.

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

43 lines
2.2 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Migration 033: lock_lines — multi-book lines at the LOCK moment (measurement-only).
--
-- The lock-time analog of `closing_captures`. As a grade LOCKS to a line, this persists
-- each book's line + both-side prices for that (graded) prop, timestamped at the lock
-- moment. A future audit joins lock_lines (lock) to closing_captures (close) to answer
-- the currently-BLOCKED question: was our locked line stale-high vs consensus AT LOCK?
--
-- FENCE: measurement/display-only. It must NOT feed the consensus-line selector, the
-- champion, any challenger, the graded line, or the ledger. Enforced by (a) a separate
-- table nothing on the grade path reads, and (b) RLS enabled with NO policies, so only
-- the service role (which bypasses RLS) can read/write it — no client, no anon.
--
-- One row per (prop × book) carrying BOTH over_odds + under_odds (the de-vig needs both;
-- one row halves volume vs closing_captures' per-side rows). Append-only; the UNIQUE key
-- makes a re-run (snapshot retry) idempotent. Honest-absent: single-book props persist
-- as one row — never a fabricated second book.
CREATE TABLE IF NOT EXISTS lock_lines (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sport text NOT NULL,
player_key text NOT NULL,
player_name text,
stat text NOT NULL,
game_date date,
game_time timestamptz,
book text,
line_type text, -- 'sharp' (pinnacle) | 'book'
line numeric,
over_odds integer,
under_odds integer,
locked_at timestamptz NOT NULL, -- the LOCK moment (snapshot gradedAt ts)
missed_reason text,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT lock_lines_uniq UNIQUE NULLS NOT DISTINCT (sport, player_key, stat, game_date, book, locked_at)
);
CREATE INDEX IF NOT EXISTS lock_lines_join_idx ON lock_lines (sport, player_key, stat, game_date);
CREATE INDEX IF NOT EXISTS lock_lines_locked_at_idx ON lock_lines (locked_at);
-- FENCE: RLS on, no policies → service-role only. Never reachable by a client/anon,
-- and never by the grade path (which does not query this table).
ALTER TABLE lock_lines ENABLE ROW LEVEL SECURITY;