Independent verification
Don’t take our word for it.
Every decision Sentinel makes is written to a hash-chained audit log, and any decision can be exported as an evidence bundle. This page gives you the full specification, a verifier that depends on nothing, and a real bundle to try it on — so you can check our evidence before you talk to us, without installing our product or trusting our infrastructure.
# nothing to install, no network, no account $ python3 verify-evidence.py sample-evidence-bundle.json note segment anchor: anchored to prior chain_hash 8c6b5de41456... note poa scheme: sentinel-poa-v2 RESULT: VERIFIED — all 17 checks passed.
The verifier shares no code with us
That is the entire point. A verification tool built on our own hashing functions would only prove that we agree with ourselves — a bug in how we canonicalise data before hashing would be invisible to it, because the same bug would sit on both sides of the comparison.
verify-evidence.py imports nothing from Sentinel. It reimplements the scheme from the written specification below. If the spec is wrong or incomplete, the two implementations disagree, and the disagreement is the finding. A test in our suite asserts both accept genuine bundles, which makes the specification staying correct a build requirement rather than a promise.
You are welcome to skip our script entirely and write your own from the spec. Several of the checks below take about twenty lines.
The specification
Three primitives define the whole scheme. Every hash in every bundle comes from these.
canonical_json(payload) = json.dumps(payload, sort_keys=True,
separators=(",", ":"), default=str)
payload_hash = SHA256( canonical_json(payload) )
chain_hash = SHA256( previous_hash | payload_hash | event_id | created_at )
All three canonicalisation details are load-bearing. Sorted keys mean key order cannot change a hash; the separator setting removes incidental whitespace; default=str coerces datetimes and enums. Change any one and every hash below differs.
Proof of Agent
Two canonical strings bind a decision to the agent and rule set that produced it. Both ship inside the bundle so you can recompute the digests.
| Binding | Canonical string |
|---|---|
registry_binding |
agent_id | agent_type | template_version | comma_sorted_rule_ids |
evaluation_binding |
decision_id | agent_id | applied_verdict | computed_verdict | mode | rules_evaluated | rules_triggered | comma_sorted_passed_rules | comma_sorted_failed_rules |
combined_digest |
SHA256 of the two binding hex strings concatenated, no separator |
The field list depends on the scheme value, which is carried in every proof so older proofs stay verifiable. The current scheme is sentinel-poa-v2.
The check that actually matters
Recomputing those digests only proves a proof is internally consistent. A perfectly valid proof lifted from a different decision would sail through it.
So parse the canonical strings and assert their fields match the decision record: the decision id, the agent, both verdicts, the governance mode, the rule counts and the sorted rule id lists. That is what ties a proof to the decision in front of you. Our verifier does this; if you write your own, do not skip it.
Verification procedure
Recompute every payload hash
For each event, hash the canonical JSON of its payload and compare against the stored payload_hash. This is what catches an edited decision.
Recompute every chain hash
Using the event’s own previous_hash, its payload hash, its id and its timestamp. This catches someone who edited a payload and recomputed its hash to match — the chain hash still disagrees.
Check the links
Each event’s previous_hash should equal the preceding event’s chain_hash. See the note on segments below before treating a gap as a failure.
Verify the Proof-of-Agent bindings, then cross-check them
Recompute both bindings and the combined digest, then assert the canonical strings describe this decision and this agent.
Compare the readable record against the hashed payload
A bundle presents the decision twice: once as a convenient object and once inside the chained payload. Only the payload is covered by the hashes, so an edit to the readable copy alone would otherwise pass every check above.
A bundle is a segment, and gaps are not tampering
An evidence bundle holds the events for one decision — a slice of the log, not the whole thing. Its first event links to whatever preceded it, which is almost never the start of the chain, and other events may sit between the ones you were given.
Each event’s own hashes are self-contained and still prove integrity, so treat a gap as adjacency unknown rather than a failure. We got this wrong ourselves: requiring every bundle to start at the beginning of the log meant every export after the very first decision reported as invalid. It is fixed, and it is called out here because you would otherwise hit it and conclude the wrong thing.
What verification proves
A verification tool that overclaims is a liability in the room this is built for. Here is the honest split.
Established from the bundle alone
- No payload has been altered since it was hashed.
- Each chain hash is consistent with its payload, id and timestamp.
- The Proof-of-Agent digests are correctly derived, and the canonical strings describe this decision and this agent — not another decision with a valid proof.
- The readable decision matches the payload that was actually hashed.
Not established, and not establishable
- That the segment belongs to your real chain. A bundle is a slice; take its anchor hash and locate it in the full log.
- That no events were deleted. Append-only storage is tamper-evident, not tamper-proof: it detects modification of retained events. It does not by itself prevent deletion. Put the log on immutable object-lock storage if you need that property — we do not claim to be WORM storage.
- Anything about the decision’s correctness. This verifies integrity, not judgement.
Prove to yourself that it catches tampering
A verifier that always prints VERIFIED is worse than no verifier, because it launders bad evidence. Change one field in the sample and run it again.
$ python3 -c "
import json
b = json.load(open('sample-evidence-bundle.json'))
b['events'][0]['payload']['verdict'] = 'APPROVE'
json.dump(b, open('tampered.json','w'))"
$ python3 verify-evidence.py tampered.json
FAIL event evt-c1e298cc295c payload_hash
FAIL decision record vs chain
RESULT: FAILED — 2 of 17 checks did not pass.
$ echo $?
1
Our own test suite runs ten such attacks — payload edits, hash recomputation, proof substitution, reattributing a decision to a different agent, dropping a failed rule, downgrading the proof scheme, breaking a chain link. All are caught, most by two independent checks.
Design partners
Bring your security questionnaire.
If you run this and get a result you did not expect, or you think our specification is wrong, we want to hear about it. That is a better first conversation than a demo.