Draft 1 · Open specification (CC-BY-4.0) · Source & test vectors: github.com/craigamcw/raucle · Five reference implementations (MIT): Python · TS · Go · Rust · C#

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

2.2 Non-goals

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:

  1. Keys at every depth MUST be sorted lexicographically.
  2. Separators MUST be , and : with no whitespace.
  3. The byte encoding MUST be UTF-8.
  4. Strings containing non-ASCII characters MUST NOT be escaped (i.e. ensure_ascii=False in Python json.dumps, equivalent in other languages).
  5. 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:

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'>"
}

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:

  1. Parse each line. On JSON or JWS parse error, mark the chain invalid and report the line.
  2. For each receipt: recompute sha256(jws) and compare to the stored receipt_hash. Mismatch → tampered.
  3. Look up the public key for agent_key_id from the verifier's configured key store (typically a directory of capability statements). Missing key → unverifiable.
  4. Verify the JWS signature over the <header>.<payload> segment using the public key.
  5. After all receipts are loaded, walk every receipt's parents list and confirm each parent exists in the chain. Missing parent → broken DAG.
  6. For every non-sanitisation receipt, verify the taint monotonicity invariant (§8.2). For every sanitisation receipt, verify that taint(R) == (union of parent taints) \ removed_tags.
  7. (OPTIONAL) Verify that every model_call / tool_call receipt invokes a model / tool listed in the emitting agent's capability statement, and that iat falls 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:

The format does NOT defend against:

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):

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:

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 artWhat it gives youWhat the Provenance Receipt adds
Macaroons / Biscuit / ZCAP / UCAN (capabilities + attenuation)Unforgeable, attenuable bearer tokensA 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 / JWSSigned claimsWe 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 / OID4VPWho the holder is and is qualified to doIdentity, 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 / SLSAAttestations over the software supply chainSame 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.
C2PAProvenance for media assetsSame "sign the artifact's history" idea, scoped to content; this scopes it to agent actions and authorisation.
Sigstore / Certificate TransparencyKeyless signing; append-only public logsBorrowed: 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:

  1. Short TTLs — capabilities are minted per-session/per-task with short expiries (examples use 60–300s), so default revocation latency is the TTL.
  2. Attenuation — long-lived authority is narrowed to per-action child tokens, limiting blast radius.
  3. Gate denylist — a gate accepts a revoked_token_ids set (and a revoke() method); a revoked token, or any child citing it as parent_id, is DENY'd before expiry.
  4. 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:

Comment: GitHub Issues · Hacker News