Skip to content
Raw Markdown

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_id the ontology belongs to and the cassis_format_version. Cassis writes it on export, publish, and cassis 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 to cassis/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 typeRequired fields
domaintype (frontmatter, always Domain)
tableschema_name, table_name
columnname
joinfrom_schema, from_table, to_schema, to_table
metricname, 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_name and expression.
  • 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 --check is 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 description or 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, not n:1. source: manual, not Manual.

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, column name, grain entries, join endpoints and condition_sql, and every identifier inside a metric’s expression or filters or a virtual table’s sql.
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 title and body, plus display_name, description, and synonyms, 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 managed AGENTS.md. Cassis reads only the ontology files (*.yml and *.yaml, plus the domain README.md files) and replaces only those. cassis schema pull adds a .schema.json snapshot, which stays local and must never be committed.
A README under domains/ is a domain
Any README.md at a domain path is read as a domain and must carry type: Domain frontmatter. A plain prose README there fails the check rather than being quietly ignored, which would drop that domain’s context. A README.md elsewhere in the tree is just prose.
Stray YAML does fail
A .yml file that is not part of the tree fails the round-trip check. So do .yaml files: they are parsed but never written back, since only .yml is 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.yml at 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.