dbt practices (Jarvus house conventions)
This is the opinionated layer: how Jarvus wants dbt models written, tested, and linted —
not how to operate dbt. The mechanics (running commands, writing a unit-test YAML, dbt state,
the semantic layer, mesh) are covered by dbt-labs' first-party skills; this skill defers
to them and adds the house conventions, quality bar, and CI gate they don't carry.
Source of authority. The conventions here are distilled from our most mature dbt work —
calitp-data-infra and wmata-tides-infra (TIDES / Cal-ITP) — and the modeling rules our
dbt lead has set on in-flight projects. Treat those as authoritative over ad-hoc/AI-scaffolded
patterns. Adapter examples use DuckDB (our TIDES stack) and BigQuery where they differ.
Maturity: first pass, pending dbt-lead review. This covers the model-quality half
(conventions + testing + lint/CI). The deployment half (run/orchestration/publish
patterns across DuckDB & BigQuery) is deliberately deferred to a second pass — see
deployment.md.
The conventions at a glance
These are the rules to apply on every model; rationale and examples in
conventions.md:
- Stage discipline.
staging is 1:1 with the source — rename/recast/light-clean only;
never dedup or aggregate in staging. Reshaping/business logic lives in intermediate;
consumable grain lives in marts.
- Materialization is deliberate, never assumed — decide view vs table vs incremental per
model and record why (grain, cost, downstream use).
- No inner joins. Use left joins with explicit
where filters so dropped rows are
visible, not silent. A silently-dropping inner join is a bug (see the case study in
conventions.md).
- Import dependencies at the top as
with <name> as ( select ... from {{ ref(...) }} )
CTEs — one per upstream — for readability and dependency tracing.
- Semantic table aliases, never letter abbreviations: derive a short noun from the
table/CTE by stripping layer/namespace prefixes (
fct_scheduled_stop_times →
scheduled_stop_times, stg_vehicle_positions → vehicle_positions). Role-based names
for self-joins (from_stop/to_stop). This is partly machine-enforced by sqlfluff
(aliasing.forbid, references.qualification).
- Timeless comments. Comments state what is persistently true — never narrate a change
("renamed from…", "replaced…").
- It must work for every tenant. Changes parse and run across all configured
tenants/projects, not just the one you touched — the CI gate enforces this.
- A fast (<5 min) dev loop is a project requirement. Every dbt project needs a documented,
quickly runnable way to exercise it (local target, sampled slice, unit tests,
state:modified)
so modeling work is never blocked by slow full-pipeline runs or remote fetch paths.
- Unvalidated demo work doesn't live in
main. Demos stay on branches; if they merged for
a demo, remove them after, leaving breadcrumbs (tag/branch + a note). Plausible-looking but
unvalidated models in the main DAG are a liability others can't ignore.
Testing
Generic schema tests + the quality-model pattern + unit tests, with severity and scoping.
See testing.md. In short: generic tests (not_null/unique/
relationships/accepted_values + dbt_utils) on keys and critical columns; the
int_* → fct_*_quality → fct_* quality-model pattern for record-level validation; and dbt
unit tests (fixture-based, no warehouse) for staging/intermediate transform logic.
Linting & CI
sqlfluff (dbt templater), run directly in a path-filtered CI gate. See
linting-and-ci.md and the copy-paste templates in
references/templates/. The DuckDB stack reads public data, so the
lint + multi-tenant dbt parse + unit-test gate is credential-free and fork-safe — no
warehouse secret needed (unlike a BigQuery project, which needs a service account).
Relationship to other skills
- dbt-labs first-party skills (
dbt, dbt-extras, dbt-migration from
dbt-labs/dbt-agent-skills) — the mechanics. Don't re-document them; point at them:
running-dbt-commands — CLI invocation / choosing the executable.
adding-dbt-unit-test — the unit-test YAML mechanics (this skill says when: staging/
intermediate transform logic).
using-dbt-state — slim CI via state:modified / deferral (serves "no huge data reads").
using-dbt-for-analytics-engineering — generic AE principles; this skill is the Jarvus
extension of it.
dbt-migration/* + creating-mermaid-dbt-dag — relevant to the deferred deployment pass.
ci-quality-gates — owns how a repo gates a PR. The dbt gate here is the dbt lane
of that contract; the CI template lives in references/templates/ and
plugs into the same path-filtered, lockfile-frozen harness.
Reference files
| File |
Read when |
| conventions.md |
writing/reviewing models — the modeling rules + rationale |
| testing.md |
deciding test coverage; generic vs quality-model vs unit tests |
| linting-and-ci.md |
setting up sqlfluff and the CI gate |
| references/templates/ |
copy-paste config (sqlfluff, CI workflow) |
| deployment.md |
deferred — run/orchestration/publish (second pass) |
1---2name: jarvus-dbt3description: Jarvus house conventions for writing, testing, and linting dbt projects — model layering and grain, what each stage may/may not do, the no-inner-joins and semantic-alias rules, the fast (<5 min) local dev-loop requirement, the no-unvalidated-demo-work-in-main rule, the generic + quality-model + unit-test patterns, the sqlfluff config, and a credential-free multi-tenant CI gate. Use when building or reviewing dbt models, deciding materialization or test coverage, setting up dbt linting/CI, or when "dbt", "sqlfluff", "staging/intermediate/marts", "dbt test", or "TIDES" come up. This is the opinionated house layer ON TOP OF dbt-labs' first-party dbt skills (which cover the mechanics) — see "Relationship to other skills".4---56# dbt practices (Jarvus house conventions)78This is the **opinionated layer**: how Jarvus wants dbt models written, tested, and linted —9not how to operate dbt. The mechanics (running commands, writing a unit-test YAML, dbt state,10the semantic layer, mesh) are covered by **dbt-labs' first-party skills**; this skill defers11to them and adds the house conventions, quality bar, and CI gate they don't carry.1213**Source of authority.** The conventions here are distilled from our most mature dbt work —14`calitp-data-infra` and `wmata-tides-infra` (TIDES / Cal-ITP) — and the modeling rules our15dbt lead has set on in-flight projects. Treat those as authoritative over ad-hoc/AI-scaffolded16patterns. Adapter examples use **DuckDB** (our TIDES stack) and **BigQuery** where they differ.1718> **Maturity: first pass, pending dbt-lead review.** This covers the **model-quality** half19> (conventions + testing + lint/CI). The **deployment** half (run/orchestration/publish20> patterns across DuckDB & BigQuery) is deliberately deferred to a second pass — see21> [deployment.md](references/deployment.md).2223## The conventions at a glance2425These are the rules to apply on every model; rationale and examples in26[conventions.md](references/conventions.md):2728- **Stage discipline.** `staging` is 1:1 with the source — rename/recast/light-clean only;29 **never dedup or aggregate in staging**. Reshaping/business logic lives in `intermediate`;30 consumable grain lives in `marts`.31- **Materialization is deliberate**, never assumed — decide view vs table vs incremental per32 model and record why (grain, cost, downstream use).33- **No inner joins.** Use left joins with explicit `where` filters so dropped rows are34 visible, not silent. A silently-dropping inner join is a bug (see the case study in35 [conventions.md](references/conventions.md)).36- **Import dependencies at the top** as `with <name> as ( select ... from {{ ref(...) }} )`37 CTEs — one per upstream — for readability and dependency tracing.38- **Semantic table aliases**, never letter abbreviations: derive a short noun from the39 table/CTE by stripping layer/namespace prefixes (`fct_scheduled_stop_times` →40 `scheduled_stop_times`, `stg_vehicle_positions` → `vehicle_positions`). Role-based names41 for self-joins (`from_stop`/`to_stop`). This is partly machine-enforced by sqlfluff42 (`aliasing.forbid`, `references.qualification`).43- **Timeless comments.** Comments state what is persistently true — never narrate a change44 ("renamed from…", "replaced…").45- **It must work for every tenant.** Changes parse and run across *all* configured46 tenants/projects, not just the one you touched — the CI gate enforces this.47- **A fast (<5 min) dev loop is a project requirement.** Every dbt project needs a documented,48 quickly runnable way to exercise it (local target, sampled slice, unit tests, `state:modified`)49 so modeling work is never blocked by slow full-pipeline runs or remote fetch paths.50- **Unvalidated demo work doesn't live in `main`.** Demos stay on branches; if they merged for51 a demo, remove them after, leaving breadcrumbs (tag/branch + a note). Plausible-looking but52 unvalidated models in the main DAG are a liability others can't ignore.5354## Testing5556Generic schema tests + the quality-model pattern + unit tests, with severity and scoping.57See [testing.md](references/testing.md). In short: generic tests (`not_null`/`unique`/58`relationships`/`accepted_values` + `dbt_utils`) on keys and critical columns; the59`int_* → fct_*_quality → fct_*` quality-model pattern for record-level validation; and dbt60**unit tests** (fixture-based, no warehouse) for staging/intermediate *transform logic*.6162## Linting & CI6364`sqlfluff` (dbt templater), run directly in a path-filtered CI gate. See65[linting-and-ci.md](references/linting-and-ci.md) and the copy-paste templates in66[references/templates/](references/templates/). The DuckDB stack reads public data, so the67**lint + multi-tenant `dbt parse` + unit-test gate is credential-free and fork-safe** — no68warehouse secret needed (unlike a BigQuery project, which needs a service account).6970## Relationship to other skills7172- **dbt-labs first-party skills** (`dbt`, `dbt-extras`, `dbt-migration` from73 `dbt-labs/dbt-agent-skills`) — the **mechanics**. Don't re-document them; point at them:74 - `running-dbt-commands` — CLI invocation / choosing the executable.75 - `adding-dbt-unit-test` — the unit-test YAML mechanics (this skill says *when*: staging/76 intermediate transform logic).77 - `using-dbt-state` — slim CI via `state:modified` / deferral (serves "no huge data reads").78 - `using-dbt-for-analytics-engineering` — generic AE principles; this skill is the Jarvus79 extension of it.80 - `dbt-migration/*` + `creating-mermaid-dbt-dag` — relevant to the deferred deployment pass.81- **`ci-quality-gates`** — owns *how a repo gates a PR*. The dbt gate here is the dbt **lane**82 of that contract; the CI template lives in [references/templates/](references/templates/) and83 plugs into the same path-filtered, lockfile-frozen harness.8485## Reference files8687| File | Read when |88|---|---|89| [conventions.md](references/conventions.md) | writing/reviewing models — the modeling rules + rationale |90| [testing.md](references/testing.md) | deciding test coverage; generic vs quality-model vs unit tests |91| [linting-and-ci.md](references/linting-and-ci.md) | setting up sqlfluff and the CI gate |92| [references/templates/](references/templates/) | copy-paste config (sqlfluff, CI workflow) |93| [deployment.md](references/deployment.md) | **deferred** — run/orchestration/publish (second pass) |