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 region

And 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.yml

Checkup 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.

KEYTYPEDEFAULTWHAT IT DOES
federation_directbooleantrueWhere 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_portinteger46100The 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_enabledbooleantrueWhether 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_enabledbooleantrueDVT'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.