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.13",
"answer": "accept",
"confidence": 0.81,
"probabilities": { "0": 0.05, "1": 0.14, "2": 0.81 }
}
#Steps
- Read the decision. Call
DecisionLog.get(id)and read itsRecordedevent forpayloadHashandpayloadURI. - 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.