Verify a Citation's Evidence
Re-check that a cited claim is actually present in its primary source — offline, in any language, with no relay and no trust in the producer's index.
A receipt proves who computed a result. It does not, by itself, prove anything about the inputs the agent used — see the honesty boundary. Evidence provenance closes part of that gap: when a citation carries an EvidenceProvenance, you can re-fetch the primary source and re-check that the cited span is actually present in it — the evidence-axis analog of verifying a signature.
The one thing to internalize: this re-verifies presence, never truth. There is no oracle inside the check — the bytes either contain the cited span or they don't. It answers "is this claim backed by the record it cites?", not "is the record correct?". That honesty is the whole point: it is a claim you (or anyone) can independently re-derive, not one you have to trust.
What you get back
verifyEvidenceProvenance(bytes, provenance, opts?) returns a structured result — never a bare boolean, so a non-present answer names why:
{ present: true }
| { present: false, reason: "digest_mismatch" | "projection_unresolved" | "span_absent" }digest_mismatch— the bytes you fetched are not the bytes the producer attested (wrong record, or tampered). Fail closed before any span check.span_absent— the bytes are right, but the cited span is not in them. The anti-fabrication case: a producer cannot place a figure into a record that does not contain it.projection_unresolved— the citation names a document→text recipe you did not supply a resolver for (see the recipe path). Fail closed — never silently pass.
Evaluation order is load-bearing: digest first, then projection, then substring.
The raw-byte path (the frictionless floor)
When the producer cited a raw-byte-addressable source — plain text, a .txt filing, a patch — provenance.projection is absent and you check the span against the raw bytes directly. No shared code, no recipe — re-verifiable by construction:
import { verifyEvidenceProvenance } from "@motebit/verifier";
// A citation you received (e.g. on a signed research brief).
const citation = {
locator: "https://example.com/filing.txt",
provenance: {
digest: { algorithm: "sha-256", value: "f5fcede7…" },
span: "$81,615 million",
},
};
// Re-fetch the primary source yourself — the bytes a stranger obtains.
const bytes = new Uint8Array(await (await fetch(citation.locator)).arrayBuffer());
const result = await verifyEvidenceProvenance(bytes, citation.provenance);
// { present: true } → the cited span is in the source you just fetched.
// { present: false, reason: "digest_mismatch" } → you fetched a different record.
// { present: false, reason: "span_absent" } → the span was fabricated.@motebit/verifier is the Apache-2.0, zero-monorepo-dependency, browser-safe consumer surface. verifyEvidenceProvenance (and the EvidenceProvenance / EvidenceProvenanceResult types) re-export from @motebit/crypto through it.
The recipe path (HTML)
HTML and other extracted sources are not raw-byte-addressable: the producer cited a span of the text extracted from the page, but the digest is over the raw HTML bytes (the honest invariant — you re-fetch raw bytes, not the producer's extraction). So provenance.projection names a published, byte-deterministic document→text recipe, and you inject a resolver for the recipes you trust:
const result = await verifyEvidenceProvenance(rawHtmlBytes, citation.provenance, {
resolveProjection: (recipeId, bytes) => {
if (recipeId === "agency.html-text.v1") return projectAgencyHtmlTextV1(bytes);
throw new Error(`unsupported recipe ${recipeId}`); // a resolver fault, not "absent"
},
});The verifier is domain-blind by construction — it owns no recipe catalog (that would be document-format authority). You bring a resolver only for recipes you trust; for any other recipe the check fails closed with projection_unresolved. To signal "I can't resolve this," omit the recipe from your resolver — never throw as a "not supported" signal (a throw propagates as a caller fault, never a false present: false).
The one recipe in use today, agency.html-text.v1, is a frozen, world-public spec. A conforming implementation reproduces its output byte-for-byte from §2 of that spec — five ordered steps, ASCII-only whitespace, a single-pass entity decode. The canonical port to copy is the stdlib-only Python reference (project_agency_html_text_v1). (If you are already running a motebit, @motebit/tools exports projectAgencyHtmlTextV1; a lightweight standalone resolver package is on the roadmap — file an issue if you want it sooner.)
Verify in any language
This is a protocol, not a TypeScript feature. The law is sha-256 + substring presence (plus a published recipe on the HTML path) — no signing library required. A language-neutral conformance corpus, spec/conformance/evidence-provenance/corpus.json, is verified byte-identically by both @motebit/crypto and the stdlib-only Python reference, enforced in CI. Run it yourself:
curl -sL https://raw.githubusercontent.com/motebit/motebit/main/spec/conformance/evidence-provenance/corpus.json -o corpus.json
python examples/python-receipt-verifier/verify_evidence_provenance.py corpus.json
# → one { present, reason? } per vector, matching the corpus's `expected`.The boundary, restated
provenance.binding (issuer authority — "this filing is really from issuer X") is not verified here; it is app-layer. locator is advisory. The law is exact-substring presence against content-addressed bytes — re-verifiable presence, never truth, no oracle. State that plainly in any UI: a re-verified citation means the cited claim is present in the record at this URL, which is exactly as much, and as little, as it sounds. Doctrine: evidence-provenance.md; spec: spec/evidence-provenance-v1.md.
Verify a Receipt
The fastest correct path to verify a motebit ExecutionReceipt — install the published verifier, do not reimplement it.
Choosing a Verify Surface
Three packages verify motebit receipts — crypto, verifier, state-export-client. Which one you import depends on the deepest binding rung you need and whether you can use the network.