gallery unit · data core
Schema-drift contract guardrail
The dashboard says revenue fell by half. It did not: an upstream feed switched cents to dollars, and every check on the pipeline still passes.
scenario sandbox
You are the analytics engineer. This morning's load is 240 rows of an ELT landing table, and it may carry one of these drifts. The real contract runs in your browser, computed live, not canned.
inject a drift
clean pass
The table as landed passes both checks. The report reads revenue $299,354.48 · 24 refunds · 3 currency buckets.
no violations, clean data passes
passed, clean data is valid
revenue $299,354.48 · 24 refunds · 3 currency buckets
Baseline: the table as landed. Nothing should fire. Same contract and standard check as the Python harness; verified to reproduce its 6/6-vs-1/6 verdicts on this table. The one drift the standard check catches is the only one that leaves the observed range.
what you are looking at
01 · data in
A synthetic ELT landing table of payment rows loads in your browser. The drift you pick is injected into a copy of it.
02 · check runs
The contract checks value domains, a magnitude band, and a precision signature. The standard schema / null / row-count check runs beside it.
03 · verdict out
Both verdicts land side by side, plus the number the downstream report would emit.
Go deeper
Watch the walkthrough (45 s, silent)
Silent drifts at the door, caught before the warehouse · recorded from this page, unedited.
The measured result: 6/6 vs 1/6
The drift keeps the column present and non-null. So the schema check passes, the null check passes, the row-count check passes, and the number goes quietly wrong.
Six drifts, one landing table
guardrail 6/6 · standard check 1/6 · ~0.1 ms/check
| Drift | What it does downstream | Std check | Contract |
|---|---|---|---|
| Partial unit changecents read as dollars, still in range | revenue reads $157,512 (true $299,354) | misses | catches |
| Precision truncationfloored to whole dollars | sum drifts; values suspiciously round | misses | catches |
| New enum valueunannounced CAD | a currency bucket nobody handles | misses | catches |
| Boolean re-encoding'true' re-encoded as 'TRUE', '1', or 'yes' | refund count silently drops to 0 | misses | catches |
| Type wideningint widened to text, coerced past | the revenue sum can't run | misses | catches |
| Out-of-range scalethe one loud drift | values blow past the max | catches | catches |
The standard check catches exactly one drift, the only one whose values leave the observed range. Every other drift keeps each value individually valid and corrupts the aggregate. The unit-scale one makes the revenue report read $157,512 against a true $299,354: no error, no null, quoted in the meeting.
Where the problem statement comes from
“A crashed pipeline gets fixed. A lying one gets quoted in a board meeting.”
dlt (dlthub) engineering blog · “Schema evolution in data pipelines” · Aman Gupta · 2026-06-05
The full mechanism: why the usual gate fails
- A schema check reads the shape. Column present, dtype unchanged, non-null, row count identical, values inside the observed range. Every silent drift preserves all five. The inferred schema signs off.
- The corruption is in meaning, not shape. A currency that split into two spellings, a boolean that grew a third encoding, a cents column that started shipping dollars, each value still looks valid. Only the aggregate is wrong.
The contract pins meaning: the declared dtype, the allowed value domain of each categorical column, a magnitude band for the money column, and a ceiling on how “round” those values may be. A drift that survives shape checks still has to survive the domain, the band, and the precision signature, and these don’t.
standard check : present? non-null? same dtype? in [min,max]? same rowcount? → all pass
contract : currency ∈ {USD,EUR,GBP}? amount mean within ±20%? ≤10% whole-dollar?
→ new "CAD", or a mean off by 47%, or 30% suddenly round → FLAGGEDEvidence + how to reproduce
One landing table, six drifts, scored by the contract guardrail and by a real off-the-shelf control (pandera’s inferred schema at defaults, plus explicit row-count and null-ratio checks). The control is not a strawman: it catches the one drift that leaves the observed range, and only that one. The rest it waves through, and the downstream number moves. Synthetic data (drift needs controlled before/after); stated, not hidden.
make setup && make test && make run # $0, laptop, no GPU, no network
Honest gaps
The contract’s categorical domains are declared, so a genuinely new-but-legitimate category reads as a drift until the contract is updated; that friction is the point, but it is friction. The magnitude and precision bands are fitted from a golden sample and tuned for this table; production needs per-column tuning. And the hardest drift class is out of reach of any per-column contract: a cross-field one, say, amounts that stop being normalized to a common currency while every column stays in its own domain, corrupts the sum with no new value anywhere, and needs a reconciliation total, not a schema. That is the next guardrail, not this one.