-- Migration 038: model_snapshots.chain_shadow — the SHADOW CHAIN read. -- -- The chain (src/services/model/chain.js) computes a probability by chaining -- base-event atoms: p per plate appearance from the PA outcome tree, chained -- over expected plate appearances. It is SERVED BY NOTHING. The counter -- (probabilityEstimator) remains the only thing a user ever sees. -- -- WHY A COLUMN AT ALL. Evidence you cannot adjudicate is not evidence. A chain -- probability stored on its own could only ever be compared to itself. This -- column stores the chain's number NEXT TO the number it must beat, on a row -- that already carries the result: -- -- chain_shadow.chain_p the chain's probability, SIDE-ALIGNED -- chain_shadow.counter_p the served counter's p_win for that same side -- model_snapshots.outcome written by the ordinary settle pass -- -- Those three make the triple. Without all three on one row, the chain could be -- described but never judged. -- -- SIDE ALIGNMENT IS LOAD-BEARING. The chain computes P(over the line); p_win is -- expressed for the graded side. An under row stores 1 - p_over. Storing the raw -- over-probability against an under row would invert every later comparison, -- silently. -- -- IT IS LABELLED UN-SERVABLE IN THE DATA, not only in a comment: every block -- carries status 'UN-SERVABLE' and servable=false, because it was produced with -- requireCalibrated=false and the chain has never passed a calibration gate. -- -- A SEPARATE COLUMN, not extra keys inside `features` — champion-ablation.js -- iterates every key of `features` for its residual scan, so widening it would -- silently enlarge that multiple-comparisons denominator. Same reasoning as 034. -- -- NULLABLE and populated only on MLB `hits` rows the chain could read, so the -- storage cost is bounded. Adding a nullable column is metadata-only in -- Postgres — no table rewrite — which matters while this database sits near its -- free-tier size cap. ALTER TABLE model_snapshots ADD COLUMN IF NOT EXISTS chain_shadow jsonb; COMMENT ON COLUMN model_snapshots.chain_shadow IS 'Chain v1 SHADOW: side-aligned chain_p + the served counter_p, forming the (chain_p, counter_p, outcome) triple with model_snapshots.outcome. UN-SERVABLE (requireCalibrated=false); read by no serving path.';