The Meaning Gap in Your dbt Project
The incident that finally exposes the gap is usually small. A refactor touches a staging model, `order_total` quietly comes out of a `CASE` expression as text instead of a number, and `dbt build` goes green — because nothing in the project ever asserted that `order_total` is a number. Three days later a finance dashboard shows revenue as zero, an analyst files a ticket, and four people spend an afternoon doing archaeology in git history to find out when the column changed and who was supposed to care.
The Meaning Gap in Your dbt Project
The incident that finally exposes the gap is usually small. A refactor touches a staging model, order_total quietly comes out of a CASE expression as text instead of a number, and dbt build goes green — because nothing in the project ever asserted that order_total is a number. Three days later a finance dashboard shows revenue as zero, an analyst files a ticket, and four people spend an afternoon doing archaeology in git history to find out when the column changed and who was supposed to care.
That afternoon is the problem our dbt integration exists to remove. Not the bug — bugs happen — but the fact that nobody, and no system, held the meaning of that column anywhere it could be checked.
dbt is excellent at building. It was never the system of record for meaning
dbt gives teams something genuinely great: transformations as code, tested, versioned, reviewed. But watch what happens to a dbt project as it grows past a handful of contributors, and a familiar gap opens up.
The schema.yml files that are supposed to describe the models decay first. They are optional, they live next to the code, and nothing fails when they fall out of date. A column gets renamed and its description doesn't; a model gains six columns and documents none of them. The YAML becomes an unreliable narrator.
Tests are point assertions, not shared definitions. A not_null here, a unique there — each one written by whoever felt the pain last. There is no single place that says what orders is: which columns constitute it, what type each one carries, which values status may legally take, which other model customer_id must point at. That knowledge exists — spread across three people's heads, a wiki page from last year, and the code itself.
And the highest-stakes surfaces are often the least protected. A model marked public is a cross-team API: other teams, other projects, other tools build on it. Yet in most estates public models ship without an enforced contract, which means their consumers have no schema guarantee at all. Our audit flags exactly this — contract-not-enforced is an error on public models for a reason. The same audit also flags id and *_id columns with no tests and no relationship declared, and sources with no freshness policy, where upstream drift will surface as broken models rather than alerts.
None of these are exotic failures. They are the default state of a dbt project under growth.
Why the usual fixes don't hold
Teams try discipline first: "everyone keeps schema.yml updated." That lasts until the next deadline. They try dbt's own model contracts, which are a real improvement — but three years of production contracts have made the limits well known. Contracts must be written and maintained per model, in the repo, by the same engineers who are busy shipping, and they only protect the models someone remembered to cover; nobody can even say what fraction of the estate is covered. They check structure only — names and types — so a status column that starts carrying values nobody has ever seen sails through a fully contracted model. And they explicitly do not apply to sources, which is the boundary where upstream drift actually enters a project.
The deeper issue is architectural. The dbt repo is the wrong sole home for meaning, because the repo is precisely the thing that changes with every pull request. When the definition and the implementation live in the same files, every refactor can silently rewrite the definition. Drift isn't detectable, because there is nothing stable to drift from.
What's missing is a governed model that lives outside the repo: a place where orders has a definition — its columns, their types, the allowed values of status, the reference from customer_id to customers, the lineage it participates in — and where changing that definition is a deliberate, human act rather than a side effect of a SQL edit.
What the fix looks like
This is what CoreModels does with a dbt estate, and it starts from artifacts you already have. Any dbt command writes target/manifest.json; that file — optionally joined by the catalog and semantic manifest — is everything we need. No warehouse credentials, no access to your dbt project, nothing to install in the pipeline beyond an HTTP call.
Import once, and your models, seeds, snapshots and sources become governed types; columns become elements with data types; accepted_values tests become taxonomies; relationships tests become references; dependency information becomes lineage. From then on, every fresh manifest can be audited against that governed model. The audit answers three questions the repo alone cannot: what part of the estate is actually governed (coverage), what changed against the agreed meaning (drift — including the field-type drift that started this article, reported as "governed: NUMBER(38,2), estate: VARCHAR" with the exact model and column named), and where the project falls short of dbt's own best practice (conformance).
Wire that audit into CI and the order_total incident becomes a failed check on the pull request that caused it, with a readable report naming the column — three days before the dashboard, not three days after. An error count above zero means the change violates governed meaning, and the build says so while the author still has full context.
The loop closes in the other direction too: from the governed model, CoreModels generates enforced dbt model contracts — a schema.yml with per-column types, not-null constraints, uniqueness tests, accepted values and relationships — so the protection dbt can enforce natively is derived from the definitions, instead of hand-maintained alongside them.
The real cost was never the incident
One broken dashboard is survivable. What compounds is everything around it: engineers afraid to refactor because they can't see what depends on a column; reviewers approving schema changes they can't evaluate; every consuming team maintaining its own defensive checks because no producer guarantee exists; and hours of institutional memory spent reconstructing, again and again, what a column was supposed to mean.
A dbt project tells you what broke. A governed model tells you what things mean — so you find out before it breaks. If you want to see it against your own estate, the dbt quickstart in the CoreModels docs walks through the first import and audit using nothing but your manifest.json.