Data Engineering Guide

Data Contracts in CI/CD: Schema Compatibility Checks

A contract nobody runs is a wiki page. This guide is the automation: the three checks that belong in the pipeline, which compatibility mode each one should enforce, and what to do the morning a check goes red.

20 min readFor Data & Platform EngineersRunnable YAML and CLI examples

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

122/20003 credits left
Try:

No account needed · Editable canvas, not a picture

The short answer

A data contract is a versioned, machine-readable agreement between the team that produces a dataset and the teams that consume it: the schema (field names, types, nullability), the meaning of each field, the service level for freshness and completeness, and the rule for what counts as a breaking change. You automate it with three checks in one pipeline. A lint step validates the contract file itself. A compatibility step diffs the contract in the pull request against the version on the main branch and exits non-zero when a field is dropped, renamed, retyped, or tightened, which fails the build before the merge. A test step runs the quality and freshness rules against the landed data on a schedule, because a shape that still matches says nothing about a table that stopped loading. Adding an optional field is backward compatible and passes; dropping one is breaking and needs a major version plus a deprecation window. Streaming teams get the first two from a schema registry compatibility mode set per subject, warehouse and batch teams from the datacontract CLI, dbt contracts, or buf. The check only enforces anything once it is a required status check on the branch.

1. How do you automate data contract checks in CI/CD?

Three checks, three different failure modes, and they are not interchangeable. Run all three or accept that one class of incident stays invisible until a consumer reports it.

CheckCatchesRuns onFails
LintA malformed contract: unknown key, missing owner, invalid ISO 8601 durationEvery PR touching contracts/The build, in under a second
Compatibility diffA dropped, renamed, retyped or newly required field without a major version bumpEvery PR, against the file on mainThe build, before merge
Data testData that no longer matches a contract nobody changed: nulls, ranges, stale loadsA schedule, against the landed tableAn alert to the owner

The failure the diff exists to stop

Backend team renames a column. Pipeline breaks at 3 AM. The data team spends hours finding out why. Without a check on the pull request, every schema change is a surprise: producers do not know who reads their data, and consumers do not know when it will change. The diff turns that into a red build at review time, when the person who made the change is still looking at it.

A GitHub Actions job that runs all three

.github/workflows/contract-check.yml

name: Data Contract Validation

on:
  pull_request:
    paths:
      - 'contracts/**'
  schedule:
    - cron: '0 6 * * *'     # the data test, once a day

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0    # the diff needs the base commit, not a shallow clone

      - name: Install the datacontract CLI
        run: pip install datacontract-cli

      - name: Lint
        run: datacontract lint contracts/orders.yaml

      - name: Fetch the version currently on main
        run: git show origin/main:contracts/orders.yaml > /tmp/orders-main.yaml

      - name: Fail on a breaking change
        run: datacontract breaking /tmp/orders-main.yaml contracts/orders.yaml

      - name: Human-readable diff in the job log
        if: always()
        run: datacontract changelog /tmp/orders-main.yaml contracts/orders.yaml

      - name: Test the contract against live data
        run: datacontract test contracts/orders.yaml --server production
        env:
          DATACONTRACT_SNOWFLAKE_USERNAME: ${{ secrets.SNOWFLAKE_USERNAME }}
          DATACONTRACT_SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}

Two details decide whether this job is enforcement or decoration. fetch-depth: 0 is required, because the default shallow clone has no base commit to diff against and the step will pass on a repository where it cannot see the old file. And the job has to be a required status check in branch protection. A job that reports red on a mergeable pull request teaches the team to merge red. If you already run docs checks in the pipeline, the contract check belongs in the same required set; docs checks in CI covers how teams keep that set fast enough to stay required.

Where enforcement sits, in order of how early it catches

1

CI/CD pipeline (shift-left)

Validate contract changes in the pull request. Reject breaking changes without a version bump. Check schema compatibility before merge. Diagram where these gates sit in your CI/CD pipeline so producers know exactly what blocks a merge.

Tools: datacontract CLI, buf (Protobuf), schema-registry compatibility check

2

Schema registry (deploy and publish time)

Centralized schema storage. Producers register schemas. Consumers fetch compatible versions. Rejects incompatible writes.

Tools: Confluent Schema Registry, AWS Glue, Hive Metastore

3

Ingestion validation (runtime)

Validate data at the ingestion point. Reject or quarantine non-compliant records. Log violations for producer feedback.

Tools: Great Expectations, Soda, custom validators

4

SLA monitoring (after the fact)

Track freshness, availability and quality metrics. Alert when SLAs are breached. Dashboard for visibility.

Tools: Monte Carlo, elementary, custom dashboards

Before writing your first contract, know your flows. A data pipeline diagram shows which producer-consumer interfaces break most often, and those are the ones worth putting a contract on first.

2. Which schema compatibility mode should the check enforce?

The compatibility mode is the single setting that decides what your check actually refuses. Most teams never change it, run on the default, and are surprised when the default turns out to permit exactly the change that broke them.

ModeGuaranteeAllowsPick it when
BACKWARDA consumer on the new schema can read data written with the previous oneDelete a field, add an optional fieldConsumers upgrade first. This is the Confluent default
FORWARDA consumer on the previous schema can read data written with the new oneAdd a field, delete an optional fieldProducers upgrade first and consumers lag
FULLBoth directions, against the previous versionAdd or delete optional fields onlyNeither side can be made to deploy first
*_TRANSITIVEThe same guarantee against every earlier version, not only the latestSame as the base modeConsumers may sit several versions behind, or replay old data
NONENo guaranteeAnythingPrototypes, and topics with a single consumer you own

The trap is replay. BACKWARD only compares against the version immediately before, so a field deleted in v2 and a different field deleted in v3 both pass, and a consumer re-reading a topic from the beginning hits data neither schema describes. Any dataset that gets replayed, or that has consumers you do not control, wants a TRANSITIVE variant. The mode is set per subject, not per cluster, so a single noisy topic does not force the whole registry into a strict mode. The compatibility rules and what a registry structurally cannot check are laid out in data contract vs schema registry.

3. What counts as a breaking change?

This is the list the compatibility check encodes. Anything on the left needs a major version and a deprecation window; anything on the right ships in a minor or patch release and merges the same day.

Breaking (major version)

  • • Removing a field
  • • Changing a field's data type
  • • Renaming a field
  • • Making an optional field required
  • • Changing field semantics
  • • Removing enum values

Non-breaking (minor or patch)

  • • Adding a new optional field
  • • Adding new enum values
  • • Relaxing constraints (required to optional)
  • • Updating descriptions
  • • Adding default values
  • • Improving SLAs

One row on that list is invisible to every automated check: changing field semantics. If status = 3 stops meaning shipped and starts meaning partially shipped, the schema is byte-identical and every compatibility mode passes, while every downstream aggregate is quietly wrong. That is why the contract carries a semantics block and why a semantic change is treated as a major version by policy rather than by tooling.

Versioning strategies

Semantic versioning (recommended)

MAJOR.MINOR.PATCH. Major = breaking, minor = new features, patch = fixes. Clear, well-understood, widely adopted.

v1.0.0 to v1.1.0 (add field) to v2.0.0 (remove field)

Date-based versioning

YYYY-MM-DD format. Good for APIs, less common for data. Makes age obvious.

2024-01-15, then 2024-06-01, then 2025-01-01

Schema evolution (Avro, Protobuf)

No explicit version number on the contract. The registry compatibility mode decides what may change, and the subject version increments on its own.

BACKWARD: new schema reads old data. FORWARD: old schema reads new data.

Example: change management process

# Deprecation workflow for breaking changes

1. ANNOUNCE (Day 0)
   - Create deprecation notice in contract
   - Notify consumers via #data-announcements
   - Update changelog

2. DUAL-WRITE (Day 0-60)
   - Keep old field + add new field
   - Both populated during transition
   - Monitor which consumers migrate

3. WARN (Day 60-90)
   - Log warnings when old field accessed
   - Direct outreach to remaining consumers
   - Offer migration support

4. REMOVE (Day 90+)
   - Remove deprecated field
   - Bump major version
   - Old versions archived but accessible

The 90-day rule

Give consumers at least 90 days notice for breaking changes. This seems long, but data pipelines have long release cycles, and consumers need time to update downstream logic, tests, and dashboards.

4. How do you wire contracts into Kafka and Confluent Schema Registry?

On a streaming stack the compatibility half of the contract is already available: the registry does it. Three settings turn it from a schema store into an enforcement point.

The subject naming strategy

The default, TopicNameStrategy, registers one subject per topic and value, named orders-value. RecordNameStrategy keys the subject on the record type instead, so the same event type carries one contract across every topic it appears on. Pick before you have a hundred subjects, because migrating the naming strategy later means re-registering all of them.

The compatibility level, set per subject

Global default plus a per-subject override. Read it before you rely on it: a subject created during a migration is often sitting on NONE.

Broker-side validation

On Confluent Server, the topic config confluent.value.schema.validation=true makes the broker reject records whose schema id is not registered for the subject. Without it, a producer that bypasses the serializer can still write whatever it likes.

Compatibility test in the producer build, before the deploy

<!-- pom.xml: the candidate schema is checked against the registered subject -->
<plugin>
  <groupId>io.confluent</groupId>
  <artifactId>kafka-schema-registry-maven-plugin</artifactId>
  <version>7.6.0</version>
  <configuration>
    <schemaRegistryUrls>
      <param>https://schema-registry.internal:8081</param>
    </schemaRegistryUrls>
    <subjects>
      <orders-value>src/main/avro/order.avsc</orders-value>
    </subjects>
  </configuration>
</plugin>

# In CI, on the pull request:
mvn io.confluent:kafka-schema-registry-maven-plugin:test-compatibility

# On merge to main, once the build is green:
mvn io.confluent:kafka-schema-registry-maven-plugin:register

Confluent also ships a feature named Data Contracts on top of the registry: schema and field level metadata, rulesets with domain validation rules written in CEL, migration rules using JSONata, and tags that drive downstream policy. It closes the value validation gap for streaming, which the schema alone cannot cover. What it does not change is the boundary: it applies to messages on topics, at publish time, for producers using that registry. The warehouse table analysts query, the nightly vendor file, and the freshness of both stay outside it, which is why teams running Confluent Data Contracts still keep contract files for the landed datasets. The reference material is in the Confluent Schema Registry documentation. For the topology itself, a Kafka streaming architecture diagram is the fastest way to show which subjects sit between which services.

5. What is the difference between a data contract and a schema?

A schema describes shape: field names, types, nullability. A data contract wraps that schema in commitments the shape cannot express, and the schema ends up as one section of it, usually the first. The useful mental model is the API contract, which nobody would reduce to a request body.

API contract

  • • OpenAPI/Swagger spec
  • • Request/response schema
  • • Versioned endpoints
  • • Rate limits & SLAs
  • • Breaking change policy

Data contract

  • • Schema definition (fields, types)
  • • Semantic meaning of fields
  • • Freshness & availability SLAs
  • • Quality rules & expectations
  • • Versioning & change policy

A schema registry is a tool that stores and validates schemas at publish time. A data contract is a practice that may use one. The registry cannot see a change in the meaning of a field, a shift in value distribution, a pipeline that stopped running, or anything in a warehouse table with no registry in front of it, which is the case for most analytical consumption. The full comparison, including which of the four gaps Confluent closes, is in data contract vs schema registry.

Key insight

Data contracts shift the responsibility for data quality left, to the producer. Instead of data teams cleaning up messes downstream, producers guarantee quality at the source. This is the shift-left movement in data.

6. What should a data contract specification include?

Seven blocks. Not every contract needs all of them, but a contract missing the owner or the deprecation policy will not survive its first disagreement.

Metadata

Name, version, owner, description, tags. Who owns this? What is it for?

Schema

Fields, data types, constraints (not null, unique). The structural definition.

Semantics

Business meaning of each field. What does "status" actually mean? What are valid values?

SLAs

Freshness (data no older than X), availability (uptime), latency (processing time).

Quality Rules

Expectations beyond schema: ranges, patterns, relationships, null rates.

Versioning

How changes are made, communicated, and deprecated. Breaking vs non-breaking.

Alerting

Who gets notified when contracts are violated? How? Where?

Example: complete data contract (YAML)

# contracts/orders.yaml
apiVersion: v1
kind: DataContract
metadata:
  name: orders
  version: 2.1.0
  owner: [email protected]
  description: Order transactions from all sales channels
  tags: [commerce, transactions, pii]

schema:
  type: object
  properties:
    order_id:
      type: string
      format: uuid
      description: Unique order identifier
      pii: false
    customer_id:
      type: string
      description: Customer who placed the order
      pii: true
    order_date:
      type: string
      format: date-time
      description: When the order was placed (UTC)
    status:
      type: string
      enum: [pending, confirmed, shipped, delivered, cancelled]
      description: Current order status
    total_amount:
      type: number
      minimum: 0
      description: Total order value in USD
  required: [order_id, customer_id, order_date, status, total_amount]

sla:
  freshness: PT1H          # Data no older than 1 hour (ISO 8601)
  availability: 99.9%      # Uptime target
  updateFrequency: PT15M   # Updates every 15 minutes

quality:
  rules:
    - name: unique_order_id
      type: unique
      column: order_id
    - name: valid_total
      type: range
      column: total_amount
      min: 0
      max: 1000000
    - name: no_future_orders
      type: custom
      sql: "order_date <= CURRENT_TIMESTAMP"

versioning:
  strategy: semantic        # major.minor.patch
  deprecationPolicy: 90d    # 90 days notice for breaking changes
  changeLog: https://wiki.company.com/data/orders/changelog

alerts:
  slack: "#data-alerts"
  pagerduty: commerce-oncall
  email: [[email protected]]

Using the Open Data Contract Standard instead of your own keys

ODCS is a YAML standard maintained by the Bitol project under the Linux Foundation, and it covers the same seven blocks with fixed key names: apiVersion, kind, id, name, version and status at the top, field definitions under schema, connection details under servers, the freshness and availability promises under slaProperties, and ownership under team and roles. The gain is not elegance, it is tooling: a standard file is something a linter, a catalog, and someone else's importer already understand. The datacontract CLI reads and writes it, and exports the same contract to Avro, JSON Schema, dbt, SQL DDL or Great Expectations, so one file can feed the registry and the warehouse tests. The section-by-section layout is in what is in a data contract.

7. Which schema language should the contract use?

The schema is the structural foundation of your contract. It defines what fields exist, their types, and basic constraints. Choose a schema language that your ecosystem supports, because the checker you get for free is the one that matters.

FormatBest ForTooling Support
JSON SchemaAPIs, JSON data, broad compatibilityExcellent (many validators)
AvroKafka, streaming, schema evolutionExcellent (Confluent Registry)
ProtobufgRPC, high-performance systemsGood (buf.build)
dbt YAMLTransformation layer, SQL modelsGood (dbt ecosystem)
Data Contract SpecFull contracts (schema + SLA + quality)Growing (datacontract CLI)

In the warehouse the equivalent of a registry is the dbt model contract: contract: { enforced: true } on the model config makes the run fail when the model output does not match the column names and data types declared in the YAML, which puts the same gate on a table that a registry puts on a topic. It checks shape only, so the quality rules still need Soda, Great Expectations, or dbt tests next to it.

What to include in the schema

Always include

  • • Field names and data types
  • • Required vs optional fields
  • • Primary key / unique identifiers
  • • Enum values for categorical fields
  • • Field descriptions

Consider adding

  • • PII/sensitive data flags
  • • Format patterns (email, UUID)
  • • Min/max for numeric fields
  • • Default values
  • • Deprecation markers

Example: JSON Schema for orders

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Order",
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "format": "uuid",
      "description": "Unique order identifier"
    },
    "customer_id": {
      "type": "string",
      "pattern": "^CUS-[A-Z0-9]{8}$",
      "description": "Customer ID in format CUS-XXXXXXXX"
    },
    "order_date": {
      "type": "string",
      "format": "date-time"
    },
    "status": {
      "type": "string",
      "enum": ["pending", "confirmed", "shipped", "delivered", "cancelled"]
    },
    "items": {
      "type": "array",
      "items": { "$ref": "#/definitions/OrderItem" },
      "minItems": 1
    },
    "total_amount": {
      "type": "number",
      "minimum": 0,
      "exclusiveMaximum": 1000000
    }
  },
  "required": ["order_id", "customer_id", "order_date", "status", "items", "total_amount"],
  "additionalProperties": false
}

8. How do you put SLAs and quality rules in the contract?

Schema defines structure. SLAs and quality rules define behavior and reliability. These are the promises that make contracts meaningful, and the ones the compatibility check never looks at.

A promise nobody measures is a comment. Freshness and volume SLAs need a monitoring layer that watches arrival times and row counts without being told what to expect, which is what data observability adds on top of explicit tests. Consumers also have to be able to find the contract before they depend on the table, so it belongs next to the dataset in the catalog; data catalog best practices covers how to keep that registration automatic instead of a launch-day chore.

Service level agreements

Freshness

How recent must the data be? "Data no older than 1 hour" means consumers can trust it's current.

freshness: PT1H (ISO 8601 duration)

Availability

What uptime do you guarantee? 99.9% means about 8.7 hours of downtime per year at most.

availability: 99.9%

Update frequency

How often is data refreshed? Real-time, hourly, daily?

updateFrequency: PT15M (every 15 min)

Latency

Time from event occurring to data being available. Critical for real-time use cases.

latency: PT5M (5 min max delay)

Quality rules (beyond schema)

Uniqueness

Primary keys must be unique. No duplicates.

unique(order_id)

Completeness

Null rate thresholds. "email null rate < 5%"

null_rate(email) < 0.05

Referential integrity

Foreign keys must exist in parent table.

exists(customer_id, dim_customers.id)

Range checks

Values within expected bounds.

total_amount BETWEEN 0 AND 1000000

Pattern matching

Values match expected format.

email MATCHES /^[a-z]+@[a-z]+\.[a-z]+$/

Consistency

Calculated fields match components.

total = subtotal + tax - discount

Pro tip

Start with SLOs, graduate to SLAs

SLOs (objectives) are targets you aim for. SLAs (agreements) are commitments with consequences. Start by tracking metrics as SLOs. Once you are consistently hitting them, formalize as SLAs with stakeholder agreement.

9. How do you evolve a schema without breaking downstream consumers?

Expand, then contract. Every safe migration is the same four moves: add the new field alongside the old one, populate both, wait for consumers to move, remove the old one in a major version. The 90-day deprecation workflow above is that pattern with dates attached. Nothing in it is hard. The part teams get wrong is knowing when the wait is over.

Find out who actually reads the dataset

Query logs give you the honest answer that a wiki page of consumers does not: who selected that column in the last 90 days. Column-level lineage turns a proposed diff into a list of affected models and dashboards. Without one of the two, the deprecation window is a guess and the removal is a gamble. Data lineage best practices covers how to get that list.

Alert in three waves, not one

The registry or CI compatibility check fires on the pull request, before any deploy. The catalog turns the diff into named consumers and notifies their owners. A freshness and volume monitor on the landed table catches whatever slipped through. Teams that only have the third one find out from a dashboard, which is the same as finding out from the business.

Never reuse a name with a new meaning

Repurposing an existing column is the one change that passes every automated check and breaks every consumer. Add amount_cents next to amount rather than changing what the old one holds.

Where the picture goes stale is the diagram of who produces and who consumes. Datadef connects read-only to the GitHub, GitLab, or Azure DevOps repository that produces the dataset and redraws the architecture diagram and an architecture.md every day, so the producer-consumer map a reviewer opens during a contract change is the one that shipped rather than a drawing from two quarters ago. A commit that changes nothing structural does not redraw the diagram, and nodes moved by hand keep their position. The diagram embeds as a live image in the README next to the contract file, so the two are read together. See repository sync for what it reads.

10. What to do when a contract check fails

A message along the lines of "the data received does not meet the data contract expectations" means one thing: a validator compared a payload against the registered contract and found a mismatch. The message rarely says which of the three causes it is, and they have different owners.

What the failure saysWhat actually happenedWho fixes it
Unexpected or missing fieldThe producer shipped a schema change without a version bump, or deployed ahead of the contractProducer: revert or bump the major version
Type or enum violationA value drifted: a new status code, a currency in cents where it used to be unitsProducer, and the enum belongs in the contract
Null in a required fieldUsually not the producer: an upstream join lost rows, or a backfill ran partialPipeline owner, before anyone edits the contract

Rejected records go to a quarantine table or a dead letter queue keyed by the run id, never silently dropped and never waved through into the sink. Quarantine keeps the failure debuggable: you can count how many records failed, see the offending values, and replay them once the producer ships a fix. The one response to avoid is editing the contract to match the data. That is not a fix, it is a rename of the incident, and it removes the only record of what was promised.

Pro tip

Fail closed vs fail open

For critical data: fail closed (reject bad data). For non-critical: fail open (accept but log). Start fail-open to learn what violations exist, then tighten to fail-closed once producers fix issues.

11. What tools track schema versions, compatibility, and lineage?

Three layers, and rarely one product. A registry or buf versions the schema and answers the compatibility question. A contract runner (datacontract CLI, dbt contracts, Soda, Great Expectations) runs the checks in CI and against landed data. A catalog holds the lineage that says which consumers a change would reach. Here is what teams are using in 2026.

ToolLayerBest ForLicensing
datacontract CLIContract runnerlint, breaking, changelog, test in one binaryOpen source
Confluent Schema RegistrySchema versionsKafka, per-subject compatibility modesOpen source and commercial
bufSchema versionsProtobuf breaking-change detection in CIOpen source and commercial
SodaContract runnerQuality rules as checks against warehouse tablesOpen source and commercial
Great ExpectationsContract runnerComprehensive expectation suitesOpen source
dbt contractsContract runnerEnforcing model output shape at build timeOpen source (dbt Core)
DataHub / Atlan / OpenMetadataCatalog and lineageFinding the consumers a change would reachOpen source and commercial
Monte Carlo / elementaryMonitoringFreshness and volume anomalies nobody wrote a rule forCommercial / open source

The gap most stacks have is the third layer. Versioning and testing are solved by the first two rows and the answer is boring; knowing that dropping order_channel hits four dbt models and a finance dashboard is what stops the deprecation from becoming an incident, and that needs lineage down to the column. Data lineage tools compares the options for that layer.

12. How do you roll contracts out across teams?

Consumer-driven, or it does not get written. Producers rarely know which of their hundred columns matter, so a contract they author alone documents everything and commits to nothing. Ask each consuming team to write down the fields and guarantees it actually depends on, merge those into one contract, and leave anything nobody claimed out of scope. Then run the check in the producer repository, so the failure lands on the person who can fix it.

Who is involved

Producer

The team that generates the data. Backend, application, or upstream data team. They commit to the contract.

Consumer

The team that uses the data. Analytics, ML, downstream pipelines. They rely on the contract.

Platform

The team that provides tooling. Schema registry, validation, monitoring. They enforce the contract.

Implementation checklist

Start with high-value datasets

Pick 2-3 critical datasets that cause the most pain. Prove value before scaling.

Get producer buy-in first

Contracts require producers to commit. Without their ownership, contracts become stale documentation.

Automate from day one

Manual contract enforcement does not scale. Set up CI/CD validation before writing your first contract.

Make contracts discoverable

Store contracts in a catalog or registry. If consumers cannot find them, they will not use them.

Start with SLOs, not SLAs

Track metrics as objectives first. Formalize as agreements once you are consistently meeting them.

Define your breaking change policy

How much notice? Who approves? What is the migration path? Document this upfront.

From experience

The hardest part is not the tooling, it is the culture change. Producers need to think of their data as a product with customers. That mindset shift takes time, executive support, and visible wins from early adopters.

13. Frequently asked questions

How do you enforce data contracts automatically?

Enforcement is three automated gates. A CI job lints the contract file and diffs it against the version on the main branch, failing the pull request on a breaking change without a major version bump. A schema registry or a dbt contract rejects an incompatible producer at deploy time. A scheduled test runs the quality and freshness rules against the landed data and pages the owner when one fails.

How do you run schema compatibility checks in a CI/CD pipeline?

Add a job that triggers on pull requests touching the contract directory. It installs the checker (the datacontract CLI, buf for Protobuf, or the Confluent Maven plugin), retrieves the previous contract with git show origin/main:contracts/orders.yaml, and compares the two. The command exits non-zero on an incompatible change, which fails the build. Keep it under a minute so it can be a required status check.

Which schema compatibility mode should the CI check use?

BACKWARD, the Confluent default, lets a new consumer read old data and permits deleting a field or adding an optional one. FORWARD lets an old consumer read new data. FULL is both. The TRANSITIVE variants check every earlier version instead of only the latest, which is what a dataset with many independent consumers needs. NONE disables the check.

What is the difference between a data contract and a schema?

A schema describes shape: field names, types, nullability. A data contract wraps that schema in commitments the shape cannot express: who owns the dataset, what each field means in business terms, the quality rules that must pass, the freshness target, and the notice period before a breaking change. The schema is one section of the contract, usually the first.

What is the difference between data contracts and a schema registry?

A schema registry stores and validates schemas at publish time, one subject per topic, and rejects a producer whose new schema violates the subject compatibility rule. A data contract is broader: it adds ownership, semantics, quality rules, freshness, and the change policy, and it covers batch tables that have no registry in front of them. Most streaming teams run both.

What should a data contract specification include?

Seven blocks: metadata (name, version, owner, tags), the schema with types and constraints, the semantics of each field, SLAs for freshness, availability and update frequency, quality rules beyond the schema, a versioning policy carrying the deprecation window, and the alerting route for a violation. The Open Data Contract Standard covers the same ground with fixed key names.

What is a data contract?

A data contract is a versioned, machine-readable agreement between the team that produces a dataset and the teams that consume it. It pins down the schema, the meaning of each field, the service level for freshness and completeness, and the rule for what counts as a breaking change. It lives in version control next to the producing code so a CI job can enforce it.

How do you adopt the Open Data Contract Standard in your team?

ODCS is a YAML standard maintained by the Bitol project under the Linux Foundation. Adopt it by converting one existing contract: keep apiVersion, kind, id, name, version and status at the top, move field definitions under schema, connection details under servers, and the freshness and availability promises under slaProperties. Then point the CI linter at the file so the shape stays valid.

What does it mean when the data received does not meet the data contract expectations?

A runtime validator compared an incoming payload against the registered contract and found a mismatch. Triage in this order: an unexpected or missing field means the producer shipped a schema change without a version bump, a type or enum failure means a value drifted, and a null in a required field usually means an upstream join lost rows. Rejected records belong in a quarantine table, not in the sink.

How do you roll out consumer-driven data contracts between teams?

Start from the consumer side. Each consuming team writes down the fields and guarantees it actually depends on, the producer merges those into one contract, and anything nobody claimed stays out of scope. Run the contract check in the producer repository so the producer sees the failure, not the consumer. Take two or three painful interfaces first, then widen once the checks are trusted.

What tools track schema versions, lineage, and compatibility?

Three layers, rarely one product. A schema registry (Confluent, AWS Glue) or buf versions the schema and answers the compatibility question. The datacontract CLI, dbt contracts, Soda or Great Expectations run the checks in CI and against landed data. A catalog such as DataHub, Atlan or OpenMetadata holds the lineage that tells you which consumers a change would reach.

How do you get schema change alerts before they cause downstream failures?

Three signals, in increasing lateness. The registry or CI compatibility check fires on the pull request, before any deploy. A catalog with column-level lineage turns that diff into a list of affected consumers and notifies their owners. A freshness and volume monitor on the landed table catches whatever slipped through, which is the last chance before a dashboard shows wrong numbers.

Diagram the interfaces you are about to put under contract

Describe the pipeline in a sentence and get an editable diagram of producers, topics, sinks and consumers, with the boundaries a contract has to cover marked.