Verify an evidence package without Vouchline
A tool that says about itself "the data is consistent" is not evidence. Vouchline's decision register is built so that you — or your auditor — can recompute it with nothing but the downloaded package and a few lines of code.
What you need
- the Package (JSON) file of the review, downloaded from Evidence package → Package (JSON)
- Node.js 18 or newer
- the script
verify-package.mjs— one file, no installation, no network access, no access to Jira
The script shares no code with the app. You can read it top to bottom in a few minutes, and you should.
Run it
node verify-package.mjs vouchline-2026-10-11T09-10-07-913Z-package.json
Vouchline evidence package — independent verification
review Q4 2026 privileged access review (2026-10-11T09-10-07-913Z)
site example.atlassian.net
[ OK ] chain 7 links, each hash recomputed
[ OK ] order links are numbered without a gap; the closing link is the last one
[ OK ] items 5 decided item(s) match their last link; 14 without a decision
[ OK ] closing closing totals equal the 5 decision(s) recorded before it
[ OK ] seal data caveats match the seal; snapshot header matches the seal
final chain hash b3e2870cbf6c46d6f26b568b4753616758cdb8f40d113e42359d2ab08bb2e51d
RESULT: every check passed
The exit code is 0 when every check passes and 1 otherwise.
What each check proves
| Check | What it establishes |
|---|---|
| chain | Every link's hash is the SHA-256 of its own content, and every link points at the hash of the one before it. Changing, removing or reordering any decision breaks it. |
| order | Links are numbered 1…N with no gap, and nothing was recorded after the link that closed the review. |
| items | Each decision shown on an item — decision, time, justification, author, subject — is the last link recorded for that item. No link is left without its item. |
| closing | The totals written into the closing link equal the decisions that precede it. |
| seal | The data caveats (snapshot state, collector errors, rules not evaluated, thresholds, the Jira site) and the snapshot header are the ones hashed into the first link when the review was opened. |
– instead of OK means there was nothing to check — for example, the closing totals of a review that is still open.
Do it by hand
For one link, without the script:
- Take the link from the
journalarray of the package. - Remove the field
hash. - Serialise the rest as JSON with object keys sorted ascending at every level. Arrays keep their order. No whitespace.
- Encode as UTF-8 and compute SHA-256. Write it as lowercase hex.
- The result must equal the removed
hash, and the link'sprevHashmust equal thehashof the link before it. The first link'sprevHashis 64 zeros.
The journal CSV is a readable extract. It does not carry every field of a link, so it cannot be used for this computation — use the JSON package.
What this does not prove
- That the snapshot was true when it was taken. A hash shows that data has not changed since; it cannot show that the data was right.
- That the package was not rebuilt from scratch. Whoever controls the storage could recompute a whole chain. The defence is an earlier copy: compare the final chain hash printed by the script with the one in a PDF report or package you downloaded before. Download the package when you close a review and keep it with the rest of your audit evidence, outside Jira.
- Who the people are. The package identifies accounts by their Atlassian account ID. Display names appear in a separate
accountNamessection, read from Jira at the moment the package was built; Vouchline does not store them and they are not covered by any hash.
What is in the package
| Section | Content |
|---|---|
journal | The decision register — the chain of links, exactly as stored. |
items | The review items with their current decision. |
review | Name, period, state, owner, counts, and dataCaveats. |
snapshotHeader | The header of the snapshot the review was built from. |
verificationByTheApp | What the app computed when it built the package. Not evidence — recompute it. |
accountNames | Display names read from Jira when the package was built. Not stored by Vouchline, not covered by any hash. |
A link carries seq, prevHash, hash, and for a decision: reviewId, itemId, rule, subjectId, subjectType, previousDecision, decision, justification, by (account ID) and at (UTC). Decisions are approved, to_revoke and deferred. The first link has event: "review opened" and carries dataCaveatsHash and snapshotHeaderHash; the last link of a closed review has event: "review closed" and a summary.
The first link carries dataCaveatsHash and snapshotHeaderHash: the SHA-256 of review.dataCaveats and of snapshotHeader exactly as they appear in the package, serialised the same way as a link. Every message in them is a { key, params } pair — for example { "key": "collector.partialScan", "params": { "done": 22, "total": 23 } }.