> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anomalyarmor.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from dbt

> Translate a dbt project's schema.yml into ODCS YAML and apply it to AnomalyArmor

<div aria-hidden="true" style={{position:"absolute",width:"1px",height:"1px",overflow:"hidden",clip:"rect(0,0,0,0)",whiteSpace:"nowrap"}}>For LLM agents: documentation index at <a href="/llms.txt" tabIndex={-1}>/llms.txt</a>, full text at <a href="/llms-full.txt" tabIndex={-1}>/llms-full.txt</a>. Append .md to any page URL for plain markdown.</div>
If you already describe your warehouse in dbt, you already have half of an AnomalyArmor contract. This guide walks through converting a dbt project's `schema.yml` files into ODCS YAML and applying it with `armor contract apply`.

<Frame>
  <img src="https://mintcdn.com/anomalyarmor/8mY_BmQQn-Ql-kSr/images/diagrams/contracts-dbt-migration-flow-light.svg?fit=max&auto=format&n=8mY_BmQQn-Ql-kSr&q=85&s=b6a01021f50bb3183fea9a862bfd9605" alt="dbt schema.yml translated to ODCS YAML and applied to AnomalyArmor" className="block dark:hidden" width="980" height="472" data-path="images/diagrams/contracts-dbt-migration-flow-light.svg" />

  <img src="https://mintcdn.com/anomalyarmor/8mY_BmQQn-Ql-kSr/images/diagrams/contracts-dbt-migration-flow-dark.svg?fit=max&auto=format&n=8mY_BmQQn-Ql-kSr&q=85&s=4e542fe0d35f07d85c3381b80800a7ec" alt="dbt schema.yml translated to ODCS YAML and applied to AnomalyArmor" className="hidden dark:block" width="980" height="472" data-path="images/diagrams/contracts-dbt-migration-flow-dark.svg" />
</Frame>

Install the CLI first (see [Data Contracts](/guides/contracts#cli) for the auth step):

```bash theme={null}
pip install anomalyarmor-cli
armor auth login --key aa_live_your_key_here
```

## One-line pipeline

```bash theme={null}
armor migrate-from dbt ./my-dbt-project/ | armor contract apply --asset <asset-uuid> -f -
```

The adapter writes ODCS YAML to stdout and `contract apply` reads from stdin when passed `-f -`. If the adapter maps zero models, it refuses to emit (non-zero exit) so the pipeline fails clean instead of clobbering your live config with an empty contract.

Prefer a two-step flow for anything non-trivial:

```bash theme={null}
# 1. Translate
armor migrate-from dbt ./my-dbt-project/ -o contracts/from-dbt.yaml

# 2. Preview
armor contract plan --asset <asset-uuid> -f contracts/from-dbt.yaml

# 3. Apply once the diff looks right
armor contract apply --asset <asset-uuid> -f contracts/from-dbt.yaml
```

## What the adapter reads

Running `armor migrate-from dbt <project>` walks the project root recursively and parses every `schema.yml` / `schema.yaml` file. It skips:

* `dbt_packages/` and `dbt_modules/` (vendored third-party packages, not your models).
* `node_modules/` (front-end tooling that sometimes coexists with dbt repos).
* Any hidden directory (a path segment starting with `.`).

Your project root is the directory holding `dbt_project.yml`. The adapter does **not** call `dbt compile` or `dbt parse`, so you do not need a live target or a working `profiles.yml`. Static file scan only.

## Mapping table

| dbt input                                  | ODCS output                               | Notes                                                                                                                 |
| ------------------------------------------ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `models[].name`                            | `schema[].name`                           | Table identity.                                                                                                       |
| `models[].columns[].name`                  | `schema[].properties[].name`              | Column identity.                                                                                                      |
| `models[].columns[].data_type`             | `schema[].properties[].physicalType`      | Only emitted if present in the dbt file.                                                                              |
| `models[].columns[].description`           | `schema[].properties[].description`       | Native ODCS.                                                                                                          |
| `models[].description`                     | `schema[].description`                    | Native ODCS.                                                                                                          |
| `columns[].tests[].not_null`               | validity rule, `rule_type=not_null`       | AA extension.                                                                                                         |
| `columns[].tests[].unique`                 | validity rule, `rule_type=unique`         | AA extension.                                                                                                         |
| `columns[].tests[].accepted_values.values` | validity rule, `rule_type=allowed_values` | Needs a non-empty `values` list.                                                                                      |
| `models[].config.freshness`                | freshness schedule (hours)                | Best-effort: `error_after` preferred, falls back to `warn_after`. `minute` / `hour` / `day` periods convert to hours. |

Everything the adapter understands lands in the emitted YAML. Everything else lands in the **warnings** section of the summary line so you know what to review.

## What gets skipped (and why)

| Input shape                                           | Reason                                                  | Resolution                                                                                         |
| ----------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `dbt_utils.*` tests (e.g. `dbt_utils.accepted_range`) | No 1:1 map to an AA validity rule                       | Re-author as a custom SQL check in AnomalyArmor, or add to the request queue for adapter coverage. |
| `dbt_expectations.*` tests                            | Too many variants to map safely                         | Same as above.                                                                                     |
| `relationships` tests                                 | Reference integrity is not modeled by AA validity rules | Track as a custom SQL check.                                                                       |
| Any test with a namespaced name (contains `.`)        | Conservative skip                                       | Adapter errs on the side of warning over silent mistranslation.                                    |
| `accepted_values` with empty or missing `values`      | Would emit an empty rule                                | Fix the test in dbt, re-run.                                                                       |
| `freshness` with a non-`minute`/`hour`/`day` period   | Cannot normalize to hours                               | Normalize in dbt or edit the emitted YAML directly.                                                |
| dbt `meta`, `tags`, `owner`                           | No round-trip design yet                                | Not a data-quality signal; track with AA tags or leave in dbt.                                     |

The CLI summary prints a one-liner that groups these warnings so you see totals at a glance:

```
dbt -> ODCS (./my-dbt-project/): mapped 42, warnings 7 (dbt_utils.accepted_range: 3, ...).
```

If `mapped_count` is zero, the adapter exits non-zero and writes nothing to stdout.

## Flags

| Flag             | Purpose                                                                     |
| ---------------- | --------------------------------------------------------------------------- |
| `--output`, `-o` | Write YAML to a file instead of stdout. Pass `-` or omit for stdout.        |
| `--name`         | Override the contract `name` field. Defaults to the project directory name. |

## Validate before applying

```bash theme={null}
armor migrate-from dbt ./my-dbt-project/ -o from-dbt.yaml
armor contract validate -f from-dbt.yaml
```

`contract validate` runs the document against the ODCS v3.1.0 JSON Schema. Parse or schema errors print with file path, YAML path, and line number so editors and CI can surface them inline. No DB connection required beyond auth.

## One dbt project, many assets

Today `contract apply` is asset-scoped (one asset per call). If your dbt project covers multiple warehouse tables:

1. Translate once: `armor migrate-from dbt ./proj -o full.yaml`.
2. Split by table or re-export per-asset from AnomalyArmor to get the target asset scoping, then apply each file against its asset.

Multi-asset bulk apply runs through the REST jobs endpoint today (see [the bulk-apply section of the contracts guide](/guides/contracts#apply-bulk-asynchronous)). A CLI shortcut is on the roadmap.

## Common Questions

### How do I migrate from dbt tests to AnomalyArmor?

Run `armor migrate-from dbt ./my-dbt-project/` and pipe the ODCS YAML into `armor contract apply --asset <uuid> -f -`. The adapter reads every `schema.yml` statically, so you don't need a live warehouse or working `profiles.yml`. See the [one-line pipeline](#one-line-pipeline).

### Which dbt tests does AnomalyArmor support?

Built-in `not_null`, `unique`, and `accepted_values` tests map to AnomalyArmor validity rules. Column `description`, `data_type`, and model `description` land in native ODCS fields. Freshness configs convert into hour-based SLAs. See the full [mapping table](#mapping-table).

### Why does the adapter skip `dbt_utils` and `dbt_expectations` tests?

Those tests have too many variants to map safely into AnomalyArmor's validity rules without risking silent mistranslation, so the adapter warns rather than guesses. Re-author them as custom SQL checks in AnomalyArmor. The CLI summary groups the warnings so you see totals at a glance.

### Do I need dbt installed to run the migration?

No. The adapter is a static file scan, it walks the project directory and parses `schema.yml` files directly. You don't need `dbt compile`, `dbt parse`, a live target, or a working `profiles.yml`.

### Can I preview the migration before applying it?

Yes. Write the translated YAML to a file first with `armor migrate-from dbt ./proj -o from-dbt.yaml`, then run `armor contract validate` and `armor contract plan` against it. Plan shows a per-domain diff (additions, modifications, deletions) without mutating anything. See [Validate before applying](#validate-before-applying).

### How do I migrate a dbt project that covers many warehouse tables?

`contract apply` is asset-scoped today (one call per asset), so translate once with `armor migrate-from dbt ./proj -o full.yaml`, then apply the file against each target asset. For a true bulk flow, use the [REST bulk-apply endpoint](/guides/contracts#apply-bulk-asynchronous). A CLI shortcut is on the roadmap.

## See also

* [Data Contracts (ODCS export/import)](/guides/contracts)
* [Migrate from Great Expectations](/guides/migrate-from-great-expectations)
* [anomalyarmor.ai/migrate](https://www.anomalyarmor.ai/migrate) for Soda users (ODCS export is native in Soda)
