Skip to main content
You have been handed an evidence pack and the kernel printed VERIFIED. This page tells you exactly what you may conclude from that, and what you may not. The limits are specified in evidence-bundle.md §5.3.4, which ships inside the sanning-proof package and is the normative source. This page is the plain reading. See Wire formats for how to open it.

What a verified pack proves

For every event in the pack:
  • The agent signed it. An Ed25519 signature over the envelope, checked against the public key embedded in the artifact.
  • The record matches its commitment, where the agent disclosed the bytes. The disclosed record hashes to the payload_hash the signature covers.
  • It was included in the witnessed set. An RFC 9162 inclusion proof from the event’s leaf to the Merkle root committed inside the signed stamp.
All of it offline, against keys and bytes inside the pack. No call to Sanning, and no account.

What it does not claim

Four boundaries. A verified verdict is a narrower statement than it first reads as, and relying on the wider reading is how evidence gets over-trusted.
It is not completeness. A pack proves the events it carries were committed to that root. It says nothing about events the agent chose not to include. “Every step is here” is not a claim this artifact can make. If completeness matters to you, ask the agent what the pack covers and check that claim separately. Three different things get called completeness, and only the first two are proved here:
Two different leaf counts sit in a pack, and the obvious one is the wrong one for this. inclusion.leaf_count, and the stamp’s top-level leaf_count, count the whole organization’s tree for that interval, so they include every other agent’s records. Reading a pack as short because it shows fewer events than that number is a false alarm in any organization running more than one agent.The number that describes one agent’s slice is subtrees[ref].leaf_count inside stamp_bytes, which is exact for that agent in that interval. Its ref is blinded with a secret only the producing organization holds, so the agent can check their own slice and a third party cannot map a subtree to an agent unless the agent discloses the ref. That blinding is deliberate: it is what keeps one customer’s agent roster out of another reader’s hands.
It is not proof the chain was consulted. A verified verdict is offline and internally consistent, and it names a witness. Confirming that the witness actually holds those bytes is a separate, online act. Run the kernel with a gateway to do it, or fetch the transaction yourself. It is not identity. The envelope carries a public key, and a key is not a person. Binding a key to an organization happens out of band. Agent identity informs a reader; it never gates verification. It is not a statement about a per-agent slice. A stamp covers an interval across a whole organization. The pack carries only its own events, so it cannot tell you what else that organization anchored in the same interval.
From 0.14.0, a verified drop carries one reliance sentence. Every pack and every case reading states, in one sentence, what a verified result there means you can rely on, and names a hand-off clause only when the drop actually holds a hand-off. It replaces wording written for a hand-off reading that read oddly on a pack holding only one agent’s own steps.

What a failure means

If the kernel prints FAILED, it names the record that broke and why. The pack itself carries an asserted verdict from whoever produced it, and the kernel throws that away and recomputes. Where the two disagree, the recomputed answer wins, and the output says the asserted verdict and the recomputed one by name, noting that the recomputed one is the one that counts. That is the property worth understanding. You are not reading the agent’s claim about their own evidence. You are reading your own machine’s answer.

What “undetermined” means

A pack can verify while leaving some things unknown, and the kernel distinguishes that from failure on purpose. Absent evidence is reported as absent, never as failure. If an agent withheld the bytes for a record, its content binding is undetermined. That is an agent exercising minimum disclosure, not a defect, and treating it as tampering would be wrong.

A verdict over a case

A case reading is one reader’s reading over the packs it was handed: several packs that together hold both sides of one or more hand-offs, often from two organizations. A reader drops every pack together and gets one case verdict, with every exception named. It needs @sanning/proof or sanning-proof 0.13.0 or later. A formed pair verifies only when each record names the other’s signer, and a pair that does not fails the check counterparty_named. The verdict is reached by one rule. Every hand-off whose two sides were found and checked contributes a verdict, and so does every exception. If they all say the same word, that word is the case verdict. If they differ, and none of them is a proven failure, the case is mixed. Any proven failure keeps the headline: a case with even one failed contribution reads failed. A case reads verified only when there is no exception at all. Each kind of exception contributes one verdict word, as the following table shows: Two more land in the “reader could not tell” group: more than one record on the other side could be a hand-off’s counterpart (fan-in, fan-out, or several messages one way), and a pack declares a record under the wrong role: a record marked handoff that no pair claims, or a record marked step that is actually a hand-off side. A candidate that another record already accounts for is set aside before the reader grades, so an honest exchange of several messages in one direction reads verified. A step record, such as a closing record, is signed as one under sanning.declaration/v2 and sits outside the hand-off scope test entirely, so a full pack, hand-offs and step records together, reads cleanly. What a hand-off record does not prove says what each exception looks like and why the reader cannot resolve it on its own. One kind carries a pack’s own verdict instead: a pack whose signature holds and which does not verify on its own passes its verdict to the case, so a case never reads better than its weakest pack. A pack whose declaration says a record’s content is withheld on purpose is not one of these: it still reads verified, and the reading’s notes (never an exception) say how many records are withheld. A missing side is an exception, never a failure. No pack signed by the counterparty’s key was in what you dropped; that is not proof the counterparty recorded nothing, and it is not tampering. The kernel prints each exception in its own words, under a scope sentence that names the packs, the signing keys, the hand-off records and the exceptions in the reading, and a witness line that says whether any witness was looked up on the public record, independently of the headline. To read a case from your own code, pass the packs to readCase (read_case in Python). The case command does the same from a terminal, as Hand over and verify a case shows.
From 0.17.0, the reading names every pack, record, exception and note by position, not by ref. Two packs can carry one ref, and one pack can carry an event_id twice, so a ref is a label for a person and never a join. The following table lists the fields a consumer joins on. Only the TypeScript readCase result is camelCase (packIndex); case --json from either command (npx @sanning/proof or the Python sanning-proof) and the Python read_case use pack_index. records lists every record of every pack, not only the ones that were read. Each carries a status:
  • read_clean: the record went into the reading and no exception or declaration finding names it. The status says nothing about its pack, so read the pack’s grade beside it.
  • read_flagged: the record went into the reading, and an exception or a declaration finding names it.
  • not_read: the record never went into the reading. Its reason is the kind of the exception that says why.
A consumer that joined by ref, or read records as “the records that were read”, must change. No verdict, exception kind, contribution or exit code moves. Run it with one directory per pack, each holding bundle.json and, when the pack has one, logs/. Over an honest case with every record withheld on purpose, both languages print the same lines:
Over a case whose two sides disagree, both languages instead print:
A case verdict carries the same limits as a pack verdict, and more: What a hand-off record does not prove states them.

Next

Verify at the command line

The two commands, and what each exit code means.