Vipra Software Articles Data Contracts That Stick
Data Contracts dbt Great Expectations Governance CI Enforcement Culture

Building a Data Contract System
That Teams Actually Follow

The tooling is the easy half: dbt enforces schema, Great Expectations enforces distributions, CI blocks breaking merges, Slack routes violations to producers. Contracts die from missing ownership and consequences — the three cultural mechanics in the second half are the actual product. Most vendors talk tools; this is the implementation that survives a year.

Discipline
Governance / Data Contracts
Easy Half
3-layer enforcement stack
Real Product
3 cultural mechanics
Vipra Proven
40% reconciliation cut
Stack
dbt · GE · CI · Slack
Published
June 2026
Executive Summary

A data contract is a versioned, machine-enforced agreement between a producer and its consumers covering schema, semantics, freshness, and volume. Every platform team can stand up the tooling in a sprint — and most contract programs are dead within three quarters anyway, killed not by weak tools but by missing ownership and absent consequences.

This article gives both halves their due: the three-layer enforcement stack (dbt for shape, Great Expectations for behaviour, routing that pages producers rather than the data team), and then the three cultural mechanics that determine survival — consumer-requested contracts, pre-agreed boring consequences, and contracts as PR-reviewed code.

The discipline is production-proven: Vipra's Fortune 500 governance engagement used exactly this make-disagreement-visible machinery to cut reconciliation effort 40%, and the contract pattern recurs across our healthcare (clinical quality metrics), supplier-mesh, and inventory playbooks. Tools are quoted; culture is the differentiator.

01 · Why Contract Programs Die in Quarter Three

The lifecycle is so consistent you can schedule it. Q1: platform team ships contract tooling, mandates specs on every table, demo goes well. Q2: producers treat the YAML as paperwork; violations alert a channel nobody triages; the data team quietly fixes breaches itself to keep dashboards alive. Q3: a reorg moves one sponsor, the checks get muted "temporarily," and the program joins the wiki of initiatives past.

Each failure has the same root: the contract described an obligation nobody had agreed to bear. Mandated contracts create checkbox compliance; alerts routed to the data team make breaches the victim's problem; consequences improvised per incident make enforcement political. The fixes are cultural, and they are Section 06 — but the tooling must be in place first, because culture without enforcement is just a memo.

02 · The Architecture: Contract as Code, Enforcement as Pipeline

contract
Versioned spec in the producer's repo. Schema, semantics, freshness SLA, volume bands, owner handle, consumer list, breach tier. Reviewed in the same PR as the code it governs.
layer 1
Shape, in CI. dbt model contracts (enforced: true), keys, relationships, accepted values. A producer cannot merge a change that breaks the declared shape.
layer 2
Behaviour, post-load. Great Expectations / Soda: volume vs baselines, null-rate drift, distributions, freshness SLAs. About the data, not the code.
layer 3
Routing. Violations page the producing team's channel with the contract link, diff, and affected consumers. The data platform team is cc'd, never assigned.
ledger
Breach ledger + tiered consequences. Auto-created tickets with SLA by tier; escalation pre-agreed; the quarterly report nobody can argue with.

03 · The Violation Data Flow: From Breach to Fix

producer ships DDL / data drifts (two distinct paths) │ │ ▼ code path (CI) ▼ data path (post-load) ┌──────────────────────────┐ ┌────────────────────────────────┐ │ dbt build + contract │ │ GE checkpoint vs baselines │ │ validation in PR │ │ volume · nulls · dist · fresh │ │ breaks shape → merge │ │ anomaly → violation event │ │ blocked, producer sees │ └───────────────┬────────────────┘ │ exactly what + who │ │ └──────────────────────────┘ ▼ ┌──────────────────────────────────────┐ │ ROUTE TO PRODUCER (never data team) │ │ #team-payments-alerts: │ │ contract: orders_v2 · tier 1 │ │ expected: ≥1.8M rows · got: 0.4M │ │ consumers affected: finance_mart, │ │ exec_dashboard, ml_churn │ └───────────────┬──────────────────────┘ ▼ auto-ticket on producer's board tier 1 = same-day SLA · tier 2 = sprint ▼ BREACH LEDGER → quarterly review: breaches ↓ · MTTR ↓ · trust ↑

The flow's two non-obvious choices: code-path and data-path violations are different events with different audiences (a blocked merge teaches at review time; a post-load anomaly demands triage), and the alert payload always names the affected consumers — abstract breaches get deprioritised, but "you broke finance_mart and ml_churn" has a constituency.

04 · Layer 1–2: Schema, Then Everything Schema Misses

the contract spec — one file, both layers (YAML)
contract: orders_v2 owner: team-payments # a Slack handle, never a person tier: 1 # breach SLA: same-day consumers: [finance_mart, exec_dashboard, ml_churn] schema: # → Layer 1, enforced in CI via dbt model: fct_orders contract: {enforced: true} columns: - {name: order_id, type: string, tests: [not_null, unique]} - {name: order_total, type: numeric(18,2), tests: [{accepted_range: {min: 0}}]} - {name: currency, type: string, tests: [{accepted_values: [USD, EUR, GBP, INR]}]} semantics: # the clauses schema validators can't check order_total: "gross, includes tax, transaction currency" grain: one row per order line behaviour: # → Layer 2, post-load via GE/Soda freshness: {landed_by: "06:00 Europe/Dublin"} volume: {min_rows_daily: 1.8M, vs: trailing-28d-dow} null_rate: {customer_id: "< 0.5%"}

Layer 1 runs in CI on every PR — shape breaks are caught before they ship, when the fix costs a code review comment. Layer 2 runs post-load against learned baselines, because schema tests pass while the data goes wrong: volume collapses, 100× drifts, staleness, and null-creep are behavioural failures only baselines catch. The semantics block is the most-skipped and highest-value section — most expensive breaches are two teams agreeing on a column name while disagreeing about what the number means.

05 · Layer 3: Routing — Violations Reach the Producer

The single most important configuration in the entire system: violations page the producing team, not the data team. Every contract declares an owner — a team handle that survives personnel churn — and the alert lands in that team's channel with the contract link, the expected-vs-observed diff, and the named consumers affected. The data platform team is cc'd for awareness, never assigned for resolution.

This one routing decision encodes the entire philosophy: the contract is an obligation the producer accepted toward named consumers, and the breach is theirs to fix. Estates that route violations to the data team have built an expensive monitoring system for the data team's own anxiety — producers never feel the feedback, behaviour never changes, and the program dies in the usual quarter.

06 · The Cultural Mechanics: The Actual Product

1 — Contracts are created at the consumer's request, never imposed. Platform-mandated contracts on every table produce resentment and checkbox YAML. The working pattern: when a consumer — finance, ML, an exec dashboard — depends on a dataset, they request the contract, and the negotiation (columns, freshness, breach behaviour) is a 30-minute meeting between the two teams. The contract documents a relationship that already exists; that is why it gets honored. Coverage grows organically along the lines where breakage actually costs money — which is exactly where it should.

2 — Breaches have a pre-agreed, boring consequence. Not punishment: process. A breached contract auto-creates a ticket on the producer's board with an SLA matched to the tier (Tier 1 same-day, Tier 2 this-sprint), and the escalation path is agreed when the contract is signed — not improvised mid-incident, when it becomes political. Boring is the design goal: the consequence machine runs identically for every team, including the loud ones, and the quarterly breach ledger replaces blame meetings with a trend line.

3 — Contracts are code, reviewed where the producer works. The spec lives in the producer's repo, versioned in git, changed via PR with consumers as reviewers. A contract change is a negotiation made visible: the producer proposes, affected consumers approve, CI enforces from merge. The artifact teams actually maintain is the one in their own workflow — external contract registries become stale wikis with API access.

🔩The test of a living contract program: a producer team, unprompted, requests a contract on their own upstream. It happens once the routing makes them a consumer with standing — and it is the moment the program stops needing a sponsor.

07 · Production Evidence: Contracts in Regulated Estates

This is the mechanism — visibility, ownership, automated consequence — behind Vipra's Fortune 500 governance engagement, which cut reconciliation effort 40% and enabled self-service BI without governance chaos. The same contract pattern carries our clinical quality metrics (FHIR profiles and value sets as contract clauses, CI-blocked on violation), the supplier mesh (federated governance enforced by contract validation rather than committee), and the inventory reconciliation playbook (where contract semantics blocks — quantity meaning, units, timezones — caught more money than every type check combined). One pattern, four industries, because the failure it fixes — producers and consumers disagreeing silently — is universal.

40%
Reconciliation Cut —
Vipra Documented
3
Cultural Mechanics —
The Actual Product
4
Industries Running
This Exact Pattern
Q3
When Tool-Only
Programs Die

08 · Lessons Learned: The Hard Truths

  • Routing is destiny. Every dead contract program we've audited alerted the data team; every living one paged producers. Decide nothing else first.
  • Mandates produce YAML, requests produce contracts. The platform-mandated rollout generated 200 specs and zero behaviour change. The consumer-requested restart generated 30 contracts that all still hold.
  • Semantics clauses pay the bills. Type checks catch typos; the "gross, includes tax, transaction currency" line catches the six-figure misunderstanding. Write the English, not just the types.
  • Consequences must be boring before the first breach. The first improvised escalation becomes precedent and politics simultaneously. Pre-agree the ticket-SLA-escalation machine while everyone is still friends.
  • The data team must refuse to quietly fix breaches. Every silent fix teaches producers that violations are someone else's problem. It feels unhelpful for a month and transforms the estate within two quarters.
  • The breach ledger ends the meetings. Breaches-created and MTTR, trending by team, replaced the quarterly blame ritual with a two-line review. Evidence beats anecdotes, especially about people.

09 · Key Takeaways for Practitioners

📨
Page the producer

Violations route to the owning team's channel with the diff and affected consumers. The data team is cc'd, never assigned.

🤝
Consumer-requested only

Contracts document relationships that exist. Coverage grows where breakage costs money — exactly where it should.

⚙️
Two layers, one spec

dbt enforces shape in CI; GE enforces behaviour post-load; one YAML file in the producer's repo declares both.

📖
Write the semantics

What the number means — gross/net, units, timezone, grain. The clause schema validators can't check is the one that pays.

🎫
Boring consequences

Auto-ticket, tiered SLA, pre-agreed escalation. The machine runs identically for every team, including the loud ones.

📒
Ledger over meetings

Breaches and MTTR trending by team. The program's report card, and the sponsor it eventually won't need.

The applied versions: inventory reconciliation (dollar-denominated), clinical quality metrics (regulated), supplier federation (cross-border). The documented result: Fortune 500 governance, 40% reconciliation cut.

FAQ · Frequently Asked Questions

What should a data contract actually contain?
Four sections: schema (columns, types, nullability, keys), semantics (what a row means, grain, accepted values), SLAs (freshness deadline, volume bounds), and operations (owner team, alert channel, breach severity tier and escalation path). Version it in git next to the producing code.
dbt tests or Great Expectations — which do I need for contracts?
Both, for different layers: dbt model contracts and schema tests enforce structure at merge time in CI; Great Expectations (or Soda) validates the data itself after load — distributions, volumes, freshness. Schema can pass while the data is wrong, so a contract system needs both layers.
Why do most data contract initiatives fail?
Missing ownership and consequences. Tooling alerts fire into channels nobody owns, breaches carry no process, and contracts are imposed platform-wide instead of created per consumer need. The fix is cultural: producer-routed alerts, pre-agreed breach SLAs, and quarterly reviews that delete dead contracts.
Who should own a data contract — producer or consumer?
The producer owns honoring it; the consumer owns requesting it and defining what they need. Violations route to the producing team's channel with a ticket on their board. The data platform team builds the rails but should never be the default owner of every breach.