Design a dbt CI pipeline that gates every pull request: compile, run, test, and check source freshness in an isolated developer schema; enforce model contracts on published marts; run slim CI on changed models only using dbt state comparison; and block merges on test failures or contract violations.
How this skill is triggered — by the user, by Claude, or both
Slash command
/analytics-engineering:dbt-ci-governanceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Purpose:** Build the CI gate that enforces data quality and schema contracts before any dbt model reaches production. Used by `analytics-engineer` (pipeline design) and `data-quality-testing-engineer` (test gate and contract enforcement).
Purpose: Build the CI gate that enforces data quality and schema contracts before any dbt model reaches production. Used by analytics-engineer (pipeline design) and data-quality-testing-engineer (test gate and contract enforcement).
dbt compile.A CI pipeline that only runs dbt compile is not a CI gate — it proves only that the SQL is syntactically valid. A real gate runs in this order:
| Step | Command | Fails on | Why it's required |
|---|---|---|---|
| 1. Compile | dbt compile | Syntax errors, missing refs | Fastest check; catches broken references |
| 2. Source freshness | dbt source freshness | Stale source data | Prevents building on yesterday's data |
| 3. Build (run + test) | dbt build --select state:modified+ | Model failures, test failures | Proves the models and their tests pass |
| 4. Contract check | dbt build --select state:modified+ with contracts enabled | Column drops, type changes on mart models | Prevents breaking downstream consumers |
On small projects (< 50 models): run dbt build against all models. On large projects, use slim CI (step 3 above uses state:modified+) to keep CI time under 10 minutes.
Slim CI uses dbt's state: selector to run only models affected by the PR's changes.
How it works:
manifest.json) from the last successful production run.--state argument.state:modified+ to select models that changed and all their downstream dependents.GitHub Actions example (simplified):
- name: Download production manifest
run: |
aws s3 cp s3://your-bucket/dbt-artifacts/manifest.json ./prod-manifest/manifest.json
- name: dbt build (slim CI)
run: |
dbt build \
--select state:modified+ \
--state ./prod-manifest \
--target ci \
--profiles-dir ./profiles
env:
DBT_TARGET_SCHEMA: dbt_ci_${{ github.event.pull_request.number }}
The CI schema isolation rule: every CI run writes to an isolated schema (dbt_ci_<PR_NUMBER>). This prevents CI runs from corrupting dev or prod data and allows parallel PRs to run without interfering.
A dbt model contract declares the columns and data types that the model guarantees to its consumers. Any breaking change (column removed, type changed) fails the dbt build with a ContractError rather than silently breaking downstream dashboards.
Add a contract to a published mart model:
# models/marts/schema.yml
models:
- name: fct_orders
config:
contract:
enforced: true
columns:
- name: order_id
data_type: bigint
constraints:
- type: not_null
- type: unique
- name: customer_id
data_type: bigint
constraints:
- type: not_null
- name: order_amount_usd
data_type: numeric
- name: order_status
data_type: varchar
- name: created_at
data_type: timestamp
Contract enforcement rules:
exposure (a declared downstream consumer) should have a contract.Source freshness checks verify that the source data the models depend on is not stale before building.
Configuration in sources.yml:
sources:
- name: raw_orders
database: raw
schema: orders
freshness:
warn_after:
count: 6
period: hour
error_after:
count: 24
period: hour
loaded_at_field: _loaded_at
tables:
- name: orders
- name: order_items
In CI: run dbt source freshness before dbt build. If the source is stale, fail the build before spending compute on downstream models that would be built on outdated data.
Freshness severity:
warn_after — prints a warning but does not fail CI; useful for sources with irregular but acceptable lag.error_after — fails CI; use this for sources that feed time-sensitive marts or dashboards.Every PR that adds or modifies a dbt model must pass the following test categories before merge:
| Test type | Tool | Required for merge |
|---|---|---|
not_null on key columns | dbt generic test | YES |
unique on the grain column | dbt generic test | YES |
accepted_values on low-cardinality categoricals | dbt generic test | YES (if applicable) |
relationships on every FK column in a mart | dbt generic test | YES (for mart models) |
| Source freshness | dbt source freshness | YES |
| Contract check (for published marts) | dbt contract enforcement | YES |
| Row-count anomaly (large fact tables) | dbt-utils or custom singular test | Recommended |
Test coverage policy (minimum):
Every model in marts/: not_null + unique + relationships + contract
Every model in staging/: not_null + unique on primary key
Every model in intermediate/: not_null on critical path columns
dbt compile — this is the most common CI shortcut; it catches syntax but not logic failures, stale sources, or test failures. Always run dbt build.dbt_ci_<PR> schemas.state:modified+ without a valid --state manifest falls back to running all models; download the latest prod manifest as an explicit CI step.npx claudepluginhub mcorbett51090/ravenclaude --plugin analytics-engineeringGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.