Skip to content

Governance Receipts and Audit

SMEPro IOS edited this page Oct 15, 2026 · 1 revision

Governance Receipts and Audit

The idea in one paragraph

Most systems can tell you what they did if you trust them. IGL is built so that you do not have to. Every projection and every governed answer comes with a receipt signed by the service, and every change is written to an audit chain where each entry is hashed together with the one before it. A receipt can be checked by anyone with the public key. A chain can be re-verified from the outside. Neither can be altered after the fact without the alteration being visible.

Receipt

The receipt

A governance receipt is the governance object returned with projections, intelligence answers and other governed results, and it can be fetched again later with GET /audit/receipts/{receiptId}. The signed part contains:

Field Meaning
receiptId Unique id, also the key for fetching it later
actor The identity, organization and roles the decision was made for: the credential's identity by default, or the actor a request declares for authority and policy evaluations, runs and intelligence operations, which must be well formed identity and role ids
authorityDecision The decision object: its effect (allow, deny, conditional or escalate), reason codes, required approvals and decision id
policiesEvaluated The ids of the policies consulted to reach it
evidenceIds For governed answers, the evidence records the answer relied on
outputHash SHA-256 of the canonical JSON of what was returned (for a projection, the admitted nodes)
createdAt When it was issued

Around that signed part sit signer (the key id, igl-production or igl-sandbox), alg (Ed25519), publicKey (the signer's public key in SPKI form) and signature.

Verifying a receipt without trusting the service

The signature covers the string IGL-RECEIPT-v0.2: followed by the canonical JSON of the signed part. Canonical means object keys sorted, no whitespace, arrays in order. The public key is published at https://auth.id-me.ai/.well-known/jwks.json for production and https://sandbox.id-me.ai/.well-known/jwks.json for the sandbox, so a verifier checks two things: the signature is valid for the embedded publicKey, and that publicKey is the one in the JWKS. The code is in Quickstart: the API; it is fifteen lines of Node.js with no dependencies.

The conformance suite performs this exact check on every deploy. If a build ever produced receipts the published key could not verify, it would not reach production.

The audit chain

Every write and every governed read appends an event to the tenant's audit chain: identity.created, projection.served, policy.evaluated, key.issued, key.revoked, consent.revoked, outcome.recorded, agent.run_completed, platform.bootstrapped and the rest. Each event stores previousHash and its own eventHash, computed as:

eventHash = sha256(previousHash + canonical({ type, body }))

The table has database triggers that reject updates and deletes, so even an operator with database access cannot quietly rewrite history; they can only append, and an appended fake would have to match a hash it cannot produce.

GET /audit/events searches a tenant's chain by time, actor, resource and event type, and returns the hashes, so a client can re-verify any stretch of the chain itself. The small script docs/examples/verify-audit-chain.mjs does that against a live tenant.

Interactions: closing the loop with the model

A projection tells an assistant what it may see. POST /audit/interactions records what the assistant then did with it: completed, refused, proceeded_on_truncated, proceeded_on_empty or escalated, with the model name and the receipt id of the projection it used. That entry joins the same chain, so a reviewer can walk from "what did the model receive" to "what did it do" to "what was the outcome" without leaving the audit log. The record_interaction MCP tool is the same operation for assistants.

The runtime's version of the same thing

On your own machine the journal in src/store.js is the chain. It is file backed, hash chained, and refuses to load if any entry was changed. Two features go beyond the platform today:

Merkle inclusion proofs. The journal publishes a root, and inclusionProof(seq) produces a proof that one entry belongs to the journal with that root. An auditor can confirm a single disclosed entry without seeing the rest. docs/examples/verify-journals.mjs shows it.

Runtime attestation. Every trace and head receipt signed by src/sign.js carries runtime, a SHA-256 digest of the runtime's own source manifest, inside the signed envelope. Which build produced a receipt is settled by recomputation, not by trust.

Webhooks

Tenants can register HTTPS endpoints for graphUpdated, outcomeRecorded and approvalRequired. Each delivery carries x-igl-event, x-igl-delivery and x-igl-signature: t=<unix seconds>,v1=<hmac-sha256(secret, t + "." + body)>, where the secret was returned once at registration. Verify the signature and reject old timestamps, as you would for any payment provider's webhooks. Three attempts are made.

What to tell an auditor

Three sentences. Every decision this system made is in a signed receipt whose key is public. Every change is in a chain the database itself refuses to edit. You can check both from your own laptop, and here is the fifteen-line script.

Clone this wiki locally