Skip to content

Data contracts: define expectations and review breaking changes

Define a data contract with schema, meaning, quality rules, and ownership. Separate automated validation from review of breaking changes.

Abstract blocks representing connected data transformations

A producer changes an order amount from cents to dollars. The column name and SQL type stay the same, so a schema check passes. A downstream report then treats the new values as cents. A useful data contract makes the unit part of the agreement, alongside the structure that software can validate.

Data contracts describe what producers will deliver and what consumers may depend on. They need both a checkable definition and an agreed process for changes. A document that nobody validates or maintains offers little protection when a dataset evolves.

What belongs in a data contract?

Start with one dataset that another team actually consumes. Identify its producer and consumers, then write down the expectations that would cause a problem if they changed.

AreaExample question to settle
Identity and ownershipWhich dataset is covered, and who maintains it?
StructureWhich fields, types, and keys are expected?
MeaningIs an amount gross or net, and which units does it use?
QualityWhich missing values, duplicate keys, or invalid states are unacceptable?
DeliveryWhen should data arrive, and what interval does a delivery cover?
Permitted useWhich consumers and uses does the agreement cover?
Change processHow are changes reviewed, announced, and retired?

The Open Data Contract Standard provides a shared specification for describing data contracts. If you adopt it, use a specific version and validate your document against that version. The worksheet below is a planning example, not an ODCS document or an ActionBox API payload.

plaintext
Dataset: analytics.orders_daily
Producer: orders data team
Consumers: revenue reporting and support analytics
Row grain: one row per order
Key: order_id, unique and non-null
Amount: amount_cents, integer USD cents, excluding tax
Unknown amount: null; never converted silently to zero
Delivery: agreed daily cutoff, recorded with a timezone
Quality checks: key uniqueness, required fields, allowed status values
Changes requiring review: units, key, meaning, removals, delivery commitment
Migration plan: replacement version, consumer checks, retirement date
Owner for failed delivery: named role and escalation path

Replace the example commitments with ones the producer can meet and consumers can use. Avoid a freshness promise that has no owner or measurement behind it.

Separate a schema from the wider agreement

A schema describes structure. A contract can also describe meaning, delivery expectations, and responsibilities. Automated checks cover only the parts you have implemented.

In dbt, an enforced model contract checks the model's column names and types. Constraint support depends on the adapter and data platform. A declared constraint may not be enforced by the warehouse, so check the dbt model-contract documentation for your setup. Data tests provide a separate way to check model contents.

A model contract therefore should not be presented as proof that revenue is defined correctly or that every expected source record arrived. Use source freshness checks for delivery recency and data reconciliation to compare records across systems. Each check supplies different evidence.

Run a structural check, then expose a semantic failure

The following Python example implements only the worksheet's field names, integer types, nullable amount, and unique order key. It is a teaching validator, not an implementation of ODCS or a substitute for your warehouse's checks.

python
def validate_orders(rows):
    """Validate only the example's exact fields, types, and unique key."""
    seen = set()
    for row in rows:
        if not isinstance(row, dict) or set(row) != {"order_id", "amount_cents"}:
            raise ValueError("Expected exactly order_id and amount_cents")
        key = row["order_id"]
        if type(key) is not int or key in seen:
            raise ValueError("order_id must be a unique non-null integer")
        amount = row["amount_cents"]
        if amount is not None and type(amount) is not int:
            raise ValueError("amount_cents must be an integer or null")
        seen.add(key)


validate_orders([{"order_id": 1, "amount_cents": 12000}])
validate_orders([{"order_id": 1, "amount_cents": 120}])

Both calls succeed. The validator cannot tell whether the second record means $1.20 or whether a producer accidentally sent $120 in dollars under a cents field. The data still has the expected shape.

Use an independently established business fixture to test that meaning. For example, if a test order is known to be $120 excluding tax, its expected value is 12,000 cents. Do not calculate that expected value using the same transformation you are trying to test.

Test inputStructural resultInterpretation
Unique integer ID and integer amountPassFields and types meet these rules
Null amountPassThis example explicitly allows unknown amounts
Repeated or null IDRejectMatching-key requirement is violated
Boolean or decimal in the amount fieldRejectThis example requires integer cents or null
Extra or missing fieldRejectThis example requires an exact shape
120 instead of the known fixture's 12,000Pass structurallyA separate business expectation is needed

The exact-field rule deliberately treats an added column as incompatible for this example. Another consumer may allow extra fields. Write that policy explicitly rather than treating this validator's choice as universal.

Download the complete Python example suite and run:

bash
python3 data_quality_examples.py

The suite uses Python 3.10 or later and the standard library. Its 13 tests include both articles' examples and demonstrate that the unit-change fixture passes structural validation. It does not validate an ODCS document, enforce a delivery promise, or prove compatibility with every consumer.

Decide how validation affects publication

Choose the point where a failing check prevents a consumer from receiving incompatible data. Running validation after an export has already been delivered can detect a problem, but it cannot prevent that delivery.

For a proposed change, record the candidate dataset or artifact, contract version, and validation results together. Make the publishing job use that same candidate. If the data or definition changes after validation, run the checks again.

A practical release sequence is:

  1. Prepare the proposed dataset and contract change.
  2. Run the structural and quality checks required for that version.
  3. Identify consumer-facing changes that need review.
  4. Confirm the migration plan with the affected owners.
  5. Publish the reviewed candidate and record the outcome.

Failing validation should produce a specific explanation. "Contract failed" forces someone to reconstruct the problem; the field, expected rule, observed result, and affected version make the next step clearer.

Assess compatibility from the consumer's perspective

A syntactically small change can have a large effect. Review compatibility against actual consumers rather than treating every added column as harmless or every version increment as sufficient notice.

Proposed changeWhat to investigate
Remove or rename a fieldQueries, exports, and applications that reference it
Change a field's typeParsing, precision, joins, and downstream casts
Change units or business meaningCalculations that still run but now produce a different answer
Add a fieldConsumers expecting an exact shape or positional column order
Change delivery timingJobs and reports that depend on the previous cutoff

For the cents-to-dollars example, choose a transition that makes the difference explicit. A separately named field or version can give consumers a migration path. Test known calculations against both representations, and agree when the old form will stop being delivered.

A version label identifies a change; it does not perform the migration or establish that consumers are ready.

Use human review for the decision software cannot make

Automated checks can compare a candidate with declared rules. A person may still need to decide whether a business-definition change is acceptable, whether a consumer can migrate in time, or whether a limited exception is justified.

Give that reviewer the contract diff, affected consumers, validation evidence, proposed release window, and recovery plan. Define whose approval is required before building the release path. Avoid substituting a generic team acknowledgement for the decision of the owner responsible for the affected use.

ActionBox can hold the review request and decision; your validation tools and publishing system continue to enforce the contract and release the data. The ActionBox architecture describes this separation. If a callback resumes publication, follow the verification and decision-binding rules so the response applies to the version that was reviewed.

A rejection should return the candidate for revision or stop its release. An unanswered request should follow the agreed deadline policy. Neither outcome should become permission to publish because the scheduled job is running late.

Keep the contract useful after release

Assign someone to update the contract when the producer or consumer changes. Review repeated exceptions: they may indicate that the commitment is unrealistic or the consumer's needs have changed. Keep temporary exceptions tied to a scope and deadline instead of weakening a rule indefinitely.

Begin with one important shared dataset and a small set of measurable expectations. Rehearse a breaking change and a failed delivery before expanding the process. If an approved model change requires historical rebuilding, the dbt full-refresh guide covers execution planning; this contract should identify why the change is needed and who depends on it.

Verification notes

Examples checked September 7, 2026 with Python 3.11.9 and SQLite 3.45.1 using synthetic fixtures. The downloadable suite reports 13 passing tests. No production datasets, warehouse performance measurements, or customer results are represented here. Linked official documentation supports the tool-specific behavior; the workflow recommendations are this article's design guidance.

Create a free Source · Plan an approval workflow

Turn the next risky operation into a reviewable decision.

Create a free Source, run the example from this guide, and keep the decision and execution outcome connected.