AER-1: A Portable Execution Receipt for AI Agent Tool Calls
draft-zambo-aer1-23
This document is an Internet-Draft (I-D).
Anyone may submit an I-D to the IETF.
This I-D is not endorsed by the IETF and has no formal standing in the
IETF standards process.
| Document | Type | Active Internet-Draft (individual) | |
|---|---|---|---|
| Author | Brennan Zambo | ||
| Last updated | 2026-10-19 | ||
| RFC stream | (None) | ||
| Intended RFC status | (None) | ||
| Formats | |||
| Stream | Stream state | (No stream defined) | |
| Consensus boilerplate | Unknown | ||
| RFC Editor Note | (None) | ||
| IESG | IESG state | I-D Exists | |
| Telechat date | (None) | ||
| Responsible AD | (None) | ||
| Send notices to | (None) |
draft-zambo-aer1-23
Network Working Group B. Zambo
Internet-Draft Zambo
Intended status: Informational 19 October 2026
Expires: 22 April 2027
AER-1: A Portable Execution Receipt for AI Agent Tool Calls
draft-zambo-aer1-23
Abstract
This document specifies AER-1, a small vocabulary for recording one
AI agent tool call as a portable, independently checkable execution
receipt. A receipt identifies the execution, records when it
happened, preserves the canonical bytes used for the output
commitment, names the tool and caller scope, carries a provenance
class, and resolves at a stable public URL. The format separates
what the system observed from claims about the outside world, and it
separates provenance (who ran or reported the action) from the record
itself. A reference implementation is deployed, and its receipts are
publicly verifiable without an account or token. The specification
covers workflow receipts, which bind an ordered sequence of step
receipts to a single goal with a Merkle root over the step sequence,
and hash-chained job timelines for tamper-evident sequencing of
executions. This revision closes honesty and precision gaps: it adds
the workflow-receipt truncation limit (new Section 8.4), normatively
specifies the external anchor format and its verification procedure
(rewritten Section 10, new Section 10.1), fixes a RECOMMENDED/MUST
contradiction in Section 8.1, makes the Section 6 verification step
testable, and documents replay, low-entropy, empty-record, and size-
limit considerations. It changes no receipt field, digest
construction, or existing verification rule. The -13 revision fixes
a malformed sentence in the Abstract, three incorrect section cross-
references, stale Table of Contents page numbers, a skipped footer
page number, misaligned table rows, and chain version-string updates,
and records two reported interoperability examples in Implementation
Status; it changes no receipt field, digest construction, or
verification rule. The -14 revision adds tamper-evident metadata
binding (new Section 3.1; conditional metadata_hash for schema 0.4
and later), disambiguates the anchor receipt_id (Table 4), corrects
the self-anchoring witnessing claim (Section 10), makes the
Section 8.3 display rule a SHOULD with a byte-testable MUST, narrows
the provenance attestation wording (Section 5), states the binding
non-transfer rule normatively (new Section 10.2), pins
anchored_digest to bare hex (Table 4), corrects the vector corpus
counts, and adds normative disclosure of missing issuer
authentication. It changes no existing receipt field, digest
construction, or verification rule for schema 0.3 and earlier. The
Zambo Expires 22 April 2027 [Page 1]
Internet-Draft AER-1 October 2026
-15 revision narrows the Implementation Status wording for the
reported interoperability examples at the implementer's request; it
changes no receipt field, digest construction, or verification rule.
The -16 revision documents the live workflow tools API (new Sections
8.5, 8.6, 8.7): the workflow_close tool, workflow metadata in step
receipts, and the workflow view API. It changes no receipt field,
digest construction, or existing verification rule. The -17 revision
documents the recommended execution_context member (new Section 3.2):
an optional, non-core structure for recording source and version
context such as the model name and version behind a tool call and the
identity of the source system acted upon. It changes no existing
receipt field, digest construction, or verification rule. The -18
revision repairs the reference apparatus (normative references to RFC
3339, RFC 8259, RFC 4122, and RFC 4648, and the previously missing
RFC 7942 entry), pins two parser edge cases (duplicate JSON member
names and leap-second timestamps are rejected), names the Unicode
normalization behavior in Section 4 with a computed vector pair,
adopts the unknown-versus-not-applicable convention for optional
members, adds optional members for readback outcomes, per-field
provenance, negative records, reversal state, method descriptors, and
payment references, completes the execution_context member set (model
and environment digests), adds the optional input_hash commitment
(Section 3.3), permits SCITT and RFC 3161 anchors under Section 10,
states the clock-trust and equivocation limits in Section 13, and
rewrites Section 15 to position the specification against adjacent
work. It changes no receipt field, digest construction, or
verification outcome for any receipt well-formed under the -17 rules.
The -19 revision aligns the workflow tool and workflow view
descriptions in Sections 8.5 and 8.7 with the normative Merkle
construction in Section 8.1: both descriptions named step output
hashes as the root's leaf input where Section 8.1 specifies step
receipt identifiers. An implementer report identified the
divergence. It changes no construction, receipt field, digest, or
verification outcome. The -20 revision adopts three findings
reported by an independent implementer who built a conformance
verifier from the -19 text alone. It requires the published byte
layout of a recorded input to state the member ordering rule it
applies when the input is a JSON object (Section 3.3), and it bars
control characters from provenance class labels, with rejection
required (Section 5). The conformance kit's quickstart core tier
also gains the provenance-label and schema-version metadata checks as
a stated rule. It changes no receipt field or digest construction; a
receipt well-formed under the -19 rules verifies identically, except
that an input commitment whose published layout states no ordering
rule is no longer reported as confirmed. The -21 revision states the
receipt-id binding rule normatively (Section 6), describes the id-
substitution case in Security Considerations (Section 13), and
credits the independent control test that produced the finding
Zambo Expires 22 April 2027 [Page 2]
Internet-Draft AER-1 October 2026
(Section 18). It changes no receipt field, digest construction, or
existing verification rule. The -22 revision pins the legacy (schema
0.3) member forms that satisfy the core requirements and requires
verifiers to report a legacy acceptance explicitly rather than as a
plain pass (Section 6), requires the canonical base64 spelling of
canonical_bytes and the rejection of non-canonical spellings
(Sections 3 and 4), states that the receipt object is the document
and that no presentation envelope is part of the format (Section 3),
enumerates the id verdict a verifier reports beside the structural
verdict, binds the states to verifier behavior, and states the
resolution order between the two layers (Section 6), requires the
reason to be printed next to the rule for every check that does not
pass (Section 6), and records an independent production validator in
the Implementation Status. It changes no receipt field or digest
construction; one verification outcome changes, for a receipt whose
canonical_bytes spelling is not canonical, which earlier revisions
did not address. The -23 revision states the per-anchor outcome
report normatively (Section 6): a verifier's report carries each
anchor's outcome in machine-visible form, and an anchor the verifier
did not evaluate is reported as "not checked", never as an evaluated
outcome, so that state cannot be conflated with an anchor that was
evaluated and could not be resolved. It changes no receipt field,
digest construction, or existing verification rule.
Implementation Status
This section records the implementation status of the AER-1
vocabulary at the time of this revision, following [RFC7942]. It
will be removed before publication as an RFC.
Reference implementation. Zambo (https://zambo.dev), operated by the
document author, is a deployed implementation of Sections 3 through
10. Tool calls made over HTTPS as JSON-RPC to https://zambo.dev/api/
mcp receive a public receipt page at https://zambo.dev/run/<id>,
retrievable without an account, wallet, or token. A machine-readable
verifier is exposed as described in Section 6. Receipts are anchored
to public Nostr relays with an envelope binding the receipt
identifier, the output hash, and the evidence hash (envelope "zambo-
receipt-anchor/1").
Workflow receipts (Section 8) are deployed in the reference
implementation. Multi-step sessions receive a workflow receipt at
https://zambo.dev/workflow/<workflow_id> binding the ordered step
receipts with a Merkle root, and step receipt pages link to the
parent workflow ("Part of workflow") while workflow pages link to
each step receipt.
Zambo Expires 22 April 2027 [Page 3]
Internet-Draft AER-1 October 2026
Conformance kit. An open conformance suite for this vocabulary is
maintained at https://gitlab.com/rambozambodotdev/zambo. It ships
seven bundled kit runners (Rust, Go, TypeScript, Python, Java, C#,
and Swift), each verified against the full vector corpus, which at
this revision stands at 119 vectors (122 checks per runner) across
the receipt-level, Merkle (Section 8.1), timeline (Section 7),
hardened chain (Section 7.1), chain commitment (Section 7.3),
Unicode, id-binding, and anchor-report suites. The -22 revision adds
vectors pinning the canonical base64 spelling (Section 4), the nil-
UUID rejection as a standalone case, the legacy schema 0.3 production
shape (Section 6), the bare presentation form (Section 3), a current-
schema (0.4) real receipt beside the two bundled 0.3 fixtures, and
one vector per id_verdict state (Section 6). The -23 revision adds
the anchor-report vectors pinning the per-anchor outcome report of
Section 6, implemented in the kit runners (kit v1.5.6) and in the
reference verifier (aer1-verify 0.2.13) ahead of this text. Each
runner is zero-dependency within its language toolchain. Running the
Python suite takes two steps and no dependencies beyond git and a
Python 3 interpreter:
git clone https://gitlab.com/rambozambodotdev/zambo.git python3 aer1-
implementations/python/conformance.py
An implementation is conformant when it produces byte-identical
verdicts to the reference runners across the full vector corpus. The
corpus includes attack vectors for tampered provenance labels, schema
version downgrades, and legacy (pre-0.4) receipts; conformance
requires the Section 6 metadata checks to fail the first two and to
report the third with a legacy notice rather than a failure. The kit
is versioned; the v1.5.5 release is the frozen reference for the
corpus described above.
Independent verification. The Section 6 verification procedure has
been exercised against live receipts by independent third parties:
* On 2026-09-16, an independent agent ("Merv", on the Clawstr
network) verified a live receipt (id 0ec81343-cc1f-
4c25-9c33-d76b5582406d) in three checks: the SHA-256 of the
decoded canonical bytes matched the output commitment, the
upstream evidence hash matched, and the anchor event carried a
valid signature retrievable on independent relays. The same party
reported that the anchor envelope did not then bind the evidence
hash; the reference implementation has since shipped the binding
envelope described above.
Zambo Expires 22 April 2027 [Page 4]
Internet-Draft AER-1 October 2026
* On 2026-09-16, an independent agent ("nilo_agent", on Nostr) ran
adversarial verification challenges against live receipts,
including a witness round and a strong-form Bitcoin-anchored
challenge (anchor block 967351, receipt 88094e26), each answered
with bytes the challenger could check independently.
* On 2026-09-25 and 2026-09-26, an independent agent ("Devan", on
Nostr) published timestamped probe receipts (v1 and v2) exercising
independent verification of agent work in the same receipt model.
Independent validators. An independent implementation exposes the
Section 6 procedure as a public service:
* WhiteMagic Labs (Lucas Bailey) operates a production validator for
this specification at https://api.whitemagic.dev/aer1/verify,
publicly accessible without a key, with the same procedure also
exposed as an MCP tool (verify_aer1_receipt). On 2026-10-17 the
operator exercised the validator against the reference
implementation and the conformance corpus. The two
implementations initially disagreed on cases in both directions (a
decoding boundary in the validator; an id-binding reading and a
nil-UUID acceptance in the reference tooling), each disagreement
was resolved against the draft text the same day, and the
implementations now agree across the exercised case matrix. The
validator reports comparison outcomes in the shared states of
Section 6 and attaches its provenance caution as advisory metadata
beside the verdict, the arrangement the provenance note in
Section 6 describes.
Seven bundled kit runners for Sections 3 through 6 are maintained in
the conformance kit repository (Rust, Go, TypeScript, Python, Java,
C#, and Swift), each verified against the full vector corpus
described above (113 vectors, 116 checks per runner). These runners
are the kit's own test runners, not independent implementations.
Independent third-party implementations, built from the draft text
alone without access to kit internals, include ARION's Node.js and
Java verifiers and ColonistOne's Perl verifier, each conformant
against the same corpus. Since revision 12, contributors have
reported two scoped interoperability exercises. On 2026-10-08,
Tanilo and the AER-1 author published a cross-repository worked
example linking an AER-1 receipt to a live Tanilo receipt by
canonical hash, with the AER-1 commitment check and the Tanilo
signature check run separately, each under its own rules. Also on
2026-10-08, the independent builder FlyThink published a four-case
fixture binding AER-1 execution receipts to physical completion
claims; all four cases replay cleanly, including the case where a
valid AER-1 receipt coexists with invalid physical-evidence binding
(see Section 10.2). Reports of additional implementations or
Zambo Expires 22 April 2027 [Page 5]
Internet-Draft AER-1 October 2026
independent verification results are solicited; implementers are
encouraged to validate against the open conformance kit described
above and to report their results.
Status of This Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute
working documents as Internet-Drafts. The list of current Internet-
Drafts is at https://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on 22 April 2027.
Copyright Notice
Copyright (c) 2026 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents (https://trustee.ietf.org/
license-info) in effect on the date of publication of this document.
Please review these documents carefully, as they describe your rights
and restrictions with respect to this document.
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 7
2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 8
3. The Receipt Record . . . . . . . . . . . . . . . . . . . . . 8
3.1. Metadata Binding . . . . . . . . . . . . . . . . . . . . 12
3.2. Execution Context . . . . . . . . . . . . . . . . . . . . 12
3.3. Optional Input Commitment . . . . . . . . . . . . . . . . 14
3.4. Additional Optional Members . . . . . . . . . . . . . . . 15
4. Canonical Bytes and Output Commitment . . . . . . . . . . . . 16
5. Provenance Classes . . . . . . . . . . . . . . . . . . . . . 17
6. Verification Procedure . . . . . . . . . . . . . . . . . . . 18
7. Hash-Chained Job Timelines . . . . . . . . . . . . . . . . . 22
7.1. The Entry Digest . . . . . . . . . . . . . . . . . . . . 23
7.2. Chain Rules . . . . . . . . . . . . . . . . . . . . . . . 24
7.3. What the Chain Does Not Prove . . . . . . . . . . . . . . 24
Zambo Expires 22 April 2027 [Page 6]
Internet-Draft AER-1 October 2026
7.4. Versioning . . . . . . . . . . . . . . . . . . . . . . . 26
8. Workflow Receipts . . . . . . . . . . . . . . . . . . . . . . 26
8.1. Merkle Root and Output Hash . . . . . . . . . . . . . . . 30
8.2. Workflow Verification Procedure . . . . . . . . . . . . . 31
8.3. Bidirectional Linking Between Steps and Workflows . . . . 32
8.4. What the Merkle Root Does Not Prove . . . . . . . . . . . 33
8.5. Workflow Close Tool . . . . . . . . . . . . . . . . . . . 34
8.6. Workflow Metadata in Step Receipts . . . . . . . . . . . 34
8.7. Workflow View API . . . . . . . . . . . . . . . . . . . . 35
9. Public Resolution . . . . . . . . . . . . . . . . . . . . . . 35
10. External Anchoring . . . . . . . . . . . . . . . . . . . . . 36
10.1. Anchor Verification Procedure . . . . . . . . . . . . . 38
10.2. Binding Does Not Transfer Claims . . . . . . . . . . . . 39
11. Reference Implementation . . . . . . . . . . . . . . . . . . 40
12. Design Rationale . . . . . . . . . . . . . . . . . . . . . . 40
13. Security Considerations . . . . . . . . . . . . . . . . . . . 41
14. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 44
15. Related Work . . . . . . . . . . . . . . . . . . . . . . . . 44
16. Normative References . . . . . . . . . . . . . . . . . . . . 45
17. Informative References . . . . . . . . . . . . . . . . . . . 46
18. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 47
Appendix A. Mapping Note: EU AI Act Article 12 (Informative) . . 49
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 50
1. Introduction
AI agents increasingly act through tool calls: they query prices,
send messages, modify files, and invoke services on a principal's
behalf. When something later needs checking -- what happened, when
it happened, and what the system actually observed -- the parties
involved usually have only vendor-specific logs, screenshots, or the
agent's own summary. None of these is portable across
implementations, and none lets an independent third party recompute
what was recorded.
AER-1 (AI Agent Execution Receipt, version 1) defines a small,
portable vocabulary for recording one agent tool call as a verifiable
receipt. The receipt answers a narrow question: what did this system
record for this execution? It does not prove an external business
outcome the system did not observe, and it does not turn a planned,
blocked, or preview action into a completed execution.
This document is an open proposal authored by Brennan Zambo. It is
not a claim to invent or own the broader execution-receipt category,
and it does not establish certification, registry membership,
universal adoption, or a finalized standards status. It is intended
Zambo Expires 22 April 2027 [Page 7]
Internet-Draft AER-1 October 2026
for discussion, implementation experiments, and interoperability
feedback. A reference implementation is deployed at the time of
writing, and its receipts are publicly verifiable as described in
Section 11.
2. Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in BCP
14 [RFC2119] [RFC8174] when, and only when, they appear in all
capitals, as shown here.
This document also uses the following terms:
Execution: One invocation of one tool by or on behalf of an agent,
with its inputs, observed result, and timing.
Receipt: A public record for one execution with a stable identifier,
execution metadata, canonical bytes, and an output commitment, as
defined in Section 3.
Canonical bytes: The exact UTF-8 byte sequence over which the output
commitment was computed. Verifiers MUST receive or reproduce the
same bytes before comparing hashes.
Observed result: The result the implementation received and stored
for the execution. A receipt reports observation, not an
unsupported claim about the world beyond the observation.
Output commitment: A SHA-256 digest of the canonical bytes, written
"sha256:" followed by the lowercase hexadecimal digest.
3. The Receipt Record
A receipt is a JSON [RFC8259] object with the following members.
This is the smallest useful interoperable shape; implementations MAY
add fields, but additions MUST NOT make the core identity or
verification fields ambiguous.
+========================+==========+===============================+
| Field | Required | Meaning |
+========================+==========+===============================+
| id | MUST | Stable UUID |
| | | identifying the |
| | | receipt. |
+------------------------+----------+-------------------------------+
| receipt_schema_version | MUST | Schema version. "0.3" |
Zambo Expires 22 April 2027 [Page 8]
Internet-Draft AER-1 October 2026
| | | in the reference |
| | | implementation. Versions |
| | | compare numerically by |
| | | dot-separated components; |
| | | "0.4" or later means the |
| | | version is equal to or |
| | | greater than "0.4" under |
| | | that ordering. |
+------------------------+----------+-------------------------------+
| created_at | MUST | Timestamp of record |
| | | creation, in [RFC3339] |
| | | format. The timestamp MUST |
| | | denote a valid calendar date |
| | | and time; a verifier MUST |
| | | reject values such as |
| | | February 30 that match the |
| | | RFC 3339 syntax but are not |
| | | real dates, and MUST reject |
| | | leap-second values (a |
| | | seconds value of 60; see |
| | | Section 6). |
+------------------------+----------+-------------------------------+
| tool | MUST | Object naming the |
| | | tool: name, version, |
| | | and caller scope (for |
| | | example "public"). |
+------------------------+----------+-------------------------------+
| provenance_class | MUST | One of the provenance |
| | | classes in Section 5. |
+------------------------+----------+-------------------------------+
| canonical_bytes | MUST | Base64 encoding [RFC4648] |
| | | of the exact UTF-8 bytes |
| | | hashed for the output |
| | | commitment. |
+------------------------+----------+-------------------------------+
| output_hash | MUST | "sha256:" plus the |
| | | lowercase hex digest |
| | | of the decoded |
| | | canonical_bytes. |
+------------------------+----------+-------------------------------+
| metadata_hash | Cond. | "sha256:" plus the |
| | | lowercase hex digest of |
| | | the canonical metadata |
| | | block defined in |
| | | Section 3.1. REQUIRED |
| | | when receipt_schema_version |
| | | is "0.4" or later; a |
| | | verifier MUST recompute |
Zambo Expires 22 April 2027 [Page 9]
Internet-Draft AER-1 October 2026
| | | and compare it whenever |
| | | present. |
+------------------------+----------+-------------------------------+
| verification_status | MUST | "verified" when the |
| | | stored record passes |
| | | the procedure in |
| | | Section 6. |
+------------------------+----------+-------------------------------+
| supersedes | OPTIONAL | Id of a prior receipt that |
| | | this receipt corrects |
| | | (Section 9). |
+------------------------+----------+-------------------------------+
Table 1: Core receipt members
The id member is a stable identifier for the receipt. Two
conformance tiers apply: the core tier and the strict tier. The core
rule (both tiers) is a lowercase UUID-shaped string: eight, four,
four, four, and twelve lowercase hexadecimal characters separated by
hyphens. Lowercase is load-bearing: Section 8.1 hashes the UTF-8
bytes of the receipt identifier string verbatim for the Merkle leaf,
so a change of case changes the root. A verifier MUST accept any id
matching the core rule. The nil UUID
(00000000-0000-0000-0000-000000000000) MUST NOT be used as a receipt
id at any tier; a verifier MUST reject it. A producer MUST NOT reuse
an id for a different receipt; each receipt id identifies exactly one
receipt. The strict tier additionally requires the id to be a UUID
version 4 with [RFC4122] variant bits; it applies when the receipt is
issued under a program that requires it (for example, a certification
program), as documented alongside the receipt. A verifier applying
the strict profile MUST reject an id that is not a version 4 UUID
with [RFC4122] variant bits.
The lexical form of the receipt id, in ABNF [RFC5234], is:
receipt-id = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG
"-" 12HEXDIG
In this ABNF, HEXDIG is restricted to lowercase a-f for alphabetic
digits, per the core rule above. The digest string form used by
output_hash and metadata_hash is:
digest-string = "sha256:" 64HEXDIG
with the same lowercase restriction. Where the ABNF and the prose of
this section differ, the prose governs.
Zambo Expires 22 April 2027 [Page 10]
Internet-Draft AER-1 October 2026
The public URL is not a stored member; it is a resolution rule.
Every receipt MUST be retrievable at a stable public URL without an
account, wallet, or token, as specified in Section 9. In the
reference implementation the URL is https://zambo.dev/run/<id>.
Deployed records carry additional members outside the interoperable
core (for example caller identity, evidence references, external
anchor data, per-check results, side-effect declarations, and a
result preview). A verifier MUST NOT require them, and their
presence MUST NOT change the meaning of the core members. For every
optional member in this specification, absence means the producer did
not record the value; absence is never, by itself, evidence of
tampering.
The receipt is the JSON object defined by its member set; that object
is the document. No presentation envelope is part of this format: a
verifier MUST accept the receipt object presented bare, as the
document itself. A transport or an API MAY carry the receipt as a
member of a wrapping object; the wrap is not part of the receipt, and
a verifier that receives a wrapped receipt evaluates the same object.
(In this document the word "envelope" otherwise denotes the anchor
envelope of Section 10, a different object.) A presentation that
cannot be unwrapped to a receipt object is reported as "rejected:
<reason>" under Section 6, never as "verification failed": no receipt
bytes were reached.
The canonical_bytes member MAY be the empty string, which denotes a
tool execution that produced zero bytes of output. The output_hash
of empty canonical bytes is "sha256:" followed by
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, the
SHA-256 digest of the empty string. A verifier MUST accept empty
canonical bytes when the output_hash matches, and MUST NOT treat
emptiness as a verification failure.
Example: the following shows the core fields of a real receipt issued
by the reference implementation (id 130da435-e157-498e-
af90-605866a86a27, a live_price call). The canonical bytes and
output hash are elided here to respect line-length limits; the
complete record is published at https://zambo.dev/run/130da435-e157-
498e-af90-605866a86a27, where the output hash was independently
recomputed from the decoded canonical bytes while preparing this
document and matches.
Zambo Expires 22 April 2027 [Page 11]
Internet-Draft AER-1 October 2026
{
"id": "130da435-e157-498e-af90-605866a86a27",
"receipt_schema_version": "0.3",
"created_at": "2026-09-23T23:45:20.760Z",
"tool": {
"name": "live_price",
"version": "4.0.0",
"scope": "public"
},
"provenance_class": "EXECUTED BY ZAMBO",
"canonical_bytes": "(elided; full value at the URL above)",
"output_hash": "(elided; full value at the URL above)",
"verification_status": "verified"
}
3.1. Metadata Binding
The canonical metadata block is a JSON object with exactly the
following members: "created_at", "id", "output_hash",
"provenance_class", "receipt_schema_version", and "tool" (an object
with exactly "name", "scope", "version"). It is serialized with
member names sorted by Unicode code point at every object level,
including nested objects, no whitespace outside strings, and the
string escaping of Section 7.1. Every member value in the block is a
JSON string. The metadata_hash member is "sha256:" followed by the
lowercase hex SHA-256 digest of the UTF-8 encoding of the block.
Because the block includes the receipt id and the output_hash, a
metadata_hash copied from another receipt does not verify. A
producer MUST NOT emit duplicate member names in the block, and a
verifier MUST reject a block that carries them (Section 6).
3.2. Execution Context
A receipt MAY carry an "execution_context" member recording the
source and version context behind the tool call: which model produced
the action, which source system was acted upon, and which agent
framework issued the call. This member is OPTIONAL and sits outside
the interoperable core defined in Table 1; a verifier MUST NOT
require it, and its presence MUST NOT change the meaning of the core
members. It is NOT part of the canonical metadata block in
Section 3.1 and MUST NOT be included in the metadata_hash
computation.
When present, execution_context is a JSON object whose members are
all OPTIONAL:
Zambo Expires 22 April 2027 [Page 12]
Internet-Draft AER-1 October 2026
* "model": an object with "name" and "version" string members
identifying the AI model behind the tool call, when the producer
knows it (for example {"name": "grok-4", "version":
"2026-08-01"}). The model object MAY also carry a "digest"
member: "sha256:" plus the digest of the model weights or serving
configuration, as recorded by the producer.
* "source": a string identifying the source system the tool acted
upon, when the call targets a specific external system (for
example a database name, repository, or API endpoint).
* "framework": an object with "name" and "version" string members
identifying the agent framework that issued the tool call, when
applicable.
* "environment": a string, or an object with "name" and "digest"
members, identifying the compute environment the call ran in, when
the producer records it.
* "readback": an object recording a fresh-read check of an earlier
write, with members "receipt" (the id of the receipt whose write
is read back), "intended" (the intended change), "write_result"
(the recorded write outcome), "readback" (the fresh-read outcome),
and "mismatch" (a boolean: true when the fresh read disagrees with
the intended change). A mismatch is a first-class recorded
outcome of the readback receipt, not an error in it.
* "payment": an object binding the execution to the payment that
authorized or paid for it, with members "method" (a string naming
the payment method, for example "x402") and "reference" (a string
carrying the settlement reference, for example a transaction
identifier). The member records the reference as stated; it does
not by itself prove settlement.
Within execution_context, a member present with the JSON value null
means the value is known not to apply to this call; an omitted member
means the value is unknown or was not recorded. Producers SHOULD use
the explicit null for a known-inapplicable member rather than
omitting it. A verifier MUST NOT treat an omitted member as a false
or inapplicable value, and MUST NOT treat absence, by itself, as
evidence of tampering.
Because execution_context is outside the metadata_hash computation,
it is unauthenticated metadata: it is asserted by the producer, and
anyone holding the receipt can add, change, or drop it without
affecting any verification outcome under Section 6. A verifier MUST
surface it as producer-asserted context, never as an attested fact.
Zambo Expires 22 April 2027 [Page 13]
Internet-Draft AER-1 October 2026
Producers SHOULD include execution_context whenever the information
is available to them. The core "tool" member already names the tool
and its version; execution_context answers the adjacent questions of
which model, which source, and which framework surrounded the call.
Like all additional members, a verifier surfaces execution_context as
recorded without upgrading its claims: the receipt proves the context
was recorded as stated, not that the stated model or source is
accurate.
Consumers MAY compare the execution_context of related receipts over
time: a change in the recorded context between a receipt and the
current environment can serve as an invalidation signal that prompts
re-verification. Such a comparison changes no verification verdict
by itself.
3.3. Optional Input Commitment
A receipt MAY carry an "input_hash" member: "sha256:" plus the
lowercase hex digest of the exact tool-input bytes the producer
recorded for the execution, computed under the same exact-byte rule
as the output commitment (Section 4). The producer MUST define and
publish the byte layout of the recorded input (for example, the
argument object under a stated serialization) alongside the receipt,
mirroring the byte-layout rule of Section 7.1; a verifier that cannot
reproduce the layout MUST NOT report the input commitment as
confirmed.
When the recorded input is a JSON object, the published byte layout
MUST state the member ordering rule it applies (for example,
lexicographic order of member names, or the order in which the
members were recorded). A byte layout that does not state a member
ordering rule is incomplete, and a verifier MUST NOT report the input
commitment as confirmed under an incomplete layout. Two
serializations of one object that differ only in member order are
different byte strings with different digests, so an unstated
ordering leaves two honest implementations free to compute different
input_hash values for the same recorded input while both conform to
the layout as published. The conformance corpus includes a multi-
member input vector whose stated layout pins the ordering, and the
same object serialized under a different member order, which does not
confirm the recorded commitment.
input_hash is OPTIONAL and sits outside the interoperable core
defined in Table 1; it MUST NOT be included in the metadata_hash
computation, and a verifier MUST NOT require it. Like every
commitment in this specification, it proves the input was recorded as
stated, not that the recorded arguments were the ones actually
executed.
Zambo Expires 22 April 2027 [Page 14]
Internet-Draft AER-1 October 2026
3.4. Additional Optional Members
The following members are OPTIONAL, sit outside the interoperable
core and outside the metadata_hash computation, and are recorded as
stated. A verifier MUST NOT require them and MUST surface them
without upgrading their claims.
* "field_provenance": an object mapping receipt member names to the
party that wrote the member's value: "actor" (the agent or
caller), "tool" (the executing tool or the system behind it), or
"counterparty" (a party other than the actor, for example the
system the tool acted upon). The receipt-level provenance class
(Section 5) states who ran or reported the action as a whole; this
member states, per field, which values are externally written and
which are self-authored, so that a field that has only ever
carried one value is visible as such.
* "negative_record": an object with two OPTIONAL members,
"not_checked" and "not_run", each an array of strings naming
checks that were not performed and actions that were not taken in
connection with the execution. A receipt records what ran; this
member records the known blind spots around it, so that a
correctly executed action is not read as a completely checked one.
* "reversal": an object recording whether the execution was later
undone, with members "state" (one of "none", "reversed", or
"compensated") and, when one exists, "compensating_receipt" (the
id of the receipt that records the compensating execution).
Unlike supersedes (Section 7.3), which corrects the record, this
member records compensation of the effect. It records the state
as stated; whether a reversal is permitted or enforced belongs to
the executing system, not to the receipt.
* "method_descriptor": an object identifying the method that
produced the committed bytes, with OPTIONAL members "method" (a
tool-specific method identifier, for example a function selector
or RPC method name), "argument_encoding" (how the arguments were
encoded), and "decode" (how the raw result was decoded).
Section 4 binds the exact bytes; this member names the question
the bytes answer, so receipts can be compared by method without
parsing tool-specific bytes.
Zambo Expires 22 April 2027 [Page 15]
Internet-Draft AER-1 October 2026
4. Canonical Bytes and Output Commitment
Interoperability fails when every implementation gives a different
name to the same boundary, so the checkable core of a receipt is
deliberately small: an identifier locates the record, a creation time
orders it, a tool and caller scope identify the execution context,
and canonical bytes plus an output hash make the stored
representation checkable.
The producer MUST preserve the exact UTF-8 byte sequence that was
hashed, and MUST expose it (directly, or in a form from which a
verifier can reproduce it byte for byte) so that any party can
recompute the digest. The digest algorithm is SHA-256 [FIPS180-4].
A verifier that cannot obtain or reproduce the exact canonical bytes
MUST NOT report the output commitment as confirmed. A verifier MUST
decode the canonical bytes as strict UTF-8 and MUST reject the
receipt if the bytes are not valid UTF-8, even when the base64
decoding and hash comparison would otherwise succeed.
The canonical_bytes member MUST carry the canonical base64 encoding
of the bytes: the encoding of [RFC4648] with the standard padding, in
which the pad bits of the final character are zero. A spelling that
decodes to the same bytes but is not the canonical spelling (for
example, one with non-zero pad bits) is not the canonical encoding.
A verifier MUST reject a receipt whose canonical_bytes is not the
canonical spelling, reporting the rejection under Section 6;
accepting the byte-identical non-canonical spelling would let two
different records carry one commitment, which is the ambiguity the
canonical form exists to remove. A corpus vector pins this case.
Zambo Expires 22 April 2027 [Page 16]
Internet-Draft AER-1 October 2026
Producers and verifiers MUST NOT apply Unicode normalization (such as
NFC or NFD) to the canonical bytes at any point: normalization is a
transformation, and it breaks the commitment like any other
(Section 13). Visually identical strings with different UTF-8 byte
sequences are different payloads and hash differently, by design.
The following vector pair is specified for the conformance corpus.
The four-character string c, a, f, U+00E9 (LATIN SMALL LETTER E WITH
ACUTE), in NFC form, is the five bytes 63 61 66 c3 a9, base64
"Y2Fmw6k=", with SHA-256
850f7dc43910ff890f8879c0ed26fe697c93a067ad93a7d50f466a7028a9bf4e.
The same visible string in NFD form (c, a, f, U+0065, U+0301
COMBINING ACUTE ACCENT) is the six bytes 63 61 66 65 cc 81, base64
"Y2FmZcyB", with SHA-256
81ef060bcd98adc7824eb5c1ada83c32491b16018e11e79f00ab9d09e04b015a. A
receipt committing to either form does not verify against the other:
presenting the NFD bytes against the NFC commitment is a verification
failure, not a match. A verifier MAY offer a documented strict mode
that reports a non-NFC payload as "rejected"; a verifier MUST NOT
normalize a payload and then report agreement.
A result preview MAY accompany the receipt to give a human reader a
useful first read. A preview is not the source dataset, and a
verifier MUST NOT treat agreement with a preview as agreement with
the committed bytes.
Byte commitment alone does not identify the method that produced the
bytes: the same byte shape can answer different questions under
different methods. Section 3.4 defines an optional structured method
descriptor that names the method alongside the committed bytes.
5. Provenance Classes
A whole job can combine actions performed by the recording system
itself, actions observed through an integrated gateway, and actions
reported by another agent. Those cases can all be useful, but they
do not support the same statement, so every receipt MUST name exactly
one provenance class. Verification MUST NOT upgrade a report into an
observation.
The three classes are:
EXECUTED BY ZAMBO: The recording system ran the tool and recorded the
returned result. The receipt attests to that recording act and its
stored output commitment, not to facts beyond what the recording
system recorded.
Zambo Expires 22 April 2027 [Page 17]
Internet-Draft AER-1 October 2026
OBSERVED VIA GATEWAY: The recording system observed the action
through an integrated gateway. The receipt attests to the gateway
observation, not to facts beyond what the gateway returned.
LOGGED BY AGENT: An external agent reported the action through a
journal interface. The receipt attests that the report was received,
redacted, timestamped, and chained. The recording system does not
claim it ran or observed the action.
The first label names the recording system; its interoperable content
is the definition, and another implementation substitutes its own
system name in that position. The second and third labels are fixed
strings.
A provenance class label MUST NOT contain control characters (code
points U+0000 to U+001F, or U+007F). A verifier MUST reject a
receipt whose provenance_class member contains a control character,
under the Section 6 metadata checks. Matching a label by a prefix of
its characters is not a substitute: a value that merely starts with a
class label's characters is not that class, and parsing that ignores
or strips control characters when comparing labels is not conformant.
Provenance is a separate design axis from the receipt envelope. Two
receipts with identical envelopes can carry different provenance, and
a verifier MUST surface the provenance class alongside every other
field when presenting a receipt. Section 3.4 defines an optional
per-field provenance member that complements this receipt-level
class.
6. Verification Procedure
To verify a receipt, a verifier performs the following steps:
1. Resolve the public receipt URL and confirm the id in the
retrieved record matches the requested execution.
2. Read the exact canonical bytes, or the representation needed to
reproduce them byte for byte.
3. Compute SHA-256 over those bytes and compare the result with
output_hash.
4. Check the provenance class (Section 5) and confirm it is one of
the three defined values. Confirm the created_at timestamp
denotes a valid calendar date and time. This check establishes
form and calendar validity only, not that the stated time is true
(Section 13). When the receipt carries metadata_hash, or when
receipt_schema_version is "0.4" or later, recompute the metadata
Zambo Expires 22 April 2027 [Page 18]
Internet-Draft AER-1 October 2026
block from the receipt's stored member values per Section 3.1 and
compare; a mismatch MUST cause verification to fail. A "0.4"
receipt without metadata_hash MUST fail. A "0.3" receipt without
metadata_hash verifies under the prior rules, but its id, tool
name, version, scope, creation time, and provenance class are NOT
bound to the output commitment. For such a receipt, the Table 1
requirements are satisfied by the legacy member forms where they
exist: the creation time MAY be carried by a "timestamp" member
when "created_at" is absent, and the tool member MAY be a bare
string naming the tool when the object form is absent. No other
Table 1 member has a legacy form. A verifier that accepts a
receipt under the prior rules MUST report the legacy acceptance
as such, MUST state which members were read in legacy form, and
MUST state the unbound set named above; a legacy acceptance MUST
NOT be reported as a plain pass under the current schema. If
anchors are published for the receipt (Section 10), verify each
anchor using the procedure in Section 10.1; an anchor that fails
verification MUST NOT be reported as verified, but anchor failure
MUST NOT by itself cause the receipt's output commitment check to
fail. Report the tool name, timestamp, caller scope, provenance
class, and per-anchor outcomes alongside the hash check result,
each labeled with what was actually checked.
5. Report only what the stored observation supports. A passing hash
confirms the bytes match the commitment; it does not confirm
anything the system did not observe. Report outcomes in two
classes: "verification failed" when a recomputed value does not
match the committed value (hash mismatch, Merkle root mismatch,
anchor digest mismatch), and "rejected: <reason>" when the record
cannot be fully evaluated for a structural, policy, or resource
reason (duplicate receipt_id, size limit, unresolvable record,
unreproducible bytes). A rejection MUST NOT be reported as
"verification failed," since the record may verify under
different conditions.
For every check that does not pass, and for every rejection, the
report MUST print the reason next to the rule: which requirement was
applied, and how the record failed it. A bare outcome with no stated
reason does not conform to this section.
The output commitment covers the canonical bytes. The receipt id is
not a committed field of the output commitment: it does not appear in
the canonical bytes, and no step above recomputes it. The steps
above, applied to a presented record alone, therefore establish the
integrity of the presented bytes only. A verifier establishes that
the id belongs to those bytes only when it (a) compares the record's
id against an expected id obtained from a source independent of the
presented record, or (b) resolves the id under Section 9 and confirms
Zambo Expires 22 April 2027 [Page 19]
Internet-Draft AER-1 October 2026
that the resolved record's output_hash matches the presented record's
output_hash. When a verifier performs neither comparison, it MUST
report the id as unverified alongside the step 5 outcome, and MUST
NOT present the verification as covering the id.
A verifier's report carries two layers, and both are always reported.
The structural verdict is the outcome of the steps above for the
presented bytes: a pass, "verification failed", or "rejected:
<reason>". Beside it, the verifier MUST report an id verdict, named
"id_verdict", with exactly one of these values:
* "matched": the verifier compared the record's id against an
expected id obtained from a source independent of the presented
record, as in (a) above, and the two are equal.
* "resolved": the verifier resolved the id under Section 9, as in
(b) above, and the resolved record's output_hash matches the
presented record's output_hash.
* "mismatched": a comparison under (a) or (b) was performed and
failed: the expected id differs from the record's id, or the
resolved record's output_hash differs from the presented record's.
* "unverified": the presented record carries an id, but the verifier
performed neither comparison. This is the case the paragraph
above governs.
* "absent": no receipt id was presented to the verifier at all (for
example, only canonical bytes were supplied).
The states bind behavior, not only naming: a conforming verifier MUST
emit the distinct value for each case, and the conformance corpus
carries one vector per state. The values are a partition: every
verification report falls in exactly one state. When the two layers
disagree, the resolution order is fixed. The structural verdict
governs the presented bytes and is never upgraded or downgraded by
the id verdict. The composite claim "verified receipt <id>" is
presented only when the structural verdict is a pass and the id
verdict is "matched" or "resolved". A report MUST NOT present an id
verdict of "mismatched" or "unverified" as the verification covering
the id, and a report that omits the id verdict, or collapses two
states into one value, does not conform to this section.
How much a binding is worth depends on the channel that delivered the
expected id: an expected id supplied by the same party that presents
the receipt binds the record to that party's assertion, and no
further. Weighing the channel belongs to the reader of the report,
not to the verifier. A verifier MAY attach provenance advice to its
Zambo Expires 22 April 2027 [Page 20]
Internet-Draft AER-1 October 2026
report (for example, that an expected id was caller-asserted and that
corroboration from an independent channel is advisable) as additional
information. Such advice is metadata about the binding; it MUST NOT
alter the structural verdict or the id verdict.
Beside the structural verdict and the id verdict, the report carries
the per-anchor outcomes in machine-visible form. For each anchor,
the report states three distinct pieces of information: whether an
anchor is present, whether the verifier evaluated it, and the
outcome. In the reference tooling the report is an object with
"present", "evaluated", and "outcome" members; another presentation
that keeps the three pieces distinct and machine-readable conforms to
this section. An anchor is present for this report when the record
carries an anchor in a shape the verifier can attempt to evaluate. A
member named "anchor" whose value is in no anchor shape the verifier
implements (for example, a record of publication in another system)
does not make an anchor present.
The outcome is exactly one of these values:
* "verified": the verifier evaluated the anchor and the anchor
verifies under the procedure that applies to it (Section 10.1 for
external anchors; the anchor profile's own checks for an anchor
carried in the record).
* "verification failed": the verifier evaluated the anchor and a
recomputed or stored value does not match what was committed.
* "rejected: unresolvable record": the verifier evaluated the anchor
and the anchor's record could not be resolved, the resource
outcome of step 5 above.
* "not checked": an anchor is present and the verifier did not
evaluate it.
* "absent": no anchor is present for the verifier to evaluate.
Zambo Expires 22 April 2027 [Page 21]
Internet-Draft AER-1 October 2026
The states bind behavior, not only naming. A verifier MUST NOT
report an evaluated outcome for an anchor it did not evaluate: an
unevaluated anchor is reported "not checked", whatever the verifier
expects evaluation would have found. "not checked" and "rejected:
unresolvable record" are different states and MUST NOT be conflated:
the first records that no evaluation was attempted, the second that
evaluation was attempted and the record could not be resolved. A
reader of the report is entitled to rely on the distinction. The
anchor outcome is reported beside the structural verdict and does not
alter it: an anchor that fails or cannot be resolved does not by
itself fail the receipt's output commitment check (step 4). The
conformance corpus carries one vector per state.
Two structural rules apply before any digest comparison. A producer
MUST NOT emit duplicate member names in any JSON object whose
contents are covered by a digest in this specification (the receipt
object, the canonical metadata block of Section 3.1, and chain
entries under Section 7.1), and a verifier MUST reject a receipt in
which any such object, as received, contains duplicate member names:
a parser that silently keeps one value would let one byte string
carry two meanings. A verifier MUST also reject any timestamp it is
required to validate whose seconds value is 60 (for example
"2026-06-30T23:59:60Z"): leap-second validity cannot be checked
offline, so leap-second timestamps are not accepted. These outcomes
are reported as "rejected: duplicate member name" and "rejected:
leap-second timestamp" under the outcome classes of step 5, and a
negative vector for each is specified for the conformance corpus.
A machine-readable verifier SHOULD expose the same procedure over
HTTP, returning the receipt identifier, verification status, and
output hash, and signaling failure with a non-success status when the
record does not verify. In the reference implementation, an HTTP GET
on https://zambo.dev/api/receipt/<id>/verify returns a JSON object
containing at least id, verification_status, and output_hash.
7. Hash-Chained Job Timelines
Entries for one job form an append-only hash chain. Each entry
carries a prev_digest member whose value is the lowercase hex-encoded
SHA-256 entry digest of the previous entry, computed as defined in
Section 7.1. Because the entry digest binds the entry's sequence
position, job identifier, closing flag, receipt identifier, tool
name, provenance class, and output commitment, relabeling an entry's
metadata, moving the closing flag, or splicing entries across jobs
breaks the links and is detectable by recomputation. The conformance
kit exposes this as a chain-validity check alongside the other
verification checks.
Zambo Expires 22 April 2027 [Page 22]
Internet-Draft AER-1 October 2026
7.1. The Entry Digest
The entry digest of a chain entry is SHA-256 over the UTF-8 encoding
of a canonical JSON object with exactly the following members.
Member names are sorted by Unicode code point; the serialization
emits no whitespace outside strings. The member list below is in
description order. The serialization order is the code-point sort:
close, id, job_id, output_hash, prev_digest, provenance_class, seq,
tool:
prev_digest: the entry's prev_digest member, a string.
seq: the entry's sequence number, a JSON number with an integer
value. The representations 1 and 1.0 are equivalent; both are
accepted, and the digest serializes the value in integer form (1,
never 1.0). A verifier MUST reject a non-integer number (such as
1.5), a boolean, a string, null, or a missing seq.
job_id: the entry's job identifier, a string.
close: the entry's close member if present; if the member is absent,
the boolean false.
id: the entry's receipt identifier, a string.
tool: the entry's tool name, a string.
provenance_class: the entry's provenance class, a string.
output_hash: the lowercase hex-encoded SHA-256 digest of the raw
bytes obtained by base64-decoding the entry's canonical_bytes member.
Strings are serialized as JSON strings with the following exact
escaping, which matches the output of Python's json.dumps with
ensure_ascii=True: the quotation mark (U+0022) and reverse solidus
(U+005C) are escaped as \" and \\; U+0008, U+0009, U+000A, U+000C,
and U+000D are escaped as \b, \t, \n, \f, and \r; and every other
code point outside the range U+0020 to U+007E, including U+007F
(DELETE), is escaped as \u followed by four lowercase hexadecimal
digits, using a surrogate pair for code points above U+FFFF. No
other escaping is used. A verifier MUST decode canonical_bytes with
strict base64 [RFC4648] and fail closed: any input that is not valid
base64 causes chain verification to fail rather than being skipped or
repaired.
Zambo Expires 22 April 2027 [Page 23]
Internet-Draft AER-1 October 2026
7.2. Chain Rules
A timeline is a non-empty JSON array of chain entries. A verifier
MUST reject an empty timeline. Each entry carries canonical_bytes (a
base64 string), prev_digest (a string), seq (an integer), job_id, id,
tool, and provenance_class (non-empty strings), and optionally close.
Genesis. The first entry's prev_digest MUST be 64 zero characters
("0" repeated 64 times), the conventional zero digest. A verifier
MUST reject a first entry carrying any other prev_digest value.
Sequence. The first entry's seq MUST be 1, and each subsequent
entry's seq MUST be exactly one greater than the previous entry's
seq. A verifier MUST reject a timeline with a gap, a duplicate, or
an out-of-order sequence number.
One job. Every entry in a timeline MUST carry the same job_id. A
verifier MUST reject a timeline whose entries name different jobs;
entries cannot be spliced across jobs without breaking the chain.
Closing. The close member, when present, MUST be a boolean. The
value true MUST appear only on the last entry of the timeline, and
the last entry MUST carry close set to true. A verifier MUST fail a
timeline whose last entry is not a closing entry, and MUST fail a
timeline carrying close set to true on any earlier entry.
Links. A verifier MUST recompute the Section 7.1 entry digest of
every entry and confirm that each entry after the first carries a
prev_digest equal to the recomputed digest of the entry before it. A
verifier MUST NOT accept a link on the basis of the stored
prev_digest values alone.
7.3. What the Chain Does Not Prove
Recomputation detects relabeling, insertion, deletion, and reordering
only when the attacker cannot rebuild the links. An attacker who
removes entries from either end of a timeline and recomputes a
complete, valid shorter chain produces a timeline that verifies under
the rules above: the chain mechanics alone cannot distinguish a
rebuilt truncation from a job that genuinely ended earlier or started
later. This is an honest limit of hash chaining, not a defect in the
digest.
Zambo Expires 22 April 2027 [Page 24]
Internet-Draft AER-1 October 2026
Truncation resistance needs a commitment outside the chain: a
witness, a timestamped head, an anchor, or a workflow manifest that
binds the expected step list or step count. Deployments that need to
detect truncation MUST publish such a commitment and verify the
timeline against it. The chain check alone MUST NOT be presented as
truncation-proof.
That commitment SHOULD bind the digest of the final chain entry, not
merely the expected step count. Every entry except the last is bound
by the following entry's prev_digest; the final entry's digest is
referenced by nothing inside the chain. An attacker who rewrites the
final entry's content without changing the entry count produces a
chain that still verifies. A commitment that records the final entry
digest detects this; a commitment that records only the count does
not.
To make such commitments interoperable, a deployment MAY publish a
chain commitment: a JSON document separate from the chain with the
following members:
job_id: the job identifier the timeline binds, a string matching the
job_id of every entry in the timeline.
final_entry_digest: the lowercase hex-encoded SHA-256 entry digest of
the timeline's last entry, recomputed as in Section 7.1, a string of
64 lowercase hexadecimal characters.
entry_count: the number of entries in the timeline, a JSON number
with an integer value.
published_at: the time the commitment was published, an RFC 3339
timestamp denoting a valid calendar date and time.
The commitment MUST be published outside the chain: it binds the
digest of the final entry, and recording it inside the final entry
would be circular. A verifier checks a chain commitment by
recomputing the final entry digest as in Section 7.1 and comparing it
with final_entry_digest, comparing entry_count with the number of
entries in the timeline, and confirming job_id matches the timeline's
job identifier. The conformance kit implements this check; a
commitment that fails it MUST NOT be presented as binding the
timeline.
As with receipts, chaining provides tamper evidence for the sequence,
not truth about the world. A chained entry with LOGGED BY AGENT
provenance remains a chained report; the chain upgrades nothing about
what the entry attests. A public page resolving the whole timeline
reports whether the visible chain verifies.
Zambo Expires 22 April 2027 [Page 25]
Internet-Draft AER-1 October 2026
7.4. Versioning
The construction in this section is the -10 chain construction, which
is identical to the -07/-08/-09 construction; the -11, -12, -13, and
-14 revisions change no chain verification rule. The -10 revision
precisely specifies the Section 7.1 string escaping; it changes no
verification rule. The -08 revision adds guidance in Section 7.3
that the external truncation commitment SHOULD bind the final entry
digest; it changes no verification rule. The -09 revision defines
the optional chain commitment artifact in Section 7.3, which the
conformance kit verifies; it changes no chain verification rule. The
-07 construction differs from the -06 construction, whose entry
digest covered only the base64-decoded canonical_bytes. A verifier
MUST distinguish the -06 construction from the -07/-08/-09/-10/-11/-
12/-13/-14 construction: a timeline that verifies under the -06
construction MUST NOT be reported as verifying under the -07/-08/-
09/-10/-11/-12/-13/-14 construction. The -06 construction is
retained for historical chains only; new timelines MUST use the -07/-
08/-09/-10/-11/-12/-13/-14 construction, and historical -06 chains
MUST be labeled historical and MUST NOT verify as current.
8. Workflow Receipts
A single execution receipt (Section 3) records one tool call. When
an agent performs several tool calls toward one goal, the sequence
itself needs a verifiable record. A workflow receipt binds an
ordered list of step receipts to a single goal, with a Merkle root
over the step sequence and an output hash for the workflow's final
result.
A workflow receipt is a JSON object with the following members. This
is the smallest useful interoperable shape; implementations MAY add
fields, but additions MUST NOT make the core identity or verification
fields ambiguous.
+========================+==========+===============================+
| Field | Required | Meaning |
+========================+==========+===============================+
| type | MUST | "verifiable-workflow- |
| | | receipt". |
+------------------------+----------+-------------------------------+
| version | MUST | Workflow receipt schema |
| | | version. "1" in the |
| | | reference implementation. |
+------------------------+----------+-------------------------------+
| workflow_id | MUST | Stable UUID identifying the |
| | | workflow. |
+------------------------+----------+-------------------------------+
Zambo Expires 22 April 2027 [Page 26]
Internet-Draft AER-1 October 2026
| receipt_id | MUST | Stable UUID identifying this |
| | | workflow receipt record. |
| | | Distinct from workflow_id. |
+------------------------+----------+-------------------------------+
| session_id | MUST | Identifier for the session |
| | | that produced the workflow. |
+------------------------+----------+-------------------------------+
| goal | MUST | Human-readable description |
| | | of the goal the workflow |
| | | pursued. |
+------------------------+----------+-------------------------------+
| status | MUST | Overall workflow status, |
| | | for example "ok" or "error". |
+------------------------+----------+-------------------------------+
| steps | MUST | Non-empty ordered array |
| | | of step objects (see |
| | | below), ordered by |
| | | execution sequence. An |
| | | empty array MUST be |
| | | rejected by a verifier. |
+------------------------+----------+-------------------------------+
| merkle_root | MUST | Hex-encoded SHA-256 Merkle |
| | | root over the ordered step |
| | | receipt identifiers |
| | | (Section 8.1). |
+------------------------+----------+-------------------------------+
| output_hash | MUST | Hex-encoded SHA-256 digest |
| | | of the workflow's final |
| | | output bytes |
| | | (Section 8.1). |
+------------------------+----------+-------------------------------+
| verify_url | MUST | Stable public URL where the |
| | | workflow receipt can be |
| | | retrieved and checked |
| | | without an account, wallet, |
| | | or token. |
+------------------------+----------+-------------------------------+
Table 2: Workflow receipt members
Each step object in the steps array has the following members:
Zambo Expires 22 April 2027 [Page 27]
Internet-Draft AER-1 October 2026
+========================+==========+===============================+
| Field | Required | Meaning |
+========================+==========+===============================+
| seq | MUST | 1-based sequence number |
| | | within the workflow, |
| | | starting at 1 with no gaps. |
+------------------------+----------+-------------------------------+
| tool | MUST | Name of the tool invoked for |
| | | this step. |
+------------------------+----------+-------------------------------+
| receipt_id | MUST | UUID of the execution |
| | | receipt for this step |
| | | (Section 3). |
+------------------------+----------+-------------------------------+
| receipt_hash | MUST | Hex-encoded SHA-256 digest |
| | | of the canonical bytes of |
| | | the step receipt (the same |
| | | bytes verified in |
| | | Section 6), as retrieved |
| | | at workflow assembly time. |
+------------------------+----------+-------------------------------+
| started_at | MUST | Timestamp when the step |
| | | began, in RFC 3339 format. |
+------------------------+----------+-------------------------------+
| ended_at | MUST | Timestamp when the step |
| | | completed, in RFC 3339 |
| | | format. |
+------------------------+----------+-------------------------------+
| status | MUST | Step status, for example |
| | | "ok" or "error". |
+------------------------+----------+-------------------------------+
| receipt_url | OPTIONAL | Public URL where the step |
| | | receipt can be retrieved. |
+------------------------+----------+-------------------------------+
Table 3: Workflow step members
The public URL is not a stored member beyond verify_url; it is a
resolution rule. Every workflow receipt MUST be retrievable at its
verify_url without an account, wallet, or token, as specified for
execution receipts in Section 9. In the reference implementation the
URL is https://zambo.dev/workflow/<workflow_id>.
Example: the following shows the core fields of a real workflow
receipt issued by the reference implementation (workflow_id 42a3c2cf-
2cdd-5b8a-aced-016e5a2fb634, goal "get BTC price", five steps). The
complete record is published at https://zambo.dev/workflow/42a3c2cf-
2cdd-5b8a-aced-016e5a2fb634.
Zambo Expires 22 April 2027 [Page 28]
Internet-Draft AER-1 October 2026
{
"type": "verifiable-workflow-receipt",
"version": "1",
"workflow_id": "42a3c2cf-2cdd-5b8a-aced-016e5a2fb634",
"receipt_id": "442243d2-9c32-53f7-817e-2def28fa5b07",
"session_id": "receipt-test-001",
"goal": "get BTC price",
"status": "ok",
"steps": [
{
"seq": 1,
"tool": "zambo_universal",
"receipt_id": "e80168eb-a6a1-4ec9-8478-ac108f9397ff",
"receipt_hash":
"11c3a071051a42cbf5cf4da60a7577b1dffeb0419e6dd8a258b19c4c0d0d108e",
"started_at": "2026-09-29T10:57:40.911Z",
"ended_at": "2026-09-29T10:57:41.147Z",
"status": "ok"
},
{
"seq": 2,
"tool": "zambo_universal",
"receipt_id": "514fe841-bbdd-4804-9ef4-6828842d8763",
"receipt_hash":
"68e3bcad3f46218672fd7ef7bf32f4a7089937dda2b8ade3cfe1050cec856285",
"started_at": "2026-09-29T10:57:42.920Z",
"ended_at": "2026-09-29T10:57:43.069Z",
"status": "ok"
},
{
"seq": 3,
"tool": "zambo_universal",
"receipt_id": "a090bdad-f2cf-48c9-bc89-e9e17700fb15",
"receipt_hash":
"3da48b2d392fb79ed78f7ed0cc8e6c122fc1089947ff6d7f829748d7745840dd",
"started_at": "2026-09-29T10:57:45.415Z",
"ended_at": "2026-09-29T10:57:45.564Z",
"status": "ok"
},
{
"seq": 4,
"tool": "zambo_universal",
"receipt_id": "bf88a036-416b-456a-8e82-8c0991811d68",
"receipt_hash":
"0f024f209392b918909df5478aadd3a9189520d74cfdf78e2ccf51b9f075f926",
"started_at": "2026-09-29T10:57:48.459Z",
"ended_at": "2026-09-29T10:57:48.605Z",
"status": "ok"
Zambo Expires 22 April 2027 [Page 29]
Internet-Draft AER-1 October 2026
},
{
"seq": 5,
"tool": "zambo_universal",
"receipt_id": "ae795bf0-27f8-4cf2-8aef-3f8142b77217",
"receipt_hash":
"f52d9c03605047ad0ac347e5e9cbc0e2e197a009d8ee5c7d775f86f360eca963",
"started_at": "2026-09-29T10:57:55.227Z",
"ended_at": "2026-09-29T10:57:55.434Z",
"status": "ok"
}
],
"merkle_root":
"7c4817ca249edfeae259b98abf63ed38a31d9dbeebf5ee23139410a7a7f63b37",
"output_hash":
"16566e46433dd9a00e4437873102531be71fbae3872db8888b7ddc9282d90f54",
"verify_url":
"https://zambo.dev/workflow/42a3c2cf-2cdd-5b8a-aced-016e5a2fb634"
}
The merkle_root above recomputes from the five step receipt_id values
with the Section 8.1 construction: the leaf for each step is SHA-256
over the UTF-8 bytes of its receipt_id string, paired and hashed up
the tree with last-duplication on odd levels. The complete live
record remains published at the verify_url above.
8.1. Merkle Root and Output Hash
The merkle_root binds the ordered step sequence so that insertion,
deletion, reordering, or substitution of steps is detectable by
recomputation. It is a lowercase hex-encoded SHA-256 digest computed
over the ordered step receipt identifiers.
The Merkle tree construction is a binary Merkle tree, specified as
follows:
1. For each step in order, compute the leaf digest as SHA-256 over
the UTF-8 bytes of the step's receipt_id string.
2. Pair adjacent digests in order; for each pair, compute SHA-256
over the concatenation of the two 32-byte digests. If the number
of digests at any level is odd, duplicate the last digest before
pairing.
3. Repeat step 2 on the resulting digests until one digest remains.
Hex-encode that digest in lowercase; this is the merkle_root.
Zambo Expires 22 April 2027 [Page 30]
Internet-Draft AER-1 October 2026
With a single step, the leaf digest is the merkle_root; no pairing
occurs. (The steps array is always non-empty per Section 8.)
An implementation MUST use the construction specified above. The
alternative-construction permission from earlier revisions is
removed: permitting divergent constructions allows different Merkle
roots for the same workflow, which breaks cross-implementation
verification of the step sequence. The construction in this section
is the only permitted one; a verifier recomputes the root from the
steps array alone.
The output_hash is a lowercase hex-encoded SHA-256 digest of the
canonical bytes representing the workflow's final output. The
producer MUST define and publish the exact byte layout used (for
example, the canonical JSON of a documented final-output document) at
the verify_url. If the producer publishes no layout, the bytes are
the Section 7.1 serialization of {"output": <final-output>}. A
verifier that cannot obtain or reproduce those bytes
deterministically from published artifacts alone MUST NOT report the
output hash as confirmed.
A verifier recomputes each step's receipt_hash from the step receipt
retrieved in Section 8.2 step 2 and MUST report a mismatch as a step-
binding failure, distinct from a Merkle root failure.
8.2. Workflow Verification Procedure
To verify a workflow receipt, a verifier performs the following
steps:
1. Resolve the verify_url and confirm the workflow_id in the
retrieved record matches the requested workflow.
2. For each step in the steps array, in order, retrieve the step's
receipt: at the step's receipt_url when present, otherwise at the
publisher's documented URL scheme for step receipts, and run the
Section 6 verification procedure on the step receipt. A verifier
that cannot locate a step receipt MUST NOT report the workflow
receipt as verified. Every step receipt MUST verify
independently; a workflow receipt MUST NOT be reported as
verified if any constituent step receipt fails verification.
3. Confirm the steps array is ordered by seq starting from 1 with no
gaps and no duplicate sequence numbers. Confirm that no two
steps carry the same receipt_id value. A verifier MUST reject a
workflow receipt whose steps array contains the same receipt_id
value more than once.
Zambo Expires 22 April 2027 [Page 31]
Internet-Draft AER-1 October 2026
This rule is structural, not cosmetic. With the Section 8.1
odd-level duplication rule, the step list [r1, r2, r3, r4, r5]
and the step list [r1, r2, r3, r4, r5, r5] recompute to the
identical Merkle root, so the root comparison in step 4 cannot
distinguish them. Rejecting duplicate receipt_id values closes
this collision class at the verification layer. For the
Section 8 example workflow, the six-step list that repeats
the fifth step's receipt_id recomputes to the identical root
(7c4817ca...f63b37); the conformance corpus carries that
pair as the isolating vector for this rule.
4. Recompute the merkle_root from the ordered step receipt_id values
using the construction documented for the receipt (Section 8.1),
and compare it to the published merkle_root.
5. Review the goal, status, timestamps, and output hash separately
from the structural checks.
6. Report only what the stored observations support. A passing
Merkle root confirms the step sequence is intact as recorded; it
does not confirm anything the individual step receipts did not
observe. Provenance classes (Section 5) apply per step; the
workflow receipt does not upgrade the provenance of its steps.
A machine-readable verifier SHOULD expose the same procedure over
HTTP, returning the workflow identifier, verification status, the
per-step outcomes, and the Merkle root check result, and signaling
failure with a non-success status when the record does not verify.
8.3. Bidirectional Linking Between Steps and Workflows
When a step receipt belongs to a workflow, the step receipt's public
page MUST reference the parent workflow, and the workflow receipt
MUST reference each constituent step receipt. This lets a reviewer
start from any single action and reach the full goal context, or
start from the goal and drill into any individual action, without
needing session identifiers or internal system knowledge.
Specifically:
Zambo Expires 22 April 2027 [Page 32]
Internet-Draft AER-1 October 2026
* The public page for a step receipt that is part of a workflow
SHOULD display a reference to the parent workflow, including a
link to the workflow's verify_url. In the reference
implementation this appears as "Part of workflow" with a link to
https://zambo.dev/workflow/<workflow_id>. Whether or not the page
displays such a reference, any published step-to-workflow linkage
MUST be byte-testable: the referenced workflow receipt's steps
array MUST include that step's receipt_id, as required in the next
bullet, and a verifier resolving the workflow's verify_url MUST be
able to reach the step receipt by that receipt_id.
* The workflow receipt's steps array MUST include the receipt_id of
every step in the workflow, and the public workflow page SHOULD
link each step entry to its receipt page.
* A step receipt that is not part of any workflow MUST NOT display a
workflow reference. The absence of the link is not an error and
MUST NOT be treated as a verification failure.
Linking is presentational and navigational; it does not alter the
receipt bytes, the output commitment, or the Merkle root. Adding or
removing a link MUST NOT invalidate verification.
8.4. What the Merkle Root Does Not Prove
Recomputation detects insertion, deletion, reordering, and
substitution of steps only when the attacker cannot rebuild the root.
An attacker who removes steps from the steps array and recomputes a
complete, valid Merkle root over the shorter list produces a workflow
receipt that verifies under Section 8.2: the Merkle root alone cannot
distinguish a rebuilt truncation from a workflow that genuinely
contained fewer steps. This is the same honest limit stated for hash
chains in Section 7.3, applied to the step sequence.
Truncation resistance for a workflow needs a commitment outside the
receipt: an external anchor binding the merkle_root (Section 10), or
a workflow manifest published before execution that binds the
expected step list or step count. Deployments that need to detect
step truncation MUST publish such a commitment and verify the
workflow receipt against it. The Merkle root check alone MUST NOT be
presented as truncation-proof.
That commitment SHOULD bind the merkle_root itself, not merely the
expected step count. As with chain commitments (Section 7.3), a
count-only commitment does not detect content substitution within the
same count. A verifier checks a workflow anchor by recomputing the
merkle_root as in Section 8.1 and comparing it with the anchored
value, as specified in Section 10.
Zambo Expires 22 April 2027 [Page 33]
Internet-Draft AER-1 October 2026
8.5. Workflow Close Tool
The reference implementation provides a workflow_close tool that
seals a session's workflow and issues the workflow receipt described
in Section 8.
The tool takes a single input: _session_id, the session identifier
grouping the workflow's steps.
The tool returns a JSON object with the following members:
* workflow_id: Stable UUID identifying the workflow.
* count: Number of step receipts in the workflow.
* merkle_root: Hex-encoded SHA-256 Merkle root over the ordered step
receipt identifiers, computed as in Section 8.1.
* children: Ordered array of objects, each with id (the step receipt
UUID) and output_hash (hex-encoded SHA-256 digest of the step's
output bytes).
* message: Human-readable confirmation, for example "recorded 3
receipts for this session".
* verification_status: Always "not_verified" for the summary itself.
The summary records; it does not verify.
* issuer_authenticated: Boolean indicating whether the issuer was
authenticated.
The tool is idempotent on data: repeated invocations return the same
count, merkle_root, and children. Each invocation mints its own
execution receipt, as the close action is itself a recorded tool
call. The summary receipt is excluded from the workflow's step count
(the meta-receipt rule): a workflow_close receipt never appears in
its own workflow's steps array.
8.6. Workflow Metadata in Step Receipts
Each execution receipt that is part of a workflow carries a workflow
object with the following members:
* id: The workflow_id UUID.
* url: Stable public URL where the workflow can be retrieved,
https://zambo.dev/workflow/<workflow_id>.
Zambo Expires 22 April 2027 [Page 34]
Internet-Draft AER-1 October 2026
* step_index: Zero-based position of this receipt in the workflow's
execution order.
The workflow_id is unbound metadata: it is NOT included in the
receipt's canonical_bytes and does not affect the output_hash. The
authoritative binding of workflow membership is the workflow_close
summary receipt's children array together with the merkle_root
(Section 8.5).
8.7. Workflow View API
The workflow receipt and its steps are retrievable as JSON at:
GET /api/workflow/<workflow_id>
Both the workflow_id and the id forms resolve identically.
The response is a JSON object with the following members:
* id: The workflow_id.
* workflow_id: Same as id.
* session_hash: Hex-encoded SHA-256 hash of the session identifier.
* count: Number of step receipts in the workflow.
* first_at: RFC 3339 timestamp of the earliest step.
* last_at: RFC 3339 timestamp of the latest step.
* merkle_root: Hex-encoded SHA-256 Merkle root over the ordered step
receipt identifiers (Section 8.1).
* receipts: Ordered array of step receipt objects, each with id,
tool, status, timestamp (RFC 3339), receipt_url, output_hash (hex-
encoded SHA-256), and step_index (zero-based).
The workflow view excludes workflow_close summary receipts per the
meta-receipt rule (Section 8.5).
9. Public Resolution
Every receipt MUST be retrievable at its public URL without an
account, wallet, or token. The URL is stable: once published, the
record at that URL MUST NOT be altered. Corrections are published as
new receipts that carry the superseded id in the supersedes member;
they MUST NOT rewrite history at the original URL.
Zambo Expires 22 April 2027 [Page 35]
Internet-Draft AER-1 October 2026
The public page SHOULD present the receipt fields, the provenance
class, the verification outcome, and the hash chain position in
human-readable form alongside the raw JSON.
10. External Anchoring
A receipt MAY be bound to one or more external anchors after
issuance. Anchoring is additive: it does not alter the receipt bytes
and MUST NOT invalidate the output commitment.
An anchor is a JSON object with the following members:
+========================+==========+===============================+
| Field | Required | Meaning |
+========================+==========+===============================+
| type | MUST | "aer1-anchor". |
+------------------------+----------+-------------------------------+
| version | MUST | Anchor schema version. "1" |
| | | in this specification. |
+------------------------+----------+-------------------------------+
| receipt_id | MUST | Identifier of the anchored |
| | | record. For a single |
| | | receipt, the receipt's id |
| | | member (Section 3). For a |
| | | workflow receipt, the |
| | | workflow receipt's |
| | | receipt_id member |
| | | (Section 8); this is the |
| | | record identifier, not the |
| | | workflow_id. For a job |
| | | timeline, the job_id of the |
| | | chain commitment |
| | | (Section 7.3). |
+------------------------+----------+-------------------------------+
| anchored_digest | MUST | The anchored digest as 64 |
| | | lowercase hexadecimal |
| | | characters, with no |
| | | algorithm prefix. For a |
| | | single receipt: the hex |
| | | digest carried by the |
| | | output_hash member |
| | | (Section 3), i.e. the |
| | | output_hash value with the |
| | | leading 'sha256:' removed. |
| | | For a workflow receipt: the |
| | | merkle_root value |
| | | (Section 8.1). For a job |
| | | timeline: the |
Zambo Expires 22 April 2027 [Page 36]
Internet-Draft AER-1 October 2026
| | | final_entry_digest of the |
| | | chain commitment (Section |
| | | 7.3). |
+------------------------+----------+-------------------------------+
| anchor_type | MUST | One of "nostr", "ots", |
| | | "witness", "scitt", or |
| | | "rfc3161". See below. |
+------------------------+----------+-------------------------------+
| anchored_at | MUST | RFC 3339 timestamp of |
| | | anchor publication. |
+------------------------+----------+-------------------------------+
| proof | MUST | Anchor-type-specific proof |
| | | object (see below). |
+------------------------+----------+-------------------------------+
| record_url | OPTIONAL | Public URL where the anchored |
| | | record can be retrieved. |
+------------------------+----------+-------------------------------+
Table 4: Anchor members
The proof object for anchor_type "nostr" carries: event_id (the Nostr
event identifier, 64 lowercase hex characters), pubkey (the
publisher's public key, 64 lowercase hex characters), and relays (an
array of relay URLs where the event was published, each a non-empty
string).
The proof object for anchor_type "ots" carries: ots_proof (a
base64-encoded OpenTimestamps proof) or ots_request (a base64-encoded
pending OTS request, with status "pending").
The proof object for anchor_type "witness" carries: witness_url (a
stable public URL where the witness record can be retrieved without
an account) and witness_statement (a human-readable description of
what the witness attests).
The proof object for anchor_type "scitt" carries: service (the URL of
the SCITT Transparency Service with which the anchored_digest was
registered as the payload digest of a Signed Statement [RFC9943]) and
cose_receipt (a base64-encoded COSE receipt: the inclusion proof the
service returned for that registration).
The proof object for anchor_type "rfc3161" carries: timestamp_token
(a base64-encoded DER TimeStampToken issued by an RFC 3161 timestamp
authority over the anchored_digest) [RFC3161].
The anchored_digest member has the lexical form anchored-digest =
64HEXDIG, with the lowercase restriction stated for digest strings in
Section 3.
Zambo Expires 22 April 2027 [Page 37]
Internet-Draft AER-1 October 2026
Privacy bound. An anchor MUST NOT contain step content, tool inputs,
tool outputs, canonical bytes, or any data beyond the members in
Table 4. The anchored_digest is a hash; hashes of low-entropy or
guessable inputs may be vulnerable to dictionary attacks, and
verifiers and publishers MUST NOT treat anchoring as a
confidentiality mechanism.
What anchoring proves. A verified anchor proves that the
anchored_digest existed no later than the anchor's publication time.
The proof lives on infrastructure outside the receipt itself: Nostr
relays, the Bitcoin blockchain, or a witness's publication point.
Verification confirms the proof is present on that infrastructure and
is correctly signed against proof.pubkey. It does not establish who
controls the signing key. A verifier MUST NOT present a verified
anchor as proof that the witness is independent of the receipt
publisher unless that independence is established out of band (for
example, a well-known witness key or a key the verifier already
trusts). A "witness" anchor whose witness record is publisher-
controlled provides no independent witnessing; it binds only the
digest to the publisher's own statement at the anchor time.
Anchoring does not prove the receipt's content was correct,
authorized, or truthful; it does not prove the publisher's identity
beyond what the anchor_type's own authentication provides; and it
does not by itself prove the absence of truncation (Section 7.3,
Section 8.4) unless the verifier also confirms the anchored digest
matches a recomputation over the complete record.
Anchor discovery. Anchor references are published alongside the
receipt at its public URL (Section 9) or at a documented anchor index
keyed by receipt_id, outside the frozen receipt bytes: adding an
anchor reference to the public page is presentational (Section 8.3)
and does not alter the published record. A verifier obtains the
anchor references from that location before running the Section 10.1
procedure; a receipt for which no anchors are published verifies
without anchor checks.
10.1. Anchor Verification Procedure
To verify an anchor, a verifier performs the following steps:
1. Confirm the anchor's receipt_id matches the identifier of the
record being verified per Table 4: the receipt id for a single
receipt, the workflow receipt_id for a workflow receipt, or the
chain commitment job_id for a job timeline.
2. Recompute the expected digest: for a single receipt, the hex
digest carried by output_hash (with the "sha256:" prefix removed)
per Section 6; for a workflow receipt, the merkle_root per
Zambo Expires 22 April 2027 [Page 38]
Internet-Draft AER-1 October 2026
Section 8.1; for a chain, the final_entry_digest per Section 7.3.
Compare it with anchored_digest. A mismatch MUST cause anchor
verification to fail.
3. Verify the proof according to its anchor_type: for "nostr",
resolve the event_id at one or more of the listed relays, verify
the event's Nostr signature against proof.pubkey, and confirm the
event content carries the same receipt_id and anchored_digest;
for "ots", verify the timestamp proof against the Bitcoin block
header it commits to; for "witness", retrieve the witness_url and
confirm the retrieved statement contains the anchored_digest
string verbatim; for "scitt", verify the COSE receipt against the
Transparency Service's published key and confirm the registered
statement's payload digest equals anchored_digest; for "rfc3161",
validate the TimeStampToken and confirm its message imprint is
the anchored_digest.
4. Report the anchor as verified only if steps 1 through 3 all pass.
A verified anchor confirms the digest existed at the anchor time;
it does not upgrade the receipt's provenance class (Section 5).
A workflow receipt MUST be retrievable by its receipt_id member as
well as by its workflow_id at its verify_url, so that either
identifier locates the same record. When an anchor carries a
record_url, retrieving that URL MUST return the anchored record, so
that the anchor reference alone suffices to locate it.
10.2. Binding Does Not Transfer Claims
Anchoring a digest (Section 10), including a receipt in a Merkle tree
or workflow (Section 8), chaining receipts (Section 7), or
referencing one record from another binds the records for integrity
checking. Binding does not transfer claims between the bound
records, in either direction. A verified anchor does not make the
anchored content true, authorized, or correctly recorded; a valid
receipt bound to an external assertion (for example, a claim about a
physical outcome) neither proves that assertion nor is invalidated by
the assertion's falsity; and a container receipt does not upgrade the
provenance class of its members (Sections 5, 8.2, 10.1). A verifier
MUST evaluate each bound record under that record's own verification
procedure and MUST NOT report a claim as verified solely because it
is bound to a verified record.
Zambo Expires 22 April 2027 [Page 39]
Internet-Draft AER-1 October 2026
11. Reference Implementation
A reference implementation of this vocabulary is deployed at the time
of writing, described at [AER-1-HOME]. The implementation exposes
the receipt contract through one MCP endpoint and gives successful
calls a public receipt page:
* Tool calls are made over HTTPS as JSON-RPC to
https://zambo.dev/api/mcp using the tools/call method. The
response contains the receipt identity and the public verification
link.
* Each receipt resolves at https://zambo.dev/run/<id> and can be
opened and checked by anyone, with no account or token.
* A verifier endpoint returns the machine-readable verification
result for a receipt identifier, as described in Section 6.
The endpoint and the receipt pages are independent live records:
anyone can reproduce the Section 6 procedure against them today.
Their availability is a deployment fact, not a standards claim; if
the deployment moves, the canonical home of this document's latest
revision is updated accordingly.
Implementers building against this vocabulary SHOULD start from the
open conformance kit (see Implementation Status): clone
https://gitlab.com/rambozambodotdev/zambo, run the conformance runner
for their language (for example, aer1-implementations/python/
conformance.py for Python) against the frozen vector corpus, and
treat byte-identical verdicts as the conformance bar. The kit
documents the vector inventory and the reference-producer profile
boundary that separates a core-valid receipt from one the reference
producer will emit.
12. Design Rationale
AER-1 starts with a narrow record rather than a universal theory of
agent behavior. An agent may call several tools, receive information
from several providers, and hand work to another agent. A reviewer
still needs a stable way to identify one execution, understand what
the system observed, and check whether the published bytes match the
stated commitment. The draft keeps those needs separate from claims
about the outside world.
The vocabulary is intentionally small because interoperability fails
when every implementation gives a different name to the same
boundary. An identifier locates the record. A creation time orders
it. A tool and caller scope identify the execution context.
Zambo Expires 22 April 2027 [Page 40]
Internet-Draft AER-1 October 2026
Canonical bytes and an output hash make the stored representation
checkable. A result preview gives a human a useful first read
without pretending that a preview is the entire source dataset.
Provenance is a separate design axis (Section 5). A system can
execute a tool itself, observe an action through an integrated
gateway, or receive a report from another agent. AER-1 treats these
as distinct attestations because they are distinct claims, and a
verifier that cannot tell them apart cannot report honestly.
On canonicalization: AER-1 preserves the producer's exact bytes and
carries them explicitly, rather than mandating a canonicalization
scheme such as the JSON Canonicalization Scheme [RFC8785]. A
verifier MUST NOT re-serialize the record under a different scheme
and claim agreement with the commitment. The cost of this choice is
stated plainly: digests computed under different canonicalizations do
not interoperate, so cross-implementation verification requires byte-
identical canonical content.
On resolution: AER-1 chooses public URL resolution as the primary
verification experience, so that checking a receipt needs no key
distribution and no trust anchor obtained out of band. The tradeoff
is availability dependence on the publisher; deployments that cannot
accept that tradeoff can pair the record with the external anchoring
in Section 10 or with offline recomputation from the carried
canonical bytes.
13. Security Considerations
A receipt is evidence of what was recorded, not a security boundary
by itself. The following considerations apply to implementations:
* The output commitment binds the recorded bytes, not the real
world. An implementation that records attacker-controlled input
produces a verifiable receipt of attacker-controlled input.
Verifiers MUST present the observed result as observed, never as
independently true.
* Canonical bytes MUST be preserved exactly. Any transformation
(re-encoding, whitespace normalization, character set conversion)
between recording and verification breaks the commitment and MUST
cause verification to fail closed.
Zambo Expires 22 April 2027 [Page 41]
Internet-Draft AER-1 October 2026
* Public receipt URLs are stable and permanent. A receipt that
embeds personal data, credentials, or confidential business
information in its canonical bytes publishes that data to
everyone; implementations MUST NOT publish such receipts without
redacting the sensitive data first. A receipt cannot be
unpublished.
* Provenance classes are attestations by the recording system about
itself. A dishonest recorder can mislabel provenance. Consumers
who need stronger guarantees should use the external anchoring in
Section 10 or bind receipts to independent witness records.
* The Section 7 chain digest binds entry metadata (sequence
position, job identifier, closing flag, receipt identifier, tool
name, provenance class) so metadata relabeling and cross-job
splicing are detectable by recomputation. It does not make the
chain truncation-proof: a rebuilt shorter chain verifies, so
deployments that need truncation detection MUST anchor an external
commitment (Section 7.3) and MUST NOT present the chain check
alone as sufficient.
* The Section 8 Merkle root binds the step sequence so that
insertion, deletion, reordering, or substitution is detectable by
recomputation, but it does not make the workflow truncation-proof:
a rebuilt shorter step list with a recomputed root verifies, so
deployments that need truncation detection MUST anchor the
merkle_root externally (Section 8.4, Section 10) and MUST NOT
present the Merkle check alone as sufficient.
* A standalone receipt (Section 3) is not bound to a job, session,
or principal beyond its own identifier. A receipt presented as
evidence for a different execution than the one it records is a
replay, and this specification provides no standalone-receipt
replay defense. Consumers that need replay resistance MUST use
chained timelines (Section 7, which binds job_id per entry) or
workflow receipts (Section 8, which binds steps to a workflow_id),
or bind the receipt identifier to their own session context out of
band. A bare receipt proves what was recorded for one execution;
it does not prove the execution belongs to any particular job.
* Anchored digests are public. SHA-256 of a low-entropy input (a
boolean, a small number, a value from a guessable set) can be
reversed by enumeration. Anchoring a digest does not keep the
preimage confidential. Where the committed bytes are sensitive
but the commitment must be public, use a high-entropy salt or a
keyed commitment and document the construction; a verifier that
cannot reproduce the salted bytes MUST NOT report the commitment
as confirmed (Section 4).
Zambo Expires 22 April 2027 [Page 42]
Internet-Draft AER-1 October 2026
* The created_at timestamp is asserted by the producer.
Verification checks its form and that it denotes a valid calendar
date and time; it does not check that the stated time is the true
time, and a verifier MUST NOT present created_at as externally
established time. The external time evidence for a receipt is its
anchors (Section 10): a verified anchor proves the anchored digest
existed no later than the anchor's publication time. Absent an
anchor, a receipt carries no trusted time.
* A producer can equivocate: present different records, or different
chain or workflow heads, to different verifiers. The core record
does not prevent this. The mitigations are the stable public URL
(Section 9), external anchors on third-party infrastructure
(Section 10) compared against previously anchored values rather
than against counts alone, and, for deployments that need a
single-history guarantee, registration with a transparency service
as a "scitt" anchor (Section 10).
* Chain verifiers MUST distinguish the -07/-08/-09/-10/-11/-12/-
13/-14 construction construction from the historical -06
construction and MUST NOT report a -06 chain as satisfying the -
07/-08/-09/-10/-11/-12/-13/-14 construction rules (Section 7.4).
* Verification endpoints MUST NOT leak information beyond the
receipt record itself, and MUST rate-limit verification requests
to prevent the endpoint from becoming an oracle for probing non-
public executions.
* This specification sets no maximum size for canonical_bytes, steps
arrays, or job timelines. Implementations MAY set local size
limits and reject records exceeding them, but a rejection on size
grounds MUST be reported as "rejected: size limit" and MUST NOT be
reported as "verification failed," since the record may verify
correctly under a higher limit. Implementations SHOULD document
their limits alongside the verifier.
* For receipts without metadata_hash (schema "0.3" and earlier), the
output commitment binds the recorded bytes but not the metadata: a
relabeled copy with identical canonical bytes verifies under
Section 6. A forger can also strip metadata_hash and rewrite the
schema version to "0.3". Consumers that need tamper-evident
metadata MUST require metadata_hash (schema "0.4" or later), or
use chained timelines (Section 7) or workflow receipts
(Section 8).
* The receipt id is an identifier label, not a committed field of
the output commitment. Replacing a receipt's id with the id of
another receipt, every other byte unchanged, does not disturb the
Zambo Expires 22 April 2027 [Page 43]
Internet-Draft AER-1 October 2026
output commitment: the relabeled record verifies under Section 6
exactly as the original did. The swap breaks nothing about the
recorded bytes and proves nothing false about them. What it
breaks is the association between the record and the execution its
presenter claims it documents. That association is established
only outside the record: by comparison against an independently
supplied expected id, or by resolving the id under Section 9 and
comparing the resolved record's output_hash against the presented
record's. A verifier that performs neither check has verified
bytes, not identity, and MUST NOT report the id as verified.
Where a receipt carries metadata_hash and a verifier recomputes it
under Section 6, an id change that leaves metadata_hash untouched
is detected, because the metadata block of Section 3.1 includes
the id; a record whose metadata block is recomputed wholesale is
indistinguishable by any in-record check, which is why the
external comparison, not another in-record field, is the control
for the swap.
* Issuer authentication is not part of this specification. Anyone
can mint a well-formed receipt for any tool name, any provenance
class, and any bytes; verification proves the recorded bytes were
not altered after minting, not who minted them and not that the
minting party was authorized to speak for the named tool. A
verifier MUST NOT present a passing check as proof of issuer
identity. Consumers that need to bind a receipt to its issuer
MUST use an out-of-band trust root or a signature scheme
documented alongside the receipt; this specification defines no
signature scheme. Without such binding, the receipt is self-
asserted as to origin.
14. IANA Considerations
This document has no IANA actions.
15. Related Work
Action receipts for AI agents are an active area with multiple
concurrent individual proposals, occupying distinct slots. The SCITT
architecture [RFC9943] defines transparency services that register
signed statements and return inclusion proofs; [NOA] profiles an
agent action receipt as such a signed statement. [NELSON] specifies
authorization receipts: records of what a principal permitted, a
different record from what executed. [XAIP], [SAHU], and [VAARA]
specify signed per-call receipts whose digests are computed over
canonicalized JSON [RFC8785]. [SHARIF] specifies an agent audit
trail: an operator-side log format with regulatory mappings and
transport exports, rather than publicly resolvable per-execution
records. [KLASSEN] specifies content profiles that carry verdicts
Zambo Expires 22 April 2027 [Page 44]
Internet-Draft AER-1 October 2026
about outputs and are designed to ride over other record formats.
Naming these slots is positioning, not endorsement.
AER-1 composes with the transparency-service slot rather than
competing with it: an AER-1 anchored_digest MAY be registered with a
SCITT Transparency Service, and the resulting inclusion proof is
carried as an anchor of type "scitt" under Section 10. AER-1 core
verification neither requires nor is changed by that registration.
AER-1 differs in three ways. First, the receipt is identified by a
UUID and resolved at a stable public URL, with verification offered
as a public HTTP endpoint rather than offline recomputation. Second,
it carries provenance (executed, observed-via-gateway, logged-by-
agent) as a first-class field, so a verifier can distinguish what the
recording system did from what it merely recorded. Third, a
reference implementation is deployed whose receipts are publicly
verifiable without an account, key, or token. AER-1 standardizes the
narrow per-execution record and its public verification procedure,
leaving decision semantics, authorization, and settlement bindings to
other specifications.
16. Normative References
[FIPS180-4] National Institute of Standards and Technology, "Secure
Hash Standard", DOI 10.6028/NIST.FIPS.180-4, FIPS 180-4, August 2015.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997, <https://www.rfc-
editor.org/rfc/rfc2119>.
[RFC3161] Adams, C., Cain, P., Pinkas, D., and R. Zuccherato,
"Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)",
RFC 3161, August 2001, <https://www.rfc-editor.org/rfc/rfc3161>.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:
Timestamps", RFC 3339, July 2002, <https://www.rfc-editor.org/rfc/
rfc3339>.
[RFC4122] Leach, P., Mealling, M., and R. Salz, "A Universally
Unique IDentifier (UUID) URN Namespace", RFC 4122, July 2005,
<https://www.rfc-editor.org/rfc/rfc4122>.
[RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data
Encodings", RFC 4648, October 2006, <https://www.rfc-editor.org/rfc/
rfc4648>.
Zambo Expires 22 April 2027 [Page 45]
Internet-Draft AER-1 October 2026
[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119
Key Words", BCP 14, RFC 8174, May 2017, <https://www.rfc-
editor.org/rfc/rfc8174>.
[RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
Interchange Format", STD 90, RFC 8259, December 2017,
<https://www.rfc-editor.org/rfc/rfc8259>.
17. Informative References
[AER-1-HOME] Zambo, B., "AER-1: AI Agent Execution Receipt (open
draft)", https://zambo.dev/aer-1, September 2026.
[KLASSEN] "draft-klassen-eu2122-content-profile-03", Internet-Draft,
Work in Progress.
[NELSON] "draft-nelson-agent-delegation-receipts-12", Internet-Draft,
Work in Progress.
[NOA] "draft-noa-scitt-ai-agent-receipt-01", Internet-Draft, Work in
Progress.
[RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax
Specifications: ABNF", STD 68, RFC 5234, January 2008,
<https://www.rfc-editor.org/rfc/rfc5234>.
[RFC7942] Sheffer, Y. and A. Farrel, "Improving Awareness of Running
Code: The Implementation Status Section", BCP 205, RFC 7942, July
2016, <https://www.rfc-editor.org/rfc/rfc7942>.
[RFC8785] Rundgren, A., Jordan, B., and S. Erdtman, "JSON
Canonicalization Scheme (JCS)", RFC 8785, June 2020,
<https://www.rfc-editor.org/rfc/rfc8785>.
[RFC9943] Birkholz, H., et al., "An Architecture for Trustworthy and
Transparent Digital Supply Chains", RFC 9943, <https://www.rfc-
editor.org/rfc/rfc9943>.
[SAHU] "draft-sahu-agent-action-receipts-00", Internet-Draft, Work in
Progress.
[SHARIF] "draft-sharif-agent-audit-trail-07", Internet-Draft, Work in
Progress.
[VAARA] "draft-sirkkavaara-vaara-receipt-13", Internet-Draft, Work in
Progress.
Zambo Expires 22 April 2027 [Page 46]
Internet-Draft AER-1 October 2026
[XAIP] "draft-xkumakichi-xaip-receipts-03", Internet-Draft, Work in
Progress.
18. Acknowledgments
Matthew Moore provided the first substantive independent technical
review of this draft on the agent2agent list, identifying the
duplicate receipt_id Merkle collision (Section 8.2), the unpinned
Section 7 chain construction and its suffix-truncation weakness, and
the stale test-vector count. The -06 revision folded those findings
into the specification and the conformance kit. Moore then ran eight
chain attacks from the VLC-1 project against the -06 construction and
showed all eight were accepted, confirming the digest bound too
little. The -07 revision hardens the Section 7 entry digest in
direct response: the digest now binds the entry's sequence position,
job identifier, closing flag, receipt identifier, tool name, and
provenance class, and the section states the honest truncation limit
his testing established. Moore then cross-tested the -07
construction with thirty cases from VLC-1, confirming all fourteen
Section 7 attack cases produced the specified outcomes, and
identified the last-entry digest gap: the final chain entry's digest
is referenced by nothing inside the chain, so the external truncation
commitment should bind it. The -08 revision adds that guidance to
Section 7.3. The -09 revision defines the optional chain commitment
artifact in Section 7.3 and the conformance kit check that verifies
it.
ARION produced the first complete independent implementation of AER-
1, a zero-dependency Node.js verifier written from the -08 draft text
alone, and filed three specification findings that the -09 revision
adopts.
ColonistOne (CMO of The Colony) produced an independent Perl
implementation of the -09 draft, verified 68/68 against the
conformance kit with zero disagreements against either reference
runner, and filed three specification findings that the -10 revision
adopts: the under-specified Section 7.1 string escaping (including
the U+007F divergence between the draft text and the reference
implementation), the Python-version-dependent timestamp check, and
nine conformance rules lacking isolating vectors.
Token Maxxer identified the workflow-receipt truncation gap that the
-12 revision closes in Section 8.4: the observation that a hash
chain's honest truncation limit applies equally to the Section 8
Merkle root, and that the draft's silence on the point was an
inconsistency.
Zambo Expires 22 April 2027 [Page 47]
Internet-Draft AER-1 October 2026
Leo Oliemans raised the unknown-versus-not-applicable distinction and
the write/readback outcome gap that the -18 revision addresses in
Section 3.2. The agent penny filed the findings behind Section 3.4
(per-field provenance, the negative record, reversal state, and the
method descriptor) and the unauthenticated-metadata wording adopted
in Section 3.2. czh raised the Unicode normalization question
answered in Section 4, and dumate-scout proposed the
execution_context invalidation signal noted in Section 3.2. The -18
revision folds those reports.
Iman Schrock of the Emilia Protocol reported that the workflow
descriptions in Sections 8.5 and 8.7 named step output hashes as the
Merkle root's leaf input where Section 8.1 specifies step receipt
identifiers, so the two descriptions give different roots for the
same workflow. The -19 revision aligns Sections 8.5 and 8.7 with
Section 8.1, and the conformance corpus adds the one-step test vector
the report requested.
Genesis, an independent agent on The Colony, built a conformance
verifier from the -19 draft text alone, with no reference
implementation import, and reported three gaps that the -20 revision
adopts: the kit quickstart's core tier did not state the provenance-
label and schema-version metadata checks (now its ninth rule),
Section 3.3 did not require a stated member ordering rule for
recorded JSON inputs (now required), and the Section 5 provenance
labels carried no rule against control characters (now barred, with
rejection required).
Gigawatt, an independent reviewer on The Colony, proposed two
negative controls against the Section 3 example receipt and predicted
their outcomes in advance: a one-byte mutation of the canonical bytes
fails verification, as specified, and substituting another receipt's
id for the example's id is not detected by the output commitment.
Both controls were run with the published tooling and the outcomes
published. The -21 revision adopts the finding: Section 6 states the
id-binding rule normatively, and Section 13 describes the
substitution case.
Zambo Expires 22 April 2027 [Page 48]
Internet-Draft AER-1 October 2026
WhiteMagic Labs (Lucas Bailey) built and operates an independent
production validator for this specification, recorded in the
Implementation Status. Exercising it against this draft on
2026-10-17 produced a same-day exchange of findings in both
directions: a base64 decoding-boundary defect in the validator, fixed
by its operator, and a nil-UUID acceptance gap in the reference
verifier, fixed in the kit (aer1-verify 0.2.9). A design
disagreement on expected-id binding was argued and resolved to the
model Section 6 states, with the operator's provenance caution
retained as advisory metadata; that discussion is the origin of the
channel note in Section 6.
Skitter (SwarmMemo) replayed the verifier case matrix independently
and filed three findings the -22 revision adopts: a non-canonical
base64 spelling (a pad-bit flip that decodes byte-identically) which
the reference verifier accepted and the draft did not address
(Sections 3 and 4, with a corpus vector), the under-specified legacy
member set behind the schema 0.3 production shape (Section 6), and
the bare-versus-enveloped presentation divergence (Section 3). The
first two also produced kit fixes ahead of this revision (aer1-verify
0.2.10).
Skitter also exercised an independent validator against the corpus
fixture whose anchor record cannot be resolved and showed that the
validator's response could not distinguish an anchor it had not
evaluated from one it had evaluated and failed to resolve: the
response carried no "not checked" state. a-messenger stated the
reporting rule the finding implies: "not checked" is not "attempted
but unresolved". The -23 revision adopts the rule in Section 6; the
kit implements it (anchor-vectors.json in the corpus; aer1-verify
0.2.13) ahead of this text.
Longcat proposed the enumerated id verdict of Section 6, and then the
two refinements that make it binding rather than decorative: the
states MUST bind verifier behavior, with one corpus vector per state,
and the resolution order between the structural layer and the id
layer MUST be stated. Both refinements are adopted. ronin_audit's
field note, "print the reason next to the rule", is adopted in
Section 6 as a reporting requirement.
Appendix A. Mapping Note: EU AI Act Article 12 (Informative)
This appendix is informative and non-normative. Article 12 of the EU
AI Act addresses record-keeping for high-risk AI systems: such
systems are to be designed so that events are recorded automatically
over the system's lifetime, at a level of traceability appropriate to
the intended purpose. An AER-1 receipt is created automatically at
execution time and records the execution with a timestamp, the
Zambo Expires 22 April 2027 [Page 49]
Internet-Draft AER-1 October 2026
recorded bytes, and tamper evidence, so a receipt can form part of
the records such a system keeps. A receipt alone does not satisfy
Article 12 or any other legal obligation: what must be recorded, how
long records are kept, and who may access them are deployment and
legal questions outside this specification.
Author's Address
Brennan Zambo
Zambo
Email: brennanzambo@zambo.dev
URI: https://zambo.dev/aer-1
Zambo Expires 22 April 2027 [Page 50]