| Internet-Draft | NOA Action Digest | August 2026 |
| Toraman | Expires 15 February 2027 | [Page] |
This document defines a domain-separated correlation value, the NOA Action Digest, that binds a verified human authorization for an agent action to the artifacts and external events associated with that action's execution attempt. The digest is a fixed-width value derived from an approved action's authorization record, its tenancy and chain identifiers, its parameter commitment, its execution grant, and a single-use nonce. It is designed to be embedded in external systems that carry an opaque, caller-chosen identifier, so that a third party can correlate an external event with a specific prior authorization. The digest establishes correlation only. Equality of digests is not evidence that any statement made by any party about the action is true, and is not evidence that any action was executed or any effect occurred.¶
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 15 February 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Autonomous software agents increasingly take actions with external consequences. Systems that require a human to approve such actions produce a record of the approval, but that record and the action's later effects are usually correlated only by trusting an intermediary's bookkeeping.¶
This document defines a value whose derivation is fixed, public, and reproducible from the authorization record itself, so that correlation does not depend on any intermediary's assertion. The value is designed for the common pattern in which an external system carries a caller-chosen opaque field that it commits to but does not interpret.¶
This document deliberately does not define an execution binding. Correlating an authorization with an external event is strictly weaker than establishing that the approved action was performed. Section 7 states the boundary.¶
Non-goal: this document does not define, and its authors do not claim, any mechanism that prevents a sufficiently capable adversary from causing harm. It defines a value that makes certain classes of substitution detectable after the fact.¶
This document is written under a discipline its authors call the Copenhagen model: a verifier never states what happened; it states what the supplied evidence supports, and it records, in the same breath and on every surface, what it does not claim. Every section below that names a control also names the residual the control leaves behind. Where a control does not exist, this document says so rather than describing an intention as a property. Section 7 is written to be attacked, and Section 7.2 is deliberately an open list rather than a taxonomy.¶
The name is a nod to a remark about physics — that the subject concerns what we can say about nature — attributed to Niels Bohr, secondhand, via Aage Petersen's 1963 recollection; it does not appear in Bohr's own writings, and this document claims no more for the attribution than that. The authors' own sentence for the same idea carries no attribution at all: we do not tell you what happened; we show you what the evidence proves.¶
This subsection is non-normative and is to be removed before publication as an RFC.¶
Two conditions govern the release of this document, and they are stated here so that a reader holding a revision can check them mechanically:¶
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.¶
noa.receipt/0.1 whose governance
verdict is ALLOWED. The record names the approving principal in a field; it is signed by the
issuing gate's key, which is not the same thing. The signature establishes that the gate issued
and has not since altered the record — not that the named human held any key, and not who
physically approved. Which identity a deployment binds to the approval, and how, is outside this
document; see Section 7.1.¶
action.paramsHash.¶
The Authorization Record format assumed by this document is frozen and MUST NOT be modified to carry an Action Digest. Implementations MUST NOT add a member to the Authorization Record at any depth. The Action Digest is carried in separate, referencing artifacts.¶
Implementation note, non-normative: in the reference implementation the record schema sets
additionalProperties:false at the root and on every nested object, and lists all root members as
required (schema/noa-receipt-0.1.schema.json), so a new member is rejected twice. Adding one
would change canonical bytes, record hashes, signatures, chain links, checkpoints and every
published conformance vector.¶
The construction is fixed and is reproduced here in full; it is a SHA-256 [FIPS180-4] value over
RFC 8785 [RFC8785] canonical JSON. The reference corpus that enforces it is the Copenhagen
vectors — the cross-language conformance corpus published with the reference implementation, at
conformance/action-digest/vectors.json. See Appendix A for the exact anchor and the
measurement date.¶
projection = a JSON object with EXACTLY these ten string members:
spec, authorizationReceiptHash, tenant, chain, actionId, actionCanonical,
actionParamsHash, executionGrantId, executionGrantHash, executionNonce
JCS = RFC 8785 canonical JSON of the projection
PREIMAGE = UTF8("NOA-ActionDigest-v0.1-dig:") || SHA-256(JCS) ; 26 + 32 = 58 bytes
DIGEST = "sha256:" || lowercase-hex(SHA-256(PREIMAGE))
wire form: { "spec": "noa.action-digest/0.1", "digest": "sha256:<64 lowercase hex>" }
¶
Four properties of the construction that a reviewer will ask about, answered rather than deferred:¶
-dig, not -sig. This value is hashed and never signed, so it
is kept out of the signing namespace: a -sig tag here would create a signing pre-image some
other verifier might one day be persuaded to accept. (Consequence for Section 8: a registry named
for signing domains does not fit a hashing tag.)¶
actionId and actionCanonical from colliding (vector
reject-action-fields-transposed).¶
authorizationReceiptHash is the receipt-reference rule, executionGrantHash the
side-artifact rule.¶
The digest input MUST commit to at least the following:¶
P4 is not verifiable from the value. Entropy is a property of the generator, not of 32 octets: no relying party and no conformance vector can distinguish a CSPRNG digest from a counter by inspection. P4 is therefore stated as a producer obligation with a disclosed residual, and a conforming implementation MUST document the nonce source. Section 7.4 carries the residual. See Appendix A for the reference implementation's measured nonce source. - P5 — Ordering. The digest MUST be computable before the execution attempt is dispatched. Any construction requiring an input that exists only after dispatch is out of scope, because a value that cannot be committed before dispatch cannot correlate a pre-committed authorization with a post-dispatch event. This is enforced by construction — every one of the projection's ten members exists at approval time — not by a conformance vector, and is stated here as such. - P6 — Not the parameter commitment. The Action Digest MUST NOT be the Parameter Commitment, and no document may name the Parameter Commitment as the action digest. Rationale: a parameter commitment does not bind the complete authorization, tenant and chain, the exact attempt, the grant, or the nonce, and it may repeat across retries. This is a naming prohibition on other documents; no conformance vector can enforce it, and it is stated here as a residual, not a control. - P7 — Uniqueness across retries. Two distinct execution attempts of the same action MUST yield distinct digests. This follows from item 8 of Section 4.1 and is stated separately because it is the property retries break.¶
| Item | Status |
|---|---|
| exact member names and their order | RESOLVED — Section 4, ten members, order irrelevant under JCS |
| encoding of the digest input (JCS vs CBOR) | RESOLVED — JCS [RFC8785] |
| the domain-tag string |
RESOLVED — NOA-ActionDigest-v0.1-dig
|
| whether the digest object is itself signed, or purely derived |
RESOLVED — purely derived, {spec, digest}, no sig
|
| test vectors |
RESOLVED — the Copenhagen vectors, conformance/action-digest/vectors.json
|
| whether a rail-binding component (chain id, contract, payer) is inside the digest or carried separately |
RESOLVED — the projection is frozen at ten members; it carries a chain member (a tenancy/record-chain identifier) but no ledger/rail chain id, no token contract and no payer. A rail binding is therefore carried outside this digest, and in the profile the reference implementation ships it is carried twice: by the mandate's Parameter Commitment, and as inputs to the derivation that consumes this digest and produces the value the rail actually carries (mode (b) of Section 5). See [I-D.toraman-noa-settlement-evidence]. |
| the nonce's entropy source | RESOLVED as a producer obligation — see P4 in Section 4.2 and Appendix A. Entropy is unverifiable from the value, so this closes as a documented obligation with a disclosed residual, never as a vector-enforced control. |
An Action Digest MAY reach an external system in either of two modes, and the difference decides what becomes public:¶
Both modes are subject to:¶
authorization_details
entry [RFC9396] transports it through the user agent ([RFC9396], Section 12), so under that
projection the digest is public at authorization-request time, to every party in the browser
path, before the payment is signed — and it is public there in mode (b) too, because that
projection carries the digest itself rather than a derived value. A profile that puts either
value in a user-agent-visible channel MUST say so. Like E1 and E2, E3 is an obligation on the
embedding party and its deployment, not a property any conformance vector can check — stated at
the point of use as a residual.¶
A Relying Party verifying a correlation claim MUST:¶
tenant and chain members take the
values those documents carry;¶
tenant and chain against the Relying
Party's own expected values, and fail on any difference. This is a distinct step from step 3
and MUST NOT be folded into it: substituting the expected values into the recomputation
would make the digest agree by construction and destroy the check. The two steps answer two
different questions — step 3 asks "what do these documents say?", step 4 asks "are these
documents mine?" — and only the second is a defence against a genuine, correctly-signed bundle
belonging to another tenant or another chain;¶
Steps 1 and 2 are not optional and not reorderable, and the reference implementation pins that
as a permanent test rather than asserting it: the digest construction verifies no signature, so
a fully forged pair — an attacker's key claiming the victim's kid, a record asserting an approved
transfer, a grant signed with 64 zero bytes — is ACCEPTED by step 3 and REFUSED by the
record's own chain verification against an honest keyring. A green step 3 over unverified
documents establishes nothing.¶
In-realm recomputation is not a trust boundary. A verifier that recomputes inside a runtime an adversary can influence has moved the trust anchor, not removed it.¶
A Relying Party MUST NOT conclude from a successful comparison that the action was performed, that any effect occurred, or that any statement about the action made by any party is true. See Section 7.1.¶
This section is written to be attacked. Where a control does not exist, this document says so rather than describing an intention as a property.¶
Digest equality is linkage and correlation only. It is not proof that a controller claim, a physical claim, a delivery claim, or any other statement about the action is true. It is not an execution binding.¶
A system built on this document establishes that a key the Relying Party's own keyring trusts signed an Authorization Record carrying this parameter commitment, and that the Execution Grant's own signature is authentic; it does not establish that that intent is what executed.¶
Say exactly what the authenticating entry point checks, because a partial check described as a
whole one is worse than none. The reference entry point (verifyActionDigest) verifies the
record chain and the Execution Grant's Ed25519 signature against a keyring the Relying Party holds
independently. It does not check the grant's authorization semantics: the signer's type and
role, the signing key's activation window, the grant's expiry, and the envelope reference chain are
not evaluated here — they belong to the full artifact verifier (verifyArtifact in the
reference implementation), which this entry point has no dependency edge to. A caller enforcing an
authorization decision MUST run that full verifier as well. Consequently this entry point alone
does not establish that the key was authorized at the time of the action — it checks
signature authenticity, never key activation or grant expiry. It also does not establish which
human held the key; the approval's subject field is an opaque identifier, not an identity; and a
device that signs without opening produces evidence indistinguishable from a device that opened,
read and approved.¶
A digest does not reduce the following. This is an OPEN list of the largest known classes, never an exhaustive attack surface; at least seven further classes are omitted or collapsed by it, including availability and denial of service, replay and rollback, verifier defects, revocation and recovery, privacy leakage, cross-tenant isolation and supply-chain compromise — four of which are live in this document and its companion. Each entry below is named permanently, on every surface where this mechanism is described, because each leaves the correlation intact while defeating a guarantee a reader might infer from it.¶
Note also that these anchors do not all leave third-party-verifiable evidence: infrastructure-root compromise can alter or suppress evidence, stolen keys produce apparently-valid evidence, and an ungated action produces no evidence at all — including no record of its absence.¶
Activity outside enforced gateways is outside coverage. This is a scope statement, not a mitigation, and it is stated with no justifying clause. Model sandbox escapes fall inside it.¶
Under mode (a) of Section 5 the digest becomes a permanent public correlator. Under mode (b) it does not — but the analysis does not simply improve, it moves, and both halves have to be stated or the improvement is oversold.¶
[PLACEHOLDER — pending working group direction]. The provisional intent:¶
application/noa-action-digest+json, and — if a COSE profile ([RFC9052], [RFC9053]) of this
artifact is adopted — application/noa-action-digest+cose.¶
authorization_details parameter and
related metadata, but type values are not IANA-registered: Section 2.1 only RECOMMENDS
that type identifiers be collision-resistant, and Section 14 creates no type registry — so
there is nothing to register for an authorization_details projection of this digest.¶
[[RFC EDITOR: please remove this section before publication.]]¶
Changes since -00: author metadata completed (country, explicit submission date). No technical changes; every normative statement, conformance count and reference is byte-identical to -00.¶
This section is to be removed before publication as an RFC, per [RFC7942].¶
A zero-runtime-dependency reference implementation, together with the Copenhagen vectors — the cross-language conformance corpus this document's construction is enforced by — is available under the Apache-2.0 license at https://github.com/NordenSoft/noa.¶
The following were measured, in that repository, on 2026-08-14, at main commit 7aa8e37:¶
docs/action-digest-spec.md; implementation
src/action-digest.ts.¶
conformance/action-digest/vectors.json — 33 cases,
1 ACCEPT and 32 REJECT, generated from fixed seeded keys, each rejection pinning the substring of
the refusal reason it measures. Named cases include cross-tenant replay, cross-chain replay, retry
non-collision, field transposition, wrong-domain-tag, no-domain-tag, and a
/0.2-spelled-as-/0.1 refusal.¶
scripts/lint-control-knockout.mjs) and are proven to turn a named vector red when the control
is removed — the ALLOWED-verdict requirement, the blank-scope-identifier refusal, chain
authentication, grant-signature verification, the closed sig object, authorization selection by
grant binding, the expected-tenant/chain check, and domain separation. Not all 32 rejections are
knockout-gated, and the gap is structural rather than an omission: the registry records that the
projection's actionId, actionCanonical, actionParamsHash, executionGrantId and
executionNonce members cannot be isolated, because authorizationReceiptHash and
executionGrantHash are hashes over the whole receipt and the whole grant — any mutation that
moves one of those five moves a whole-document hash too, so the attack is refused either way and a
knockout that "passed" for that reason would be vacuous.¶
nonce. The grant schema pins
it to ^[0-9a-f]{64}$ (packages/approval-artifacts/schema/noa-execution-grant-0.1.schema.json)
and the gate generates it as 32 CSPRNG bytes rendered as 64 lowercase hex
(packages/gate/src/trust.ts), refusing loudly rather than minting a nonce that fails that
pattern. That is 256 bits against P4's 128-bit floor. This is a producer property, not a
verifiable one: a conforming verifier cannot distinguish this generator from a counter, which
is why P4 is written as an obligation with a disclosed residual.¶
packages/rail-x402/src/correlation-nonce.mjs performs the derivation
and packages/rail-x402/src/settlement-evidence.mjs recomputes it during verification. The
Execution Grant nonce measured above is that derivation's seed, so the one measurement carries
both of the loads P4 names.¶
NON-CLAIMS.md — the execution-grant signing key is held as a string in the process
the grants are meant to defend. No claim of unforgeable authority is made for it until that key
leaves the process and the signer validates approval proof per signature.¶