Sources declare the raw tables dbt 37,942 reads but does not build. Models use {{ source('shop', 'orders') }} instead of a hard-coded name, so lineage starts at the real inputs and one YAML entry can move them. The Jinja 15,439 expressions pick DuckDB 61,228 's attached database or PostgreSQL 1,289 's public schema from the active target:
version: 2
sources:
- name: shop
description: BookNest's order database (generated sample data, Section 4.1).
database: "{{ 'src' if target.type == 'duckdb' else target.database }}"
schema: "{{ 'main' if target.type == 'duckdb' else 'public' }}"
tables:
- name: books
- name: customers
- name: order_items
- name: orders
config:
loaded_at_field: order_ts
freshness:
warn_after: {count: 1, period: day}
error_after: {count: 7, period: day}A freshness rule turns "is the loader still running?" into a check that compares max(order_ts) with now:
dbt source freshness17:22:00 1 of 1 ERROR STALE freshness of shop.orders ............ [ERROR STALE in 0.12s] 17:22:01 [ERROR]: in source orders (models/staging/_sources.yml)
The check is right: the sample orders end on 30 June 2026, far past the seven-day limit. In a pipeline this stops the run before stale data is reported as current. A business timestamp such as order_ts also goes stale when the shop has no orders; an ingestion timestamp measures only the loader.