dbt logo

Six Refusals: What CoreModels Will Not Do to Your dbt Project

A dbt project is the most consequential text a data team owns. The definitions in it decide what gets built, what gets tested, and what every downstream consumer believes. CoreModels asks to publish into that text — governed descriptions, types, vocabularies, and bound ontology terms, emitted as dbt model property files with enforced contracts. Anything asking for that position should be judged less by what it can do than by what it will not do.

Six Refusals: What CoreModels Will Not Do to Your dbt Project

A dbt project is the most consequential text a data team owns. The definitions in it decide what gets built, what gets tested, and what every downstream consumer believes. CoreModels asks to publish into that text — governed descriptions, types, vocabularies, and bound ontology terms, emitted as dbt model property files with enforced contracts. Anything asking for that position should be judged less by what it can do than by what it will not do.

So here is the governance model as six commitments. Each one is falsifiable from the responses you already get back, with no privileged knowledge of our internals required.

1. Credential-free: artifacts in, artifacts out

CoreModels holds no warehouse credential and no dbt platform token. There is no live connection to read from and no background job phoning anything. The input is manifest.json — the file any dbt command already writes to target/ — optionally joined by catalog.json for warehouse-real column types and semantic_manifest.json for the semantic layer. The output is text returned in the HTTP or MCP response.

That means CoreModels never runs dbt, never queries your warehouse, and never reads a live platform API. When a security review asks what this integration can reach, the answer is: the JSON you chose to upload. Live sync is declared and deliberately deferred rather than quietly shipped, because it needs a credential story we have not decided is worth the trust it would cost.

The role split enforces the same boundary from the inside. Import requires Admin on the project; audit, generate, and status run at Viewer. A CI key that can fail your build is structurally incapable of editing the definitions it is checking against.

2. We never write to your repository

Generate returns a list of artifacts, each one a name, a kind, and its content. The name is a repository-relative path such as models/staging/stg_orders.yml, colocated beside that model's own .sql because the path was captured from the model's original_file_path at import. Two other layouts exist — one file per model directory, or a single repo-wide file kept for back-compat — and all three produce the same thing: text with a suggested home.

A path is a proposal about where a file belongs. It is not a write. No branch is created, no commit is made, no pull request is opened, and nothing lands in your project until your own review process lands it. Every generated file opens by saying what it is:

# Generated by CoreModels — governed model contracts.
# Meaning changes belong in CoreModels; regenerate this file rather than editing it.
version: 2

The last mile stays human because the last mile is where the judgment is.

3. We never overwrite a properties file we do not own

dbt refuses to parse a project in which one model has two property blocks. So when a model we govern already has properties somewhere else — its patch_path, recorded at import — the generator has a decision to make, and it makes the conservative one. It does not touch that file. It emits its own file and records a loss naming the file whose block has to be removed before the project will parse.

That restraint is deliberate. The other file may carry properties for models CoreModels does not govern, hand-written tests we know nothing about, or comments somebody cares about. Silently rewriting it would be the fastest available way to destroy the trust this integration depends on. The duplicate patch is a one-time cutover cost, and we would rather hand it to you as a named, reviewable item than resolve it behind your back and hope you agree.

4. We never guess

A relationships test is generated from a governed reference — a field whose expected type is another governed entity. Producing one takes two facts: which model to point at, and which column in it to match. When the target model is not in the governed schema, or its key column was never recorded and there is no unique-tested column to fall back on, the test is omitted and the reason is written down against that exact column.

The tempting alternative is to guess id and move on. That produces a test which sails through review and then fails against a real warehouse — a false failure in someone else's pipeline, attributed to a governance tool nobody asked for. When the facts are present, the test is exact:

      - name: customer_id
        description: The customer this order belongs to.
        data_type: varchar(36)
        constraints:
          - type: not_null
        data_tests:
          - not_null
          - relationships:
              to: ref('dim_customers')
              field: customer_id

When they are absent, that block is simply not there, and the ledger says which column and why. Absent and explained beats present and wrong.

5. Every loss is itemized

A successful generate still returns a ledger. In our vocabulary, an error means we could not proceed; a loss means we proceeded, and here is precisely what did not survive the crossing.

The real entries are mundane and specific. A governed vocabulary with parent and child terms flattens into accepted_values, which is a flat list — every term crosses, the hierarchy does not, and the record says so for that column. Targeting dbt 1.7 emits the legacy tests: key together with a note that 1.8 and newer read data_tests:. An ephemeral model is skipped, because ephemeral models cannot carry contracts. A governed type with no columns is skipped, because dbt rejects an enforced contract that has none. And when nothing at all is eligible, generate fails outright rather than handing you a valid, empty, misleading file.

Some of what dbt has no first-class slot for is carried anyway, in meta, where a catalog or an agent can read it rather than infer it from a column name:

        meta:
          coremodels:
            vocabulary: "Order Status"

That is the compromise, stated in full: the allowed values cross as a test, the vocabulary's identity crosses as metadata, and the parent-child structure is written on the receipt as something you did not get.

6. Ontology bindings are made by people, not by search results

CoreModels can search public ontologies and propose terms for a governed field. Those suggestions are advisory and persisted nowhere — they are search results in a relevance order, and closing the response discards them. Nothing is auto-applied, however confident a match looks, because a term that quietly attaches itself to a column is a claim about meaning that nobody made.

A binding is a separate, explicit act by someone holding the Admin role, written into the governed model as an assertion that this field means that term. Only then does it ride out into dbt: structurally, as standard and uri pairs under meta.coremodels.maps_to on the model and on each column, and — because description is the only slot persist_docs carries down into the warehouse column comment — appended to the description in brackets, which you can switch off. The structural carriage always rides.

The order matters more than the mechanism. A term reaches your published contract because a person with the authority to say what a column means said it.

Why "success, with a loss report" is the honest shape

Every translation between two systems loses something. dbt property files are an excellent home for types, constraints, tests, and prose, and a poor home for a term hierarchy or an ontology IRI; the governed model holds all of it. The only real question is whether a tool tells you which is which.

Most tools answer with silence, and silence reads as success. We would rather return a run that says: five files written, forty-one columns under enforced contracts, three losses — a flattened hierarchy here, a duplicate patch there, one omitted relationships test named with its reason. That is a longer answer and a less flattering one. It is also the only kind of answer you can act on, and a governance layer you cannot act on is decoration.

The verb-by-verb walkthrough — import, audit, generate, and what each one does and does not touch — is in the dbt quickstart that ships with the CoreModels integration docs.