Verifying decisions
Check that a published decision record is exactly what the chain acted on, step by step.
Every decision exists in two places: a summary on chain in DecisionLog, and the full record at the location the summary points to. Checking that they match takes five steps.
The record
{
"kind": "grade",
"subject": "214",
"question": "How well does the result meet the brief? ...",
"request": { "state": "...", "questions": { "decision": { "type": "score" } } },
"model": "jev-1.14.0",
"answer": "accept",
"confidence": 0.95,
"scoreConfidence": 0.64,
"probabilities": { "0": 0.05, "1": 0.14, "2": 0.81 }
}
Grade probabilities are keyed by level index, counted from the lowest level as 0. The brief lists its levels best first, so the keys run in the reverse order of the brief.
A grade's confidence is the probability on the paid levels, so you can recompute it. In the record above, the brief has three levels and pays from the second, so the paid levels are keys 1 and 2: 0.14 + 0.81 = 0.95. scoreConfidence is Jev's own measure of how concentrated the score is. It is kept for reference, and the chain does not gate on it.
Steps
- Read the decision. Call
DecisionLog.get(id)(ids start at 1). Find itsRecordedevent by filtering on the indexedid, for exampleclient.getContractEvents({ address, abi, eventName: "Recorded", args: { id }, fromBlock: 0n }), and readpayloadHashandpayloadURIfrom it. - Fetch the record from
payloadURI. - Check the record. Serialize it canonically, with object keys sorted and no whitespace, and compute
keccak256of the UTF-8 bytes. It must equalpayloadHash. - Check the input. Serialize the record's
requestfield the same way. Itskeccak256must equal the on-chaininputHash. - Check the confidence.
confidence × 10,000, rounded, must equal the on-chainconfidenceBps.
import { keccak256, toBytes } from "viem";
function canonical(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
if (value && typeof value === "object") {
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`)
.join(",")}}`;
}
return JSON.stringify(value);
}
const ok =
keccak256(toBytes(canonical(record))) === payloadHash &&
keccak256(toBytes(canonical(record.request))) === decision.inputHash &&
Math.round(record.confidence * 10_000) === decision.confidenceBps;
What this proves
That the published question, request, answer and probabilities are the ones recorded on chain before the decision was applied, and that the confidence the contract compared against its threshold is the one in the record.