Skip to content

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

  1. 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.
  2. 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.
  3. Run time: quality rules become dbt tests and platform-native checks (Data quality and observability). Freshness SLAs become alerts that go to the owner.
  4. 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).