DVT BUILD

dvt buildis the build you know: seeds, then models, snapshots and tests, one command, DAG order — same verb, same flags, same selection syntax as dbt. The difference is reach: on DVT the build doesn't stop at one warehouse's edge, so federated models land on every engine and bucket your project touches, in the same pass, in the same dbt-style output.

THE MINIMAL INVOCATION

Run it bare and everything builds. Here's a project with two seeds, two federated models (one landing on a MySQL database, one landing as a Parquet file on S3), a table model, its tests, and a snapshot — watch the phases go by:

$ dvt build
09:14:03  Concurrency: 4 threads (target='pg_warehouse')

09:14:03  1 of 2 START seed file analytics.raw_payments .................................................. [RUN]
09:14:03  2 of 2 START seed file analytics.country_codes ................................................. [RUN]
09:14:04  2 of 2 OK loaded seed file analytics.country_codes ............................. [INSERT 249 in 0.72s]
09:14:04  1 of 2 OK loaded seed file analytics.raw_payments ............................. [INSERT 1200 in 0.91s]

09:14:04  Finished running 2 seeds in 0 hours 0 minutes and 0.94 seconds (0.94s).
09:14:04  Completed successfully

09:14:04  Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2
09:14:11  Running 2 federation models (threads=4)

09:14:11  1 of 2 START federation table model mysql_crm.customer_360 ..................................... [RUN]
09:14:14  1 of 2 OK created federation table model mysql_crm.customer_360 ................ [SLING 8412 in 3.10s] [8412 rows inserted]
09:14:14  2 of 2 START federation table model s3_lake.daily_revenue ...................................... [RUN]
09:14:16  2 of 2 OK created federation table model s3_lake.daily_revenue .................. [SLING 365 in 2.02s] [365 rows inserted]

09:14:16  Finished running 2 federation models in 0 hours 0 minutes and 5.31 seconds (5.31s).
09:14:16  Completed successfully

09:14:16  Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2
09:14:18  Concurrency: 4 threads (target='pg_warehouse')

09:14:18  1 of 4 START sql table model analytics.orders_enriched ......................................... [RUN]
09:14:19  1 of 4 OK created sql table model analytics.orders_enriched ................... [SELECT 5417 in 0.87s]
09:14:19  2 of 4 START test not_null_orders_enriched_order_id ............................................ [RUN]
09:14:19  2 of 4 PASS not_null_orders_enriched_order_id ........................................ [PASS in 0.11s]
09:14:19  3 of 4 START test not_null_raw_payments_payment_id ............................................. [RUN]
09:14:20  3 of 4 PASS not_null_raw_payments_payment_id ......................................... [PASS in 0.09s]
09:14:20  4 of 4 START snapshot snapshots.orders_snapshot ................................................ [RUN]
09:14:20  4 of 4 OK snapshotted snapshots.orders_snapshot ............................... [INSERT 0 14 in 0.64s]

09:14:20  Finished running 1 table model, 2 data tests, 1 snapshot in 0 hours 0 minutes and 2.79 seconds (2.79s).
09:14:20  Completed successfully

09:14:20  Done. PASS=4 WARN=0 ERROR=0 SKIP=0 TOTAL=4

Three phases, three tallies, one build. The seeds load first through DVT's own seed engine. Then the federated models run in dependency waves — customer_360 lands on the MySQL connection, and daily_revenue(which reads it) lands on the S3 bucket as a Parquet file, because buckets hold files, not tables. Then the final phase builds everything that's left on the default target — models, tests, snapshots — in the ordinary DAG order. The quiet gap between the first two phases is the project compiling; nothing prints unless it fails.

IN PLAIN DBT

buildruns everything inside one connection — whatever your target can't reach doesn't exist. Here the same one command also landed a table on a MySQL database and a Parquet file on S3, seeds included, tests included, and every phase reported in the same status-line dialect — so the whole cross-engine build reads like the single run it is.

THE BUILD ORDER — WHAT RUNS WHEN

dvt build --help describes itself in one line: Seeds + models + snapshots + tests, DAG order — federation included. Under that line, a fixed order:

Seeds first.The seed phase runs before anything else, through DVT's own load engine — which is why your seeds can be CSV, Parquet, JSON or JSONL, not CSV alone. Everything downstream can rely on fresh seed tables.

Then the graph, federation included. Models a federated model reads from are refreshed before extraction touches them, the federated models themselves run in dependency waves (parallel within each wave, honoring your threads), and the final phase builds the rest — models, snapshots, tests — on the default target.

Nothing runs twice.The final phase is told exactly what DVT already did: seeds are excluded (they're loaded), federated models are excluded (they've landed), already-refreshed upstream models are excluded. Tests still run where they belong — a test on a seed runs in the final phase against the table the seed phase loaded, as you saw with not_null_raw_payments_payment_id above. Tests on sources that live on otherconnections are the one honest exception: the default target can't reach those relations, so DVT skips them loudly, by name, instead of failing your build on a connectivity fact.

Two more guarantees frame every build. It begins from a passing dvt parse — a broken project refuses with exit 2before any engine is touched. And every relation the build lands — models on every connection, seeds, snapshots, stored test failures — is recorded in DVT's materialization ledger, with the federation results merged into run_results.json beside everything else. That record is what makes dvt retract — the exact inverse of this command — possible.

WHEN A SEED FAILS — DEPENDENTS SKIP BY NAME

A failed seed doesn't sink the build, and it doesn't let the build lie either. The phases continue for everything untouched, and every node downstream of the failed seed — transitively, exactly dbt's own skip semantics — is marked SKIP naming the seed that caused it:

$ dvt build
09:20:04  Concurrency: 4 threads (target='pg_warehouse')

09:20:04  1 of 2 START seed file analytics.raw_payments .................................................. [RUN]
09:20:04  2 of 2 START seed file analytics.country_codes ................................................. [RUN]
09:20:05  2 of 2 OK loaded seed file analytics.country_codes ............................. [INSERT 249 in 0.70s]
09:20:05  1 of 2 ERROR loading seed file analytics.raw_payments ............................... [ERROR in 0.88s]

09:20:05  Finished running 2 seeds in 0 hours 0 minutes and 0.92 seconds (0.92s).
09:20:05  Completed with 1 error(s):
09:20:05    Failure in seed raw_payments
09:20:05      invalid input syntax for type numeric: "n/a" (row 214, column amount)

09:20:05  Done. PASS=1 WARN=0 ERROR=1 SKIP=0 TOTAL=2
seed phase reported failures — continuing; dependents of the failed seed(s) will be SKIPPED, and the build exits nonzero
09:20:08  SKIP orders_enriched (upstream seed 'raw_payments' failed)
09:20:08  SKIP orders_snapshot (upstream seed 'raw_payments' failed)
09:20:08  SKIP not_null_orders_enriched_order_id (upstream seed 'raw_payments' failed)

Note what skipped: the model that reads the seed, the snapshot that reads the model, and the test on the model — the whole downstream chain, each line naming raw_payments as the reason. The rest of the build — the other seed, the federated models, everything not downstream of the failure — carries on and completes. The skips are written into run_results.jsonexactly as skips, so CI and observability tools see them the way they'd see any skip. And the exit code is 1 no matter how well the rest went: a build with a failed seed is a failed build, reported honestly.

Why this matters: the seed phase runs in DVT's own lane, outside the engine's DAG — so without this, the engine would never learn a seed failed, and dependents would run anyway, dying on raw errors that name the wrong node. DVT closes that gap: the skip reaches every phase, and it names the actual culprit.

--FAIL-FAST — STOP AT THE FIRST FAILURE

Pass --fail-fast (or -x) and a seed failure aborts the build on the spot instead of skipping forward — nothing downstream is attempted:

$ dvt build --fail-fast
...
09:20:05  Done. PASS=1 WARN=0 ERROR=1 SKIP=0 TOTAL=2
Aborting the build: --fail-fast and the seed phase reported a failure.

The same flag threads through every later phase too: in the federation waves and the final phase, models already executing run to completion, nothing new starts, and whatever never ran is reported as SKIP in the tally. Use it in CI where a fast red matters more than a complete picture.

SELECTION — SEEDS ARE SELECTION-AWARE, GRAPH OPERATORS INCLUDED

--select and --exclude work across the whole build, seed phase included, with the selection syntax you already know — and graph operators resolve through the real DAG, so +modelpulls in the model's seed ancestors:

$ dvt build --select +orders_enriched     # the model, its upstream models,
                                          # AND the seeds it depends on
$ dvt build --select raw_payments         # just one seed, by bare name
$ dvt build --exclude country_codes       # everything but that seed

One selection, resolved once, honored by every phase. And when your selection simply includes no seeds, the seed phase says so and steps aside rather than loading everything:

$ dvt build --select customer_360
No selected seeds — skipping the seed phase (the selection includes none).

A selection that matches nothing at all warns and exits clean — check the name against dvt ls.

REFERENCE — EVERY FLAG

FLAGDEFAULTWHAT IT DOES
--select, -severythingSelection syntax — which nodes to build (repeatable). Resolves once against the real DAG and is honored by every phase, seeds included: graph operators like +model pull in seed ancestors.
--models, -mnoneSpecific model names — a plain-name alternative to --select.
--excludenoneSelection syntax — which nodes to leave out (repeatable). Honored by every phase, seed phase included.
--threadsprofile threadsThreads for execution — governs the seed loaders, the parallelism inside each federation wave, and the final phase alike.
--varsnoneProject variables as a YAML/JSON dict string, e.g. --vars '{key: value}'.
--target, -tprofile defaultTarget to execute against — the output seeds load into and default-target models build on. Federated models pinned to their own target are unaffected.
--full-refreshoffRebuild from scratch: seed tables are dropped and recreated instead of truncated and reloaded, and incremental replicas rebuild in full.
--fail-fast, -xoffStop at the first failure instead of finishing the rest. A seed failure aborts the build outright; in later phases, models already executing run to completion and nothing new starts.
--project-dir.Project directory (default: the current directory).
--profiles-dirstandard resolutionDirectory holding profiles.yml (default: the project directory, then your home profiles directory).
--debugoffDebug logging — the engine's internal narration, normally kept out of the status lines.

Exit codes: 0 on a clean build, 1 when anything failed — including a failed seed whose dependents were skipped while everything else passed — 2 when the project failed to parse (nothing ran, no engine touched).