dbt data-quality gate (verified over artifacts)
Hold a dbt project to a data-quality and governance policy and prove it —
conformance is gated by a script that reads dbt's own compiled artifacts, maps
each breach to a rule id + severity, and exits non-zero on blocking failures, not
by assertion.
Core principle
Quality is enforced, not assumed. The loop is: run the gate → triage by
severity → fix the root cause (add a test, a description, a tag, an owner) →
re-run, until the blocking-severity count is zero.
Be honest about scope (this is the rule that keeps the skill correct): tests
only assert what you encode. A green gate means your declared expectations
held, not that the data is correct, complete, or compliant. PII detection by
column name is heuristic — it misses unnamed/encoded PII and false-positives on
lookalikes. Freshness and volume anomalies need runtime data, not just the
manifest. This assists data governance; it is not a guarantee of data
correctness or GDPR compliance. → references/01-data-contracts-and-quality.md
When to use vs. not
- Use for: adding a data-quality / data-contract CI gate to a dbt project;
requiring tests, descriptions, owners, and freshness on models and sources;
enforcing not_null/unique on keys; finding untagged likely-PII columns;
setting a minimum test count or a test pass-rate threshold.
- Not for: profiling raw data values or detecting drift/anomalies at the row
level (needs a runtime data-observability tool); non-dbt pipelines; or
certifying GDPR compliance (this assists, it does not certify).
Inputs to gather first
- The artifacts —
target/manifest.json is required (run dbt compile or
dbt build). target/run_results.json is optional but enables the test
pass-rate check (run dbt build/dbt test). → references/02-the-manifest-gate.md
- The policy — minimum tests per model, which columns are "keys", which
meta/tags are required (owner?), and the PII tag + name patterns. Defaults
are sensible; confirm the bar with the user. →
references/04-dbt-tests-and-freshness.md
- Severity bar — the gate blocks on
blocking-severity rules by default;
warn rules are reported only. Promote/demote rules per project.
Workflow
Load each reference when you reach its step.
Set the policy & scope. Confirm the quality bar and that "green gate" ≠
"correct data". → references/01-data-contracts-and-quality.md
Produce the artifacts. From the dbt project root, generate the manifest
(and run_results for the pass-rate check). → references/02-the-manifest-gate.md
dbt deps && dbt build # -> target/manifest.json + target/run_results.json
# or, contract/metadata-only check without running models:
dbt compile # -> target/manifest.json
Configure the gate. Copy the example config and tune thresholds, key
patterns, PII patterns, required meta/tags, and the severity gate. → references/04-dbt-tests-and-freshness.md
cp scripts/dbt-quality.config.example.json dbt-quality.config.json # then edit
Run the gate and triage by severity. → references/02-the-manifest-gate.md
python3 scripts/dbt_quality_gate.py \
--manifest target/manifest.json \
--run-results target/run_results.json \
--config dbt-quality.config.json # -> dbt-quality-report/report.md
Scan for untagged PII and tag or remove what it finds. → references/03-pii-governance.md
python3 scripts/pii_scan.py --manifest target/manifest.json --config dbt-quality.config.json
Fix root causes in the dbt project — add generic tests
(not_null/unique/accepted_values/relationships), descriptions,
meta.owner, source freshness, and pii tags in your schema.yml /
_models.yml — then re-run steps 4–5 until the blocking count is zero. →
references/04-dbt-tests-and-freshness.md
Gate in CI on every PR, archiving the report. → references/05-running-it-and-ci.md
What's in this skill
scripts/dbt_quality_gate.py — the gate backbone: enforces tests-per-model, key not_null/unique, descriptions, source freshness, required meta/tags, PII tagging + exposure, and (with run_results) test pass-rate. Maps each breach to a rule id + severity, writes report.json + report.md, exits non-zero on blocking failures.
scripts/pii_scan.py — flags likely-PII columns (email, ssn, dob, name, phone, address, iban, credit_card, …) that aren't tagged; exits non-zero if any are found.
scripts/dbt-quality.config.example.json — thresholds, key/PII patterns, required meta/tags, severity gate + overrides, ignore list.
scripts/requirements.txt — stdlib-only; nothing to install.
scripts/sample/ — a tiny hand-built manifest + run_results used to self-test the gate.
references/01–05 — data contracts & quality dimensions, the manifest gate, PII governance, dbt tests & freshness, and running it in CI.
Definition of done
Guardrails — avoid these mistakes
- Don't claim "the data is correct" from a green gate. State "0 blocking
policy violations against the declared contract; tests passed." Overclaiming is
the cardinal error here.
- Don't add a test to silence the count — a
not_null on a column that's
never null proves nothing. Test the invariant that actually matters.
- Tag PII at the source, not just in marts; a
pii tag on a downstream model
doesn't protect the raw column. Re-run pii_scan.py after schema changes.
- Don't widen
ignore or demote a severity to go green. Suppress only
verified exceptions, with a written reason in review.
- Compile against the real target. A stale
target/manifest.json gates the
old graph — regenerate after every model/yml change.
- Freshness in the manifest is config, not a result. This gate checks that
thresholds are declared; run
dbt source freshness to check they're met.
- Heuristic PII detection is a floor, not a ceiling. A human still owns the
data-minimization and exposure decision.
1---2name: dbt-data-quality-gate3description: Enforce data quality, testing, contracts, and PII governance in a dbt project, gated by checks that actually run over dbt's compiled artifacts (target/manifest.json, target/run_results.json) — both plain JSON, so the gate is stdlib-only Python with no warehouse connection. Use when the user wants to add a data-quality CI gate, require tests/descriptions/owners on dbt models, enforce data contracts, check source freshness, find untagged PII columns, set a minimum test count or test pass-rate, or harden a data pipeline before merge. Triggers: "dbt", "data quality", "data contracts", "PII", "data tests", "freshness", "data pipeline gate".4license: MIT5---67# dbt data-quality gate (verified over artifacts)89Hold a dbt project to a data-quality and governance policy and **prove it** —10conformance is gated by a script that reads dbt's own compiled artifacts, maps11each breach to a rule id + severity, and exits non-zero on blocking failures, not12by assertion.1314## Core principle1516**Quality is enforced, not assumed.** The loop is: run the gate → triage by17severity → fix the root cause (add a test, a description, a tag, an owner) →18re-run, until the blocking-severity count is zero.1920**Be honest about scope (this is the rule that keeps the skill correct):** tests21only assert what you encode. A green gate means **your declared expectations22held**, not that the data is correct, complete, or compliant. PII detection by23column name is heuristic — it misses unnamed/encoded PII and false-positives on24lookalikes. Freshness and volume anomalies need runtime data, not just the25manifest. This **assists** data governance; it is **not** a guarantee of data26correctness or GDPR compliance. → `references/01-data-contracts-and-quality.md`2728## When to use vs. not2930- Use for: adding a data-quality / data-contract CI gate to a dbt project;31 requiring tests, descriptions, owners, and freshness on models and sources;32 enforcing not_null/unique on keys; finding untagged likely-PII columns;33 setting a minimum test count or a test pass-rate threshold.34- Not for: profiling raw data values or detecting drift/anomalies at the row35 level (needs a runtime data-observability tool); non-dbt pipelines; or36 certifying GDPR compliance (this assists, it does not certify).3738## Inputs to gather first39401. **The artifacts** — `target/manifest.json` is required (run `dbt compile` or41 `dbt build`). `target/run_results.json` is optional but enables the test42 pass-rate check (run `dbt build`/`dbt test`). → `references/02-the-manifest-gate.md`432. **The policy** — minimum tests per model, which columns are "keys", which44 meta/tags are required (owner?), and the PII tag + name patterns. Defaults45 are sensible; confirm the bar with the user. → `references/04-dbt-tests-and-freshness.md`463. **Severity bar** — the gate blocks on `blocking`-severity rules by default;47 `warn` rules are reported only. Promote/demote rules per project.4849## Workflow5051Load each reference when you reach its step.52531. **Set the policy & scope.** Confirm the quality bar and that "green gate" ≠54 "correct data". → `references/01-data-contracts-and-quality.md`55562. **Produce the artifacts.** From the dbt project root, generate the manifest57 (and run_results for the pass-rate check). → `references/02-the-manifest-gate.md`58 ```bash59 dbt deps && dbt build # -> target/manifest.json + target/run_results.json60 # or, contract/metadata-only check without running models:61 dbt compile # -> target/manifest.json62 ```63643. **Configure the gate.** Copy the example config and tune thresholds, key65 patterns, PII patterns, required meta/tags, and the severity gate. → `references/04-dbt-tests-and-freshness.md`66 ```bash67 cp scripts/dbt-quality.config.example.json dbt-quality.config.json # then edit68 ```69704. **Run the gate** and triage by severity. → `references/02-the-manifest-gate.md`71 ```bash72 python3 scripts/dbt_quality_gate.py \73 --manifest target/manifest.json \74 --run-results target/run_results.json \75 --config dbt-quality.config.json # -> dbt-quality-report/report.md76 ```77785. **Scan for untagged PII** and tag or remove what it finds. → `references/03-pii-governance.md`79 ```bash80 python3 scripts/pii_scan.py --manifest target/manifest.json --config dbt-quality.config.json81 ```82836. **Fix root causes** in the dbt project — add generic tests84 (`not_null`/`unique`/`accepted_values`/`relationships`), descriptions,85 `meta.owner`, source `freshness`, and `pii` tags in your `schema.yml` /86 `_models.yml` — then **re-run** steps 4–5 until the blocking count is zero. →87 `references/04-dbt-tests-and-freshness.md`88897. **Gate in CI** on every PR, archiving the report. → `references/05-running-it-and-ci.md`9091## What's in this skill9293- `scripts/dbt_quality_gate.py` — the gate backbone: enforces tests-per-model, key not_null/unique, descriptions, source freshness, required meta/tags, PII tagging + exposure, and (with run_results) test pass-rate. Maps each breach to a rule id + severity, writes `report.json` + `report.md`, exits non-zero on blocking failures.94- `scripts/pii_scan.py` — flags likely-PII columns (email, ssn, dob, name, phone, address, iban, credit_card, …) that aren't tagged; exits non-zero if any are found.95- `scripts/dbt-quality.config.example.json` — thresholds, key/PII patterns, required meta/tags, severity gate + overrides, ignore list.96- `scripts/requirements.txt` — **stdlib-only**; nothing to install.97- `scripts/sample/` — a tiny hand-built manifest + run_results used to self-test the gate.98- `references/01–05` — data contracts & quality dimensions, the manifest gate, PII governance, dbt tests & freshness, and running it in CI.99100## Definition of done101102- [ ] `dbt_quality_gate.py` reports **0 blocking-severity** violations across all103 models and sources.104- [ ] Every model has **>= minTestsPerModel** tests; key columns carry105 **not_null + unique**.106- [ ] Every model and source has a **description**; every source has107 **freshness** configured.108- [ ] `pii_scan.py` finds **no untagged** likely-PII columns; tagged PII is109 protected (masked/hashed) or justified.110- [ ] Required **meta/tags** (e.g. `owner`) present on every model.111- [ ] If run with `--run-results`, **test pass-rate** meets the threshold.112- [ ] CI runs both scripts on every PR; `report.md` archived.113114## Guardrails — avoid these mistakes115116- **Don't claim "the data is correct" from a green gate.** State "0 blocking117 policy violations against the declared contract; tests passed." Overclaiming is118 the cardinal error here.119- **Don't add a test to silence the count** — a `not_null` on a column that's120 never null proves nothing. Test the invariant that actually matters.121- **Tag PII at the source**, not just in marts; a `pii` tag on a downstream model122 doesn't protect the raw column. Re-run `pii_scan.py` after schema changes.123- **Don't widen `ignore` or demote a severity to go green.** Suppress only124 verified exceptions, with a written reason in review.125- **Compile against the real target.** A stale `target/manifest.json` gates the126 old graph — regenerate after every model/yml change.127- **Freshness in the manifest is config, not a result.** This gate checks that128 thresholds are *declared*; run `dbt source freshness` to check they're *met*.129- **Heuristic PII detection is a floor, not a ceiling.** A human still owns the130 data-minimization and exposure decision.