# Gate 03 — Every claim traces to a source

**Check:** `checks/trace_check.py` · **Stops the build:** yes

## What this gate asserts

Every claim, figure or decision in an output artefact traces to one of exactly
three things, and nothing else:

| Trace kind | Marker | Resolves against |
|---|---|---|
| Specification clause | `[trace: spec:<feature>#<scenario>]` | A scenario in `features/` |
| Data query | `[trace: query:<name>]` | A named block in `queries.sql` |
| Human decision | `[trace: decision:<id>]` | The decision log in the project procedures |

The check runs in both directions.

**Forwards.** Every marker resolves to something that exists. A marker pointing
at a query that was renamed, or a decision that was never recorded, fails.

**Backwards.** Every paragraph containing a figure carries a marker. A number
that appears in an artefact with nothing behind it was, on the balance of
probability, produced by a model rather than by a data source, and it is the
single most expensive kind of error to discover at a client.

It also reports **orphans**: queries that no artefact refers to. An orphan
query is usually a figure that was quietly dropped from a report. Orphans are
reported rather than failed, because there are legitimate reasons for one.

Scenarios are only orphan-checked under `--strict-orphans`. An unreferenced
scenario is the normal case — artefacts cite the data behind a figure far more
often than they cite the test for a behaviour — so warning on every scenario
would turn this gate into noise, and a noisy gate is one somebody eventually
switches off.

## Why it exists

A model asked to produce a report on live data will produce a plausible report
whether or not the data supports it. That is not a defect in the model; it is
what a language model does. The control is not to ask it more carefully. The
control is to require that every figure names the query that produced it, and
to fail the build when one does not.

This is also the gate that makes the pipeline defensible in a room. "Where did
this number come from" has a mechanical answer, in the artefact, that the
client's own data team can check without asking anybody.

## What a client can challenge

- *"Where did this figure come from?"* Follow the marker to the named query
  block in `queries.sql`, which your own data team can read and run.
- *"Who decided this threshold?"* Follow the marker to the decision log, which
  records the person and the date.
- *"Is this query still the one that ran?"* The run log records the hash of
  every query executed against the source, at the time it ran.
- *"What about the figures with no marker?"* There are none. That is the gate.

## Known limits

The check identifies figures by looking for digits. It will not catch an
unsourced qualitative claim — "materially improved", "most clients" — which is
just as inventable and harder to detect mechanically. Those are gate 04 and
human-gate territory. Do not tell a client this gate proves the prose is true;
it proves the numbers are attributed.

Dates, section numbers and similar incidental digits are excluded by a small
set of patterns, which is visible in the check and reviewable.

## How it fails

```
gate-03  FAIL  report.md:22  unresolved marker  query:revenue_by_month
gate-03  FAIL  report.md:41  figure with no trace marker — "grew 18% year on year"
gate-03  WARN  orphan query  queries.sql:headcount_by_site (no artefact refers to it)
```

## Remediation

1. **Unresolved marker.** Either the source was renamed — fix the marker — or
   it never existed, in which case the claim has no basis and comes out.
2. **Figure with no marker.** Find the query that produced it and cite it. If
   no query produced it, the figure was invented; delete it. If it is an
   illustration on stated assumptions, label it as one — see the E4 pattern in
   `templates/briefing/EXAMPLE.html` — and trace it to the decision that
   approved the assumption.
3. **Orphan.** Confirm the figure was meant to be dropped. If it was, remove
   the query too; dead queries get re-used by accident.
4. Re-run `python3 qa-gates/checks/run_gates.py`.

## Configuration

| Setting | Default | Where to change it |
|---|---|---|
| Sources directory | project root | `--sources` |
| Orphans fail the build | no, reported only | `--strict-orphans` |
