node_dependency

Rule: node_dependency

Manifest Rule

node_dependency details
This rule forbids dependency relationships between nodes that match configurable name, path, or type patterns. It checks the parent_map in manifest.json to find the direct upstream dependencies of each node.

Use it to enforce DAG layering conventions — for example: staging models must not depend on other staging models, intermediate models must not bypass staging by referencing source() directly, or staging models must not reference downstream layers (inverted DAG).

One violation is produced per forbidden parent found.


Configuration

  • type: Must be node_dependency.
  • from_name_pattern: (optional) Regex matched against the dependent node’s name. If omitted, all nodes in applies_to are checked.
  • parent_name_pattern: (optional) Regex matched against the parent node’s name (last segment of its unique ID). A parent must match all specified filters to trigger a violation.
  • parent_path_pattern: (optional) Regex matched against the parent node’s file path. Only applies when the parent exists in manifest.json nodes or sources.
  • parent_type: (optional) Type filter for the parent. Valid values: model, source, seed, snapshot. AND-combined with any name/path patterns.
  • applies_to: (optional) List of dbt object types this rule checks.
    • Default: ["models"]
    • Options: models, snapshots
Common Rule Config
  • name: Human-readable name of the rule.
  • severity: "error" (fail) or "warning" (warn only).
    • (optional, defaults to "error" if not specified)
  • description: Human-readable explanation of the rule.
  • category: Override the default rule category. Included in structured output (JSON, CSV, NDJSON) but not in the CLI table. Each rule has a built-in default (e.g. documentation, naming, testing, governance, structure, performance).
    • (optional, defaults to the rule type's built-in category)
  • includes: List of patterns to explicitly include for this rule. See Includes & Excludes for pattern syntax and examples.
  • excludes: List of patterns to explicitly exclude from this rule. See Includes & Excludes for pattern syntax and examples.
  • model_materializations: Filter models by materialization type. Only applies when applies_to includes models.
    • (optional, if not specified all materializations are included)
    • Built-in types: table, view, incremental, ephemeral, materialized_view. Custom materializations are also supported.
    • Example: ["table", "incremental"]

Example Config

manifest_tests:
  # Use case 1 — staging must not depend on staging
  - name: "no_stg_to_stg"
    type: "node_dependency"
    from_name_pattern: "^stg_"
    parent_name_pattern: "^stg_"
    severity: "error"

  # Use case 2 — intermediate/mart layers must not bypass staging
  - name: "no_bypass_staging"
    type: "node_dependency"
    from_name_pattern: "^(int_|fct_|dim_|obt_|rpt_)"
    parent_type: "source"
    severity: "error"

  # Use case 3 — no inverted DAG (staging must not depend on downstream)
  - name: "no_inverted_dag"
    type: "node_dependency"
    from_name_pattern: "^stg_"
    parent_name_pattern: "^(int_|fct_|dim_|obt_|rpt_)"
    severity: "error"
[[manifest_tests]]
name = "no_stg_to_stg"
type = "node_dependency"
from_name_pattern = "^stg_"
parent_name_pattern = "^stg_"
severity = "error"

[[manifest_tests]]
name = "no_bypass_staging"
type = "node_dependency"
from_name_pattern = "^(int_|fct_|dim_|obt_|rpt_)"
parent_type = "source"
severity = "error"

[[manifest_tests]]
name = "no_inverted_dag"
type = "node_dependency"
from_name_pattern = "^stg_"
parent_name_pattern = "^(int_|fct_|dim_|obt_|rpt_)"
severity = "error"
[[tool.dbtective.manifest_tests]]
name = "no_stg_to_stg"
type = "node_dependency"
from_name_pattern = "^stg_"
parent_name_pattern = "^stg_"
severity = "error"

[[tool.dbtective.manifest_tests]]
name = "no_bypass_staging"
type = "node_dependency"
from_name_pattern = "^(int_|fct_|dim_|obt_|rpt_)"
parent_type = "source"
severity = "error"

[[tool.dbtective.manifest_tests]]
name = "no_inverted_dag"
type = "node_dependency"
from_name_pattern = "^stg_"
parent_name_pattern = "^(int_|fct_|dim_|obt_|rpt_)"
severity = "error"
Examples

Use case 1 — No staging → staging dependency

-- ❌ Non-compliant: stg_order_items selects from another staging model
-- models/staging/stg_order_items.sql
select order_id, product_id
from {{ ref('stg_orders') }}

-- ✅ Compliant
select order_id, product_id
from {{ source('shop', 'order_items') }}

Use case 2 — Intermediate/mart models must not bypass staging

-- ❌ Non-compliant: intermediate model reads source() directly
-- models/intermediate/int_order_summary.sql
select customer_id, count(*) as order_count
from {{ source('shop', 'orders') }}
group by 1

-- ✅ Compliant
select customer_id, count(*) as order_count
from {{ ref('stg_orders') }}
group by 1

Use case 3 — No inverted DAG

-- ❌ Non-compliant: staging model references a downstream layer
-- models/staging/stg_customers.sql
select * from {{ ref('int_customer_aggregates') }}

-- ✅ Compliant
select customer_id, first_name, last_name
from {{ source('crm', 'customers') }}

Directory-based conventions also work via includes/excludes for the from side and parent_path_pattern for the parent side:

manifest_tests:
  - name: "no_staging_to_staging_by_path"
    type: "node_dependency"
    includes: ["models/staging"]
    parent_path_pattern: "models/staging"
    severity: "error"