Markdown for LLMs
Show grounds: proof, sources, and hashes
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Show grounds: proof, sources, and hashes
## At a glance
- **Goal:** show the user why the answer is what it is — the rule
that fired, the source anchors behind it, and the hashes that pin
the computation — each presented for exactly what it warrants, no
more, and list the checks actually performed, not the checks the
format would allow.
- **You need:** [Calculate and check](/build/calculate-and-verify/):
the proof and hashes below come from the `COMPUTED` answers built
there.
- **Run:**
```bash
node --import tsx --test test/evaluate.test.ts
```
- **Files:**
```text
src/to-view.ts grounds section of the view model
src/runtime.ts (answers already carry proof/sources/hashes)
```
## What to show next to the answer
The answer document carries its own grounds; the application does
not compute them. On the example truth call (event `2026-03-06`,
14 days, candidate `2026-03-20`) the grounds section of the view
model reads:
```text
rule: TagesfristEnde (urn:de:corpus:clir:bgb-fristen#TagesfristEnde)
sources: No sources anchored in this canon build
program: sha256:468e3fe1… semantic: sha256:90e53b45… result: sha256:4c543151…
via: local document: 4691 bytes
```
Four things sit next to the answer: the rule that fired, the source
list, the three hashes, and the checks that ran on this answer. The
next four sections take them in that order. The full answer document
with untruncated hashes is in **Verify the numbers** at the bottom.
## Read the rule and the source references
`TagesfristEnde` is the rule that counts a period in days; its full
identifier is `urn:de:corpus:clir:bgb-fristen#TagesfristEnde`. The
view shows that rule identifier as the address of the reasoning — it
is stable across runs and versions, so a reviewer can look it up. It
is not a link into the source text: this build attaches no source
anchors, and the rule id does not become one.
The `sources` list is empty, and the application says so plainly:
"this canon build attaches no source anchors to the answer". An empty
list is information, not a failure — but hiding it would imply
anchors that do not exist.
## What the three hashes mean
Each hash pins a different thing, and the view labels them separately:
| Hash | Pins | Changes when |
|---|---|---|
| `program` | the canon build that answered | the canon version changes |
| `semantic` | the evaluated case with its context | facts, legal time, timezone, or policy change — not the question kind |
| `result` | the outcome of this query | the outcome changes |
Read it as: `semantic` answers "same case?", `result` answers "same
outcome from this SDK?", `program` answers "same build?". None of
them answers "same document bytes?" — that is the checksum's job
in chapter 7. The document bytes do not hash to `result`: `result`
is the engine's outcome hash, while the checksum over the stored
bytes is a separate value the application computes itself (chapter 7
names it `documentSha256`).
The middle row is the one newcomers misread. Collect and truth on
the same case share `semantic` while their `result` hashes differ:
the question kind is not covered. Change the facts, the legal time,
the timezone, or the policy, and `semantic` moves.
## Which checks ran and what they confirm
Seven trust properties meet in one answer. The view shows each as the
status of a check actually performed — never as potential
checkability, and never collapsed into a single badge:
```text
Proof graph: present (the TagesfristEnde application the view reads)
Proof checker: not run — the app displays the graph, it does not re-verify it
Document checksum: passed over the stored bytes (input and metadata not covered)
Replay: passed under the recorded environment (see chapter 7) — after Replay ran on this capture; before that, not run
Producer authentication: not configured — 'via: local' names the execution
path inside the document; it does not authenticate who wrote the file
Source anchors: absent in this build
Applicable law: legalTime 2026-09-17, Europe/Berlin, BGB_FRISTEN_TAG —
outside those assumptions the answer says nothing
```
The replay line is the state after Replay ran on that capture; before
any replay it reads `not run`, and the application never renders a
static "passed" — the replay outcome appears only in the Replay
report for the capture it ran on.
A single "verified" badge would fuse all seven into one glow. The
view shows seven short lines instead, each checkable on its own.
Every line belongs to one capture: new input recomputes, stores a
new capture, and starts un-replayed — a passed replay never
carries over to a new calculation.
## How each change moves the hashes
One row per change, each probed on the worked case:
| Change | `program` | `semantic` | `result` |
|---|---|---|---|
| collect → truth, same case | same | same | differs |
| a fact changed | same | moves | recomputed — compare, never assume |
| legal time or timezone changed | same | moves | recomputed — compare, never assume |
| policy `TAG` → `KALENDER` | same | moves | recomputed — the verdict itself may flip |
| another model version | moves | do not compare | do not compare — replay refuses the mix by spec |
| another SDK, same case | same | same | differs — each SDK replays its own captures |
The policy row is why the app pins one policy per case: the new
`semantic` value is shared by that policy's collect and truth alike.
The last row is the cross-language comparison. The same case through
the Python SDK yields the same `program` and `semantic` hashes but a
different `result` hash and different document bytes: same statuses,
same values, same case hash — different outcome bytes. Replay
therefore stays within one SDK (chapter 7).
## Limits and errors
- Proof nodes are present on `COMPUTED` answers; on other statuses
the graph may be partial or absent, and the view must say that.
- Hash equality across SDKs does not hold: same statuses, same
values — different bytes and different `result` hashes.
- The canon manifest (`questions()`, `passport()`) describes what the
model can answer; it is inventory, not grounds, and belongs in the
form builder, not in the answer view.
- `via: local` describes the execution path recorded in the answer.
It says nothing about who produced the file holding the answer or
whether that file changed afterwards.
## Verify the numbers
The example truth answer, with the fields the view reads:
```json
{
"evaluationStatus": "COMPUTED",
"truthStatus": "TRUE_ONLY",
"rulesApplied": ["TagesfristEnde"],
"sources": [],
"hashes": {
"program": "sha256:468e3fe17c6a28371c6ce8258ae7928c496593673d4b73a52c2a9c40cb50ee33",
"semantic": "sha256:90e53b453107dfee9422a5fe24774823430b5c82c3577b9321a0838a6e54c695",
"result": "sha256:4c5431514533d2afe380ec92a6427831120fdbeca7315f731b5e374c490f759a"
},
"via": "local",
"documentBytes": 4691
}
```
The probe values behind the change table, on the worked case:
```text
collect vs truth, same case ..... semantic 90e53b45… on both; result ae9a7b19… (collect) vs 4c543151… (truth)
facts: event 2026-05-01, 30 days semantic af79c78f…
legal time 2026-01-01 only ...... semantic 26d9fd0a…
timezone UTC only ............... semantic 63a056fb…
policy BGB_FRISTEN_KALENDER ..... semantic ac8223da… (collect and truth alike)
Python SDK, collect ............. same program and semantic; result 1f0abeda…; document 4884 vs 4820 bytes
```
## Check your understanding
Run the grounds test and confirm rule, empty sources, and hashes:
```bash
node --import tsx --test test/evaluate.test.ts
```
```text
ok 3 - the example answer carries its grounds
# tests 13
# pass 13
# fail 0
```
Predict before you open each answer.
<details>
<summary>You ask collect and then truth on the same case. Which of the three hashes match?</summary>
`program` and `semantic` match: same build, same case. `result`
differs (`ae9a7b19…` for collect, `4c543151…` for truth), because the
question kind is part of the outcome, not of the case.
</details>
<details>
<summary>You change only the legal time to 2026-01-01. Which hash moves?</summary>
`semantic` moves (to `26d9fd0a…`): the legal time is part of the case
context. `program` stays, because the canon build is the same. The
`result` is recomputed, so compare it rather than assume it.
</details>
<details>
<summary>The answer has `sources: []`. Is it ungrounded?</summary>
No. This canon build attaches no source anchors, and the application
says so plainly. The grounds it can show are the applied rule
identifier, which is the address of the reasoning, and the three
hashes.
</details>
## Next
- [Save and replay](/build/save-and-replay/)
- [How to read an answer](/guide/reading-an-answer/)
- [Results and errors](/build/sdk/results-and-errors/) for proof, hashes, and the local-vs-host split