MLB canonical event identity, impossible-binding refusal, event-aware dedupe, publication commit

Release-isolated slice built from 41ba38e. Ships ONLY the event-integrity +
publication + lineage-canary closure; the 90-path development tree stays
undeployed.

- canonical MLB event identity from statsapi gamePk (mlb:gamepk:<pk>), with
  event_identity_source/method/version recorded. The id is canonical; the
  binding is derived and says so.
- IMPOSSIBLE-BINDING REFUSAL. Verified in prod 2026-08-26: Joe Mack (Marlins)
  was bound to Dodgers@Braves and Yandy Diaz (Rays) to Rangers@WhiteSox, both
  from one book in the 01:00/03:01 UTC cycles after their own games began. Root
  cause is source market data, not the binder. A prop whose player's team is not
  an event participant now refuses; unknown team preserves uncertainty.
- event-aware dedupe: books still collapse, events no longer do. An unresolved
  MLB event fractures rather than falling back to the collision-prone
  date+teams key.
- publication commit moved AFTER the authoritative Redis slate write, with
  exact parity-gap identity when the product publishes and the record does not.
- lineage dual-write behind LINEAGE_CANARY_SPORTS, DISABLED for this deploy.

Excluded deliberately: WNBA feed/chain, market ontology, PerformanceDistribution,
calibration certification, truth diagnostics, applyRevision Phase-1, and the
analyzeViaEngine1 confidence-rounding change (a served field).

Suite 380/5,040/0 from this worktree; web tsc exit 0; champion output identical
to production.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
Kev
2026-08-27 16:27:36 -04:00
parent 41ba38ed4e
commit 352016790a
16 changed files with 4217 additions and 6 deletions
@@ -0,0 +1,52 @@
-- Migration 046: publication semantics (ADDITIVE ONLY).
--
-- Every statement is ADD COLUMN IF NOT EXISTS or CREATE INDEX. Nothing is
-- dropped, renamed, retyped or backfilled. Historical rows keep NULL.
--
-- ── WHY 045 WAS NOT ENOUGH ────────────────────────────────────────────────
-- Migration 045 gave `model_snapshots` a lineage. Tracing the publication path
-- afterwards showed the lineage would have described the wrong population:
-- `gradeSlateService` fires its retention hook with BOTH sides, graded AND
-- refused, BEFORE any filtering, and only the higher-confidence graded side
-- becomes the served Read.
--
-- MEASURED, mlb 2026-08-26: of 13,012 captured rows, 3,859 were refusals and
-- only 4,569 matched a prop that reached the ledger. **8,443 rows (64.9%)
-- describe a state no user was ever shown**, and nothing on the row said which
-- was which. Building chronology over all of them would have produced
-- "publication history" for model activity that was never published.
--
-- `published` is the smallest missing link. It is set by the collector's
-- publication signal at the one place that knows the answer — the moment the
-- winner is selected — and defaults FALSE, because a capture is not a
-- publication until something says it is.
ALTER TABLE model_snapshots ADD COLUMN IF NOT EXISTS published boolean;
-- ── WHY THE PUBLISHED STATE ADVANCED ──────────────────────────────────────
-- The revision ordinal says WHEN. It cannot say WHY, and the difference is the
-- whole product question: VYNDR's belief can hold still while the market moves.
-- A replay of one real date found 4,507 digest changes of which only 301 moved
-- the grade — 4,206 were price alone. One undifferentiated "revision" count
-- conflates a changed opinion with a changed price.
ALTER TABLE model_snapshots ADD COLUMN IF NOT EXISTS change_type text;
-- ── CLAIM SEMANTICS ARE VERSIONED ─────────────────────────────────────────
-- A digest is only interpretable against the claim definition that produced it.
-- The first digest listed only `locked_odds` while this table stores
-- `over_odds`/`under_odds`, so it was blind to price on exactly the rows it
-- digested — caught by a replay crashing on a missing column, i.e. by luck.
-- These stamps make the next such change visible instead of silent. A
-- historical digest is never recomputed under a newer definition.
ALTER TABLE model_snapshots ADD COLUMN IF NOT EXISTS claim_schema_version text;
ALTER TABLE model_snapshots ADD COLUMN IF NOT EXISTS digest_algorithm_version text;
-- Publication chronology is read published-only; the partial index matches.
CREATE INDEX IF NOT EXISTS model_snapshots_published_lineage_idx
ON model_snapshots (read_id, revision_ordinal) WHERE published IS TRUE;
COMMENT ON COLUMN model_snapshots.published IS
'TRUE only for the side that actually became the served Read. Measured: 64.9% of captured rows describe a state no user was shown, and before this column nothing on the row distinguished them. NULL on rows written before migration 046 - unknown, not reconstructed.';
COMMENT ON COLUMN model_snapshots.change_type IS
'INITIAL_PUBLICATION | BELIEF_CHANGE | MARKET_REPRICE | MARKET_LINE_CHANGE | COMPARISON_CHANGE | PROVENANCE_CHANGE | PRESENTATION_CHANGE | MIXED_CHANGE | NO_MATERIAL_PUBLISHED_CHANGE. Ordinal says when; this says why.';
COMMENT ON COLUMN model_snapshots.claim_schema_version IS
'The claim definition in force when the digest was computed. Never recompute a historical digest under a newer definition and present it as original.';