AegisAgent Action Receipt — Open Format Spec v0¶
Status: draft v0 · Date: 2026-06-02 · Extended: 2026-06-05 (§7, SOC evidence spine) · Canon scheme: aegis-jcs-1
Reference implementation: sdk-python/aegisagent/receipts.py (verifier) · tests: sdk-python/tests/test_receipts.py
SOC architecture: AegisAgent_Agent_SOC_Design.md
An action receipt is a tamper-evident record of one AI-agent action decision. Receipts are the verifiable evidence layer behind AegisAgent's compliance story (SOC 2 / EU AI Act Article 14) and are designed as an open format any gateway can emit and any third party can verify — independently of the runtime that produced them. They are also the evidence spine of the integrity-anchored Agent SOC: every SOC alert and incident references the receipt_hash chain covering its events, which is what makes a SOC incident timeline provable rather than merely logged (§7). The format below is locked and byte-exact — do not change it without bumping the canon scheme and re-pinning the cross-language corpus, or both the fail-closed guarantee and the SOC's evidence integrity break.
1. Canonicalization (aegis-jcs-1)¶
All hashing is over a canonical JSON string:
- object keys sorted by Unicode code point
- compact separators (no spaces):
,and: - raw UTF-8 — no
\uXXXXescaping of non-ASCII - non-finite floats (
NaN/Infinity) are invalid and rejected nullfor absent values
This is the same scheme used for action_hash (see tests/canonical_action_vectors.json). Implementations MUST be byte-identical across languages.
2. Receipt fields¶
| Field | Type | Notes |
|---|---|---|
event_id |
string | unique receipt id |
ts |
string | RFC 3339 UTC timestamp |
agent_id |
string | acting agent |
user_id |
string|null | initiating user, if any |
run_id / trace_id |
string|null | correlation |
tool / action / resource |
string|null | the tool call |
source_trust |
string | one of the 6 trust levels |
decision |
string | allow | deny | require_approval | rejected_on_swap | approved | ... |
approver |
string|null | approver identity, if applicable |
action_hash |
string|null | SHA-256 of the canonical action (approval binding) |
executed_hash |
string|null | hash of what actually executed (for tamper attempts) |
input_hash / output_hash |
string|null | hashes, never raw payloads |
prev_receipt_hash |
string | link to the previous receipt ("" for genesis) |
receipt_hash |
string | SHA-256 of the canonical body (see §3) |
canon_version |
string | canonicalization scheme tag (e.g. aegis-jcs-1); self-describing, additive metadata — NOT in the hashed body |
signature |
string|null | optional Ed25519 signature over receipt_hash; additive — NOT in the hashed body |
signer_public_key |
string|null | signer public key (hex) for signature; additive — NOT in the hashed body |
Additional body fields are permitted and are included in the hash. The three metadata fields above (canon_version, signature, signer_public_key) sit outside the canonical body and are excluded from receipt_hash, so they can be added without changing the byte-parity-locked hash. Secrets MUST NOT appear — store hashes, not raw payloads.
3. Hash chain¶
Let body = the receipt object excluding receipt_hash and including prev_receipt_hash.
Because prev_receipt_hash is inside the hashed body, both field tampering and re-linking are detectable. The first receipt in a chain uses prev_receipt_hash = "" (genesis).
4. Verification algorithm¶
A chain [r0, r1, ...] is valid iff, walking in order with prev = "":
recompute(r_i) == r_i.receipt_hash— integrity of each receiptr_i.prev_receipt_hash == prev— chain linkage- set
prev = r_i.receipt_hash
Any mismatch ⇒ invalid (fail closed). Reference: verify_chain() / verify_receipt().
5. Worked example (genesis receipt)¶
{
"event_id": "rcpt_svc#482",
"ts": "2026-06-02T12:00:00Z",
"agent_id": "coding-agent-prod",
"user_id": "lavkush",
"tool": "github", "action": "merge_pull_request", "resource": "payments-service#482",
"source_trust": "untrusted_external",
"decision": "rejected_on_swap",
"approver": "platform-lead",
"action_hash": "sha256:9af1...", "executed_hash": "sha256:1c20...",
"prev_receipt_hash": "",
"receipt_hash": "<SHA-256 of canonicalize(everything above except receipt_hash)>"
}
6. Implementation status (2026-06-02)¶
- Done & verified (Python): format + hash chain + reference verifier (
seal_receipt,seal_chain,verify_receipt,verify_chain); CLIaegis-verify-receipts/python -m aegisagent.verify_receipts; shared corpustests/receipt_chain_vectors.json(pins exactreceipt_hashvalues). 25/25 SDK tests incl. tamper/broken-link/reorder/non-ASCII/CLI. Canonicalization centralized inaegisagent/canon.py. - Done, pending
cargoverification (Rust gateway): - cross-language parity lock —
routes.rs::receipt_chain_matches_shared_corpusreproduces everyreceipt_hashintests/receipt_chain_vectors.json(canonical_value_string=aegis-jcs-1). - gateway emission — every
/v1/authorizedecision writes a hash-chained receipt into theaction_receiptstable (emit_action_receipt, chained per tenant viarowidhead); body fields perroutes.rs::receipt_body_value(excludesreceipt_hash+ volatilecreated_at). GET /v1/receipts/:id/verify— recomputes the hash and returns{verified, receipt_hash, recomputed_hash, prev_receipt_hash}. Test:authorize_emits_verifiable_receipt.- Next (Rust): single-use
nonce+ consume step for full replay defense (T-A3). Enterprise: KMS-backed signing / transparency-log anchoring; chain-head selection should move into a transaction to be race-safe under concurrency.
7. The receipt as the SOC evidence spine¶
The same hash chain that proves human oversight for compliance is what makes the integrity-anchored Agent SOC (see AegisAgent_Agent_SOC_Design.md) different from a generic SIEM: its incident timelines are provable, not just recorded.
7.1 Receipt ↔ Agent Security Event (ASE)¶
When the gateway decides, it (1) writes a receipt to the chain and (2) emits an ASE onto the async SOC bus. The ASE carries the decision and the integrity linkage:
// fields the ASE copies from the receipt for tamper-evident correlation
"integrity": {
"action_hash": "sha256:...", // frozen action (approval binding)
"decision_id": "uuid",
"receipt_hash": "sha256:...", // this event's chain link
"prev_receipt_hash": "sha256:..." // previous link
}
The SOC never re-hashes payloads or trusts raw logs; it correlates on receipt_hash/prev_receipt_hash, so a forged or replayed event (T-D5) fails to chain.
7.2 Provable incident timelines¶
A correlated incident stores evidence_receipts: [receipt_hash, ...] — the ordered chain links covering its events. An investigator (or auditor) runs the §4 verification over those links; if every recompute(r_i) == r_i.receipt_hash and r_i.prev_receipt_hash == prev, the timeline is proven untampered. This is the SOC Console's one-click "verify incident."
7.3 Chain integrity as a detection¶
A break in the chain (tampered from /verify, a missing link, or a re-linking) is itself a P1 SOC detection (receipt-chain-broken) — evidence tampering (Threat Model T-C1) surfaces as an alert, not just a failed audit.
7.4 Redaction (unchanged, reaffirmed for the SOC)¶
Because the SOC consumes receipts/ASEs, the §2 rule is doubly important: secrets MUST NOT appear — store input_hash/output_hash, never raw payloads. The SOC, its indexer, and the sandboxed RCA narrator therefore never hold plaintext secrets (closes T-D7 exfiltration via the RCA LLM).
Invariant: the receipt chain is simultaneously the compliance evidence and the SOC evidence. One tamper-evident structure serves both. Keep it byte-exact (§1) and append-only; it is the single most load-bearing data structure in AegisAgent.