Data engineering · dbt contracts

warehouse-quality-gate

A dbt contract that blocks a bad batch before it reaches the mart. The sabotaged batch loads without a single error and reports revenue of $4,905,051 instead of $395,751 — a 1,139% overstatement that a plain pipeline waves straight through. The contract fails 12 tests and the mart is never built.

Public · synthetic demo
The problem

"It loaded without errors" is not a quality signal.

Every one of the twelve defects in the sabotaged batch is a defect that survives a plain load. Nothing throws. A duplicated customer file, an order amount that arrived in cents, a status with a capital D — the rows land, the job exits 0, and the number a stakeholder reads is wrong by an order of magnitude.

The money shot

Same sabotaged batch, loaded two ways

① Plain load vs the contract
runrows loadedrevenue reportederrorstests failed
Plain load — clean batch900$395,751.280
Plain load — sabotaged901$4,905,051.180
Contract — clean batch900$395,751.2800 of 15
Contract — sabotagedblockednot built1212 of 15
12/12planted defects caught
1,139%overstatement a plain load ships
0false alarms on the clean batch
② Two defects worth expanding on

D9 tests the raw value, not the normalised one. It is tempting to lower() the status in staging and test the clean column — but then the test can never fail, and the downstream filter doing an exact match on 'delivered' still silently drops the row. The model exposes both status_raw and status_normalised; the contract tests the raw one.

D7 is a contract, not a bug. Nothing is wrong with an EUR order. What is wrong is that fct_revenue_daily sums amount without conversion, so mixing currencies makes the total meaningless. The single-currency rule is written down as a test precisely because the assumption lives in a model somewhere else.

③ The twelve, and what caught each
#what arrivedcaught by
D1customer_id 8 duplicated — a re-sent file loaded twiceunique
D2customer 16 email arrived as "", not NULLnot_null
D3country USA instead of ISO-2 USaccepted_values
D4signup_date in 2027 — an upstream timezone bugnot_in_future
D5order 101 references customer 99999, which does not existrelationships
D6order 201 amount is -450.00 with status placednon_negative
D7orders 301–302 switched to EUR while the mart sums as USDaccepted_values
D8order 401 amount 4,500,000 — a cents/dollars mix-upwithin_magnitude
D9status Delivered with a capital Daccepted_values on raw
D10order 601 present twice — double revenue recognitionunique
D11order 701 dated before the reporting window openswithin_reporting_window
D12order 801 amount arrived empty and loads as 0not_null
Reproduce it
pip install dbt-core dbt-duckdb
python3 scripts/make_batches.py     # regenerates both batches, seed is fixed
./scripts/run_evidence.sh           # runs the contract over each, writes evidence/
python3 scripts/naive_vs_gate.py    # shows what a plain load reports instead

The warehouse here is DuckDB so the whole thing runs on a laptop in under a second. The contract is ordinary dbt — moving it to Snowflake, BigQuery or Postgres is a change of profiles.yml, not a change of tests.

Honest limitations
· Synthetic batches (900 rows, fixed seed) — a demonstrator of the method, not a benchmark.
· Contracts stop bad data. They do not tell you a job never ran, or that a scraper returned
  an empty page — those are pipeline-heartbeat and scraper-canary.
· The twelve defects are the ones that survive a plain load. Defects that crash the load
  are already visible and are deliberately out of scope.
This is a synthetic sample demonstrating the method. Inspect the checks, the fixtures, and the reproducible evidence ↗