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.
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.