A generated document is only useful to an auditor if it can be checked, not just trusted. /verify re-runs the checks a generated pack’s own citations imply against your live data, and reports the result honestly — including admitting when it couldn’t check something.
What it checks
Click Verify next to any completed pack on the Compliance page, or call GET /v1/projects/:id/evidence-packs/:packId/verify directly. Two independent checks run:
- Content integrity — the stored document is read back and re-hashed with SHA-256. If the recomputed hash doesn’t match the hash recorded at generation time,
sha256Matches is false: the stored bytes have changed since the document was generated (or the object couldn’t be read at all).
- Citation survival — every source record the document cited is re-resolved against your live data, scoped to the pack’s project (or organization, for org-level sources like membership). A citation whose record is gone is reported as a divergence.
Result fields
unverifiableRefs — read this before you trust a clean result
Not every citation in a document points at a single database row that can be looked up again later. Two kinds of citation are structurally unverifiable:
- ClickHouse aggregates. A “models used” citation, for example, doesn’t name one row — it names a synthetic key over an aggregate (a model, summed across a whole period). There’s no single row to re-fetch, and re-running the aggregate would just compare a number the document already printed against itself, proving nothing about whether the underlying data changed.
- Certain change-timeline events. Prompt-deploy, agent-lifecycle, and externally-reported change events carry composite ids from their own source systems rather than an audit-log row. Audit-sourced change events are checkable — they resolve as ordinary audit-log citations for free.
These citations are excluded from evidenceStillPresent entirely — not counted as verified, not counted as failed. They’re reported separately, in unverifiableRefs, specifically so a document with a hundred citations, ninety of them structurally uncheckable, doesn’t come back reading like “the whole document checks out” just because the ten checkable ones passed. You — or your auditor — need to see that ninety citations were never re-checked at all, not just that the ten that could be were fine.
evidenceStillPresent: true means every checkable citation still resolves. It does not mean every citation in the document was checked. Always read unverifiableRefs alongside it: a pack reporting checkedRefs: 3 and unverifiableRefs: 340 passed a much smaller check than one reporting checkedRefs: 340 and unverifiableRefs: 3 — even though both can legitimately report evidenceStillPresent: true.
missing vs. wrong_project
When a cited record doesn’t resolve inside the pack’s own project (or org), /verify does one more lookup — unscoped, across every tenant — before deciding how to report the divergence:
missing — the record doesn’t exist anywhere. It was deleted.
wrong_project — the record still exists, just not under this project anymore.
Reporting these as two distinct reasons means “the guardrail was deleted” and “the guardrail moved to another project” don’t look like the same failure.
Failure modes that don’t error
/verify never throws for a data condition — a deleted record, a deleted storage object, or a pack that never finished generating are all results, not exceptions:
- A pack whose status isn’t
completed (or that has no stored object or hash yet) returns a result with everything false, 0, or empty, rather than a partial or misleading pass.
- A resolver failure — a transient database error while re-checking one source — is not treated as proof those records are gone. Those citations move into
unverifiableRefs for that check instead of being reported as false divergences.
Next steps