> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getcanonic.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Contracts & guardrails

> What's canonical, what's mandatory, and what the compiler must obey.

Contracts are the one layer that's **human-owned**, not auto-maintained. They declare which definition is authoritative and what a compiled answer must satisfy. Governance decisions canonic surfaces but never makes for you.

For the exhaustive field-by-field reference across all four contract file types, see the [contract schema](/reference/contract-schema).

## Canonical metric bindings

A binding (`contracts/metrics/<metric>.yaml`) resolves a logical metric *name* to exactly one owning definition:

```yaml theme={null}
metric: revenue
owner: "@data-platform"
canonical:
  kind: single           # default: a single (source, measure) pair
  source: orders
  measure: total_revenue
provenance: human_curated
aliases: ["net revenue", "rev"]
status: active
```

**Ambiguity rule:** if a requested name matches zero or more than one active binding, the compiler doesn't guess. It returns a structured `AMBIGUOUS`/`UNRESOLVED` error listing candidates, so the caller can refuse-and-ask instead of picking wrong.

### Beyond `single`: composable metrics

Not every metric is one measure on one table. The binding's `kind` selects a compilation strategy for metrics built from parts:

| `kind`             | Compiles as                                                                                                 | Example                                               |
| ------------------ | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `single` (default) | Resolve to one `(source, measure)`.                                                                         | `revenue`                                             |
| `ratio`            | Aggregate numerator & denominator independently, divide after.                                              | `avg_repair_costs = total_repair_cost / damage_count` |
| `weighted_avg`     | Same as `ratio`, structured as weighted-sum ÷ weight: rating weighted by review count                       |                                                       |
| `semi_additive`    | Sum over every dimension except one (typically time), which collapses via `last`/`first`/`avg`/`min`/`max`. | `ending_inventory` collapsing `snapshot_date`         |
| `distinct_count`   | Recompute `count(distinct …)` at the requested grain, never summed from partial counts.                     | `active_customers`                                    |
| `percentile`       | Recompute the quantile at the requested grain from base rows.                                               | `median_order_value`                                  |
| `opaque`           | Serve only at its declared native grain. Any other grain is rejected.                                       | a pre-computed external score                         |

Because `ratio`/`weighted_avg` components reference *other metric names*, each component keeps its own guardrails and provenance. A numerator's guardrails fire automatically wherever it's used. Every `kind` also accepts an optional `population_filter`, applied before aggregation on every leaf, for metrics that are only defined over a restricted population (e.g. excluding test accounts from `active_customers`). See [the compiler](/concepts/compiler#fanout--additivity) for how each strategy actually compiles to SQL.

## Guardrails

A guardrail declares a rule the compiler must enforce:

```yaml theme={null}
id: revenue-excludes-refunds
applies_to: { source: orders, measure: total_revenue }
kind: mandatory_filter
filter: "status != 'refunded'"
severity: error
rationale: "Refunds are reversals, not revenue."
```

| `kind`               | Behavior                                                                                                                                    | Status   |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `mandatory_filter`   | The predicate is always AND-ed into the compiled `WHERE`, even if the request already filters differently.                                  | Enforced |
| `restrict_source`    | In a given `context` (e.g. board reporting), only a specific source is permitted.                                                           | Enforced |
| `required_dimension` | The query must group by or filter on a given `dimension` (by canonical name, qualified `alias.dim` form, or declared alias) or be rejected. | Enforced |
| `min_trust`          | In a given `context`, blocks (or warns) if the answer's trust tier is below `level`.                                                        | Enforced |

`severity: error` (the default) blocks the query, but `severity: warn` lets it through with a `warnings[]` entry instead. `mandatory_filter` has no "block" outcome to gate this way: its predicate is always injected regardless of severity, and `severity: warn` there only adds the `warnings[]` entry on top.

## Trust score

Every compiled answer carries a **trust tier**: `caution` \< `provisional` \< `trusted`. It is a category with reasons, not a numeric score. It reports confidence in the *context* behind an answer (provenance, assertion coverage, finality, freshness), never a claim that the number itself is factually correct.

Scoring is **worst-signal-dominates**. The tier starts at `trusted` and drops to the lowest tier forced by any active signal, with `reasons` listing only the signals tied at that worst tier.

| Signal                 | Caps at       | When active                                                                                                                                                                             |
| ---------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Provenance             | `provisional` | The binding's provenance is `inferred`.                                                                                                                                                 |
| Assertion coverage     | `provisional` | No assertion covers the binding (or one does, but pass/fail isn't wired to scoring yet).                                                                                                |
| Finality               | `provisional` | Served rows include `provisional` (non-final) rows.                                                                                                                                     |
| Freshness              | `provisional` | A source is stale (not yet enforced, no staleness policy defined).                                                                                                                      |
| Outcome (E11, dynamic) | `caution`     | A binding has a recent confirmed `wrong_definition` outcome within the feedback loop's trust-cap window. See [Feedback loop](/concepts/ingestion-and-reconciliation#feedback-loop-e11). |

A `min_trust` guardrail enforces a floor on the served tier for a given `context`:

```yaml theme={null}
id: board-reporting-trusted-only
applies_to: { metric: gross_revenue }
kind: min_trust
level: trusted
context: board_reporting
severity: error
rationale: "Board figures must come from human-approved, final, validated definitions."
```

`canonic audit` includes a trust-tier worklist ranking every canonical metric worst-tier-first. See [`canonic audit`](/cli-reference/status-report#canonic-audit).

## Finality

Some metrics are served by two physical realizations along a freshness axis, a batch table that's final, and a real-time table that's provisional:

```yaml theme={null}
metric: revenue
realizations:
  - { source: orders,    role: final,       watermark: "business_day - 1 day" }
  - { source: orders_rt, role: provisional }
coalescing: "window <= watermark ? final : provisional"
result_flag: per_row
board_only_final: true
```

The compiler selects the right source per time window and tags every result row `final` or `provisional`. `board_only_final` pairs with a `restrict_source` guardrail so a `board_reporting` context sees only the final source.

## Assertions

An assertion (`contracts/assertions/<id>.yaml`) is a trusted query → expected-result check:

```yaml theme={null}
id: revenue-2025-q1
query: { metrics: [revenue], filters: ["order_date in 2025-Q1"] }
expect: { rows: 1, values: { revenue: 4218334.10 }, tolerance: 0.01 }
source_of_truth: "Finance close, FY25 Q1"
```

[`canonic assert`](/cli-reference/query-sql-assert) runs every assertion through the compiler and gates on the result: the CI integration that turns ">90% accuracy" from aspirational into measured. `canonic query --harness` runs matching assertions inline against a single query.

## The contract ↔ compiler seam

The compiler never reads contract files directly. It asks a single resolver:

```text theme={null}
ContractResolver exposes to the compiler:
  resolve_metric(name, context)        -> Binding | Ambiguous | Unresolved
  guardrails_for(source, measure, ctx) -> [Guardrail]      # ordered, deterministic
  finality_for(metric)                 -> FinalityRule | None
  assertions_for(query)                -> [Assertion]
```

The resolver is the only authority on "what is canonical / what must be obeyed". The compiler trusts its results and never reimplements canonicality logic. Results are deterministic and stably ordered, so identical queries compile to identical SQL every time.
