Skip to main content

Why a package

An agent that works a regulated case - onboarding a business, dispositioning a screening hit - leaves its trail across several systems: the grant that authorised it, the provider calls it made, the policy that scored the result, the person who approved the decision. An auditor asking “why was this business referred, and who approved the decline?” should not have to query all of them, or trust any of them. An evidence package is one JSON document per case that answers those questions on its own: The format is specified in spec/evidence-package.md, with a JSON Schema in spec/evidence-package-1.0.schema.json.

How tamper evidence works

  1. Canonical bytes. A package is only valid as its own RFC 8785 canonical JSON, so every byte matters.
  2. A hash chain. Each entry carries the hash of the entry before it and of itself. Change an entry and verification names that entry and shows the expected and actual hash.
  3. An anchor. On export the auth service writes the package root into the tenant’s audit chain as a platform-only entry and embeds that entry in the package. Tenants cannot write such entries through POST /v1/audit/log.
  4. A signature. The service signs the root and the anchor with its platform key, so anyone with the service JWKS can check the package came from the service.

What verification proves

Entries marked tenant-asserted are statements the tenant’s platform made to the service when they were recorded; the service’s signature proves it received them then, not that they are true. Grants, decisions, consumptions and revocations come from the service’s own records.

Privacy by default

A default package does not let a reader recover or confirm who was involved, what the case is about, which provider records were fetched or what the tool inputs were, and two packages cannot be joined. Five classes are keyed per case with HMAC unless disclosed:
  • principal and approver identifiers, and the case subject: pseudonyms (pz:...);
  • upstream record identifiers: pseudonyms, stable within the case so citations still line up;
  • tool input and output content digests: keyed digests (hmac-sha256:...), so equal inputs in two cases do not match.
A decision grant’s own action_hash is an unkeyed hash over the clear subject; putting it next to a pseudonymised subject would let anyone confirm a guess, so such packages carry a keyed action_ref instead. Memos, rationales and rule reasons appear only as digests. Disclosure (for example "disclose": ["record"] so an auditor can look up the provider records) is allowed only for developers an operator lists in EVIDENCE_DISCLOSURE_DEVELOPER_IDS. The case owner, holding the tenant key, can always recompute a keyed value to confirm it.

Verifying a package

Get the root and anchor from somewhere other than the package - the export response headers you stored, or the tenant audit log (GET /v1/audit/entries?action=evidence.package_exported) - and the service JWKS from /.well-known/jwks.json. Then:
(grantex-evidence verify ... with the Python SDK.) It exits 0 only if every check passes and prints the trust basis:
On any break it prints the failing link and exits 1 (usage errors, such as a missing --root, exit 2):
In code:

Producing evidence

A platform records evidence as the case runs with POST /v1/evidence/cases/{caseId}/records: run context, tool calls with their upstream record identifiers, policy evaluations, dispositions and recommendations. Each record is checked when it is written - including its references to earlier records and its grant - so an accepted case can always be exported. Records are idempotent on their own ids, and mistakes are voided with POST /v1/evidence/cases/{caseId}/void, never deleted. Decisions come from the auth service’s decision grants. When the case is decided, POST /v1/evidence/cases/{caseId}/export returns the package. Everything is behind the EVIDENCE_EXPORT_ENABLED flag, off by default.

What it does not prove

The package proves what was recorded, by whom and when, and that nothing changed since. It does not prove the platform recorded every call it made; that rests on recording at the enforcement point and reconciling with the audit log. Keyed values are not anonymous to whoever holds the tenant key.
Last modified on September 15, 2026