---
title: "The Consumers dbt Can't See"
description: "Your mart has a contract. It is enforced, it is checked on every build, and it is genuinely good."
slug: "h8-contract-of-record"
type: article
locale: en-US
canonical: "https://www.coremodels.io/connector/dbt/h8-contract-of-record"
updatedAt: "2026-09-01T02:38:10+03:00"
---

# The Consumers dbt Can't See

Your mart has a contract. It is enforced, it is checked on every build, and it is genuinely good.

It is also invisible to the reverse-ETL job syncing that mart into your CRM, to the ML pipeline that
trains on it nightly, and to the team down the hall whose service reads the table directly. None of
them can parse a dbt contract. None of them are dbt.

What those three consumers actually rely on is an agreement nobody wrote down: a Slack thread from
last year, a column that has always been non-null, and an assumption about what the status values
are. You will discover the terms of that agreement the first time you break one.

## This is not a dbt shortcoming

Worth being precise, because the reflex is to hear this as criticism. dbt model contracts stop at
the dbt boundary **by design**, and that design is correct. dbt's job is transformation inside a
warehouse; a contract mechanism that tried to also serve arbitrary external consumers would be a
worse version of both jobs.

The gap is not inside dbt's remit. It is that nothing else picked it up. So most teams end up with a
strong internal contract and nothing at the edge — which is precisely backwards, because the
internal consumers are the ones you can fix in an afternoon and the external ones are the ones that
page someone.

## Two things that are missing, not one

The obvious missing thing is a contract those consumers can read. The less obvious one is a list of
who they are.

Ask any analytics team who consumes their marts and you will get a confident partial answer. The
reverse-ETL job everyone knows about, the dashboard everyone forgets, and — reliably — one consumer
nobody in the room is aware of. There is no register, because dbt has nowhere to put one and no
reason to ask.

That absence is why "who breaks if I change this?" has no answer. Not because the analysis is hard,
but because the input was never collected.

## What publishing a contract actually means

Govern what the mart guarantees — the columns, their types, what values are permitted, what is
required — and then export that as an **ODCS v3.1.0** contract. ODCS is where the data-contracts
conversation settled, which matters: a consumer team can act on it without adopting your toolchain
or learning your modeling conventions.

Alongside it, keep the register. Who consumes this mart, how to reach them, and how much notice they
need before a breaking change. Three columns. It is not sophisticated and it is the difference
between an impact question that returns names and one that returns a shrug.

## The distinction that matters most

Most contract tooling **stores** contracts. You author a document, it lands in a catalog, and it sits
there being documentation. It was accurate the day it was written and nothing checks it afterwards,
so its accuracy decays silently while its authority — being the official contract — does not. That
is arguably worse than no contract, because people rely on it.

The alternative is a contract that is **generated from the governed model and re-checked against the
estate**. Two properties follow, and both are the point:

**It cannot drift from the model.** It is an output. Regenerate and it matches, because there is
nothing else for it to match.

**Its accuracy has evidence.** Every re-audit is recorded — a trail of runs, a status, a history. So
"is this contract still true?" is answered by a record rather than by whoever remembers. That is
what turns a contract from a document into a commitment you can show someone.

## Being honest about the boundary

A contract does not enforce itself in a consumer's runtime. Nothing here reaches into the reverse-ETL
job and validates its assumptions — that job runs in someone else's infrastructure and will keep
doing whatever it does.

What changes is that the agreement becomes **explicit, versioned, and checkable**, and the breakage
conversation moves from after the incident to before the merge. That is a smaller claim than
"enforced end to end", and it is the true one.

The other honest limit: the contract describes what you govern. Marts you have not modeled are not
in it, and the register lists the consumers you have recorded. Both improve by doing the work, and
neither pretends to be complete on day one.

## Where this leaves you

The mart keeps its dbt contract, which keeps doing its job inside the project. What is added is an
outward-facing one: a document three teams who have never opened your repo can hold you to, a list
of who those teams are, and a trail proving the document still matches what ships.

Then "we're changing this column next sprint" becomes an email to a known list, rather than an
incident with a retrospective.

The dbt quickstart that ships with the CoreModels integration docs covers the import and the export
end to end.

## Recipes

- [A contract for the consumers dbt can't see](https://www.coremodels.io/recipe/dbt/contract-of-record): Model contracts stop at the dbt boundary. Your reverse-ETL job, your ML pipeline and the team next door have nothing.
