Layout
The whole ontology lives as a small file tree in one directory of your repository: each domain is a Markdown README, and tables, joins, and metrics are YAML.
Two complete example trees are yours to copy: a minimal one as a skeleton, and a fully authored one for a fictional marketplace.
This is Cassis’s analytics profile of the Open Knowledge Format (OKF). It keeps OKF’s open, human- and agent-readable file model, then adds canonical YAML structures Cassis can validate and use to generate SQL.
The tree
Everything lives directly under one directory: the project’s Path setting, cassis by default, configurable per project and nestable (dbt/cassis works). These docs write cassis/ throughout.
cassis/
project.yml # project identity: id + format version
domains/README.md # root domain: project-wide context
domains/<path>/README.md # one directory per domain, nested by path segments
tables/<schema>/<table>.yml # one file per table (columns inline)
metrics/<name>.yml # one file per metric
joins.yml # ALL joins, one YAML list
.schema.json # local source-schema snapshot, gitignored
Domains are Markdown; every other file is YAML with the .yml extension, not .yaml.
- Cassis owns the directory
- Cassis only ever writes inside the Path directory, so your dbt project, README, and CI config are never touched. Within it, though, Cassis is the owner of record: every export replaces the ontology files wholesale.
- project.yml
- The
project_idthe ontology belongs to and thecassis_format_version. Cassis writes it on export, publish, andcassis ontology pull; you never edit it. It carries no ontology content. - Legacy layout
- Repositories connected before the Path setting existed have their tree one level deeper, at
cassis/ontology/. Either set Path tocassis/ontology, or re-publish from Cassis and delete the orphaned directory.
Required fields
An import fails, and the pull-request check fails, when any of these is missing. Everything else is optional.
| File type | Required fields |
|---|---|
| domain | type (frontmatter, always Domain) |
| table | schema_name, table_name |
| column | name |
| join | from_schema, from_table, to_schema, to_table |
| metric | name, display_name, expression |
Four semantic rules are enforced everywhere the same validation runs: the import, the pull-request check, and cassis ontology check.
- Metrics must carry a non-empty
display_nameandexpression. - Every
domain_path(on tables and metrics) must name a domain that exists in the tree. - Domain paths are lowercase slug segments (
a-z,0-9,_,-) separated by/. - The tree must contain at least one domain, table, metric, or join. An empty tree is rejected, because importing it would erase the ontology.
A tree that passes the check cannot fail these rules when its merge is synced. The exact error for each is in Troubleshoot git and publishing.
Canonical form
Canonical form is the one way Cassis writes any given ontology, which is what keeps git diffs meaningful. Two different gates care about it, and they are not the same gate:
- The validation check (
cassis ontology check, and the pull-request check) fails only when a file would lose or change data on import: an unknown or typo’d field the canonical form drops, a file whose name does not match its content, a file the canonical serialization expects and cannot find. Formatting differences never fail it. cassis ontology fmt --checkis the formatting gate. It fails when any file is not canonical, which is what keeps the next diff clean.
You do not have to write canonical YAML by hand. cassis ontology fmt rewrites the tree using the exact serializer the round-trip compares against, so a formatted tree cannot fail that stage. Run it before committing, and read the diff.
What canonical form means, for when you read a fmt diff: keys sorted alphabetically at every level, two-space indentation with list dashes flush against their parent key, block style only, multi-line strings as literal blocks, Unicode written literally, defaults and empties omitted, columns ordered by ordinal then name, joins by their endpoints then condition_sql, and file paths derived from content.
Three rules change what you can write, rather than how it looks:
- No YAML comments. They do not survive re-serialization. Prose for the agent goes in a
descriptionor a domain README body; prose for humans goes outside the ontology directory. - No unknown fields. A typo’d field name disappears on re-serialization, and the check names the fields that would be dropped.
- Exact enum values.
cardinality: many_to_one, notn:1.source: manual, notManual.
Files with duplicate YAML keys are the one case fmt refuses: fix those by hand, since the formatter cannot know which value you meant.
Identifier case
Physical identifiers must match the warehouse’s stored case exactly, because Cassis quotes every identifier in generated SQL and quoted identifiers are case-sensitive. Snowflake typically stores uppercase, PostgreSQL lowercase.
- Which fields
schema_name,table_name, columnname,grainentries, join endpoints andcondition_sql, and every identifier inside a metric’sexpressionorfiltersor a virtual table’ssql.- The rule
- Take them verbatim from the warehouse. Never retype or prettify them. A case mismatch does not fail validation, it fails at query time.
- Business-facing names
- A domain’s
titleand body, plusdisplay_name,description, andsynonyms, are prose. Write them the way people speak.
What else can live there
- Extra files are fine
- A
.gitkeep, a hand-written note, or the managedAGENTS.md. Cassis reads only the ontology files (*.ymland*.yaml, plus the domainREADME.mdfiles) and replaces only those.cassis schema pulladds a.schema.jsonsnapshot, which stays local and must never be committed. - A README under domains/ is a domain
- Any
README.mdat a domain path is read as a domain and must carrytype: Domainfrontmatter. A plain prose README there fails the check rather than being quietly ignored, which would drop that domain’s context. AREADME.mdelsewhere in the tree is just prose. - Stray YAML does fail
- A
.ymlfile that is not part of the tree fails the round-trip check. So do.yamlfiles: they are parsed but never written back, since only.ymlis canonical. - No empty ontology files
- Every YAML file must contain at least one field, and every domain README must have its frontmatter. No joins means no
joins.ymlat all: an empty file or an empty list fails validation, so delete the file.
What to write in the fields
A structurally valid ontology can still contain weak business context. The modeling guide written to cassis/AGENTS.md explains where to place rules, how to describe columns, and when a metric needs its own definition. cassis ontology fmt and cassis ontology pull write the guide into your checkout, and Cassis writes it alongside every ontology push to a synced repository. Read it before making substantial modeling decisions, and commit it with your changes. See Install and configure the CLI.