Library · Data Practice OS
Data contracts
Reference guide · Published · Source reviewed
The answer: every published gold Data product has a Data contract in git, next to the code that builds it. The contract is enforced in three places: in CI (no breaking change without a version bump), at build time (enforced schema), and at run time (quality and freshness checks). Without contracts, AI consumers are guessing. With them, the agent and the analyst read the same promise (What AI-ready means).
What a contract says
| Part | Example |
|---|---|
| Identity | registrations.fct_registration_event, version 2.1.0 |
| Ownership | Owner team, steward, support channel |
| Schema | Columns, types, nullability, keys, and meaning (descriptions with units and code lists) |
| Semantics | Grain ("one row per registration transaction"), time zone, business rules applied |
| Quality | Uniqueness, referential integrity, accepted values, volume bounds |
| Service level | Freshness ("by 06:00 AEST daily"), availability, support hours |
| Classification | OFFICIAL_SENSITIVE, contains personal information, permitted AI uses |
| Access | Who may consume it, and through which interface (view, share, semantic layer) |
The format
Use the Open Data Contract Standard (ODCS) YAML so contracts aren't tied to a tool:
# contracts/fct_registration_event.odcs.yaml (abridged, ODCS v3.2.0)
apiVersion: v3.2.0
kind: DataContract
id: registrations.fct_registration_event
version: 2.1.0
status: active
domain: registrations
dataProduct: registration-events
team:
name: registrations-data
members:
- username: [email protected]
role: Owner
description:
purpose: Registration transactions for reporting, forecasting and service analytics.
usage: Approved for aggregate analytics and AI summarisation. Not for individual decisions.
schema:
- name: fct_registration_event
physicalName: prod_registrations.gold.fct_registration_event
description: One row per registration transaction.
properties:
- name: registration_event_id
logicalType: string
primaryKey: true
required: true
- name: event_type
logicalType: string
required: true
quality:
- metric: invalidValues
arguments:
validValues: [NEW, RENEWAL, TRANSFER, CANCELLATION, SUSPENSION]
mustBe: 0
- name: customer_key
logicalType: string
classification: SENSITIVE # security classification label
tags: [pii]
slaProperties:
- property: latency # ODCS prefers latency to freshness
value: 6
unit: h
Where it is enforced
- CI: a script diffs the contract against
main. Removing or renaming a column, changing a type or tightening an accepted-values list is a breaking change: it needs a major version bump and a new versioned interface (v_fct_registration_event_v3), with the old one kept for an agreed notice period. - Build: dbt model contracts (
contract: {enforced: true}) make the build fail if the model's output schema doesn't match. Generate the dbt YAML from the ODCS file, or check the two agree in CI, so there's one source of truth. - Run time: quality rules become dbt tests and platform-native checks (Data quality and observability). Freshness SLAs become alerts that go to the owner.
- Catalog: publish the contract's descriptions, owner and classification into Snowflake Horizon Catalog or Unity Catalog as comments and tags. That's what BI users and AI agents actually see.
The smallest version, this sprint
If full ODCS feels heavy, start with this and grow:
models:
- name: fct_registration_event
description: "One row per registration transaction."
config:
contract: { enforced: true }
meta:
owner: registrations-data
freshness_sla_hours: 6
classification: SENSITIVE # security classification label
ai_use: aggregate-only
columns:
- name: registration_event_id
data_type: varchar
constraints: [{ type: not_null }, { type: primary_key }]
tests: [unique]
When not to bother
- Bronze and silver. These are internal. Use source freshness and basic tests, but don't promise anything to consumers.
- Exploratory and one-off analysis. A contract is a promise. Don't make promises about a sandbox.
Pitfalls
- Contracts nobody reads. If the contract isn't in the catalog and in the consumer's tooling, it's just documentation. Publish it where consumers look.
- Contracts that drift from reality. Generate dbt schema from the contract, or the other way round, but never maintain both by hand.
- "Required: true" on everything. Over-strict contracts get ignored. Be honest about what is nullable.
- No consumer list. You can't negotiate a breaking change if you don't know who's affected. Lineage plus share registers give you the list (Catalogs and the catalog of record).