dbt / Intermediate: history and controlled workflows / Lesson 7
Contracts, documentation and model interfaces
Publish a stable branch model while separating column-shape contracts, business assertions and access governance.
On this page
Lesson goals
- Define an explicit model shape and its supported enforcement scope.
- Distinguish model versions and access from database user permissions.
A shared model behaves like a data API
A branch dimension consumed by several reports has an interface: column names, types, grain, meaning and freshness expectations. A model contract can check supported column-shape guarantees during a build, but it does not prove that branch_name is correct or that every branch exists. Keep business tests and source reconciliation alongside the contract. Removing a column or changing its type can break consumers even when the query still runs. Document intentional changes and choose a migration window rather than making downstream teams discover them through a failed morning report.
An enforced shape must list the actual columns
The illustrative contract declares two varchar columns for a two-column branch model, assuming the selected adapter and target support that type name. An enforced contract must cover every output column with compatible types; it is not a partial list of fields the author happens to remember. Support varies by materialization and platform: ephemeral and ordinary Python models are not covered by the same SQL-model contract behavior. Constraints are another layer whose definability and enforcement differ by database. A primary_key declaration accepted as metadata is not necessarily an enforced uniqueness rule.
version: 2
models:
- name: dim_branches
config:
contract:
enforced: true
columns:
- name: branch_id
data_type: varchar
- name: branch_name
data_type: varchar
A complete two-column shape example only. Target type compatibility and contract support require separate approved verification.
Descriptions should preserve meaning rather than restate names
Describe branch_id as the stable source identity, not simply โthe branch ID.โ Explain whether renamed branches retain identity, which branch names are canonical and how missing source rows are handled. A docs block can centralize a longer explanation and doc() can reference it in supported property contexts. Documentation generation and hosted browsing have their own command and platform boundaries; no such command is run here. Descriptions and artifacts can expose private business metadata, so publish only approved content. A polished catalog page cannot compensate for an ambiguous underlying grain.
Pin a model interface when a breaking change needs coexistence
Model versions are different from the engine version, project version and YAML format version. A consumer can explicitly reference version1 while another migrates to version2, provided the project defines those versions and their relations. The fragment requests a known model version rather than automatically following latest_version. This can improve stability but creates maintenance and deprecation responsibilities. A change in business meaning can be breaking even if column names and types stay identical. Versioning does not eliminate the need to communicate grain and measure changes or reconcile both interfaces during migration.
select branch_id, branch_name
from {{ ref('dim_branches', v=1) }}
Requires an explicitly defined version1 of dim_branches. It does not create that version or grant database read access.
Model access governs references rather than every user query
Private, protected and public model access govern supported dbt reference boundaries, with group and project context. They are not replacements for warehouse grants, row policies or secret management. In the v1.12 baseline, run-operation macros have documented access behavior different from ordinary model refs, which is another reason not to treat the modifier as a universal security boundary. Review actual runtime permissions separately. This course creates no contract, role, model version or documentation site. Local validation checks the authored shape and interface examples only, without claiming database enforcement or a successful build.
Common errors and fixes
A column contract is treated as a business-truth guarantee
- Why it happens
- Shape validation was confused with content and completeness assertions.
- How to fix it
- Add the relevant data tests and source reconciliation.
- Prevent it next time
- Document each guaranteeโs exact scope.
Private model access is called warehouse access control
- Why it happens
- Project reference governance was confused with database permissions.
- How to fix it
- Review the actual runtime principal and platform enforcement.
- Prevent it next time
- Keep model governance and security tests separate.
A same-type calculation change silently breaks consumers
- Why it happens
- Semantic compatibility was reduced to column names and types.
- How to fix it
- Version or coordinate the meaning change and reconcile downstream measures.
- Prevent it next time
- Review grain, units and definitions in interface changes.
Check your understanding
1. Does a two-column enforced contract prove branch IDs are unique?
Show worked solution
Not by shape alone. It checks the declared output structure under supported behavior. Use a uniqueness assertion and verify any actual database constraint separately. Also reconcile missing branches and source identity; correct column types can contain incorrect business data.
2. Why might a consumer pin ref("dim_branches",v=1)?
Show worked solution
It can preserve the agreed interface while a new version is introduced. The project must actually define that version and maintain its deprecation policy. Pinning is not a database grant and does not freeze the underlying data against every source update.
Key takeaways
- Shape contracts, data tests and permissions are different guarantees.
- A model interface includes semantics and migration ownership.