DVT_PROJECT.YML
dvt_project.ymlis DVT's own settings file — one flat key per setting, sitting at your project root right beside dbt_project.yml (same underscore spelling, deliberately). If a setting is about DVT rather than dbt, this is where it lives, and it is the only place DVT reads its own settings from.
THE WHOLE FILE
Here it is, in full — four keys, all optional, all at their defaults. This is a complete, working dvt_project.yml:
# dvt_project.yml — one flat key per setting, at the top level federation_direct: true # single-connection models push down to the source engine suite_port: 46100 # the port `dvt serve` opens on — one per project ai_enabled: true # whether this project may reach an AI model at all telemetry_enabled: true # DVT's usage telemetry (metadata only, never your data)
You don't write this file yourself — dvt init scaffolds it, with a longer comment above every key explaining what its default means. The file is meant to teach, not merely configure. Keep it in version control: these are decisions about the project, not about the machine it runs on (machine-local state lives in .dvt/ and is gitignored).
IN PLAIN DBT
There is no second file — tool settings get wedged into dbt_project.yml as vars, flags, or environment variables, where dbt's config inheritance quietly merges them into every model. DVT gives its settings their own flat file, and your dbt_project.ymlstays byte-pure dbt — any dbt tooling can read it and never meets a key it doesn't recognize.
TWO FILES, ONE BOUNDARY
The split is enforced, not just recommended. DVT never reads its own keys from dbt_project.yml — and a DVT key written there is a configuration error, not a shrug. dvt run refuses before doing anything, naming the file, the key, where it sits, and the right home:
Configuration Error in dbt_project.yml (./dbt_project.yml)
'suite_port' is a DVT setting, found at: suite_port
Move it to dvt_project.yml beside it — one flat key at the
top level of DVT's own project file:
# dvt_project.yml
suite_port: <value>Why refuse instead of ignore? Because left in dbt_project.yml the key is notignored — dbt's own config inheritance merges unknown keys into every model, silently. The refusal removes exactly that silent behavior. dvt config-checkup reports the same finding earlier, before a run is even attempted.
FEDERATION_DIRECT — WHERE SINGLE-CONNECTION COMPUTE HAPPENS
When every source a federated model reads lives on oneconnection, DVT has a choice: push the whole query down to that engine, or extract and compute in DVT's own federation engine. federation_direct is the project-wide answer:
# dvt_project.yml
federation_direct: true # (default) push the whole query down to the
# source engine — no extraction, no staging,
# no local compute. The fastest path when one
# connection can answer the model by itself.Set it to falseand single-connection models extract, compute and load through DVT's federation engine instead. That's a choice, not a workaround — a busy production source shouldn't be made to do your transformation work, and owning a federation engine is what lets you take that work off it:
# dvt_project.yml federation_direct: false # spare the sources: compute in DVT's engine
The resolution order is simple: a model's own config() always outranks the project file, and the project file outranks the built-in default (true). So one model can opt back in while the project stays conservative:
-- models/marts/small_rollup.sql
-- the project says false; this model pushes down anyway
{{ config(materialized='f_table', federation_direct=true) }}
select region, sum(amount) as revenue
from {{ source('pg_prod', 'orders') }}
group by regionAnd when a model does take the compute path, the run says which of the two decided — so you always know which file to edit.
SUITE_PORT — THE PORT DVT SERVE OPENS ON
suite_port is the only source of the app-suite port. Not a default, not a hint — the one place the port is defined. dvt serveopens exactly this port, and if something already holds it, that's a hard error naming what's there. DVT never quietly moves to a free port, because your URLs would move with it.
# dvt_project.yml suite_port: 46103 # this project's pin — 46100-46149 is DVT's range
Running two DVT projects on one machine? Give each its own pin — the scaffold's own comment says it in capitals: change this for every other project on this machine. The pin lives in version control, so everyone who clones the project gets the same URLs. Bookmarks, proxy rules and API consumers keep working — which is the point.
AI_ENABLED — THE PROJECT'S AI SWITCH
true (the default) means AI features work wherever they're offered. false means nothing leaves this project for AI — disabled, not muted: credentials are never even resolved, so there is no request left to make. Every AI surface says plainly that it's off, rather than failing in some other shape.
# dvt_project.yml ai_enabled: false # nothing leaves this project for AI
It's a local decision and only a local decision: no server turns it back on, and no license overrides it.
TELEMETRY_ENABLED — USAGE TELEMETRY, STATED PLAINLY
With true (the default), DVT sends metadata only: which commands ran, versions, outcomes. Never your data, never your SQL, never your schema or table names. A signed-out or air-gapped machine sends nothing at all.
# dvt_project.yml telemetry_enabled: false # honored with an enterprise license
Setting falseis honored with an enterprise license. Without one it is not an error and never fails a run — DVT tells you the opt-out is an enterprise capability, and telemetry stays on until it is one. Stated rather than hidden, because it's the bargain: the product is free for individuals, and usage data comes back.
THE HEALTH DOCTRINE — A DELETED KEY IS DRIFT
Every key the scaffold writes must exist in the file. DVT still runs with one missing — every reader supplies its own default — but the project is now unhealthy: the engine's silent runtime default must never be a project's only record of a decision. dvt config-checkup flags it:
$ dvt config-checkup
🔧 dvt_project.yml
suite_port is missing — deleted or never scaffolded; the suite
would fall back to its silent default (46100) — the port is
defined in dvt_project.yml and NOWHERE else (no sticky record,
no dynamic allocation), so a missing key leaves no record of
where this project serves
`dvt config-fix` restores the scaffolded key (suite_port: 46100),
keeping a .bak of dvt_project.ymlCheckup only reports — it writes nothing, ever. dvt config-fix is the one that repairs: it appends the missing key at its scaffolded default, with a comment saying it was restored and why, and keeps the previous file beside it as a timestamped .bak. Your ordering, your notes and your commented-out experiments are never rewritten — which is also why dvt init leaves an existing dvt_project.yml exactly as you have it and says so.
IN PLAIN DBT
There is no health guard on project state. Delete a line from a dbt project file and nothing notices until behavior changes — the silent default just takes over. DVT treats a missing scaffolded setting as drift, names it in config-checkup, and puts it back (with a .bak) in config-fix.
REFERENCE — EVERY KEY
All keys are flat, top-level, and optional; unknown keys are tolerated, so settings from a newer DVT land in this file without breaking an older one.
| KEY | TYPE | DEFAULT | WHAT IT DOES |
|---|---|---|---|
| federation_direct | boolean | true | Where a single-connection model's compute happens: true pushes the whole query down to the source engine; false extracts, computes and loads through DVT's federation engine. A model's own config() always outranks this project-wide default. |
| suite_port | integer | 46100 | The port dvt serve opens on — the ONLY place the port is defined. Pinned, never dynamic: an occupied port is a hard error naming what holds it. Give every project on a machine its own pin (46100-46149 is DVT's range). |
| ai_enabled | boolean | true | Whether this project may reach an AI model at all. false disables rather than mutes: credentials are never resolved, and every AI surface says plainly that it's off. No server or license overrides it. |
| telemetry_enabled | boolean | true | DVT's usage telemetry — metadata only (commands, versions, outcomes), never your data, SQL or schema. false is honored with an enterprise license; without one it's stated, never an error, and telemetry stays on. |