mend.rest

verify offline
Status  Development instance. The official benchmark held-out execution is BLOCKED pending independent steward isolation. Nothing here is a production claim or an assurance claim.

Do not trust this website

That is the design, not a disclaimer.

A report rendered in your browser is a rendering. It travelled over our network, through our proxy, into our JavaScript. If any of that were dishonest, the page would look exactly the same. So the report is built to be checkable without us: its facts reduce to a digest you can recompute yourself, and its receipts verify under a verifier we do not control at the moment you run it.

The verifier stays open. It lives in the repository, it is built from source, and it runs on your machine with no network. Nothing about the hosted instance can change what it says.

Layer 1 — recompute the fact digest

Cheapest check, no toolchain. Proves the facts you were shown are the facts the digest was taken over.

A report is mend.task-report/1: a facts object, a fact_digest over it, and a small rendering block that is presentation only and is not covered by the digest.

Canonical formJSON with object keys sorted, no whitespace between tokens (separators "," and ":"), and non-ASCII characters emitted literally rather than escaped.
DigestSHA-256 over the UTF-8 bytes of that canonical form, lowercase hex.
InputExactly the facts sub-object — not the envelope, not rendering.
Referencemend/canonical.py (canonical_bytes, digest) and mend/report.py (build_report) in the repository.
Known sharp edge — numbers. The canonical form is produced by a Python JSON encoder, and Python distinguishes an integer from a float of the same value: 7 and 7.0 serialize differently and therefore digest differently. A language that collapses the two — JavaScript, for one — cannot recover the distinction from a parsed value, and no global rule fixes it: a single record routinely holds an integer and a float side by side. The fix is to take the number literals from the response text, which already says exactly what Python wrote, and emit them verbatim. (That is what the Reports page does.) Until you have done that, a mismatch is not evidence of tampering. Recompute in Python, or use layer 2.

Layer 2 — run the open verifier over a receipt bundle

The real check. Cryptographic verification is the verifier's job and nothing else's.

Bundle layout

on disk, self-describing
manifest.jsonWhat the bundle claims to contain.
receipts/*.dsse.jsonEach receipt as a DSSE envelope wrapping an in-toto statement.
keys/trust-bundle.jsonThe keys, their validity windows, and what replaced what.

The verifier

exit code is the verdict

notary-verify is a Rust binary built from the assets/proof workspace in the repository. It is invoked as notary-verify verify <bundle-dir>, optionally with --trust-root <path> to pin the root you accept rather than the one the bundle carries. Exit code 0 means verified; anything else means it did not verify — the callers in this codebase treat the exit code as the answer and record digests of the output rather than reinterpreting it.

It runs with an empty PATH and makes no network request. The proof workspace is tested to forbid networking crates. Build it yourself and the binary you run is the binary you compiled.

Verifier sourceassets/proof/ — cargo workspace, offline-lockable
Bundle assemblerassets/bundle/bundle.py assemble --receipts <dir> --keys <dir> --out <dir> --now <ts> — note its own declared limit: it does layout only, and asserts nothing about the cryptography.
Reading surfaceassets/surfaces-ledger/ledger.html — opens a bundle from local disk, entirely in the page, no network and no backend. It re-hashes the chain and names every absence. It does not verify signature cryptography, and says so.

What verification does and does not buy you here

Read this before treating a green exit code as more than it is.

it does proveThe receipts in the bundle are internally consistent, chain to each other, and resolve against the keys the trust bundle names.
it does not proveThat the receipts describe reality. A receipt records what the mediator observed. Absence of a receipt is unobserved, not didn't-happen, and no verifier can close that gap.
not witnessedReports on this instance carry a local structural pin with external_witnesses: []. There is no third party attesting to the timeline. A local pin is a self-consistency claim, not independent corroboration.
not a public signatureIdentity on this instance is local symmetric HMAC — no HSM, no TEE, no public-key signature an outsider could check without the secret.
simulation pinsA shadow task's pin is mend.shadow-pin/1 with assurance fixture-digest-simulation-only and production_proof: false. A PASS resting on that pin is amber on our surfaces and should be amber in your head.
What this page is
Documentation, at documentation level. It describes the verification path and the exact digest rule; it does not run anything, fetch anything, or hold anything. The authoritative statements are the code and the tests in the repository, and where this page and the repository disagree, the repository is right and this page is a bug.