Raucle Provenance Receipt — Specification v1
| Field | Value |
|---|---|
| Status | Draft 1 |
| Identifier | raucle-provenance-receipt/v1 |
| First published | 2026-05-14 |
| Editor | Raucle. |
| License | CC-BY 4.0 (this document) — implementations may be any license |
| Permanent URL | https://raucle.com/spec/provenance/v1 |
1. Abstract
This document specifies the Raucle Provenance Receipt v1 — a compact, content-addressed, cryptographically signed envelope that records a single step in a Large Language Model (LLM) workflow. Receipts compose into a Merkle DAG enabling end-to-end provenance verification across multi-agent, multi-tool, retrieval-augmented, and guardrail-mediated workflows.
The format is the LLM-equivalent of Sigstore for software artifacts and SLSA for build provenance — applied to AI inference, tool calls, and agentic orchestration.
2. Goals and non-goals
2.1 Goals
- Verifiable causality. Given any output receipt, a verifier with the relevant public keys MUST be able to walk back to all root receipts and prove that every intermediate step is signed, well-formed, and consistent with its declared parents.
- Tamper-evident. Any modification to a receipt's payload after signing MUST invalidate either its signature or its content-addressed hash.
- Privacy by default. Receipts MUST NOT contain raw prompt or output content in the v1 normative payload — only their hashes. Implementations MAY add opt-in content fields under a vendor-specific extension namespace.
- Composable across vendors. Two independently-built implementations MUST produce byte-identical signed payloads given the same inputs (canonical JSON).
- Cheap to emit and verify. Signing and verification MUST complete in < 1 ms per receipt on commodity hardware (Ed25519, ~64-byte signatures).
- Stable on disk. A receipt persisted today MUST verify decades from now using only the published spec and the issuer's public key.
2.2 Non-goals
- Confidentiality. Receipts are not encrypted. Transport-layer or storage-layer encryption is the deploying party's responsibility.
- Revocation transparency. v1 does not specify a revocation log. Public-key revocation MAY be layered by the deploying party using existing PKI (e.g. CRL, OCSP) or future Raucle revocation extensions.
- Inference attestation. Receipts describe what was claimed to happen, not what cryptographically happened inside the model. Hardware-attested inference (TEE / ZK-of-inference) is out of scope for v1 but MAY be layered via header extensions.
3. Terminology
Key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are used as defined in RFC 2119 and RFC 8174.
| Term | Definition |
|---|---|
| Receipt | A single signed envelope describing one step in an LLM workflow. |
| Agent | An autonomous or semi-autonomous identity (human-supervised or fully automated) that emits receipts. Identified by an agent_id and an Ed25519 public key. |
| Operation | The kind of step a receipt represents (see §6). |
| Parent | A receipt whose hash is cited in a later receipt's parents field. Establishes the causal DAG. |
| Root | A receipt with an empty parents array. MUST be of operation type user_input or a vendor-specified root type. |
| Chain | An ordered sequence of receipts persisted together (typically JSON Lines). |
| Capability statement | A separately-signed declaration of what models, tools, and data classifications an agent is permitted to invoke. Distributed alongside the public key. |
| Taint | A set of string tags propagated through the DAG marking the provenance of untrusted, external, or otherwise non-pristine data sources. |
| Sanitisation | A specially-marked operation that explicitly removes named taint tags from descendants. The only operation permitted to shrink the taint set. |
4. Wire format
A receipt is encoded as a compact JWS (RFC 7515 §3.1):
<base64url(JOSE header)>.<base64url(payload)>.<base64url(signature)>
Implementations MUST use alg = EdDSA over Ed25519 keys (RFC 8037).
Implementations MUST NOT accept alg = none. Implementations MUST NOT accept any algorithm other than EdDSA for v1 receipts.
4.1 JOSE header
The header is a JSON object with the following keys.
| Field | Type | Required | Description |
|---|---|---|---|
alg |
string | Yes | MUST be the literal string "EdDSA". |
typ |
string | Yes | MUST be the literal string "provenance-receipt/v1". |
kid |
string | Yes | The agent_key_id (see §5). 16 lowercase hex characters. |
crit |
array of string | Yes | MUST be exactly ["raucle/v1"]. Forces JWT-compatible libraries to either understand the Raucle profile or reject the token, preventing accidental acceptance as a bearer auth token. |
raucle/v1 |
string | Yes | MUST be the literal string "provenance". |
Verifiers MUST reject any receipt whose JOSE header contains additional keys not listed in this section, OR whose crit array contains values other than "raucle/v1" that the verifier does not understand. Implementations MAY define their own header extensions under a vendor-specific namespace prefix (e.g. x-acme-*) provided they also appear in crit.
4.2 Payload
The payload is a JSON object serialised in canonical form (see §4.3) with the following fields.
| Field | Type | Required | Description |
|---|---|---|---|
iss |
string | Yes | MUST be "raucle-detect/provenance" for receipts emitted by raucle. Other implementations MUST use a stable issuer identifier they control. |
typ |
string | Yes | MUST be "provenance-receipt/v1" (mirrors the JOSE header). |
iat |
integer | Yes | Unix timestamp in seconds when the receipt was issued. |
agent_id |
string | Yes | The emitting agent's stable identifier (see §5). |
agent_key_id |
string | Yes | First 16 lowercase hex characters of sha256(agent_public_key_pem). |
operation |
string | Yes | One of the operation types defined in §6. |
parents |
array of string | Yes | Receipt hashes (see §7) of the immediate parents. MUST be sorted in lexicographic order. MAY be empty (root receipt). |
taint |
array of string | Yes | Set of taint tags (see §8). MUST be sorted in lexicographic order. MAY be empty. |
input_hash |
string | Conditional | Receipt hash format (see §7) of the operation's input. REQUIRED for user_input, model_call, tool_call, retrieval, guardrail_scan, sanitisation. |
output_hash |
string | Conditional | Receipt hash format of the operation's output. REQUIRED for model_call, tool_call, retrieval, agent_handoff, sanitisation, merge. |
model |
string | Conditional | Model identifier. REQUIRED when operation = "model_call". |
tool |
string | Conditional | Tool name. REQUIRED when operation = "tool_call" or "sanitisation". |
corpus |
string | Conditional | Retrieval corpus identifier, OR for sanitisation the prefix "removed:" followed by a comma-separated sorted list of removed taint tags. REQUIRED when operation = "retrieval" or "sanitisation". |
ruleset_hash |
string | Conditional | Hash of the active detection ruleset. REQUIRED when operation = "guardrail_scan". |
guardrail_verdict |
string | Conditional | One of "CLEAN", "SUSPICIOUS", "MALICIOUS". REQUIRED when operation = "guardrail_scan". |
tenant |
string | Optional | Multi-tenant SaaS identifier. MAY be omitted. |
Verifiers MUST reject receipts whose payloads are missing any field marked Required for the given operation type.
4.3 Canonical JSON
For both header and payload, prior to base64url encoding:
- Keys at every depth MUST be sorted lexicographically.
- Separators MUST be
,and:with no whitespace. - The byte encoding MUST be UTF-8.
- Strings containing non-ASCII characters MUST NOT be escaped (i.e.
ensure_ascii=Falsein Pythonjson.dumps, equivalent in other languages). - Numbers MUST use the minimal lossless representation (no trailing zeros on integers, IEEE-754 default for floats).
This is the same canonicalisation used by JCS, RFC 8785 restricted to the subset above; full RFC 8785 compliance is a forward-compatible MAY for v1.
4.4 Signature
The signature is computed as:
signature = Ed25519_sign(private_key, ASCII(b64url(header) + "." + b64url(payload)))
The wire form concatenates the three base64url segments with . separators (RFC 7515 compact serialisation).
5. Agent identity
An agent is identified by:
agent_id: a stable string matching the regex^agent:[a-z0-9][a-z0-9_\-./]{0,127}$. Theagent:prefix is mandatory. Implementations MUST validate this regex when issuing receipts and MAY validate it on verify.- An Ed25519 keypair. The private key is held by the agent; the public key is distributed via a capability statement (see §5.1).
- An
agent_key_id: the first 16 lowercase hex characters ofsha256(public_key_pem_pkcs_subject_public_key_info).
5.1 Capability statement
A signed JSON document distributed alongside (not inside) receipts. Verifiers consult capability statements to enforce that emitted receipts only invoke models, tools, and data classifications the agent is permitted to use.
Capability statement schema:
{
"agent_id": "agent:billing-summariser",
"key_id": "ab12cd34ef567890",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQ...\n-----END PUBLIC KEY-----\n",
"allowed_models": ["claude-sonnet-4-6", "gpt-4o"],
"allowed_tools": ["lookup_invoice", "send_email"],
"data_classifications": ["internal", "pii"],
"issuer": "raucle",
"issued_at": 1715625600,
"expires_at": 1747161600,
"signature": "<base64 Ed25519 sig over canonical-JSON of all fields except 'signature'>"
}
- An empty
allowed_modelslist MUST be interpreted as "unrestricted". The same applies toallowed_toolsanddata_classifications. Implementations that wish to forbid this default MAY require non-empty lists at issuance time. - The
issuerfield identifies who signed the statement. In OSS / self-signed mode the issuer SHOULD equal the agent's own key ID; in commercial / authority-signed mode it SHOULD reference a separate authority key. expires_atMAY be omitted for non-expiring statements. Verifiers SHOULD reject receipts issued after a statement'sexpires_at.
Capability statements MUST be signed with Ed25519 over the canonical-JSON form of all fields except signature. The signature is base64-encoded (standard alphabet, not URL-safe) in the signature field.
6. Operations
A receipt's operation field MUST be one of:
| Operation | Description | Roots a chain? |
|---|---|---|
user_input |
Untrusted input enters the graph (user message, fetched URL, retrieved document). Carries inherent taint. | Yes (only) |
model_call |
An LLM invocation. Records model, input_hash, output_hash. |
No |
tool_call |
A tool / function invocation. Records tool, input_hash, output_hash. |
No |
retrieval |
A retrieval-augmented-generation step. Records corpus, input_hash (query), output_hash (chunks). Adds an inherent rag:<corpus> taint unless the corpus is attested as trusted. |
No |
guardrail_scan |
A safety/security scanner inspected an input or output. Records ruleset_hash and guardrail_verdict. |
No |
agent_handoff |
One agent passed work to another. Records input_hash (target ID) and output_hash (handoff payload). |
No |
sanitisation |
Explicit removal of named taint tags. The ONLY operation permitted to shrink the taint set. The corpus field carries "removed:<tag1>,<tag2>,…". |
No |
merge |
N parents combine into one. Records output_hash. MUST have ≥ 2 parents. |
No |
Implementations MAY define vendor-specific operation types using a x-vendor-name:operation prefix. Verifiers that do not understand such an operation MUST reject the receipt (rather than ignoring it) to preserve safety properties.
7. Receipt hashes (content addressing)
A receipt's own hash — the value that descendants cite in their parents field — is computed as:
receipt_hash = "sha256:" + lowercase_hex(sha256(ASCII(compact_jws_string)))
i.e. the SHA-256 of the full compact-JWS wire form, prefixed with sha256:.
Hashes of arbitrary text and objects use the same prefix:
hash_text(s) = "sha256:" + lowercase_hex(sha256(UTF-8(s)))
hash_object(o) = "sha256:" + lowercase_hex(sha256(canonical_json(o)))
The sha256: prefix is mandatory and reserves namespace for future hash algorithm migrations.
8. Taint
taint is a sorted set of string tags marking the provenance of untrusted, external, or otherwise non-pristine data.
8.1 Reserved tags
| Tag prefix | Meaning |
|---|---|
external_user |
Originates from a user-controlled input boundary. |
rag:<corpus> |
Retrieved from the named corpus. Auto-applied by retrieval operations unless the corpus is explicitly attested as trusted. |
guardrail-scan:<target> |
Touched by a guardrail scan (input / output / tool_call). |
x-vendor-name:<tag> |
Vendor-specific tags. SHOULD NOT collide with reserved prefixes. |
Implementations MAY define additional unreserved tags. Verifiers MUST treat unknown tags as opaque strings.
8.2 Monotonicity invariant
For any non-sanitisation receipt R with parents P₁, P₂, …, Pₙ:
taint(R) ⊇ taint(P₁) ∪ taint(P₂) ∪ … ∪ taint(Pₙ)
A sanitisation receipt MAY shrink the inherited set, but ONLY by tags explicitly listed in its corpus field after the removed: prefix:
taint(sanitisation_R) = (taint(P₁) ∪ … ∪ taint(Pₙ)) \ removed_tags
where removed_tags is parsed from corpus.split(":")[1].split(",").
Verifiers MUST enforce this invariant. Violations indicate either tampering or a buggy emitter and MUST be reported.
9. Chains
A chain is an append-only sequence of receipts persisted together. The canonical on-disk format is JSON Lines (one receipt per line):
{"agent_id": "...", "agent_key_id": "...", "operation": "user_input", "parents": [], ..., "receipt_hash": "sha256:...", "jws": "<header>.<payload>.<sig>"}
{"agent_id": "...", "agent_key_id": "...", "operation": "guardrail_scan", "parents": ["sha256:..."], ..., "receipt_hash": "sha256:...", "jws": "<header>.<payload>.<sig>"}
Each line is a JSON object whose fields are the receipt payload PLUS two metadata fields:
| Field | Description |
|---|---|
receipt_hash |
The content-addressed hash of the JWS (see §7). MUST equal sha256(jws); mismatch indicates tampering. |
jws |
The full compact JWS wire form. The single authoritative signed artifact — the other fields are denormalised for convenient indexing. |
Verifiers MUST recompute the receipt hash from the jws and compare it to the stored receipt_hash; they MUST NOT trust the denormalised payload fields.
Implementations MAY persist chains in other formats (SQLite, Postgres, S3 object per receipt) provided the canonical JSONL form can be reconstructed without loss.
10. Verification algorithm
To verify a chain, an implementation MUST:
- Parse each line. On JSON or JWS parse error, mark the chain invalid and report the line.
- For each receipt: recompute
sha256(jws)and compare to the storedreceipt_hash. Mismatch → tampered. - Look up the public key for
agent_key_idfrom the verifier's configured key store (typically a directory of capability statements). Missing key → unverifiable. - Verify the JWS signature over the
<header>.<payload>segment using the public key. - After all receipts are loaded, walk every receipt's
parentslist and confirm each parent exists in the chain. Missing parent → broken DAG. - For every non-
sanitisationreceipt, verify the taint monotonicity invariant (§8.2). For everysanitisationreceipt, verify thattaint(R) == (union of parent taints) \ removed_tags. - (OPTIONAL) Verify that every
model_call/tool_callreceipt invokes a model / tool listed in the emitting agent's capability statement, and thatiatfalls within the statement's validity period.
The verifier's overall verdict is valid = True if and only if all of the above pass for every receipt in the chain.
11. Security considerations
11.1 Threat model
The format defends against:
- Tampering with past receipts (caught by step 2 above).
- Forging receipts without the emitting agent's private key (caught by step 4).
- Silently dropping taint to launder untrusted data (caught by step 6).
- Invoking models or tools the agent is not permitted to use (caught by step 7).
The format does NOT defend against:
- A compromised agent private key. An attacker with the key can emit receipts indistinguishable from the legitimate agent. Mitigation: short-lived keys, capability-statement
expires_at, future revocation extension. - A lying agent. An agent can record
input_hashof one prompt while passing a different prompt to the model. Mitigation: pair with TEE-attested inference (out of scope for v1). - Replay. A valid receipt may be re-emitted into a different chain. Mitigation:
iatand content-hashes make replay detectable, but the spec does not mandate replay-window enforcement.
11.2 crit header is mandatory
Implementations MUST include "raucle/v1" in the JWS crit array. This prevents JWT-conformant libraries from silently accepting provenance receipts as bearer authentication tokens (RFC 7515 §4.1.11 requires conforming verifiers to fail on unrecognised crit entries).
11.3 No content in the normative payload
Implementations MUST NOT place raw prompt or output content in the normative v1 payload. Privacy of source content is the most fragile property of the format and MUST be preserved by default. Vendor extensions that opt-in to content storage SHOULD be explicit in their naming (e.g. x-acme-content-cleartext).
12. Versioning
The receipt typ field (provenance-receipt/v1) is the version anchor. Future incompatible changes MUST increment the version (v2, v3, …) and SHOULD provide a documented migration path.
Verifiers SHOULD support multiple typ versions concurrently. A receipt's version is established by its typ header and typ payload (which MUST agree).
13. Interoperability test vectors
Reference test vectors for v1 are published at:
https://raucle.com/spec/provenance/v1/test-vectors.json
Each test vector is a tuple (seed, expected_jws, expected_receipt_hash). Implementations claiming v1 conformance MUST reproduce every published vector byte-for-byte.
14. Reference implementation
Five interoperable reference implementations, cross-verified to byte-identical content-addressed IDs, are MIT-licensed (deliberately permissive — embed them in any product, including proprietary ones):
- Python:
raucle—raucle/provenance.py(the canonical implementation). - TypeScript / Go / Rust / C#:
raucle/reference/.
A Vercel AI SDK middleware that gates tool calls and emits these receipts
lives at reference/vercel-ai-middleware.
15. Changelog
| Version | Date | Notes |
|---|---|---|
| v1 Draft 1 | 2026-05-14 | First publication. Subject to non-breaking clarifications during the comment period; breaking changes will be issued as v2. |
16. Acknowledgements
This format borrows ideas from:
- Sigstore — keyless signing for software supply chains.
- SLSA — software supply-chain levels.
- in-toto — attestation format for software supply chains.
- Certificate Transparency — append-only public logs.
- JCS (RFC 8785) — canonical JSON.
A. Related work — how this differs (non-normative)
This format is capability discipline plus a content-addressed, offline-verifiable artifact plus a machine-checked policy binding. The pieces are not new; the combination is the point.
| Prior art | What it gives you | What the Provenance Receipt adds |
|---|---|---|
| Macaroons / Biscuit / ZCAP / UCAN (capabilities + attenuation) | Unforgeable, attenuable bearer tokens | A token is permission; the receipt is evidence of what happened, content-addressed and chainable into an audit DAG. The cited policy is additionally SMT-proven sound over the tool's schema, not just asserted. |
| JWT / JWS | Signed claims | We are JWS — this is a profile of it (typ, crit, EdDSA) with canonical-JSON content addressing, taint monotonicity, and an operation/parents model on top. |
| W3C Verifiable Credentials / OID4VP | Who the holder is and is qualified to do | Identity, not action-evidence. Complementary: a VC says "this agent is authorised"; the receipt records "this action ran, against this proven policy" — they chain by content hash. |
| in-toto / SLSA | Attestations over the software supply chain | Same philosophy, different layer: this attests agent runtime actions (tool calls, retrievals, hand-offs) with taint. A receipt could reasonably be an in-toto predicate type; portability is the goal, not a silo. |
| C2PA | Provenance for media assets | Same "sign the artifact's history" idea, scoped to content; this scopes it to agent actions and authorisation. |
| Sigstore / Certificate Transparency | Keyless signing; append-only public logs | Borrowed: the receipt is offline-verifiable against a published key, and chains form a tamper-evident log. We do not (yet) mandate a public transparency log. |
The deltas that are genuinely ours: (a) the policy a capability cites is machine-checked (Z3) sound over the tool's JSON Schema, with gate/attenuation soundness mechanised in Lean 4; (b) the receipt is a portable, content-addressed record of the action, not just the grant; (c) monotone taint across the chain. We do not claim to be first on capability tokens, attenuation, signed receipts, or offline verification — several systems ship those.
B. Canonical form — design rationale (non-normative)
The receipt signs JCS-canonicalised JSON (RFC 8785, integer-only subset), then content-addresses the resulting JWS bytes. Canonical JSON is a known footgun — independent implementations drift. The mitigation is structural: five reference implementations (Python, TS, Go, Rust, C#) are pinned to a shared test vector and cross-verified to produce byte-identical receipt IDs, so drift is caught mechanically rather than trusted. JSON/JOSE was chosen for inspectability and ecosystem fit; a COSE/CBOR profile is a reasonable future binding and welcome — the payload model is encoding-agnostic.
C. Token lifetime and revocation (non-normative)
Capability tokens are short-lived bearer credentials; the model is layered, not a CRL:
- Short TTLs — capabilities are minted per-session/per-task with short expiries (examples use 60–300s), so default revocation latency is the TTL.
- Attenuation — long-lived authority is narrowed to per-action child tokens, limiting blast radius.
- Gate denylist — a gate accepts a
revoked_token_idsset (and arevoke()method); a revoked token, or any child citing it asparent_id, is DENY'd before expiry. - Key rotation — removing an issuer key from a gate revokes everything it signed.
There is intentionally no per-token CRL in v1; a signed, distributable revocation feed for multi-gate fleets is scoped as a future addition — a deliberate design choice for a bearer-token system, documented rather than implied.
17. Feedback
Issues and proposed changes:
- GitHub: https://github.com/craigamcw/raucle/issues (tag
spec) - Mailing list: TBD (announce when established)