Skip to content
Back to skills

Dbt Ci Governance

ASecurity

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.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentsgosqlawstestingrefactoringgitdatabase

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill dbt-ci-governance --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dbt Ci Governance?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dbt Ci Governance
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-dbt-ci-governance/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-dbt-ci-governance)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dbt-ci-governance
description: "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."
---

# Skill: dbt-ci-governance

**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).

## When to use

- Setting up CI for a new dbt project.
- Adding a CI gate to an existing project that only runs `dbt compile`.
- Enforcing model contracts on published mart boundaries.
- Reducing CI cost on large projects by running only changed models (slim CI).

---

## The dbt CI gate — what must run on every PR

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 — run only what changed

Slim CI uses dbt's `state:` selector to run only models affected by the PR's changes.

**How it works:**
1. In CI, download the production manifest (`manifest.json`) from the last successful production run.
2. Pass it as the `--state` argument.
3. Use `state:modified+` to select models that changed **and all their downstream dependents**.

**GitHub Actions example (simplified):**

```yaml
- 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.

---

## Model contracts — enforce the mart boundary

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:**

```yaml
# 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:**
- Enforce contracts only on **published mart models** (the consumer boundary). Staging and intermediate models are internal; over-contracting them makes refactoring painful.
- Any model referenced in an `exposure` (a declared downstream consumer) should have a contract.
- Adding a new column is non-breaking (allowed without a full-refresh signal). Removing a column or changing a type is breaking and will fail the contract check — this is the desired behaviour.

---

## Source freshness in CI

Source freshness checks verify that the source data the models depend on is not stale before building.

**Configuration in `sources.yml`:**

```yaml
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.

---

## Test gates — what must pass before merge

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
```

---

## Pitfalls

- **CI runs only `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`.
- **No schema isolation in CI** — concurrent PR pipelines overwrite each other's tables in a shared schema; use `dbt_ci_<PR>` schemas.
- **Contracts on internal models** — enforcing contracts on staging and intermediate models prevents normal refactoring; contracts belong at the mart boundary.
- **Slim CI without state comparison** — `state:modified+` without a valid `--state` manifest falls back to running all models; download the latest prod manifest as an explicit CI step.
- **Source freshness not integrated into CI** — the build passes on data that is 30 hours old; the dashboard is correct SQL on stale numbers.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…