IETF Internet-Draft for AI Compliance Receipts
The canonical wire-format spec for signed AI agent compliance receipts.
Asqav implements draft-marques-asqav-compliance-receipts, an IETF Internet-Draft published under the Independent Submission stream. The draft standardises the wire format for signed AI agent action receipts: each receipt commits to an action, a decision, and an anchor binding, with hash-chained integrity per agent.
The profile maps the receipt format onto nine regulatory regimes, namely the EU AI Act, DORA, NYDFS Part 500, NIST AI RMF, Colorado AI Act, Texas TRAIGA, CIRCIA, the HIPAA Security Rule, and SEC 17a-4. Read the canonical draft on IETF Datatracker: https://datatracker.ietf.org/doc/draft-marques-asqav-compliance-receipts/.
At a glance
- Signature algorithm: ML-DSA-65 (NIST FIPS 204)
- Anchor types:
rfc3161(qualified timestamp from an EU Trusted List QTSP when the external witness is reachable) andopentimestamps(Bitcoin commitment) - Canonicalisation: JCS (RFC 8785)
- Hash chain: per-agent, all-zero SHA-256 genesis, JCS-canonical signed-payload scope
- Retention floor: 5 years (DORA Art 17), regime-specific extensions
- Reference implementation: open-source (Asqav cloud, Python SDK, TypeScript SDK, MCP server)
Normative clauses (the MUST/SHOULD set)
The 17 pinned clauses below summarise the binding requirements of the draft. Each one is a plain-English gloss. The canonical text lives on Datatracker.
Every receipt carries a top-level anchors[] array whose entries are {type, value} objects. The type field acts as the discriminator and selects the schema for value.
The type field MUST be either rfc3161 or opentimestamps. Any other value is a hard rejection at verification time.
Digest-covered fields MUST NOT contain IEEE-754 floats. Numeric values that need decimal precision are encoded as strings to keep the JCS canonicalisation byte-stable across language runtimes.
A receipt with decision = observation MUST NOT pair with type = protectmcp:decision. Observation is a non-enforcement vocabulary and pairing it with a decision-type receipt is a false-attestation hazard.
Receipts whose decision is deny or rate_limit MUST carry a reason string. A bare denial without a reason is rejected.
The issued_at timestamp MUST be within a 300-second forward-only window of the verifier's clock. Receipts dated in the future beyond that bound are rejected. Backward skew is tolerated to accommodate replay.
The issuer_id field on the receipt MUST be byte-equal to the kid field inside the signature envelope. A mismatch is a hard rejection.
The payload_digest field is an object with two members, algorithm and value. A bare digest string is not accepted on the wire.
The previousReceiptHash field commits to the JCS-canonical bytes of the prior receipt's signed payload, and only those bytes. Headers and the signature envelope are out of scope for the chain.
The first receipt emitted by an agent uses an all-zero 32-byte SHA-256 as its previousReceiptHash. Verifiers detect chain start by exact byte-match against the zero vector.
An anchor MUST be present on every signed receipt, with type set to either rfc3161 or opentimestamps. A receipt without an anchor is incomplete and is rejected.
When the chosen anchor is opentimestamps, the calendar-pending proof MUST be upgraded to a Bitcoin attestation within seven days. Beyond that window the proof is treated as incomplete.
The counterparty_binding object lands in the cloud signing relay, the Python SDK, the TypeScript SDK, and the conformance vectors as one atomic vocabulary. The same token shape applies on all four surfaces.
Legal Entity Identifiers carried on receipts are 20 characters and pass ISO 7064 mod 97-10 checksum validation. Malformed LEIs are rejected at envelope parse time.
Verifiers MUST accept both base64 and base64url encodings on the envelope_hash field. The signer is free to pick either. The verifier normalises before comparison.
When chain emission is intentionally suppressed (for example, during key rotation or maintenance windows), the receipt SHOULD carry the reason code chain_emission_blocked. This keeps the chain gap auditable.
The action_type string follows the namespace:verb shape, with no spaces. The advertised vocabulary lives at /.well-known/governance.json on the issuer.
Reference implementations
Four open-source implementations ship the spec today. They share conformance vectors and stay in lockstep.
| Implementation | What it covers |
|---|---|
| Asqav cloud | Signing relay, audit pack, multi-region anchors. Reference verifier. |
| Python SDK | PyPI: pip install asqav. Source at github.com/jagmarques/asqav-sdk. |
| TypeScript SDK | npm: npm install @asqav/sdk. Byte-identical receipts with the Python SDK. |
| Claude Code hook | Enforcement gateway for agent tool calls. Run asqav hook pretool and asqav hook posttool from the SDK. |
Conformance vectors
Conformance test vectors covering counterparty_binding, anchor entry shapes, IEEE-754 float gating, and reason-required deny/rate_limit live in tests/conformance/test_ietf_vectors.py of the asqav repo. Re-running them against any verifier produces a binary PASS or FAIL for spec conformance.
Co-implementors and signatories
Open to co-implementors. If you ship a verifier or signer against this draft and want to be listed, open an issue on github.com/jagmarques/asqav-sdk.