Skip to main content
Quickstart gets you a first answer. This page walks the loop you’ll actually live in day to day, including the “a bit more effort” half quickstart only lists, against the Rental guide project: canonic ingest → review → apply, a real schema change and how canonic reacts to it, defining a canonical metric and a guardrail by hand, and the same loop running headless in CI. Every command below and every diff shown is real output from a live run against a copy of examples/rental, not hand-written.

The loop, once

A fresh project, bootstrapped:
canonic introspects the schema and drafts one proposal per table. With an LLM configured, the deterministic core (types, grains, joins, additive measures) is joined by LLM-assisted naming: human-readable labels, aliases, and a guessed join with its reasoning:
Nothing is written to semantics/ yet. It’s a proposal. Walk through them:
Accept, reject, skip, or freeze each one in turn. Quitting or crashing mid-review resumes at the first still-pending item next time. canonic apply <run_dir> is the batch alternative: apply everything still pending in one shot, useful scripted or after hand-editing a diff file yourself.

When the data actually changes

Six months later, someone adds a column: the rental company starts tracking goodwill repair-cost waivers.
Run canonic ingest again (not --bootstrap, this project is already onboarded):
Seven tables didn’t change: same source_fingerprint, so nothing is proposed beyond a last_validated_at refresh, no_op, per the reconciliation decision table. damages did change, so it’s an edit, not a silent rewrite:
The deterministic builder didn’t just notice the new column. It drafted an additive sum measure for it too, confidence: 1.0. It’s still propose-only: this project’s canonic.yaml has reconcile.auto_apply.enabled: false (the default), and structural fields like new measures never auto-apply regardless of confidence. canonic review shows the same diff and prompt as before. Accept it, and damages.yaml now has waived_amount and total_waived_amount.
If damages.yaml had instead been human_curated (promoted with canonic review curate after the wizard, as the tracked rental example is), this same run would not propose a clean edit. A curated file always outranks new inferred evidence. Reconciliation flags a contradiction and leaves the file untouched, for every table whose drafted shape no longer matches the curated one, not just the changed table. See provenance tiers.
At this point waived_amount is a real, queryable measure, but a raw (source, measure) pair, not yet a canonical metric.

From auto-drafted to governed

This is the “a bit more manual effort” half quickstart mentions but doesn’t show. Semantics is auto-maintained. Deciding what’s canonical and mandatory is deliberately human-owned:
Neither file comes from canonic ingest. You write them, commit them, review them like any other code change. That’s the split rule from the three layers: semantics tracks physical reality, and contracts decide what’s authoritative.

Prove it

--json shows the resolution and the guardrail actually firing:
The guardrail’s mandatory_filter is AND-ed into the compiled SQL whether or not the caller asked for it. An MCP-connected agent resolves the exact same binding and guardrail on its next call: CLI and MCP dispatch to the same core service, there’s no separate “agent view” to keep in sync. canonic audit’s onboarding funnel also now shows the curated-review milestone reached:
The earlier funnel steps only light up when they’re driven through the interactive canonic setup wizard: this walkthrough called canonic ingest/query directly, so only the review milestone fired. Following Quickstart first fills in the rest.

The same loop, headless

Everything above works unattended. Headless mode is explicit --headless, or auto-detected from CI=true:
The deterministic builder is pinned: no LLM in the loop, so no labels, no aliases, and no guessed joins:
Compare that to the LLM-assisted version at the top of this page: same columns, grain, and types (the deterministic core is identical either way: that’s the part that’s reproducible), but no naming or join guesses without an LLM in the loop. canonic apply batches these in without a review prompt, matching how a CI job would consume them. The same data-change scenario, headless:
By default this also opens a PR carrying the diff and any contradiction notes (--open-pr/--no-pr to force either way). It needs an actual git remote to push to, which a scratch directory doesn’t have. --strict is the CI gate: any flagged contradiction fails the run with exit code 14, instead of silently proceeding.
Headless mode never authors contracts or guardrails: that step from “From auto-drafted to governed” is always a deliberate, human, out-of-band change, in every mode.
A minimal scheduled-ingest job looks like:
Because reconciliation’s decision is always deterministic, identical evidence and accepted state reproduce byte-identical proposals across runs, so this job is safe to run on a schedule.

Recap

Same design principle throughout: canonic proposes, you approve.