Internet-Draft SCITT AI-Agent Action Receipts August 2026
Toraman Expires 16 February 2027 [Page]
Workgroup:
SCITT
Internet-Draft:
draft-noa-scitt-ai-agent-receipt-01
Published:
Intended Status:
Standards Track
Expires:
Author:
T. Toraman
NordenSoft

A SCITT Profile for AI-Agent Action Receipts

Abstract

This document profiles the IETF SCITT (Supply Chain Integrity, Transparency, and Trust) architecture for AI-agent action receipts: tamper-evident, signed, offline-verifiable records of what an autonomous agent was recorded as doing at the governed boundary, under which recorded principal class, with what recorded verdict, and -- where the issuer records one -- under which policy identity. Each receipt is a signed record over a canonical JSON payload, hash-chained so that each record commits to its predecessor, and presented either bare -- the payload with its own native signature -- or enveloped in a COSE_Sign1. This revision specifies how such a receipt is carried as a SCITT Signed Statement, with the protected claims a Transparency Service requires, so that a receipt can be registered. Registration obtains a Transparency Service's signed proof that the statement was registered in its log -- a property a self-signed chain cannot provide alone. It does not, by itself, give an offline holder non-equivocation: that requires consistency proofs and monitoring of the log, which this profile does not specify. The profile makes a deliberately narrow, checkable claim: this is an issuer-authenticated, signature-verifiable, tamper-evident record of the action, the recorded principal class, the recorded verdict, and any policy identity the receipt carries. It explicitly does not claim that the agent was correct, safe, or wise, that the recorded inputs were true or complete, that a named approver authorized this exact action before it ran, that a downstream controller succeeded, or that any physical effect occurred. This revision separates those last three as distinct claims with independent failure behaviour, states the boundary of a shared action digest, and keeps a deterministic offline policy-replay capability out of scope.

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 16 February 2027.

Table of Contents

1. Introduction

An AI agent books the flight, moves the money, or tells a machine on a factory floor to move an object. Afterwards someone asks a plain question: what actually happened, and who can prove it?

Today the answer usually comes from the party that ran the agent: its own logs, its own summary, its own word. This creates a first-party evidence problem when an action is disputed.

This document defines a receipt: one small, signed record per governed agent action, describing what was done, under which recorded principal class, with what verdict, and -- when the issuer records it -- under which policy. A bare receipt can be checked by anyone holding the issuing key; an enveloped one is checked against that key and the key that signed the envelope, which need not be the same (Section 4.2). Either way the check needs no access to the agent, the vendor, or any running service. It is deliberately narrow. A receipt proves that this record was issued by that key and has not been altered since. It does not prove that the agent was right, that its inputs were true, or that the world changed the way the record suggests.

Being narrow is the point. A record that promises more than it can support is worse than no record, because a relying party cannot tell where the support ends. This profile therefore states, in normative language, the things a receipt does not establish -- and, where the industry needs those things established, points at the separate documents that do it.

Three documents, one system. This revision is written as one part of a shipped, measured architecture, and a reader should be able to see the whole shape from here:

The division of labour is the argument: the record says what the governed boundary decided or dispatched, the digest says which action a later document is about, and the witness says what an outside system observed. Each fails on its own (Section 8.1), and no one of them is asked to carry the others' weight.

The gap this fills is a specification gap, not only a product one. Protocol specifications for agent connectivity and orchestration, and the agent runtimes built on them, leave the accountability layer -- a standardized, tamper-evident, issuer-authenticated record of an agent's actions -- open. OWASP's MCP Top 10 lists "Lack of Audit and Telemetry" as MCP08:2025 [OWASP-MCP]. Several recent efforts emit signed PERMIT/DENY decision receipts for agent actions; what is missing is a single, SCITT-native, COSE-based profile for per-action receipts that is (a) carriable as a Signed Statement for registration in a Transparency Service -- a log a third party can check for consistency -- and (b) honest about exactly what a receipt does and does not prove.

This profile does not define a new signature container. It defines an application payload profile carried in a COSE_Sign1 [RFC9052], so the outer signature can be verified by conforming COSE implementations that support the fully-specified Ed25519 algorithm identifier used by this profile. Section 4.3 specifies the additional protected claims that make one an RFC 9943 Signed Statement, and states plainly which of those the reference implementation does not yet emit.

What this revision adds. Review on the SCITT mailing list in July 2026 found that the single phrase "real-world outcome" in -00 hides three claims that a consumer must be able to fail separately: whether a named person authorized this exact action before it ran, what a downstream machine reported afterwards, and whether the intended physical effect actually occurred. Section 8 and Section 9 pull them apart and require each to be reported as a result a verifier renders, not a caveat a reader has to remember. Section 10 draws the boundary around the value that lets records about one action be joined at all. Appendix A tells the whole story of this revision -- what was asked, what was built and measured, and what the document now guarantees -- and every change is listed there.

The base record noa.receipt/0.1 is not modified: no member is added, removed, or re-typed. What that guarantees is byte-level and nothing wider: every receipt, hash chain and checkpoint produced under -00 still hashes to the same value, its signatures are still signatures over the same bytes, and no linkage is invalidated. It does not follow that a -00 receipt is accepted by a -01 verifier -- acceptance is a separate question, answered in the next paragraph.

It does not guarantee that every -00 receipt is valid under -01, and the difference is worth enumerating rather than glossing. This revision states validity conditions -00 did not: the verdict, risk-class and governance-mode vocabularies are closed with a MUST-reject (Section 2); the object is closed against unknown members (Section 3); a timestamp must be a valid RFC 3339 date-time and not merely shape-correct (Section 3.3); id is bounded at 128 code points, seven members must be non-empty strings and a present governance.compliance.verdict must be ALLOW or DENY (Section 3.1); sig.alg must be the exact literal ed25519 (Section 4.2); and the cross-field rules of Section 3.1 refuse combinations -00 permitted. A receipt carrying an out-of-vocabulary verdict, an unknown member, an impossible instant, an empty or over-long identifier, or contradictory simulation markers would have passed a permissive -00 verifier and is malformed here. A verifier's NFC configuration moves the verdict on non-NFC strings in both directions (Section 3.3).

For a document adopting this profile's chain convention by reference (Section 1.2), the practical statement is therefore: the construction is unchanged; the acceptance rules are stricter.

1.1. Scope and the adjacent slot

Two related SCITT-AI drafts occupy different slots: an EU AI Act Article 50 disclosure profile (what content was generated and disclosed) and a session-archive format (a portable bundle of a whole agent session). This profile is orthogonal to both: it defines the per-action signed receipt -- one signed record per agent action, carried bare or in a COSE_Sign1 (Section 4), and holding the action, the recorded principal class, the recorded verdict, and any policy identity the issuer chose to record.

Three slots adjacent to this one are named here because this revision defines its boundaries against them rather than against prose: a reconciliation layer that admits heterogeneous attestations about one action and reports per-source verdicts [I-D.hillier-scitt-arp]; an executor-side admission boundary that decides whether an action may be consumed [I-D.schrock-action-evidence-boundary]; and a correlation value that lets records produced by parties with no shared schema commit to the same action projection [I-D.toraman-noa-action-digest]. None of the three is a dependency of this profile, and this profile is not a dependency of them.

1.2. Relationship to other work

  • SCITT Architecture [RFC9943]: this is a Signed Statement profile; it adds AI-agent-action semantics, not new transparency machinery. (-00 cited this as a work-in-progress draft; it was published as an RFC in June 2026.)
  • Signed decision receipts for machine-to-machine access control [ACTA]: a custom-JSON receipt field catalogue covering the same ground in a custom JSON envelope; this profile is SCITT-native (COSE_Sign1) where that format requires an adapter to register. A field-by-field mapping between them changes no requirement here and is left to whoever needs to port one.
  • draft-dawkins-scitt-ai-article50 [AI-ART50], draft-stone-aivs [AIVS], draft-marques-asqav-compliance-receipts [ASQAV], draft-nivalto-agentroa-route-authorization [AGENTROA]: adjacent work, none of it a SCITT-native per-action receipt profile. The first two target regulatory disclosure and session archives; the third is a compliance profile of signed action receipts, which overlaps this one in subject and not in mechanism -- it is not a COSE/SCITT profile and defines no registration path; the fourth authorizes agent routes rather than recording actions.
  • draft-kamimura-scitt-vcp [SCITT-VCP]: a domain SCITT profile (algorithmic-trading audit) whose structure this document follows.
  • draft-wilder-scitt-physical-site-engage-receipt [PSER]: a physical-site engagement profile that adopts the hash-chain convention of Section 5 by reference. That section's number, title and anchor are unchanged from -00. Two sentences of its text were factually wrong in -00 and are corrected in this revision; Appendix A gives the before and after, and an implementer of [PSER] should read that entry before relying on -00's wording. Its new subsection Section 5.1 adds Chain-Verifier-class requirements that -00 did not state: a document that adopts Section 5's convention to build receipts is unaffected, while a verifier claiming conformance to -01 takes on those obligations.
  • The action digest and one of its consumers [I-D.toraman-noa-action-digest], [I-D.toraman-noa-settlement-evidence], both published 14 August 2026: a domain-separated correlation value -- one value computed under a fixed public rule, so two parties can tell they mean the same action -- and a profile that carries it into an external system. Both are referenced, neither is required; Section 10 states what a receipt does and does not contribute to such a construction.

1.3. Requirements Language

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.

1.4. Actors

Requirements in this document are written in one of two forms. Most name their actor directly, and most sections address a single actor and say so in their first sentence; where a section carries requirements for more than one, each requirement names its own and the section preamble lists them. The rest are written about an artifact -- "a receipt MUST contain no member other than those shown", "sig.value MUST be canonical base64", "the kid SHOULD be carried in the protected header" -- a shorthand used deliberately for schema and encoding constraints, because writing each of them twice would bury them.

An artifact-form sentence binds the Producer that builds the artifact, at the strength written. It does not, on its own, bind the verifier that receives it, and the two strengths are not symmetric: a Producer MAY is a permission, and the corresponding verifier duty is to ACCEPT what that permission allows, not to enjoy the same latitude. Section 4 shows the shape — kid MAY be carried in the unprotected header, and a verifier MUST resolve it from either bucket.

A verifier's disposition is therefore stated explicitly wherever the profile requires one: an artifact-form MUST is paired with the rejection it implies, and an artifact-form SHOULD or MAY is paired with the acceptance a verifier owes a conforming artifact that exercises the permission. An artifact-form SHOULD is never, by itself, a ground for a verifier to reject. Where this document states a Producer SHOULD and assigns no verifier disposition, none is required: the receiver's behaviour is unconstrained and conformance is not tested on it.

A sentence that fits neither form is an editorial error rather than a hidden obligation. Two kinds of sentence deliberately carry no keyword and no obligation: the blocks marked non-normative, and statements about what a future revision might do. Seven actors appear: three verifiers of increasing scope, and four parties that make, operate, register and consume. A section states its actor in its first sentence where the whole section is addressed to one; where a section mixes actors, each requirement names its own.

Table 1
Actor Holds / is given Is required to
Producer (Issuer) the signing key, and the facts it records build receipts to Section 3, Section 3.1 and Section 3.2, and sign them per Section 4.2
Receipt Verifier one receipt; a keyring; optionally an identity manifest check that one object: shape, canonical form, the recomputed chain.hash, the signatures it is given, and -- when a manifest is supplied -- attribution
Chain Verifier a presented set of receipts; a keyring; optionally a checkpoint and an identity manifest everything above for each member, plus the set-level conditions of Section 5.1
Evidence Verifier (not a conformance role in this revision) all of the above, plus the separately signed artifacts of Section 8 and Section 9, a trust configuration, and the consumer intent it was asked to answer everything above, plus authorization, outcome, signer relationship and sufficiency for that intent -- described here, but NOT testable against this document, because those artifacts, that configuration and the result representation are all left undefined (Section 1.5). Nothing in this revision can be conformant or nonconformant as an Evidence Verifier; the passages addressed to it are analysis, and a future revision or companion must define its inputs and outputs before the role can carry conformance
Transparency Service a submitted Signed Statement, and a means of resolving the registrant's verification key apply the Registration Policy of Section 7, which states which of its checks are conditional on what it can resolve
Relying Party a verifier's result, and any receipts it chose to keep read that result within the limits Section 8 states; where it compares, indexes or alerts on receipt fields itself, apply the byte-comparison and parsing rules of Section 3.1 and Section 13
Deployment (Operator) the running system: producers, verifiers, keys, and what is presented or registered choose the configurations this profile leaves open -- identifier syntax, NFC mode, resource limits, checkpoint issuance and freshness, what is registered -- and present complete chains for verification

Three rules govern the table, and each closes a way of misreading it.

  • Inputs are part of the obligation, and an input may be empty. A requirement addressed to an actor assumes the inputs in its row. Where a requirement needs an input that may be absent -- a checkpoint, an identity manifest -- it is written conditionally ("when a checkpoint is supplied ... MUST"), and an actor that was not given the input has not failed the requirement. The keyring is different from those two and the difference matters: it is a listed input of both verifier roles, and an implementation that accepts it but is handed an empty one is still that actor, still conformant, and simply cannot authenticate anything. That is the state Section 5.1 means by "holds no keyring" -- an accepted input with nothing in it, not an implementation that refuses the input and thereby declines the role.
  • A wider verifier inherits the narrower one's checks, and nothing else. A Chain Verifier performs every Receipt-Verifier check on each receipt in the set, because the set-level conditions are meaningless over objects that are not individually valid. It does not inherit Evidence-Verifier obligations, and no verifier inherits Producer obligations.
  • One role per claim of conformance. An implementation conforms to the role whose obligations it performs. Verifying only receipts is Receipt-Verifier conformance and nothing more; a Receipt Verifier that cannot detect a chain-level condition is conformant, because it was never given the set.

1.5. What this profile does not define

Several requirements in Section 6.1, Section 8, Section 9 and Section 10 are addressed to the Evidence Verifier over objects whose wire formats are not specified here. (The attribution rule of Section 6 is not among them: it is a Receipt-Verifier requirement, conditional on a manifest being supplied.) This section lists them once, so that no reader mistakes a boundary for an omission and no implementer mistakes an unimplementable MUST for an interoperability requirement:

  • the identity manifest (binding agent.id to authorized key identifiers): its encoding, distribution, signing, versioning and revocation rules;
  • the approval artifacts of Section 9 -- hold, decision, execution grant, consumption and uncertainty records -- and therefore grant identifiers, single-use nonces, audiences and expiries;
  • the outcome artifacts of Section 8 -- controller-reported outcome and physical observation;
  • the trust configuration from which a signer relationship is derived, and the digests that identify it;
  • a shared action digest, defined separately in [I-D.toraman-noa-action-digest];
  • the result schema in which a verifier reports any of the above. This revision names result tokens in several places -- the six chain conditions and the two named checkpoint-axis outcomes, the four absence results, the relationship vocabulary, the intents, UNTRUSTED, and the per-receipt outcomes VALID, MALFORMED and TAMPERED -- so that implementations describing the same finding use the same word. Two results are deliberately not tokenised in this revision: the conflict result of Section 8.2 and the unproven-authorization result of Section 9. A third case that might look like one is not: the indeterminate comparison of Section 10 is reported with Section 8.3's INDETERMINATE, which is a token this document defines. An implementation MAY report the two untokenised results under a name of its own; what it MUST NOT do is present that name as this profile's vocabulary, since a second implementation reading this document will not have chosen the same one. Whether they should be tokenised here is left to the working group (Section 15). This revision also does not define how any of them are carried, so two implementations will not be wire-compatible in their outputs until something does.

Where such an object is absent, the corresponding requirement is not a requirement a conforming implementation can fail: it has nothing to evaluate, and Section 8 states what a bare receipt cannot establish. Deployments that need these claims need those artifact definitions, which live in the companion documents and in the reference implementation cited in Section 12 -- not here.

2. Terminology

Definitions bind every actor. Where an entry carries a requirement it names its own actor: the entries below address the Producer (what it MUST emit), the Receipt Verifier (what it MUST reject), the Relying Party (what it MUST NOT read into a value) and the Deployment (what it selects).

3. Receipt structure (payload)

Every requirement below names its actor: construction rules bind the Producer, validation rules the Receipt Verifier, the tenant rule of Section 3.3 the Chain Verifier, and the comparison rules for identifiers and timestamps the Relying Party (Section 1.4).

The receipt payload is the JSON Canonicalization Scheme (JCS) [RFC8785] serialization of a JSON object. A Producer MUST emit integers only (no floating-point), and a Receipt Verifier MUST reject a payload carrying a non-integer number. String normalization is specified in Section 3.2: producers MUST emit NFC, and verifiers MUST NOT normalize. A verifier MAY be configured to refuse a non-NFC receipt, but a verifier that accepts one MUST NOT repair it, and in either mode a Relying Party MUST NOT infer that a verified receipt's strings are NFC (Section 3.3). The fields:

{
  "spec":   "noa.receipt/0.1",
  "id":     "<receipt id>",
  "ts":     "<RFC 3339 date-time>",
  "scope":  { "tenant": "<id>"?, "chain": "<chain id>" },
  "agent":  { "id": "<agent id>",
              "model": "<vendor/model|null>"?,
              "principal": "HUMAN|SERVICE|POLICY|SANDBOX_SIM" },
  "action": { "id": "<tool/action id>",
              "canonical": "<risk-table key>",
              "riskClass": "LOW|MEDIUM|HIGH|CRITICAL|IRREVERSIBLE",
              "paramsHash": "sha256:<hex>|hmac-sha256:<hex>",
              "reversible": <bool>,
              "rollbackRef": "<id>|null"? },
  "governance": { "mode": "<governance mode>",
                  "verdict": "<lifecycle verdict>",
                  "sandboxed": <bool>,
                  "ruleId": "<id>|null"?,
                  "approval": { "by": "<approver>",
                                "at": "<RFC 3339 date-time>" }|null?,
                  "compliance": { "policyHash": "sha256:<hex>",
                                  "readSetHash": "sha256:<hex>",
                                  "inputsHash": "sha256:<hex>",
                                  "verdict": "ALLOW|DENY"? }|null? },
  "chain":  { "seq": <int>,          ; >= 0
              "prevHash": "sha256:<hex>|null",
              "hash": "sha256:<hex>" },
  "sig":    { "alg": "ed25519",
              "kid": "<key id>",
              "value": "<base64 Ed25519 signature>" }
}

The object is closed. A receipt MUST contain no member other than those shown here, at any level; a verifier MUST reject an unknown member rather than ignore it. Extension by adding members is therefore not possible in noa.receipt/0.1 -- that is what makes the hash construction of Section 4.2 reproducible across implementations, and it is why Section 8.4 carries new claims in separate artifacts instead.

A ? marks a member that MAY be absent; every other member is REQUIRED, including sig. No member of this record is added, removed, or re-typed by this revision; -00's figure omitted sig and did not mark the optional members, and both omissions are corrected here to match the shipped schema (Section 3.3).

A Producer MUST NOT place raw prompts, tool arguments or secrets in any member, and the commitments this record carries are hashes rather than values. That is a rule about what a producer may write, not a property the format enforces, and its limit is worth stating precisely -- several members (id, agent.id, agent.model, action.id, action.canonical, governance.ruleId, governance.approval.by, action.rollbackRef) are constrained in length and emptiness at most (Section 3.1) and in content not at all, so a producer can put anything in them and no verifier can detect it. The prohibition binds producers; it is not a property a verifier can check, and Section 14 says what follows. paramsHash MAY be hmac-sha256:<hex> with a tenant-scoped key where a plain SHA-256 over a low-entropy value (an amount, an id, a boolean) would be guessable.

These commitments are per-producer, and this profile does not make them comparable. Neither action.paramsHash nor the three hashes of governance.compliance has a preimage construction defined here: what is hashed, in what serialization, under what domain separation, is the producer's own contract with whoever will later open the commitment. Two conforming producers given identical parameters can therefore compute different values -- by design rather than by omission. Section 10 explains why a cross-producer join key needs a construction identifier before any two parties may compare digests, and names the companion that defines one. A verifier MUST NOT compare these values across producers, and MUST NOT treat equality of them as evidence of anything. For the keyed form the same reasoning cuts harder: the key is a tenant secret, so an offline third party cannot check an hmac-sha256 commitment at all, and this profile defines no key identifier, rotation rule or distribution mechanism for it. A producer that rotates that key can still open earlier commitments for as long as it retains the retired key -- and cannot once it destroys it. Which of those happens is a deployment's key-management decision, not something this profile requires or can check; what it means is that the openability of a keyed commitment depends on a practice outside the record.

chain.hash is "sha256:" followed by the lowercase hex of SHA-256 over the JCS serialization of the receipt with chain.hash and sig.value removed; Section 4.2 pins that construction byte-exactly, together with the signature computed over the same digest. Receipts in one scope.chain are linked by prevHash; prevHash is null only at the genesis receipt (seq == 0).

3.1. Field semantics and cross-field rules

The cross-field rules address the Receipt Verifier; the emission obligations they imply address the Producer. The tenant rule of Section 3.3 addresses the Chain Verifier.

The figure above shows shapes; this subsection fixes the meanings a verifier may rely on, and the combinations it must refuse. Where a member's meaning is the issuer's to choose, that is said plainly rather than left to be inferred.

Notation. In the figure, null denotes the JSON null literal, never the four-character string "null"; a quoted alternative such as "sha256:<hex>|null" means "either that string form or JSON null". Angle brackets mark a value the issuer supplies.

The figure's types are normative. A Receipt Verifier MUST reject a receipt in which a member's JSON type differs from the one shown, rather than coerce it: <bool> means the JSON literals true or false and never the strings "true"/"false" or the numbers 1/0; <int> means a JSON number that is an integer within the range Section 3.2 fixes; every other member is a string except where null is shown as an alternative. Three shapes are fixed exactly, because a later comparison depends on them:

  • chain.seq MUST be an integer and MUST NOT be negative;
  • chain.hash, chain.prevHash when not null, and each of governance.compliance's policyHash, readSetHash and inputsHash MUST be "sha256:" followed by exactly 64 lowercase hexadecimal digits;
  • action.paramsHash MUST be that same form or "hmac-sha256:" followed by exactly 64 lowercase hexadecimal digits.

Uppercase hexadecimal is not an accepted spelling of any of them. These are byte-level rules, not presentation preferences: the digest is inside the signed payload, so two spellings of one digest are two different receipts.

spec. The exact string noa.receipt/0.1. A verifier MUST reject any other value rather than attempt a best-effort parse: this profile defines no version negotiation, no downgrade rule, and no forward-compatibility contract, and a future wire version will carry a different spec and its own signing domain (Section 8.4).

id, and identifiers generally. id identifies this receipt within its issuer's own naming. This profile defines no namespace, no uniqueness requirement across issuers, and no collision procedure -- and the same is true of scope.chain, agent.id, action.id, action.canonical, governance.ruleId, action.rollbackRef and governance.approval.by. Two issuers may use the same string for different things. A relying party that treats any of them as globally meaningful MUST supply the namespace itself, typically by scoping to the issuing key; equality of these strings across receipts from different issuers establishes nothing (Section 13).

Two syntactic bounds are pinned, because every implementation already enforces them and no document said so. Both bind the Producer on construction and the Receipt Verifier on validation:

  • id MUST be at most 128 Unicode code points, and a verifier MUST reject a longer one. The unit is code points, not UTF-16 code units: an identifier of 65 astral characters is 65 code points and 130 code units, so a verifier counting units rejects a receipt a verifier counting points accepts, on identical signed bytes.
  • Seven members MUST be non-empty strings, and a verifier MUST reject a receipt in which any of them is the empty string: id, scope.chain, agent.id, action.id, action.canonical, sig.kid and sig.value. governance.approval.by is deliberately not among them -- it is an opaque label this profile never authenticates (Section 6.1), and forbidding one of its values would suggest the others mean something. The checkpoint object of Section 5.2 carries the same rule for three further members, which is ten string boundaries across the two objects.

ts. The time the issuer recorded the receipt. It is not the time a decision was taken, nor the time an action was attempted, nor an observation time, and it is signer-chosen: nothing in this profile makes it checkable (Section 3.3).

action.canonical. A key into the issuer's own risk table -- the stable name under which the issuer classifies this kind of action. The table is not defined here, has no registry, and is not comparable across issuers; action.id names the specific tool or operation invoked.

Cross-field rules a verifier MUST enforce. These are the combinations that would let one record say two things:

  • governance.sandboxed MUST be true whenever agent.principal is SANDBOX_SIM or governance.verdict is SIMULATED (Section 2);
  • action.rollbackRef MUST be absent or null unless action.reversible is true, and a governance.verdict of ROLLED_BACK MUST be accompanied by action.reversible: true;
  • where governance.compliance.verdict is present it records the policy evaluation's own result, which is a different question from governance.verdict; the two MUST NOT be read as one, and a verifier MUST NOT resolve a disagreement between them by preferring either -- it reports both. The member is a closed two-value set: where present it MUST be exactly ALLOW or DENY, and a verifier MUST reject any other value rather than carry an unrecognized string through into a report a relying party will read.

What no rule constrains, stated so nobody assumes it. governance.approval may accompany any lifecycle verdict, and its at may be later than ts: this profile places no temporal invariant on them, because a signer-chosen timestamp cannot establish that an approval preceded an action (Section 6.1). A verifier MUST NOT infer ordering from these fields.

3.2. Canonicalization parameters

A digest is only interoperable if the parameters that produced it are pinned. Naming a canonicalization ("canonical JSON") without pinning its parameters is the failure mode that does not announce itself: two implementations each behave correctly under their own reading and produce different bytes, or -- worse -- the same bytes for inputs that should differ. This section therefore pins every parameter of the construction above, normatively and exhaustively for the receipt object as a whole. One carve-out belongs next to the claim rather than three sections away: the encoding of the action sub-object is not fixed for a party computing its own digest over that sub-object, because an optional member may be absent or present-as-null and the two produce different bytes (Section 10). Inside a receipt that choice is signed and therefore settled; across producers computing an independent digest it is not.

Table 2
Parameter Value for noa.receipt/0.1
Serialization JCS [RFC8785]
Member sort UTF-16 code unit (RFC 8785); NOT Unicode code point -- the two diverge for astral characters, which encode as surrogates D800-DBFF and sort BEFORE E000-FFFF
Number rendering RFC 8785; integers only, abs(n) <= 2^53 - 1; non-integer and non-finite values MUST be rejected
Unicode normalization NONE applied by the canonicalizer, ever. Producers MUST emit NFC ([UAX15]); verifiers MUST NOT normalize before hashing. A verifier MAY refuse a non-NFC receipt outright (see Section 3.3); what it MUST NOT do is silently repair one
String escaping RFC 8785: " (U+0022) and \ (U+005C) escaped; the short forms \b \t \n \f \r used where defined; every other control character below U+0020 escaped as \u00xx; every other code point emitted literally as UTF-8
Unpaired surrogates MUST be rejected (they would collapse to U+FFFD at the UTF-8 hashing step, mapping distinct inputs onto one digest)
Duplicate member names MUST be rejected at the parse boundary, not resolved last-wins
Forbidden member names __proto__, prototype and constructor MUST be rejected at the parse boundary, in every JSON document a verifier parses -- receipt, keyring, identity manifest, checkpoint -- and not filtered after parsing
Digest SHA-256 ([FIPS180-4]), lowercase hex, sha256: prefixed. action.paramsHash MAY instead be HMAC-SHA-256 ([RFC2104]) under a tenant-scoped key, written hmac-sha256:<hex>
Timestamps RFC 3339 [RFC3339] date-time, with Z or a numeric offset -- the accepted spellings are pinned in Section 3.3, which permits both. A verifier MUST reject a value that is not a valid RFC 3339 date-time, and valid syntax still does not make a timestamp truthful
Domain separation the receipt signature is over the UTF-8 bytes of "NOA-Receipt-v0.1-sig:" followed by the raw SHA-256 of the hash input, pinned byte-exactly in Section 4.2; the checkpoint signature uses a distinct tag (Section 5.2), so neither can be replayed as the other

The forbidden-name rule is a parse rule rather than a schema rule, and the difference is the whole point of it: in several host languages, materializing a member with one of those names mutates the object being built, so a document carrying one can change how the verifier's own checks behave before any schema runs. A receipt could not carry them in any case, since the object is closed (Section 3) -- but the keyring, identity manifest and checkpoint a verifier also parses are not closed by this profile, and the parse boundary is where all four documents meet the same rule.

On the outer COSE path, verifiers MUST NOT normalize, re-order, or re-render a received payload before verifying it: the bytes as received are the bytes that were signed. On the derived path of Section 4.2 the verifier does re-serialize, by construction -- it must, since two members are omitted -- and this table is exactly the rule that makes that re-serialization reproduce the producer's bytes.

action.paramsHash is a per-producer commitment to that producer's own parameter set. It is NOT a cross-producer correlation key and MUST NOT be treated as a shared action digest: it does not commit to the tenant, chain, authorization, execution attempt, or nonce, and it may legitimately repeat across retries of the same action. Section 10 states what a construction must carry before any two parties may compare digests at all.

3.3. Optional members, and what their absence means

Seven members of Section 3 MAY be absent: scope.tenant, agent.model, action.rollbackRef, governance.ruleId, governance.approval, governance.compliance, and -- inside a present compliance -- its verdict. Absence is a producer's version or configuration choice; it is never, by itself, evidence of tampering, and a verifier MUST NOT convert it into one. Five of them -- agent.model, action.rollbackRef, governance.ruleId, governance.approval and governance.compliance -- may also be present-as-null, which a verifier MUST treat as the value null and not as absence: the two encodings produce different canonical bytes and therefore different hashes and signatures.

The two whose absence changes what a verifier may conclude are singled out below; the rest are ordinary optional fields carrying no security consequence beyond that rule.

  • scope.tenant MAY be absent, and the security-bearing rule is stated first (this one is Chain-Verifier-class, since it compares receipts to one another): a conforming Chain Verifier MUST reject a transition between two present, unequal scope.tenant values within one scope.chain. One chain belongs to one tenant, so a change of tenant inside it is a splice of two histories rather than a version change. Absence then carries forward rather than resetting: a verifier MUST NOT treat an absent scope.tenant as a fresh start, so A -> absent -> B is the same cross-tenant splice as A -> B and MUST receive the same verdict, while A -> absent -> A (the same tenant resuming) and absent -> A (enrichment) remain valid. Without the carry-forward rule, an optional field becomes a splice channel.
  • governance.compliance MAY be present. When present it carries a commitment to the policy's published identity (policyHash), to the policy's closed read-set (readSetHash), and to the recorded decision inputs (inputsHash) -- hashes only, never the inputs themselves. Its presence does NOT make the receipt policy-replayable under this profile: this document does not specify the evaluation grammar, operator set, or evaluation order that a re-derivation would need, and Section 11 still governs. Its presence also does not establish that the referenced policy was adequate or in force.

On rejecting non-NFC input. A verifier MAY offer a mode that refuses any receipt carrying a non-NFC string, reporting it as malformed. That mode is conformant and is a reasonable default for a relying party that indexes on these fields; what is NOT conformant is normalizing a received payload before hashing it, in any mode. A verifier's default SHOULD be stated in its documentation, because the two modes give different answers on the same bytes and a caller cannot tell which it got from a boolean.

On timestamps. ts and governance.approval.at are RFC 3339 [RFC3339] date-time values, and a conforming verifier MUST reject a value that is not a valid RFC 3339 date-time. A digit-and-separator shape check is not sufficient: 2026-13-45T99:99:99Z matches the shape while lying outside the grammar's own numeric ranges, and a receipt carrying it is malformed.

Because these values are inside the hashed payload, the spelling is part of the signed bytes, and two spellings of one instant are two different receipts with two different hashes. The profile therefore pins what is accepted rather than leaving it to each implementation:

  • the T and Z separators MAY be upper or lower case, as RFC 3339 permits;
  • a numeric offset (+HH:MM / -HH:MM) is permitted as well as Z; a receipt is not required to be written in UTC, and -00:00 carries the meaning RFC 3339 gives it;
  • a fractional-second part of 1 to 9 digits is permitted, and trailing zeroes are significant bytes rather than a canonical form to be trimmed;
  • a Producer MUST NOT emit a date-time whose seconds field reads 60, and a verifier MUST reject one. This profile forbids the leap-second representation rather than requiring anyone to validate it. The reason is that no other rule here can be enforced offline without it: [RFC3339], Section 5.7 admits 60 only at an actual leap second, adjusted by the numeric offset, and says future leap seconds cannot be predicted -- so a verifier holding no leap-second table cannot decide whether 2030-06-30T23:59:60Z is a valid date-time at all, and the requirement to reject what is not valid RFC 3339 would have no determinate answer. Forbidding the representation costs a Producer nothing it needs: an agent action stamped during a leap second is recorded at :59 or at the following second, and the receipt still denotes an instant. This is a deliberate narrowing of RFC 3339 by this profile, not a claim about RFC 3339, and Section 15 records it as a decision a working group may reverse.

A verifier MUST NOT normalize a timestamp before hashing, for the same reason it MUST NOT normalize anything else (Section 3.2). Relying parties comparing timestamps across receipts MUST parse them into instants first: byte equality and instant equality are different questions here.

Valid RFC 3339 syntax does not establish that the timestamp is truthful; it remains signer-chosen. A verifier that accepts a well-formed timestamp has learned that the issuer wrote a well-formed timestamp, and nothing about when anything happened.

governance.ruleId is a signer-asserted label naming which rule fired. It is not a content commitment to a policy, and a relying party MUST NOT treat it as one. Where a receipt carries no governance.compliance, the receipt's "policy identity" is exactly that label and nothing stronger. (-00's terminology defined a policyHash that -00's payload never carried; this subsection resolves that inconsistency in favour of what the wire format actually does.)

This profile adds no member to noa.receipt/0.1. A claim that this record does not carry travels as a separate signed artifact, linked by hash -- see Section 8.4 for why that choice is load-bearing rather than stylistic.

4. COSE Signed Statement profile

A receipt may be presented in two forms, and this profile defines both. A receipt's payload is the JSON object of Section 3, and its native signature (Section 4.2) is computed over that object; a receipt presented as payload plus native signature, with no COSE envelope, is a bare receipt. A receipt carried inside a COSE_Sign1 is an enveloped receipt, and only an enveloped receipt can become a Signed Statement (Section 4.3). Both forms are legitimate presentations: the reference implementation's chain path verifies bare receipts and never touches COSE (Section 12), while its COSE path produces the enveloped form. Every requirement below that names the envelope applies to the enveloped form only; a Receipt Verifier given a bare receipt checks the payload, the recomputed chain.hash and the native signature, and reports that no envelope was present. Whether a SCITT-native profile should require the envelope is left open (Section 15).

4.1. Protected header

Every requirement below names its actor: what a Producer MUST emit, and what a Receipt Verifier -- given one receipt and a keyring (Section 1.4) -- MUST check. No set and no evidence artifacts are needed.

An enveloped receipt is a COSE_Sign1 carried as the tagged form, CBOR tag 18: a Producer MUST emit the tag, and a verifier MUST reject an untagged COSE structure rather than guess at its type. Its protected header MUST contain alg; the receipt MUST additionally carry a key identifier, which SHOULD be in the protected header and MAY be in the unprotected header (see below -- this avoids the RFC 2119 contradiction of requiring kid in the protected header while also permitting it in the unprotected one). In detail:

  • alg (label 1) = -19 (Ed25519) as registered in the IANA COSE Algorithms registry by [RFC9864], Section 2.2 (the fully-specified Ed25519 identifier; [RFC8032] Section 5.1 parameter set). Issuers MUST use -19. Verifiers MUST reject every other alg value (algorithm-confusion defense). That includes the polymorphic EdDSA identifier -8, which [RFC9864], Section 4.2.2 marks Deprecated (it was registered in [RFC9053]). It is not a special case, and for this profile there is no legacy-compatibility exception. The signing key MUST be an OKP key with crv = 6 (Ed25519), the curve registered for Ed25519 in the COSE Elliptic Curves registry ([RFC9053], Section 7.1); a verifier that can inspect the resolved key's type or curve MUST reject any key that is not Ed25519 rather than attempt the verification, since a check against a key of another type answers a different question than the alg value claims. [RFC9864] makes alg = -19 self-disambiguating, so a verifier MUST NOT rely on the key's crv alone to determine the algorithm.
  • A key identifier sufficient to resolve the verification key. kid (label 4) is the baseline: a Producer MUST emit one, and it MUST be sufficient on its own for a verifier holding the right keyring to resolve the verification key. The kid SHOULD be carried in the protected header (where it is covered by the signature and cannot be stripped or swapped); it MAY instead be carried in the unprotected header -- a verifier MUST resolve it from either bucket, preferring the protected copy when both are present, and MUST report which bucket it used. Resolving a key and naming an identity are two different acts, and this profile separates them: an unprotected kid MAY be used to resolve the verification key, and MUST NOT be reported as the emitter's identity, because it is not covered by the signature and can be swapped without breaking it (Section 6). [RFC9052] treats kid as a hint that need not be unique; this profile narrows that for a verifier's own key store. Where one kid resolves to more than one key, a verifier MUST try each candidate and accept only if exactly one verifies; where more than one key was tried it MUST report the identifier as ambiguous rather than silently choose -- attribution built on a collision is attribution to the wrong party. kid is an opaque byte string in [RFC9052] and this profile does not narrow that; what it does require is that no verifier resolve it through a lossy decode. A verifier whose key store is keyed by text MUST decode the kid bytes, re-encode the result, and require the original bytes back before using the identifier, and MUST refuse to resolve one that fails that round trip -- a lossy decode maps every invalid byte sequence onto the same replacement character, so distinct signed identifiers collapse onto one entry and attribution goes to whoever holds it.
  • Certificate references are OPTIONAL and additional, never a substitute. A deployment using a PKI MAY additionally carry x5t (label 34) or x5chain (label 33) as defined by [RFC9360]. A receipt MUST NOT rely on them as its only key identifier, because a verifier conforming to this profile is required to resolve kid and is not required to process certificates. The reference implementation ignores both headers and refuses a receipt carrying neither a protected nor an unprotected kid (Section 12).
  • Unknown headers are ignored, critical ones are fatal. The mere presence of an additional registered header parameter -- kid, content type, CWT_Claims, or crit itself -- is never on its own a reason to reject a COSE_Sign1. What decides the outcome is crit (label 2). This profile requires a verifier to ignore any header parameter it does not understand that crit does not list, rather than reject the object -- that rule is this document's, not [RFC9052]'s, which defines crit without imposing it. What [RFC9052], Section 3.1 does state is the fatal direction: a crit entry naming a header parameter that is not in the protected bucket is a fatal processing error. A verifier MUST likewise reject an object whose crit lists a header parameter it cannot process. Section 4.3 adds one further rejection rule, for CWT_Claims appearing in both buckets.

The protected header MUST satisfy the Core Deterministic Encoding Requirements of [RFC8949], Section 4.2.1: shortest-form argument encodings and map keys sorted in bytewise lexicographic order of their deterministic encodings, each key occurring once. Verifiers MUST reject any other encoding.

4.2. Payload binding, and the two signatures

Construction rules address the Producer; verification rules address the Receipt Verifier, which is given the receipt and a keyring (Section 1.4).

The payload is the JCS serialization from Section 3, carried attached as the COSE_Sign1 payload. This profile does not define a detached-payload mode: in COSE, verifying a detached payload requires the verifier to be supplied the payload bytes themselves for the Sig_structure, so a detached receipt is not self-contained, and chain.hash -- a digest over the receipt with chain.hash and sig.value removed -- is a chain-linkage value, not a substitute for those bytes. A verifier conforming to this profile MUST reject a COSE_Sign1 whose payload element is absent or null. (An earlier revision said a detached payload's digest was bound through chain.hash; that was wrong in both directions, and is corrected rather than carried forward. A detached mode, if one is ever wanted, is future work -- see Section 12.)

A receipt in this profile carries two independent signatures, and conflating them is the failure this subsection exists to prevent.

  1. The native receipt signature, sig.value, is computed over a domain-separated preimage, fixed byte-exactly by the construction below.
  2. The outer COSE_Sign1 signature covers the RFC 9052 Sig_structure over the protected header and the payload -- and the payload is the whole receipt including its native sig member. The external_aad field of that structure is the empty byte string in this profile; a verifier MUST use a zero-length external_aad and MUST NOT accept an envelope that only verifies under some application-supplied value, since nothing in the envelope would record what that value was.

The native construction, in full:

HASH-INPUT = JCS( the receipt object with exactly two members
                  removed: chain.hash and sig.value; every other
                  member, including sig.alg and sig.kid, is present )

MESSAGE    = UTF8("NOA-Receipt-v0.1-sig:")  ; 21 octets: tag then ":"
          || SHA-256(UTF8(HASH-INPUT))      ; RAW 32-octet digest,
                                            ; NOT its hex form
                                            ; 21 + 32 = 53 octets

sig.value  = base64(Ed25519(MESSAGE))       ; standard alphabet,
                                            ; with padding
sig.alg    = "ed25519"                      ; exact lowercase literal
chain.hash = "sha256:" || lowercase-hex(SHA-256(UTF8(HASH-INPUT)))

|| denotes octet-string concatenation, and base64 is the standard alphabet with padding ([RFC4648], Section 4). Two encoding rules close variants that would otherwise let one signature be written two ways: sig.value MUST be canonical base64 -- exactly one padded form, no whitespace, no URL-safe alphabet, and the unused bits of the final quantum set to zero, as [RFC4648], Section 3.5 requires of a canonical encoding -- and MUST decode to exactly 64 octets. Re-encoding the decoded value and requiring the original string back enforces every one of those rules at once, and is the check a verifier SHOULD implement rather than testing each separately. The signature itself MUST be a canonical Ed25519 encoding, with the scalar S reduced modulo the group order. A verifier MUST reject a non-canonical encoding of either kind rather than accept it as an equivalent form. This matters more here than in a bare COSE profile because sig.value is excluded from the hash input (Section 4.2): a permissive decoder would let two byte-different receipts share one chain hash. The digest that is signed and the digest that appears in chain.hash are the same digest of the same input, in two encodings. The domain tag means a receipt signature cannot be replayed as a signature for any other object class in the same system, and the checkpoint tag (Section 5.2) is distinct from it.

The native algorithm label is checked before any cryptography. sig.alg MUST be the exact lowercase string ed25519. A Receipt Verifier MUST reject any other value -- including a differently cased spelling, and including a value naming an algorithm it happens to support -- before attempting a signature check. A verifier that selects its algorithm from the resolved key or from its own configuration answers a different question than the receipt asks.

The verification equation is pinned. A verifier MUST check the equation [S]B = R + [k]A' -- the cofactorless check that [RFC8032], Section 5.1.7 names as "sufficient, but not required" beside the cofactored [8][S]B = [8]R + [8][k]A'. Both are permitted by RFC 8032 and they do not accept the same set of signatures: every signature the cofactorless equation accepts is accepted by the cofactored one, and the reverse does not hold. Leaving the choice to the implementer would leave validity itself implementation-defined, and in a hash chain a disagreement about one signature is a disagreement about accepted history -- which is the hazard this profile exists to remove. This revision therefore pins the narrower equation, so that two conforming verifiers accept the same set. Section 15 records that the working group may revisit the choice; what it may not do is leave it unmade.

Determinism is a product of this equation together with the encoding rules around it -- the canonical S above, the canonical base64 that carries the signature, and the public-key rules below -- and not of the equation alone. Section 12 reports which shipped verifiers satisfy the pinned equation and which do not.

The verification key itself. Three rules bind whichever actor resolves a key for a native signature check, and they exist for the same reason -- the same signed chain must not be accepted by one verifier and rejected by another:

  • The resolved key MUST be an Ed25519 key ([RFC8032], Section 5.1). A verifier that can inspect the resolved key's type or curve MUST reject any other key rather than attempt the verification: dispatching the verification algorithm on the key's type instead of on sig.alg is the same algorithm-confusion defect Section 4.1 closes on the envelope path.
  • A public key whose encoded y-coordinate is not a canonical field element MUST be rejected: after the sign bit (bit 255) is cleared, the remaining 255-bit little-endian value MUST be less than 2^255 - 19. A public key that is one of the eight canonical small-order Ed25519 point encodings MUST also be rejected. Both rules are about the key, not about the verification equation: they remove the two classes on which widely deployed libraries were measured to disagree, so that two verifiers handed one signed chain do not accept two different histories. The verification equation itself is pinned above, not left to the implementer.
  • Where a verifier accepts a key in an encoded form, that encoding MUST be canonical: the verifier MUST re-encode the key it parsed and require byte equality with the input, and MUST reject a base64 spelling that does not re-encode to itself. This profile defines no keyring format (Section 1.5), so the rule binds whatever encoding a deployment chooses. Without it one logical key has many accepted spellings, and any later comparison on key bytes -- a fingerprint, a pin, a deduplication -- is bypassable by re-spelling the key.

The relationship between the two signatures is normative:

  • A verifier MUST NOT treat a valid outer COSE signature as evidence that the native signature verifies, and MUST NOT treat a valid native signature as evidence that the envelope is authentic. Each is checked on its own inputs. A Receipt Verifier (Section 1.4) given an enveloped receipt MUST run both, and MUST report which of the two produced any result it returns; given a bare receipt it runs the native check alone and MUST report that no envelope was verified. What is never conformant is running one check and reporting the other's property.
  • The two signatures MAY be produced by different keys. Where they are, the COSE kid identifies the party that emitted this envelope and sig.kid identifies the party that signed this receipt into its chain. A relying party MUST NOT collapse the two identities, and neither may a verifier: they are two claims about two different parties, and Section 6 says what each is checked against rather than letting one stand for both. A verifier MUST report every identifier it reports as an identity, saying which claim each belongs to -- always the native sig.kid, and the outer kid when it came from the protected bucket. An outer kid taken from the unprotected bucket resolved a key and named nobody (Section 4.1), and MUST NOT be reported as the emitter's identity.
  • chain.hash MUST be recomputed, and a mismatch MUST be rejected. A Receipt Verifier MUST compute "sha256:" followed by the lowercase hex of SHA-256 over HASH-INPUT -- the same input the native signature covers -- and compare it to the receipt's chain.hash. If they differ the receipt MUST be rejected as tampered. This check is not optional and is not implied by either signature: HASH-INPUT removes chain.hash, so sig.value does not cover it, and the outer COSE signature that does cover it may have been applied by a different party (Section 4.2). Without this recomputation a receipt whose chain.hash is an arbitrary value passes every other Receipt-Verifier check, and every artifact that references a receipt by hash (Section 8.4) inherits the forgery.
  • The two checks consume different inputs, and the difference is normative. The outer COSE signature is verified over the payload bytes exactly as received: a verifier MUST NOT parse, re-serialize, re-order or normalize them first. The native signature and chain.hash are verified over a derived input: the payload is parsed, the two named members are omitted, the result is re-serialized under Section 3.2, and that string is hashed. A verifier MUST run both and MUST NOT substitute one input for the other.
  • The canonicality check is over the whole payload, not over the derived input. A verifier MUST parse the received payload bytes, re-serialize the complete parsed object under Section 3.2, and require the result to equal the received bytes: JCS(parse(bytes)) == bytes. If they differ, the receipt MUST be rejected rather than repaired, because the field values the verifier would go on to report are then not the values the outer signature covers. The derived input of the native path omits two members by construction and therefore can never equal the received bytes; nothing in this profile asks it to.

4.3. Carrying a receipt as an RFC 9943 Signed Statement

The mapping binds the Producer. The evaluation rule at the end binds whichever actor evaluates a receipt as a Signed Statement, and names it there.

A COSE_Sign1 built to Section 4.1 alone is a valid signed object, but it is not by itself a Signed Statement in the sense of [RFC9943]. That architecture states two protected-header requirements in the body of its Section 6: the protected header of a Signed Statement MUST include the CWT Claims header parameter, whose value MUST include the Issuer Claim (label 1) and the Subject Claim (label 2); and kid MUST be present in the protected header whenever neither x5t nor x5chain is present there. A receipt that omits them will be refused at registration by a conforming Transparency Service. This subsection specifies the mapping, so that the difference between "a signed receipt" and "a registrable Signed Statement" is a checkable property rather than a hopeful sentence.

To be carried as a Signed Statement under this profile, a receipt MUST additionally have:

Table 3
Requirement Value for this profile
CWT_Claims header parameter (label 15) in the protected header, encoded as specified in [RFC9597], Section 2 REQUIRED ([RFC9943], Section 6)
iss (CWT claim 1) the Issuer of the Signed Statement: the authority on whose behalf the receipt is emitted. It is a signer-asserted string until something binds it to the verification key -- Section 7 requires a Transparency Service to make that binding and states that this profile defines no mechanism for it. This profile does not require iss to equal the identity that submits the statement for registration; the two are separable, and a Transparency Service applies its own Registration Policy to the submission
iss when x5t or x5chain is in the protected header additionally MUST be a string conforming to the URI syntax, of length 1 to 8192 ([RFC9943], Section 6)
sub (CWT claim 2) the scope.chain identifier, so one chain is one subject. scope.chain carries no namespace of its own (Section 3.1), so two issuers can choose the same string: a Transparency Service MUST scope subjects by issuer rather than treat sub as globally unique, and a chain carrying several agents remains one subject
kid in the protected header REQUIRED. [RFC9943], Section 6 makes this conditional on the absence of x5t/x5chain; this profile requires a resolvable kid unconditionally (Section 4.1) and requires it protected for registration (Section 7), so the condition never relaxes it here

The CWT_Claims header parameter MUST occur exactly once across the two header buckets, as [RFC9597], Section 2 requires: a receipt carrying label 15 in both the protected and unprotected headers is malformed, and a verifier MUST reject it rather than prefer one copy.

These claims are necessary, and this profile does not claim they are sufficient. [RFC9943] describes a Statement as carrying a media type that tells a Transparency Service what it is holding, and this revision registers none (Section 16) while leaving the protected content type a SHOULD. So a receipt that carries iss and sub has met the requirement this profile can state and may still be refused by a service that dispatches on a media type it does not have. Closing that is the media-type registration named in Section 15, not a further claim here.

A verifier or Transparency Service evaluating a receipt as a Signed Statement MUST reject one that lacks protected CWT_Claims carrying both iss and sub, per [RFC9943], Section 6. A verifier evaluating a receipt merely as a signed record MUST NOT infer registrability from a successful signature check: the two are different questions and this profile keeps them apart deliberately.

A receipt carried as a Signed Statement SHOULD also carry a protected content type (label 3) describing the payload. This revision registers no media type (Section 16), which has a consequence worth stating rather than leaving to discovery: until one exists, nothing makes two conforming producers choose the same value, and where they choose differently a Transparency Service that dispatches policy on content type will treat them as different formats. A Deployment SHOULD therefore agree the value with the Transparency Service it registers against, and MUST NOT rely on it for cross-deployment interoperability. Registering a media type is the first IANA action a future revision should request.

Producers that do not emit these claims produce receipts that verify offline exactly as specified everywhere else in this document, and that a conforming TS is entitled to refuse. The reference implementation is currently in that position; Section 12 states it as a named conformance gap rather than leaving a reader to discover it at registration time.

5. Hash-chaining and completeness

This section's number, title and anchor are unchanged from -00, and the construction it defines is unchanged. Two sentences of -00's paragraph were factually wrong and are corrected here rather than reproduced and contradicted twenty lines later. Appendix A states the before and after, for the benefit of a document that adopts this section by reference (Section 1.2).

Each receipt carries a monotonic chain.seq (genesis = 0) and prevHash. A Chain Verifier checking a presented chain MUST verify: each prevHash equals the prior receipt's chain.hash; the genesis receipt has prevHash == null; and, when a signed checkpoint is present and an identity manifest is supplied, that the checkpoint was signed by one of the keys that manifest authorizes for the genesis receipt's agent.id, so that a foreign checkpoint produced by a re-heading attacker is rejected -- without a manifest that binding does not hold (Section 5.2). This profile detects in-band tampering within a presented chain, and detects tail truncation only against an authenticated checkpoint (Section 5.1), never from the presented set alone; it does NOT, by itself, detect equivocation (an issuer signing two divergent chains) -- that requires registration in a SCITT Transparency Service or an equivalent external witness.

5.1. Chain-level failure axes

Section 5 and this subsection address the Chain Verifier (Section 1.4): a Receipt Verifier is given no set, cannot evaluate these conditions, and does not fail them.

This subsection is NEW in -01. Section 5 keeps its number, title and anchor because another profile adopts it by reference (Section 1.2); two of its sentences are corrected in this revision and Appendix A states which; everything below adds Chain-Verifier-class requirements that -00 did not state, and Appendix A lists each of them.

The checks above are properties of a receipt SET, not of any receipt. Six conditions are named below, and every one of them is a property of the presented receipts, decided by comparing them to one another: a verifier presented with any single receipt from the set will find that receipt individually well-formed, correctly hashed, and correctly signed. They are named so that a reconciling verifier can report WHICH set-level property failed, rather than collapsing every one of them onto a single "invalid".

A checkpoint is deliberately not among them. It is a separate object, supplied separately, and what it answers is a separate question -- so it has its own axis, defined after this table. Nothing on that axis is a chain condition, and nothing on it can fail a chain. That separation is Section 8.3's rule applied to this section: a finding about one object is never a finding about another.

Table 4
Axis Failure
chain-link-broken a prevHash does not equal the prior receipt's chain.hash; a non-genesis receipt carries prevHash == null; or the genesis receipt carries a prevHash that is not null (Section 5 requires it to be)
chain-sequence-duplicate two receipts in one scope.chain carry the same chain.seq
chain-sequence-gap the presented chain.seq values are not contiguous, or do not begin at 0. A conforming Chain Verifier requires the complete chain from genesis; see below
chain-scope-mismatch the presented set does not all carry one scope.chain. A Chain Verifier is handed a set as presented and checks that it is one chain; it does not assume it. A supplied checkpoint that names a different chain is not this condition -- it is a finding about the checkpoint (Section 5.2)
chain-tenant-split two present, unequal scope.tenant values appear within one scope.chain (Section 3.3)
chain-key-discontinuity two receipts in the presented chain carry the same agent.id and different sig.kid

A Chain Verifier MUST detect all six conditions -- each is decided from the presented set alone, so none of them depends on an input the verifier may not have been given -- and MUST report them distinctly enough that a consumer can tell which held.

The checkpoint axis, which is not a chain condition. A checkpoint answers one question: has the tail of this chain been cut? A Chain Verifier MUST report that question in exactly one of three states, and MUST NOT let any of them alter the verdict the six conditions produced:

  • answered -- a checkpoint was supplied, was structurally valid, named this chain, and authenticated. The verifier compared it to the presented head. The comparison yields either consistent -- the head matches, or the presented chain is a consistent extension of it as described below -- or the failure checkpoint-head-conflict: the presented head and an authenticated checkpoint disagree. Which of them is wrong -- a deleted tail, a fork, or a signer error -- is not established by the comparison, and a verifier MUST NOT name a cause it did not observe.
  • unanswered -- no checkpoint was supplied. The verifier MUST report the tail question as unanswered and MUST NOT report it as an absence of truncation (Section 5.2).
  • unusable -- a checkpoint was supplied and could not be used: it was malformed, it named another chain, or it did not authenticate (Section 5.2). The verifier MUST report which, MUST report the tail question as unanswered, and MUST NOT convert any of it into a finding about the receipts. checkpoint-unverified is this document's identifier for the authentication case.

Two consequences follow, and both are the reason the axis is separate. A presented chain that satisfies all six conditions is a valid chain whatever arrives beside it: a defective checkpoint subtracts an answer, it does not create a failure. And whether the verifier could authenticate the checkpoint is what decides between answered and unusable -- never whether a keyring happened to be supplied. Section 12 measures what the other reading costs: every shipped verifier branches on whether a keyring was passed, and both branches produce a finding against the chain.

The distinction between the conditions is operational, not cosmetic: chain-sequence-duplicate and chain-scope-mismatch can indicate a splice of two genuine histories -- every receipt involved verifies in isolation. A duplicate sequence has innocent causes too, a retransmitted copy being the common one: where the two receipts at that sequence are byte-identical a verifier SHOULD say so, and where they differ it has found an issuer that signed two histories at one position, which is the serious case -- while chain-link-broken indicates in-band alteration. Reporting them as one verdict tells a relying party that something is wrong but not what an operator must do next.

The six labels above are this document's identifiers for the six conditions, and checkpoint-head-conflict and checkpoint-unverified are its identifiers for the two named checkpoint-axis outcomes. A Chain Verifier MAY report any of them under its own strings; what it MUST do is make them distinguishable in its output, and keep the chain conditions distinguishable from the checkpoint axis. No particular spelling is required on the wire. Implementers aligning with a reconciliation layer should note that these conditions correspond to divergence axes carried by [I-D.hillier-scitt-arp]; that alignment was measured against this profile's own adversarial corpus (see Section 12).

Completeness is required, not optional. A Chain Verifier MUST reject a presentation that does not begin at seq == 0, and a Deployment presenting a chain for verification MUST present it from genesis. This is what every implementation of this profile already does -- five verifiers in five languages each require 0..n-1, though only one of the five is an independent oracle for the others (Section 12) -- and a presentation of, say, seq 1 and seq 2 is refused as a missing genesis rather than accepted as a partial view. Verification of a range that does not start at genesis is therefore not a capability of this profile: it is neither specified here nor implemented anywhere, and Section 12 lists it as such. -00 said "a verifier checking a range of receipts" and left the boundary of a "range" unstated; this revision states it. No implementation behaviour changes: the stricter reading is the one every implementation has always enforced, and only the documentation has caught up.

What a checkpoint comparison can and cannot show. A mismatch between the presented head and a checkpoint's headHash can reveal truncation relative to that checkpoint -- that is, relative to one signed assertion made at one moment by one key. It is not a general detector of tail truncation: a prover who withholds both the tail and the checkpoint that would expose it presents a set with no mismatch in it, and no property of the presented set reveals the omission (Section 13). Registration in a Transparency Service replaces the prover's own checkpoint with a third party's signed proof of registration, which is stronger and still not a general answer; Section 7 states exactly what it is worth to an offline holder.

An older checkpoint over a longer chain is not truncation. A checkpoint asserts a head at the moment it was minted; a chain that has grown since then legitimately presents a later head. A verifier MUST therefore distinguish two mismatches. Where the checkpoint's headHash appears in the presented chain at exactly its highestSeq, and the presented head's chain.seq is greater, the presented chain extends the checkpoint consistently: this is not checkpoint-head-conflict, and a verifier MUST NOT report it as a conflict. Every other mismatch -- a head at a lower sequence, or a checkpoint whose asserted head does not appear in the presented chain at its own sequence at all -- is checkpoint-head-conflict: the presented set and an authenticated checkpoint disagree about the head. A verifier MUST report the disagreement and MUST NOT report a cause for it. Deletion of a tail is one explanation; an issuer that signed two divergent histories is another; a checkpoint minted over a different lineage, or a signer error, is a third. The comparison separates none of them, and naming one is the same manufacture of a specific accusation Section 8.3 forbids -- which is why this revision renamed the condition rather than keep -00's reading of it as truncation. The reference implementation does not draw this distinction and reports one verdict for both cases; that is recorded as a conformance gap in Section 12.

Why the unauthenticated case turns on authentication, and not on inputs. The rule above -- that a checkpoint the verifier did not authenticate yields checkpoint-unverified and never a truncation finding -- is the one this whole document turns on. A checkpoint is supplied to the verifier, often by the same party whose chain is in question. If an unauthenticated one could establish truncation, anyone able to hand a verifier a file could mint a signed-looking forensic conclusion about someone else's chain -- an accusation manufactured from attacker-controlled bytes. Only an authenticated checkpoint carries the authority to say a tail is missing, and checkpoint-unverified is the indeterminate result that names the reason instead. Section 8.3 states the general form of the rule, over every input rather than this one; Section 12 measures two places where a shipped verifier breaks it.

The prohibitions in the other direction are unchanged: an unauthenticated checkpoint MUST NOT be reported as a satisfied tail check, and MUST NOT upgrade any result. A verifier MUST state, in its output, whether the checkpoint that produced any tail conclusion was authenticated, and Section 13 states what an authenticated one is and is not worth.

One agent, one key, within one presented chain. A Chain Verifier MUST reject a presented chain in which two receipts share an agent.id and differ in sig.kid, reporting chain-key-discontinuity. The condition needs no keyring: it compares the receipts to one another.

This is the condition that catches the ordinary form of chain extension, and what it catches is narrower than "extension is closed". Consider a party that holds any key the verifier trusts and appends receipts to another party's chain under the victim's own agent.id: the links are intact, there is no sequence gap, the scope.chain is constant and the tenant is constant, so none of the other conditions fires. What gives that attacker away is that the appended receipts carry a different sig.kid while claiming the same agent.

Two escapes remain, and they belong with the claim rather than after it, because a reader who takes the claim without them will believe the chain is closed when it is not:

  • It fires within one agent.id, and chains may carry several. An attacker holding another trusted key can append receipts under a new agent.id and no condition in the table fires: the set is still one chain, one tenant, contiguous and correctly linked. Closing that needs a rule about which agents may appear in a chain -- an append-authority binding this profile does not define (Section 1.5). The identity manifest binds an agent.id to keys; it does not bind a chain to a set of agents, and a Chain Verifier given one still has nothing to test the new agent against.
  • The comparison is on identifiers, and that is what it detects. The condition compares sig.kid strings, because a Chain Verifier is defined over a presented set and needs no keyring to evaluate it. One consequence is unavoidable and is stated rather than hidden: two different keys presented under one kid are indistinguishable to it, so an attacker who can get a second key registered under the victim's identifier is not caught by this condition -- Section 7 and the keyring, not this comparison, are where that is addressed. A verifier MAY additionally compare resolved key material where it can, and if it does it MUST report that as a separate result, because it is a different check with a different failure set; what it MUST NOT do is substitute one for the other under this condition's name.

Within one presented chain, then, one agent speaks under one key identifier or the set fails -- and the two escapes above are the boundary of that sentence, not exceptions to be discovered later. The cost is the corresponding limit stated in Section 5.2: key rotation is not expressible inside a chain, and a deployment that rotates a signing key starts a new one.

Sequence orders a chain; timestamps do not. chain.seq and prevHash are the only ordering this profile defines. ts is signer-chosen (Section 3.3) and this profile places no requirement that it be non-decreasing along a chain: a set whose timestamps run backwards can be fully conformant on every axis above, and a relying party that sorts or reasons by ts is using a field no check constrains. Where a deployment needs temporal ordering it MUST obtain it from something other than these records.

Where more than one condition holds, a verifier MUST report all of them rather than the first it found: an operator's next step depends on the set, and a single-value result field cannot carry it. This profile defines no wire encoding for that report -- how a verifier's output is structured is outside it (Section 1.5) -- only that the conditions be individually recoverable from it.

A condition that holds is not a note; it is a failure. A Chain Verifier MUST NOT return a positive result for a set on which any of the six conditions holds. That rule has no exception in this revision, and one case is worth spelling out because the reasoning above cuts the other way: two receipts at the same chain.seq that are byte-identical are a retransmitted copy rather than a divergent history, and a verifier SHOULD say so in its report -- but the condition still holds, so the result is still a failure. De-duplicating such a set and returning a positive result is not conformant here. Whether a future revision should permit it is an open question (Section 15); this one answers it in the strict direction so that two verifiers cannot disagree about the same bytes today.

A verifier MUST NOT report a chain-level axis as a per-receipt failure, and MUST NOT infer from the absence of these axes that the presented set is complete: completeness against records the prover never presented is not a property of the presented set (see Section 13).

5.2. Checkpoint structure

The structure and signing rules address the Producer of a checkpoint; the authentication and scoping rules address the Chain Verifier, and apply only when a checkpoint is supplied.

A checkpoint is a separately signed assertion of a chain's current head. It is not a receipt and is not part of any chain; it is the object the tail check of Section 5.1 compares against.

{
  "spec":       "noa.checkpoint/0.1",
  "chain":      "<chain id>",         ; the scope.chain it speaks for
  "highestSeq": <int>,                ; chain.seq of the head receipt
  "headHash":   "sha256:<hex>",       ; chain.hash of that receipt
  "ts":         "<RFC 3339 date-time>",
  "sig":        { "alg": "ed25519", "kid": "<key id>",
                  "value": "<base64>" }
}

Its signature mirrors the receipt construction of Section 4.2, and is pinned here to the same byte-exactness:

CHECKPOINT-HASH-INPUT =
    JCS( the checkpoint object with exactly one member removed:
         sig.value; sig.alg and sig.kid remain present, and there
         is no chain.hash member to omit )

CHECKPOINT-MESSAGE =
       UTF8("NOA-Checkpoint-v0.1-sig:")   ; 24 octets: tag then ":"
    || SHA-256(UTF8(CHECKPOINT-HASH-INPUT))
                                          ; RAW 32-octet digest,
                                          ; NOT its hex form

sig.value = base64(Ed25519(CHECKPOINT-MESSAGE))
sig.alg   = "ed25519"

The domain tag is distinct from the receipt tag, so neither signature can be replayed as the other, and the colon is part of the signed prefix in both constructions.

The object is closed, and a Chain Verifier MUST validate its structure before authenticating it. A checkpoint MUST carry exactly the members shown above and no others, at either level, and sig MUST carry exactly alg, kid and value. A Chain Verifier MUST reject a checkpoint carrying any other member. Closure is load-bearing here in a way it is not for an ordinary schema: CHECKPOINT-HASH-INPUT copies every member of the object except sig.value, so a smuggled member sits inside the signature. Its signer signs it honestly, the signature verifies, and the closed-object check is the only control that rejects it. The value rules, stated as requirements rather than left to be read off a figure:

  • spec MUST be the exact string noa.checkpoint/0.1;
  • chain MUST be a non-empty string, and a Chain Verifier MUST compare it to the scope.chain of the presented set. A checkpoint naming another chain is not evidence about this one, and the finding is about the checkpoint: the verifier MUST report that the checkpoint does not describe the presented chain, MUST NOT use it for any tail conclusion, and MUST NOT let it fail an otherwise valid presentation. It is not a chain-level condition (Section 5.1), for the reason Section 8.3 gives: nothing about the presented receipts failed;
  • highestSeq MUST be an integer, at least 0, and within the range Section 3.2 fixes for numbers;
  • headHash MUST be "sha256:" followed by 64 lowercase hexadecimal digits;
  • ts MUST be a valid RFC 3339 date-time, under the same rule and for the same reason as a receipt's (Section 3.3);
  • sig.alg MUST be the exact string ed25519; sig.kid and sig.value MUST be non-empty strings; and sig.value MUST satisfy the encoding rules of Section 4.2.

A checkpoint failing any of these value rules is malformed, and so is one whose chain does not match. Every such failure is a finding about the checkpoint: it MUST NOT establish checkpoint-head-conflict, MUST NOT be reported as a finding about the chain, and MUST NOT by itself turn an otherwise valid presented chain into a failure. The object the verifier was handed is defective, which is a different statement from the records being defective; a verifier that conflates the two lets anyone who can hand over a file manufacture an accusation about someone else's history. What the verifier MUST do is report that it was given a checkpoint it could not use, and answer the tail question as unanswered (Section 5.1) -- the same outcome as being given no checkpoint at all, because that is what it effectively has.

Which key may sign it. A checkpoint is authenticated against the same keyring as the receipts. Authentication either succeeds or it does not, and a verifier that cannot perform it -- because it was given no keyring, or none holding that signer's key -- holds an unauthenticated checkpoint rather than an absent one: it MUST report every checkpoint-derived conclusion as unauthenticated (Section 5.1), and MUST NOT fall back to comparing heads. Key-level authentication alone, however, only establishes that some trusted key asserted this head. To bind the assertion to the chain, a Chain Verifier that was supplied an identity manifest (Section 6) MUST require the checkpoint's sig.kid to be one of the keys that manifest authorizes for the agent.id of the genesis receipt -- the receipt at seq == 0, the chain opener. A checkpoint signed by any other trusted key MUST be rejected as UNTRUSTED -- the result defined in Section 6 for a valid signature by a key this identity is not authorized to use -- rather than accepted as a tail check; this is what stops a co-trusted key from certifying its own truncated head.

Two scoping rules follow, and both are limits rather than features. The comparison is between two native key identifiers -- the checkpoint's own sig.kid, and the identifiers the manifest authorizes for the genesis receipt's agent.id -- because a checkpoint is a native artifact and is not carried in a COSE envelope; a deployment that wraps receipts for registration therefore has two identifier spaces, and MUST NOT assume the outer envelope's kid is the one the manifest authorizes here. And the guarantee is opener-scoped: on a chain carrying more than one agent.id, the opener's checkpoint certifies the opener's view of the head and does not certify a co-agent's tail. A Chain Verifier MUST NOT report an unqualified tail check on such a chain, and MUST state which agent's view the check covers.

What a checkpoint does not commit to, and what follows. The object above binds a chain identifier, a sequence, a head hash and a timestamp. It does not bind the tenant, the issuer, the agent set, the genesis hash, or any manifest version. Three consequences are normative for a Chain Verifier:

  • Freshness is not self-evident. ts is signer-chosen, so an authentic checkpoint stays authentic forever. This profile defines no expiry, and a verifier MUST NOT treat a checkpoint's age as bounded by anything in the object. A deployment that needs freshness MUST impose a maximum age from its own clock and say so in its output; an attacker who can replay an obsolete but authentic checkpoint otherwise faces no barrier.
  • The guarantee is opener-scoped, and a co-agent's tail is not covered. On a chain carrying more than one agent.id, a checkpoint authorized for the opener says nothing about receipts signed by a co-agent -- the requirement for that case is stated once, above.
  • Key identifiers are pinned within a presented chain. The reference implementation binds each agent.id to the first sig.kid it presents and rejects any later change as a key swap (Section 12). In-chain key rotation is therefore not expressible in this revision: a deployment that rotates a signing key starts a new chain, and this profile defines no rotation or revocation mechanism.

Checkpoint issuance policy -- how often a checkpoint is minted, how it is distributed, and how a relying party obtains one it did not receive from the prover -- is deployment-specific and is not specified here. A verifier given no checkpoint performs no tail check at all. It MUST report the tail question as unanswered, and MUST NOT report it as an absence of truncation. The absence vocabulary that names such a result is defined once, in Section 8.3; a Chain Verifier that uses it is reading a definition, not inheriting an Evidence-Verifier obligation. Those are different answers, which is what Section 8.3 exists to keep apart.

6. Identity binding

Attribution addresses the Receipt Verifier, conditional on an identity manifest being supplied. The relationship rules of Section 6.1 address the Evidence Verifier and depend on artifacts listed in Section 1.5.

agent.id is a signer-asserted label. To authenticate WHICH agent acted (not merely that a trusted key signed), a verifier MAY be supplied an identity manifest binding agent.id to its authorized key identifiers.

Each signature attributes a different party, and the manifest answers only one of the two questions. An enveloped receipt carries two identifiers, and treating either as a substitute for the other is the failure this subsection exists to prevent:

A checkpoint has one signature and one claim: the manifest check is against the checkpoint's own sig.kid, under the opener-binding rule of Section 5.2.

Without a manifest, attribution is key-level only -- "some key the verifier trusts signed this" and nothing about which agent -- and a verifier MUST surface that limitation to the relying party.

6.1. Who signed is not who approved

governance.approval.by names an approver. The receipt's signature establishes that the issuer emitted this record and has not since altered it; it does not establish that the named party held any key, saw this action, or approved it before it ran. A relying party MUST NOT treat governance.approval as pre-execution authorization by the named party. Section 9 states what would be required instead.

Two levels, and the difference between them is the whole answer. The strength of an approval claim depends on what the relying party was given, and a specification that states only one level misleads in one direction or the other.

  • A bare receipt, checked by a receipt verifier. governance.approval.by is an opaque string. The verification rules of this profile neither resolve it against a key nor compare it to any signer. It is covered by the issuer's signature -- so the issuer cannot alter the name after the fact -- but the name and any key are unrelated at this layer: a receipt naming a person who has never held a key verifies exactly like one naming the true approver. A relying party holding only a receipt therefore holds a recorded name, not a cryptographic party.
  • A complete evidence presentation. Where a deployment also produces the approval artifacts of Section 9, the approver IS a cryptographic party: a key enrolled as an approver, holding the authority tier the action's risk class requires, neither revoked nor used before activation, signs the decision artifact and the verdict receipt, and a verifier can require all three to be the same key. What becomes checkable at this level is narrower than "a human decided": it is that a key enrolled as an approver signed a decision bound to this action, and its absence is fatal rather than assumed. Whether a person held that key, saw the action, or understood it stays outside every check in this profile, as the paragraph below states.
  • An issuing gate MAY close the gap at emission time, and this profile recommends it. Nothing prevents the party that mints a receipt from requiring governance.approval.by to name the key that signed the approval, and refusing to issue otherwise. That is a producer-side control, so it binds nobody downstream and a verifier MUST NOT assume it ran. What it changes is what the issuer knows, not what a later reader can check: the receipt still carries an opaque string, and no member records that the gate ran (Section 8.1). A holder of that receipt alone is in the first case above, whatever the issuer's process was; the name becomes checkable only when the approval artifacts of Section 9 travel with it. The reference implementation's gate and grant signer both enforce the control (Section 12).

A Receipt Verifier or Chain Verifier that reports anything about an approval MUST state that its result rests on the opaque name alone. That requirement is testable: its input is a receipt and its subject is what a receipt carries. The second level -- a name backed by the approval artifacts of Section 9 -- is described here but carries no requirement in this revision, because those artifacts are not defined by this document. A bare receipt is precisely where the confusion arises -- and MUST NOT let a bare receipt's approval field be read as the second. Neither level establishes that a named human physically held the signing device: that binding is an out-of-band enrolment ceremony, outside every check either level performs, and it MUST be stated as a non-claim wherever an approval is presented. Nor does either level establish that anyone read, understood, or consented to what was displayed.

Section 12 reports a shipped instance of the second level and of the producer-side control, and states exactly which component leaves the name opaque and which refuses it.

The rest of this subsection is NON-NORMATIVE in this revision. It runs over the signed outcome artifacts and the trust configuration Section 1.5 lists as undefined here, and over a "request signer" this document can only describe by example. Nothing below can be tested against this specification alone, so none of it is stated as an RFC 2119 requirement: it is what a companion that defines those artifacts has to require, written now so that the companion inherits the analysis instead of rediscovering it. An implementation cannot fail this subsection, and Section 15 records the work of turning it into requirements somewhere it can be tested.

Where an outcome about the action is itself signed, independence is decided by comparing the outcome signer against two other parties, because a deployment that separates them has two distinct keys and collapsing them hides a real dependency:

  1. the request signer -- the key that asked for the action (in the reference implementation, the gate key that signs the request receipt, the execution grant and the hold records); and
  2. the authorization signer -- the key that approved it (the enrolled approver device key that signs the decision artifact and the verdict receipt).

A verifier has to derive and report each relationship separately rather than a single merged verdict: an outcome independent of the approver but signed by the requesting gate is not independent evidence, and neither is its mirror image. Comparing against whichever key merely emitted the receipt answers a third question and is not a substitute for either. Both relationships have to be derived by the verifier from its own trust configuration. A self-declared relationship -- a producer writing "independent" about itself -- is not evidence of independence, and an undetermined relationship cannot be upgraded to an independent one.

A verifier that reports such a relationship draws the value from the vocabulary below -- closed, though a verifier need not be able to produce every member of it (see the rule after the table) -- names the two parties it compared, marks the result as verifier-derived, and carries the identity of the trust configuration it used -- for example a trust-policy digest over the thresholds applied and a registry-snapshot digest over the key material consulted -- so that the determination can be recomputed by anyone holding the same configuration. This profile does not define those digests' constructions (Section 1.5), so reproducibility here is scoped to parties who share a construction rather than promised in general. What it requires is that a verifier reporting a relationship publish enough for someone else to recompute it, and state the construction where consumers of the result can find it; values computed under different constructions are not comparable:

Table 5
Relationship Meaning
SAME_SIGNING_KEY one key signed both
SAME_ADMINISTRATIVE_PARTY distinct keys under one administrative registry
SEPARATE_ROLE_SAME_ORGANIZATION distinct authorized purposes, one organization
INDEPENDENT_ORGANIZATION distinct organizations, established from the configuration
UNKNOWN the configuration does not establish a relationship

Two rules make this testable rather than decorative.

  • A verifier should not offer a value its configuration cannot derive. An enum member that no input can produce is an invitation to claim it: a consumer reads the vocabulary, not the derivation. A verifier that cannot establish organizational independence omits INDEPENDENT_ORGANIZATION from what it can return, rather than returning it on weak evidence.
  • SAME_SIGNING_KEY is decided on key material, not on a key identifier. One key presented under two identifiers, in two roles, is the same signer; a determination that compares identifier strings alone reports independence to an attacker who registers a second name for one key.

UNKNOWN is the default and is not a failure of the artifact; it is a statement about the verifier's configuration. Where a consumer asks for independent evidence (Section 8.2), no result other than INDEPENDENT_ORGANIZATION satisfies that intent. Separate keys prove only distinct keys; separate processes improve isolation; separate roles prove purpose authorization. None of the three, alone, establishes independent administrative control. Section 12 reports a shipped instance of this determination, including the value it deliberately does not offer.

7. Registration in a Transparency Service

This section addresses the Transparency Service, except the first sentence, which addresses the submitting Deployment.

A receipt carrying the protected claims of Section 4.3 is a Signed Statement and MAY be submitted to a Transparency Service. A receipt without them is not, and MUST NOT be described as registrable.

The Registration Policy for this profile is stated as a list because each item closes a specific substitution. A Transparency Service registering a statement under this profile:

  1. MUST verify the outer COSE_Sign1 signature, and MUST verify it with a key it has bound to the Issuer named in iss -- the resolved key or certificate belongs to that Issuer, or an explicit delegation from that Issuer to the signing key is presented and checked. [RFC9943], Section 6.3 states the same requirement in its own terms: the signature is verified with the verification key of the Issuer. A service that cannot make that binding MUST NOT record the statement as that Issuer's;
  2. MUST verify the protected header conforms to Section 4.1 -- an alg of -19 and deterministic CBOR -- and MUST require a protected kid for registration specifically. A receipt whose kid is only in the unprotected header conforms to Section 4.1 and is not registrable under this policy: registration binds a subject to an issuer, and an identifier the signature does not cover cannot do that. Producers intending to register MUST protect the kid;
  3. MUST verify that protected CWT_Claims are present and carry both iss and sub;
  4. MUST verify that the payload is a well-formed receipt under Section 3; and, where it can resolve the native signer's key, MUST verify the payload's native signature. Registering an envelope whose payload is a forged receipt is precisely the attack the outer signature does not stop, since Section 4.2 permits the two signers to differ. This profile defines no way for a Transparency Service to obtain that key -- the keyring is an out-of-band input (Section 2) -- so where the service cannot resolve it, it MUST refuse the registration. The alternative a reader might expect, registering while attesting that the payload was not checked, is not available: this profile defines no header, claim, log-entry field or media type in which that status could travel, so a later reader could not distinguish it from a checked one. Refusal is the only outcome this document can specify without inventing a carrier it has not defined. A future revision that defines one MAY relax this;
  5. MUST verify that sub equals the payload's scope.chain, so that the subject a log indexes is the chain the statement is actually about; and
  6. The issuer/submitter relationship is the sixth item, and it is guidance rather than a requirement, because this document cannot make it testable: a Transparency Service applies its own policy to the relationship between the Issuer named in iss and the party submitting the statement, and records which policy it applied. This profile does not require those two to be the same party, does not define the policy, and -- decisively -- defines neither the minimum contents of such a record nor anywhere it is carried, so a service that refused every submission under an unnamed policy would satisfy any wording used here. What the profile can do is say plainly that the question exists and that a service which never asks it is registering statements whose issuer relationship nobody checked.

What iss is worth, and where it stops. This profile defines no issuer key discovery: no registry, no well-known location, no delegation format. Item 1 therefore states a requirement whose mechanism lives outside this document, and a deployment must supply one. Until it does -- and for any holder who has only the receipt, with no registration behind it -- iss is an issuer-asserted string: it names the authority the signer claims to speak for, and nothing in the envelope proves the relationship. A Transparency Service, and any Receipt Verifier that reads iss at all, MUST NOT report it as an authenticated identity on the strength of a valid outer signature alone, and MUST say which it had: an identity bound to the verification key, or a string the signer asserted.

A Transparency Service knows this policy applies by agreement with the registrant, not by inspection: no media type is registered for the payload (Section 16), so a service cannot dispatch on content type alone. Until one exists, a deployment states the profile out of band.

Items 4 and 5 are the ones a reader should not skim. Item 4 is what stops a trusted registrant obtaining a log entry -- and a Transparency Receipt -- for a receipt nobody in the chain ever signed. Its cost is stated rather than hidden: a Transparency Service that cannot resolve native signer keys registers nothing under this profile, which is a real operational constraint and the honest consequence of having no way to publish "not checked". Without item 5 an issuer can register a chain-A receipt under sub = chain-B, leaving the log holding a subject history that no receipt supports. On success the TS returns a Transparency Receipt: a third party's signed proof that this statement was registered in its log. In [RFC9942]'s terms that is a proof about the log's verifiable data structure, not the COSE countersignature header parameter of [RFC9052]; the two are different constructs, and this profile means the first everywhere it says Transparency Receipt.

What registration is worth, stated exactly. An inclusion proof establishes membership in the view that log served. Non-equivocation -- that the log did not serve one history to one party and a different history to another -- is not established by any single receipt. It requires consistency proofs between log states and a party that monitors them; this profile specifies neither the monitoring role nor any requirement on anyone to perform it, and a relying party that wants non-equivocation MUST obtain it from the Transparency Service's own ecosystem.

The offline case gets the same scepticism this document applies to checkpoints. A Transparency Receipt handed to an offline relying party by the prover is prover-supplied evidence, exactly like the checkpoint of Section 5.2: a better signer, and still no way for a party that never queries the log to detect a log that served two views.

Two further honesty notes. Nothing in Section 5 supplies any of this, so a deployment that never registers has strictly less. And no reproducible demonstration of registration against an external Transparency Service has been published for this profile (Section 12): the policy above is a specification, not a report of an executed interop.

8. What a receipt does not claim about the external world

The non-claims in this section constrain what any actor may assert, and each is written against the actor it binds. Two blocks are marked NON-NORMATIVE where they appear -- the claim-separation duty of Section 8.1 and the intent machinery of Section 8.2 -- because their inputs are artifacts Section 1.5 lists as undefined; the absence vocabulary of Section 8.3 is normative, because a verifier's own results are something this document can constrain.

A receipt in this profile is emitted at the governed boundary: the point where an action was admitted, refused, deferred, or dispatched. Everything beyond that boundary -- what a downstream controller did with the request, and what happened physically -- is outside the record.

8.1. Three claims, three independent failures

The following are distinct claims. They may share one action digest as a join key (Section 10), but digest equality proves none of them. Each has to be evaluated and reported separately, so that one can fail while the others stand -- a requirement for whichever document defines claims 2 and 3, since this one does not (Section 1.5):

  1. Pre-execution authorization -- a named, accountable principal approved this exact action before it ran.
  2. Controller-reported outcome -- an identified downstream system reported a terminal status for the request, under its own semantics.
  3. Physical completion -- an identified witness observed the intended effect, against a criterion fixed before execution.

A receipt under this profile carries NONE of claims 2 and 3, and carries claim 1 only as the signer-asserted label described in Section 6.1. A deployment MAY apply the producer-side control Section 6.1 recommends -- refusing to issue unless governance.approval.by names the key that signed the approval -- but no receipt carries evidence that the control ran, so a verifier MUST NOT report anything about it. Whether the control was applied is a fact about the issuer's process, established outside this profile or not at all. These are NORMATIVE non-claims, scoped to what a receipt establishes. A Producer MUST NOT present a receipt as evidence of any of the three. A verifier evaluating a receipt -- alone or as part of a chain -- MUST NOT return a result whose named fields assert any of them on that basis, and where a consumer asked for one it MUST report that the receipt does not establish it. For claims 2 and 3 Section 8.2 names the intent the consumer asked for and the evidence it would require. For claim 1 it does not: this profile defines no intent token for pre-execution authorization, so the report carries the non-claim above rather than a name from Section 8.2. The prohibition is about the basis of an assertion rather than about the claims themselves: a verifier that also holds an authenticated outcome or observation artifact may report what that artifact establishes, which is the case Section 8.2 describes.

In particular, governance.verdict == EXECUTED records that the action was dispatched, or attempted, at the governed boundary. It does not attest that a controller accepted the request, that the request succeeded, or that any physical effect followed. A controller reporting "succeeded" attests success under its own semantics; that is a report, and a report is not an effect.

8.2. Validity and sufficiency are separate results

A receipt may be cryptographically impeccable and still be the wrong evidence for the question being asked. Two questions therefore have to be kept apart:

  • artifact validity -- signature, canonical form, chain position, identity binding;
  • sufficiency for the consumer's intent -- whether what was presented answers the question asked.

One of the four intents is normative here and three are not, and the line is drawn by what this document defines. A verifier that supports intents at all MUST answer BASE_ACTION_RECEIPT from receipt validity alone, and MUST NOT report it as satisfied for an object that fails Section 3 or Section 4: its predicate is defined here in full, so two implementations can be tested against the same answer. The other three are satisfied only by artifacts Section 1.5 lists as undefined, and the machinery around them -- how an intent is supplied, how sufficiency is compared, how the answer is carried -- is undefined with them. Those three rows and the paragraphs that follow the table are NON-NORMATIVE in this revision, for that reason -- with one exception, labelled where it appears at the end of this subsection, which is normative because its subject is a receipt. It names four intents so that the common questions have shared spellings, and defines each by the evidence that answers it:

Table 6
Intent Satisfied only by
BASE_ACTION_RECEIPT a receipt valid under Section 3 and Section 4
CONTROLLER_OUTCOME an authenticated controller-outcome artifact bound to this action. Section 8.4 describes what such an artifact must say; this revision defines no wire format for it (Section 1.5)
PHYSICAL_COMPLETION an authenticated physical-observation artifact bound to this action, from a witness the verifier trusts for that purpose. Same status: described in Section 8.4, not defined here
INDEPENDENT_OUTCOME one of the two artifact-based intents above -- CONTROLLER_OUTCOME or PHYSICAL_COMPLETION -- whose signer the verifier derives as INDEPENDENT_ORGANIZATION (Section 6.1). A receipt cannot satisfy it: the receipt's issuer is the party whose account is in question, so it is never independent of itself

Only the first is satisfiable from a receipt alone. The other three need artifacts Section 1.5 lists as undefined in this revision, and INDEPENDENT_OUTCOME is additionally unsatisfiable by any verifier that cannot derive organizational independence -- which, per Section 12, is every implementation this document reports on. A verifier that cannot satisfy an intent reports that in those terms rather than as a failure of the artifact.

A verifier that supports intents should support at least these four and report a named insufficiency rather than a generic failure: a relying party that asked for physical proof and received a valid receipt has learned something specific, and collapsing that onto "invalid" destroys it. Where two authenticated claims conflict -- a controller reporting success and a witness reporting that the effect did not occur -- the conflict is its own result, carrying both claims, with neither erased in favour of the other; this document defines no token for it (Section 1.5).

One rule here is normative, because it constrains what may be said about artifacts this document does define. A verifier MUST NOT report an unqualified "physical completion proven". The strongest defensible result is proven to the stated policy, naming the trust configuration that made it so, and a receipt never supplies it (Section 8.1).

8.3. Absence is not a negative claim

A verifier MUST keep the following distinct, and MUST NOT collapse them:

Table 7
Result Meaning
NOT_CLAIMED the producer made no claim on this point
NOT_PROVIDED a claim may exist, but the verifier was not given acceptable evidence of it
NOT_APPLICABLE the point does not arise for this action
INDETERMINATE evidence was presented but does not settle the question

"The verifier did not receive acceptable evidence" is not "no evidence exists", and rendering the first as the second turns a gap in a presentation into a negative finding about the world. This is the same discipline Section 5.1 applies to set-level failures: name the condition, do not merge it. A verifier MUST NOT define a result code that asserts a negative fact about the world it did not observe; Section 12 records a shipped verifier that carries no such code by design.

A finding names the object that failed, and no other. The rule the paragraph above is one instance of, stated once so that an implementer can apply it to the case this document did not foresee: a failure to authenticate or to understand one object MUST NOT produce a finding about a different object. The subject of the finding is the object that failed.

The rule is about whose failure is being reported, not about whether failures may be reported at all. A receipt whose own signature does not verify is TAMPERED -- a finding about that receipt, and the correct one. A receipt this profile cannot parse is malformed -- again about that receipt. What is forbidden is transferring the failure: an unauthenticated checkpoint yields checkpoint-unverified, a finding about the checkpoint, and never a finding about the chain (Section 5.1); a checkpoint whose structure or scope is wrong is a finding about the checkpoint (Section 5.2); a trust document a verifier does not recognise is a finding about that document, never about the receipts it was supposed to authenticate. In each case the honest object -- the chain, the receipts -- is not accused of anything, because nothing about it failed.

This matters because the accusation is the whole thing these artifacts exist to make credible. A verifier that turns its own inability to read one file into a finding against someone else's records has produced the one output a verification tool must never produce, and Section 12 records two measured places where a shipped implementation does exactly that.

8.4. Why the base record is not extended

A machine-checkable non-claim carried as a field of the base receipt -- for example a physical_completion member defaulting to "none" -- was proposed on the SCITT list and is not adopted here, for two reasons that are worth stating rather than asserting.

First, mechanics: adding a member to noa.receipt/0.1 changes the JCS bytes of every receipt that carries it, and therefore the receipt hash, the native signature, the prevHash linkage, every checkpoint over that chain, and every archived conformance vector. That cost is payable only at a new wire version with a new signing domain, never by mutating a version other implementations have already frozen.

Second, semantics: a default value on a frozen record cannot distinguish "the producer considered physical completion and claims nothing" from "the producer has never heard of the question". A default is not a statement.

The claims of Section 8.1 therefore belong in separate signed artifacts, each linked to the authorization by hash -- at minimum the hash of the receipt that carried the authorizing verdict and the hash of the execution grant, and OPTIONALLY a shared action digest (Section 10) -- and each carrying its own signer, its own vocabulary, and its own failure. A controller-outcome artifact says what a controller reported; a physical-observation artifact says what a witness observed, under a named method, in a named window, against a criterion digest fixed before execution. Motor or controller telemetry alone is not an observation of the object.

This revision does not define those artifacts' wire formats. It fixes the vocabulary and the failure model they must satisfy, so that the work can land in one place instead of fragmenting across profiles, and so that a receipt consumer today can be told exactly which question a receipt does not answer.

9. Pre-execution authorization and post-execution evidence

This section is addressed to the Evidence Verifier (Section 1.4), over artifacts this profile does not define (Section 1.5), and is NON-NORMATIVE in its entirety: no bullet below carries an RFC 2119 requirement, none of them can be failed, and there is no exception. The requirements they point at live where they are testable -- Section 8.1 states the non-claims a receipt cannot escape, and Section 8 states what a bare receipt cannot establish. A Receipt or Chain Verifier neither satisfies nor fails anything in this section.

Authorization and evidence differ in time, in signer, and in failure mode. An authorization is made before the action, by a party accountable for allowing it, and fails if that party did not approve this exact action. Evidence is made after the action, by a party that observed something, and fails if the observation is not bound to the same action or its signer is not trusted for the purpose.

The substitution these differences permit is the one this section is about. All three bullets run over artifacts, comparisons or results this document does not define -- the hold, decision and execution-grant records of Section 1.5, and the query that would ask about a named human -- and no binding defined here establishes which grant authorized a particular attempt, or that it preceded the attempt: ts cannot supply the ordering, because it is signer-chosen (Section 3.3). They are written as what a companion defining those artifacts has to require.

A relying party that needs the authorization claim needs artifacts this profile does not define: a signed hold or deferral, a signed decision by a party holding an approval role, and a single-use execution grant, each under its own signing domain. Section 12 reports what the reference implementation does today, so that the gap between this profile and that implementation is a measurement rather than an impression.

10. The shared action digest boundary

The first rule below binds the Producer that emits a digest -- it must travel with a construction identifier -- and every actor that compares one, in practice an Evidence Verifier. The second rule binds whoever reads the comparison.

Records about one action are only comparable if they carry a value that means the same thing to both producers. Two rules make that possible, and this profile states both while defining neither construction.

Determinability, not assumption. This rule -- the one paragraph below, and nothing else in this section -- is NON-NORMATIVE here: the digest it governs is not a member of this record and is defined by a companion (Section 1.5), and it is written because a companion defining one has to carry it. The two requirements at the end of this section are normative and are labelled there. Any digest offered as a cross-producer join key travels with a construction identifier, and that identifier commits to the construction's canonicalization parameters -- serialization, member sort, number rendering, normalization, escaping, digest algorithm, and domain separation. A verifier presented with a digest under an unrecognised or non-matching construction identifier does not compare the values at all. The comparison then yields nothing -- not a match, and not a mismatch -- and is reported as indeterminate: "these records were not shown to be about the same action", never "these records are about different actions". That is the same distinction Section 8.3 draws; a question the verifier could not reach is not a negative answer to it. Two constructions that differ only in whether they apply Unicode normalization will agree on almost every input and disagree on the ones that matter; a shared vector, not a shared name, is what establishes that two parties compute the same value.

Correlation is not attestation. Digest equality establishes that two records commit to the same action projection -- the same field values under the same construction. It establishes nothing about whether that action was authorized, whether a controller reported anything, or whether any effect occurred; each of those fails independently (Section 8.1). A digest is a join key, and a join key that is mistaken for an authorization claim is the substitution Section 9 forbids.

Two rules do stay normative here, because their subject is this record. action.paramsHash does not satisfy the first rule above and MUST NOT be used as a join key (Section 3.2); and a verifier MUST NOT treat equality of any digest as evidence that an action was authorized, which is the substitution Section 9 is about. Nor does the base record carry a member for a shared digest: noa.receipt/0.1 is closed (Section 3), so a correlation value travels beside a receipt in whatever artifact references it, never inside one. A deployment that wants correlation does not modify the receipt.

A construction meeting both rules is published: [I-D.toraman-noa-action-digest] fixes the projection members, the domain tag, the preimage, the digest, and a published conformance corpus (counted in Section 12). One consumer of it is [I-D.toraman-noa-settlement-evidence]. This profile does not restate that construction, does not require it, and would apply the same two rules to any other.

On indeterminate positive controls. In an independent reconciliation run reported on the SCITT list on 27 July 2026 (https://mailarchive.ietf.org/arch/msg/scitt/zWZBQXXSGV-vgbTeOLTjlb9DGLg/), a positive control was held INDETERMINATE because the digest it carried came from a construction the reconciler had no vector for. That is the rule above working, not a harness defect: a positive control that upgrades itself on an unverified binding makes every negative case unreliable too.

Three things follow, and they are different things.

11. Policy replay is out of scope

This section addresses the Producer and every verifier: none of them may present a receipt as carrying a re-derivable verdict.

A deterministic, offline policy-replay capability -- a verifier re-running the in-force policy over the recorded inputs to re-derive the recorded verdict, with no access to the agent, model, or any service -- is a distinct capability that this revision deliberately does NOT specify. The optional commitment of Section 3.3 carries hashes of a policy, its read-set, and the recorded inputs; it does not carry the replay construction (the deterministic evaluation grammar, operator set, evaluation order, and conformance vectors), which is left to a separate companion profile and is not normative here. A Producer and every verifier MUST NOT represent a receipt under this profile as carrying a re-derivable verdict, and MUST NOT infer from a present commitment that the referenced policy was adequate or in force.

12. Implementation status

This section is to be removed by the RFC Editor before publication, per [RFC7942]. Removing it will renumber what follows, so a citation of this revision by section number should be read against this revision. References to this section from the body are pointers to measurement, never to normative content: no requirement depends on it.

A zero-runtime-dependency reference implementation (signer, offline verifier, deterministic CBOR, COSE_Sign1 profile, JCS canonicalizer, conformance vectors, and separately implemented verifiers in four further languages) is available under the Apache-2.0 license at https://github.com/NordenSoft/noa. Unless explicitly stated otherwise, the measurements below are from main commit 4764cab. Later repository changes are identified by their own commit hashes and are not included in the pinned snapshot's counts. No number in this section is an estimate: each was produced by running a command or counting files in that tree. Three classes of measurement are reproducible from what is written here -- the corpus counts (three are file counts in the named directories; the action-digest and settlement-evidence corpora are each a single vectors.json whose entry array is counted, which is why counting files there gives the wrong answer), the conformance run (node impl-py/conformance.mjs), and the repository facts cited to a path. The rest -- the 25-verdict cross-field measurement below, the divergence runs of D1, and the decoded header bytes -- were produced by ad-hoc scripts that are not committed, so they are reported as measurements with their inputs described rather than as anything a reader can re-run today. Committing them is open work.

Algorithm identifier. The implementation emits the fully-specified Ed25519 identifier -19 ([RFC9864]) and rejects every other alg, including the polymorphic EdDSA identifier -8, which [RFC9864], Section 4.2.2 marks Deprecated (it was registered in [RFC9053]). There is one emission site and one verification gate; no code path emits -8 and no legacy-compatibility flag exists.

The protected header it builds is the deterministic CBOR map {1: -19, 4: kid} -- two members, because the kid is in the signed bucket, so attribution cannot be swapped without breaking the signature. Its bytes therefore begin a2 01 32 04 ..., the trailing part being the kid byte string; a measured envelope's header reads a2 01 32 04 49 ... for a nine-octet kid. A forged envelope carrying -8 under a genuine Ed25519 signature -- the only construction that tests the alg pin rather than the signature -- is rejected at the product API with "protected header alg is not Ed25519 (-19, RFC 9864)"; so are -7, alg as a text string, a non-canonical two-byte encoding of -19, a duplicated alg label, and an absent alg.

One implementation, stated as such. The -19/-8 behaviour above is a property of a single COSE implementation. The separately implemented Python verifier does not implement COSE at all, and the Go, Rust and C# verifiers contain none, so they provide no confirmation in either direction. A third-party check does exist and did run -- a foreign stack (cbor2 + PyCA cryptography) reads alg = -19 from a shipped vector, verifies the Ed25519 signature over the RFC 9052 Sig_structure, and rejects a tampered payload -- but that corpus holds one vector and it is a positive one. At the pinned snapshot that vector's own metadata still carried the stale a10132 header string beside an envelope whose protected header holds both alg and kid, so the file contradicted itself; df777a6 corrected it. There is no cross-implementation NEGATIVE vector for -8; that refusal is proven only inside this implementation's own tests, and closing it is one of the open vector commitments recorded below.

The two signatures. receiptToCose wraps the JCS-canonical receipt as the COSE_Sign1 payload; receiptFromCose verifies the COSE signature, re-canonicalizes the payload and requires byte equality with the signed bytes, and validates the receipt shape -- it does not verify the native sig.value or any chain property, which is the separate verifyChain path. The two checks are genuinely independent, which is what Section 4.2 says they are; what no entry point does is run both, recorded as a gap below.

Chain-level axes. All six chain conditions of Section 5.1 are detected and reported distinctly, under the implementation's own reason strings rather than under this document's labels: a broken link, a duplicate sequence number, a sequence gap, more than one chain partition in one input, the cross-tenant split, and a mid-chain key change for one agent -- the last reported as a "key swap", in both the TypeScript and the separately implemented Python verifier. What it gets wrong is the checkpoint axis, on both of its named outcomes: checkpoint-unverified is not distinguished at all, and checkpoint-head-conflict is reported over-broadly by treating a legitimate extension as one. Both are in the gap list below. scope.tenant drift across one chain is fail-closed by default, and absence of the member does not reset the comparison.

On the word "separately". What is demonstrated is five implementations in five languages that agree on a shared corpus, not five independent derivations of this specification: the Go, Rust and C# sources describe themselves as ports of the Python reference, and the Python one was written against the TypeScript one. Agreement between a source and its port is weaker evidence than agreement between two independent readings of a specification, and this document does not claim the stronger thing.

Cross-implementation conformance, run rather than cited. node impl-py/conformance.mjs, executed at the pinned 4764cab snapshot on 2026-08-14, ran 99 checks and exited 0, with its own summary line reporting that the separately implemented Python verifier agrees with the TypeScript reference on every vector, including the impersonation, truncation and duplicate-key security verdicts. The Go, Rust and C# verifiers are each compared against the Python one over a shared file-based corpus, and the TypeScript reference is compared against it too, so the topology is a star with the Python reference at its centre rather than five implementations tested against one another; what that can and cannot settle is stated at the end of the register below. No claim of demonstrated external interoperability across all five is made here.

Conformance gaps against this revision's own rules, at the pinned snapshot. What follows is the output of two independent reverse audits -- code read first, then the requirement it should have mapped to -- and it is deliberately long. An earlier draft of this section listed six gaps and called that the whole count; the claim was false, and the correction is to publish the register rather than to shorten it. "All five" below means the TypeScript reference and the separately implemented Python, Go, Rust and C# verifiers. A fix for the first three is in flight in the reference repository; this register describes the code as measured at the pinned snapshot, not the code as it will be.

One measurement covers the first three rows, and it was run rather than reasoned. Five receipts were built, each carrying action.riskClass: CRITICAL on a wire.transfer action, and each was presented as identical bytes to all five verifiers: (A) agent.principal: SANDBOX_SIM with governance.sandboxed: false and governance.verdict: EXECUTED; (B) governance.verdict: SIMULATED with governance.sandboxed: false; (C) action.reversible: false with action.rollbackRef: "snap_1"; (D) governance.verdict: ROLLED_BACK with action.reversible: false; (E) ts: "2026-13-45T99:99:99.000Z". All 25 combinations returned VALID.

A and B are the worst of the five, for the reason this profile exists at all: a reader has to be able to tell a real action from a rehearsal. A and B are signed, chain-valid, fully verifying receipts for an action the issuer itself classified CRITICAL, and they can be argued in both directions afterwards -- the issuer can say the record shows a simulation, the counterparty can say it shows an execution, and the receipt supports both readings. The receipt carries no amount and this profile defines no way to open paramsHash (Section 3), so nothing here establishes what the action was worth; the risk class the issuer assigned is the whole of what the record says about its seriousness.

Behaviour the reference enforces that this revision does not require. These are not gaps against a rule; they are places where a receipt this profile calls conformant is refused by the reference implementation. That is an interop hazard in the opposite direction and it is stated here rather than discovered in a bake-off. The classes that earlier reviews found in this position have left this list, because this revision states them as requirements: checkpoint structure and scope; the native sig.alg literal; the key type, encoding and public-point rules; the id bound; the non-empty-string members; the forbidden member names; compliance.verdict; and the UTF-8 round trip before a kid is resolved. What remains:

Divergences among the five that the conformance harness cannot see. The harness compares verdicts over a shared corpus. A behaviour only one implementation has, or that no vector exercises, is invisible to it -- and these were found by reading the five sources against one another rather than by running them.

What the harness can and cannot settle, stated once. conformance/MATRIX.md is generated from four runs: the TypeScript reference against the Python one, and then the Go, Rust and C# verifiers each against the Python one. The topology is a star with the Python reference at its centre, not five implementations tested against one another -- so a behaviour the Python verifier does not implement cannot be seen by that harness at all, and five-way agreement and five-way nonconformance coexist without contradiction. G1 to G3 are exactly that case: all five agree with each other, and none of them agrees with this document.

Two claims in the reference implementation's own comments are retracted by this revision and not yet corrected there. src/cose/receipt-cose.ts calls its output a "COSE_Sign1 Signed Statement" and promises "the external non-equivocation anchor NOA's self-signed chain lacks". Neither survives this revision: Section 4.3 shows what a Signed Statement additionally requires and the register above records that the implementation emits none of it, while Section 7 narrows what registration supplies. A reader who opens the code will find the older wording; it is a comment, not a control, and correcting it is filed.

Conformance corpora, counted at the pinned snapshot. conformance/vectors/attack/ 16 files and conformance/vectors/malformed/ 9 (25 adversarial in total); conformance/action-digest/ 33 vectors, 34 in the repository since df777a6; packages/rail-x402/conformance/settlement-evidence/ 72 (1 accept, 57 reject, 14 control); packages/evidence/conformance/ 77 bundles. Alongside them, conformance/golden/ answers a different question: whether a real past release's own signed output still verifies today.

What a third party measured. On 27 July 2026 an independent reconciliation implementation ran against this corpus at a pinned commit and reported on the SCITT list (https://mailarchive.ietf.org/arch/msg/scitt/zWZBQXXSGV-vgbTeOLTjlb9DGLg/): 23 of 23 adversarial files then published produced at least one integrity-class divergence, with cross-chain-splice the clean case -- both receipts verify in isolation and the forgery exists only in the set. The same run measured a shared subject digest agreeing with another party's canonicalization corpus on 22 of 22 cases, while a separate ledger-indexing construction that applies Unicode normalization agreed on 20 of 22 under a UTF-16 member sort and 19 of 22 under a code-point sort -- the collisions being invisible without a shared vector, which is the argument Section 10 makes normative. The corpus has since grown from 23 adversarial files to 25.

What is still missing. The purpose-specific negative vectors that Section 8, Section 8.2 and Section 9 call for are not in the public corpus, with one exception: the attribution-substitution vector landed at df777a6. The rest -- a controller success with no physical evidence, a controller success against a witness reporting non-completion, same-party evidence presented as independent, a missing signer relationship, a stale or replayed observation, and the positive baselines mirroring each -- remain committed and unwritten, as does a cross-implementation negative for the alg pin.

Not implemented, stated plainly. Ten things, so that no reader has to infer them. Item 1 is gap G8 of the register above. Item 2 is permitted but unimplemented; items 3 and 4 are capabilities this profile deliberately excludes (Section 4.2, Section 5.1); the rest are absences with nothing owed against a rule. The distinction is the one Section 8.3 insists on, applied to this section.

  1. No Signed Statement mode exists anywhere in the reference implementation, on either side. The producer emits {1: -19, 4: kid} and no CWT_Claims (label 15), so what it mints does not meet Section 4.3 and a conforming Transparency Service is entitled to refuse it at registration. The verifier is the other half, and the half a reader is most likely to assume: it ignores label 15 as a non-critical header, and a committed test shows it accepting a protected header carrying a malformed 15: bstr as a perfectly good generic COSE_Sign1. No code path anywhere evaluates a receipt as a Signed Statement.
  2. Certificate-based key resolution is permitted but not implemented. Section 4.1 lets a deployment carry x5t or x5chain in addition to kid; the verifier ignores both headers and resolves kid alone. Nothing is excluded -- it is simply not built.
  3. There is no detached-payload mode. The parser requires the payload element to be a byte string and refuses a null one; the producer always attaches. Section 4.2 describes that and nothing more.
  4. No range-presented verification. Every implementation requires the complete chain from seq == 0; a presentation starting at seq 1 is refused as a missing genesis, not verified as a partial view. Section 5.1 states that as the rule. A verifier that could authenticate a mid-chain range against a trusted anchor is a real capability and this profile does not have one.
  5. No controller-outcome artifact and no physical-observation artifact. The vocabulary of Section 8.1 for claims 2 and 3 is specification ahead of implementation. The plane that exists is a payment settlement plane over one rail family; it is an instance of the shape (Section 8.2, Section 8.3), not an answer to the actuator case the review thread was about.
  6. No settlement-evidence enrolment registry at the pinned 4764cab snapshot -- the registry an Evidence Verifier would consult to decide which action classes require settlement evidence. Device enrolment through pairing is a different mechanism and did exist at that snapshot, with its own conformance vectors; this item is not about it. Without the action-class registry, settlement evidence cannot be required, so at this snapshot its absence costs nothing and its presence buys no upgrade. Subsequent implementation work is outside the measurements reported here.
  7. The relationship signal is not surfaced in bundle verdicts, and no level of this implementation can derive organizational independence at all.
  8. The bundle verifier never reaches a network service, by design: a verifier that silently depends on a reachable service is not an offline verifier. Anything that would require a live query is therefore outside what one of its verdicts can establish.
  9. No published reproducible demonstration of registration with an external SCITT Transparency Service.
  10. The purpose-specific negative vectors of Section 12 are not written, including a cross-implementation negative for the alg pin. The attribution-substitution vector that was open in earlier drafts of this list is no longer among them: it landed at df777a6.

The boundary item 1 leaves is worth stating in the sharpest available words: "the signature verifies" and "an RFC 9943 Signed Statement validates" are different properties, and this implementation currently establishes only the first. Closing it is work on both sides -- the producer must emit the protected claims, and the verifier must gain a mode that checks them and refuses a receipt that lacks or malforms them, per Section 4.3 and [RFC9597], Section 2, which defines label 15 as a CWT Claims map that occurs exactly once across the two header buckets.

Every one of these is a boundary this document states rather than a gap it hides. Where a reader finds a normative requirement here whose enforcement is on this list, that requirement is a specification for implementers -- including this one -- and not a report of shipped behaviour.

13. Security Considerations

Analysis binds every actor. Where a sentence carries a requirement it names its actor; the requirements in this section fall on the Producer (what MUST NOT appear on a receipt), on verifiers (what they MUST NOT report), and on the Relying Party (how results may be read).

This profile attests exactly two things: (1) the record is tamper-evident and signature-verifiable under the issuer's key; and (2) the recorded fields -- action, principal class, verdict, and whichever optional members are present -- are bound into that signature. "Policy identity" is one of the optional ones: both carriers may be absent (Section 3.3), and a receipt that omits them records no policy identity at all while remaining conformant. The principal field is a class marker and not an identity: a receipt recording HUMAN names no human, and a relying party MUST NOT read one out of it. It does NOT attest that the recorded inputs are true, complete, or timely (no capture-completeness); that the policy was adequate; that no action occurred outside the instrumented boundary; that the agent was correct, safe, or wise; that a named approver authorized this exact action before it ran; that a controller succeeded; or that any physical effect occurred. These non-goals are NORMATIVE: implementations and relying parties MUST NOT imply the stronger claims from a receipt.

Equivocation -- one issuer signing two divergent histories and showing each to a different party -- is not detectable by any check in this profile. Section 5.1 catches a fork only when both branches are presented together, as a duplicate sequence; nothing reaches a branch the prover withholds. Registration in a Transparency Service is a necessary step toward detecting them and is not a sufficient one: detection additionally requires consistency proofs between log states and a party that monitors them (Section 7), neither of which this profile specifies or requires. A deployment that registers and never monitors has an auditable record and no equivocation defence. Identity attribution above key level requires the out-of-band identity manifest. Algorithm-confusion and key-confusion attacks are mitigated by the strict alg = -19 and pinned-curve requirements of Section 4. Raw prompts, tool arguments, and secrets MUST NOT appear on a receipt; only hashes (optionally HMAC for low-entropy values) are carried.

The scope of a checkpoint check is the scope of the key that signed it. A checkpoint can reveal truncation relative to itself, and only to the strength of the binding between checkpoint authority and chain. With an identity manifest, that authority is bound to the keys the manifest authorizes for the genesis receipt's agent.id -- a set, not one key (Section 5.2) -- so a re-heading attacker's checkpoint over its own appended head is rejected. WITHOUT a manifest, checkpoint authentication is key-level only: any key the verifier trusts can mint a checkpoint over any head, so a co-trusted key holder can delete the most recent receipts, sign a checkpoint over the truncated head, and obtain a verified tail check over an incomplete chain. A Chain Verifier MUST surface this limitation to the relying party whenever a checkpoint authenticates without a manifest, and MUST NOT report such a result as an unqualified tail-truncation check. Even with a manifest the guarantee is opener-scoped: on a chain carrying more than one agent.id, the opener's checkpoint does not certify a co-agent's tail.

A verifier never normalizes a non-NFC string, and this profile sets no default for whether it rejects one (Section 3.3); the analysis below is written for the accepting configuration, and a verifier in the refusing configuration simply never sees the ambiguity. In the accepting configuration, because a non-NFC string is neither normalized nor rejected (Section 3.2, Section 3.3), two receipts may carry agent.id or action.id values that render identically but differ in bytes, and both verify. A verifier configured in the optional refusal mode rejects such a receipt as malformed instead -- which removes the ambiguity for that verifier and for nobody else, since the same bytes still verify elsewhere. Relying parties that match, index, or alert on these fields MUST compare them as bytes. Normalizing at their own layer before comparing is not an equivalent option: it is precisely what collapses two distinct, validly signed identifiers onto one index entry, match or alert subject, which is the collision this paragraph exists to warn about. A relying party MUST NOT assume that visual equality implies byte equality.

A trusted key can try to extend someone else's chain, and one condition stops part of it. Every receipt in a chain is individually valid; nothing in a receipt says who is entitled to add the next one. A party holding any key the verifier trusts can therefore append well-formed receipts to another party's chain -- correct prevHash, contiguous sequence, same scope.chain, same tenant -- and, where the appended receipts claim the victim's own agent.id, every condition but one stays silent. The one that fires is chain-key-discontinuity (Section 5.1): those receipts carry a different sig.kid under that same agent.id. A Chain Verifier that omits the check accepts a forged tail as history. Two escapes remain, stated here rather than left for an attacker to find: an attacker who appends under a new agent.id trips no condition at all, because this profile defines no append authority for a chain (Section 5.1, Section 1.5); and an attacker whose second key is registered under the victim's kid defeats a comparison made on identifiers rather than on resolved key material. Where the attacker instead reuses the victim's own agent.id and sig.kid, it is signing with the victim's key, which is a key-compromise problem this profile cannot address.

A receipt can be re-wrapped by anyone who holds a trusted key. The payload is self-contained and self-signed, and Section 4.2 permits the outer COSE signer to differ from the native signer. It follows that any party whose key a verifier trusts can take another party's receipt, strip its envelope, and re-emit the same payload under its own outer signature: both signatures verify, because both are genuine. Nothing in the bytes distinguishes the original emitter from a relay. The consequences are bounded but real: the relay cannot alter a field without breaking the native signature, so the content is safe; what it can do is present someone else's record as something it emitted, and -- if the verifier attributes from the outer kid -- have that attribution land on itself. What stops that is the attribution model of Section 6, and it is worth being exact about which half does the work. The manifest binds agent.id to authorized keys and is checked against the native sig.kid, so a relay cannot make itself the agent: the agent claim is carried by a signature the relay did not produce and cannot alter. The relay is not refused for relaying -- Section 6 forbids rejecting a receipt solely because its outer signer is not one of the agent's keys, because relaying is a legitimate presentation. What a verifier MUST NOT do is report the outer signer as the party that made the record. Without a manifest, attribution is key-level only and even the agent claim is unbound. Registration does not help here: a Transparency Service that follows Section 7 will happily register a relayed statement, because every signature in it is valid.

Same-party evidence is the forensic trap this profile names rather than solves. A signed outcome produced under the same administrative control as the authorization is not independent evidence, however sound its cryptography; independence is a property of the trust configuration a verifier applies, never of a field a producer writes about itself (Section 6.1). An implementation that renders UNKNOWN as "independent" has defeated the control.

Substitution across claim classes is a signature-valid attack. Both the post-execution record presented as authorization and the machine decision presented as human approval (Section 9) carry genuine signatures. An Evidence Verifier that returns one boolean cannot express "signature good, claim wrong". That is a defect in what such a result can tell its reader, not a conformance failure: Section 8.2's claim-class machinery is non-normative in this revision, so nothing here makes a one-boolean result nonconformant. A reader deciding whether to rely on such a verifier should treat the missing distinction as the gap it is.

Parsing attacker-supplied input is a cost this profile does not bound. A verifier ingests JSON and CBOR from whoever presents them, and the checks required here -- duplicate-member detection, surrogate validation, canonical re-serialization -- are all performed on that input before anything about it is trusted. This profile bounds exactly one string -- id, at 128 code points (Section 3.1) -- and sets no maximum payload size, nesting depth, member count, or chain length, and a Chain Verifier is additionally required to hold a complete chain from genesis (Section 5.1), which grows without bound. A Deployment MUST impose its own limits and MUST refuse input that exceeds them rather than attempting a partial verification; a limit that is exceeded is not a tampering finding and MUST NOT be reported as one. A deployment that omits limits has a denial-of-service surface, not a correctness problem.

An unpinned digest construction is a silent join. Comparing digests produced under different constructions can both miss a real correlation and, where one construction normalizes and another does not, map two different actions onto one value. Section 10 requires the comparison to fail closed and be reported as indeterminate.

Absence of scope.tenant is not a tenancy reset (Section 3.3); a verifier that treats it as one accepts a cross-tenant splice that carries no tampering evidence of its own.

14. Privacy Considerations

This section addresses the Producer and the Deployment (Section 1.4); verifiers inherit no obligation from it.

A receipt is designed to be shown to parties who were not present when the action ran, so what it carries is a privacy decision as much as a security one.

Raw parameters are forbidden, not prevented. This profile prohibits a Producer from placing raw prompts, tool arguments, secrets or other sensitive parameters anywhere on a receipt (Section 3), and action.paramsHash carries a digest in place of the values. What the format cannot do is enforce it: several members are unconstrained strings (Section 3.1), so a producer that writes a secret into one produces a receipt every verifier accepts. A deployment handling sensitive parameters therefore needs a producer-side control, and a relying party that needs the parameters must obtain them out of band. Whether it can then open the commitment is a matter for the producer's own contract, not for this profile: Section 3 states that the preimage construction is undefined here, and for the keyed form the key is a tenant secret. So the privacy property is one-directional -- the receipt does not disclose the parameters themselves -- and the verification property it might seem to imply does not follow from this document alone. What the receipt does disclose is set out below: a guessable commitment is an oracle, and the identifiers and timestamps are linkable by design.

A hash of a low-entropy value is not a privacy control. Where the committed parameters are guessable -- an amount, a boolean, a small identifier -- SHA-256 is a confirm-by-guess oracle rather than a shield, since anyone can hash a candidate and compare. For those cases the profile provides the keyed form hmac-sha256:<hex> under a tenant-scoped key, which removes the oracle from anyone who does not hold the key. Producers SHOULD use it whenever the parameter space is enumerable, and MUST NOT treat a plain digest of a low-entropy value as a disclosure control.

What remains linkable, by design. A receipt still carries scope.chain, agent.id, action.id, action.canonical, timestamps and, where present, governance.approval.by. These are opaque strings to this format, but they are stable across receipts, which is what makes a chain a chain: any party holding two receipts can tell they concern the same chain or the same agent, and timestamps support timing correlation. A Deployment that treats those identifiers as sensitive SHOULD choose opaque, non-descriptive values -- an internal identifier rather than a person's address -- because the format cannot enforce that and a verifier cannot undo a descriptive choice.

Registration publishes, to the extent the log retains. Submitting a Signed Statement places it and the CWT Claims in its protected header into an append-only log whose purpose is that third parties can inspect it. How much of the payload they can see is the log's choice, not this profile's. [RFC9943], Section 6.2 contemplates statements whose payload is carried as a hash rather than in full -- and that construction is not conformant with this profile as specified: Section 4.2 requires the receipt itself to be the attached payload, and Section 7 requires a Transparency Service to validate that payload as a receipt and to check sub against its scope.chain. A hash-only statement fails both. A deployment that needs to disclose less therefore cannot get it from this revision; defining a mapping -- what the hash covers, and what a service can still check -- is work for a future one. CWT Claims in COSE headers are not encrypted ([RFC9597], Section 3), so iss and sub are visible to every party that can read the log. A deployment SHOULD decide what may be published before it registers: transparency rests on an append-only sequence, which [RFC9943], Section 5.1.3 requires the log's verifiable data structure to guarantee -- it cannot be modified, deleted or reordered -- so an entry cannot be un-registered, and this document defines no redaction mechanism. What a service retains and serves beyond that history is its own policy, which this profile does not constrain.

Signer relationship reporting is a disclosure too. The trust-configuration digests that Section 6.1 requires alongside a derived relationship commit to a policy and a registry snapshot; they do not disclose their contents, and a Producer SHOULD keep it that way rather than inlining registry material into a result a relying party may forward.

15. Open questions for the working group

This section is to be removed by the RFC Editor before publication. It records what this revision deliberately did not settle, so that a reader does not mistake an open question for an oversight.

16. IANA Considerations

This section carries no normative requirement on any actor.

This document has no IANA actions in this revision, and one of those absences is load-bearing: without a registered media type for the action-receipt payload, the content type of Section 4.3 cannot be interoperable across deployments. A future revision ought to register one, and a Standards Track document that carries a wire format arguably should not ship without it -- this revision states the gap rather than papering over it. It uses the existing IANA COSE Algorithms registry value -19 (Ed25519) and the existing COSE/CWT header parameters. The vocabularies named in Section 6.1, Section 8.2, and Section 8.3 are shared spellings for reporting, not an interoperable output format -- this revision defines no result schema (Section 1.5), so two implementations using the same word still do not produce comparable output -- and they are not proposed for registration in this revision. A future revision could request registration of a media type for the action-receipt payload and/or of CWT or COSE claims for the payload fields.

17. References

17.1. Normative References

[FIPS180-4]
National Institute of Standards and Technology, "Secure Hash Standard (SHS)", FIPS PUB 180-4, , <https://csrc.nist.gov/pubs/fips/180-4/upd1/final>.
[RFC2104]
Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-Hashing for Message Authentication", RFC 2104, DOI 10.17487/RFC2104, , <https://www.rfc-editor.org/info/rfc2104>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3339]
Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, , <https://www.rfc-editor.org/info/rfc3339>.
[RFC4648]
Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, , <https://www.rfc-editor.org/info/rfc4648>.
[RFC8032]
Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, DOI 10.17487/RFC8032, , <https://www.rfc-editor.org/info/rfc8032>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8785]
Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, DOI 10.17487/RFC8785, , <https://www.rfc-editor.org/info/rfc8785>.
[RFC8949]
Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, , <https://www.rfc-editor.org/info/rfc8949>.
[RFC9052]
Schaad, J., "CBOR Object Signing and Encryption (COSE): Structures and Process", STD 96, RFC 9052, DOI 10.17487/RFC9052, , <https://www.rfc-editor.org/info/rfc9052>.
[RFC9053]
Schaad, J., "CBOR Object Signing and Encryption (COSE): Initial Algorithms", RFC 9053, DOI 10.17487/RFC9053, , <https://www.rfc-editor.org/info/rfc9053>.
[RFC9360]
Schaad, J., "CBOR Object Signing and Encryption (COSE): Header Parameters for Carrying and Referencing X.509 Certificates", RFC 9360, DOI 10.17487/RFC9360, , <https://www.rfc-editor.org/info/rfc9360>.
[RFC9597]
Looker, T. and M.B. Jones, "CBOR Web Token (CWT) Claims in COSE Headers", RFC 9597, DOI 10.17487/RFC9597, , <https://www.rfc-editor.org/info/rfc9597>.
[RFC9864]
Jones, M.B. and O. Steele, "Fully-Specified Algorithms for JSON Object Signing and Encryption (JOSE) and CBOR Object Signing and Encryption (COSE)", RFC 9864, DOI 10.17487/RFC9864, , <https://www.rfc-editor.org/info/rfc9864>.
[RFC9942]
Steele, O., Birkholz, H., Delignat-Lavaud, A., and C. Fournet, "CBOR Object Signing and Encryption (COSE) Receipts", RFC 9942, DOI 10.17487/RFC9942, , <https://www.rfc-editor.org/info/rfc9942>.
[RFC9943]
Birkholz, H., Delignat-Lavaud, A., Fournet, C., Deshpande, Y., and S. Lasker, "An Architecture for Trustworthy and Transparent Digital Supply Chains", RFC 9943, DOI 10.17487/RFC9943, , <https://www.rfc-editor.org/info/rfc9943>.
[UAX15]
The Unicode Consortium, "Unicode Standard Annex #15: Unicode Normalization Forms", , <https://www.unicode.org/reports/tr15/>.

17.2. Informative References

[ACTA]
Farley, T., "Signed Decision Receipts for Machine-to-Machine Access Control", Work in Progress, Internet-Draft, draft-farley-acta-signed-receipts-02, , <https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/>.
[AGENTROA]
Michalak, J., "Agent Route Origin Authorization (AgentROA): A Cryptographic Policy Enforcement Framework for AI Agent Actions", Work in Progress, Internet-Draft, draft-nivalto-agentroa-route-authorization-01, , <https://datatracker.ietf.org/doc/draft-nivalto-agentroa-route-authorization/>.
[AI-ART50]
Dawkins, V., "A SCITT Profile for EU AI Act Article 50 Transparency Receipts", Work in Progress, Internet-Draft, draft-dawkins-scitt-ai-article50-00, , <https://datatracker.ietf.org/doc/draft-dawkins-scitt-ai-article50/>.
[AIVS]
Stone, B., "AIVS: Agentic Integrity Verification Standard", Work in Progress, Internet-Draft, draft-stone-aivs-00, , <https://datatracker.ietf.org/doc/draft-stone-aivs/>.
[ASQAV]
Gomes Marques, J. A., "Compliance Profile of Signed Action Receipts for AI Agents", Work in Progress, Internet-Draft, draft-marques-asqav-compliance-receipts-07, , <https://datatracker.ietf.org/doc/draft-marques-asqav-compliance-receipts/>.
[I-D.hillier-scitt-arp]
Hillier, J. D., "Attestation Reconciliation Protocol", Work in Progress, Internet-Draft, draft-hillier-scitt-arp-03, , <https://datatracker.ietf.org/doc/draft-hillier-scitt-arp/>.
[I-D.schrock-action-evidence-boundary]
Schrock, I., "The Action Evidence Boundary for Consequential Agent Effects", Work in Progress, Internet-Draft, draft-schrock-action-evidence-boundary-03, , <https://datatracker.ietf.org/doc/draft-schrock-action-evidence-boundary/>.
[I-D.toraman-noa-action-digest]
Toraman, T., "The NOA Action Digest: a Domain-Separated Correlation Value for Human-Approved Agent Actions", Work in Progress, Internet-Draft, draft-toraman-noa-action-digest-01, , <https://datatracker.ietf.org/doc/draft-toraman-noa-action-digest/>.
[I-D.toraman-noa-settlement-evidence]
Toraman, T., "Settlement Evidence for Human-Approved Agent Payments", Work in Progress, Internet-Draft, draft-toraman-noa-settlement-evidence-01, , <https://datatracker.ietf.org/doc/draft-toraman-noa-settlement-evidence/>.
[OWASP-MCP]
OWASP, "OWASP MCP Top 10", Entry MCP08:2025, Lack of Audit and Telemetry, , <https://owasp.org/www-project-mcp-top-10/>.
[PSER]
Wilder, R., "A SCITT Profile for Physical-Site Engagement Receipts", Work in Progress, Internet-Draft, draft-wilder-scitt-physical-site-engage-receipt-00, , <https://datatracker.ietf.org/doc/draft-wilder-scitt-physical-site-engage-receipt/>.
[RFC7942]
Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, , <https://www.rfc-editor.org/info/rfc7942>.
[SCITT-VCP]
Kamimura, T., "A SCITT Profile for Verifiable Audit Trails in Algorithmic Trading: The VeritasChain Protocol (VCP)", Work in Progress, Internet-Draft, draft-kamimura-scitt-vcp-03, , <https://datatracker.ietf.org/doc/draft-kamimura-scitt-vcp/>.

Appendix A. Changes since -00

This section is to be removed by the RFC Editor before publication.

This revision exists because of a public review thread. What follows is that story in order: what was asked, what was built and measured in answer, and what this document now guarantees. Every difference between the posted -00 and this text is accounted for here -- a reader diffing the two should find no change this section does not explain.

A.1. Interface stability, and the two corrections a citing document must act on

Section 5 keeps its number, its title and its XML anchor (hash-chaining-and-completeness), so both numeric and fragment references from [PSER] continue to resolve, and the construction it defines -- chain.seq, prevHash, the linkage rule -- is unchanged.

Two sentences of its paragraph were wrong in -00 and are corrected in this revision. A document that adopted Section 5 by reference inherited both, so the change is stated here in the terms an implementer needs:

  • Checkpoint authority. -00 said the authority "is bound to the genesis key (the key authorized at seq == 0), so a foreign checkpoint produced by a re-heading attacker is rejected". That binding does not exist on its own: it requires an identity manifest, and it binds to the set of keys the manifest authorizes for the genesis receipt's agent.id. -01 says so, and says plainly that without a manifest the property does not hold. An implementation that relied on -00's sentence believes it rejects a re-heading attacker and does not.
  • Tail truncation. -00 said the profile "detects in-band tampering and tail truncation within a presented chain". Tail truncation is not detectable from a presented set at all -- a prover who withholds the tail presents a set with nothing missing in it. -01 says truncation is detectable only against an authenticated checkpoint. An implementation that relied on -00's sentence believes a clean verification result means nothing was deleted from the end. It does not.

Nothing else in the paragraph changed, and no receipt changes validity because of these two corrections: they describe what verification establishes, not how a receipt is built. Subsections 5.1 and 5.2 are NEW, and the principal requirements they add are:

  • detect all six chain conditions, each decided from the presented set alone (MUST), and report them distinguishably (MUST);
  • report the checkpoint axis in exactly one of its three states -- answered, unanswered, unusable (MUST) -- and never let any of them change the verdict the six conditions produced (MUST NOT);
  • require the complete chain from seq == 0, refusing a presentation that begins elsewhere (MUST);
  • distinguish a consistent extension past an older checkpoint from a head conflict (MUST), never report the former as the latter (MUST NOT), and never name a cause -- truncation, fork or signer error -- that the comparison did not establish (MUST NOT);
  • refuse to let an unauthenticated checkpoint establish a head conflict (MUST NOT), reporting checkpoint-unverified instead;
  • state whether the checkpoint behind any tail conclusion was authenticated (MUST);
  • report every condition that holds rather than the first found (MUST);
  • never report a chain-level condition as a per-receipt failure (MUST NOT);
  • never infer completeness from the absence of these conditions (MUST NOT);
  • (this one binds a Deployment, not a Chain Verifier) take temporal ordering from something other than ts (MUST), since no check constrains it;
  • on a multi-agent chain, never report an unqualified tail check (MUST NOT) and state whose view the check covers (MUST);
  • never treat a checkpoint's age as bounded by anything in the object (MUST NOT), since ts is signer-chosen and this profile defines no expiry;
  • validate a supplied checkpoint's structure before authenticating it (MUST) -- closed object, spec, the four value shapes and the sig sub-object -- and never turn a malformed checkpoint into a finding about the chain (MUST NOT);
  • compare a supplied checkpoint's chain to the presented scope.chain (MUST), report a mismatch as a finding about the checkpoint, and never let it fail an otherwise valid presented chain (MUST NOT);
  • never return a positive result for a set on which any of the six chain conditions holds (MUST NOT), with no exception for a byte-identical duplicate;
  • when an identity manifest is supplied, require a checkpoint's sig.kid to be authorized for the genesis receipt's agent.id and reject any other trusted key as UNTRUSTED (MUST);
  • when no checkpoint is supplied, report the tail question as unanswered (MUST) rather than as an absence of truncation (MUST NOT);
  • reject a presented chain in which two receipts share an agent.id and differ in sig.kid (MUST), reporting chain-key-discontinuity;
  • where it chooses to compare resolved key material as well (MAY), report that as a separate result rather than under the identifier comparison's name (MUST, conditional on the choice);
  • report an unusable checkpoint as unusable and say which reason applies -- malformed, wrong chain, or unauthenticated -- and report the tail question as unanswered in every one of those cases (MUST);
  • (Deployment) present a chain for verification from genesis (MUST), since a Chain Verifier refuses a presentation that begins elsewhere;
  • (Deployment) never assume the outer envelope's kid is the identifier an identity manifest authorizes for a checkpoint (MUST NOT) -- a checkpoint is a native artifact and the two identifier spaces are separate;
  • (Deployment) where freshness is needed, impose a maximum checkpoint age from its own clock and say so in its output (MUST), because nothing in the object bounds it.

This list is not exhaustive, and this revision no longer claims that it is. Three earlier drafts made that claim and each was wrong: the list is written by hand, 5.1 and 5.2 are long, and a hand-written inventory of a long section rots the moment either changes. It records the principal additions -- most binding a Chain Verifier, four binding a Deployment as marked, one conditional on an option a verifier need not take. None constrains a Producer or changes how a chain is built, so a profile that adopts the convention to build receipts is unaffected. A reader who needs the complete set has to read 5.1 and 5.2, which is the honest instruction.

One documentation narrowing, disclosed rather than buried. -00 said "a verifier checking a range of receipts MUST verify ..." and never said what a range may be. 5.1 now states that a conforming verifier requires the complete chain from seq == 0 and refuses a presentation that begins elsewhere. That is a narrowing of the text, not of any behaviour: all five verifiers -- the reference implementation, the Python one compared directly to it, and the Go, Rust and C# ones compared to Python -- already hardcode 0..n-1, and a presentation of seq 1,2 has always been refused as a missing genesis. So no receipt that verified under a -00 implementation fails under a -01 implementation, and no implementation changes; what changed is that the requirement is now written down where an implementer can read it, and the capability nobody has -- verifying a mid-chain range against a trusted anchor -- is named as absent in Section 12 instead of being implied by silence.

The same care applies to the gap condition itself: a correctly linked chain presenting 0 then 2 was underspecified in -00 and is a chain-sequence-gap failure under 5.1. Any implementation of -00 already rejected it, so this is again documentation catching up to behaviour rather than a change of outcome -- but an implementer who read -00 literally and built a permissive verifier would find their verifier non-conforming under -01, and that is worth saying plainly.

What happened to every -00 section, checked against the posted text rather than remembered. Sections 1 through 7 keep their -00 numbers. The rest moved, and one was removed:

Table 8
In -00 In -01
1-7 unchanged numbers
8 -- Relationship to ACTA signed receipts (equivalence), with its mapping table removed. It changed no requirement, and a field-by-field mapping to one other individual draft did not earn a section here
9 -- Policy-replay out of scope 11
10 -- Security Considerations 13
11 -- IANA Considerations 16
Appendix A -- Implementation status 12, promoted into the body where RFC 7942 places it
Appendices B and C -- the two reference lists 17.1 and 17.2
-- 8, 9 and 10 are new (external-world non-claims, authorization vs evidence, the digest boundary)
-- 14, Privacy Considerations, is new, and -00 had no privacy section at all
-- 15, Open questions for the working group, is new
-- Appendix A (this change log) and Appendix B (acknowledgments) are new

The record format itself is untouched: no member of noa.receipt/0.1 is added, removed, or re-typed.

A.2. What the author committed to on 23 July, item by item

The author's reply to the thread (https://mailarchive.ietf.org/arch/msg/scitt/7QfK2KTD-WdRrPMnfZlwnDC3_NA/) listed ten things -01 would do. Eight are done in this text; two -- items 3 and 4 -- are partly done, and the table says which part of each is outstanding.

Table 9
Committed Where it landed
1. Separate authorization, controller-reported outcome and physical completion Section 8.1
2. Define trust semantics and failure behaviour for each claim independently Section 8.2, Section 8.3
3. Make signer relationships machine-readable when signed outcome evidence is present Section 6.1; derived and machine-readable in the reconciler (Section 12), not yet surfaced in bundle verdicts, and never derivable as organizational independence today
4. Add purpose-specific negative conformance vectors The attribution-substitution vector landed at df777a6 (corpus 33 -> 34); the rest are named and committed in Section 12 and not yet written -- stated as open, not as done
5. Define the shared action-digest construction rather than assume equivalence with action.paramsHash Section 10, construction published separately [I-D.toraman-noa-action-digest]
6. Clarify the relation between the native receipt signature and the outer COSE envelope Section 4.2
7. Resolve the policy-identity inconsistency in the base payload Section 3.3
8. Say clearly that a self-signed chain cannot prove an unseen tail does not exist Section 5, Section 5.1 and Section 13
9. Replace "non-repudiable" with the narrower wording Abstract and Section 1 now say issuer-authenticated / signature-verifiable
10. Update the stale algorithm-migration note Section 12: -19 is emitted, -8 rejected under a genuine signature, note corrected rather than carried forward -- and the stale byte string an earlier revision quoted is corrected in the same place

A.3. Corrections this revision makes to -00's own text

  • The protected-header key-identifier rule CHANGED, and this is a change rather than a correction. -00 required the protected header to contain "a key identifier sufficient to resolve the verification key: kid (label 4), or a certificate reference (x5t label 34 / x5chain label 33) where a PKI is used" -- a disjunction, not a contradiction. -01 makes a resolvable kid mandatory and demotes the certificate references to additional-only, because a verifier conforming to this profile is required to resolve kid and is not required to process certificates. A producer that satisfied -00 by carrying x5t alone is not conformant to -01. Section 4 now says kid SHOULD be protected, MAY be unprotected, MUST be resolvable from either with the protected copy preferred, and that a verifier MUST report which bucket it used -- because attribution from an unprotected identifier is not covered by the signature, so an unprotected kid may resolve a key but never names an identity (Section 6). It also states that additional registered headers MUST NOT cause rejection unless listed in crit.
  • Terminology defined a policyHash the payload never carried. Section 3.3 resolves it in favour of what the wire format does: governance.ruleId is a label, the optional governance.compliance member is where a policy commitment lives, and neither makes a receipt replayable.
  • scope.tenant was shown as always present. It is optional, and Section 3.3 states the rule that matters: absence MUST NOT reset a tenancy comparison, or an optional field becomes a splice channel.
  • -00 said the commitment fields were not carried at all, in the section that is now Section 11. They may be; what remains unspecified is the replay construction. The prohibition on presenting a receipt as carrying a re-derivable verdict is unchanged.
  • The implementation-status byte string was wrong. An earlier revision quoted a10132 as the protected header this implementation emits. It is not: that encodes {1: -19} alone, and the emitted header is the two-member map {1: -19, 4: kid} -- the kid having been moved into the signed bucket, which the same document's own Section 4 says. The identifier claim was right and the byte-level claim was not; Section 12 now states the map, and the paired a10127 claim about the deprecated identifier is dropped rather than repeated. It was found by an adversarial pass that decoded a shipped vector -- one command, which is exactly how a reviewer would have found it.
  • The profile called its objects SCITT Signed Statements and called them registrable. They are COSE_Sign1 objects; a Signed Statement additionally needs protected CWT Claims that a Transparency Service can apply a Registration Policy to, and the reference implementation emits none. Section 4.3 is new and specifies the mapping normatively; Section 4 and Section 7 no longer assert registrability in the present tense; and the gap heads the not-implemented register in Section 12. This was the round-1 review's categorical objection and it was right.
  • The payload figure omitted sig. -00's figure ended at chain, which made its own "JCS(receipt WITHOUT chain.hash AND WITHOUT sig.value)" construction impossible to implement from the figure. sig (with alg, kid, value) is now shown as the mandatory member the schema has always required.
  • The optional-member contract listed two members; the schema has seven. agent.model, action.rollbackRef, governance.ruleId, governance.approval and compliance.verdict join scope.tenant and governance.compliance, and the figure marks each. The rule that present-as-null is not absence -- different bytes, different hash -- is stated with them.
  • Detached payloads were described backwards. -00 said a detached payload's digest is bound through chain.hash. COSE requires the detached bytes themselves for the Sig_structure, and chain.hash is a chain-linkage value computed over the receipt with chain.hash and sig.value removed. The shipped parser refuses a non-byte-string payload. Section 4.2 now defines the attached form only, and a detached mode is future work.
  • The algorithm rule contradicted itself. -00 said verifiers MUST reject every other alg and then that they SHOULD reject -8 unless a legacy mode was negotiated. -8 is not a special case, no such mode exists in any implementation, and the exception is deleted.
  • Certificate references were offered as a sufficient key identifier. They are now additional and OPTIONAL, with kid required, matching a verifier that ignores x5t/x5chain.
  • The signer-relationship comparator named the wrong pair. The relationship that decides independence is outcome signer versus request/authorization signer -- what both reviewers asked for, and what this document's own security prose already said. An earlier draft compared against the receipt's issuer, which would report independence for an outcome signed by the requester.
  • The escaping row was wrong and the normalization row was too absolute. JCS also escapes " and \; and a verifier MAY refuse a non-NFC receipt outright, which the shipped verifier offers as a mode. What no verifier may do -- normalize before hashing -- is unchanged and is the rule that matters.
  • Timestamp validation was underspecified, and -01 makes the strict reading normative. -00 required "RFC 3339" form without saying what a verifier must do with a string that matches the shape but is not a valid date-time. Section 3.3 now requires rejection and pins the accepted grammar; Section 12 records that the shipped validator stops at the shape and why it will stay there. An earlier -01 draft carried the opposite sentence here, which contradicted three other surfaces; this is the corrected one.
  • -00's NFC rule was rewritten, and that rewrite is itself a change. -00 said flatly that all strings MUST be Unicode NFC, which reads as a validity condition on receipts. -01 splits it into a producer obligation and a verifier prohibition on normalizing, plus an optional refusal mode. A receipt that -00 called malformed may verify under -01 in the default mode, and vice versa under the refusal mode; implementers moving between revisions should read Section 3.2 and Section 3.3 together.
  • Nine -00 fragment anchors were broken and are restored. A shortened anchor scheme in an earlier -01 draft would have broken deep links into introduction, relationship-to-other-work, identity-binding, policy-replay-is-out-of-scope-for-this-revision, security-considerations, implementation-status, hash-chaining-and-completeness, and the two reference sections. Every one of the -00 anchors is restored in the published XML, so both numeric and fragment references from -00 continue to resolve.
  • Six references were reclassified or added. RFC 9053 (the deprecated identifier named in a MUST, and the curve registry), RFC 3339, RFC 2104, RFC 9360 and Unicode Annex #15 are normative where the text depends on them; FIPS 180-4 is cited for SHA-256.
  • Requirements that existed only as code are now text. Two reverse audits -- code read first, then the requirement it should have mapped to -- found rejection boundaries that every shipped verifier enforces and that no revision of this document required. They are requirements here, transcribed rather than invented: the 128-code-point bound on id and the seven non-empty members, and the closed ALLOW/DENY set for governance.compliance.verdict (Section 3.1); the three JSON member names refused at the parse boundary (Section 3.2); the exact sig.alg literal, the Ed25519 key-type rule, the canonical key encoding and the two public-point rejections (Section 4.2); the UTF-8 round trip before a kid is resolved (Section 4.1); and the checkpoint's closed object, value shapes and chain equality (Section 5.2). One of them is not stated by one implementation, which Section 12 records. The general rule of Section 8.3 -- that a verifier's own failure to authenticate or understand an input MUST NOT become a finding against the subject of the verification -- is new as a rule; the two requirements it generalizes were already in Section 5.1 and Section 5.2.
  • Conformance classes are new, and they change how every MUST is read. -00 and earlier -01 drafts said "a verifier" and meant at least three different things, which made some requirements unsatisfiable by a single-receipt implementation and others meaningless to a chain one. Section 1.4 defines Receipt, Chain and Evidence Verifier; each normative section states its class; and no class inherits another's obligations. Two implementers can now build to the same class and know they were asked for the same checks -- which is not the same as being able to interoperate from this document alone. For the Receipt and Chain classes the inputs are defined here and agreement is testable on shared vectors. For the Evidence class it is not: Section 1.5 lists the artifacts that layer runs over and this revision defines none of their wire formats, so two Evidence Verifiers cannot be given common inputs or compared on their outputs by reading this text (Section 15).
  • What this profile does not define is now a section rather than a scatter. Section 1.5 lists the objects behind the Evidence-Verifier requirements -- identity manifest, approval and outcome artifacts, trust configuration, shared digest, result schema -- and says plainly that their formats live in the companions and the reference implementation. An unimplementable MUST is worse than none; the requirements that remain are the ones a reader can act on.
  • The non-equivocation claim is narrowed to what registration actually buys. The Abstract, Section 7 and Section 13 previously asserted that registration supplies non-equivocation. It supplies a third party's signed proof of registration. Non-equivocation needs consistency proofs and monitoring, which this profile does not specify and places no requirement on anyone to perform -- and a Transparency Receipt handed to an offline party by the prover is prover-supplied evidence, exactly as Section 5.1 says of a checkpoint. The document's own argument now applies to its own escape hatch.
  • Registration binds more than the envelope. Section 7 now requires a Transparency Service to verify the payload's own schema and native signature, and to check sub against scope.chain. Without the first, a trusted registrant can obtain a log entry for a receipt nobody in the chain signed; without the second, a chain-A receipt can be registered under chain-B.
  • Two further chain-level conditions are named. The cross-tenant splice rule of Section 3.3 is a set property the axis table did not carry; it is now chain-tenant-split. And every shipped verifier already rejects a mid-chain key change for one agent while no revision had ever named it: that is chain-key-discontinuity, the condition that stops a trusted-but-unrelated key from extending another party's chain. The set of chain conditions is now six, and the two checkpoint-derived results that earlier drafts of this revision counted among them are on a separate axis: a checkpoint is a separate object answering a separate question, and nothing on that axis can fail a chain (Section 5.1).
  • An older checkpoint over a longer chain is no longer truncation. Section 5.1 distinguishes a consistent extension from a truncation, and Section 12 records that the reference implementation reports one verdict for both.
  • The re-canonicalization requirement was impossible as written -- it demanded that a serialization omitting two members equal the bytes those members are in. Section 4.2 now states the check the implementation actually performs: JCS(parse(bytes)) == bytes over the whole payload.
  • Field semantics are pinned (Section 3.1): the notation legend, spec exactness, the absence of identifier namespaces, what ts means, and the cross-field rules that stop one record saying two things -- including the simulation-marker rule, which -00 left open.
  • Timestamps have a grammar (Section 3.3): which spellings are accepted, why the spelling is signed, and why a verifier must not normalize one.
  • The relay attack is in the threat model (Section 13). The two-signature design permits any holder of a trusted key to re-wrap someone else's receipt; that was the one attack the design directly enables and the one the section omitted.
  • Parser resource bounds are named as the implementer's responsibility, since this profile sets none and requires a complete chain from genesis.
  • Two RFC 9943 section pinpoints were wrong and are corrected. An earlier -01 draft cited "Section 6.12" and "Section 6.13" for the kid and CWT Claims requirements. RFC 9943 has no such subsections -- its Section 6 runs 6, 6.1, 6.2, 6.3 -- and both requirements live in the body of Section 6. The citations now say Section 6. The error entered through a summarized reading of the RFC rather than its text; every external section pinpoint in this revision has since been checked against the RFC's own published text.
  • The deterministic-CBOR pin was too coarse. "Section 4.2" named the whole Deterministically Encoded CBOR section; the requirement this profile depends on is the Core Deterministic Encoding Requirements of Section 4.2.1, which is now what it cites.
  • A quoted claim about another protocol's specification was removed. The Introduction quoted a sentence about non-repudiation attributed to an agent protocol, with no primary citation to support it. The surrounding point does not need it, and the paragraph now states the gap in neutral technical terms. The OWASP entry is cited to its own project page, listing MCP08:2025 by its published title; the severity rating an earlier draft asserted is not on that page and is gone.
  • The intended status CHANGED, and it is a change rather than an editorial detail. The posted -00 carried "Intended status: Informational"; this revision carries "Intended status: Standards Track". The document now defines wire-format acceptance rules that two implementations must agree on, which is why the header moved -- and the move is not settled: Section 15 states the case for Experimental instead, and the header records the current intention while the working group decides, not a conclusion the author reached alone.
  • The SCITT Architecture reference was a work-in-progress draft. It was published as [RFC9943] in June 2026 and is cited as an RFC.
  • The equivalence section is gone, not edited. -00 Section 8 mapped this profile's fields onto another individual draft's field catalogue. It changed no requirement in either document, so it is removed rather than carried forward; Section 1.2 still names that work and says a mapping is available to whoever needs to port one.

Appendix B. Acknowledgments

This revision is the product of a public review thread on the SCITT mailing list in July 2026, archived at https://mailarchive.ietf.org/arch/browse/scitt/?q=noa-scitt-ai-agent-receipt. The reviewers named here disagreed usefully, contributed concrete text, and in one case ran their own implementation against this profile's adversarial corpus rather than arguing about it. Errors that remain are the author's.

Author's Address

Tora Toraman
NordenSoft
Denmark