The only document in a data platform that everybody trusts and nobody tests. Everrest has one: 36 rows, a description on every single one, last touched eight months ago. This page audits it against the warehouse it claims to describe, then rebuilds it so it cannot rot. Seed 42, learn-python env, every number below measured against 131,832 real rows.
The star schema from the Infrastructure flagship is already live and already described twice: a data contract that the pipeline enforces, and a metric registry the agent is grounded on. The dictionary is the third document about the same 39 columns, which is exactly how a platform ends up with four versions of the truth. So the job is not only to fix the dictionary. It is to make it point at what the other two already own.
The contract owns guarantees. The registry owns metric formulas. The dictionary owns column meaning, and points at the other two instead of restating them. Every check on this page reads the contract and the registry as inputs, so a disagreement between documents is itself a finding.
There is no honest way to generate a human's prose from a seed, so the legacy dictionary is synthesized rather than sampled - and every single departure from the live warehouse is deliberate and listed in the generator docstring. That turns Step 5 into a recall test: which of the 19 does the audit find, and which does it miss? The answer at the bottom of Step 5 includes one it misses.
D8 wrong uniqueness order_id documented as the primary key; 2.16 rows each D9 stale value set status listed as Delivered|Shipped|Cancelled - wrong case, and 'paid' is missing entirely D15 wrong label net_amount described as revenue. The metric registry defines platform revenue as net_amount x take rate D17 wrong grain fact_orders documented as one row per order; it is one row per order LINE D18 wrong timezone order_ts documented as Asia/Singapore local; the contract says UTC. Prose against prose - EXPECTED MISS
Nobody opens a data dictionary to admire it. They open it at 4pm with one question, take the first answer they find, and put it in a deck.
Everything here exists to move facts out of a human's hands and into a query, and to make the result fail a build rather than sit in a wiki.
Profiles the whole warehouse in-process. Type, nullability, cardinality, uniqueness, the real value set - measured, never typed.
The introspection surface every SQL engine exposes, so the profiler is not tied to one warehouse.
Where the generated dictionary lands in a real company. This skill produces the payload; the catalog serves it.
Already enforcing the contract upstream. Read as an input here so the doc and the guarantee cannot drift apart.
The semantic layer is a reviewed YAML file in the repo, and the gate runs on every pull request. A wiki page has no diff.
Built the star schema and the contract this page reads. The dictionary is the layer directly on top of it.
One script runs the whole thing against the live warehouse: profile it, audit the legacy document, fail the gate, generate the replacement, pass the gate, then move the schema for real on a copy and watch the gate fail again. Every number on this page comes out of that run.
Coverage is the number every catalog reports and the only one a document can reach 100 percent on while being wrong about a third of its rows.

Set difference between the columns the document names and the columns that exist. Cheap, and it is the one nobody runs.
Type, nullability, uniqueness, value set, range, unit scale, grain and the foreign keys the contract declares. Each one is a query.
The doc against the contract and the metric registry. Reads prose, so it can miss and it can flag something a human dismisses.
Rows with a description and nothing else, and rows nobody has reviewed inside a year. Never blocking - an incomplete row states nothing false.

Every finding carries a proof field with three values. measured means a query settled it (27 of 67). screen means text or a distribution was read and a human must confirm (40 of 67). undecided means the check could not run at all - and that third value only exists because the review panel found that its absence was the whole design's blind spot. See Step 6.


Left: one column, two live definitions plus one wrong one. Delivered GMV and platform revenue are 9.1x apart - that is the take rate - and counting non-delivered lines adds a further 29%. The full traded window is shown, so these are not the registry's signed-off figures: it binds platform revenue to November 2025 only. The formula is the registry's, the window is this chart's. Right: the grain sentence at the top of the table, which multiplies every order count by 2.16 and leaves nothing in the data looking wrong.
[registry_label_conflict] fact_orders.net_amount
claimed: described as "Net revenue recognised on the order, in USD."
measured: metric registry defines platform_net_revenue as a SCALED
formula over net_amount, not the column itself
[grain_mismatch] fact_orders
claimed: one row per order
measured: 107,841 rows over 50,000 distinct order_id = 2.16 rows per key

status = 'Delivered' returns 0 rows and looks like a quiet month.




The replacement dictionary is two files. One is generated on every build from the live warehouse. The other is about 200 lines of judgement a person writes once.

semantics.yaml is 5 owners, 5 grain sentences, 5 classifications, 39 definitions and 10 gotchas. That is the whole hand-written cost, and it is an afternoon at this size - 39 columns across 5 tables. A 2,000-column estate is not an afternoon, and nothing here makes it one.
net_amount:
definition: >-
Merchandise value of the line after discount. Summed over delivered
lines this is GMV, what buyers paid merchants. It is NOT platform
revenue.
unit: USD
gotcha: >-
Platform revenue is net_amount times the take rate, per the metric
registry. Reporting net_amount as revenue overstates the P&L line by
about 9x.
Type, cardinality and the value set actually present are measured from the warehouse. Nullability and uniqueness are read from the data contract, because "will this column ever be null" is not a question today's rows can answer - a fully backfilled column has promised nothing, and publishing it as "never null" turns the first legitimate NULL next quarter into a blocking finding about a promise nobody made. Meaning, ownership and sensitivity are decided by a human. The first version of this page got the middle one wrong; a reviewer caught it.
The generated dictionary passes its own detectors because it is derived from the same profile they query. That is arithmetic, not evidence. So the document gets broken one field at a time, and each mutant has to produce its specific finding by name.

On its first run this suite scored 11 of 12. The miss was not in a detector - it was in the harness: the audit was reading the grain sentence from the semantic layer instead of from the claims being audited, so mutating a claim's grain changed nothing. Nothing else on this page would have found that. Fixing it also surfaced a second bug, a range parser reading 1-10 as (1, -10), which had been quietly firing a false out-of-range finding on qty.
A gate that only ever passes teaches nothing. So the build copies the warehouse, adds a column and retypes another for real, and runs again.
================================================================ DATA DICTIONARY GATE ================================================================ coverage 97.5% of live columns have a dictionary row claims tested 75 findings 2 major, 3 minor schema drift BREAKING BREAKING type changed dim_date.year WARN added column fact_orders.refund_amount RESULT: FAIL - a breaking schema change since the recorded baseline ================================================================
Drift is tiered, and the tiering is the difference between a gate and a log line. A retyped column falsifies a claim the document already makes, so it fails. An added column falsifies nothing - it only leaves the document incomplete - so it warns. A gate that reddens the main branch every time somebody adds a column is a gate that gets switched off in week three.
One self-contained HTML file. Searchable, filterable, no build step, no CDN, opens from a file path. Every structural value on it was measured minutes before it was written.
| Planted defect | Caught by | What the run measured | Status |
|---|---|---|---|
| Ghost columns (2) | existence · blocking | gross_amount, dim_merchant.status | ✓ caught |
| Undocumented columns (5) | existence | 5 of 39 live columns | ✓ caught |
| Wrong types (2) | structure | qty decimal→BIGINT, dow string→BIGINT | ✓ caught |
| Wrong nullability (2) | structure | 2,696 and 1,074 nulls | ✓ caught |
| Wrong uniqueness | structure | order_id: 2.16 rows per value | ✓ caught |
| Stale value sets (4) | structure | status, region, category, dow | ✓ caught |
| Wrong range | structure | unit_price max 5,379.20 vs 1,000 | ✓ caught |
| Wrong unit scale | structure · 2 screens | discount 0 to 0.2; span 500x too wide | ✓ caught |
| Wrong grain sentence | structure | 107,841 rows / 50,000 orders | ✓ caught |
| Wrong label (revenue) | agreement · screen | registry scales net_amount by 0.11 | ✓ caught |
| Missing status filter | agreement · screen | order_id described as countable | ✓ caught |
| Governance fields blank | policy · screen | 34 rows without owner, unit or grain | ✓ caught |
| Wrong timezone in prose | nothing | doc says Asia/Singapore, contract says UTC | ✗ missed |
The miss is structural, not a bug. No detector here compares one document's prose against another's, and building one would be a language model guessing rather than a query proving. It is planted, published and left unfixed so the boundary of the method is on the page rather than in a footnote. Six further findings appeared that were never planted: the same wrong claims re-detected as disagreements with the data contract, plus two owner conflicts nobody had noticed - the legacy doc assigns net_amount to finance and status to ops, where the contract assigns both to data-platform.
Five senior reviewer agents - 11 to 15 years each in warehouse modelling, governance, analytics engineering, data platform CI and commercial insight - were pointed at the code and the executed outputs. They did not describe defects; they ran them. Three of the five arrived independently at one sentence: a detector that cannot decide returns nothing, and nothing is indistinguishable from clean. This page had been written to argue that a document can look complete because nobody checked it. The audit had exactly that defect.
The generated dictionary reported 0 findings and 100 percent coverage. That number included an untested grain claim, two unaudited foreign keys on a star schema, a value-set check that switches itself off above 40 distinct values, and a type check that returns silently for every BigQuery and Snowflake type. proof is now three-valued - measured, screen, undecided - and the gate prints a NOT CHECKED block. The current run is honestly at 0 undecided, which is a result rather than an assumption.
"Your domain check disables itself exactly when the doc is most stale - past 40 distinct values it stops looking. And fk_resolution is defined and called from nowhere. On a star schema the join IS the claim, and it is the one thing you never test."
fk_unaudited."Zero hits for PII, classification or sensitivity anywhere in the plugin. You document a customer identity dimension and cannot say which columns are personal. Worse, the renderer prints the distinct values of any column under 40 - point that at a real warehouse and the dictionary becomes the disclosure."
classification is a required field, table-level with column overrides. The rendered page withholds values for anything not classified public or internal - withholding is now the default, not the opt-in."On a Snowflake catalog against a lower-case spreadsheet, nothing matches: 800 major findings, zero coverage, exit 0, and the report says every documented claim held. An empty CI clone blocks every documented primary key. BigQuery STRING and INT64 both resolve to nothing, so type checking is dead across the estate."
not_evaluated instead of a false blocker, the type vocabulary covers Snowflake, BigQuery, Postgres and Redshift with parameterisation stripped, and an unrecognised type is reported rather than skipped."Three of the four ways a run can be meaningless exit 0 with a green banner - no dictionary, no tables, no baseline. And the one thing that reliably goes red, any added column, is the change least worth blocking on. Ship it and the team's first fix is --no-fail-on-drift."
"Your chart says the registry signs off $577K. It does not - the registry binds that metric to November 2025, which is $82,195. And '$156,982 of GMV' is all-status net_amount, so it is not GMV, it is not a loss, and the two bars overlap so they do not add up."
The mutation suite built in response to the panel scored 11 of 12 on its first run. The miss was in the harness, not a detector: the audit read the grain sentence from the semantic layer rather than from the claims under test, so a mutated grain was audited against the unmutated original.
parse_range read 1-10 as (1, -10), which had been firing a false out-of-range finding on qty for the whole build.# v1: printed whenever the exit code was 0 - including with 3 major findings # and real schema drift present but not gated. lines.append("RESULT: PASS - every documented claim held, schema unchanged") # v2: assembled from what was actually established. held = ["0 blocking findings"] if non_block: held.append(f"{non_block} non-blocking") if undecided: held.append(f"{len(undecided)} NOT CHECKED") if drift is None: held.append("schema not compared") elif drift.breaking: held.append("breaking drift present but not gated") # -> "PASS - 0 blocking findings, 4 non-blocking, schema unchanged"
Every reproduction the panel executed is committed as test_panel_regressions.py and runs green at 39 of 39. A fix without a test is a fix until the next refactor.
Three of their findings are real and deferred rather than quietly dropped. Ownership is table-level, so a conformed column joined into two tables resolves to two owners - catalog_price is owned by data-platform on the fact and by commercial on the dimension. Concept-level ownership is the right shape and is not built. Profiling is up to three full scans per column, one query at a time, with no sampling or cost guard; fine on DuckDB, not safe to point at a metered warehouse. The fingerprint cannot see constraints, defaults, views, or any semantic change at constant structure - a currency switched from USD to SGD moves it by nothing. Each is named in the skill's own "what this does not do" section rather than left for a user to discover.
Point it at the document you already have and the warehouse it claims to describe. The audit takes about ten minutes and is the highest-value thing on this page.
/plugin marketplace add phoebefu6/phoebe-data-skills /plugin install how-to-data-dictionary@phoebe-data-skills