# dbt Project Standard: [Project name]

> Analytics engineering conventions for one dbt project or a group of
> related projects: layers and folder layout, naming, sources and
> freshness, model design and materialisation, tests (generic, singular
> and unit tests), model contracts and versions, groups and access,
> documentation, macros and packages, environments and targets, CI with
> state comparison and deferral, the code review checklist and cost
> guardrails. Written for dbt Core 1.11 and 1.12 (stable lines in
> October 2026, 1.12 marked Latest) and kept neutral where dbt Core 2.0
> (announced as an alpha in June 2026, with 2.0.x releases tagged since
> September 2026) or another engine differs; check docs.getdbt.com for
> the support status of the release you run. Replace every
> `[bracketed]` value and every row marked *Example*.

<!--
Guidance: How to use this template
- One standard per dbt project, or one shared standard for a mesh of
  projects with a short appendix per project. Keep it in the repository
  (for example `docs/project-standard.md`) so changes go through review.
- Fill section 1 (scope and principles) and section 2 (layers) first.
  Every later rule should follow from them.
- Write rules as short imperatives with a rule ID (DBT-NNN) so reviewers
  can cite them in pull request comments.
- The worked example is "Corvid Commerce", a fictional SaaS retailer
  with a lakehouse on Apache Iceberg, and an "Orders" and a "Checkout"
  data product. Replace it with your own project.
- This standard covers how models are built. Data quality rules for the
  whole organisation belong in Data Quality Standard
  (`data/data-quality-standard`); the promise to one consumer belongs in
  Data Contract (`data/data-contract`); scheduling, orchestration and
  recovery belong in Data Pipeline Specification
  (`data/data-pipeline-spec`).
-->

## Document control

<!-- Guidance: Name the project, the adapter and the dbt release line the rules were tested on. A rule that depends on a feature of one release must say so. -->

| Field | Value |
|---|---|
| Document ID | [DBT-STD-001] |
| Project | [corvid_analytics] *(Example)* |
| Repository | [git URL] |
| Data platform and adapter | [Lakehouse engine and dbt adapter, with version] |
| dbt release line | [dbt Core 1.12.x / other engine and version] |
| Version | [1.0] |
| Status | [Draft / Approved / Superseded] |
| Owner | [@analytics-engineering-lead] |
| Approvers/reviewers | [@data-platform-lead], [@data-governance-lead], [@security-reviewer] |
| Classification | [Internal] |
| Last updated | YYYY-MM-DD |
| Next review | YYYY-MM-DD (at least every [12] months or on a major dbt upgrade) |
| Related documents | [Data platform architecture], [Data quality standard], [CI/CD pipeline design] |

### Approvals

| Role | Name | Decision | Date |
|---|---|---|---|
| Analytics engineering lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Data platform lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Data governance lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |

### Revision history

| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@author] | First draft |
| 1.0 | YYYY-MM-DD | [@author] | Approved for use |

## Contents

- [1. Scope and principles](#1-scope-and-principles)
- [2. Layers and folder layout](#2-layers-and-folder-layout)
- [3. Naming conventions](#3-naming-conventions)
- [4. Sources and freshness](#4-sources-and-freshness)
- [5. Model design and materialisation](#5-model-design-and-materialisation)
- [6. Testing](#6-testing)
- [7. Contracts, versions, groups and access](#7-contracts-versions-groups-and-access)
- [8. Documentation](#8-documentation)
- [9. Macros and packages](#9-macros-and-packages)
- [10. Environments and targets](#10-environments-and-targets)
- [11. CI and deployment](#11-ci-and-deployment)
- [12. Code review checklist](#12-code-review-checklist)
- [13. Cost guardrails](#13-cost-guardrails)
- [14. Exceptions and compliance](#14-exceptions-and-compliance)
- [15. Glossary and references](#15-glossary-and-references)

## 1. Scope and principles

<!-- Guidance: Say which projects, teams and environments the standard binds, and the few principles that settle disputes. Pitfall: a long list of rules with no principle behind them, which invites endless debate in review. -->

This standard applies to [all dbt projects in the `corvid-analytics`
organisation] and to every person who opens a pull request against them.
It applies to models, seeds, snapshots, sources, tests, macros,
semantic definitions and exposures. Orchestration, ingestion and storage
design are out of scope and live in the documents named in the guidance
above.

| ID | Principle | What it means in practice |
|---|---|---|
| DBT-P-01 | Source text is reviewed code | Every change goes through a pull request, CI and one approving review |
| DBT-P-02 | One way in, one way out | Raw data enters only through declared sources; consumers read only marts or public models |
| DBT-P-03 | Transform once | A business rule lives in exactly one model; downstream models reuse it with `ref` |
| DBT-P-04 | Explicit interfaces | Models used outside their group have an enforced contract, an owner and a version policy |
| DBT-P-05 | Tested before trusted | No model reaches a mart without primary key tests and the tests its layer requires |
| DBT-P-06 | Cheapest correct build | Choose the materialisation and CI selection that give a correct result for the least compute |
| DBT-P-07 | Engine neutral where possible | Prefer standard SQL and cross-database macros; isolate engine-specific SQL in macros |

Rules are written as "must" (blocking in review) or "should" (expected,
an exception needs a short reason in the pull request). Section 14 says
how to record a standing exception.

## 2. Layers and folder layout

<!-- Guidance: Define each layer by what it may read, what it may do and who may read it. Questions: do you need an intermediate layer, where do snapshots and seeds go, how do you split marts by domain? Pitfall: marts that read straight from sources, which duplicates cleaning logic. -->

The project uses three model layers. Each layer may read only from the
layer before it, except that marts may also read other marts of the same
group.

| Layer | Purpose | May read from | May do | Default materialisation |
|---|---|---|---|---|
| Staging | One cleaned model per source table: rename, cast, standardise units and time zones, light deduplication | Sources only | Rename, cast, basic computation, filter deleted records | View |
| Intermediate | Reusable building blocks: joins, pivots, fan-out and re-grain steps that serve several marts | Staging and other intermediate models | Join, aggregate, apply business rules | Ephemeral or view |
| Marts | Business-facing entities and facts at a declared grain, organised by domain | Intermediate, staging, other marts in the group | Final business logic, denormalisation for consumers | Table or incremental |

Rules:

- **DBT-001** A staging model must select from exactly one source table
  and must not join.
- **DBT-002** Only staging models may call `source()`.
- **DBT-003** Intermediate models must not be exposed to BI tools or
  other projects; set their access to private or protected (section 7).
- **DBT-004** A mart must declare its grain in its description, for
  example "one row per order line".
- **DBT-005** Seeds hold small, static reference data maintained by
  people (country codes, channel mappings). Seeds must not hold data
  exported from a system.

Folder layout *(Example)*:

```text
corvid_analytics/
  dbt_project.yml
  packages.yml
  models/
    staging/
      shop_db/            # one folder per source system
        _shop_db__sources.yml
        _shop_db__models.yml
        stg_shop_db__orders.sql
        stg_shop_db__order_lines.sql
      payments_api/
        stg_payments_api__payments.sql
    intermediate/
      orders/
        int_orders__lines_joined_to_payments.sql
    marts/
      orders/             # one folder per domain or group
        _orders__models.yml
        fct_orders.sql
        dim_customers.sql
      checkout/
        fct_checkout_sessions.sql
    semantic/
      orders_semantic.yml
  snapshots/
  seeds/
  macros/
  tests/                  # singular tests
  analyses/
```

The `semantic/` folder holds semantic definitions governed by Semantic
Model Specification (`data/semantic-model-specification`). Analyses are
for ad hoc SQL that is versioned but never materialised.

## 3. Naming conventions

<!-- Guidance: Make names predictable enough that a reader knows the layer, source and grain from the file name alone. Pitfall: renaming conventions halfway through and leaving two styles in the project. -->

| Object | Pattern | Example |
|---|---|---|
| Staging model | `stg_<source>__<entity>` (double underscore) | `stg_shop_db__orders` *(Example)* |
| Intermediate model | `int_<domain>__<verb or description>` | `int_orders__lines_joined_to_payments` *(Example)* |
| Fact | `fct_<business process>` (plural) | `fct_orders` *(Example)* |
| Dimension | `dim_<entity>` (plural) | `dim_customers` *(Example)* |
| Wide mart for a tool | `rpt_<audience>__<subject>` | `rpt_finance__daily_revenue` *(Example)* |
| Snapshot | `snp_<source>__<entity>` | `snp_shop_db__products` *(Example)* |
| Seed | `seed_<subject>` | `seed_channel_mapping` *(Example)* |
| YAML properties file | `_<folder>__models.yml`, `_<folder>__sources.yml` | `_orders__models.yml` *(Example)* |
| Macro | verb first, snake case | `cents_to_currency` *(Example)* |

Column rules:

- **DBT-010** Use snake case for every model and column name.
- **DBT-011** Name primary keys `<entity>_id` and keep the same name for
  the foreign key in every other model.
- **DBT-012** Prefix booleans with `is_` or `has_`.
- **DBT-013** Suffix timestamps with `_at` and store them in UTC; suffix
  dates with `_date`. State the time zone in the description when a
  local date is required by the business.
- **DBT-014** Put the unit in the name of every measure whose unit is not
  obvious: `amount_eur`, `duration_seconds`.
- **DBT-015** Do not use abbreviations that are not in the glossary in
  section 15 or in the Data Dictionary (`architecture/data-dictionary`).
- **DBT-016** Do not use reserved words of any supported engine as
  column names.

## 4. Sources and freshness

<!-- Guidance: Every raw table the project reads must be declared once, with an owner, a loaded-at column and freshness thresholds. Questions: who is paged when a source is stale, does a stale source block the build? Pitfall: freshness thresholds copied from another source without asking when data actually arrives. -->

- **DBT-020** Declare every raw table in a sources file next to its
  staging models, with a description, an owner in `meta` (placed under
  `config`, which current releases document and which supports
  inheritance) and the loader that writes it.
- **DBT-021** Every source table that is loaded incrementally must have a
  loaded-at field and a warn and error freshness threshold.
- **DBT-022** Run the source freshness check before each production build
  and publish the result to the alert channel of the source owner.
- **DBT-023** A source that has a Data Contract (`data/data-contract`)
  must reference the contract ID in `meta`, and its tests must cover the
  contract's quality rules.

| Source | Owner | Loaded-at field | Warn after | Error after | On error *(Example)* |
|---|---|---|---|---|---|
| `shop_db.orders` *(Example)* | [@orders-platform] | `_loaded_at` | 2 hours | 6 hours | Skip downstream marts, alert owner |
| `payments_api.payments` *(Example)* | [@payments-team] | `_ingested_at` | 4 hours | 12 hours | Build with warning banner |
| [source.table] | [@owner] | [column] | [interval] | [interval] | [Action] |

Sources file sketch *(Example)*:

```yaml
sources:
  - name: shop_db
    description: Operational order database, replicated by CDC.
    config:
      meta:
        owner: "@orders-platform"
        contract_id: DC-ORD-001
      loaded_at_field: _loaded_at
      freshness:
        warn_after: {count: 2, period: hour}
        error_after: {count: 6, period: hour}
    tables:
      - name: orders
        columns:
          - name: order_id
            data_tests: [not_null, unique]
```

Property keys move between dbt releases: `freshness` moved under
`config` in 1.9, `loaded_at_field` in 1.10, and column-level `meta` in
1.10 (backported to 1.9). Top-level `meta` is still accepted for
backward compatibility but does not inherit, so write new properties
under `config`. Check the property reference for your release before
copying a sketch.

## 5. Model design and materialisation

<!-- Guidance: Agree the shape of a model file and when to use each materialisation. Pitfall: incremental models without a strategy for late-arriving or deleted records, which drift silently from the source. -->

Model file rules:

- **DBT-030** Start each model with import CTEs, one per `ref` or
  `source`, then logical CTEs, then a final `select`.
- **DBT-031** Select explicit columns in marts; `select *` is allowed only
  in import CTEs.
- **DBT-032** Use `ref` and `source` for every relation; hard-coded
  schema or table names are forbidden.
- **DBT-033** Keep one business rule per CTE and name the CTE after the
  rule.
- **DBT-034** Format SQL with the project's linter and formatter
  configuration; CI fails on lint errors.

| Materialisation | Use when | Rules |
|---|---|---|
| View | Staging, light transformations, small data | Default for staging |
| Ephemeral | A small intermediate step used by one or two models | Avoid when the step is reused widely or needs tests on its output |
| Table | Marts that are read often and rebuilt within the window | Default for marts under [50 GB] *(Example)* |
| Incremental | Large, append-heavy facts | Must declare `unique_key`, an incremental strategy and a look-back window for late data; must have a documented full-refresh procedure |
| Snapshot | Type 2 history of a mutable source table | Use timestamp strategy where a reliable updated-at column exists |
| Materialised view or dynamic table | Engine-managed refresh is cheaper than scheduled rebuilds | Approval of the data platform lead; model contracts are not supported on materialised views, so record an exception (section 14) with a compensating test |

- **DBT-035** Every incremental model must handle late-arriving records
  with a look-back window of at least [3 days] *(Example)* and must say in
  its description how deletes in the source are propagated.
- **DBT-036** Schedule a full refresh of each incremental model at least
  [monthly] *(Example)* or document why it is unsafe.
- **DBT-037** Python models need approval from the data platform lead and
  must state why SQL is not sufficient.

## 6. Testing

<!-- Guidance: Map the required tests to each layer so authors know the minimum before review. Questions: which tests block a deployment and which only warn, who triages failures? Pitfall: hundreds of warning-level tests nobody reads. -->

The project uses four kinds of tests. Data tests run against built data;
unit tests run against small fixed inputs before data is built
(available from dbt 1.8).

| Kind | What it checks | Where defined | Runs in |
|---|---|---|---|
| Generic data test | A reusable rule on a column or model: not null, unique, accepted values, relationships, or a custom generic test | Properties YAML | CI and production |
| Singular data test | A one-off SQL query that returns failing rows | `tests/` folder | CI and production |
| Unit test | Model logic on mocked input rows with expected output rows | Properties YAML under `models/` | Development and CI only |
| Source freshness | Data arrived on time | Sources YAML | Production, before the build |

Minimum tests per layer:

| Layer | Required | Recommended |
|---|---|---|
| Staging | Primary key `not_null` and `unique`; `accepted_values` on status columns | Row count within an expected range |
| Intermediate | Primary key tests where the grain changes | Unit test for any fan-out or re-grain logic |
| Marts | Primary key tests; `relationships` for each foreign key to a dimension; tests for each rule in an attached contract | Unit tests for every non-trivial calculation; reconciliation test against a finance or source total |

- **DBT-040** A test that fails in production with severity error stops
  downstream models in the same run and raises an alert for the model
  owner, using the severity scale in Incident Severity Matrix
  (`ops/incident-severity-matrix`) when consumers are affected.
- **DBT-041** Warning-level tests must have an owner and a review date;
  a warning ignored for [30 days] *(Example)* is either fixed, promoted
  to error or deleted.
- **DBT-042** Unit tests must not run in production builds; exclude them
  by resource type in production jobs.
- **DBT-043** Store failing rows for marts tests in a dedicated audit
  schema with a retention of [14 days] *(Example)*.
- **DBT-044** Write a unit test for every bug fix to a calculation, so
  the bug cannot return.

Organisation-wide thresholds (for example acceptable null rates per data
classification) come from Data Quality Standard
(`data/data-quality-standard`); this section only says how they are
implemented in dbt.

## 7. Contracts, versions, groups and access

<!-- Guidance: Decide which models are interfaces and how they change. Questions: which models do other teams or tools read, how much notice do they get before a breaking change? Pitfall: everything public, so nothing can change without breaking someone. -->

A model contract guarantees the column names and data types of a
model's output and fails the build when they differ. Model versions let
a breaking change ship as a new version while consumers migrate. Groups
assign models to an owning team, and access levels limit who may `ref` a
model.

| Access level | Who may reference | Use for |
|---|---|---|
| Private | Models in the same group | Intermediate steps |
| Protected | Models in the same project | Staging and internal marts (default) |
| Public | Any group or project (tools read through warehouse grants, not through `access`) | Marts that are interfaces for other teams, BI or data products |

Rules:

- **DBT-050** Every model belongs to a group, and every group has an
  owner with a contact in the group definition.
- **DBT-051** Every public model must have an enforced contract, a
  description, a declared grain and tests on its primary key.
- **DBT-052** A breaking change to a public model (removing or renaming
  a column, changing a type, changing the grain) must ship as a new model
  version. Keep the previous version for at least [90 days]
  *(Example)* and set its deprecation date.
- **DBT-053** Announce a new version and the deprecation date to every
  consumer listed in exposures and in the related data contract before
  merge.
- **DBT-054** Non-breaking additions (a new nullable column) do not need
  a new version but must be listed in the changelog of the data product.
- **DBT-055** Declare exposures for each dashboard, application or data
  product that reads a public model, with the owner and URL.

| Model | Group | Access | Contract | Latest version | Deprecated versions *(Example)* |
|---|---|---|---|---|---|
| `fct_orders` *(Example)* | orders | Public | Enforced | v2 | v1, deprecation date YYYY-MM-DD |
| `dim_customers` *(Example)* | orders | Public | Enforced | v1 | None |
| `int_orders__lines_joined_to_payments` *(Example)* | orders | Private | Not required | Not versioned | None |

Contracts are supported on some materialisations only: tables, views
(column names and types, without constraints) and incremental models
whose schema change policy is `append_new_columns` or `fail`. They are
not supported on materialised views, ephemeral models, Python models,
custom materialisations unless the author added support, or on seeds,
snapshots and sources. Check the contract page for your release and
adapter, and record unsupported cases in section 14.

Warehouse access is separate from dbt `access`: the access level controls
which groups and projects may `ref` a model, while BI tools and other
readers are governed by the grants and masking policies set per target
(section 10).

## 8. Documentation

<!-- Guidance: Say what must be documented and where, so generated documentation is useful without opening SQL. Pitfall: descriptions that restate the column name ("order_id: the order id"). -->

- **DBT-060** Every model and every column of a mart has a description.
  Descriptions say what the value means for the business, its unit, and
  any rule that changes it.
- **DBT-061** Reuse descriptions of shared columns through doc blocks so
  `customer_id` means the same thing everywhere.
- **DBT-062** Record in `config.meta` for each mart: owner, data classification
  per Data Classification Policy (`data/data-classification-policy`),
  contains personal data (yes or no), and the related data product ID.
- **DBT-063** Columns that hold personal data carry a tag that drives
  masking policies and the retention rules in Data Retention and Deletion
  Standard (`data/data-retention-deletion-standard`).
- **DBT-064** Generate and publish project documentation from the
  production build only, so it describes what consumers actually read.
- **DBT-065** Keep business glossary terms in the Data Dictionary
  (`architecture/data-dictionary`) and link to them; do not maintain a
  second glossary inside the project.

## 9. Macros and packages

<!-- Guidance: Keep macros few, tested and documented, and pin packages like any other dependency. Pitfall: macros that generate whole models, which hide logic from reviewers and from lineage. -->

- **DBT-070** Write a macro only when the same logic appears in three or
  more models or when engine-specific SQL must be isolated.
- **DBT-071** Each macro has a description, documented arguments and at
  least one model or unit test that exercises it.
- **DBT-072** Prefer the cross-database macros shipped with dbt and the
  adapter over custom engine-specific SQL.
- **DBT-073** Pin every package to an exact version or a narrow range in
  `packages.yml`, commit the lock file, and upgrade through a pull
  request that runs the full CI.
- **DBT-074** Approve new packages through the organisation's dependency
  review: licence, maintenance activity, and whether the package executes
  SQL with elevated grants.
- **DBT-075** Override `generate_schema_name` only in one place, and
  document the resulting schema names per target in section 10.

| Package | Version | Purpose | Approved by *(Example)* |
|---|---|---|---|
| [package name] | [exact version] | [Generic tests, date spine, cross-database helpers] | [@data-platform-lead] |

## 10. Environments and targets

<!-- Guidance: Map each target to a database or catalogue, a schema pattern, credentials and who may run it. Pitfall: developers sharing a schema, so one person's build overwrites another's. -->

| Target | Purpose | Catalogue and schema pattern | Credentials | Who runs it | Data *(Example)* |
|---|---|---|---|---|---|
| `dev` | Personal development | `dev` catalogue, `dbt_<username>` | Personal, read on raw, write on own schema | Each developer | Production data limited by a date filter of [30 days] |
| `ci` | Pull request checks | `ci` catalogue, `pr_<number>` | Service account | CI runner | Deferred to production artefacts, sampled where needed |
| `staging` | Release candidate verification | `staging` catalogue, production schema names | Service account | Release job | Full copy or zero-copy clone |
| `prod` | Consumer-facing build | `prod` catalogue, production schema names | Service account, no human write | Scheduler only | Full |

- **DBT-080** Credentials are never stored in `profiles.yml` in the
  repository; read them from environment variables or the secret store.
- **DBT-081** Personal development builds must limit data volume with a
  target-aware filter macro.
- **DBT-082** Masking and row access policies apply in every target that
  holds production personal data, including `dev`.
- **DBT-083** Drop CI schemas automatically when the pull request is
  closed and drop idle development schemas after [14 days] *(Example)*.

## 11. CI and deployment

<!-- Guidance: Describe the pull request job and the production job step by step. Questions: which artefacts are compared for state, what happens if the production manifest is missing? Pitfall: CI that builds the whole project on every change and is then switched off because it is slow. -->

The pull request job uses "slim CI": it compares the project with the
manifest of the latest production run (state comparison), builds only
modified models and their children, and defers unmodified parents to the
production relations so they are not rebuilt.

```mermaid
flowchart LR
  PR[Pull request opened] --> Lint[SQL lint and YAML checks]
  Lint --> Parse[Parse project]
  Parse --> State[Fetch production manifest]
  State --> Build[Build modified models and children with deferral]
  Build --> Tests[Data tests and unit tests on built models]
  Tests --> Contract[Contract and breaking change checks]
  Contract --> Review[Code review]
  Review --> Merge[Merge to main]
  Merge --> Deploy[Production job]
  Deploy --> Fresh[Source freshness]
  Fresh --> ProdBuild[Build and test]
  ProdBuild --> Docs[Publish docs and manifest]
```

| Step | Rule | Blocking |
|---|---|---|
| Lint | SQL linter and YAML schema validation pass | Yes |
| Selection | Modified models plus downstream children, selected by state | Yes |
| Deferral | Unmodified parents resolve to production relations | Yes |
| Breaking changes | Contract errors on public models fail the job | Yes |
| Tests | All error-severity tests pass on built models; unit tests pass | Yes |
| Documentation | New models and mart columns have descriptions | Yes |
| Cost | Bytes scanned or credits used above [threshold] post a warning | No |

- **DBT-090** The production job publishes its manifest to a fixed
  location after every successful run so CI can compare state.
- **DBT-091** If the production manifest is missing, state comparison
  cannot run: CI builds and tests the whole project (or the paths changed
  in the pull request), never skipping tests.
- **DBT-092** Merges to the main branch follow Branching Strategy
  (`dev/branching-strategy`); deployments run through the pipeline
  described in CI/CD Pipeline Design (`dev/ci-cd-pipeline-design`).
- **DBT-093** Upgrades of the dbt engine or adapter run the full project
  in `staging` and compare row counts and key aggregates of every public
  model before production.
- **DBT-094** Reruns after a failure use the retry capability for the
  failed nodes rather than a manual partial build.

## 12. Code review checklist

<!-- Guidance: Keep the checklist short enough that reviewers use it on every pull request; link each item to a rule. Pitfall: approving a pull request because CI passed, without reading the SQL. -->

| ID | Check | Rule |
|---|---|---|
| CR-01 | The pull request states the business reason and links the ticket | DBT-P-01 |
| CR-02 | New or changed models are in the right layer and folder | DBT-001 to DBT-005 |
| CR-03 | Names follow section 3, including keys, booleans and units | DBT-010 to DBT-016 |
| CR-04 | No hard-coded relations; import CTEs at the top | DBT-030 to DBT-032 |
| CR-05 | Grain is declared and joins cannot fan out unexpectedly | DBT-004 |
| CR-06 | Required tests for the layer exist; calculation changes have a unit test | DBT-040 to DBT-044 |
| CR-07 | Public model changes are additive, or ship as a new version with a deprecation date | DBT-051 to DBT-054 |
| CR-08 | Descriptions, owner, classification and personal data tags are present | DBT-060 to DBT-063 |
| CR-09 | Materialisation and incremental logic suit volume and late data | DBT-035, DBT-036 |
| CR-10 | CI cost warning reviewed; no full scan added to a frequent job without reason | Section 13 |
| CR-11 | Metric logic changes are reflected in the semantic model and the metric definition | Section 2 (`semantic/` folder), DBT-065 |

Use Pull Request (`dev/pull-request`) for the description template.

## 13. Cost guardrails

<!-- Guidance: Set budgets and technical limits that stop expensive mistakes before the invoice arrives. Pitfall: a single developer build scanning the full history of the largest table several times a day. -->

| ID | Guardrail | Limit or rule *(Example)* | Enforced by |
|---|---|---|---|
| COST-01 | Development data window | 30 days on facts | Target-aware filter macro |
| COST-02 | Query timeout per model | 30 minutes in CI, 2 hours in production | Profile and warehouse settings |
| COST-03 | Compute size per target | Small for dev and CI, medium for prod | Warehouse or cluster policy |
| COST-04 | Partition and clustering | Facts over 10 GB are partitioned by event date | Review checklist CR-09 |
| COST-05 | Incremental by default for large facts | Facts over 100 million rows are incremental | Review checklist |
| COST-06 | Job cost tagging | Every job sets query tags for project, target and job | Profile configuration, Cost Allocation and Tagging Standard (`ops/cost-allocation-tagging-standard`) |
| COST-07 | Monthly review | Top 10 models by compute reviewed each month | FinOps Cost Review (`ops/finops-cost-review`) |

- **DBT-100** A model that becomes one of the top 10 by compute for two
  months in a row needs an optimisation ticket or a recorded exception.
- **DBT-101** Delete models with no downstream nodes, no exposures and no
  queries in [90 days] *(Example)* after notifying the group owner.

## 14. Exceptions and compliance

<!-- Guidance: Say how a team departs from a rule without silently ignoring it, and how compliance is measured. Pitfall: exceptions agreed in chat and lost. -->

Record standing exceptions here. Each exception names the rule, the
reason, the compensating control and an expiry date.

| ID | Rule | Scope | Reason | Compensating control | Owner | Expires |
|---|---|---|---|---|---|---|
| EXC-001 *(Example)* | DBT-051 | `fct_checkout_sessions` | Materialised view used for near-real-time refresh; contract not supported | Singular test checks column names and types after each refresh | [@checkout-analytics] | YYYY-MM-DD |
| [EXC-NNN] | [Rule] | [Models] | [Reason] | [Control] | [@owner] | YYYY-MM-DD |

Measure compliance each quarter from the project manifest: share of
models with descriptions, share of public models with contracts, models
without primary key tests, and open exceptions past their expiry.
Report the figures to the data governance forum described in Data
Governance Operating Model (`data/data-governance-operating-model`).

## 15. Glossary and references

<!-- Guidance: Define project terms once and list the documents this standard depends on. -->

| Term | Meaning in this standard |
|---|---|
| Model | A SQL or Python file that dbt builds into a relation |
| Source | A raw table declared so that lineage and freshness are tracked |
| Grain | What one row of a model represents |
| Contract | Enforced guarantee of a model's column names and data types |
| Model version | A numbered variant of a model that lets consumers migrate after a breaking change |
| Group | A named set of models owned by one team |
| Exposure | A declared downstream consumer such as a dashboard or application |
| State comparison | Selecting nodes by comparing the project with a saved manifest |
| Deferral | Resolving references to unbuilt parents against another environment's relations |
| Slim CI | A CI job that builds only changed models and their children, using state and deferral |

References:

- dbt Core releases and changelog: https://github.com/dbt-labs/dbt-core
  (1.11.16 and 1.12.6 released 2026-10-14, 1.12.6 marked Latest; dbt
  Core 2.0, a Rust implementation, was announced as an alpha on
  2026-06-01 and 2.0.x releases have been tagged since September 2026;
  check docs.getdbt.com for its current support status before adopting
  it).
- dbt documentation: best practice guide on project structure, model
  contracts, model versions, model access and groups, unit tests, source
  freshness, state selection and deferral: https://docs.getdbt.com
- Related templates: Data Platform Architecture
  (`architecture/data-platform-architecture`), Data Pipeline
  Specification (`data/data-pipeline-spec`), Data Contract
  (`data/data-contract`), Data Quality Standard
  (`data/data-quality-standard`), Data Product Specification
  (`data/data-product-specification`), Semantic Model Specification
  (`data/semantic-model-specification`), Metric Definition
  (`data/metric-definition`), Coding Standards (`dev/coding-standards`).

## Standard and licence note

Structure aligned with the project structure, testing, governance and
CI concepts described in the dbt documentation (docs.getdbt.com) and
with dbt Core (Apache License 2.0). Only concept, command and property
names are used; no text, examples or diagrams from the dbt
documentation are reproduced, and the Corvid Commerce example is
fictional. This template is original text written for this collection.
dbt, dbt Core and dbt Labs are trademarks of dbt Labs, Inc.; this
template is not affiliated with or endorsed by dbt Labs. Check the
current documentation for your dbt release and adapter before use, as
property names and defaults change between releases. Template licence:
MIT.
