---
title: "Data platform review rules | Metacenta"
description: "The rules a Metacenta review checks, one page each: what the rule looks for, why it matters, how to fix it, and when breaking it is fine."
url: "https://metacenta.com/rules"
updated: "2026-10-14"
tokens: 3317
---

Rule library

# The rules a review checks

Each rule tests one condition in the artefacts you send. Each page says what the rule looks for, why it matters, how to fix it, and when it is fine to leave. You can use them without ever commissioning a review.

## What this library is

The published half of our method. Every rule in the current check suite has a page here. Each runs in every review that has the artefact it needs, and we name the rule behind each finding. [How a review is assessed](https://metacenta.com/standard) covers scoring, severity and confidence, and [a sample review](https://metacenta.com/sample) shows the rules at work in a finished report.

## Documentation

Can someone tell what a model, source or column means without reading its SQL?

- [dbt models with no description](https://metacenta.com/rules/model-undocumented)
- [dbt columns with no description](https://metacenta.com/rules/model-columns-undocumented)
- [dbt models that declare no columns](https://metacenta.com/rules/model-no-column-schema)
- [dbt sources with no description](https://metacenta.com/rules/source-undocumented)
- [dbt source groups with no description](https://metacenta.com/rules/source-parent-undocumented)
- [Warehouse columns missing from dbt YAML](https://metacenta.com/rules/catalog-undeclared-column)
- [dbt metrics with no description](https://metacenta.com/rules/metric-undocumented)
- [Power BI measures with no description](https://metacenta.com/rules/powerbi-measure-undescribed)
- [dbt models with several dates and no default](https://metacenta.com/rules/model-date-ambiguous)
- [Undocumented columns on dbt models behind dashboards](https://metacenta.com/rules/core-model-columns-undocumented)

## Repository hygiene

Does the repository run CI, carry tests, a README and an owner file, pin its packages, and keep credentials out?

- [Credential files committed to a data repository](https://metacenta.com/rules/repo-committed-credential-file)
- [Data repository with no CI pipeline](https://metacenta.com/rules/repo-no-ci)
- [Archived repositories under review](https://metacenta.com/rules/repo-archived)
- [Data repository with no tests](https://metacenta.com/rules/repo-no-tests)
- [Data repository with no README](https://metacenta.com/rules/repo-no-readme)
- [No CODEOWNERS file in a data repository](https://metacenta.com/rules/repo-no-codeowners)
- [Public data repository with no licence](https://metacenta.com/rules/repo-unlicensed)
- [Data repository with no recent pushes](https://metacenta.com/rules/repo-stale)
- [dbt packages.yml: how it works and why to lock it](https://metacenta.com/rules/dbt-packages-unlocked)
- [dbt_project.yml with no require-dbt-version](https://metacenta.com/rules/dbt-no-require-version)
- [Passwords and keys written into SQL files](https://metacenta.com/rules/sql-credential-literal)
- [Admin-level grants in Terraform](https://metacenta.com/rules/tf-broad-grant)
- [Public buckets and datasets in Terraform](https://metacenta.com/rules/tf-public-access)
- [Secrets written into Terraform files](https://metacenta.com/rules/tf-secret-literal)

## Testing

Is every model tested, and does a test confirm one row per key?

- [dbt models with no tests](https://metacenta.com/rules/model-untested)
- [dbt unique_combination_of_columns: how to use it](https://metacenta.com/rules/model-grain-not-asserted)
- [dbt key columns: no not_null test](https://metacenta.com/rules/model-no-not-null-test)
- [SQL tables with no assertion](https://metacenta.com/rules/sql-relation-untested)
- [Dagster asset checks: how to set them up](https://metacenta.com/rules/dagster-asset-unchecked)
- [OpenLineage datasets with no quality assertion](https://metacenta.com/rules/lineage-dataset-unasserted)

## Source monitoring

Is each source checked for freshness, and was it fresh when last checked?

- [dbt source freshness: how to set it up](https://metacenta.com/rules/source-no-freshness)
- [dbt freshness checks with no loaded_at_field](https://metacenta.com/rules/source-freshness-no-timestamp)
- [dbt sources past their freshness threshold](https://metacenta.com/rules/source-freshness-stale)
- [dbt freshness thresholds that are never run](https://metacenta.com/rules/source-freshness-unmeasured)

## Lineage integrity

Does every model read through ref() and source(), with no hard-coded tables and no cycles?

- [dbt dependency cycles in the model DAG](https://metacenta.com/rules/dependency-cycle)
- [dbt models with a hardcoded table name](https://metacenta.com/rules/hardcoded-table-reference)
- [dbt exposures: what they are and how to declare them](https://metacenta.com/rules/model-possibly-unused)
- [Deep dbt lineage: long chains of models](https://metacenta.com/rules/deep-lineage)
- [dbt models with too many direct dependents](https://metacenta.com/rules/fan-out-hotspot)
- [dbt models with no ref() or source()](https://metacenta.com/rules/root-model)
- [Large dbt sources that nothing references](https://metacenta.com/rules/catalog-large-unused-source)
- [Large dbt models with no declared reader](https://metacenta.com/rules/catalog-large-unread-model)
- [Circular dependencies between SQL files](https://metacenta.com/rules/sql-dependency-cycle)
- [SQL tables written by more than one file](https://metacenta.com/rules/sql-relation-written-twice)
- [Long chains of SQL tables built on tables](https://metacenta.com/rules/sql-deep-lineage)
- [Datasets written by more than one OpenLineage job](https://metacenta.com/rules/lineage-dataset-many-writers)

## Modelling correctness

Do incremental models, contracts and materialisations match what the warehouse actually holds?

- [dbt incremental models: how unique_key works](https://metacenta.com/rules/incremental-without-unique-key)
- [dbt public models with no enforced contract](https://metacenta.com/rules/public-model-without-contract)
- [dbt sources declared twice for one table](https://metacenta.com/rules/duplicate-source)
- [dbt staging models: how to set up the staging layer](https://metacenta.com/rules/staging-depends-on-staging)
- [dbt staging models that read marts](https://metacenta.com/rules/staging-depends-on-downstream)
- [dbt marts that read raw sources directly](https://metacenta.com/rules/downstream-depends-on-source)
- [dbt models mixing ref() and source()](https://metacenta.com/rules/direct-join-to-source)
- [dbt sources read directly by several models](https://metacenta.com/rules/source-fanout)
- [dbt models reading several raw sources at once](https://metacenta.com/rules/multiple-sources-joined)
- [dbt models joined back onto their own parent](https://metacenta.com/rules/rejoining-upstream-concept)
- [dbt exposures built on views or raw sources](https://metacenta.com/rules/exposure-parent-not-materialized)
- [dbt exposures on access: private models](https://metacenta.com/rules/exposure-on-private-model)
- [dbt YAML columns missing from the warehouse](https://metacenta.com/rules/catalog-phantom-column)
- [dbt models built differently from their config](https://metacenta.com/rules/catalog-materialization-drift)
- [dbt models that do not exist in the warehouse](https://metacenta.com/rules/catalog-model-not-built)
- [dbt models that never say what one row is](https://metacenta.com/rules/core-model-grain-undeclared)

## Maintainability

Can a model be read and changed safely: explicit columns, a readable body, no copied logic?

- [Duplicate logic across dbt models](https://metacenta.com/rules/duplicate-model-logic)
- [select * in dbt models](https://metacenta.com/rules/select-star)
- [dbt models with too many CTEs](https://metacenta.com/rules/high-cte-count)
- [dbt layer naming: inconsistent model prefixes](https://metacenta.com/rules/layer-naming-mismatch)
- [dbt tests declared away from their models](https://metacenta.com/rules/test-not-beside-model)
- [select * in SQL transformation files](https://metacenta.com/rules/sql-select-star-output)
- [SQL files with too many CTEs](https://metacenta.com/rules/sql-high-cte-count)
- [Duplicate SQL files in a warehouse repo](https://metacenta.com/rules/sql-duplicate-logic)

## Execution health

Did the last run build every model and pass every test?

- [dbt tests that are failing](https://metacenta.com/rules/test-failing)
- [dbt models that failed to build](https://metacenta.com/rules/node-build-error)
- [dbt models skipped behind a failure](https://metacenta.com/rules/run-blocked-cascade)
- [dbt table models dominating build time](https://metacenta.com/rules/run-slow-full-rebuild)
- [dbt tests slower than the model they test](https://metacenta.com/rules/run-slow-test)
- [Pipeline jobs whose latest run failed](https://metacenta.com/rules/lineage-job-failing)

## Ownership & accountability

Can each model, source and exposure be routed to a named owner who can be reached?

- [dbt models with no owner](https://metacenta.com/rules/model-unowned)
- [dbt sources with no owner](https://metacenta.com/rules/source-unowned)
- [dbt groups with no contactable owner](https://metacenta.com/rules/group-owner-unreachable)
- [dbt exposures with no contactable owner](https://metacenta.com/rules/exposure-owner-unreachable)
- [Dagster assets with no owner](https://metacenta.com/rules/dagster-asset-unowned)

## Consumption & cost

Does anybody read what the project builds, and are the most-read models tested?

- [dbt models nobody queries](https://metacenta.com/rules/warehouse-model-never-read)
- [Widely read dbt models with no tests](https://metacenta.com/rules/warehouse-hot-model-untested)
- [One dbt model dominating warehouse scans](https://metacenta.com/rules/warehouse-scan-hotspot)
- [Snowflake warehouses that never suspend](https://metacenta.com/rules/tf-warehouse-never-suspends)
- [Dashboards nobody opens](https://metacenta.com/rules/bi-dashboard-unviewed)

## AI lifecycle

Are AI and ML dependencies pinned, and is model use evaluated and observed?

- [Unpinned AI and ML library versions](https://metacenta.com/rules/ai-dependency-unpinned)
- [Model training with no experiment tracking](https://metacenta.com/rules/ai-training-without-tracking)
- [LLM apps with no evaluation suite](https://metacenta.com/rules/ai-llm-without-evaluation)
- [LLM calls with no tracing or cost tracking](https://metacenta.com/rules/ai-llm-without-observability)
- [Jupyter notebooks committed with outputs](https://metacenta.com/rules/ai-notebook-outputs-committed)

## Orchestration & reliability

Do scheduled jobs name an owner, retry, alert on failure, and avoid replaying history by accident?

- [Airflow DAG owner: how to set it and what it does](https://metacenta.com/rules/dag-unowned)
- [Airflow DAGs with no retries](https://metacenta.com/rules/dag-no-retries)
- [Airflow DAGs with no failure alert](https://metacenta.com/rules/dag-no-failure-alert)
- [Airflow catchup: what it does and how to control it](https://metacenta.com/rules/dag-catchup-unbounded)
- [Passwords and API keys hardcoded in DAG files](https://metacenta.com/rules/dag-credential-literal)
- [Airflow sensors with no timeout](https://metacenta.com/rules/dag-sensor-no-timeout)
- [dbt projects with no declared orchestrator](https://metacenta.com/rules/orchestration-undeclared)
- [dbt models behind dashboards on fragile schedules](https://metacenta.com/rules/consumer-model-fragile-schedule)

## BI semantic layer

Does the BI layer read the models dbt builds, and do its joins count correctly?

- [dbt semantic models reading unbuilt columns](https://metacenta.com/rules/semantic-column-not-built)
- [dbt semantic model keys with no unique test](https://metacenta.com/rules/semantic-primary-entity-unasserted)
- [Looker views reading tables dbt does not know](https://metacenta.com/rules/lookml-view-unbuilt-table)
- [Looker views reading raw dbt sources](https://metacenta.com/rules/lookml-view-reads-raw-source)
- [Looker derived tables: SQL, native and persistent](https://metacenta.com/rules/lookml-sql-derived-table)
- [Looker joined views with no primary key](https://metacenta.com/rules/lookml-joined-view-no-primary-key)
- [Looker joins with no declared relationship](https://metacenta.com/rules/lookml-join-no-relationship)
- [Power BI relationships filtering both ways](https://metacenta.com/rules/powerbi-bidirectional-filter)
- [Power BI tables reading unmodelled tables](https://metacenta.com/rules/powerbi-table-reads-unknown)
- [Tableau data sources using custom SQL](https://metacenta.com/rules/tableau-custom-sql)
- [Tableau data sources reading unmodelled tables](https://metacenta.com/rules/tableau-reads-unknown)
- [One BI measure name, several definitions](https://metacenta.com/rules/definition-inconsistent)
- [BI measures that redefine a dbt metric](https://metacenta.com/rules/bi-reimplements-dbt-metric)
- [LookML joins that inflate sums (fan-out)](https://metacenta.com/rules/lookml-join-fanout-sums)

## Frequently asked questions

### Are these all the rules a review checks?

Yes, for the current check suite. When a rule is added, its page is published in the same release, and a rule cannot ship without one.

[Ask about a review](https://metacenta.com/enquire?src=rules&via=md)

## Related pages

- [How a review is assessed](https://metacenta.com/standard)
- [What the review covers and what it costs](https://metacenta.com/)
