Managing dbt Model Governance
This domain tests whether you can turn a dbt project (or a set of projects) into a system with explicit producers and consumers: which models are part of a stable public interface, which are internal implementation details, what guarantees a consumer gets about columns and types, and how you evolve or retire an interface without breaking downstream users. The four dbt features that implement this are groups, access modifiers, model contracts, and model versions — collectively marketed as the governance half of dbt Mesh. At an estimated 14% of the exam (roughly 9 items on a 65-question exam), expect a mix of: "which YAML is correct," "what error/warning does dbt raise," "which access level is required," and short scenarios where a team must expose one model and hide the rest. Questions are heavily parse-time vs run-time flavored and heavily YAML-syntax flavored, so memorize key names (access, group, contract.enforced, constraints, latest_version, versions, defined_in, deprecation_date) and where each one lives.
Model access: public, protected, and private access modifiers
access declares who may ref() a model. It is a dbt-level, parse-time restriction — it has nothing to do with database grants.
Values and defaults
| Modifier | Who can ref() it | Group required? | Usable cross-project in dbt Mesh? | Typical use |
|---|---|---|---|---|
private | Only resources in the same group (same project) | Yes — private is defined by group membership | No | Staging/intermediate models owned by one team |
protected (default) | Any resource in the same project, or in a project that installs this project as a package | No | No | Everything you haven't deliberately published |
public | Any project, including cross-project ref() in dbt Mesh | No | Yes | Marts that are a stable, supported API |
Key mechanics the exam expects you to state exactly:
- Default is
protected. Every model you never configure isprotected, notprivateand notpublic. accessapplies to models. Sources are consumed withsource()and have no access modifier; seeds and snapshots are project-internal and cannot be cross-project referenced. In dbt Mesh, only public models appear to downstream projects.- Canonical declaration is the YAML property under
models:; from dbt 1.10+accessandgroupmay also be set as configs (indbt_project.ymlor aconfig()block), which lets you apply them by folder. For syntax questions, the YAML property form is the safest answer. - Violations are parse-time errors (
DbtReferenceError), not run-time failures. No SQL is executed; the run never starts. - Changing
accessdoes not issueGRANT/REVOKE. Warehouse permissions are thegrantsconfig's job. Settingaccess: privatedoes not stop a BI tool or an analyst with warehouse credentials from querying the table.
# models/marts/finance/_finance__models.yml
models:
- name: fct_revenue # the published interface
access: public
group: finance
config:
contract:
enforced: true
- name: int_revenue_allocated # internal step
access: private
group: finance
Selection helper: dbt list --select access:public (also access:protected, access:private) — useful for auditing what you've published.
How to choose the exposure mechanism
Decision tree: picking between private, protected, public, and package vs cross-project consumption.
Common traps
- Assuming the default is
private. It isprotected. - Believing
access: privatealone hides a model. Without a group, private is meaningless; dbt validates that private models belong to a group, and the restriction is group scoped, not folder scoped. - Thinking
protectedblocks other teams inside the same project. It does not —protectedis "anyone in this project." - Thinking
publicweakens security.publiconly widens dbt reference rules; it also implicitly promises stability, which is why contracts and versions accompany it. - Confusing
accesswithgrants, row-level security, or dbt Cloud RBAC/permission sets. Exam distractors love "set access: private to prevent analysts from querying the table" — wrong. - Expecting an access violation to fail during
dbt runon the offending model. It fails during parsing, before compilation of any node. - Assuming exposures or
source()calls are governed byaccess. They are not.
Worked scenario. The finance team owns 14 models. Marketing has started ref()-ing int_revenue_allocated, an intermediate model that finance rewrites monthly. Finance wants marketing to depend only on fct_revenue, and both teams work in the same dbt project. Correct move: create a finance group, assign all 14 models to it, set access: private on the 13 internal ones, set access: public (or leave protected, but public documents intent) plus contract.enforced: true on fct_revenue. Marketing's existing ref('int_revenue_allocated') now fails at parse time with a reference error naming the group. Note what does not work: moving the models to a different folder, or renaming them with an int_ prefix — neither is enforced by dbt.
Unlock with a paid plan
7 more sections (about 3,480 words) continue below. Every paid plan unlocks all dbt Analytics Engineering Certification study guides, the full question bank and mock exams, from $4.97/month.