---
title: "Finding the accepted_values List That Drifted"
slug: "t7-vocabularies"
type: article
locale: en-US
canonical: "https://www.coremodels.io/connector/dbt/t7-vocabularies"
updatedAt: "2026-09-04T05:04:27+03:00"
---

# Finding the `accepted_values` List That Drifted

Two files in your project test the same column against different value lists. Both tests pass, so
nothing has ever told you. This guide finds them, and turns the list into something that can only be
defined once.

Prerequisite: a dbt project imported from `manifest.json`. Nothing else.

## What the import already did

Every `accepted_values` test in the manifest became a **governed vocabulary** — a first-class object
with a name, terms, and somewhere to put a definition — rather than a string array sitting inside a
test.

That conversion is what makes the comparison possible. Eleven copies of a list are eleven unrelated
arrays as far as dbt is concerned. Once each is an object referenced by the columns that use it, the
question "do these agree?" finally has something to compare.

The recipe opens on the **Vocabularies** grid, which is that view.

## Step 1 — Find the ones that matter

Sort the grid by how many columns reference each vocabulary. The single-reference ones are usually
fine and always low-stakes; a list used by one model can only disagree with itself.

**The multi-reference rows are the whole exercise.** A vocabulary referenced by four columns is four
chances for someone to have updated three of them.

For each one, open it and compare the terms against what the column *should* permit. Two failure
shapes turn up almost every time:

- **A value that exists in the warehouse and in no list.** Somebody added a status and updated the
  model they were working in. This is the common one.
- **A value in the list that the warehouse has not produced in two years.** Harmless, and worth
  removing, because a permitted value nobody has seen is a value nobody has thought about.

If you have `catalog.json` imported as well, the column types are warehouse-real, which makes the
second check considerably less speculative.

## Step 2 — Make it a definition, not a list

The value of a vocabulary over an array is that it can hold things an array cannot. Three worth
filling in:

**A name that is the concept, not the column.** `order status values` is better than
`fct_orders_status_accepted_values`, because the same vocabulary is about to govern `stg_orders`
too, and a name derived from one model reads as wrong everywhere else.

**A description per term.** What does `return_pending` actually mean — the customer has requested a
return, or the warehouse has received it? Teams argue about this and then encode one interpretation
into a dashboard. Writing it down is often the first time the argument gets resolved rather than
relitigated.

**Structure, where the concept has it.** `return_pending` and `returned` are not peers; one is a
stage of the other. A vocabulary can hold that nesting and the grid renders it as a tree.

That last point comes with an honest caveat: **dbt's `accepted_values` is a flat list**, so when the
list is published back the hierarchy cannot cross the boundary. The values go; the structure stays
here. The loss report says so explicitly rather than dropping it quietly — and the structure still
earns its place, because it is what an agent reads and what a new analyst learns from.

## Step 3 — Give it an owner

The recipe provisions ownership columns on the vocabulary grid for a reason: a definition without an
owner is a definition that drifts again in six months, and you will be doing this exercise a second
time.

Vocabularies are the right place to start assigning ownership across the whole estate, because they
are what people actually argue about. Columns rarely cause disputes; the permitted values of a
status column cause them constantly.

## Step 4 — Publish it back

Once the corrected list is the governed one, it can be generated into every model that uses the
field — so the eleven copies become eleven outputs of one definition and can no longer disagree.

**This step is Early access today**: the generation is built and tested, and we run it with you
while the self-serve path is finished. The reason to say that plainly here rather than in a footnote
is that steps 1–3 are worth doing on their own. Finding the drifted list and writing down what the
values mean is most of the value, and it is available now.

## What you will notice afterwards

The specific relief is small and repeats: nobody types an enum into YAML again, and the question
"which list is right?" stops being asked, because there is one.

The larger one takes a month to show up. Once the vocabulary has descriptions, the arguments about
what `return_pending` means stop recurring — they were never really about the column, they were
about the fact that nobody had written the answer down anywhere durable.

## What this does not do

- It does not detect values in your **data** that are missing from the list. This reads your project
  metadata, not your warehouse rows. A `dbt test` run is still what catches an unexpected value in
  production, and that job is dbt's.
- It does not merge two vocabularies for you. If `order status` and `order_state` turn out to be the
  same concept modelled twice, deciding that is a human call — the crosswalk workspace is where that
  adjudication happens.

The dbt quickstart that ships with the CoreModels integration docs covers the import this guide
assumed.

## Recipes

- [One enum, every model](https://www.coremodels.io/recipe/dbt/governed-vocabulary): Two files test the same column with different accepted_values. Both pass. Find the one that drifted.
