# Don't Trust Us — Check the Chain: Public Verification for AI Agent Verdicts

> Source: <https://agentbadge.xyz/blog/arc-c16-verdict-verification>
> Published: 2026-10-14 00:00:00+00:00

# Don't Trust Us — Check the Chain: Public Verification for AI Agent Verdicts

Every verdict ships with an on-chain receipt: GET /api/verify/:verdictId (public facade), the /verify SSR page accepting verdict id | tx hash | memo id, and @agentbadge/verify-verdict — a standalone package that re-checks the memo anchor, epoch head anchor, and full chain recompute with zero calls to our server.

Every AgentBadge verdict can be verified independently of our server. GET /api/verify/:verdictId returns the artifact, reason codes, memo anchor, hash-chain status, and the PROOF_KINDS_V1 registry (verdict | witness | anchor-head; ?kind= narrows per layer, unknown → 400 unknown_proof_kind). The paid response itself carries an inline receipt — verify:{api,page,memoId} — with the deterministic memo id. Level-2 is npx @agentbadge/verify-verdict: proofs[] across verdict anchor, anchor-tx, anchor-head, and chain-recompute (firstBrokenSeq on tamper), exit 0/1, memo contract 0x5294E9927c3306DcBaDb03fe70b92e01cCede505. Unanchored renders pending, never valid; RPC failure is an honest rpc_unavailable error.

What happens if we lie to you? Not a bug — a lie. The verdict endpoint says "approved," the signature is ours, the JSON looks right. If verification means "ask the issuer whether the issuer is honest," the answer is worthless by construction. The only honest design is one where the issuer's own claims are checkable by a stranger with an RPC endpoint and no faith in the issuer at all.

That is what we shipped for every verdict our evaluator signs. Each paid response now carries its own **verification receipt** — links and a deterministic memo id — and every claim in that receipt re-verifies on-chain through a standalone package that never calls our API. If our server disappears tomorrow, the proofs still check.

## How do you verify a verdict through the public API?

One GET, no auth: `GET /api/verify/:verdictId` returns the stored verdict artifact, machine `reason_codes`, the per-verdict **memo anchor** state (` status`, `memoId`, `txHash`, `blockNumber`), the **hash-chain** status (` anchored`/` pending`/` disabled` with a `head_ref` pointer for offline recomputation), and a `proof_kinds` registry summary. Unknown id → `404 verdict_not_found`; it never answers a fake-valid.

The response is layered by design. `?kind=verdict` returns only the verdict layer (artifact + memo anchor); `?kind=anchor-head` returns only the chain layer; `?kind=witness` answers `implemented:false` — the kind is declared in the registry so verifiers can feature-detect it before it ships. Ask for an unregistered kind and you get `400 unknown_proof_kind` with the full `known` list, not a silent fallback.

## What does the /verify page do?

It is the same check for humans: a server-rendered page at `/verify` that accepts a **verdict id, an anchor transaction hash, or a memo id** — all `0x`-prefixed 64-hex strings — and resolves which one you pasted via the anchor reverse-lookup. It renders the verdict, the reason codes, the anchor status with an explicit *pending* banner when the on-chain write has not landed, explorer links, and JSON-LD metadata — all without JavaScript, so it works in `curl` and in an agent's text browser.

Honest statuses are the point. An unanchored verdict renders "pending," never "valid." A malformed id is a `400`, a well-formed unknown id is a `404`, and the page tells you which of the two happened.

## How do you verify without trusting AgentBadge at all?

Run the standalone package. `@agentbadge/verify-verdict` re-checks everything against the chain itself — the memo contract at `0x5294E9927c3306DcBaDb03fe70b92e01cCede505` on Arc, no call to our API, no account, no key:

```
npx @agentbadge/verify-verdict 0x<verdictId-or-txHash>
# exit 0 — every executed proof passed
# exit 1 — invalid, unanchored, or a typed error (rpc_unavailable)
```

Exit 0 is not the same as full coverage. If a check ran in a weaker mode — for example `verdict` without `--artifact`, which proves only that a memo exists, not what it says — the run still exits 0 but prints `VALID (partial)` and sets `partial: true` in the JSON. Treat `partial` as "not tamper-proven" and pass `--artifact` to close the gap.

Each run produces a `proofs[]` report — one entry per layer, each `ok:true/false` plus detail:

- **verdict** — the per-verdict memo anchor exists at`memoIdFor("eaas", verdictId)` and its memo data equals`keccak256` of the canonical artifact JSON (pass the artifact via`--artifact` for the tamper check).
- **anchor-tx** — a memo event for*this* verdict lives inside the transaction you supplied. A bare txHash without a verdictId cannot bind anything, so the proof is`ok:false` with reason`no verdictId supplied` . A transaction that carries other memos does not prove this verdict; the bound event's memo is reported as`artifactHash` .
- **anchor-head** — the periodic epoch anchor at`memoIdFor("chain", "eaas-verdicts", epochSeq)` binds`(headHash, count, prevAnchoredHeadHash)` , chaining epochs.
- **chain-recompute** — feed it the chain entries (`--entries` ) and it folds them locally: a broken entry fails with`firstBrokenSeq` , a verdict absent from the entries fails honest-invalid.

Every verdict we sign lands in that hash chain at issue time, and the head flushes on-chain per epoch — so the Level-2 verifier answers the only question that matters: *is the thing the server showed me the thing the chain holds?*

```
verify-verdict 0x4af3… --artifact verdict.json --entries chain.json
# proofs: verdict ok | anchor-head ok | chain-recompute ok
# valid: true — exit 0
# without --artifact the same run prints:
# VALID (partial)
#   coverage: partial — see proof warnings above
#   ✓ verdict  … warning: artifact not supplied — only memo existence checked
# exit 0
```

## What is the proof-kind registry?

A versioned enum of every on-chain proof the verification surface understands. `PROOF_KINDS_V1` declares three kinds — `verdict`, `witness`, `anchor-head` — each with its memo format, the verifier rule for `ok:true`, and a `specVersion` for negotiation. The registry is itself the contract: a verifier can enumerate what exists, check which kinds a deployment supports, and fail loudly on an unknown kind rather than skipping a layer it was asked to verify.

The `witness` kind is the telling entry. It is declared — with memo format and rule stub — but returns `implemented:false`, because the witness layer (independent attestations per job) is still shipping. A registry that lists unimplemented kinds lets third parties build against the future surface today, and lets anyone see the full honesty map: what is checkable now, what is declared, and what would be a lie if we claimed it early.

*The seam in one picture: a verdict writes two proofs — its own memo anchor and a chain entry — and the chain's head anchors each epoch. Level-1 is our facade over the same data; Level-2 checks both layers from the RPC directly, and the recompute fold fails at the first tampered entry.*

## What does "pending" mean — and why is there no fake-valid?

Because a verification surface that lies is worse than none. Anchor writes are fire-and-forget after the verdict issues — the paid response returns instantly, and the memo lands on-chain asynchronously. Until it lands, every surface says `pending`: the API's `anchor.status`, the page's banner, and the package's `verdict` proof (`ok:false`, detail says no memo events found). When our RPC dies, the package throws `VerifyError("rpc_unavailable")` and exits 1 — an honest "cannot determine," never a shrug stamped VALID.

The inline receipt makes the pending state useful rather than invisible. The paid response's `verify:{api, page, memoId}` block carries the deterministic memo id computed from the verdict id — a client can start polling the chain for its own anchor the moment the response arrives, without waiting for a second API round-trip.

*This is C16 in the Arc Campaign series. Previously: [The x402 Bazaar — a service catalog machines can shop](https://agentbadge.xyz/blog/arc-c15-x402-bazaar). Next: [Never Charged for a No — the machine-checkable refusal contract](https://agentbadge.xyz/blog/arc-c17-honest-refusal).*

**Links:** [Verify page](https://agentbadge.xyz/verify) · [openapi.yaml](https://agentbadge.xyz/openapi.yaml) · [llms.txt](https://agentbadge.xyz/llms.txt) · [@agentbadge/verify-verdict](https://www.npmjs.com/package/@agentbadge/verify-verdict) · [AgentBadge](https://agentbadge.xyz)

*Don't certify. Measure.*
