N-AALP: The Native Agentic Application Layer Protocol
draft-bubblefish-naalp-01
This document is an Internet-Draft (I-D).
Anyone may submit an I-D to the IETF.
This I-D is not endorsed by the IETF and has no formal standing in the
IETF standards process.
| Document | Type | Active Internet-Draft (individual) | |
|---|---|---|---|
| Author | Shawn Edward Sammartano | ||
| Last updated | 2026-09-06 | ||
| RFC stream | (None) | ||
| Intended RFC status | (None) | ||
| Formats | |||
| Stream | Stream state | (No stream defined) | |
| Consensus boilerplate | Unknown | ||
| RFC Editor Note | (None) | ||
| IESG | IESG state | I-D Exists | |
| Telechat date | (None) | ||
| Responsible AD | (None) | ||
| Send notices to | (None) |
draft-bubblefish-naalp-01
Network Working Group S. Sammartano
Internet-Draft BubbleFish Technologies, Inc.
Intended status: Informational 6 September 2026
Expires: 10 March 2027
N-AALP: The Native Agentic Application Layer Protocol
draft-bubblefish-naalp-01
Abstract
The Native Agentic Application Layer Protocol (N-AALP) is an
application-layer object protocol for autonomous software agents.
Every N-AALP object is a deterministically encoded CBOR structure
signed with COSE, carrying under one signature its content identity,
its originating signer, a closed effect label that is an
authorization input rather than a hint, optional approval and audit
bindings, and its causal derivation. Objects are transport-
independent: the identical signed object is carried, with identical
object-level guarantees, over the N-PAMP substrate, QUIC, WebSocket,
or HTTP. N-AALP defines a frozen envelope, a post-quantum signature
profile (pure ML-DSA by default, with an optional Ed25519+ML-DSA
composite), a self-certifying identity with key rotation, a single-
use approval ledger, a hash-chained audit and causal- ordering model
with a federated higher tier, native streaming with a single per-
stream commitment, foreign-protocol carriage by class, and twenty
tiered channel surfaces. This document is an Independent Submission
and does not represent IETF consensus.
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 10 March 2027.
Sammartano Expires 10 March 2027 [Page 1]
Internet-Draft N-AALP September 2026
Copyright Notice
Copyright (c) 2026 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents (https://trustee.ietf.org/
license-info) in effect on the date of publication of this document.
Please review these documents carefully, as they describe your rights
and restrictions with respect to this document.
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1. Scope . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2. Non-Goals . . . . . . . . . . . . . . . . . . . . . . . . 4
2. Conventions and Terminology . . . . . . . . . . . . . . . . . 4
3. Architecture . . . . . . . . . . . . . . . . . . . . . . . . 5
4. Object Model . . . . . . . . . . . . . . . . . . . . . . . . 5
4.1. Deterministic encoding . . . . . . . . . . . . . . . . . 6
4.2. The object body . . . . . . . . . . . . . . . . . . . . . 6
4.3. Content identity . . . . . . . . . . . . . . . . . . . . 7
4.3.1. Multihash length-octet worked example . . . . . . . . 7
4.4. Extensions and versioning . . . . . . . . . . . . . . . . 8
5. Cryptographic Constructions . . . . . . . . . . . . . . . . . 9
5.1. Signing . . . . . . . . . . . . . . . . . . . . . . . . . 9
5.2. Algorithms and profiles . . . . . . . . . . . . . . . . . 10
6. Identity . . . . . . . . . . . . . . . . . . . . . . . . . . 12
7. Effects and Authorization . . . . . . . . . . . . . . . . . . 12
8. Approval . . . . . . . . . . . . . . . . . . . . . . . . . . 13
9. Audit, Causal Graph, and Ordering . . . . . . . . . . . . . . 15
10. Delivery . . . . . . . . . . . . . . . . . . . . . . . . . . 16
11. Streaming . . . . . . . . . . . . . . . . . . . . . . . . . . 17
12. Object State Machines . . . . . . . . . . . . . . . . . . . . 17
12.1. Delivery state machine . . . . . . . . . . . . . . . . . 18
12.2. Stream state machine . . . . . . . . . . . . . . . . . . 19
12.3. Approval state machine . . . . . . . . . . . . . . . . . 21
12.4. Reconcile state machine . . . . . . . . . . . . . . . . 22
12.5. Timers . . . . . . . . . . . . . . . . . . . . . . . . . 23
12.6. Clock skew and validity windows . . . . . . . . . . . . 24
12.7. Cancel . . . . . . . . . . . . . . . . . . . . . . . . . 24
12.8. Deadline . . . . . . . . . . . . . . . . . . . . . . . . 25
13. Transport Bindings . . . . . . . . . . . . . . . . . . . . . 25
14. Foreign Carriage by Class . . . . . . . . . . . . . . . . . . 25
15. Channel Surfaces . . . . . . . . . . . . . . . . . . . . . . 26
16. Additive Object Families . . . . . . . . . . . . . . . . . . 26
16.1. Collaboration and Rooms Membership . . . . . . . . . . . 27
16.2. Multi-Hop Delegation Grant . . . . . . . . . . . . . . . 28
Sammartano Expires 10 March 2027 [Page 2]
Internet-Draft N-AALP September 2026
16.3. MCP Tool-Call Binding . . . . . . . . . . . . . . . . . 29
16.4. Description and Directory . . . . . . . . . . . . . . . 30
16.5. Name Bindings and A2A Task Transitions . . . . . . . . . 30
16.6. Governed Negotiation . . . . . . . . . . . . . . . . . . 31
16.7. Advisory Risk Labels . . . . . . . . . . . . . . . . . . 32
16.8. Flow Continuations . . . . . . . . . . . . . . . . . . . 32
16.9. Governed-Decision Records and Transparency Log
Primitives . . . . . . . . . . . . . . . . . . . . . . 33
16.10. Portable Egress Evidence . . . . . . . . . . . . . . . . 35
16.11. Manufacturing Physical-Hazard Claims and
Authorizations . . . . . . . . . . . . . . . . . . . . 35
17. Security Considerations . . . . . . . . . . . . . . . . . . . 36
17.1. Trust-decision closure sovereignty . . . . . . . . . . . 44
18. Privacy Considerations . . . . . . . . . . . . . . . . . . . 45
19. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 46
19.1. Media type application/vnd.bubblefish.naalp+cbor . . . . 46
19.2. N-AALP Channel registry . . . . . . . . . . . . . . . . 47
19.3. N-AALP Object Kind registries . . . . . . . . . . . . . 47
19.4. N-AALP Effect registry . . . . . . . . . . . . . . . . . 51
19.5. N-AALP Carriage Protocol Id registry . . . . . . . . . . 51
19.6. N-AALP Error Code registry . . . . . . . . . . . . . . . 51
19.7. N-AALP Extension Key registry . . . . . . . . . . . . . 57
19.8. N-AALP Carriage Content Type registry . . . . . . . . . 58
19.9. N-AALP Trust-Decision Input Class registry . . . . . . . 58
19.10. COSE algorithms . . . . . . . . . . . . . . . . . . . . 59
19.11. Registration criteria for the RFC-Required registries . 59
20. Implementation Status . . . . . . . . . . . . . . . . . . . . 60
21. Conformance . . . . . . . . . . . . . . . . . . . . . . . . . 60
22. Specification License . . . . . . . . . . . . . . . . . . . . 61
23. Normative References . . . . . . . . . . . . . . . . . . . . 61
24. Informative References . . . . . . . . . . . . . . . . . . . 62
Appendix A. Collected CDDL . . . . . . . . . . . . . . . . . . . 63
Appendix B. Acknowledgments . . . . . . . . . . . . . . . . . . 103
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 103
1. Introduction
Autonomous agents increasingly exchange consequential messages --
tool invocations, memory writes, capability delegations, settlements
-- across trust and organizational boundaries. Existing agent
protocols secure the transport connection but leave the individual
message un-signed, its effect on the world undeclared, and its
authorization implicit. N-AALP makes the object, not the connection,
the unit of security and governance.
N-AALP is the application layer above the N-PAMP substrate [NPAMP]:
N-PAMP provides the post-quantum secure channel, channel identifiers,
and foreign-protocol Bridge; N-AALP provides the signed, effect-
Sammartano Expires 10 March 2027 [Page 3]
Internet-Draft N-AALP September 2026
labeled, audited object that rides any transport. The two compose
but N-AALP does not require N-PAMP: the same object is valid over
QUIC, WebSocket, or HTTP.
1.1. Scope
This document specifies: the object envelope and its deterministic
CBOR encoding (# Object Model); the COSE signing constructions and
crypto-agile profiles (# Cryptographic Constructions); self-
certifying identity and key lifecycle (# Identity); the closed effect
vocabulary and effect-to-authorization rule (# Effects and
Authorization); the approval object and single-use consume ledger (#
Approval); the audit receipt chain, causal graph, and tiered ordering
(# Audit, Causal Graph, and Ordering); delivery stages (# Delivery);
native streaming (# Streaming); the four transport bindings and the
confidentiality boundary (# Transport Bindings); foreign carriage by
class (# Foreign Carriage by Class); the twenty tiered channel
surfaces (# Channel Surfaces); and the higher-tier additive object
families -- rooms membership, multi-hop delegation, MCP tool-call
binding, description and directory, name bindings and A2A task
transitions, governed negotiation, advisory risk labels, flow
continuations, governed- decision records and transparency log
primitives, portable egress evidence, and manufacturing physical-
hazard claims (# Additive Object Families). The document classifies
these surfaces by status (# Conformance): the spine and each
channel's frozen baseline surface are normative and required for
conformance, while the higher tiers are experimental and OPTIONAL.
The classification is editorial and does not narrow implementation
coverage.
1.2. Non-Goals
N-AALP does not define a transport handshake, key exchange, or record
layer; those are the substrate's (N-PAMP's, or the underlying TLS/
QUIC's). It does not define agent reasoning, planning, or model
behavior. It does not replace the foreign protocols it carries; it
wraps them.
2. Conventions and Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
[BCP14] (RFC2119) (RFC8174) when, and only when, they appear in all
capitals, as shown here.
The following terms are used:
Sammartano Expires 10 March 2027 [Page 4]
Internet-Draft N-AALP September 2026
Object: a deterministically encoded CBOR map, signed with COSE, that
is the unit of N-AALP security and governance.
Content id: the multihash of the SHA-384 of an object's canonical
body excluding its own id field; it binds the object's bytes.
Signer id: a self-certifying identifier derived from a public
signing key (# Identity); no certificate authority is involved.
Effect: one closed value stating what an object does to the world,
aligned with the N-PAMP Bridge SafetyLabel and used as an
authorization input (# Effects and Authorization).
Channel: one of twenty application surfaces, identified by an N-PAMP
channel id (# Channel Surfaces).
Carriage: wrapping a foreign protocol's message octet-for-octet
inside a signed N-AALP object (# Foreign Carriage by Class).
3. Architecture
N-AALP separates three layers that MUST NOT be conflated:
1. The object (this document): self-secured, transport-independent.
2. The transport: provides confidentiality, connection
authentication, and framing; N-PAMP is the reference confidential
transport, but QUIC, WebSocket, and HTTP are equally valid
carriers of the identical object.
3. The application: the twenty channel surfaces, each a thin body
over the one object model.
Object-level guarantees (integrity, identity, non-repudiation,
effect, audit) are always present regardless of transport.
Confidentiality, forward secrecy, and connection authentication are
transport-provided and conditional (# Transport Bindings).
4. Object Model
Every top-level wire structure this document defines -- the object
body below, its COSE signature wrapper, the identity, approval,
audit, delivery, streaming, flow-continuation, carriage, and
additive-family records (# Additive Object Families) -- is collected
as a single reachable root, naalp-artifact, in the CDDL module
(Appendix A). The union exists only so a CDDL validator sees every
production as reachable from one entry point; it carries no
independent wire meaning of its own, and a concrete instance is
Sammartano Expires 10 March 2027 [Page 5]
Internet-Draft N-AALP September 2026
always validated against the specific production for its kind, not
against the union.
4.1. Deterministic encoding
All N-AALP structures are encoded as deterministic CBOR per [RFC8949]
Section 4.2.1: shortest-form integer and length encoding, major-type
map keys sorted in bytewise-lexicographic order of their encoded
form, no indefinite-length items, and no duplicate keys. A non-
canonical encoding of any structure defined here MUST be rejected.
[RFC8949] Section 4.2.2 leaves the integer/float question, and
[RFC9052] Section 3 the empty protected header, to the protocol;
N-AALP resolves each as a MUST-reject so that one logical object has
exactly one encoding. A CBOR float (major type 7) MUST NOT appear in
any object body field, ext value, or cext value, and a decoder MUST
reject a major-type-7 item as non-canonical. An empty COSE protected
header MUST be the zero-length byte string 0x40; the redundant 0x41A0
form (a byte string wrapping an empty map) MUST be rejected as non-
canonical, before the header is interpreted. A map with duplicate
keys MUST be rejected at the decoder ([RFC8949] Section 5.6).
4.2. The object body
The signed payload is the object body, a CBOR map, expressed here in
CDDL [RFC8610]:
naalp-object = {
1 : bstr, ; id: multihash(0x20, SHA-384(body-without-1))
2 : uint, ; kind (per channel surface)
3 : 0..19, ; channel id
4 : uint, ; tier (0 = baseline)
5 : bstr, ; signer: self-certifying signer id
6 : uint, ; created: epoch ms (advisory)
7 : effect, ; closed effect value
8 : [* bstr], ; causes: content ids; may be empty
9 : profile, ; crypto profile
10 : any, ; body: kind-specific, validated by the surface
? 11 : { * uint => any }, ; ext: non-critical; unknown ignored
? 12 : { * uint => any }, ; cext: critical; unknown => reject
? 13 : tstr, ; audience: consuming-authority binding;
; omitted when empty (see below)
? 14 : uint, ; suite: signed signature-suite selector;
; present iff composite alg (see below)
}
effect = &( read_only:0, idempotent_write:1,
non_idempotent_write:2, destructive:3 )
profile = &( public:1, enterprise:2, sovereign:3 )
Sammartano Expires 10 March 2027 [Page 6]
Internet-Draft N-AALP September 2026
Fields 4 (tier) and 9 (profile) are independent axes with distinct
meanings and MUST NOT be conflated. profile selects the cryptographic
signature floor (# Cryptographic Constructions) that bounds which
signature strength a verifier accepts for the object. tier selects
the channel's capability depth (0 = baseline; higher values are named
per channel, # Channel Surfaces) under the one frozen envelope. The
two are set independently: a higher channel tier does not raise the
signature floor, and a higher crypto profile does not unlock channel
capability.
4.3. Content identity
The id (field 1) is multihash(0x20, SHA-384(C)) where C is the
deterministic CBOR encoding of the body with field 1 removed, and
0x20 is the multiformats code for SHA-384. A verifier recomputes the
content id and MUST reject a mismatch (ContentIdMismatch). Because
the id binds the exact bytes, altering any field changes the id.
4.3.1. Multihash length-octet worked example
A multihash is unsigned-varint(code) || unsigned-varint(digest-
length) || digest, per the multiformats multihash and unsigned-varint
specifications. unsigned-varint is a base-128 (LEB128-style)
encoding: a value under 128 encodes as exactly one octet equal to
that value. Both integers a content id ever carries are under 128
(32 and 48), so both octets are single-byte:
octet 0 : 0x20 -- unsigned-varint(32); sha2-384 multicodec code
octet 1 : 0x30 -- unsigned-varint(48); a 48-octet SHA-384 digest
octets 2..49 : the 48-octet SHA-384 digest
Using the FIPS 180-4 SHA-384 known-answer digest for the ASCII input
"abc" (shown below), the 50-octet content id is:
SHA-384("abc"):
cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163
1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7
content id:
2030cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1
631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7
The two prefix octets are derived from the unsigned-varint algorithm
applied to 32 and 48, not asserted as a fixed literal; the same
algorithm produces a two-octet encoding (0xac 0x02) for a value at or
above 128 (e.g. 300), which is how a future digest algorithm with a
multicodec code or digest length at or above 128 would be carried
without changing this framing. A conformance oracle and grading gate
Sammartano Expires 10 March 2027 [Page 7]
Internet-Draft N-AALP September 2026
for this construction, cross-checked against the multiformats spec
and NIST FIPS 180-4 SHA-384 known-answer values independent of any
N-AALP encoder, ship with the reference implementation (tools/
multihash_oracle.py, scripts/gates/gate_multihash.py).
4.4. Extensions and versioning
The protected header carries naalp-version, which tracks the
*envelope object grammar* -- the top-level numbered fields of naalp-
object. The rule is mechanical: adding or changing a top-level
envelope field moves the version; adding an ext/cext key (a key
inside the field-11 or field-12 maps) never does. naalp-version 1 was
the previous version, which lacked the audience and suite fields;
this document specifies *naalp-version 2*, anchored by two envelope
additions -- the top-level audience field (field 13) and the suite
field (field 14). A verifier MUST reject an object whose naalp-
version it does not support (UnsupportedVersion).
audience (field 13, a text string) names the one endpoint or channel-
scope identity an object is bound to. It is omitted when empty, so a
no-audience object encodes byte-identically to a naalp-version 1
object and its content id is unchanged. A _consume-once_ kind -- one
whose acceptance spends a single-use ledger resource (the consume
ledger, see Approval) -- MUST carry an audience naming its one
consuming authority. The audience is checked by that consuming
authority at the moment it would spend the object, before the single-
use compare-and-set and outside core object verification, so that an
in-transit relay, ordering authority, or auditor can still verify an
object addressed to someone else. An object whose audience is not
the checking authority -- or a consume-once object that omits it --
is rejected WrongAudience, with no ledger entry and no state change
(fail-closed).
suite (field 14, a uint) is the signed selector for the object's
signature suite. It is PRESENT if and only if the object is signed
with the opt-in composite algorithm (the composite COSE alg id in the
private-use range, see Signing) and ABSENT for a pure ML-DSA object,
so a pure object encodes byte-identically to a naalp-version 1 object
carrying neither field 13 nor field 14 and its content id is
unchanged. Value 1 selects the COMPSIG-MLDSA65-Ed25519-SHA512
composite suite (Public/Enterprise); value 2 is RESERVED for the ML-
DSA-44 edge suite. Because suite is a signed body field it cannot be
stripped or altered without invalidating the signature; a suite value
that disagrees with the COSE algorithm is rejected (SuiteMismatch).
Turning the composite on, or changing the default suite in a later
profile, is a suite-registry change, not a wire break.
Sammartano Expires 10 March 2027 [Page 8]
Internet-Draft N-AALP September 2026
Field 11 (ext) carries non-critical extensions a verifier that does
not recognize them MUST ignore. Field 12 (cext) carries critical
extensions a verifier that cannot honor them MUST reject the whole
object (fail-closed). Three extension keys are defined in this
revision and ride this mechanism without moving the version. Key 13
(a key inside the ext/cext maps -- a distinct namespace from the top-
level audience field 13), recheck, names by id the procedure a
verifier runs to re-check the body claim, from a closed set -- 1
recompute-content-id, 2 verify-cose-sign1, 3 walk-causes, 4 replay-
consume-check; carried in ext an unrecognized id is ignored, in cext
it is rejected fail-closed (UnknownCriticalExt). Key 14, signer-
counter, is an OPTIONAL forward-only per-signer position carried only
in the non-critical ext map; it is a duplication-_detection_ aid,
never a verification gate, and because ext is part of the signed
payload it is covered by the signer's own signature. Key 15,
producing-boundary, is an OPTIONAL per-object disclosure of the trust
boundary that produced the object and whether that boundary observed
the described event first-hand or is relaying a report of it: a small
map of boundary (a party identifier in the same bstr form as the
object signer), kind (a closed enum -- 1 observed, 2 reported), and
an OPTIONAL reporting-boundary (bstr) present only when kind is
reported and absent under observed. It rides the non-critical ext
map, so a verifier that does not recognize it -- or reads a malformed
value (no boundary, a kind outside the enum, or a reporting-boundary
under observed) -- ignores the entry and the object still verifies;
placing it in cext is an unrecognized critical key rejected fail-
closed (UnknownCriticalExt). Because ext is part of the signed
payload the disclosure is covered by the signer's own signature: it
is a claim the signer makes about itself, naming whose observational
domain the object rests on and whether first-hand or relayed, and
does not by itself establish that the named boundary is honest or
that its clock is authoritative outside its own domain (# Security
Considerations). Higher channel tiers add capability by defining new
kind codes and, at higher tiers, new ext/cext keys; the envelope
grammar itself changes only with the version.
5. Cryptographic Constructions
5.1. Signing
An object is signed with COSE_Sign1 [RFC9052] over the deterministic-
CBOR object body as the payload. The protected header carries the
COSE algorithm and a pre-parse routing copy of the signer, profile,
and protocol version under a text-string label. A verifier MUST
reject an object whose header signer/profile copies disagree with
body fields 5 and 9 (HeaderBodyMismatch). On the wire the object is
a tagged COSE_Sign1 (CBOR tag 18). The default and mandatory-to-
implement signature is pure ML-DSA; a deployment MAY opt in to a
Sammartano Expires 10 March 2027 [Page 9]
Internet-Draft N-AALP September 2026
hybrid, which is a single COSE_Sign1 (tag 18) whose signature value
is one non-separable IETF LAMPS composite of an ML-DSA and an Ed25519
signature -- not two separate signatures -- selected by the signed
suite field (field 14, below).
An N-AALP object's signature wrapper (naalp-signed-object) is
therefore one of exactly two tagged COSE structures: the COSE_Sign1
(tag 18) form above, or COSE_Sign (tag 98), a multi-signature form
this revision uses for exactly two cases -- the OPTIONAL legacy two-
signature Ed25519+ML-DSA hybrid (not required by the Standard
profile; (# Security Considerations) discusses why the single-
COSE_Sign1 composite above is preferred to it), and the Rotation
object's co-signature (# Identity). A COSE_Sign carries the same
protected, unprotected, and payload positions as COSE_Sign1 plus a
signatures array of two or more COSE_Signature elements, each an
independent [protected, unprotected, signature] triple with its own
per-signer protected header {1 => cose-alg}; this per-element header
is distinct from the COSE_Sign body's own protected header. The
unprotected header position in both structures is the generic COSE
header map ([RFC9052] Section 3); this revision defines no N-AALP-
specific unprotected-header parameter.
5.2. Algorithms and profiles
The mandatory-to-implement signature algorithm is pure ML-DSA
[FIPS204] using the deterministic variant (rnd = 0) so two
implementations produce byte-identical signatures. This determinism
is a producer and cross-implementation-parity property, not a
security requirement: [FIPS204] permits either the deterministic or
the hedged (randomized) signing variant, and a production signer MAY
use the hedged variant. A conforming verifier MUST accept any
signature that is valid under the object's declared algorithm and
profile floor regardless of whether it was produced deterministically
or with per-signature randomness; the deterministic variant is
required only for generating the conformance corpus and its byte-
identical cross-implementation vectors (# Conformance). The
composite's classical leg is Ed25519 [RFC8032] [RFC9864]. Within the
COSE algorithm framework [RFC9053], the pure ML-DSA and Ed25519
suites reuse the code points registered for ML-DSA [RFC9964] and
Ed25519 [RFC9864]; the opt-in composite is named by a single COSE
algorithm id in the COSE private-use range (integers < -65536), which
N-AALP owns provisionally until IANA assigns a public composite code
point, so adopting the eventual public id is a registry swap rather
than a wire break. Three profiles select a signature floor:
Sammartano Expires 10 March 2027 [Page 10]
Internet-Draft N-AALP September 2026
+============+=======+==========================+
| profile | value | signature floor |
+============+=======+==========================+
| public | 1 | ML-DSA-65 (NIST level 3) |
+------------+-------+--------------------------+
| enterprise | 2 | ML-DSA-65 (NIST level 3) |
+------------+-------+--------------------------+
| sovereign | 3 | ML-DSA-87 (NIST level 5) |
+------------+-------+--------------------------+
Table 1
The closed cose-alg identifiers this revision defines are: ml-dsa-44
(-48, NIST level 2, reserved for an optional edge/light tier and
never a Public/Enterprise/Sovereign default), ml-dsa-65 (-49, NIST
level 3, the Public/Enterprise floor), ml-dsa-87 (-50, NIST level 5,
the Sovereign floor), ed25519 (-19, classical, an opt-in composite
leg only), compsig-mldsa65-ed25519 (-65537, the opt-in Public/
Enterprise LAMPS composite above), and compsig-mldsa44-ed25519
(-65538, an edge composite suite that is registered but not
implemented in this revision). An unrecognized algorithm identifier
is rejected UnknownAlg.
A verifier for a profile MUST reject a signature below the profile's
floor (ProfileDowngrade) and an unknown algorithm (UnknownAlg). The
opt-in Ed25519+ML-DSA composite is a single COSE_Sign1 whose LAMPS
composite value is valid only when both components verify against the
shared message representative; because the two components are bound
into one non-separable value, a stripped-leg object has no valid
signature at all (the strongest level of the LAMPS composite non-
separability spectrum: strong non-separability with simultaneous
verification). A signed suite field (field 14) that disagrees with
the signature algorithm is rejected (SuiteMismatch), and a Sovereign
verifier refuses a composite object outright (CompositeRefused),
since Sovereign signs with pure ML-DSA-87 and carries no classical
leg. Digests use SHA-384 [FIPS180].
The public and enterprise profiles carry an identical object-
signature floor: both require ML-DSA-65 (NIST level 3), both permit
the opt-in composite, and both use SHA-384 digests. They are not
distinguished by object cryptography. The distinction is one of
deployment policy -- the confidential transport an object composes
with: an enterprise deployment mandates the higher N-PAMP transport
profile, while a public deployment MAY compose with either the
Standard or the higher profile. A verifier applies the same object-
signature-floor check to both; the profile value records the
deployment posture, not a different object-crypto strength.
Sammartano Expires 10 March 2027 [Page 11]
Internet-Draft N-AALP September 2026
6. Identity
The signer id (object field 5) is self-certifying: multibase(base32,
multihash(0x12, SHA-256(multicodec(mc, pubkey)))), identical in form
to the N-PAMP PeerHandle, where mc is the key-type code: 0xed
(ed25519-pub), 0x1211 (mldsa-65-pub), 0x1212 (mldsa-87-pub). These
three values are N-AALP's own normative constants for signer-id
derivation (sourced from the multiformats table, 2026-08-19); a
verifier MUST derive mc from exactly these three values, independent
of the live upstream multiformats table (# Security Considerations).
A verifier recomputes the id from the key and MUST reject a mismatch
(SignerMismatch). No certificate authority is involved.
Key lifecycle records are signed with the same COSE crypto: a
Rotation is co-signed by both the old and new key -- carried as a
COSE_Sign (tag 98) structure whose two COSE_Signature elements are
the old-key and new-key signatures (# Cryptographic Constructions); a
Rotation encoded as a single-signature COSE_Sign1 (tag 18) is
rejected RotationUnauthorized. A Revocation is signed by the revoked
key or a deployer-configured recovery key (a Revocation signed by
neither is rejected, fail-closed); a foreign-identity link is cross-
signed by the foreign identity's key. Identity strings that carry
human text MUST be Unicode NFC (NonNFC otherwise). A receipt signed
under a superseded key remains attributable to the durable identity
thread across rotations.
7. Effects and Authorization
Every object carries, under signature, one value from a closed four-
value effect vocabulary aligned 1:1 with the N-PAMP Bridge
SafetyLabel: read_only (0), idempotent_write (1),
non_idempotent_write (2), destructive (3). The values form a lattice
with destructive at the top.
An unrecognized effect value MUST be treated as destructive and MUST
NOT fail open. Unlike a SafetyLabel that "describes intent and does
not replace authorization", the N-AALP effect IS an authorization
input: an endpoint grants a maximum effect (a capability) to an
authenticated signer id, and an object is authorized only if its
effect does not exceed the grant (EffectNotAuthorized otherwise). No
layer MUST treat transport metadata, a foreign header, or a client-
supplied name as an authorization identity; the authorizing principal
is the object's signature-verified signer id.
An object MAY carry an optional signed safety label (a non-critical
ext) { risk, scope }; it is an accountable claim attributable to the
signer, not a guarantee that the content is safe.
Sammartano Expires 10 March 2027 [Page 12]
Internet-Draft N-AALP September 2026
8. Approval
An Approval object binds, under signature, the content id of the
exact canonical argument object it approves, so approving one action
cannot authorize another (ApprovalMismatch if the args are mutated).
It carries the approver signer id, the granted effect, an anti-replay
nonce, and an expiry.
The consume ledger is a durable, hash-chained set keyed by approval
content id. Consume is an atomic compare-and-set: the first consumer
appends a ledger entry and wins; a second consume for the same
approval id is rejected (AlreadyConsumed). Atomicity is provided by
a write-ahead log written before the acknowledgment and a single-
writer-per-approval-id discipline; under concurrency exactly one
consumer succeeds. An approval past its expiry is rejected
(ApprovalExpired). An approval-required-but-not-granted outcome is a
distinct signed non-success result, never a silent success or denial.
The consume ledger is on the path for a single-use approval spend
only, not for every object. An object that consumes no approval -- a
read-only object, or any object that is verified but not spent -- is
checked from its own signed bytes with no ledger access, so ordinary
verification stays available under partition. Only the spend of an
approval reaches the ledger; a partition that makes the ledger
unreachable denies that spend fail-closed (# Security Considerations)
and denies nothing else. Placing the ledger on every object's path
would make the protocol unavailable under partition for read-only and
other non-spending objects, which this design specifically avoids.
At the baseline tier, an executor that cannot reach the consume
ledger SHALL deny the spend (fail-closed): local spend-and-reconcile-
later is not a baseline behavior, and a consumer that spends locally
while the ledger is unreachable is non-conforming. Federated
ordering over identical signed objects, which relaxes this at a
bounded and stated cost, is a named higher tier only (# Audit, Causal
Graph, and Ordering); see (# Security Considerations) for the
exposure model that governs the higher tier.
Sammartano Expires 10 March 2027 [Page 13]
Internet-Draft N-AALP September 2026
Each consume is recorded as a hash-chained ledger entry: { seq, prev,
approval_id, by }, where prev is the SHA-384 of the previous entry
(the genesis prev is 48 zero bytes) and by is the consuming signer
id; the head after an entry is SHA-384(entry), so editing any entry
breaks the next entry's linkage and is detectable on replay
(LedgerCorrupt). This chaining format is what the single-writer-per-
approval-id property above rests on as a mechanism, not merely an
operational discipline: the write-ahead log persists an entry before
acknowledging it, and the first-append-wins compare-and-set is what
makes exactly one consumer's entry extend the chain for a given
approval id, with every later append for that id rejected
(AlreadyConsumed) rather than merely discouraged.
Each consume also mints a *consume receipt* (naalp-consume-receipt),
a wire object distinct from the ledger entry above: { ledger,
approval_id, position }, signed by the consuming ledger's own key
over exactly those bytes, and minted by the same first-append-wins
compare-and-set -- the first consume of an approval id assigns
exactly one position and mints exactly one receipt; a second consume
mints nothing (AlreadyConsumed). Because the position is under the
ledger's own signature rather than the requester's, the requester
cannot forge it, and the receipt is the normative, independently-
verifiable artifact by which a third party detects a double spend
without trusting either party to the spend.
At the federated tier, cross-authority single-consume is specified by
issuance-time audience binding, not by a mandated cross-authority
consensus protocol: a consensus mechanism between ordering
authorities is explicitly out of scope for this specification. A
consume-once object -- one whose acceptance spends this ledger --
MUST carry a signed audience naming its one consuming authority (#
Object Model); any authority other than the named one rejects it
WrongAudience before consume logic runs, so an object addressed
elsewhere can never reach this ledger's compare-and-set. The
residual case a partitioned federation still allows is the named
authority's own ledger state forking -- two ledger-signed receipts
issued against one approval id from divergent state. Two such
receipts contradict on comparison by any third party holding both,
since neither party to the spend could have produced the other's
ledger signature; this contradiction is ConsumeFork, the detected
double spend, bounded in exposure by the spent approval's own expiry
(# Security Considerations).
The delivery guarantee this ledger provides therefore has a scope
boundary, stated here explicitly. On a single reachable authority
the guarantee is prevention: exactly-once, by the compare-and-set
above. Across a federation the guarantee is weaker and is stated as
such: a double consume remains a verifiable wire violation --
Sammartano Expires 10 March 2027 [Page 14]
Internet-Draft N-AALP September 2026
prevented before the fact by WrongAudience for any misdirected
object, and detected after the fact by ConsumeFork for the residual
same-authority fork -- never a consensus-based exactly-once
guarantee, because this specification fixes invariants over signed
bytes and does not mandate any cross-authority consensus topology.
The approval object's full state machine -- pending, approved,
consumed, expired, its timer, and the clock-skew rule governing
not_after -- is specified normatively in (# Object State Machines).
9. Audit, Causal Graph, and Ordering
An ordering authority records each accepted object by appending a
signed Receipt { prev, obj, seq, at } where prev is the SHA-384 of
the previous receipt body (genesis is zero) and obj is the object
content id. The chain is tamper-evident: reordering, omission, or
substitution breaks a prev link or a seq (ChainBroken). The
authority MUST NOT mutate the origin object to order it; ordering is
an outer signed layer.
Every object MAY name its causes by content id (field 8). This is a
signed partial order: an edge "A causes B" is proven by B's signature
over A's content id, checkable offline with no ordering authority
present. An object MUST NOT name a cause it could not have seen (a
cause whose ordering position exceeds the effect's, or a cycle),
which is rejected (CausalViolation). A total order is a policy
layered over this partial order.
A receiver applies the causal graph, not merely stores it: a receiver
SHALL NOT apply an object's effect before it has applied the effects
of that object's already-received causes (the causes named by content
id in field 8). An object naming a cause the receiver has not yet
received is held *pending* -- a state distinct from CausalViolation
-- for a bounded interval: the receiver neither applies nor rejects
it. It is resolved when the named cause is applied, which lifts the
hold, or by timeout once the bounded interval elapses, which rejects
the object. Pending and CausalViolation answer different questions:
CausalViolation is a cause the object could not have seen -- a
future-cause or a cycle -- and is rejected outright and immediately;
pending is a cause the receiver has simply not received yet, which
gives the receiver no basis to reject the object, so an object is
never rejected merely for arriving before a cause that is itself
still in flight. The bound on the hold interval is a timer value,
fixed normatively by this document's per-object-kind timer
specification.
Sammartano Expires 10 March 2027 [Page 15]
Internet-Draft N-AALP September 2026
An independent auditor detects equivocation -- two receipts by one
authority at one seq naming different objects -- from the signed
receipts alone (Equivocation) and expresses the finding as a non-
repudiable ForkProof carrying the accused authority's own two
signatures over the two conflicting receipt bodies, plus an external
counter binding the proof against replay or reorder. A verifier
accepts a ForkProof only when the signer is named, the two receipts
share one seq, name different objects, and both signatures verify
under the accused key, and rejects it whole otherwise
(ForkProofInvalid, or ReceiptUnsigned for a signature that does not
verify). This is a draft-01 change (it supersedes draft-00's
signatureless fork proof, which an accused could repudiate). A
receipt whose signature does not verify is rejected
(ReceiptUnsigned).
Ordering is tiered. The baseline tier is a single authority's
monotonic receipt chain. The higher tier is federated: multiple
independent authorities each order their own scope, and a signed
Reconcile object records their deterministic merge. Because both
tiers order the identical signed objects, moving to federated
ordering requires no envelope change.
The federated merge SHALL produce a deterministic total order: a
topological linearization of the union of the per-authority causal
graphs above (the signed partial order over field 8), with ties --
pairs the causal graph does not order relative to each other --
broken by object content id in bytewise ascending order. The tie-
break is fixed by this document, not left to an implementation's
choice: because the linearization depends only on the union causal
graph and this fixed tie-break, and not on which authority reconciles
first or the order the objects arrived in, two conforming reconcilers
reconciling the identical set of objects SHALL produce the identical
total order.
The receive-side pending-cause hold introduced above, the federated
reconcile machine, and their timers are specified normatively in (#
Object State Machines).
10. Delivery
Delivery is four distinct, monotonic, separately-observable stages,
each a signed delivery update naming the object content id and the
stage reached: persisted_origin (0), accepted_relay (1),
persisted_target (2), presented (3). There is no single "sent" flag.
An endpoint MUST durably persist an object before emitting the
acknowledgment that advances its stage (persist-before-acknowledge),
so a crash immediately after an acknowledgment loses nothing. A
stage earlier than the one already reached is rejected
Sammartano Expires 10 March 2027 [Page 16]
Internet-Draft N-AALP September 2026
(StageOutOfOrder). A relay that holds an object only in transit MAY
write an audit trail over content ids while retaining no payload.
The full delivery state machine, including the idempotent-repeat and
legal-skip cases and its stage-advance timer, is specified
normatively in (# Object State Machines).
11. Streaming
A native stream is three signed objects plus unsigned chunks.
StreamOpen binds the stream identity, effect, and (where it causes an
effect) approval; a stream whose effect is not authorized is refused
before any chunk. Chunks are raw data frames; they are not
individually signed. Per-chunk authentication before StreamCommit is
a property of the specific transport in use, not of the N-AALP
object: over N-PAMP, the Stream channel's AEAD authenticates every
chunk to the peer (# Transport Bindings); over a transport without an
equivalent per-frame guarantee, a chunk consumed before StreamCommit
is unauthenticated at the object layer. StreamCommit carries a
single rolling SHA-384 over the chunks in absolute-offset order,
making the whole stream non-repudiable with one signature; altering
any delivered byte invalidates it (StreamDigestMismatch). This
rolling digest, together with any optional signed StreamCheckpoint,
is the object-level mechanism that binds the chunk sequence,
transport-independent and identical across N-PAMP and non-N-PAMP
transports. A StreamCheckpoint confirms only a contiguous prefix
through its stated offset, never the stream's end. The same three
objects map onto QUIC streams, WebSocket messages, and HTTP chunked/
event streams; the commitment verifies identically across transports.
The full stream state machine, including the illegal-reuse cases
after StreamCommit, is specified normatively in (# Object State
Machines).
12. Object State Machines
This section collects the normative event x state tables governing
the four N-AALP objects whose correct handling depends on accumulated
history rather than being decidable from one signed object in
isolation: delivery (# Delivery), streaming (# Streaming), approval
(# Approval), and the reconcile record (# Audit, Causal Graph, and
Ordering). Each table is total: every (state, event) pair a
conforming implementation can encounter resolves to exactly one
Reaction, either a transition to a next state (-> NextState) or a
rejection under a named error (reject (ErrorName)). A (state, event)
pair not listed in a table is rejected under that table's stated
default error, so no combination is left undefined. Every rejection
under these machines is fail-closed: the triggering object is refused
Sammartano Expires 10 March 2027 [Page 17]
Internet-Draft N-AALP September 2026
whole, its named error is returned, and no state change occurs.
Every transition below presupposes the triggering object has already
passed the envelope-layer checks -- signature verification and
content-id recomputation (# Cryptographic Constructions, # Object
Model) -- before this table is consulted; an envelope-layer failure
(BadSignature, ContentIdMismatch) is rejected there and never reaches
these tables.
12.1. Delivery state machine
The delivery machine (# Delivery) has states persisted_origin,
accepted_relay, persisted_target, and presented, ordered 0 through 3.
Its one event is a delivery update reporting a stage S'; relative to
the machine's current state (the highest stage already reached for
the object), the update is classified advance(S') when S' is strictly
greater than the current stage, repeat(S') when S' equals it, and
regress(S') when S' is strictly less than it. advance(S') transitions
directly to S', not merely to the next stage in sequence: a stage MAY
be skipped (for example a relay-less delivery path observing
persisted_origin followed directly by persisted_target, with no
accepted_relay in between), because skipping a stage is not a
regression -- only regress(S') is. repeat(S') -- the same stage
reported a second time, such as a redelivered acknowledgment -- is
accepted idempotently and leaves the state unchanged; it is not an
error and produces no new observable state.
Sammartano Expires 10 March 2027 [Page 18]
Internet-Draft N-AALP September 2026
+==================+=============+==========================+
| State | Event | Reaction |
+==================+=============+==========================+
| persisted_origin | advance(S') | -> S' |
+------------------+-------------+--------------------------+
| persisted_origin | repeat(S') | -> persisted_origin |
+------------------+-------------+--------------------------+
| accepted_relay | advance(S') | -> S' |
+------------------+-------------+--------------------------+
| accepted_relay | repeat(S') | -> accepted_relay |
+------------------+-------------+--------------------------+
| accepted_relay | regress(S') | reject (StageOutOfOrder) |
+------------------+-------------+--------------------------+
| persisted_target | advance(S') | -> presented |
+------------------+-------------+--------------------------+
| persisted_target | repeat(S') | -> persisted_target |
+------------------+-------------+--------------------------+
| persisted_target | regress(S') | reject (StageOutOfOrder) |
+------------------+-------------+--------------------------+
| presented | repeat(S') | -> presented |
+------------------+-------------+--------------------------+
| presented | regress(S') | reject (StageOutOfOrder) |
+------------------+-------------+--------------------------+
Table 2
The machine's initial state for an object is established by that
object's first delivery update: the first update records the stage it
reports and cannot regress, because no earlier stage has yet been
reached for that object; the table above governs every update after
the first.
Any (state, event) pair not listed above is rejected with
StageOutOfOrder.
12.2. Stream state machine
The stream machine (# Streaming) has states idle (no stream open for
this stream id), open, committed, and abandoned (a terminal state an
open stream enters when its idle/commit timer expires; see # Timers),
and events StreamOpen, chunk, StreamCheckpoint, and StreamCommit.
Sammartano Expires 10 March 2027 [Page 19]
Internet-Draft N-AALP September 2026
+===========+====================+========================+
| State | Event | Reaction |
+===========+====================+========================+
| idle | StreamOpen (effect | -> open |
| | authorized) | |
+-----------+--------------------+------------------------+
| idle | StreamOpen (effect | reject |
| | not authorized) | (EffectNotAuthorized) |
+-----------+--------------------+------------------------+
| open | chunk | -> open |
+-----------+--------------------+------------------------+
| open | StreamCheckpoint | -> open |
+-----------+--------------------+------------------------+
| open | StreamCommit | -> committed |
| | (digest matches) | |
+-----------+--------------------+------------------------+
| open | StreamCommit | reject |
| | (digest mismatch) | (StreamDigestMismatch) |
+-----------+--------------------+------------------------+
| open | StreamOpen | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| committed | chunk | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| committed | StreamCheckpoint | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| committed | StreamCommit | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| abandoned | StreamOpen | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| abandoned | chunk | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| abandoned | StreamCheckpoint | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
| abandoned | StreamCommit | reject |
| | | (StreamStateError) |
+-----------+--------------------+------------------------+
Table 3
Sammartano Expires 10 March 2027 [Page 20]
Internet-Draft N-AALP September 2026
Any (state, event) pair not listed above is rejected with
StreamStateError, which names a stream-state violation: an object
arriving for a stream whose current state does not admit it -- a
second StreamOpen on an already-open stream, or any chunk,
StreamCheckpoint, or StreamCommit after the stream has committed or
been abandoned. A committed or abandoned stream id is terminal: it
is never re-admitted, so a StreamOpen naming it is rejected
StreamStateError rather than opening a fresh stream. A chunk that
exceeds its granted flow-control credit is refused at the transport,
not the object, layer (NPAMP-STREAM FlowControlError, # Transport
Bindings): that rejection is a property of the specific transport in
use and is distinct from the state-machine violations in this table,
which apply uniformly across every transport binding.
12.3. Approval state machine
The approval machine (# Approval) has states pending, approved,
consumed, and expired, and events approve, consume, and expiry (the
passing of not_after; see Clock skew and validity windows, below).
ApprovalHeld is not a fifth state: it is the distinct signed non-
success result an action requiring this approval receives while the
machine is in pending, never a silent success or denial. An action
requiring an approval for which no Approval object naming its exact
args has ever been signed is refused ApprovalRequired at the point of
use, independent of this table.
+==========+=========+==========================+
| State | Event | Reaction |
+==========+=========+==========================+
| pending | approve | -> approved |
+----------+---------+--------------------------+
| approved | consume | -> consumed |
+----------+---------+--------------------------+
| approved | expiry | -> expired |
+----------+---------+--------------------------+
| consumed | consume | reject (AlreadyConsumed) |
+----------+---------+--------------------------+
| expired | consume | reject (ApprovalExpired) |
+----------+---------+--------------------------+
Table 4
An args-content-id mismatch -- the presented args do not hash to the
content id the Approval object binds -- is rejected ApprovalMismatch
regardless of state and takes precedence over every cell in this
table, because content binding is checked before any state-dependent
reaction is evaluated. Where an approval is both past not_after and
already present in the consume ledger, expiry takes precedence over
Sammartano Expires 10 March 2027 [Page 21]
Internet-Draft N-AALP September 2026
consumption: a consume request MUST be checked against not_after
before the ledger is consulted, so such a request is rejected
ApprovalExpired, never AlreadyConsumed, and the ledger is left
untouched by the rejected request.
Any (state, event) pair not listed above is rejected with
ApprovalRequired.
12.4. Reconcile state machine
The reconcile machine (# Audit, Causal Graph, and Ordering) governs
the production of one federated Reconcile record. States: collecting
(per-authority receipt chains are being gathered), linearized (the
deterministic total order has been computed), verified. Events: add-
chain (a per-authority receipt chain is added to the merge),
linearize (the deterministic linearization is computed over the
accumulated chains), verify (an independent recomputation is compared
against a claimed total order).
+============+===============================+=====================+
| State | Event | Reaction |
+============+===============================+=====================+
| collecting | add-chain (valid link, no | -> collecting |
| | equivocation) | |
+------------+-------------------------------+---------------------+
| collecting | add-chain (broken prev/seq | reject |
| | link) | (ChainBroken) |
+------------+-------------------------------+---------------------+
| collecting | add-chain (two receipts, one | reject |
| | seq, different objects) | (Equivocation) |
+------------+-------------------------------+---------------------+
| collecting | add-chain (receipt signature | reject |
| | does not verify) | (ReceiptUnsigned) |
+------------+-------------------------------+---------------------+
| collecting | linearize (union causal graph | -> linearized |
| | acyclic, no future-cause) | |
+------------+-------------------------------+---------------------+
| collecting | linearize (cycle or future- | reject |
| | cause in the merged graph) | (CausalViolation) |
+------------+-------------------------------+---------------------+
| linearized | verify (independent | -> verified |
| | recomputation agrees) | |
+------------+-------------------------------+---------------------+
| linearized | verify (independent | reject |
| | recomputation disagrees) | (ReconcileMismatch) |
+------------+-------------------------------+---------------------+
Table 5
Sammartano Expires 10 March 2027 [Page 22]
Internet-Draft N-AALP September 2026
ReconcileMismatch is the one new error name this section introduces:
a verifier that independently re-runs the deterministic linearization
(# Audit, Causal Graph, and Ordering) over the identical set of
objects and obtains a total order different from the one the
Reconcile record claims has found a violation of the fixed
determinism property that section states, and rejects the record
whole.
Any (state, event) pair not listed above is rejected: a fault in an
individual per-authority receipt chain is rejected under its own
already-registered name (ChainBroken, Equivocation, or
ReceiptUnsigned), a cycle or future-cause in the merged causal graph
is rejected CausalViolation, and a disagreeing linearization is
rejected ReconcileMismatch.
12.5. Timers
Every timer named in this section has a name, a start condition, a
clear condition, and a named reaction on expiry. This document
specifies each timer's behavior but not its duration: the interval a
timer runs for is a deployment and local-policy parameter, and no
timer in this section carries a protocol-mandated default duration.
The hard numeric bounds this document does impose on the wire -- the
per-stream chunk-count limit (TooManyChunks, # Streaming) and the
decoder resource limits in the CDDL (# Collected CDDL) -- are stated
where they are enforced, not here.
Delivery stage-advance timer: starts when a delivery update advances
an object's state to any stage before presented; clears when a
later update reports presented (or, per the Delivery table above,
a repeat or a further advance) for the same object; on expiry, the
stalled delivery is reported to the sender as undelivered
(NotDelivered) without changing the object's last durably-recorded
stage -- expiry ends the wait, not the recorded progress.
Stream idle/commit timer: starts when a StreamOpen transitions the
stream machine to open; clears when a StreamCommit transitions it
to committed; on expiry, the stream transitions open -> abandoned
and is rejected StreamStateError. abandoned is terminal (# Object
State Machines): no later chunk, StreamCheckpoint, or StreamCommit
for the stream is admitted, and its stream id MUST NOT be reopened
-- a StreamOpen naming an abandoned id is rejected
StreamStateError, never re-admitted as a fresh stream. No chunk
delivered without an eventual StreamCommit is non-repudiable (#
Streaming), so an abandoned stream commits nothing.
Approval not_after timer: starts when an Approval object is signed;
Sammartano Expires 10 March 2027 [Page 23]
Internet-Draft N-AALP September 2026
clears when the approval is consumed (# Approval) while still
valid; on expiry, the approval transitions approved -> expired per
the Approval table above, and any further consume is rejected
ApprovalExpired.
Pending-cause hold timer: starts when an object is received naming a
cause (# Audit, Causal Graph, and Ordering) the receiver has not
yet received, putting the object into the held pending state
described there; clears when the named cause is applied, which
lifts the hold and allows the held object's own effect to be
applied; on expiry, the held object is rejected and its effect is
never applied. This timer is the bounded interval that section
states the pending-cause hold is "resolved ... by timeout once the
bounded interval elapses."
12.6. Clock skew and validity windows
An Approval's not_after (# Approval) is judged against the action's
authoritative ordering position -- its receipt seq/at where a receipt
exists (# Audit, Causal Graph, and Ordering) -- and, only where no
receipt yet exists for the action, its advisory created timestamp
stands in as the best available position for this one purpose (#
Object Model); this narrow fallback does not make created reliable
ordering evidence for any other purpose (# Security Considerations).
In neither case is validity judged against any participant's own wall
clock: there is exactly one clock in this check -- the authoritative
ordering position -- so wall-clock skew between the approver, the
consumer, and any relay never enters the determination of whether
not_after has passed. This document does not define a numeric clock-
skew tolerance: a validity check is a single deterministic comparison
against one authoritative position, not a comparison between two
participants' clocks, so no tolerance is required by this design, and
none is introduced.
12.7. Cancel
A cancel object -- TaskCancel (0x0011/2), Cancel (0x000E/3), or a
channel-specific cancel object elsewhere in (# Channel Surfaces) --
MUST propagate a real abort to the work it names: an implementation
that flips a status field to a canceled state without actually
stopping the underlying work has not canceled it, and reporting the
canceled status in that condition is a false report and is non-
conforming. Once an object's status has reached a terminal canceled
state, no later transition MAY leave it: a terminal-absorbing guard
MUST ensure that a status update completing the same work after
cancellation (for example a late completed transition arriving after
canceled) is dropped, not applied, so a race between an abort and an
in-flight completion can never overwrite the canceled outcome. This
Sammartano Expires 10 March 2027 [Page 24]
Internet-Draft N-AALP September 2026
is the general terminal-state rule already stated elsewhere for an
imported task lifecycle -- a terminal state has no out-edge and no
transition may leave it -- applied here to cancellation specifically;
it is not a new rule.
12.8. Deadline
Effecting objects are time-bounded today by their required approval's
not_after, judged at the action's authoritative ordering position as
stated above (# Approval). A distinct, per-object signed deadline
field is out of scope for this revision and is noted here for a
future revision.
13. Transport Bindings
A binding carries exactly one signed object as one message unit, with
identical object semantics over N-PAMP, QUIC, WebSocket, and HTTP.
The object is self-secured; the binding adds only framing and, from
the transport, confidentiality and connection authentication. The
media type is application/vnd.bubblefish.naalp+cbor (one object per
representation).
The confidentiality boundary is normative: an object marked sensitive
MUST NOT be emitted in cleartext over a non-confidential transport;
the binding refuses it (ConfidentialTransportRequired) and directs
the deployment to a confidential transport. A transport lacking peer
authentication where policy requires it is refused
(PeerUnauthenticated).
14. Foreign Carriage by Class
N-AALP carries a foreign agent protocol by wrapping its message,
octet-for-octet, in a signed carriage object interpreted by a
carriage class:
naalp-carriage-body = {
1 : uint, ; protocol_id (N-AALP protocol registry)
2 : carriage-class, ; 0 JSONRPC .. 5 OPAQUE
3 : uint, ; content_type (foreign encoding)
4 : bstr, ; correlation token
5 : tstr, ; method (advisory routing key)
6 : bstr, ; foreign message, carried octet-for-octet
}
carriage-class = &( jsonrpc:0, http:1, msg:2,
stream:3, doc:4, opaque:5 )
Sammartano Expires 10 March 2027 [Page 25]
Internet-Draft N-AALP September 2026
The foreign field is carried verbatim and MUST NOT be re-serialized,
canonicalized, summarized, or rewritten. For every carriage class,
N-AALP treats the foreign message as opaque octets: it never decodes
or parses the foreign payload, and it binds those octets by hash
under the carriage object's content id and signature. A verifier
checks the payload by recomputing that content id over the carried
bytes, never by interpreting the foreign format, so a tampered
payload is rejected on the content-id check before any foreign parser
could run. This rule is normative for every carriage class -- each
foreign binding is carriage by class, so no binding adds a foreign-
format parser to the verify path. The carriage object's signer
remains the authority: a foreign protocol's identity, header, or
metadata MUST NOT become an N-AALP authorization identity. The
OPAQUE class carries any protocol, including an undefined one, on an
experimental protocol id with no registration. A below-foreign
failure uses a defined structured error and MUST NOT report an
undelivered message as delivered; an N-AALP semantic a foreign
protocol cannot represent surfaces a typed mapping error, never a
silent drop.
15. Channel Surfaces
N-AALP defines twenty channel surfaces (channel ids 0x0000..0x0013):
Control, Memory, Capability, Identity, Governance, Immune,
Federation, Settlement, Compliance, Sensory, Telemetry, Audit,
Stream, Bridge, Commerce, Interaction, Discovery, Workflow,
Knowledge, and Spatial. Each surface adds only object kind codes and
their declared effects over the one object model; it introduces no
channel-local encoding, signature, or identity. Every channel has a
complete frozen baseline surface. Higher tiers add capability under
the frozen envelope through the tier field and critical/non-critical
extensions. The complete kind/effect table is the N-AALP Channel and
Object Kind registries (# IANA Considerations).
16. Additive Object Families
This section documents the higher-tier (tier 1+) object families this
revision adds. None of them introduces a new envelope field,
encoding, signature form, identity mechanism, or effect value: each
family reuses the frozen naalp-object envelope (# Object Model), the
closed effect lattice (# Effects and Authorization), the content-id
framing (# Object Model), and, where a family is receipt-chained, the
audit receipt chain construction unchanged (# Audit, Causal Graph,
and Ordering). A baseline verifier that has not licensed a family's
tier rejects its kind as UnknownKind (# Object Model), consistent
with the tier model (# Object Model, # Channel Surfaces). Every
capability in this section carries experimental status under the
tiering rule of (# Conformance): it is OPTIONAL, and an
Sammartano Expires 10 March 2027 [Page 26]
Internet-Draft N-AALP September 2026
implementation that omits it is still conforming.
16.1. Collaboration and Rooms Membership
A room's membership is a first-class signed object, not implicit
connection state. A membership change is carried as a naalp-room-op
-- a naalp-object whose body (field 10) is { room, op, epoch,
subject, role } -- on the Governance channel (0x0004). op selects one
of the closed room-op-code operations: create (0), add_member (1),
remove_member (2), change_role (3), or add_owner (4). subject (the
affected member's signer id) MUST be Unicode NFC (NonNFC otherwise).
role is one of the closed member-role values: member (0), admin (1),
or owner (2).
A room-op object is CURSOR-OCCUPYING and RECEIPT-CHAINED: each
accepted op is ordered at a cursor by an ordering authority's naalp-
receipt over the op's content id, weaving membership into the append-
only audit chain (# Audit, Causal Graph, and Ordering) the same way
any other object is ordered. It is EPOCH-BUMPING: epoch carries the
membership epoch the op is built against, and each accepted op
increments the room's epoch; an op built against a superseded epoch
is rejected StaleEpoch. Ownership is multi-owner and ADD-ONLY:
add_owner adds an owner, but an owner is never removed (remove_member
naming an owner is rejected OwnerImmutable) and never demoted
(change_role refusing to lower an owner's role is rejected
RoleInvalid), so a room's owner count is monotonically at least one
and a room can never become ownerless. remove_member naming an
unknown member, or add_member/change_role naming an already-present
or already-absent member as the operation requires, is rejected
MemberExists or MemberUnknown as appropriate; an op whose fields do
not match its declared op code is RoomOpMismatch; an op value outside
the closed set is OpUnknown.
Sammartano Expires 10 March 2027 [Page 27]
Internet-Draft N-AALP September 2026
A naalp-principal-binding maps a stable semantic principal id to a
durable Handle (a signer id): { principal, handle, epoch, prev }.
principal MUST be Unicode NFC. It is kept as a per-principal SHA-384
hash chain -- prev carries the prior binding's head (48 bytes;
genesis is 48 zero bytes) -- the same chaining shape as the audit
receipt chain and the consume ledger, so an omitted or substituted
binding is detectable the same way. A delivery addresses the
semantic principal id and resolves it to the Handle at send time: a
durable naming layer above a connection-scoped peer handle. A rebind
(a new binding for an already-bound principal) is authorized only by
a verified rotation from the current Handle to the new one; a rebind
naming an unrelated key is rejected RebindUnauthorized. Binding a
principal id already bound is PrincipalExists; resolving an unbound
principal is PrincipalUnknown. The membership op-authorization,
epoch guard, owner-immutability, and rebind-continuity rules above
are endpoint behaviors graded against the non-circular oracle, not
wire productions in their own right.
16.2. Multi-Hop Delegation Grant
A naalp-delegation-grant is a normal naalp-object -- an independent
COSE_Sign1 whose issuer is the verified envelope signer (field 5),
never a body field -- carried as Capability-channel (0x0002) kind
DelegationGrant (kind 4), tier 1. The object's own envelope effect
(field 7) is non_idempotent_write (issuing a grant); the body's
effect_cap field is a SEPARATE ceiling the grant confers on its
subject, never the object's own effect. The body is { subject,
effect_cap, max_depth, not_before, not_after, ?scope }: subject (the
delegatee's signer id) MUST be Unicode NFC; effect_cap (the closed
effect lattice) MUST NOT exceed the parent grant's own ceiling, else
CapExceedsParent; max_depth bounds how many FURTHER delegation hops
are permitted below this grant (0 means the subject may act but not
re-delegate); not_before/not_after bound the grant's validity window
(GrantNotYetValid / GrantExpired); the OPTIONAL scope (Unicode NFC
when present) narrows the grant to a resource scope that a child
grant's scope MUST be contained within, else CapExceedsParent.
A DelegationGrant reuses the existing Capability-channel delegation
substrate rather than a parallel mechanism: its delegation parent is
named by content id in the envelope's causes field (# Object Model)
as the UNIQUE cause resolving to a Capability authority object
(CapIssue, CapDelegate, or another DelegationGrant) whose subject/
holder equals this grant's issuer. A root grant -- one whose issuer
is in the verifier's trust-anchor set -- names no such cause; a grant
naming two or more is rejected ChainBroken. A malformed grant body
is GrantMalformed; a grant whose chain of authority does not
terminate at a trusted root is UntrustedChainRoot; a chain exceeding
the accumulated max_depth bound is DelegationDepthExceeded; a revoked
Sammartano Expires 10 March 2027 [Page 28]
Internet-Draft N-AALP September 2026
grant is GrantRevoked. The invariant that delegated authority only
ever shrinks across every hop -- on effect, scope, and depth -- is
stated as a closure-sovereignty property in (# Security
Considerations).
16.3. MCP Tool-Call Binding
An MCP (Model Context Protocol) tool call is CARRIED, not adopted:
its bytes are unchanged, and its unenforced, untrusted annotation
hints are mapped to the closed four-value effect lattice by a
published table for which the wrapping signer is accountable -- a
false declaration is attributable to that key. The wrapper (naalp-
mcp-tool-call) is a Bridge-channel (0x000D) tier-1 kind McpToolCall
(kind 1), a named escalation over the frozen baseline Carriage kind
(0, # Foreign Carriage by Class) under the unchanged envelope. Its
body is { tool, args, annotations }: tool and args carry the MCP tool
definition and call-argument bytes octet-for-octet (never re-
serialized); annotations (naalp-mcp-annotations) is the wrapping
signer's transcription of the tool's MCP ToolAnnotations.
The object's own envelope effect (field 7) is the wrapping signer's
DECLARED effect. A verifier independently recomputes the annotation-
derived effect from the carried annotations and enforces the MORE
SEVERE of the two values -- an unknown or disagreeing input collapses
UP, never down. A declared effect below the annotation-derived
effect is rejected EffectUnderDeclared; an annotation set that maps
outside the closed lattice is rejected MalformedAnnotation, never
defaulted to benign. Each annotation field (readOnlyHint,
destructiveHint, idempotentHint, openWorldHint) is an OPTIONAL mcp-
hint -- the uint 1 (true) or 0 (false); a value outside {0,1}
transcribes no boolean and is rejected. MCP is boolean-typed in JSON
but the N-AALP spine carries no CBOR boolean, so an ABSENT hint takes
the MCP-documented default: readOnlyHint false, destructiveHint TRUE
(the fail-closed default, matching the "absent effect on a state-
changing object defaults to destructive" rule), idempotentHint false,
openWorldHint true. A present 0 and an absent hint therefore encode
to DIFFERENT bytes even where they resolve to the same effect.
openWorldHint is carried for accountability only -- it is an ADVISORY
risk signal that never enters the effect lattice.
naalp-mcp-call-binding ({ tool_id, args_id }) is the value an
approval binds: tool_id and args_id are the content ids of the tool
and argument bytes respectively. Because the binding names BOTH the
tool description and the arguments by content id, a changed tool
description or a changed argument set yields a different call content
id, invalidating a prior approval bound to the old one. A malformed
tool-call body is ToolCallMalformed.
Sammartano Expires 10 March 2027 [Page 29]
Internet-Draft N-AALP September 2026
16.4. Description and Directory
A signed, OFFLINE-VERIFIABLE description and discovery layer, carried
on N-AALP's own signed object: the authority is the SIGNATURE OVER
THE BYTES, never the connection or host that served them, so a signed
description re-verifies byte-identically when an unrelated host
serves the same bytes (a bearer credential, not a fetched document).
A naalp-description ({ service, operations }) lists a service's
operations, each a naalp-description-operation ({ name, effect,
requires_approval }) carrying its effect (the closed lattice) and an
approval declaration: requires_approval (desc-approval-flag, the uint
1/0 -- the spine carries no CBOR boolean) outside {0,1} is rejected
MalformedApprovalFlag.
A naalp-directory ({ directory, version, members }) is a signed
collection whose members are content ids (the same list-of-content-
ids shape the causal partial order uses), carrying a monotonic per-
signer version so two versions can be compared. Two conflicting
versions from ONE signer -- the SAME directory and version but
DIFFERENT members -- are a FORK, detected at the FIRST-DIFFERING
member position and reported DirForkProofInvalid, the same way the
audit ForkProof reports the position of an equivocation (# Audit,
Causal Graph, and Ordering).
A naalp-description-import ({ importer, format, foreign, operations
}) carries a FOREIGN description format -- an A2A Agent Card, an ANP
Agent Description, or an AGNTCY Agent Badge, selected from the closed
naalp-description-format registry (a2a-agent-card 1, anp-agent-
description 2, agntcy-agent-badge 3; an unrecognized value is
UnknownDescriptionFormat) -- octet-for-octet in foreign (carriage,
not adoption: the foreign bytes MUST NOT be re-serialized,
canonicalized, or rewritten) as a signed attestation binding the
foreign bytes' content id AND an N-AALP effect mapping (operations)
for the described operations. importer is the wrapping signer id and
is the SOLE authorization identity: a verifier recomputes the self-
certifying signer id from the verifying key and requires importer to
equal it (ImporterMismatch otherwise), so a foreign identity embedded
in foreign NEVER becomes an N-AALP authorization identity -- the
confused-deputy rule the MCP profile above also applies. A malformed
description body is DescMalformed.
16.5. Name Bindings and A2A Task Transitions
Two receipt-CHAINED, OFFLINE-WALKABLE surfaces reusing the audit
receipt-chain construction unchanged: the head of each object is SHA-
384(body), the genesis prev is 48 zero bytes, seq is monotonic, and
the body carries the prior head in prev, so editing or omitting a
record breaks the next record's linkage.
Sammartano Expires 10 March 2027 [Page 30]
Internet-Draft N-AALP September 2026
A naalp-name-binding ({ name, signer, seq, prev }) maps a durable,
human-readable name to a signer id and chains onto the prior binding
for that name. A key ROTATION for a name is a NEW binding at the
next seq naming the new signer; a binding is dated by its chain
position (seq), not by the envelope's advisory created field. A
name's history is walkable offline; a deleted or omitted binding
leaves a detectable HOLE at the first-broken position, reported
NameChainBroken; two bindings by ONE authority at the SAME (name,
seq) naming DIFFERENT signers are a FORK, reported
NameForkProofInvalid at that seq. A malformed binding is
NameMalformed; a binding signed under a key that does not match the
claimed signer is VerifierKeyMismatch.
A naalp-task-transition ({ task, card, from, to, seq, prev }) is one
signed, receipt-chained A2A (Agent2Agent) task-lifecycle state
transition. The task-state set -- submitted (0), working (1), input-
required (2), auth-required (3), completed (4), canceled (5), failed
(6), rejected (7) -- is an IMPORTED vocabulary (carriage, not
adoption): the A2A specification Section 4.1.3 defines these eight
states and their terminal/interrupted categories NORMATIVELY (start =
submitted; terminal = {completed, canceled, failed, rejected};
interrupted = {input-required, auth-required}), and N-AALP's legal-
edge table is DERIVED from those documented category rules and
enforced by the endpoint, not by this wire production, which carries
only the from/to state values. card is the content id of the A2A
Agent Card attestation (a naalp-description-import with format a2a-
agent-card) binding the task profile to an agent/operation; a
transition carrying a card other than the profile's bound card is
rejected ForeignCard. An illegal edge, a non-contiguous from, a
transition out of a terminal state, or a gap or reorder in the chain
is rejected fail-closed IllegalTransition or TaskChainBroken, with
the violating position reported.
16.6. Governed Negotiation
Three signed surfaces reusing the closed effect lattice, the content-
id framing, and the causal partial order (causes) unchanged: offer,
counter, and accept are SIGNED, CAUSALLY-LINKED messages, each naming
its predecessor(s) by content id in causes (the same shape the causal
partial order uses). Each SELECTS a profile from the CLOSED, PRE-
REGISTERED negotiation-profile set -- baseline (0), streaming (1), or
batch (2) -- never a free-form capability string and never a runtime-
generated handler; an unknown profile is rejected UnknownProfile.
The three productions -- naalp-negotiation-offer ({ negotiation,
role, profile, causes }), naalp-negotiation-counter, and naalp-
negotiation-accept -- are distinguished by a fixed role literal from
the closed negotiation-role set (offer 0, counter 1, accept 2), so an
Sammartano Expires 10 March 2027 [Page 31]
Internet-Draft N-AALP September 2026
offer body never validates against the accept production and vice
versa; a role value outside the set, or a body whose fixed-literal
field disagrees with its production, is rejected UnknownRole
(NotOffer/NotAccept name the specific offer/accept-shape mismatch).
An offer is the root of the exchange and carries no causes; a counter
chains onto the offer or a prior counter; an accept MUST DESCEND from
its offer along the causes DAG, enforced by the endpoint
(NotDescended otherwise). A malformed negotiation body is
NegMalformed.
16.7. Advisory Risk Labels
A naalp-risk-label ({ code, critical }) carries one label: code
(risk-code, an open uint -- a closed standard vocabulary plus a
private/experimental extensible range, enforced by the endpoint and a
registry the same way the per-signer counter's value space is open, #
Object Model) and critical (risk-critical, the per-carriage must-
understand flag: 1 critical, 0 advisory -- the spine carries no CBOR
boolean). A naalp-labeled-object ({ effect, labels }) carries an
object's effect together with a set of risk labels. The critical-
extension rule (# Object Model) applies to labels the same way it
applies to ext/cext: an unknown CRITICAL label is rejected
UnknownCriticalRisk, and an unknown non-critical one is ignored; a
critical flag outside {0,1} is MalformedCriticalFlag. LOAD-BEARING
INVARIANT: a risk label is an ADVISORY dimension, NEVER a fifth
effect -- carrying a label never changes the object's effect class,
and the closed four-value effect lattice (# Effects and
Authorization) is untouched by this family.
16.8. Flow Continuations
A long-running flow costs exactly two full ML-DSA signatures --
FlowOpen and FlowCommit -- regardless of the number of intermediate
steps, generalizing native streaming (# Streaming) from a byte stream
to a flow of typed steps. naalp-flow-open ({ flow_id, effect_ceiling,
approvals }) is a fully signed naalp-object that fixes the flow's
effect ceiling and its approval bindings; its authority is
reconstructable from its own bytes alone. Its head (SHA-384 of the
body) anchors the chain, and its content id is carried by every child
object as flow_open_id; a continuation naming a different
flow_open_id than the one it chains from is rejected WrongFlow.
A naalp-continuation ({ flow_open_id, seq, effect, payload_id, prev
}) is a cheap, UNSIGNED hash-chain link: its head is SHA-384(body),
and prev is the previous link's head (the FlowOpen's own head for seq
0). Its effect MUST NOT exceed the FlowOpen's effect_ceiling, else
AboveCeiling. payload_id is the content id of that step's payload,
carried separately so the continuation itself stays small. A naalp-
Sammartano Expires 10 March 2027 [Page 32]
Internet-Draft N-AALP September 2026
flow-checkpoint ({ flow_open_id, through_seq, head }) confirms a
contiguous prefix of the chain through through_seq; a dropped or
reordered link short of that prefix is GapDetected. naalp-flow-commit
({ flow_open_id, final_head }) binds the FINAL chain head under the
second full signature, closing the flow; a missing or altered link
relative to the committed final_head is CommitMismatch. The four
body shapes (3, 5, 3, and 2 fields respectively) are structurally
distinct, so domain separation between them needs no additional tag.
16.9. Governed-Decision Records and Transparency Log Primitives
This family carries the accountability triple natively -- UNIQUE
SELECTION, GOVERNED-AT-T, and BINDING-FIXED-BY-T -- as three signed,
additive sibling productions; none moves naalp-version.
An ordering-disclosure ({ basis, ?boundary, ?mechanism, ?relation })
states what, if anything, establishes a record's order relative to
the event it concerns, and from which observational domain, via the
closed ordering-basis set: correspondence-only (0) -- the weakest
claim, ON PURPOSE, meaning a record that says nothing about ordering,
and an existing record whose ordering field is ABSENT, are BOTH read
as correspondence-only (a verifier MUST NEVER infer a stronger
ordering claim from silence); single-boundary (1) -- one covering
boundary (boundary, a signer id) attests the order; or external-
mechanism (2) -- mechanism names an external sequencing mechanism and
OPTIONAL relation binds this record under it (for example, the
content id of a naalp-checkpoint-root the record is included under).
Field presence is fail-closed and native: correspondence-only
requires keys 2-4 absent; single-boundary requires key 2 present and
3-4 absent; external-mechanism requires key 3 present (4 optional)
and 2 absent; any other combination is OrderingDisclosureMalformed.
Whether an external mechanism's operator is genuinely distinct from
both parties is a structural deployment fact checkable in substance
later, not a claim the wire itself can close. A term-disposition ({
kind, ?source }) reuses the producing-boundary kind codes (1
observed, 2 reported; # Object Model) to state whether one term of a
record was observed first-hand or is relayed from source; a code
outside {1,2} or a source present under observed is malformed.
enforcement-disposition is a closed pair: enforced (1) or advised
(2).
A naalp-decision-record ({ action, governing, ?consume, outcome,
ordering, ?terms, ?enforcement }) is the SIGNED record a governed
decision point emits stating that it decided about action under a
CLOSED, uniquely selected condition set. governing names that set in
the clear as content ids -- may be empty when the decision is
governed by standing policy alone, naming that policy object's
content id instead (UNIQUE SELECTION: identification and availability
Sammartano Expires 10 March 2027 [Page 33]
Internet-Draft N-AALP September 2026
together). The OPTIONAL consume names the naalp-consume-receipt that
spent the governing authority at decision time (GOVERNED-AT-T): it is
PRESENT for an allow that consumed a single-use authority and MUST be
ABSENT for deny/hold, since a refusal consumes nothing (a deny/hold
body carrying a consume reference is DecisionMalformed). outcome
reuses the closed gw-decision set (# Security Considerations).
ordering is MANDATORY -- the record states its ordering basis or
states correspondence-only; there is no silent default, and an
unrecognized basis value is UnknownOrderingBasis. The OPTIONAL terms
map keys per-term dispositions by THIS record's own field numbers
1..5 (a key outside that set is TermDispositionMalformed), and the
OPTIONAL enforcement states whether the producer enforces the outcome
or only advises it -- the producer's own unverifiable self-account.
The record is deliberately CLOCK-FREE: it carries no claimed time,
and both time properties above are POSITIONAL, never a self-asserted
timestamp; the signature binds THE DECISION, not a retrieval of it.
A naalp-checkpoint-root ({ log, size, root, prev, at }, an RFC
9162-style construction) is a log operator's SIGNED Merkle tree head
over a leaf set of record content ids -- the NEITHER-PARTY ANCHOR
PRIMITIVE (BINDING-FIXED-BY-T). Tree construction follows [RFC9162]
Section 2.1: leaf hash = HASH(0x00 || leaf), interior node =
HASH(0x01 || left || right), instantiated with SHA-384 (48-byte
heads); the 0x00/0x01 prefixes supply leaf/node domain separation.
Checkpoints chain by prev (genesis 48 zero bytes), so a withheld or
reordered checkpoint breaks a link, reported CheckpointMalformed on a
structurally invalid checkpoint. A naalp-witness-cosign ({ witness,
root, at }) COUNTERSIGNS one exact checkpoint by content id; the wire
carries the cosignature, while whether the witness's observational
domain is genuinely distinct from both parties is a structural
deployment fact a later verifier checks in substance -- the wire hook
for the neither-party property, stated honestly as a hook rather than
a guarantee. A cosignature naming a root that does not match the
checkpoint it purports to cover is WitnessRootMismatch.
A naalp-inclusion-proof ({ root, leaf, index, path }) proves that a
leaf existed in the tree a named naalp-checkpoint-root commits to
(RFC 9162 Section 2.1.3.1 path recomputation, SHA-384 profiled):
recording a content id as a leaf under a witnessed checkpoint
establishes existed-no-later-than-the-checkpoint, closing the
BINDING-FIXED-BY-T leg. Verification recomputes the path bottom-up
from leaf at position index using the sibling hashes in path and
compares the result against root, fail-closed InclusionProofInvalid
on any mismatch.
Sammartano Expires 10 March 2027 [Page 34]
Internet-Draft N-AALP September 2026
16.10. Portable Egress Evidence
A naalp-egress-attestation ({ binding, digest, effect, audience, at,
?ordering }) is a SIGNED attestation a gateway or sidecar emits that
an object of a given effect class, bound to a given audience, crossed
an egress boundary at a given time -- third-party re-serve, payload-
free. binding selects one of the closed egress-binding values:
content-bound (0), where digest is the crossed object's own content
id, or content-free (1), where digest is a hiding commitment SHA-
384(content_id || salt), openable later only by the gateway revealing
the content id and salt out of band. effect is the crossed object's
effect class and audience is its bound destination (empty-permitted).
The OPTIONAL ordering (an ordering-disclosure above) states the
attestation's ordering basis; its absence is read correspondence-
only, never a stronger claim. A malformed attestation is
EgMalformed; an unrecognized binding value is UnknownEgressBinding.
16.11. Manufacturing Physical-Hazard Claims and Authorizations
hazard is a dimension ORTHOGONAL to effect: effect (# Effects and
Authorization) is DATA reversibility (can the state change be
undone); hazard is PHYSICAL danger -- a data-reversible action may
still be a high physical hazard (for example, a tool re-approaching a
work envelope). The two are never merged, and neither is derived
from the other. A hazard CLAIM (naalp-hazard-claim) rides as the
critical cext key 16 on the acting object (# Object Model); a hazard
AUTHORIZATION (naalp-hazard-authorization) is a standalone
Governance-channel (0x0004) kind 7 object, referenced by the acting
object's causes. All numeric bounds in this family are fixed-point
integers (signed millimeters for position, unsigned millimeters-per-
second for speed) -- no floats, per the spine's deterministic-CBOR
subset (# Object Model) -- and map keys are 1-based.
hazard-class is a CLOSED 0..4 enum: none (0), tool-actuation (1),
thermal (2), energy-release (3), motion-in-shared-space (4). This
closed set carries a DECODE-TIME rule mirroring effect's unknown-
value handling: an absent or unrecognized raw hazard value MUST
normalize to the HIGHEST class (4), never to none or a weaker value,
so a missing declaration fails safe (HazardUnknown when no claim
exists at all; else HazardNotCovered against a lower-class grant) --
a rule CDDL cannot itself express, since it governs the absence or
invalidity of a raw wire value, so it is stated here as a MUST on the
decoder. spatial-bounds ({ frame, axes }) names a coordinate frame
(frame, which MUST be Unicode NFC, NonNFC otherwise) plus a signed
axis-aligned bounding region in that frame: axes MUST be non-empty
and every [min, max] entry MUST satisfy min <= max, else
HazardMalformed. The frame id is integrator-defined; N-AALP requires
only that a claim's frame id equal the authorization's for
Sammartano Expires 10 March 2027 [Page 35]
Internet-Draft N-AALP September 2026
containment to be checkable. hazard-window ({ not_before, not_after
}) is a validity window in the spine's epoch-ms convention. hazard-
envelope ({ spatial, speed_bound, window }) is the full physical
envelope a claim or authorization bounds itself by; all three fields
are MANDATORY -- a silently absent axis, speed, or window is a
MALFORMED envelope (HazardMalformed), never treated as
"unconstrained," which would fail OPEN in a physical-safety context.
naalp-hazard-claim ({ class, envelope }) and naalp-hazard-
authorization ({ class, envelope }) share the identical { hazard-
class, hazard-envelope } shape; both fields are mandatory in each,
else HazardMalformed. Coverage of a claim by an authorization
requires an EXACT class match AND full containment of the claim's
envelope in the authorization's: the frame id equal; the same axis
count and order; every claim axis inside the matching authorization
axis; the claim's speed_bound no greater than the authorization's;
and the claim's window a sub-interval of the authorization's window.
Any single failing dimension denies the WHOLE claim
(HazardNotCovered) -- there is no partial authorization.
17. Security Considerations
Mandatory-to-implement algorithms and post-quantum rationale: N-AALP
signs with ML-DSA [FIPS204], a post-quantum signature, because N-AALP
objects (receipts, approvals, audit chains) are long-lived non-
repudiable records subject to store-now-verify-later forgery risk
from a future cryptographically relevant quantum computer; a
classical-only signature on such records would be a latent forgery
exposure. An optional Ed25519+ML-DSA composite provides defense-in-
depth during the transition; it is a single non-separable LAMPS
composite, so it is accepted only when both components verify and a
stripped object has no valid signature at all. Pure ML-DSA is SUF-
CMA; the opt-in composite is EUF-CMA but not SUF-CMA, which is not
load-bearing because N-AALP never keys a security decision off raw
signature bytes.
Downgrade and negotiation: algorithm agility is expressed by the COSE
algorithm identifier bound under the signature and checked against
the profile floor; a signature below the floor is rejected, so
agility cannot become a downgrade.
Replay and reordering: approvals bind a nonce and expiry and are
single-use through the consume ledger; the audit receipt chain binds
each object into a hash chain so reordering, omission, and
substitution are detectable; delivery stages are monotonic.
Sammartano Expires 10 March 2027 [Page 36]
Internet-Draft N-AALP September 2026
Identity and trust: the signer id is a pure function of the public
key, so a forged id cannot recompute; there is no certificate
authority to compromise. Compromise of a signing key is bounded by
rotation and revocation, and attribution survives rotation. A non-
injective or forgeable identity function would collapse this
property; the SHA-256 multihash over the multicodec-tagged key
provides collision and second-preimage resistance.
External code-point stability: the key-type codes 0xed, 0x1211, and
0x1212 that signer-id derivation uses (# Identity) originate in the
external multiformats table, a registry N-AALP does not govern. If
that upstream table ever reassigned one of these three codes to a
different meaning, an implementation that resolved mc by looking the
key type up in the live table would derive a different signer id than
an implementation that used the value current when it was built,
silently splitting the identity space. N-AALP closes this dependency
by pinning all three values as its own normative constants (#
Identity) rather than by indirection through the upstream table, so
signer-id derivation stays fixed under this specification regardless
of any later upstream reassignment.
Effect and authorization: the effect is an authorization input, not a
hint; an unrecognized effect fails closed to destructive;
authorization is never derived from transport metadata or a foreign
principal. This closes the gap a pure intent label leaves open.
Effect inflation as an availability risk (accepted and stated): the
effect lattice has an abuse direction. A malicious wrapping signer
can mark ordinary actions destructive so that everything demands an
approval and throughput collapses, and attribution identifies who did
it only after the fact. N-AALP does not prevent this on the wire and
does not pretend the lattice has no abuse direction; it is an
accepted risk, mitigated operationally by attribution -- every object
binds to a signer id -- together with per-signer rate limiting
applied by the deployment. Rate limiting is a deployment control
rather than a wire field, stated here as an operational obligation,
not a format guarantee.
Continuation compromise, honest limit: a node compromised inside an
open flow can keep emitting continuations up to the ceiling. The
ceiling bounds the damage. It does not prevent it. The flow's
effect ceiling caps the severity any continuation can reach, so the
damage is bounded to actions within that ceiling; but until the flow
is closed or the compromised key is revoked, the compromised node
keeps acting within it. This limit is stated plainly here rather
than left for a reviewer to infer.
Sammartano Expires 10 March 2027 [Page 37]
Internet-Draft N-AALP September 2026
Denial of service: an object requires one signature verification and
one deterministic decode; verification is fail-closed and performed
before any state change or effect. Streaming amortizes one signature
over many chunks. Implementations SHOULD bound object and stream
sizes by policy.
Cost-ordered verification (cheapest checks first): a verifier SHALL
perform its checks in increasing order of cost and reject on the
cheapest failing check before it performs the signature verification.
The cheap checks -- deterministic decode, content-id recomputation,
field-range checks, protected-header/body agreement and version,
critical-extension recognition, and kind/channel dispatch -- all run
before the ML-DSA signature verification, which is the most expensive
step and is therefore last. Because the expensive step is last, a
flood of mismatched or malformed objects is rejected on a cheap check
and cannot force a signature verification on attacker-controlled
bytes; the cost of rejecting garbage is bounded by the cheap checks,
not by the post-quantum signature.
Confidentiality: object-level guarantees do not include
confidentiality; a sensitive object MUST use a confidential transport
(# Transport Bindings), enforced by refusal.
Offline verification proves validity-at-issue, not current unspent-
ness: a signature verified offline proves an approval was well-formed
and authorized when it was issued; it does NOT prove the approval is
still unspent. Spent-or-unspent is state held at the consume ledger
(# Approval), not a property of the object's bytes. A verifier that
treats a syntactically valid, unexpired approval as spendable without
consulting the ledger has confused attributability at the moment of
issue with current spendability.
Exposure and reconciliation cadence: a relying party that accepts a
consume receipt during a network partition, before that receipt has
been compared against the receipt sets held by other parties, is
exposed to a conflicting spend it cannot yet see. Its exposure is a
function of its own reconciliation cadence -- the window between
accepting a receipt and next comparing its receipt set against the
others -- multiplied by the value at risk in that window (the rate,
in value per unit time, at which double-spendable value is being
accepted). N-AALP makes no liveness promise here: the format does
not bound how quickly a conflicting spend elsewhere becomes visible,
and it is the relying party, not the format, that lowers its exposure
by reconciling more often. Cadence is a parameter the exposed party
sets, not a guarantee the format provides.
Sammartano Expires 10 March 2027 [Page 38]
Internet-Draft N-AALP September 2026
Worked exposure example: a party reconciles on a 3,600-second (one
hour) cadence and, within one such window, accepts approvals whose
combined double-spendable value arrives at a value at risk of $5,000
per second. Its worst-case exposure across one unreconciled window
is (value at risk) x (window length) = $5,000/s x 3,600 s =
$18,000,000 that could be double-spent before the next comparison
detects the conflict. Cutting the reconciliation cadence to 60
seconds cuts the window length, and the exposure with it, by 60x, to
$300,000. The exposure figure is set entirely by the party's own
cadence and value at risk; the object bytes do not change, and the
protocol computes none of it.
Baseline consume under an unreachable ledger: at the baseline tier,
an executor that cannot reach the consume ledger SHALL deny the spend
(this is the normative rule of (# Approval), restated here for its
security weight). Local spend-and-reconcile-later is not a baseline
behavior; a consumer that spends locally while the ledger is
unreachable is non-conforming. This is the fail-closed choice --
under partition the baseline refuses rather than risk an undetected
double spend -- and federated ordering over identical signed objects,
which trades this refusal for the bounded exposure modeled above, is
a named higher tier only.
Attributable versus checkable: a signature makes a statement
attributable to a signer -- it binds the bytes to a key -- but
attributability is not the same as checkability. A statement is
checkable only if a stranger who trusts no one can re-derive the
claim for themselves. A checkable claim (a) says what it is about,
(b) carries the evidence it rests on, directly or by content id, and
(c) names the procedure that re-checks it. N-AALP already delivers
(a) and (b) for its structural claims: the content id recomputes from
the bytes, so anyone can confirm the id names these exact bytes, and
the causes field is a signed partial order a stranger can walk
offline. An object that carries a body claim without naming the
procedure that re-checks it is attributable only, and this
specification says so plainly: absent (c), a relying party has the
signer's word, not an independent re-derivation.
The per-signer forward-only counter is detection, not prevention:
N-AALP MAY define a forward-only per-signer counter that a signer
increments on each object. Its purpose is detection, not prevention:
it exists to detect key duplication, not to stop it. A counter a
signer writes about itself proves nothing on its own, because once a
key is duplicated both the legitimate holder and the thief emit
locally consistent, monotonic sequences, and neither sequence
contradicts the other in isolation. The duplication becomes provable
only when two conflicting sequences bearing the same signer id
physically meet somewhere the attacker cannot suppress; until that
Sammartano Expires 10 March 2027 [Page 39]
Internet-Draft N-AALP September 2026
meeting the counter has detected nothing. The counter's value is
therefore contingent on a reachability property the wire cannot
guarantee -- that the conflicting evidence reaches a common observer
-- and this specification states that dependency rather than implying
the counter prevents duplication. The sharper contradiction,
authored by neither the requester nor a thief, is carried by the
ledger-signed consume receipt (# Approval), not by the self-authored
counter.
Correspondence is not precedence: the causal graph (# Audit, Causal
Graph, and Ordering) and parent-by-content-id let a stranger
establish record order offline -- an object that names another as a
cause existed no later than its effect, within the two-party
construction that produced them. That is not cross-trust-boundary
event precedence. Each boundary's observational domain is
authoritative only within itself, so ordering two boundaries' events
against each other requires an ordering authority whose observational
domain is neither party; the baseline single-authority ordering (#
Audit, Causal Graph, and Ordering) does not guarantee an authority
that is neither party, so cross-boundary precedence is a higher-tier
or deployment property, not a baseline guarantee. The OPTIONAL
producing-boundary disclosure (ext key 15, # Object Model) makes the
one fact an object can honestly assert explicit -- whose domain
observed the event, and whether first-hand or relayed -- and a
relying party that needs precedence across a boundary composes it
with an independent ordering authority. One such construction is a
Transparency Service as defined in [RFC9943], whose own Section 9.1
(Ordering of Signed Statements) states that a relying party cannot
assume the registration order matches the issuance order unless the
service's registration policy advertises it, and whose Section 9.2
(Accuracy of Statements) states that registering a statement only
proves it was produced, not that it is accurate -- the same limit,
stated by an independent standard.
Sammartano Expires 10 March 2027 [Page 40]
Internet-Draft N-AALP September 2026
Custody continuity and rotation dating: a valid signature proves the
signer held the private seed at signing time and nothing more; it
does not prove continuous custody. draft-01 does not solve rotation-
continuity, because a successor (rotation) statement signed by an old
key can be forged by a thief who holds that old key, and the forged
handoff verifies. What the design provides instead is dating after
the fact. The created timestamp (field 6) is advisory only and MUST
NOT be relied on as ordering evidence; the receipt-chain position is
the record of when a statement happened (# Audit, Causal Graph, and
Ordering). A rotation statement is dated by where it lands in the
receipt chain, the same as any other object. On discovery of a theft
there is a specific receipt position to cut at -- a cut position --
before which the history is still provable from the original
signatures. Because the mandatory signatures are post-quantum (ML-
DSA), the pre-cut history stays checkable for years after the
compromised keys are gone.
Worked custody timeline: at receipt position 100 a key K is used
legitimately. At position 140 the key is stolen and the thief
immediately signs a rotation from K to the thief's own key K'. At
position 200 the theft is discovered. The relying party sets the cut
position at 140 -- the earliest position it can attribute to the
thief, or, where that is uncertain, the last position it can vouch
for. Every object at receipt position below 140 remains verifiable
from its original ML-DSA signature and its receipt-chain position,
independent of K's later compromise; objects at position 140 and
after, including the forged rotation, fall outside the cut and are
not trusted on K's authority. The forged handoff still verified as
bytes -- dating does not prevent it -- but the receipt-chain position
gives the relying party a defined cut position and a pre-cut history
it can still stand on.
The never-signs-again case: if the legitimate holder never signs
again after a theft, the case is undecidable from the signer's own
bytes: as far as the signer's own signatures can prove, the thief is
that identity from the moment of theft onward, because every
subsequent signature verifies under the stolen key and there is no
later legitimate signature to contradict it. Anything that resolves
this works from statements other parties make -- counterparty
acknowledgments, obligations that settled, an observer outside the
thief's reach -- and N-AALP carries exactly those as checkable signed
objects. The weighing of that outside evidence is left to the
relying party, to whoever decides to transact, and is not a property
the wire computes. N-AALP does not pick a trust graph: this revision
deliberately encodes no trust-weighting scheme into the object,
keeping the line between what the protocol carries -- checkable
evidence, and refusal when required evidence is missing -- and what
the deployment decides -- who to believe -- where it belongs.
Sammartano Expires 10 March 2027 [Page 41]
Internet-Draft N-AALP September 2026
Third-party trust statements are carriage, not adjudication: N-AALP
carries third-party trust statements -- attestations, reputation
assertions, and external-registry references such as an ERC-
8004-style identity or reputation registry record -- as checkable
signed objects, each naming its subject and carrying the external
record it references by content id, so a relying party re-derives the
reference for itself rather than trusting the connection that
delivered it. The wire carries these statements and weighs none of
them: no wire field scores a trust statement, ranks two conflicting
attestations, or selects among registries, because the protocol takes
no position on which trust statement outranks which. A carried
reputation or registry reference therefore verifies -- its content-id
recomputes and its signature checks -- without the protocol computing
any score from it; which statement to believe remains a decision for
the deployment, not one the wire performs.
Payment import, user-interface consent, and portable gateway evidence
add guarantees around imported and human-in-the-loop actions without
introducing a new effect, ledger, or policy mechanism. A payment
instruction issued in a foreign format -- an AP2 mandate, an Agentic
Commerce Protocol delegated token, an x402 payload -- is carried
octet-for-octet and imported as a single-use approval: imported
payment instructions become single-use approvals bound to the amount,
currency, payee, and the carried payload's content id, so a wrong
amount or payee no longer matches the approval and a replayed payment
authorization is rejected by the consume ledger. The imported format
(naalp-payment-format) is one of a closed, registered set -- an AP2
mandate, an Agentic Commerce Protocol delegated token, or an x402
payload -- and an unrecognized format is rejected
UnknownPaymentFormat; the value an approval binds (naalp-payment-
charge-binding) carries the same fields as the import (naalp-payment-
import) with the foreign payload replaced by its content id, so the
binding names the exact value approved without re-embedding the
foreign bytes. A user-interface approval binds the exact action
shown by content id, with the shown tool-lifecycle events (naalp-ui-
event) receipt-chained so the shown sequence is provable and an
omitted event is detected at its position; an action other than the
one shown and approved has a different content id and is refused.
The shown-event stream is one of a closed vocabulary -- shown, args-
shown, approved, or rejected (ui-event-kind) -- and an event outside
that set is rejected UnknownUIEventKind; a malformed event body is
UIMalformed and a broken receipt chain is UIChainBroken. A gateway
decision (naalp-gateway-decision) is portable evidence whose
authority is the signature over its bytes, not the connection that
delivered it, so it re-verifies identically when a party other than
the gateway serves it; it carries the decision, the action it is
about, and the deciding policy's identity as an opaque name, and
defines no policy language. The decision is one of a closed
Sammartano Expires 10 March 2027 [Page 42]
Internet-Draft N-AALP September 2026
vocabulary -- allow, deny, or hold (gw-decision); an unrecognized
value is rejected UnknownGatewayDecision, and a malformed body is
GwMalformed. A gateway decision MAY additionally carry an ordering
disclosure (# Additive Object Families) and, where the decision was
made over foreign-protocol evidence, a naalp-foreign-profile-pin
naming the foreign evidence profile's identifier and the specific
revision pinned at decision time, so a later revision of that foreign
profile cannot be silently substituted for the one the decision
actually evaluated.
Signing outside the page context (browser and WebMCP bindings): where
N-AALP signing is exposed to a web page -- for example a WebMCP-style
in-browser binding -- the signing operation MUST be performed outside
the page script context, in an isolated signer (a browser-extension
background context, a distinct origin, or a platform key store) that
the page cannot script. The page MAY request a signature over a
named object but never holds the signing key or the signing routine,
so a compromised or malicious page can request but never forge a
signature and cannot exfiltrate the key. A binding that signs inside
the page script context is non-conforming.
What N-AALP does NOT defend against: it does not provide
confidentiality by itself (that is the transport's); it cannot force
an ordering authority to deliver events it chooses to withhold (a
chain reveals equivocation and omission-of-known-events but cannot
compel delivery); it does not provide cross-trust-boundary event
precedence at the baseline tier (the causal graph proves record
order, not event precedence across trust boundaries -- see the
correspondence-is-not-precedence discussion above); it does not
police the semantic correctness of a carried foreign message beyond
octet-exact carriage; it does not prove an offline-verified approval
is still unspent (that is consume-ledger state, bounded by the
reconciliation cadence and exposure model above); it does not make a
self-authored per-signer counter proof of anything until two
conflicting sequences meet where the attacker cannot suppress them;
it does not solve rotation-continuity or the never-signs-again theft
from the signer's own bytes (it provides dating and a cut position,
not prevention); and it does not defend against a signer that is
itself authorized and malicious (it makes that signer's actions
attributable and auditable, not impossible).
Sammartano Expires 10 March 2027 [Page 43]
Internet-Draft N-AALP September 2026
17.1. Trust-decision closure sovereignty
A relying party's decision to act on an authenticated party's object
is trustworthy only when that decision depends only on inputs outside
the authenticated party's influence -- transitively, so that an
input's own inputs are inputs. Where the authenticated party does
influence an input, that influence MUST take one of exactly three
safe shapes: verifiable, attenuating, or committed. A verifiable
influence is a fixed function of bytes the party cannot forge and is
recomputed by the relying party; an attenuating influence can only
reduce the party's own authority; a committed influence is mixed with
an independent contribution the party cannot bias. An input safe in
none of the three, and not otherwise checkable, is treated as
unverified and MUST fail closed. This property is already enforced
across N-AALP's identity, encoding, conformance, delegation,
approval, and negotiation requirements; the paragraphs below close
the remaining inputs.
Verification MUST be deterministic: an implementation performs
verification as deterministic code whose control flow does not depend
on unverified content, and no part of an object under verification
reaches a model or inference component before it is deterministically
accepted. This keeps an agent-native relying party -- one whose
evaluator is itself a model -- from having its trust decision steered
by the very bytes it is judging.
A refusal or deny outcome is a signed, attributable, fail-closed
record, and a refusal returns only a coarse outcome to the
authenticated party -- a single value from a closed vocabulary
together with the content id of the signed record, and nothing more.
Discriminating detail is carried only in the signed record an auditor
reads, so that repeated refusals cannot serve an adaptive party as an
oracle. A party-visible outcome that carries discriminating detail,
or omits the record content id, is a refusal detail leak.
A standalone judgment that a credential is in force at the present
moment rests on time the party cannot supply. A credential's
present-moment freshness is judged against a clock the authenticated
party does not provide, and the ordering authority is structurally
distinct from the party being authenticated, so the party whose
credential would expire is never itself the source of the time
against which expiry is judged.
Where a verdict or approval travels beyond the context that produced
it, a portable verdict names the context in which it is valid, and a
relying party checks that named context at use, so a verdict sound
for one context cannot be replayed into another the party chose.
Naming a context is optional -- an approval that names no context is
Sammartano Expires 10 March 2027 [Page 44]
Internet-Draft N-AALP September 2026
unrestricted by the issuer's choice, and a deployment MAY require one
by local policy -- but the check is mandatory when a context is
present.
Delegated authority only ever shrinks: derived authority is non-
increasing across every hop of a delegation chain, on effect, scope,
and depth, whether the chain is read leaf-to-root or root-to-leaf; a
hop that would raise authority denies the whole chain.
18. Privacy Considerations
N-AALP objects are signed and content-addressed, and are often long-
lived, which creates two privacy tensions the format addresses
explicitly: correlation of identical content, and erasure of personal
data from records that are immutable by design.
Salted body option: because the content id is a deterministic hash of
the body, two objects with identical logical content produce an
identical content id and are trivially correlatable across contexts.
N-AALP therefore offers an optional body salt so that identical
logical content does not yield a correlatable content id; a signer
that must avoid cross-context linkage includes a fresh random salt in
the body, carried under the signature like any other field.
Hash-only personal data: an object that must reference a person
SHOULD NOT carry the personal data in the clear. Personal data is
carried only as a salted one-way hash of the datum, so the object
binds to the person without disclosing the identifier; the plaintext
and its salt are held off the record by the party that needs them.
Erasure resolution against immutable records: a signed, hash-chained
record cannot have bytes removed without breaking the chain, which
appears to conflict with an erasure (right-to-be-forgotten)
obligation. N-AALP resolves this without mutating the signed record:
because personal data is present only as a salted hash, erasure is
resolved by destroying the off-chain preimage and its salt, leaving
the signed record intact but its hashed personal data unlinkable to
any person. The signature and the audit chain stay valid; what is
destroyed is the ability to reverse the hash to a person, which is
exactly the property erasure requires.
Sammartano Expires 10 March 2027 [Page 45]
Internet-Draft N-AALP September 2026
19. IANA Considerations
This document is an Independent Submission. All registries requested
below use registration policies permitted on the Independent stream,
requiring no IETF Review or Standards Action: the media type
registers into the existing IANA Media Types registry under [RFC6838]
(that registry's Expert Review), and each new registry this document
creates uses RFC Required (First Come First Served in the standards
range) per [RFC8126] -- an ISE-permissible policy. Numeric values
shown are the values this specification defines; where IANA
assignment is requested the placeholder TBD is used.
19.1. Media type application/vnd.bubblefish.naalp+cbor
IANA is requested to register the following media type in the vendor
tree ([RFC6838] Section 3.2), using the +cbor structured syntax
suffix defined in [RFC8949] and registered in the IANA Structured
Syntax Suffixes registry (whose mechanism is established by [RFC6838]
and populated by [RFC6839]). The vnd.bubblefish. facet designates
BubbleFish Technologies, Inc. as the producing organization; a
vendor-tree registration is submitted directly to IANA and undergoes
Expert Review ([RFC6838] Section 3.2), requiring no IESG approval or
IETF standards action:
* Type name: application
* Subtype name: vnd.bubblefish.naalp+cbor
* Required parameters: none
* Optional parameters: none
* Encoding considerations: binary (CBOR per [RFC8949])
* Security considerations: see (# Security Considerations) of this
document
* Interoperability considerations: objects are deterministic CBOR;
see (# Object Model)
* Published specification: this document
* Applications that use this media type: autonomous-agent
application-layer messaging
* Fragment identifier considerations: as specified for application/
cbor
Sammartano Expires 10 March 2027 [Page 46]
Internet-Draft N-AALP September 2026
* Additional information: Magic number(s): none; File extension(s):
.naalp; Macintosh file type code(s): none
* Person & email address to contact for further information: the
author (front matter)
* Intended usage: COMMON
* Restrictions on usage: none
* Author: S. Sammartano
* Change controller: the author (BubbleFish Technologies, Inc.)
19.2. N-AALP Channel registry
IANA is requested to create the "N-AALP Channels" registry.
Registration policy: RFC Required (First Come First Served in the
standards range) -- an ISE-permissible policy; a successor RFC
provides the stable public specification and a non-colliding Channel
Id. Columns: Channel Id (uint 0..19), Name, Reference. Initial
contents: the twenty channels of (# Channel Surfaces), ids
0x0000..0x0013, this document.
19.3. N-AALP Object Kind registries
IANA is requested to create, per channel, an "N-AALP Object Kinds
(channel N)" registry. Registration policy: RFC Required (First Come
First Served in the standards range) -- an ISE-permissible policy.
Columns: Kind Code (uint), Name, Effect (one of the four closed
effect names, or "variable"), Reference. The initial contents are
the sixty-five baseline kinds below, enumerated here (channel, code,
kind, effect) so an implementer building from only this document has
the complete set:
+================+======+===================+======================+
| Channel | Code | Kind | Effect |
+================+======+===================+======================+
| 0x0000 Control | 0 | Hello | read_only |
+----------------+------+-------------------+----------------------+
| 0x0000 Control | 1 | Bye | idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x0000 Control | 2 | Ack | read_only |
+----------------+------+-------------------+----------------------+
| 0x0000 Control | 3 | Error | read_only |
+----------------+------+-------------------+----------------------+
| 0x0001 Memory | 0 | MemoryOffer | idempotent_write |
+----------------+------+-------------------+----------------------+
Sammartano Expires 10 March 2027 [Page 47]
Internet-Draft N-AALP September 2026
| 0x0001 Memory | 1 | MemoryAccept | idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x0001 Memory | 2 | MemoryWrite | non_idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x0001 Memory | 3 | MemoryRead | read_only |
+----------------+------+-------------------+----------------------+
| 0x0001 Memory | 4 | MemoryExpire | destructive |
+----------------+------+-------------------+----------------------+
| 0x0001 Memory | 5 | MemoryRevoke | destructive |
+----------------+------+-------------------+----------------------+
| 0x0002 | 0 | CapIssue | non_idempotent_write |
| Capability | | | |
+----------------+------+-------------------+----------------------+
| 0x0002 | 1 | CapDelegate | non_idempotent_write |
| Capability | | | |
+----------------+------+-------------------+----------------------+
| 0x0002 | 2 | CapRevoke | destructive |
| Capability | | | |
+----------------+------+-------------------+----------------------+
| 0x0002 | 3 | CapLookup | read_only |
| Capability | | | |
+----------------+------+-------------------+----------------------+
| 0x0003 | 0 | Rotation | non_idempotent_write |
| Identity | | | |
+----------------+------+-------------------+----------------------+
| 0x0003 | 1 | Revocation | destructive |
| Identity | | | |
+----------------+------+-------------------+----------------------+
| 0x0003 | 2 | ForeignLink | idempotent_write |
| Identity | | | |
+----------------+------+-------------------+----------------------+
| 0x0003 | 3 | KeyAnnounce | read_only |
| Identity | | | |
+----------------+------+-------------------+----------------------+
| 0x0004 | 0 | PolicyPublish | non_idempotent_write |
| Governance | | | |
+----------------+------+-------------------+----------------------+
| 0x0004 | 1 | Approval | non_idempotent_write |
| Governance | | | |
+----------------+------+-------------------+----------------------+
| 0x0004 | 2 | ApprovalHeld | read_only |
| Governance | | | |
+----------------+------+-------------------+----------------------+
| 0x0004 | 3 | Consume | non_idempotent_write |
| Governance | | | |
+----------------+------+-------------------+----------------------+
| 0x0005 Immune | 0 | AnomalyReport | read_only |
+----------------+------+-------------------+----------------------+
Sammartano Expires 10 March 2027 [Page 48]
Internet-Draft N-AALP September 2026
| 0x0005 Immune | 1 | Quarantine | destructive |
+----------------+------+-------------------+----------------------+
| 0x0005 Immune | 2 | QuarantineLift | non_idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x0006 | 0 | AuthorityAnnounce | read_only |
| Federation | | | |
+----------------+------+-------------------+----------------------+
| 0x0006 | 1 | ScopeReceipt | non_idempotent_write |
| Federation | | | |
+----------------+------+-------------------+----------------------+
| 0x0007 | 0 | SettleIntent | non_idempotent_write |
| Settlement | | | |
+----------------+------+-------------------+----------------------+
| 0x0007 | 1 | SettleReceipt | non_idempotent_write |
| Settlement | | | |
+----------------+------+-------------------+----------------------+
| 0x0007 | 2 | SettleReject | idempotent_write |
| Settlement | | | |
+----------------+------+-------------------+----------------------+
| 0x0008 | 0 | ComplianceRecord | non_idempotent_write |
| Compliance | | | |
+----------------+------+-------------------+----------------------+
| 0x0008 | 1 | ComplianceQuery | read_only |
| Compliance | | | |
+----------------+------+-------------------+----------------------+
| 0x0008 | 2 | ComplianceReport | read_only |
| Compliance | | | |
+----------------+------+-------------------+----------------------+
| 0x0009 Sensory | 0 | Observation | read_only |
+----------------+------+-------------------+----------------------+
| 0x0009 Sensory | 1 | Subscribe | idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x0009 Sensory | 2 | Unsubscribe | idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x000A | 0 | Metric | read_only |
| Telemetry | | | |
+----------------+------+-------------------+----------------------+
| 0x000A | 1 | HealthReport | read_only |
| Telemetry | | | |
+----------------+------+-------------------+----------------------+
| 0x000B Audit | 0 | Receipt | non_idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x000B Audit | 1 | AuditQuery | read_only |
+----------------+------+-------------------+----------------------+
| 0x000B Audit | 2 | ForkProof | read_only |
+----------------+------+-------------------+----------------------+
| 0x000C Stream | 0 | StreamOpen | variable |
+----------------+------+-------------------+----------------------+
Sammartano Expires 10 March 2027 [Page 49]
Internet-Draft N-AALP September 2026
| 0x000C Stream | 1 | StreamCommit | read_only |
+----------------+------+-------------------+----------------------+
| 0x000C Stream | 2 | StreamCheckpoint | read_only |
+----------------+------+-------------------+----------------------+
| 0x000D Bridge | 0 | Carriage | variable |
+----------------+------+-------------------+----------------------+
| 0x000E | 0 | Offer | read_only |
| Commerce | | | |
+----------------+------+-------------------+----------------------+
| 0x000E | 1 | Order | non_idempotent_write |
| Commerce | | | |
+----------------+------+-------------------+----------------------+
| 0x000E | 2 | Fulfil | non_idempotent_write |
| Commerce | | | |
+----------------+------+-------------------+----------------------+
| 0x000E | 3 | Cancel | destructive |
| Commerce | | | |
+----------------+------+-------------------+----------------------+
| 0x000F | 0 | Elicit | read_only |
| Interaction | | | |
+----------------+------+-------------------+----------------------+
| 0x000F | 1 | Respond | idempotent_write |
| Interaction | | | |
+----------------+------+-------------------+----------------------+
| 0x000F | 2 | Confirm | non_idempotent_write |
| Interaction | | | |
+----------------+------+-------------------+----------------------+
| 0x0010 | 0 | DiscoveryRecord | read_only |
| Discovery | | | |
+----------------+------+-------------------+----------------------+
| 0x0010 | 1 | DiscoveryQuery | read_only |
| Discovery | | | |
+----------------+------+-------------------+----------------------+
| 0x0011 | 0 | TaskCreate | non_idempotent_write |
| Workflow | | | |
+----------------+------+-------------------+----------------------+
| 0x0011 | 1 | TaskInput | non_idempotent_write |
| Workflow | | | |
+----------------+------+-------------------+----------------------+
| 0x0011 | 2 | TaskCancel | destructive |
| Workflow | | | |
+----------------+------+-------------------+----------------------+
| 0x0011 | 3 | TaskResult | non_idempotent_write |
| Workflow | | | |
+----------------+------+-------------------+----------------------+
| 0x0012 | 0 | Assert | non_idempotent_write |
| Knowledge | | | |
+----------------+------+-------------------+----------------------+
Sammartano Expires 10 March 2027 [Page 50]
Internet-Draft N-AALP September 2026
| 0x0012 | 1 | Retract | destructive |
| Knowledge | | | |
+----------------+------+-------------------+----------------------+
| 0x0012 | 2 | KnowledgeQuery | read_only |
| Knowledge | | | |
+----------------+------+-------------------+----------------------+
| 0x0013 Spatial | 0 | FrameDefine | idempotent_write |
+----------------+------+-------------------+----------------------+
| 0x0013 Spatial | 1 | Pose | read_only |
+----------------+------+-------------------+----------------------+
| 0x0013 Spatial | 2 | StateUpdate | read_only |
+----------------+------+-------------------+----------------------+
| 0x0013 Spatial | 3 | SnapshotQuery | read_only |
+----------------+------+-------------------+----------------------+
Table 6
19.4. N-AALP Effect registry
IANA is requested to create the "N-AALP Effects" registry.
Registration policy: RFC Required -- an ISE-permissible policy; this
closed set is not expected to grow, and any addition (via a successor
RFC) MUST preserve the fail-closed lattice. Columns: Value (0..3),
Name, Reference. Initial contents: read_only 0, idempotent_write 1,
non_idempotent_write 2, destructive 3, this document.
19.5. N-AALP Carriage Protocol Id registry
IANA is requested to create the "N-AALP Carriage Protocol Ids"
registry, a one-octet space partitioned: standards 0x01-0x0F (RFC
Required / First Come First Served -- an ISE-permissible policy),
experimental 0x10-0x7F (no registration), private 0x80-0xFF (no
registration). Columns: Protocol Id, Name, Carriage Class,
Reference. Initial standards-range contents: 0x01 MCP (JSONRPC),
0x02 A2A (JSONRPC), 0x03 HTTP (HTTP), 0x04 WebSocket (STREAM), this
document.
19.6. N-AALP Error Code registry
A fail-closed rejection reason is carried on the wire by a *naalp-
error object* -- a Control/Error object (channel 0x0000, kind 3,
effect read_only; an error report effects nothing) whose body is the
map { 1: code (uint), 2: name (tstr), ?3: detail (tstr), ?4: subject
(bstr content id) } (Appendix A). code is the numeric error code;
name is its registered name; optional detail is a non-normative human
diagnostic with no security meaning; optional subject is the content
id (Section 2.3) of the object the error is about.
Sammartano Expires 10 March 2027 [Page 51]
Internet-Draft N-AALP September 2026
Two dual-carriage rules make the code/name pair unambiguous and
forward-compatible:
* A receiver that recognizes code MUST require name to equal the
registered name for that code; a code/name disagreement is
rejected Malformed. (This is the strengthening direction: the
numeric code is authoritative and the name cannot contradict it.)
* A code outside the receiver's registry snapshot MUST NOT be fatal:
it is opaque (the name is diagnostic only and no semantics are
inferred), so a receiver interoperates with a peer that emits a
later-registered code.
IANA is requested to create the "N-AALP Error Codes" registry.
Registration policy: *RFC Required* (First Come First Served in the
standards range) -- an ISE-permissible policy. The value space is a
uint: *1-0x7FFF* is the standards range (RFC Required / FCFS),
*>=0x8000* is private-use (no registration), and *0 is reserved* and
MUST NOT appear on the wire. Columns: Code, Name, Retryable,
Reference. Retryable is yes only when an unmodified retry of the
same object can later succeed because of a transient or environmental
change (with no object or configuration change); every deterministic
verification or decode failure is therefore non-retryable (the fail-
closed default), and the only registered transient is NotDelivered.
The reference for every initial value is this document.
Initial contents (assigned sequentially in the order below; the same
order is the naalp-error-code production of Appendix A):
+======+===============================+===========+
| Code | Name | Retryable |
+======+===============================+===========+
| 1 | NonCanonical | no |
+------+-------------------------------+-----------+
| 2 | DepthExceeded | no |
+------+-------------------------------+-----------+
| 3 | Malformed | no |
+------+-------------------------------+-----------+
| 4 | ContentIdMismatch | no |
+------+-------------------------------+-----------+
| 5 | HeaderBodyMismatch | no |
+------+-------------------------------+-----------+
| 6 | UnsupportedVersion | no |
+------+-------------------------------+-----------+
| 7 | UnknownCriticalExt | no |
+------+-------------------------------+-----------+
| 8 | UnknownKind | no |
+------+-------------------------------+-----------+
Sammartano Expires 10 March 2027 [Page 52]
Internet-Draft N-AALP September 2026
| 9 | RangeError | no |
+------+-------------------------------+-----------+
| 10 | NonNFC | no |
+------+-------------------------------+-----------+
| 11 | WrongAudience | no |
+------+-------------------------------+-----------+
| 12 | TooLarge | no |
+------+-------------------------------+-----------+
| 13 | TooManyCauses | no |
+------+-------------------------------+-----------+
| 14 | TooManyExtensions | no |
+------+-------------------------------+-----------+
| 15 | TooManyChunks | no |
+------+-------------------------------+-----------+
| 16 | UnknownAlg | no |
+------+-------------------------------+-----------+
| 17 | KeyAlgMismatch | no |
+------+-------------------------------+-----------+
| 18 | ProfileDowngrade | no |
+------+-------------------------------+-----------+
| 19 | HybridIncomplete | no |
+------+-------------------------------+-----------+
| 20 | SuiteMismatch | no |
+------+-------------------------------+-----------+
| 21 | CompositeRefused | no |
+------+-------------------------------+-----------+
| 22 | BadSignature | no |
+------+-------------------------------+-----------+
| 23 | SignerMismatch | no |
+------+-------------------------------+-----------+
| 24 | RotationUnauthorized | no |
+------+-------------------------------+-----------+
| 25 | KeyRevoked | no |
+------+-------------------------------+-----------+
| 26 | EffectNotAuthorized | no |
+------+-------------------------------+-----------+
| 27 | UnauthenticatedPrincipal | no |
+------+-------------------------------+-----------+
| 28 | MalformedSafetyLabel | no |
+------+-------------------------------+-----------+
| 29 | ApprovalRequired | no |
+------+-------------------------------+-----------+
| 30 | ApprovalMismatch | no |
+------+-------------------------------+-----------+
| 31 | ApprovalExpired | no |
+------+-------------------------------+-----------+
| 32 | AlreadyConsumed | no |
+------+-------------------------------+-----------+
Sammartano Expires 10 March 2027 [Page 53]
Internet-Draft N-AALP September 2026
| 33 | ConsumeFork | no |
+------+-------------------------------+-----------+
| 34 | ConsumeForkInvalid | no |
+------+-------------------------------+-----------+
| 35 | ConsumeReceiptUnsigned | no |
+------+-------------------------------+-----------+
| 36 | LedgerCorrupt | no |
+------+-------------------------------+-----------+
| 37 | LedgerUnsigned | no |
+------+-------------------------------+-----------+
| 38 | AudienceMismatch | no |
+------+-------------------------------+-----------+
| 39 | FreshnessSelfAsserted | no |
+------+-------------------------------+-----------+
| 40 | UnknownRefusalOutcome | no |
+------+-------------------------------+-----------+
| 41 | RefusalDetailLeak | no |
+------+-------------------------------+-----------+
| 42 | ChainBroken | no |
+------+-------------------------------+-----------+
| 43 | Equivocation | no |
+------+-------------------------------+-----------+
| 44 | CausalViolation | no |
+------+-------------------------------+-----------+
| 45 | ReceiptUnsigned | no |
+------+-------------------------------+-----------+
| 46 | ForkProofInvalid | no |
+------+-------------------------------+-----------+
| 47 | StageOutOfOrder | no |
+------+-------------------------------+-----------+
| 48 | StreamDigestMismatch | no |
+------+-------------------------------+-----------+
| 49 | StreamStateError | no |
+------+-------------------------------+-----------+
| 50 | ConfidentialTransportRequired | no |
+------+-------------------------------+-----------+
| 51 | PeerUnauthenticated | no |
+------+-------------------------------+-----------+
| 52 | NotDelivered | yes |
+------+-------------------------------+-----------+
| 53 | MappingError | no |
+------+-------------------------------+-----------+
| 54 | EffectDeclarationMismatch | no |
+------+-------------------------------+-----------+
| 55 | StateTransitionError | no |
+------+-------------------------------+-----------+
| 56 | CapExceedsParent | no |
+------+-------------------------------+-----------+
Sammartano Expires 10 March 2027 [Page 54]
Internet-Draft N-AALP September 2026
| 57 | TransformCycle | no |
+------+-------------------------------+-----------+
| 58 | InputGateBypass | no |
+------+-------------------------------+-----------+
| 59 | TaskStateError | no |
+------+-------------------------------+-----------+
| 60 | ScopeOverlapConflict | no |
+------+-------------------------------+-----------+
| 61 | ReconcileMismatch | no |
+------+-------------------------------+-----------+
| 62 | WrongFlow | no |
+------+-------------------------------+-----------+
| 63 | SeqGap | no |
+------+-------------------------------+-----------+
| 64 | AboveCeiling | no |
+------+-------------------------------+-----------+
| 65 | GapDetected | no |
+------+-------------------------------+-----------+
| 66 | CommitMismatch | no |
+------+-------------------------------+-----------+
| 67 | ContMalformed | no |
+------+-------------------------------+-----------+
| 68 | GrantExpired | no |
+------+-------------------------------+-----------+
| 69 | GrantNotYetValid | no |
+------+-------------------------------+-----------+
| 70 | GrantRevoked | no |
+------+-------------------------------+-----------+
| 71 | UntrustedChainRoot | no |
+------+-------------------------------+-----------+
| 72 | DelegationDepthExceeded | no |
+------+-------------------------------+-----------+
| 73 | GrantMalformed | no |
+------+-------------------------------+-----------+
| 74 | NameMalformed | no |
+------+-------------------------------+-----------+
| 75 | NameChainBroken | no |
+------+-------------------------------+-----------+
| 76 | NameForkProofInvalid | no |
+------+-------------------------------+-----------+
| 77 | IllegalTransition | no |
+------+-------------------------------+-----------+
| 78 | TaskChainBroken | no |
+------+-------------------------------+-----------+
| 79 | ForeignCard | no |
+------+-------------------------------+-----------+
| 80 | DescMalformed | no |
+------+-------------------------------+-----------+
Sammartano Expires 10 March 2027 [Page 55]
Internet-Draft N-AALP September 2026
| 81 | MalformedApprovalFlag | no |
+------+-------------------------------+-----------+
| 82 | DirForkProofInvalid | no |
+------+-------------------------------+-----------+
| 83 | ImporterMismatch | no |
+------+-------------------------------+-----------+
| 84 | UnknownDescriptionFormat | no |
+------+-------------------------------+-----------+
| 85 | VerifierKeyMismatch | no |
+------+-------------------------------+-----------+
| 86 | NegMalformed | no |
+------+-------------------------------+-----------+
| 87 | UnknownRole | no |
+------+-------------------------------+-----------+
| 88 | UnknownProfile | no |
+------+-------------------------------+-----------+
| 89 | NotDescended | no |
+------+-------------------------------+-----------+
| 90 | NotOffer | no |
+------+-------------------------------+-----------+
| 91 | NotAccept | no |
+------+-------------------------------+-----------+
| 92 | MalformedCriticalFlag | no |
+------+-------------------------------+-----------+
| 93 | UnknownCriticalRisk | no |
+------+-------------------------------+-----------+
| 94 | ReferenceMismatch | no |
+------+-------------------------------+-----------+
| 95 | MalformedAnnotation | no |
+------+-------------------------------+-----------+
| 96 | EffectUnderDeclared | no |
+------+-------------------------------+-----------+
| 97 | EffectOutsideLattice | no |
+------+-------------------------------+-----------+
| 98 | ToolCallMalformed | no |
+------+-------------------------------+-----------+
| 99 | PayMalformed | no |
+------+-------------------------------+-----------+
| 100 | UnknownPaymentFormat | no |
+------+-------------------------------+-----------+
| 101 | GwMalformed | no |
+------+-------------------------------+-----------+
| 102 | UnknownGatewayDecision | no |
+------+-------------------------------+-----------+
| 103 | UIMalformed | no |
+------+-------------------------------+-----------+
| 104 | UIChainBroken | no |
+------+-------------------------------+-----------+
Sammartano Expires 10 March 2027 [Page 56]
Internet-Draft N-AALP September 2026
| 105 | UnknownUIEventKind | no |
+------+-------------------------------+-----------+
| 106 | ActionSubstituted | no |
+------+-------------------------------+-----------+
| 107 | UINoConsent | no |
+------+-------------------------------+-----------+
| 108 | StaleEpoch | no |
+------+-------------------------------+-----------+
| 109 | Unauthorized | no |
+------+-------------------------------+-----------+
| 110 | OwnerImmutable | no |
+------+-------------------------------+-----------+
| 111 | MemberExists | no |
+------+-------------------------------+-----------+
| 112 | MemberUnknown | no |
+------+-------------------------------+-----------+
| 113 | OwnerExists | no |
+------+-------------------------------+-----------+
| 114 | RoleInvalid | no |
+------+-------------------------------+-----------+
| 115 | RoomOpMismatch | no |
+------+-------------------------------+-----------+
| 116 | OpUnknown | no |
+------+-------------------------------+-----------+
| 117 | PrincipalUnknown | no |
+------+-------------------------------+-----------+
| 118 | PrincipalExists | no |
+------+-------------------------------+-----------+
| 119 | RebindUnauthorized | no |
+------+-------------------------------+-----------+
Table 7
19.7. N-AALP Extension Key registry
IANA is requested to create the "N-AALP Extension Keys" registry, the
shared key namespace for the non-critical ext (object field 11) and
critical cext (field 12) maps, so two independent extensions cannot
collide on a key. Registration policy: RFC Required (First Come
First Served in the standards range) -- an ISE-permissible policy.
Columns: Key (uint), Name, Maps (ext or ext|cext), Reference.
Initial contents:
Sammartano Expires 10 March 2027 [Page 57]
Internet-Draft N-AALP September 2026
+=====+====================+===========+===============+
| Key | Name | Maps | Reference |
+=====+====================+===========+===============+
| 1 | safety-label | ext | this document |
+-----+--------------------+-----------+---------------+
| 13 | recheck | ext\|cext | this document |
+-----+--------------------+-----------+---------------+
| 14 | signer-counter | ext | this document |
+-----+--------------------+-----------+---------------+
| 15 | producing-boundary | ext | this document |
+-----+--------------------+-----------+---------------+
| 16 | naalp-hazard-claim | cext | this document |
+-----+--------------------+-----------+---------------+
Table 8
19.8. N-AALP Carriage Content Type registry
IANA is requested to create the "N-AALP Carriage Content Types"
registry for the content_type field (naalp-carriage-body field 3),
the foreign encoding an OPAQUE-class object carries. It is a one-
octet space partitioned: standards 0x00-0x0F (RFC Required / First
Come First Served -- an ISE-permissible policy), experimental
0x10-0x7F (no registration), private 0x80-0xFF (no registration).
Columns: Content Type (uint), Name, Reference. Initial standards-
range contents:
+==============+==============+===============+
| Content Type | Name | Reference |
+==============+==============+===============+
| 0 | json | this document |
+--------------+--------------+---------------+
| 1 | octet-stream | this document |
+--------------+--------------+---------------+
| 2 | text | this document |
+--------------+--------------+---------------+
Table 9
19.9. N-AALP Trust-Decision Input Class registry
IANA is requested to create the "N-AALP Trust-Decision Input Classes"
registry, the closed set of input classes a relying party's decision
to act may key on, each classified by the safe shape its influence
must take -- verifiable, attenuating, or committed -- so that a party
cannot influence a decision except through one of those shapes, or
the decision fails closed (see the trust-decision-closure-sovereignty
subsection of the Security Considerations). Registration policy: RFC
Sammartano Expires 10 March 2027 [Page 58]
Internet-Draft N-AALP September 2026
Required (First Come First Served in the standards range) -- an ISE-
permissible policy. Columns: Class (uint), Name, Safe shape,
Reference. Initial contents:
+=======+=========================+=============+===============+
| Class | Name | Safe shape | Reference |
+=======+=========================+=============+===============+
| 1 | identifier | verifiable | this document |
+-------+-------------------------+-------------+---------------+
| 2 | object-parse | verifiable | this document |
+-------+-------------------------+-------------+---------------+
| 3 | object-identity | verifiable | this document |
+-------+-------------------------+-------------+---------------+
| 4 | conformance-expectation | verifiable | this document |
+-------+-------------------------+-------------+---------------+
| 5 | verification-procedure | verifiable | this document |
+-------+-------------------------+-------------+---------------+
| 6 | delegated-authority | attenuating | this document |
+-------+-------------------------+-------------+---------------+
| 7 | approved-action | committed | this document |
+-------+-------------------------+-------------+---------------+
| 8 | negotiated-parameters | committed | this document |
+-------+-------------------------+-------------+---------------+
| 9 | validity-clock | committed | this document |
+-------+-------------------------+-------------+---------------+
| 10 | use-context | committed | this document |
+-------+-------------------------+-------------+---------------+
Table 10
19.10. COSE algorithms
N-AALP reuses the existing IANA COSE Algorithms registry for ML-DSA
[RFC9964] and Ed25519 [RFC9864] and requests no new COSE code points.
19.11. Registration criteria for the RFC-Required registries
The Independent stream appoints no Designated Expert, so the RFC-
Required registries above are self-administered: a registration is
made by a successor RFC (or by First Come First Served in the
standards range) and MUST satisfy the same four criteria a reviewer
would otherwise confirm: (1) a stable, publicly available
specification documents the value; (2) the value does not collide
with an existing entry; (3) the name is protocol-neutral and carries
no vendor product name; and (4) for object kinds, the declared effect
is one of the closed set and preserves the fail-closed model.
Sammartano Expires 10 March 2027 [Page 59]
Internet-Draft N-AALP September 2026
20. Implementation Status
RFC-Editor: please remove this section and the reference to [RFC7942]
before publication.
This section records the status of known implementations at the time
of posting, per [RFC7942]. Listing here does not imply endorsement.
Two independent reference implementations exist, in Go and in Rust,
from a single codebase but separate language runtimes. For every
construction carrying a security or interoperability claim
(deterministic CBOR and content id; COSE_Sign1 and the ML-DSA/Ed25519
profiles; the object envelope; signer id; effect authorization;
approval and the consume ledger; audit chain, causal graph, and
federated reconcile; delivery; streaming; transport bindings; foreign
carriage; and the twenty channel surfaces), the two implementations
produce byte-identical output and are cross-validated against an
independent oracle whose expected values come from the relevant RFC,
FIPS, or NIST vector or a from-scratch constructor, never from the
implementation under test. A runnable conformance harness grades
every construction and validates the CDDL module against the
committed vectors. Coverage and known gaps are tracked in the
project's parity ledger.
21. Conformance
A conforming implementation MUST implement the object model, the
signing constructions, identity, effects and authorization, and the
baseline surfaces of every channel, and MUST reject a non-conforming
object whole with its named error and no state change (fail-closed).
Conformance is demonstrated against the machine-gradable vector
corpus and the CDDL module (Appendix A). This specification
recommends, but does not require for Independent-stream publication,
two independent interoperating implementations; two exist (#
Implementation Status).
This specification tiers its surfaces by editorial status, so a first
conforming implementation is not asked to build the whole surface at
once. The normative surface required for conformance is the spine --
the object model, the signing constructions, identity, effects and
authorization, approval and the single-use consume ledger, the
baseline audit receipt chain, delivery, the per-stream commitment,
the transport bindings, and the carriage-never-decodes rule --
together with the frozen baseline surface (tier 0) of every channel.
Every capability an object gains above tier 0, including the
federated higher tier of ordering and any higher channel tier carried
through the tier field and critical or non-critical extensions, has
experimental status: it is OPTIONAL, it MAY change in a later
Sammartano Expires 10 March 2027 [Page 60]
Internet-Draft N-AALP September 2026
revision, and an implementation that omits it is still conforming.
An implementation that does provide such a capability MUST provide it
exactly as specified, and a verifier that does not recognize a
critical extension MUST reject the object fail-closed (# Object
Model). This tiering is editorial: it bounds what conformance
requires, not what the reference implementations provide, which is
the full surface across all ten reference languages (# Implementation
Status).
22. Specification License
This specification may be implemented by anyone, royalty-free. This
right to implement is granted independently of the license of any
reference implementation (the reference code is licensed separately).
Contributions to this document are subject to BCP 78 and the IETF
Trust's Legal Provisions Relating to IETF Documents.
23. Normative References
[BCP14] Best Current Practice 14,
<https://www.rfc-editor.org/info/bcp14>.
At the time of writing, this BCP comprises the following:
Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119,
DOI 10.17487/RFC2119, March 1997,
<https://www.rfc-editor.org/info/rfc2119>.
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
May 2017, <https://www.rfc-editor.org/info/rfc8174>.
[FIPS180] National Institute of Standards and Technology, "Secure
Hash Standard (SHS) (FIPS 180-4)", 2015.
[FIPS204] National Institute of Standards and Technology, "Module-
Lattice-Based Digital Signature Standard (FIPS 204)",
2024.
[RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type
Specifications and Registration Procedures", BCP 13,
RFC 6838, DOI 10.17487/RFC6838, January 2013,
<https://www.rfc-editor.org/rfc/rfc6838>.
[RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital
Signature Algorithm (EdDSA)", RFC 8032,
DOI 10.17487/RFC8032, January 2017,
<https://www.rfc-editor.org/rfc/rfc8032>.
Sammartano Expires 10 March 2027 [Page 61]
Internet-Draft N-AALP September 2026
[RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for
Writing an IANA Considerations Section in RFCs", BCP 26,
RFC 8126, DOI 10.17487/RFC8126, June 2017,
<https://www.rfc-editor.org/rfc/rfc8126>.
[RFC8610] Birkholz, H., Vigano, C., and C. Bormann, "Concise Data
Definition Language (CDDL): A Notational Convention to
Express Concise Binary Object Representation (CBOR) and
JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610,
June 2019, <https://www.rfc-editor.org/rfc/rfc8610>.
[RFC8949] Bormann, C. and P. Hoffman, "Concise Binary Object
Representation (CBOR)", STD 94, RFC 8949,
DOI 10.17487/RFC8949, December 2020,
<https://www.rfc-editor.org/rfc/rfc8949>.
[RFC9052] Schaad, J., "CBOR Object Signing and Encryption (COSE):
Structures and Process", STD 96, RFC 9052,
DOI 10.17487/RFC9052, August 2022,
<https://www.rfc-editor.org/rfc/rfc9052>.
[RFC9053] Schaad, J., "CBOR Object Signing and Encryption (COSE):
Initial Algorithms", RFC 9053, DOI 10.17487/RFC9053,
August 2022, <https://www.rfc-editor.org/rfc/rfc9053>.
[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, October 2025,
<https://www.rfc-editor.org/rfc/rfc9864>.
[RFC9964] Prorock, M. and O. Steele, "ML-DSA for JSON Object Signing
and Encryption (JOSE) and CBOR Object Signing and
Encryption (COSE)", RFC 9964, DOI 10.17487/RFC9964, May
2026, <https://www.rfc-editor.org/rfc/rfc9964>.
24. Informative References
[NPAMP] Sammartano, S., "N-PAMP: Native Post-Quantum Agent
Messaging Protocol", Work in Progress, Internet-Draft,
draft-bubblefish-npamp (work in progress), 2026,
<https://datatracker.ietf.org/doc/html/draft-bubblefish-
npamp (work in progress)>.
[RFC6839] Hansen, T. and A. Melnikov, "Additional Media Type
Structured Syntax Suffixes", RFC 6839,
DOI 10.17487/RFC6839, January 2013,
<https://www.rfc-editor.org/rfc/rfc6839>.
Sammartano Expires 10 March 2027 [Page 62]
Internet-Draft N-AALP September 2026
[RFC7942] Sheffer, Y. and A. Farrel, "Improving Awareness of Running
Code: The Implementation Status Section", BCP 205,
RFC 7942, DOI 10.17487/RFC7942, July 2016,
<https://www.rfc-editor.org/rfc/rfc7942>.
[RFC9162] Laurie, B., Messeri, E., and R. Stradling, "Certificate
Transparency Version 2.0", RFC 9162, DOI 10.17487/RFC9162,
December 2021, <https://www.rfc-editor.org/rfc/rfc9162>.
[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, June 2026,
<https://www.rfc-editor.org/rfc/rfc9943>.
Appendix A. Collected CDDL
This appendix is the complete and normative CDDL module for the
N-AALP byte-level wire format. It defines the object body, effect,
and profile productions of (# Object Model) and the carriage body of
(# Foreign Carriage by Class), together with the COSE wrapper,
protected header, identity records, safety label, approval and
consume records, receipt, delivery update, stream objects, and the
federated reconcile record. A byte-identical copy is maintained with
the reference implementation and machine-validated against the
conformance vectors. Where a CDDL excerpt elsewhere in this document
differs from this appendix, this appendix is authoritative.
; draft-bubblefish-naalp-01 -- Native Agentic Application Layer
; Protocol: the complete CDDL wire-format module (RFC 8610). The copy
; in Appendix A of the Internet-Draft is normative; the copy
; maintained with the reference implementation is a byte-identical
; mirror, machine-validated for well-formedness and against the
; conformance vector corpus.
;
; This module is complete: the object body, the effect, profile, and
; channel vocabularies, the COSE_Sign1 / COSE_Sign wrapper (RFC 9052
; Section 4), the protected header, the identity records, the safety
; label, the approval and consume records, the receipt and ForkProof,
; the delivery update, the stream objects, the carriage body, the
; channel-registry note, the federated reconcile record, and the
; collaboration/rooms membership records (room op + principal
; binding) -- all reachable from the `naalp-artifact` root.
;
; All instances are deterministic CBOR per RFC 8949 Section 4.2.1
; (shortest heads, map keys ascending by encoded bytes, no indefinite
; lengths, no duplicate keys). A non-canonical encoding of any
; production here is rejected.
Sammartano Expires 10 March 2027 [Page 63]
Internet-Draft N-AALP September 2026
; ---- Collected root -----------------------------------------------
; The set of top-level N-AALP artifacts this module defines, as a
; single reachable root so a CDDL tool sees no unreferenced rule.
; Each concrete instance is validated against the specific production
; for its kind (the conformance harness targets the rule per
; construction), not against this union; the union exists only for
; reachability.
naalp-artifact = naalp-object
/ naalp-signed-object / naalp-protected-header
/ naalp-rotation / naalp-revocation / naalp-foreign-link
/ naalp-safety-label
/ naalp-approval / naalp-approval-held / naalp-consume-entry
/ naalp-consume-receipt
/ naalp-receipt / naalp-fork-proof / naalp-delivery-update
/ naalp-stream-open / naalp-stream-commit / naalp-stream-checkpoint
/ naalp-carriage-body / naalp-reconcile
/ naalp-room-op / naalp-principal-binding
/ naalp-delegation-grant
/ recheck-procedure / signer-counter
/ naalp-mcp-tool-call / naalp-mcp-annotations
/ naalp-mcp-call-binding / mcp-hint
/ naalp-flow-open / naalp-continuation / naalp-flow-checkpoint
/ naalp-flow-commit
/ naalp-description / naalp-directory / naalp-description-import
/ naalp-name-binding / naalp-task-transition
/ naalp-negotiation-offer / naalp-negotiation-counter
/ naalp-negotiation-accept
/ negotiation-role
/ naalp-risk-label / naalp-labeled-object / naalp-trust-ref
/ naalp-payment-import / naalp-payment-charge-binding
/ naalp-payment-format
/ naalp-ui-event / ui-event-kind
/ naalp-gateway-decision / gw-decision
/ naalp-refusal / refusal-outcome
/ naalp-error / naalp-error-code
/ trust-decision-input-class
/ naalp-decision-record / ordering-basis
/ naalp-checkpoint-root / naalp-witness-cosign / naalp-inclusion-proof
/ naalp-egress-attestation / egress-binding
/ naalp-foreign-profile-pin
/ naalp-hazard-claim / naalp-hazard-authorization
/ hazard-class / hazard-envelope / spatial-bounds / hazard-window
; Decoder resource bounds -- CDDL cannot express element counts,
; nesting depth, or octet size (RFC 8610 restricts none of these), so
; these are MUST-level limits every decoder enforces fail-closed, not
; this schema: object octet size <= 1 MiB (TooLarge, on raw bytes
; before parse); |causes| (field 8) <= 1024 (TooManyCauses); |ext|
Sammartano Expires 10 March 2027 [Page 64]
Internet-Draft N-AALP September 2026
; (11) and |cext| (12) <= 64 each (TooManyExtensions); CBOR nesting
; depth <= 16 with the outermost item at depth 1 (DepthExceeded);
; stream chunk count <= 2^20 (TooManyChunks, native streaming below).
; The maxima are wire constants, projected identically into all ten
; ports so one port cannot accept what another refuses. The object
; body -- the map that is signed as the COSE_Sign1 payload.
naalp-object = {
1 : bstr,
; id -- content id: multihash(0x20, SHA-384(body-without-1))
2 : uint,
; kind -- object kind code (per channel surface)
3 : channel-id,
; channel -- N-PAMP channel id
4 : uint,
; tier -- channel capability tier (0 = baseline)
5 : bstr,
; signer -- self-certifying signer id
6 : uint,
; created -- signer's claimed creation time, epoch ms (advisory)
7 : effect,
; effect -- closed effect value
8 : [* bstr],
; causes -- content ids of causing objects; may be empty
9 : profile,
; profile -- crypto profile
10 : any,
; body -- kind-specific body, validated by the channel surface
? 11 : { * uint => any },
; ext -- non-critical extensions; unknown keys ignored
? 12 : { * uint => any },
; cext -- critical extensions; any unknown key => reject
? 13 : tstr,
; audience -- endpoint/channel-scope id the object is bound to.
; OMITTED when empty -- a no-audience object encodes byte-
; identically to a draft-00 object. MANDATORY for consume-once
; kinds (enforced by channel-surface validation, not this
; cardinality). A verifier rejects an object whose audience is not
; itself: WrongAudience. This is object-map FIELD 13 (a top-level
; envelope field) -- the genuine draft-01 envelope addition that
; anchors naalp-version 2. DISTINCT namespace from ext/cext KEY 13
; (recheck) below, which is a key INSIDE the field-11/12 maps, not
; a top-level object field.
? 14 : uint,
; suite -- signed suite declaration: PRESENT iff the object's alg
; is the composite id -65537; ABSENT (byte-identical to a pure
; object) for a pure ML-DSA object. Value 1 = COMPSIG-MLDSA65-
; Ed25519-SHA512. Top-level object FIELD 14 -- DISTINCT namespace
; from ext key 14 (signer-counter) INSIDE the field-11/12 maps
Sammartano Expires 10 March 2027 [Page 65]
Internet-Draft N-AALP September 2026
; below.
; The ext (11) / cext (12) extension-KEY namespace is a single
; registry (RFC Required / FCFS) so two implementers assigning keys
; cannot COLLIDE -- a critical-ext collision is a denial; the
; registry_drift gate asserts key uniqueness + agreement with the
; per-capability registries. The currently-registered keys are 1
; safety-label (ext), 13 recheck (ext/cext), 14 signer-counter
; (ext), 15 producing-boundary (ext), 16 naalp-hazard-claim
; (cext): ext/cext key 13 = recheck: it
; names the body claim's re-check procedure by id
; (recheck-procedure below). In ext (11) it is may-ignore (an
; unknown id ignored); in cext (12) it is must-understand (an
; unknown id => UnknownCriticalExt, the same rule).
; ext key 14 = signer-counter: an OPTIONAL forward-only per-signer
; position (signer-counter below). It rides the NON-CRITICAL ext
; map (11)
; ONLY -- a detection aid, not a verification gate -- so it is
; may-ignore; placing it in cext (12) is an unrecognized critical
; key => UnknownCriticalExt (the same rule). It is covered by the
; SIGNER's own COSE_Sign1 signature (ext is part of the signed
; body). Detection, not prevention (# Security Considerations). ext
; key 15 = producing-boundary: an OPTIONAL per-object disclosure
; (naalp-producing-boundary below). of the trust boundary that
; emitted the object and whether that boundary OBSERVED the event
; first-hand or RELAYED a report of it. It rides the NON-CRITICAL
; ext map (11) ONLY -- a disclosure, not a verification gate -- so
; it is may-ignore (a verifier that does not understand it, or
; reads a malformed value, ignores the entry and the object still
; verifies); placing it in cext (12) is an unrecognized critical
; key => UnknownCriticalExt (the same rule). It is covered by the
; SIGNER's own COSE_Sign1 signature (ext is part of the signed
; body), so it is SELF-ASSERTED; it establishes record-order /
; observational domain, NOT cross-boundary event precedence (#
; Security Considerations).
; cext key 16 = naalp-hazard-claim: an OPTIONAL critical
; must-understand physical-hazard claim (naalp-hazard-claim below) --
; the hazard dimension orthogonal to effect (physical danger, not
; data reversibility). It rides the CRITICAL cext map (12) ONLY: a
; safety gate, so a verifier that has not implemented Component F
; rejects the WHOLE object (UnknownCriticalExt) rather than letting a
; hazardous action through unchecked -- the fail-closed direction,
; the OPPOSITE of producing-boundary's may-ignore ext placement. It
; is covered by the SIGNER's own COSE_Sign1 signature (cext is part
; of the signed body).
}
; Closed effect vocabulary, aligned 1:1 with the N-PAMP Bridge
; SafetyLabel. An unrecognized effect is treated as destructive,
Sammartano Expires 10 March 2027 [Page 66]
Internet-Draft N-AALP September 2026
; never fail-open.
effect = &(
read_only: 0,
idempotent_write: 1,
non_idempotent_write: 2,
destructive: 3,
)
; Crypto profiles -- one wire, per-profile row.
profile = &(
public: 1,
enterprise: 2,
sovereign: 3,
)
; N-PAMP channel ids 0x0000..0x0013 -- the twenty channels.
channel-id = 0..19
; ---- COSE signing wrapper (RFC 9052) ------------------------------
; On the wire, an N-AALP object is a tagged COSE structure whose
; payload is the deterministic-CBOR `naalp-object` body above. A
; single signature uses COSE_Sign1 (tag 18) -- either a pure ML-DSA
; signature (per profile) or the opt-in classical-bridge signature: a
; single COSE_Sign1 under the private-use composite alg id -65537
; whose signature value is the IETF LAMPS composite mldsaSig(3309) ||
; tradSig(64). COSE_Sign (tag 98, multiple signatures) carries two
; roles: the legacy two-signature Ed25519+ML-DSA hybrid (OPTIONAL;
; not required by the Standard profile), and the Rotation object
; co-signature (channel 3 / kind 0; old-key then new-key legs, both
; required); a channel-3/kind-0 object MUST be tag 98, and a tag-18
; single-signature rotation is rejected RotationUnauthorized. Signing
; is the FIPS 204 deterministic variant (rnd = 0) so two
; implementations produce byte-identical signatures.
;
naalp-signed-object = COSE_Sign1_Tagged / COSE_Sign_Tagged
COSE_Sign1_Tagged = #6.18(COSE_Sign1)
COSE_Sign1 = [
protected : bstr,
; serialized protected header {1 => cose-alg}; empty => zero-length
; bstr 0x40 (reject the 0x41A0 bstr-wrapped-empty-map form)
unprotected : cose-header,
payload : bstr,
; the deterministic-CBOR naalp-object body (the signed payload)
signature : bstr,
]
Sammartano Expires 10 March 2027 [Page 67]
Internet-Draft N-AALP September 2026
COSE_Sign_Tagged = #6.98(COSE_Sign)
; optional hybrid (Ed25519 + ML-DSA)
COSE_Sign = [
protected : bstr,
; body protected header: empty for the legacy hybrid; for a
; Rotation object (channel 3/kind 0) the enriched naalp header {1:
; newKeyAlg, "naalp": {...}}
unprotected : cose-header,
payload : bstr,
signatures : [+ COSE_Signature],
]
COSE_Signature = [
protected : bstr,
; serialized per-signer header {1 => cose-alg}
unprotected : cose-header,
signature : bstr,
]
cose-header = { * (int / tstr) => any }
; COSE algorithm ids. ML-DSA from RFC 9964; Ed25519 from RFC 9864.
; SLH-DSA (FIPS 205) is reserved, addable with no envelope change
; once its COSE code point is assigned.
cose-alg = &(
ml-dsa-44: -48,
; NIST level 2 -- optional edge/light tier only, never a
; Public/Enterprise/Sovereign default
ml-dsa-65: -49,
; NIST level 3 -- Public / Enterprise default (MTI)
ml-dsa-87: -50,
; NIST level 5 -- Sovereign default / mandate
ed25519: -19,
; classical, opt-in composite leg only
compsig-mldsa65-ed25519: -65537,
; opt-in LAMPS composite (Public/Enterprise) -- COSE private-use,
; N-AALP provisional
compsig-mldsa44-ed25519: -65538,
; edge composite suite -- RESERVED (registered, not implemented
; this wave)
)
; ---- object envelope protected header -----------------------------
; A COSE_Sign1 over an N-AALP object uses this protected header: the
; COSE `alg` plus a pre-parse routing copy of the signer, profile,
; and naalp-version, carried under a text-string label ("naalp"). A
; tstr label cannot collide with any integer-labeled standard COSE
; header parameter (RFC 9052 Section 3.1). A verifier MUST reject an
; object whose header signer/profile copies disagree with the body's
Sammartano Expires 10 March 2027 [Page 68]
Internet-Draft N-AALP September 2026
; field 5 / field 9 (HeaderBodyMismatch). An empty protected header
; is a zero-length bstr (RFC 9052 Section 3); the N-AALP object
; header is never empty (it always carries alg + "naalp"). Per this
; disposition, the redundant 0x41A0 encoding of an empty protected
; header (a bstr wrapping an empty map) MUST be rejected as
; NonCanonical, at every protected-header parse site, before the
; header is interpreted.
naalp-protected-header = {
1 : cose-alg,
; COSE algorithm id
"naalp" : naalp-header-meta,
}
naalp-header-meta = {
1 : bstr,
; signer -- copy of object body field 5
2 : profile,
; profile -- copy of object body field 9
3 : uint,
; naalp-version (draft-01: 2; draft-00 was 1 -- anchored by the
; audience envelope field)
}
; ---- identity + key lifecycle -------------------------------------
; The signer id (envelope field 5) is a self-certifying multibase
; string, identical in form to the N-PAMP PeerHandle: signer =
; multibase(base32, multihash(0x12, SHA-256(multicodec(mc, pubkey))))
; mc is a multiformats key-type code: 0xed (ed25519-pub), 0x1211
; (mldsa-65-pub), 0x1212 (mldsa-87-pub) -- all draft status; 0x12
; (sha2-256) is permanent. base32 is RFC 4648 lowercase without
; padding (multibase prefix "b"). A verifier recomputes the id from
; the key and rejects a mismatch (SignerMismatch); no CA.
;
; Identity-channel (0x0003) lifecycle record bodies (carried in
; envelope field 10 by the channel surface):
naalp-rotation = {
; co-signed by BOTH the old and new key
1 : tstr,
; old -- prior signer id
2 : tstr,
; new -- successor signer id
3 : uint,
; not_before (epoch ms)
}
naalp-revocation = {
; signed by the key it revokes (or a recovery key)
1 : tstr,
; key -- the signer id being revoked
2 : uint,
Sammartano Expires 10 March 2027 [Page 69]
Internet-Draft N-AALP September 2026
; not_after (epoch ms)
}
naalp-foreign-link = {
; cross-signed by the FOREIGN identity's key
1 : tstr,
; controls -- the N-AALP signer id
2 : tstr,
; foreign_id -- foreign identifier; MUST be NFC (else NonNFC)
3 : uint,
; not_after (epoch ms)
}
; ---- effect authorization + safety label --------------------------
; The effect (object body field 7) is the closed four-value set
; defined above
; (read_only/idempotent_write/non_idempotent_write/destructive),
; aligned 1:1 with the N-PAMP Bridge SafetyLabel u8 (NPAMP draft-01
; 10_bridge_framework Section 7): 0x00..0x03, an identity map, so
; carriage over the Bridge is loss-free. The values form a LATTICE
; with destructive at the top.
;
; The effect is an AUTHORIZATION INPUT, not a hint -- closing the gap
; N-PAMP names and leaves open ("describes intent and does not
; replace authorization"). An endpoint grants a maximum effect (a
; capability) to an AUTHENTICATED signer id, and an object is
; authorized iff effect <= granted (the lattice); otherwise it is
; denied (EffectNotAuthorized). An effect value the evaluator does
; not recognize is treated as destructive and MUST NOT fail open. No
; layer authorizes from transport metadata, a foreign header, or a
; client-supplied name (UnauthenticatedPrincipal). This is endpoint
; policy, not a wire production: the wire carries only the effect
; value (field 7).
;
; The OPTIONAL safety label is a signed, non-critical ext entry under
; ext key 1 (object body field 11). It is attributable to the
; object's signer and auditable; it is an ACCOUNTABLE CLAIM, NOT a
; guarantee that the content is safe.
naalp-safety-label = {
; ext[1]
1 : tstr,
; risk -- an accountable risk claim (e.g. "elevated")
2 : tstr,
; scope -- what the object affects (e.g. "billing-records")
}
; ---- approval + single-use consume ledger -------------------------
; An Approval binds, UNDER SIGNATURE, the content id of the exact
; canonical argument object it approves. Because the args are named
Sammartano Expires 10 March 2027 [Page 70]
Internet-Draft N-AALP September 2026
; by content id, mutating any argument changes that id and the
; approval no longer matches (ApprovalMismatch). The approval is not
; valid across sessions/contexts (it carries a nonce) nor after
; not_after (ApprovalExpired), and once consumed it is dead. These
; record bodies are signed with the crypto over deterministic CBOR;
; wrapping them as Governance-channel (0x0004) objects.
naalp-approval = {
1 : bstr,
; approves -- content id of the exact args object
2 : tstr,
; approver -- approver signer id
3 : effect,
; grant -- the granted effect class
4 : bstr,
; nonce -- anti-replay nonce
5 : uint,
; not_after -- expiry, epoch ms
? 6 : tstr,
; audience -- OPTIONAL valid-context; field omitted = unrestricted
; by the issuer's explicit choice; when present, a relying party
; checks it at use and rejects AudienceMismatch on mismatch. An
; empty string is not a distinct value -- omit field 6 to mean
; absent.
}
; A held outcome -- approval required but not yet granted -- is a
; DISTINCT signed non-success result, never a silent success or
; denial.
naalp-approval-held = {
1 : bstr,
; approves -- content id of the args whose approval is pending
2 : tstr,
; reason -- accountable explanation
}
; PARTY-VISIBLE COARSE REFUSAL. A refusal returned to the
; authenticated party carries ONLY a single value from a closed
; vocabulary and the content id of the full signed record -- a
; reference, not the reason. Every discriminating detail lives only
; in that full signed record (a naalp-approval-held, a
; naalp-gateway-decision, or an audit event), resolvable by a party
; authorized to read it, so repeated refusals cannot serve an
; adaptive party as an oracle. A party-visible refusal that carries
; discriminating detail, or omits the record content id, is a
; RefusalDetailLeak. An outcome outside the closed set is
; UnknownRefusalOutcome.
refusal-outcome = &(
denied: 0,
; the action is refused
held: 1,
Sammartano Expires 10 March 2027 [Page 71]
Internet-Draft N-AALP September 2026
; the action requires a further step not yet taken
unverifiable: 2,
; required evidence did not verify
)
naalp-refusal = {
1 : refusal-outcome,
; outcome -- the coarse, party-visible outcome (closed set)
2 : bstr,
; record -- content id of the full signed record carrying the
; detail
}
; N-AALP ERROR OBJECT (Control/Error surface: channel 0x0000, kind
; 3). The body of a Control/Error object. It reports one fail-closed
; rejection reason as BOTH a numeric `code` (field 1) and its
; registered `name` (field 2), plus an OPTIONAL free-text `detail`
; (field 3) and an OPTIONAL `subject` content id (field 4, the object
; the error is about). The Control/Error kind has effect read_only
; (kind table) -- an error report effects nothing. Field 1 is `code :
; uint`, deliberately NOT the naalp-error-code enum below, so a code
; from a LATER-registered or private-use point still validates
; against this grammar (the open, RFC-Required registry).
; DUAL-CARRIAGE: a receiver that KNOWS `code` MUST find `name` equal
; to the registered name for that code, else it rejects `Malformed`
; (the strengthening direction); a `code` it does NOT know is opaque
; and non-fatal (name diagnostic only, no semantics inferred), for
; forward compatibility. The authoritative name<->code taxonomy is
; the naalp-error-code enum below.
naalp-error = {
1 : uint,
; code -- registered error code (standards 1..0x7FFF; private
; >=0x8000)
2 : tstr,
; name -- the registered name for `code` (MUST agree; else
; Malformed)
? 3 : tstr,
; detail -- OPTIONAL non-normative human diagnostic (no security
; meaning)
? 4 : bstr,
; subject -- OPTIONAL content id of the object the error is about
}
; N-AALP ERROR CODES registry (# IANA). Registration policy RFC
; Required / FCFS in the standards range 1..0x7FFF; >=0x8000 is
; private-use, no registration; 0 is reserved and MUST NOT appear on
; the wire. The naalp-error body above binds `code` as a bare uint,
; NOT this enum, so the registry can grow without breaking CDDL
; validation of a future or private code. This enum is the
; authoritative error-code taxonomy.
;
Sammartano Expires 10 March 2027 [Page 72]
Internet-Draft N-AALP September 2026
naalp-error-code = &(
NonCanonical: 1,
DepthExceeded: 2,
Malformed: 3,
ContentIdMismatch: 4,
HeaderBodyMismatch: 5,
UnsupportedVersion: 6,
UnknownCriticalExt: 7,
UnknownKind: 8,
RangeError: 9,
NonNFC: 10,
WrongAudience: 11,
TooLarge: 12,
TooManyCauses: 13,
TooManyExtensions: 14,
TooManyChunks: 15,
UnknownAlg: 16,
KeyAlgMismatch: 17,
ProfileDowngrade: 18,
HybridIncomplete: 19,
SuiteMismatch: 20,
CompositeRefused: 21,
BadSignature: 22,
SignerMismatch: 23,
RotationUnauthorized: 24,
KeyRevoked: 25,
EffectNotAuthorized: 26,
UnauthenticatedPrincipal: 27,
MalformedSafetyLabel: 28,
ApprovalRequired: 29,
ApprovalMismatch: 30,
ApprovalExpired: 31,
AlreadyConsumed: 32,
ConsumeFork: 33,
ConsumeForkInvalid: 34,
ConsumeReceiptUnsigned: 35,
LedgerCorrupt: 36,
LedgerUnsigned: 37,
AudienceMismatch: 38,
FreshnessSelfAsserted: 39,
UnknownRefusalOutcome: 40,
RefusalDetailLeak: 41,
ChainBroken: 42,
Equivocation: 43,
CausalViolation: 44,
ReceiptUnsigned: 45,
ForkProofInvalid: 46,
StageOutOfOrder: 47,
Sammartano Expires 10 March 2027 [Page 73]
Internet-Draft N-AALP September 2026
StreamDigestMismatch: 48,
StreamStateError: 49,
ConfidentialTransportRequired: 50,
PeerUnauthenticated: 51,
NotDelivered: 52,
MappingError: 53,
EffectDeclarationMismatch: 54,
StateTransitionError: 55,
CapExceedsParent: 56,
TransformCycle: 57,
InputGateBypass: 58,
TaskStateError: 59,
ScopeOverlapConflict: 60,
ReconcileMismatch: 61,
WrongFlow: 62,
SeqGap: 63,
AboveCeiling: 64,
GapDetected: 65,
CommitMismatch: 66,
ContMalformed: 67,
GrantExpired: 68,
GrantNotYetValid: 69,
GrantRevoked: 70,
UntrustedChainRoot: 71,
DelegationDepthExceeded: 72,
GrantMalformed: 73,
NameMalformed: 74,
NameChainBroken: 75,
NameForkProofInvalid: 76,
IllegalTransition: 77,
TaskChainBroken: 78,
ForeignCard: 79,
DescMalformed: 80,
MalformedApprovalFlag: 81,
DirForkProofInvalid: 82,
ImporterMismatch: 83,
UnknownDescriptionFormat: 84,
VerifierKeyMismatch: 85,
NegMalformed: 86,
UnknownRole: 87,
UnknownProfile: 88,
NotDescended: 89,
NotOffer: 90,
NotAccept: 91,
MalformedCriticalFlag: 92,
UnknownCriticalRisk: 93,
ReferenceMismatch: 94,
MalformedAnnotation: 95,
Sammartano Expires 10 March 2027 [Page 74]
Internet-Draft N-AALP September 2026
EffectUnderDeclared: 96,
EffectOutsideLattice: 97,
ToolCallMalformed: 98,
PayMalformed: 99,
UnknownPaymentFormat: 100,
GwMalformed: 101,
UnknownGatewayDecision: 102,
UIMalformed: 103,
UIChainBroken: 104,
UnknownUIEventKind: 105,
ActionSubstituted: 106,
UINoConsent: 107,
StaleEpoch: 108,
Unauthorized: 109,
OwnerImmutable: 110,
MemberExists: 111,
MemberUnknown: 112,
OwnerExists: 113,
RoleInvalid: 114,
RoomOpMismatch: 115,
OpUnknown: 116,
PrincipalUnknown: 117,
PrincipalExists: 118,
RebindUnauthorized: 119,
EgMalformed: 120,
UnknownEgressBinding: 121,
DecisionMalformed: 122,
UnknownOrderingBasis: 123,
OrderingDisclosureMalformed: 124,
TermDispositionMalformed: 125,
CheckpointMalformed: 126,
WitnessRootMismatch: 127,
InclusionProofInvalid: 128,
ForeignProfileMalformed: 129,
HazardMalformed: 130,
HazardNotCovered: 131,
HazardUnknown: 132,
)
; TRUST-DECISION INPUT-CLASS REGISTRY. The open,
; RFC Required / FCFS registry of the decision inputs the closure
; property governs, each classified by its safe shape (verifiable /
; attenuating / committed). It grows input-by-input as new
; architectures mint new inputs, without reopening the property.
;
trust-decision-input-class = &(
identifier: 1,
; the identifier a decision keys on (verifiable)
object-parse: 2,
Sammartano Expires 10 March 2027 [Page 75]
Internet-Draft N-AALP September 2026
; the object bytes and their parse (verifiable)
object-identity: 3,
; the object content id (verifiable)
conformance-expectation: 4,
; the expected conformance value (verifiable)
verification-procedure: 5,
; the evaluator's own procedure (verifiable)
delegated-authority: 6,
; authority derived across a delegation hop (attenuating)
approved-action: 7,
; which action an approval authorizes (committed)
negotiated-parameters: 8,
; the negotiated profile and parameters (committed)
validity-clock: 9,
; when a credential's validity is judged (committed)
use-context: 10,
; the context a portable verdict is valid in (committed)
)
; The consume ledger is a durable, hash-chained set keyed by the
; approval content id. Consume is an atomic compare-and-set: the
; first append for an approval id wins; a second is rejected
; (AlreadyConsumed). The head after an entry is SHA-384(entry); the
; entry carries the prior head in field 2, so editing any entry
; breaks the next entry's linkage (LedgerCorrupt on replay). The
; genesis head is 48 zero bytes. Atomicity = the store's write-ahead
; log (persist-before-acknowledge) + a single-writer-per-approval-id
; discipline.
naalp-consume-entry = {
1 : uint,
; seq -- ledger sequence position
2 : bstr,
; prev -- prior chain head (48 bytes; genesis = zero)
3 : bstr,
; approval_id -- the approval content id being consumed
4 : tstr,
; by -- consumer signer id
}
; The ledger-signed consume receipt -- draft-01 addition. It moves
; the anti-double-spend counter OFF the requester and ONTO the
; consuming ledger (the ordering authority): the receipt binds the
; approval content id to the LEDGER's own forward-only position and
; is SIGNED BY THE LEDGER KEY over these exact bytes. The requester
; cannot forge the ledger's position or signature, so a partition
; that spends one approval twice leaves TWO ledger-signed receipts
; against ONE approval id, each carrying a position drawn from forked
; state -- a contradiction authored by neither the requester nor a
; thief, provable on comparison (ConsumeFork; the surfaced evidence
Sammartano Expires 10 March 2027 [Page 76]
Internet-Draft N-AALP September 2026
; carries both receipts and both ledger signatures). It does not
; PREVENT the second spend; it makes the double-spend detectable in
; bytes neither party could repudiate, and the approval's expiry
; bounds the exposure window. Keyed by approval content id,
; first-append-wins: on one reachable ledger the first consume
; assigns exactly one position and one receipt; a byte-identical
; re-emission is a benign duplicate, and a second receipt with a
; DIFFERENT position (or a DIFFERENT ledger) is a detected fork.
; Wrapping receipts as Audit-channel (0x000B) objects. This does not
; alter naalp-consume-entry (the ledger's internal hash-chained
; entry, above).
naalp-consume-receipt = {
1 : bstr,
; ledger -- the consuming ledger's signer id (the ordering
; authority)
2 : bstr,
; approval_id -- the approval content id consumed (the
; compare-and-set key)
3 : uint,
; position -- the ledger's forward-only position bound to this
; consume
}
; ---- audit chain + causal graph + tiered ordering -----------------
; An ordering authority records each accepted object by appending a
; signed Receipt, signed by the authority with the crypto over the
; deterministic-CBOR body. The chain is tamper-evident: the head
; after a receipt is SHA-384(receipt body), the genesis prev is 48
; zero bytes, and the body carries the prior head in field 1 -- so
; any reorder, omission, or substitution breaks a `prev` link
; (ChainBroken) or duplicates a `seq`. The authority NEVER mutates
; the origin object to order it; ordering is an outer signed layer
; and the object's own signature stays valid. `at` is the authority's
; time anchor, evidence a verifier checks independently of the
; signer's clock. An independent auditor detects equivocation -- two
; receipts by one authority at one `seq` naming different objects --
; from the signed receipts alone and mints a non-repudiable
; naalp-fork-proof (below, draft-01) carrying both of the accused's
; signatures. Wrapping receipts as Audit-channel (0x000B) objects;
; the federation (higher) tier that reconciles multiple authorities
; over the shared causal graph -- both tiers order the identical
; signed objects, so federation needs no wire change.
naalp-receipt = {
1 : bstr,
; prev -- hash of the previous receipt body (48 bytes; genesis =
; zero)
2 : bstr,
; obj -- content id of the accepted object (never the object
Sammartano Expires 10 March 2027 [Page 77]
Internet-Draft N-AALP September 2026
; itself)
3 : uint,
; seq -- monotonic sequence position within this authority's chain
4 : uint,
; at -- authority time anchor, epoch ms
}
; ForkProof -- the auditor's NON-REPUDIABLE equivocation proof.
; CHANGED IN draft-01 (SUPERSEDES draft-00's ForkProof): draft-00's
; fork proof named the two conflicting receipts but carried NEITHER
; conflicting signature, so an accused signer could deny the fork.
; draft-01's ForkProof carries the accused authority's OWN two
; signatures -- sig-a over body-a and sig-b over body-b, two
; validly-signed receipts by ONE authority at ONE seq naming
; DIFFERENT objects -- making the proof self-contained and
; non-repudiable: any third party verifies both signatures against
; the accused key with no further evidence. `ext-counter` is an
; external monotonic counter bound into the proof so a replayed or
; reordered proof is detectable. The two embedded bodies are
; naalp-receipt encodings (the exact bytes each signature covers).
; Verify accepts iff signer is present, the two bodies share one seq,
; name different objects, and BOTH signatures verify under the
; accused key; it is rejected whole (fail-closed) otherwise.
naalp-fork-proof = {
1 : bstr,
; signer -- accused authority signer id (envelope field-5 form)
2 : uint,
; ext-counter -- external monotonic counter bound into the proof
3 : bstr,
; body-a -- receipt A body (naalp-receipt det-CBOR); sig-a's input
4 : bstr,
; sig-a -- the accused authority's signature over body-a
5 : bstr,
; body-b -- receipt B body (naalp-receipt det-CBOR); obj !=
; body-a's
6 : bstr,
; sig-b -- the accused authority's signature over body-b
}
; The causal graph is not a distinct wire object: an edge "A causes
; B" is carried by B's object body field 8 (`causes`, an array of
; content ids) and proven by B's own signature. It is a signed
; partial order, checkable offline with no ordering authority
; present; a total order is a policy layered over it. A cause an
; effect could not have seen -- a present cause at a later ordering
; position, or a cycle -- is rejected (CausalViolation).
; ---- delivery stages + persist-before-ack + switchboard -----------
Sammartano Expires 10 March 2027 [Page 78]
Internet-Draft N-AALP September 2026
; Delivery is four distinct, monotonic, separately-observable stages
; -- there is no single "sent" boolean. Each stage is a signed
; delivery.update naming the target object's content id and the stage
; reached. Stages advance in order; observing a stage earlier than
; the one already reached is StageOutOfOrder. An endpoint MUST
; durably persist an object (write-ahead log fsync) BEFORE emitting
; the acknowledgment that advances its stage, so a crash immediately
; after an acknowledgment loses nothing (persist-before-ack). The
; live switchboard (two connections held open, objects passed through
; both directions concurrently) and the content-free relay (a relay
; holding objects only in transit still writes a valid audit trail
; over content ids) are behavioural properties of an endpoint/relay,
; not additional wire objects. Wrapping delivery.update as a
; Delivery-channel object.
delivery-stage = &(
persisted_origin: 0,
; object durably persisted at the origin
accepted_relay: 1,
; accepted by a relay in transit
persisted_target: 2,
; durably persisted at the target
presented: 3,
; presented to the target application
)
naalp-delivery-update = {
1 : bstr,
; obj -- content id of the object whose delivery this reports
2 : delivery-stage,
; stage -- the stage reached (0..3)
3 : uint,
; at -- observer time, epoch ms
}
; ---- native streaming + per-stream commitment ---------------------
; A native stream runs on the N-PAMP Stream channel 0x000C, distinct
; from foreign streamed carriage (0x000D) -- N-AALP never carries a
; foreign protocol on 0x000C. The chunks are raw data frames the
; transport AEAD already authenticates; they are NOT signed
; individually (per-chunk ML-DSA would be ruinous). Instead three
; signed objects govern the stream. StreamOpen binds the stream's
; identity, effect, and -- where it causes an effect -- its approval;
; a stream whose effect is not authorized is refused BEFORE any
; chunk. StreamCommit carries a single rolling SHA-384 over the
; chunks in absolute-offset order (a bare 48-octet digest), making
; the whole stream non-repudiable with one signature, not N; altering
; any delivered byte invalidates it (StreamDigestMismatch). Optional
; StreamCheckpoints let a verifier confirm a prefix (digest_so_far =
; SHA-384 of the prefix through that offset) without waiting for the
Sammartano Expires 10 March 2027 [Page 79]
Internet-Draft N-AALP September 2026
; end. The same three objects map onto QUIC / WS / HTTP native
; streaming and the commitment verifies identically. Full-duplex is
; inherent Wrapping these as Stream-channel objects.
naalp-stream-open = {
1 : bstr,
; stream_id
2 : effect,
; effect -- the stream's effect class
? 3 : bstr,
; approval -- content id of the approval binding (present iff
; effecting)
4 : uint,
; substream -- NPAMP-STREAM sub-stream id
}
naalp-stream-commit = {
1 : bstr,
; stream_id
2 : bstr,
; digest -- rolling SHA-384 over the complete ordered stream (48
; octets)
}
naalp-stream-checkpoint = {
1 : bstr,
; stream_id
2 : uint,
; through_offset -- absolute offset the prefix ends at
3 : bstr,
; digest_so_far -- SHA-384 over the prefix through_offset (48
; octets)
}
; ---- N-AALP-CONT flow continuation --------------------------------
; A long-running flow costs two full ML-DSA signatures (FlowOpen +
; FlowCommit) regardless of the number of continuations, generalizing
; native streaming from a byte stream to a flow of typed steps.
; FlowOpen fixes the effect ceiling + approval bindings and is a
; signed N-AALP object whose authority is reconstructable from its
; bytes alone; its head = SHA-384(body) anchors the chain and its
; content-id (multihash(0x20, SHA-384(body))) is carried by every
; child object. A Continuation is a cheap UNSIGNED hash-chain link
; (head = SHA-384(body); prev = the previous head, the FlowOpen head
; for seq 0) whose effect MUST be <= the ceiling (AboveCeiling
; otherwise). A Checkpoint confirms a contiguous prefix (a
; dropped/reordered link is GapDetected). FlowCommit binds the final
; chain head under the second full signature (CommitMismatch on a
; missing/altered link). Domain separation is structural: the four
; body shapes (3 / 5 / 3 / 2 fields) are distinct.
naalp-flow-open = {
Sammartano Expires 10 March 2027 [Page 80]
Internet-Draft N-AALP September 2026
1 : bstr,
; flow_id
2 : effect,
; effect_ceiling -- the max effect any continuation may cause
3 : [* bstr],
; approvals -- content-ids of the approvals authorizing the flow
}
naalp-continuation = {
1 : bstr,
; flow_open_id -- content-id of the FlowOpen (WrongFlow if
; mismatched)
2 : uint,
; seq -- 0-based position in the chain
3 : effect,
; effect -- this step's effect; MUST be <= the FlowOpen ceiling
4 : bstr,
; payload_id -- content-id of this step's payload
5 : bstr,
; prev -- the previous link head (FlowOpen head for seq 0) (48
; octets)
}
naalp-flow-checkpoint = {
1 : bstr,
; flow_open_id
2 : uint,
; through_seq -- the contiguous prefix ends at this seq
3 : bstr,
; head -- chain head after the prefix (48 octets)
}
naalp-flow-commit = {
1 : bstr,
; flow_open_id
2 : bstr,
; final_head -- chain head over the whole ordered sequence (48
; octets)
}
; ---- transport bindings -------------------------------------------
; A transport binding adds NO new wire object: it carries exactly one
; signed N-AALP object (any production above) as one message unit,
; byte-for-byte, over N-PAMP / QUIC / WebSocket / HTTP, with
; identical object semantics. The media type is
; `application/vnd.bubblefish.naalp+cbor` (one object per
; representation), registered in the IANA Considerations. The object
; is self-secured; the binding adds only framing and, from the
; transport, confidentiality and connection authentication. The
; confidentiality boundary is NORMATIVE: an object marked sensitive
; MUST NOT be emitted in cleartext over a non-confidential transport
Sammartano Expires 10 March 2027 [Page 81]
Internet-Draft N-AALP September 2026
; -- the binding refuses it (ConfidentialTransportRequired) and
; directs the deployment to a confidential transport (N-PAMP is the
; reference confidential transport). A transport lacking peer
; authentication where policy requires it is refused
; (PeerUnauthenticated). These are endpoint behaviours, not wire
; productions.
; ---- foreign carriage by class ------------------------------------
; N-AALP carries a foreign protocol by wrapping its message,
; octet-for-octet, in a signed N-AALP carriage object interpreted by
; a carriage CLASS. The carriage body is a normal N-AALP object body
; (envelope field 10): signed, effect-labeled, identity-bound,
; audited. The `foreign` field is carried VERBATIM and MUST NOT be
; re-serialized, canonicalized, summarized, or rewritten; N-AALP
; metadata is carried around it, never inside it. The carriage
; object's signer remains the authority -- a foreign identity NEVER
; becomes an N-AALP authorization identity. The OPAQUE class carries
; any protocol, including an undefined one, on an experimental
; protocol id with no registration. protocol_id ranges: standards
; 0x01-0x0F (RFC Required / FCFS), experimental 0x10-0x7F (no
; registration), private 0x80-0xFF. content_type (field 3) is the
; foreign ENCODING, an OPEN registry ("N-AALP Carriage Content
; Types"): standard encodings 0x00-0x0F (RFC Required / FCFS),
; experimental 0x10-0x7F and private 0x80-0xFF take no registration
; (0 json, 1 octet-stream, 2 text). Over N-PAMP the raw `foreign`
; octets ride an N-PAMP Bridge frame of the matching class
; byte-exact.
naalp-carriage-class = &(
jsonrpc: 0,
; any JSON-RPC 2.0 protocol (MCP, A2A core)
http: 1,
; any HTTP-semantics protocol
msg: 2,
; any message-passing / performative protocol
stream: 3,
; any event/streaming foreign reply
doc: 4,
; capability/schema documents (agent cards, tool catalogs)
opaque: 5,
; any declared-content-type payload, incl. undefined protocols
)
naalp-carriage-body = {
1 : uint,
; protocol_id -- N-AALP registry
2 : naalp-carriage-class,
; class -- 0 JSONRPC .. 5 OPAQUE
3 : uint,
; content_type -- foreign encoding (open registry)
Sammartano Expires 10 March 2027 [Page 82]
Internet-Draft N-AALP September 2026
4 : bstr,
; correlation -- exchange correlation token
5 : tstr,
; method -- advisory routing key (foreign op name)
6 : bstr,
; foreign -- the foreign message, carried octet-for-octet
}
; ---- the twenty channel surfaces, baseline tier -------------------
; A channel surface adds NO new encoding, signature, or identity: it
; only assigns `kind` codes (object body field 2), each scoped by the
; `channel` id (field 3), and binds each kind to a declared `effect`
; (field 7). All twenty channels (0x0000..0x0013) and their baseline
; kinds are enumerated in the machine-readable registry (65 kinds). A
; fixed-effect kind's object MUST carry exactly its declared effect
; (EffectDeclarationMismatch otherwise); a variable-effect kind
; (Stream StreamOpen, Bridge Carriage) takes the carried/stream
; action's effect, authorized at run time by the effect lattice. An
; object on an unregistered (channel, kind) is rejected
; (UnknownKind). Each channel's baseline body is a spine production
; already defined above -- Identity rotation/
; revocation/foreign-link, Governance approval/held/consume, Audit
; receipt/fork-proof, delivery-update, Stream open/commit/checkpoint,
; Bridge carriage -- or a thin channel-specific body carried in
; object body field 10; the surface introduces no new wire
; production, only the kind/effect/state vocabulary. Higher tiers
; (tier 1+) are the higher-tier surfaces below.
; ---- Higher tiers: federated ordering + the tier model ------------
; The channel tier is envelope field 4; tier = 0 is the frozen
; baseline (all twenty channels). A higher tier (tier 1+) adds
; capability WITHOUT changing the baseline envelope, effect
; vocabulary, identity model, or audit chain: a baseline-tier
; verifier accepts a higher-tier object's spine and IGNORES an
; unknown higher-tier NON-critical extension (field 11), while an
; unknown CRITICAL extension (field 12) it cannot honor is rejected
; fail-closed (UnknownCriticalExt) -- the same field-11/field-12
; mechanism defined in the protected header. No new wire field is
; introduced for tiering.
;
; Federated ordering (the Federation channel higher tier, tier 1):
; multiple independent authorities each issue baseline receipts over
; their own scope, and a signed Reconcile object records a
; DETERMINISTIC merge of their receipt chains over the shared causal
; graph -- a topological linearization of the union causal DAG
; tie-broken by object content id (bytewise ascending). Because the
; order depends only on the causal graph, any split of the same
; objects across authorities reconciles to the same order, so moving
Sammartano Expires 10 March 2027 [Page 83]
Internet-Draft N-AALP September 2026
; from single-authority to federated ordering requires NO envelope or
; object change. ScopeOverlapConflict (an object claimed by two
; authorities) is an operator error at baseline and is resolved by
; the merge at tier 1 (the object is ordered once).
naalp-reconcile = {
1 : [+ tstr],
; authorities -- the ordering authorities' signer ids reconciled
2 : [+ bstr],
; order -- the deterministic total order, object content ids
}
; ---- Collaboration / rooms membership (higher tier, tier 1) -------
; A Phase-3 ADDITIVE higher-tier surface over the frozen baseline
; envelope, effect vocabulary, identity model, and audit chain -- it
; introduces NO new wire mechanism, only tier-1 kinds on the
; Governance channel (0x0004; membership ops) and the Identity
; channel (0x0003; the principal registry), so it reuses the
; naalp-object envelope, the naalp-receipt chain, and the identity
; records unchanged. A baseline verifier that has not licensed the
; tier rejects a room kind as UnknownKind (fail-closed), consistent
; with the tier model above. It builds three recorded decisions: #4a
; Membership carriage -- a membership change is a first-class SIGNED
; naalp-object whose body (field 10) is a naalp-room-op. It is
; CURSOR-OCCUPYING and RECEIPT-CHAINED: each accepted op is ordered
; at a cursor by an ordering authority's naalp-receipt over the op's
; content id (multihash(0x20, SHA-384(op body))), weaving membership
; into the append-only audit chain. It is EPOCH-BUMPING: the op
; carries the membership epoch it is built against (field 3); each
; accepted op increments the room's epoch, so a superseded-epoch op
; is rejected (StaleEpoch). Ops:
; create/add_member/remove_member/change_role/add_owner. #4b O2
; ownership -- multi-owner, ADD-ONLY: add_owner adds an owner; an
; owner is never removed (remove_member refuses an owner) nor demoted
; (change_role refuses to lower an owner), so the owner count is
; monotonically >= 1 (a room can never become ownerless). #3 Delivery
; Model B -- a naalp-principal-binding maps a stable semantic
; principal id to a durable Handle (a signer id), kept as a
; per-principal SHA-384 chain (head = SHA-384(body), prior head in
; field 4, genesis = 48 zero bytes). A delivery addresses the
; semantic id and resolves it to the Handle at send time -- a durable
; naming layer above the connection-scoped N-PAMP PeerHandle. A
; rebind is authorised only by a verified rotation from the current
; handle to the new handle; a rebind to an unrelated key is refused.
; The membership op-authorisation, epoch guard, owner-immutability,
; and rebind continuity are endpoint behaviours (graded by two
; implementations against the non-circular oracle), not wire
; productions.
naalp-room-op = {
Sammartano Expires 10 March 2027 [Page 84]
Internet-Draft N-AALP September 2026
1 : bstr,
; room -- room id
2 : room-op-code,
; op -- membership operation
3 : uint,
; epoch -- membership epoch this op is built against (bumps on
; accept)
4 : tstr,
; subject -- affected member signer id (the creator, for create);
; NFC
5 : member-role,
; role -- the role assigned to the subject
}
room-op-code = &(
create: 0,
add_member: 1,
remove_member: 2,
change_role: 3,
add_owner: 4,
)
member-role = &(
member: 0,
admin: 1,
owner: 2,
)
naalp-principal-binding = {
1 : tstr,
; principal -- the stable semantic principal id (MUST be NFC)
2 : tstr,
; handle -- the current durable Handle (a signer id) it resolves to
3 : uint,
; epoch -- monotonic per-principal binding epoch (0 for first bind)
4 : bstr,
; prev -- prior per-principal chain head (48 bytes; genesis = zero)
}
; ---- multi-hop agent-delegation grant -- draft-01 addition --------
; A DelegationGrant is a NORMAL N-AALP object (envelope): an
; independent COSE_Sign1 whose ISSUER is the verified envelope signer
; (field 5), NEVER a body field. It is a Capability-channel (0x0002)
; kind DelegationGrant (kind 4), tier 1 -- a named escalation adding
; MULTI-HOP capability under the frozen baseline envelope; a verifier
; that has not licensed the tier rejects the kind as UnknownKind
; (fail-closed). The object's OWN effect (envelope field 7) is
; non_idempotent_write (issuing a grant); effect_cap below is the
; SEPARATE ceiling it confers on the subject. It REUSES the
; CapDelegate substrate -- parent-by-content-id in `causes` and
; CapExceedsParent attenuation -- rather than a parallel mechanism
Sammartano Expires 10 March 2027 [Page 85]
Internet-Draft N-AALP September 2026
; (D5). The delegation PARENT (the grant or CapIssue that authorised
; THIS grant's issuer) is named by content id in envelope field 8
; (`causes`): the UNIQUE cause resolving to a Capability authority
; object (CapIssue kind 0, CapDelegate kind 1, DelegationGrant kind
; 4) whose subject/holder == this grant's issuer; a root grant
; (issuer in the verifier's trust-anchor set) has none, two-or-more
; is ChainBroken. scope is OPTIONAL: an absent field 6 is
; unconstrained (an empty scope is not a distinct value).
naalp-delegation-grant = {
1 : tstr,
; subject -- delegatee agent id (agent B), signer-id form; MUST be
; NFC
2 : effect,
; effect_cap -- MAX effect conveyed (the lattice); child <= parent
; else CapExceedsParent
3 : uint,
; max_depth -- max FURTHER delegation hops below this grant (0 =
; act, not re-delegate)
4 : uint,
; not_before -- validity-window start, epoch ms (GrantNotYetValid
; before)
5 : uint,
; not_after -- validity-window end, epoch ms (GrantExpired after)
? 6 : tstr,
; scope -- OPTIONAL NFC resource scope; child scope MUST be
; contained else CapExceedsParent
}
; ---- The checkable minimum: recheck procedure (draft-01 addition) -
; A signature makes a body claim ATTRIBUTABLE; a claim is CHECKABLE
; only if a stranger can re-derive it without trusting the speaker,
; which requires the object to NAME the procedure a verifier runs to
; re-check it. That naming is carried as extension key 13 (see
; naalp-object above): the VALUE is a recheck-procedure id into this
; CLOSED registry. In the non-critical ext map (field 11) it is
; may-ignore (an unknown id ignored); in the critical cext map (field
; 12) it is must-understand and an unknown id is rejected
; (UnknownCriticalExt, the critical-extension rule reaching the
; procedure it names). A known id verifies in either map. There is no
; boolean on the wire -- placement (ext vs cext) is the criticality
; signal (the N-AALP spine carries no CBOR booleans). recheck is an
; ext-key (key 13) and does NOT move naalp-version -- the version is
; anchored by the top-level audience envelope field.
;
recheck-procedure = &(
recompute-content-id: 1,
; recompute the content id from the body and compare
verify-cose-sign1: 2,
Sammartano Expires 10 March 2027 [Page 86]
Internet-Draft N-AALP September 2026
; verify the COSE_Sign1 signature under the signer key
walk-causes: 3,
; walk the signed causal partial order offline
replay-consume-check: 4,
; replay the single-use consume ledger for the approval
)
; ---- The per-signer forward-only counter (draft-01 addition) ------
; A signer MAY carry a forward-only counter it increments on each
; object, to DETECT key duplication -- NOT to prevent it. It is
; carried as extension key 14 in the object's NON-CRITICAL ext map
; (field 11, may-ignore), covered by the SIGNER's own COSE_Sign1
; signature -- the deliberate contrast with the LEDGER-signed
; naalp-consume-receipt position. The value is a bare forward-only
; position; ANY 64-bit uint is valid (a large value is not an error,
; it must decode as a 64-bit unsigned position without rounding). The
; field is OPTIONAL: an absent counter is valid. It is
; DETECTION-only: a single self-authored sequence proves nothing;
; duplication is flagged only when two conflicting sequences bearing
; ONE signer id physically meet where the attacker cannot suppress
; one (# Security Considerations). Placing the counter in the
; critical cext map is an unrecognized critical key
; (UnknownCriticalExt). No wire-version bump: this reuses the ext
; mechanism (a new ext-key assignment, not a change to an existing
; object body), so naalp-version stays at 2.
signer-counter = uint
; ---- The NAALP-MCP binding profile (draft-01 addition) ------------
; An MCP tool call is CARRIED (its bytes unchanged -- carriage, not
; adoption), its unenforced, untrusted annotation hints are mapped to
; the closed four-effect lattice by a PUBLISHED table, and the
; WRAPPING SIGNER is accountable for that mapping (a false
; declaration is attributable to that key). The wrapper is a
; Bridge-channel (0x000D) tier-1 kind McpToolCall (kind 1) -- a named
; escalation over the frozen baseline Carriage kind (0), under the
; frozen envelope; a baseline verifier that has not licensed the tier
; rejects it as UnknownKind (fail-closed). Its OWN envelope effect
; (field 7) is the wrapping signer's DECLARED effect; a verifier
; independently recomputes the annotation-derived effect from the
; carried annotations and enforces the MORE SEVERE of the two (the
; good-regulator attenuator -- an unknown or disagreeing input
; collapses UP, never down). A declared effect BELOW the
; annotation-derived effect is rejected (EffectUnderDeclared); an
; annotation set that maps outside the lattice is rejected
; (MalformedAnnotation), never defaulted to benign.
;
;
; The MCP ToolAnnotations are booleans in JSON, but the N-AALP spine
Sammartano Expires 10 March 2027 [Page 87]
Internet-Draft N-AALP September 2026
; carries NO CBOR boolean Each hint is transcribed as the uint 1
; (true) / 0 (false); an ABSENT hint is the MCP documented default,
; applied by the mapping (readOnlyHint default false, destructiveHint
; default true, idempotentHint default false, openWorldHint default
; true). destructiveHint's default of TRUE is why an un-annotated
; write maps to destructive -- the same fail-closed rule as N-AALP's
; "absent effect on a state-changing object => destructive". A
; present 0 and an absent hint encode to DIFFERENT bytes (empty !=
; absent) though they may resolve to the same effect. A hint value
; outside {0,1} is rejected (it transcribes no boolean).
; openWorldHint (key 4) is carried for accountability but is an
; ADVISORY risk signal ONLY -- it never enters the effect lattice
; (risk dimensions ride as advisory labels, not effects).
mcp-hint = 0..1
; a JSON boolean hint transcribed to the spine: 1 = true, 0 = false
naalp-mcp-annotations = {
; the wrapping signer's transcription of the tool's MCP annotations
? 1 : mcp-hint,
; readOnlyHint (absent => MCP default false)
? 2 : mcp-hint,
; destructiveHint (absent => MCP default true; the fail-closed
; default)
? 3 : mcp-hint,
; idempotentHint (absent => MCP default false)
? 4 : mcp-hint,
; openWorldHint (absent => MCP default true; ADVISORY, not an
; effect)
}
naalp-mcp-tool-call = {
; the wrapper body (envelope field 10); its OWN effect is field 7
1 : bstr,
; tool -- the MCP tool definition bytes, carried octet-for-octet
2 : bstr,
; args -- the MCP tool-call argument bytes, carried octet-for-octet
3 : naalp-mcp-annotations,
; annotations -- the signer's transcription; the mapping is over
; these
}
; The approval binds the content id of this call binding. Because it
; names BOTH the tool id and the args id by content id, a changed
; tool DESCRIPTION (new tool_id) OR changed ARGUMENTS (new args_id)
; yields a new call content id, invalidating a prior approval bound
; to the old one.
naalp-mcp-call-binding = {
1 : bstr,
; tool_id -- content id of the tool bytes: multihash(0x20,
; SHA-384(tool))
2 : bstr,
Sammartano Expires 10 March 2027 [Page 88]
Internet-Draft N-AALP September 2026
; args_id -- content id of the args bytes: multihash(0x20,
; SHA-384(args))
}
; ---- signed description / directory primitive (draft-01 addition) -
; A signed, OFFLINE-VERIFIABLE description and discovery layer
; carried on N-AALP's own signed object: the authority is the
; SIGNATURE OVER THE BYTES, never the connection or host that served
; them, so a signed description re-verifies byte-identically when an
; unrelated host serves the same bytes (a bearer credential, not a
; fetched document). It introduces NO new envelope, encoding,
; signature, identity, or audit mechanism: each object below is an
; ordinary COSE_Sign1 over a deterministic-CBOR body, reusing the
; closed effect lattice and the content-id framing. The head of each
; object is SHA-384(body); its content-id is multihash(0x20,
; SHA-384(body)).
;
; A naalp-description lists a service's operations, each a
; naalp-description-operation carrying its effect (lattice) and an
; approval declaration. requires_approval is the uint 1/0 -- the
; N-AALP spine carries no CBOR boolean; a value outside {0,1} is
; rejected (MalformedApprovalFlag), never defaulted. A
; naalp-directory is a signed collection whose members are
; content-ids (the same list-of-content-ids shape the causal partial
; order uses); it carries a monotonic per-signer version so two
; versions can be compared. Two conflicting versions from ONE signer
; -- the SAME directory and version but DIFFERENT members -- are a
; FORK, detected at the FIRST-DIFFERING member position, the same way
; the audit fork proof reports the position of an equivocation.
;
; A naalp-description-import carries a FOREIGN description format (an
; A2A Agent Card, an ANP Agent Description, an AGNTCY Agent Badge)
; octet-for-octet in `foreign` (carriage, not adoption -- the foreign
; bytes MUST NOT be re-serialized, canonicalized, or rewritten) as a
; signed attestation that binds the foreign bytes' content-id
; (multihash(0x20, SHA-384(foreign))) AND an N-AALP effect mapping
; (`operations`) for the described operations. The `importer` is the
; wrapping signer id and is the SOLE authorization identity: a
; verifier recomputes the self-certifying signer id from the
; verifying key and requires `importer` to equal it (ImporterMismatch
; otherwise), so a foreign identity embedded in `foreign` NEVER
; becomes an N-AALP authorization identity -- the confused-deputy
; rule of the MCP profile and foreign carriage.
;
naalp-description-operation = {
1 : tstr,
; name -- operation name (advisory routing key)
2 : effect,
Sammartano Expires 10 March 2027 [Page 89]
Internet-Draft N-AALP September 2026
; effect -- the operation's effect class (lattice)
3 : desc-approval-flag,
; requires_approval -- 1 requires an approval, 0 otherwise (no CBOR
; bool)
}
desc-approval-flag = 0..1
; a boolean transcribed to the spine: 1 = requires approval, 0 = not
naalp-description = {
1 : bstr,
; service -- opaque service id
2 : [* naalp-description-operation],
; operations -- the listed operations
}
naalp-description-format = &(
a2a-agent-card: 1,
; A2A Agent Card
anp-agent-description: 2,
; ANP Agent Description
agntcy-agent-badge: 3,
; AGNTCY Agent Badge
)
naalp-directory = {
1 : bstr,
; directory -- opaque directory id
2 : uint,
; version -- monotonic per-signer version
3 : [* bstr],
; members -- content-ids of the member objects (the causal-order
; shape)
}
naalp-description-import = {
1 : bstr,
; importer -- the wrapping signer id; the SOLE authorization
; identity
2 : naalp-description-format,
; format -- the foreign description format code
3 : bstr,
; foreign -- the foreign description bytes, carried octet-for-octet
4 : [* naalp-description-operation],
; operations -- the N-AALP effect mapping the importer attests
}
; ---- name bindings + the signed A2A task-state profile (draft-01 ad
; Two receipt-CHAINED, OFFLINE-WALKABLE surfaces reusing the audit
; receipt chain unchanged: the head of each object is SHA-384(body),
; the genesis prev is 48 zero bytes, seq is monotonic, and the body
; carries the prior head in `prev`, so editing or omitting a record
; breaks the next record's linkage. They add NO new envelope,
Sammartano Expires 10 March 2027 [Page 90]
Internet-Draft N-AALP September 2026
; encoding, signature, identity, or audit mechanism: each object is
; an ordinary COSE_Sign1 over a deterministic-CBOR body, reusing the
; content-id framing.
;
; A naalp-name-binding maps a name to a signer id and CHAINS onto the
; prior binding for that name. A key ROTATION is a NEW binding at the
; next seq naming the new signer. A binding is DATED BY ITS CHAIN
; POSITION (seq); the envelope's `created` field (naalp-object field
; 6) is advisory only. A name's history is WALKABLE offline (the
; signer succession); a deleted/omitted binding leaves a detectable
; HOLE at the first-broken position (gap-evident, as the audit chain
; / directory fork report position); two bindings by ONE authority at
; the SAME (name, seq) naming DIFFERENT signers are a FORK, reported
; at that seq (the same way the audit fork proof reports an
; equivocation position).
naalp-name-binding = {
1 : tstr,
; name -- the name being bound (a durable, human-readable name)
2 : bstr,
; signer -- the signer id the name maps to at this position
; (signer-id form)
3 : uint,
; seq -- monotonic per-name chain position; seq 0 is the genesis
; binding
4 : bstr,
; prev -- the prior binding's head (48 bytes; genesis = 48 zero
; bytes)
}
; A naalp-task-transition is one signed, receipt-CHAINED A2A
; (Agent2Agent) task-lifecycle state transition. The A2A TaskState
; set is an IMPORTED vocabulary (carriage, not adoption): the A2A
; specification (Section 4.1.3) defines the eight states and the
; terminal/interrupted categories NORMATIVELY (start = submitted;
; terminal = {completed, canceled, failed, rejected}; interrupted =
; {input- required, auth-required}) and leaves the exact legal edges
; to implementations; N-AALP's legal-edge table is DERIVED from those
; documented category rules and is enforced by the endpoint, not by
; this wire production (the wire carries only the from/to state
; values). `card` is the content-id of the A2A Agent Card attestation
; (a naalp-description-import with format a2a-agent-card) that binds
; the task profile to an agent/operation; a transition carrying a
; card other than the profile's bound card is rejected (ForeignCard).
; An illegal edge, a non-contiguous `from`, a transition out of a
; terminal state, or a gap/reorder in the chain is detected
; fail-closed (IllegalTransition / TaskChainBroken), position
; reported.
;
Sammartano Expires 10 March 2027 [Page 91]
Internet-Draft N-AALP September 2026
task-state = &(
submitted: 0,
; acknowledged, not yet started (the start state)
working: 1,
; actively processed
input-required: 2,
; interrupted, awaiting client input
auth-required: 3,
; interrupted, awaiting authentication
completed: 4,
; terminal success
canceled: 5,
; terminal, canceled before completion
failed: 6,
; terminal, finished with an error
rejected: 7,
; terminal, the agent declined the task
)
naalp-task-transition = {
1 : bstr,
; task -- the task id (opaque)
2 : bstr,
; card -- content-id of the bound A2A Agent Card attestation
3 : task-state,
; from -- the source state
4 : task-state,
; to -- the target state
5 : uint,
; seq -- monotonic per-task chain position; seq 0's from =
; submitted
6 : bstr,
; prev -- the prior transition's head (48 bytes; genesis = zero)
}
; ---- governed negotiation, advisory risk labels, and trust (draft-0
; Three signed surfaces reusing the closed effect lattice, the
; content-id framing, and the causal partial order (`causes`)
; UNCHANGED: each object is an ordinary COSE_Sign1 over a
; deterministic-CBOR body. head = SHA-384(body); content-id =
; multihash(0x20, SHA-384(body)).
;
; GOVERNED NEGOTIATION. offer / counter / accept are SIGNED,
; CAUSALLY-LINKED messages: each names its predecessor(s) by
; content-id in `causes` (the same list-of-content-ids shape the
; causal partial order uses). Each SELECTS a profile from the CLOSED,
; PRE-REGISTERED negotiation-profile set -- the negotiation selects a
; pre-registered profile, never a free-form capability string and
; never a runtime-generated handler. The three productions are
Sammartano Expires 10 March 2027 [Page 92]
Internet-Draft N-AALP September 2026
; distinguished by a fixed `role` literal (0/1/2), so an offer body
; never validates against the accept production and vice-versa. An
; accept MUST DESCEND from its offer along the `causes` DAG (enforced
; by the endpoint, NotDescended otherwise); an unknown profile
; (UnknownProfile) or role is rejected fail-closed. The
; machine-readable role/profile source is the negotiation-role /
; negotiation-profile enums below.
negotiation-role = &(
offer: 0,
; the initiating offer (the root; no causes)
counter: 1,
; a counter chaining onto the offer or a prior counter
accept: 2,
; the accept; it MUST descend from its offer
)
negotiation-profile = &(
baseline: 0,
; the baseline capability profile
streaming: 1,
; the native-streaming capability profile
batch: 2,
; the batched-delivery capability profile
)
naalp-negotiation-offer = {
1 : bstr,
; negotiation -- opaque negotiation id (ties the exchange together)
2 : 0,
; role -- offer (fixed literal 0)
3 : negotiation-profile,
; profile -- the selected pre-registered profile (closed set)
4 : [* bstr],
; causes -- content-ids of predecessors (empty for an offer)
}
naalp-negotiation-counter = {
1 : bstr,
; negotiation
2 : 1,
; role -- counter (fixed literal 1)
3 : negotiation-profile,
; profile
4 : [* bstr],
; causes -- names the offer or a prior counter
}
naalp-negotiation-accept = {
1 : bstr,
; negotiation
2 : 2,
; role -- accept (fixed literal 2)
Sammartano Expires 10 March 2027 [Page 93]
Internet-Draft N-AALP September 2026
3 : negotiation-profile,
; profile -- the AGREED pre-registered profile
4 : [* bstr],
; causes -- descends from the offer along this chain
}
; ADVISORY RISK LABELS. A naalp-risk-label is one carried label
; {code, critical}; a naalp-labeled-object carries an effect (the
; lattice) together with a set of risk labels. The vocabulary is a
; closed standard set -- sensitive/egress (gating), reversible
; (informing) -- plus a private/experimental extensible range, so
; `code` is an open uint and the known set is enforced by the
; endpoint + registry (as the signer-counter is an open uint).
; `critical` is the per-carriage must-understand flag (uint 1/0 --
; the spine carries no CBOR boolean); the critical-extension rule
; rejects an unknown CRITICAL label (UnknownCriticalRisk) and ignores
; an unknown non-critical one. LOAD-BEARING INVARIANT: a risk label
; is an ADVISORY dimension, NEVER a fifth effect -- carrying a label
; does not change the object's effect class (the closed effect
; lattice is untouched).
risk-code = uint
; a risk-label code (standard set + private/experimental extensible
; range)
risk-critical = 0..1
; per-carriage must-understand flag: 1 = critical, 0 = advisory (no
; CBOR bool)
naalp-risk-label = {
1 : risk-code,
; code -- the risk-label code
2 : risk-critical,
; critical -- the must-understand flag
}
naalp-labeled-object = {
1 : effect,
; effect -- the effect class (field 7); the effect class derives
; from this ALONE -- risk labels never alter it
2 : [* naalp-risk-label],
; labels -- the advisory risk labels carried on the object
}
; TRUST. A naalp-trust-ref carries a THIRD-PARTY trust statement as a
; CHECKABLE signed object: `reference` is the content-id of an
; EXTERNAL registry record (an ERC-8004-style reputation/identity
; registry record). A verifier CONFIRMS the reference by recomputing
; that content-id over the external bytes; the signature checks. But
; NO wire field WEIGHS the statement -- there is no score, rank, or
; ordering on the wire; the protocol carries trust statements and
; does not weigh them, and which to believe is left to the relying
Sammartano Expires 10 March 2027 [Page 94]
Internet-Draft N-AALP September 2026
; party.
naalp-trust-ref = {
1 : bstr,
; registry -- opaque external-registry identifier (e.g.
; "erc-8004:reputation")
2 : bstr,
; reference -- content-id of the referenced external record
3 : bstr,
; subject -- the subject the statement is about (opaque id)
}
; ---- payment import, UI consent, and portable gateway evidence (dra
; Three signed surfaces reusing the closed effect lattice, the
; content-id framing, the approval + single-use consume ledger, and
; the receipt chain UNCHANGED: each object is an ordinary COSE_Sign1
; over a deterministic-CBOR body. head = SHA-384(body); content-id =
; multihash(0x20, SHA-384(body)). These are NEW productions, not
; changes to an existing object body, so naalp-version stays at 2.
;
; PAYMENT IMPORT. A naalp-payment-import carries a foreign payment
; payload -- an AP2 mandate, an Agentic Commerce Protocol delegated
; token, an x402 payload -- octet-for-octet in `foreign` (carriage,
; not adoption; the foreign bytes MUST NOT be re-serialized,
; canonicalized, or rewritten), and turns it into a value-bearing
; charge N-AALP governs with its OWN added guarantees. `format`
; selects the imported FORMAT from the closed naalp-payment-format
; registry; an unknown format is rejected (UnknownPaymentFormat). The
; naalp-payment-charge-binding names the exact value an approval
; binds by content-id -- including the foreign payload's content-id
; (`foreign_id`, the carriage binding) -- so a wrong
; amount/payee/currency OR a substituted foreign payload yields a
; different content-id and no longer matches the approval
; (ApprovalMismatch). A payment spend is a non_idempotent_write spent
; SINGLE-USE through the consume ledger (AlreadyConsumed on replay).
; There is NO fifth effect and NO payment-specific ledger. There is
; no boolean on the wire.
naalp-payment-format = &(
ap2-mandate: 1,
; AP2 mandate
acp-delegated-token: 2,
; Agentic Commerce Protocol delegated token
x402-payload: 3,
; x402 payload
)
naalp-payment-import = {
1 : naalp-payment-format,
; format -- the imported payment format (closed registry)
2 : uint,
Sammartano Expires 10 March 2027 [Page 95]
Internet-Draft N-AALP September 2026
; amount -- the charge amount in minor units
3 : tstr,
; currency -- the charge currency code (e.g. "USD")
4 : bstr,
; payee -- the payee id (opaque)
5 : uint,
; not_after -- the charge expiry, epoch ms
6 : bstr,
; foreign -- the imported payment payload, carried octet-for-octet
}
; The charge-binding is the payment-import's fields with `foreign`
; replaced by its content-id (`foreign_id`); it shares the import's
; 6-field shape by design and is distinguished by position and
; semantics (the value an approval binds), not by CDDL structure. It
; is a hashing input, not itself an on-wire signed object (only
; naalp-payment-import is signed as an envelope body).
naalp-payment-charge-binding = {
; the exact value an approval binds by content-id
1 : naalp-payment-format,
; format
2 : uint,
; amount
3 : tstr,
; currency
4 : bstr,
; payee
5 : uint,
; not_after
6 : bstr,
; foreign_id -- content-id of the foreign payload: multihash(0x20,
; SHA-384(foreign))
}
; UI CONSENT BINDING. A naalp-ui-event is one shown tool-lifecycle
; event (AG-UI style) in a user- interface event stream,
; receipt-CHAINED by the chain construction: head = SHA-384(body),
; genesis prev = 48 zero bytes, monotonic seq, prior head carried in
; `prev`, so editing or omitting an event breaks the next event's
; linkage (a detectable hole with position). `kind` is the closed
; ui-event-kind set; `action` is the content-id of the exact action
; bytes shown to the user. A human approval (a naalp-approval) binds
; the shown-and-approved action's content-id, and the action actually
; executed MUST hash to that same content-id -- a SUBSTITUTED action
; has a different content-id and is rejected (ActionSubstituted).
; There is no boolean on the wire.
ui-event-kind = &(
shown: 0,
; the action / tool call was shown (rendered) to the user
Sammartano Expires 10 March 2027 [Page 96]
Internet-Draft N-AALP September 2026
args-shown: 1,
; the arguments were shown to the user
approved: 2,
; the user approved the shown action
rejected: 3,
; the user rejected the shown action
)
naalp-ui-event = {
1 : bstr,
; session -- UI session id (ties the stream together)
2 : ui-event-kind,
; kind -- the event kind (closed set)
3 : bstr,
; action -- content-id of the exact action bytes shown at this step
4 : uint,
; seq -- monotonic per-session chain position; seq 0 is the genesis
; event
5 : bstr,
; prev -- the prior event's head (48 bytes; genesis = zero)
}
; PORTABLE GATEWAY EVIDENCE. A naalp-gateway-decision is a SIGNED
; decision object an enforcement gateway of ANY vendor emits as
; portable evidence. Its authority is the SIGNATURE OVER THE BYTES,
; never the connection or host that served them, so it verifies
; offline and RE-VERIFIES IDENTICALLY when served by a party OTHER
; than the gateway (the third-party re-serve property). This is the
; EVIDENCE FORMAT ONLY -- it defines NO policy language. `decision`
; is the closed gw-decision set; `action` is the content-id of the
; action decided about; `policy` is the opaque identity of the
; deciding policy (a name, not a program); `effect` is the effect
; class of the action. An unknown decision code is rejected
; (UnknownGatewayDecision). The gateway is the SIGNER (envelope field
; 5); a verifier resolves its key offline from the self-certifying
; signer id and checks the signature over the bytes.
gw-decision = &(
allow: 0,
; the gateway allows the action
deny: 1,
; the gateway denies the action
hold: 2,
; the gateway holds the action pending a further step
)
naalp-gateway-decision = {
1 : gw-decision,
; decision -- the decision outcome (closed set)
2 : bstr,
; action -- content-id of the action decided about
Sammartano Expires 10 March 2027 [Page 97]
Internet-Draft N-AALP September 2026
3 : bstr,
; policy -- the deciding policy's opaque identity (NOT a policy
; language)
4 : effect,
; effect -- the effect class of the action
? 5 : ordering-disclosure,
; ordering (R1) -- OPTIONAL. ABSENT is read correspondence-only,
; never a stronger claim. The mandatory fields 1-4 are validated
; FIRST: a body failing a mandatory-field check is GwMalformed
; regardless of any optional 5/6, so a look-alike whose field 1 is
; not the gw-decision uint stays GwMalformed.
? 6 : naalp-foreign-profile-pin,
; foreign-profile (R8) -- OPTIONAL; present iff the decision was over
; foreign-protocol evidence. Pins the foreign evidence profile's
; identifier AND revision at decision time (bind the reference, not
; just the class).
}
naalp-foreign-profile-pin = {
1 : tstr,
; id -- the foreign evidence profile identifier, an ABSOLUTE URI
; (opaque per-protocol ids drift at the mapping)
2 : tstr,
; revision -- the profile revision pinned at decision time
}
; ===== EVIDENCE-RECORD FAMILY (R1 ordering + S1 DecisionRecord +
; S3 CheckpointRoot). All additive sibling productions; naalp-version
; stays 2. The accountability triple, carried natively:
; unique-selection + governed-at-T + binding-fixed-by-T. =====
; ORDERING BASIS (R1): what, if anything, establishes
; decision->effect / record->event ORDER, and from which
; observational domain. The weakest claim is the ZERO value ON
; PURPOSE: a record that says nothing, and an existing record whose
; optional ordering field is ABSENT, are both read as
; correspondence-only -- a verifier MUST NEVER infer a stronger
; ordering claim from silence (fail-closed reading direction).
; external-mechanism NAMES the mechanism; whether its operator is
; genuinely distinct from both parties is a structural deployment fact
; checkable in substance at T+n, not a claim the wire can close.
ordering-basis = &(
correspondence-only: 0,
single-boundary: 1,
external-mechanism: 2,
)
ordering-disclosure = {
1 : ordering-basis,
? 2 : bstr,
Sammartano Expires 10 March 2027 [Page 98]
Internet-Draft N-AALP September 2026
; boundary -- the single covering boundary (signer-id form); present
; iff basis = single-boundary
? 3 : bstr,
; mechanism -- the external sequencing mechanism's identity; present
; iff basis = external-mechanism
? 4 : bstr,
; relation -- the log relation binding this record under the
; mechanism (e.g. the content id of the naalp-checkpoint-root the
; record is included under); present only when basis =
; external-mechanism
}
; Well-formedness (fail-closed, native): correspondence-only -> keys
; 2/3/4 absent; single-boundary -> key 2 present, 3/4 absent;
; external-mechanism -> key 3 present (4 optional), 2 absent. Any
; violation is OrderingDisclosureMalformed on the whole record.
term-disposition = {
1 : uint,
; kind -- 1 observed / 2 reported (reuses the producing-boundary
; kind codes)
? 2 : bstr,
; source -- the boundary the term was received from; present iff
; kind = reported
}
enforcement-disposition = &(
enforced: 1,
advised: 2,
)
; THE GOVERNED-DECISION RECORD (S1). A naalp-decision-record is the
; SIGNED record a governed decision point emits that it decided about
; an action under a CLOSED, uniquely-selected condition set. It is the
; carrier for the T/T+n accountability triple: UNIQUE SELECTION (field
; 2, the governing set in the clear as content ids -- identification
; AND availability); GOVERNED-AT-T (field 3, the consume-receipt spent
; at decision time -- decision-time consumption, not
; identity-to-the-act); BINDING-FIXED-BY-T (established off-record by
; inclusion under a witnessed naalp-checkpoint-root). The record is
; deliberately CLOCK-FREE: it carries no claimed time; both time
; properties are POSITIONAL, never a self-asserted timestamp. The
; signature binds THE DECISION, not a retrieval of it.
naalp-decision-record = {
1 : bstr,
; action -- content id of the action decided about
2 : [* bstr],
; governing -- the CLOSED governing condition set, content ids, in
; the clear; may be empty (a decision governed by standing policy
Sammartano Expires 10 March 2027 [Page 99]
Internet-Draft N-AALP September 2026
; alone names that policy object's cid here)
? 3 : bstr,
; consume -- content id of the naalp-consume-receipt that spent the
; governing authority at decision time. PRESENT for an allow that
; consumed a single-use authority; ABSENT for deny/hold (a refusal
; consumes nothing). A deny/hold body carrying a consume ref is
; rejected DecisionMalformed -- nothing was consumed.
4 : gw-decision,
; outcome -- allow / deny / hold (reuses the closed gw-decision set)
5 : ordering-disclosure,
; ordering -- MANDATORY (R1 folded in from the start): the record
; states its ordering basis or states correspondence-only; there is
; no silent default
? 6 : { * uint => term-disposition },
; terms -- OPTIONAL per-term observed-vs-reported dispositions, keyed
; by THIS record's own field numbers (1..5). A key outside the field
; set is TermDispositionMalformed (fail-closed).
? 7 : enforcement-disposition,
; enforcement -- OPTIONAL: whether the producer ENFORCES the outcome
; or only ADVISES it (the producer's own unverifiable self-account).
}
; THE NEITHER-PARTY ANCHOR PRIMITIVE (S3, RFC 9162-style). A
; naalp-checkpoint-root is a log operator's SIGNED Merkle tree head
; over a leaf set of record content ids. Tree construction follows RFC
; 9162 Section 2.1: leaf hash = HASH(0x00 || leaf), interior node =
; HASH(0x01 || left || right), instantiated with the profile hash
; SHA-384 (48-byte heads), the 0x00/0x01 prefixes supplying leaf/node
; domain separation. Checkpoints chain by prev (genesis = 48 zero
; bytes) so a withheld or reordered checkpoint breaks a link.
naalp-checkpoint-root = {
1 : bstr,
; log -- the log operator's signer id
2 : uint,
; size -- leaf count at this checkpoint
3 : bstr,
; root -- the Merkle tree head over the leaf set (48 bytes)
4 : bstr,
; prev -- the prior checkpoint root value (48 bytes; genesis zero)
5 : uint,
; at -- the log's time anchor, epoch ms (advisory)
}
; A witness COUNTERSIGNS one exact checkpoint by content id. The wire
; carries the cosignature; whether the witness's observational domain
; is genuinely distinct from both parties is a structural deployment
; fact a T+n verifier checks in substance -- the wire hook for the
; neither-party property, stated honestly as a hook.
Sammartano Expires 10 March 2027 [Page 100]
Internet-Draft N-AALP September 2026
naalp-witness-cosign = {
1 : bstr,
; witness -- the witness's signer id
2 : bstr,
; root -- content id of the exact naalp-checkpoint-root cosigned
3 : uint,
; at -- the witness's own time anchor, epoch ms (advisory)
}
; Inclusion proof: a leaf existed in the tree the root commits to (RFC
; 9162 Section 2.1.3.1 path recomputation, SHA-384-profiled). Record
; cid as a leaf under a witnessed checkpoint = existed-no-later-than
; the checkpoint (the binding-fixed-by-T leg). Verification recomputes
; the path bottom-up and compares against the named root, fail-closed
; (InclusionProofInvalid).
naalp-inclusion-proof = {
1 : bstr,
; root -- content id of the naalp-checkpoint-root proven against
2 : bstr,
; leaf -- the included record's content id (the leaf value)
3 : uint,
; index -- the leaf's 0-based position in the tree
4 : [* bstr],
; path -- the audit path, leaf-to-root sibling hashes (48 bytes each)
}
; PORTABLE EGRESS EVIDENCE (E6.3). A naalp-egress-attestation is a
; SIGNED attestation a gateway/sidecar emits that an object of a given
; effect class, bound to a given audience, crossed an egress boundary
; at a given time -- third-party re-serve, payload-free. content_bound:
; digest is the crossed object's T1 content id. content_free: digest is
; a hiding commitment SHA-384(content_id || salt), openable later by
; the gateway revealing (content_id, salt).
egress-binding = &(
content-bound: 0,
content-free: 1,
)
naalp-egress-attestation = {
1 : egress-binding,
; binding -- content_bound vs content_free (closed set)
2 : bstr,
; digest -- content id (content-bound) or commitment (content-free)
3 : effect,
; effect -- the effect class of the crossed object
4 : bstr,
; audience -- the bound destination (empty-permitted)
5 : uint,
; at -- the gateway's egress timestamp, epoch ms
Sammartano Expires 10 March 2027 [Page 101]
Internet-Draft N-AALP September 2026
? 6 : ordering-disclosure,
; ordering -- OPTIONAL (R1); ABSENT is read correspondence-only
}
; MANUFACTURING PHYSICAL-HAZARD (Mfg-F). `hazard` is a dimension
; ORTHOGONAL to `effect`: effect (field 7) is DATA reversibility (can
; the state change be undone); hazard is PHYSICAL danger -- a
; data-reversible action may still be a high physical hazard (e.g. a
; tool re-approaching a work envelope). The two are never merged and
; neither is derived from the other. A hazard CLAIM rides as the
; critical cext key 16 (naalp-hazard-claim) on the acting object; a
; hazard AUTHORIZATION is a standalone Governance (0x0004) kind 7
; object (naalp-hazard-authorization), referenced by the acting
; object's `causes`. Coverage: EXACT class match AND full containment
; of the claim envelope in the grant envelope (frame id equal; same
; axis count and order; every claim axis inside the matching grant
; axis; claim speed_bound <= grant speed_bound; claim window a
; sub-interval of the grant window); any single failing dimension
; denies the whole claim (HazardNotCovered). All numeric bounds are
; fixed-point integers (signed mm; unsigned mm/s) -- no floats, per the
; spine's deterministic-CBOR subset. Map keys are 1-based.
;
; hazard-class is a CLOSED 0..4 enum. A DECODE-TIME rule (like effect's
; unknown->destructive): an absent or unrecognized raw hazard value
; MUST normalize to the HIGHEST class (4, motion-in-shared-space) --
; never to none(0) or a weaker value -- so a missing declaration fails
; safe (HazardUnknown when no claim exists; else HazardNotCovered
; against a lower-class grant). CDDL cannot see the absence or
; invalidity of a raw wire value, so this is a MUST on the decoder, not
; this grammar.
hazard-class = &(
none: 0,
tool-actuation: 1,
thermal: 2,
energy-release: 3,
motion-in-shared-space: 4,
)
; A named coordinate frame plus a signed axis-aligned bounding region
; in that frame, integer millimeters. `axes` MUST be non-empty and
; every entry MUST satisfy min <= max (HazardMalformed otherwise). The
; frame id is integrator-defined; N-AALP requires only that a claim's
; frame id equal the grant's for containment to be checkable.
spatial-bounds = {
1 : tstr, ; frame -- MUST be Unicode NFC
; (NonNFC else)
2 : [ + [min: int, max: int] ], ; axes -- per-axis [min,max], mm,
; signed
Sammartano Expires 10 March 2027 [Page 102]
Internet-Draft N-AALP September 2026
}
; A validity window in the epoch-ms convention of the spine.
hazard-window = {
1 : uint, ; not_before -- epoch ms
2 : uint, ; not_after -- epoch ms
}
; The full physical envelope a hazard claim or authorization bounds
; itself by. All three fields MANDATORY: a silently-absent axis, speed,
; or window is a MALFORMED envelope (HazardMalformed), never
; "unconstrained" (which would fail OPEN in a physical-safety context).
hazard-envelope = {
1 : spatial-bounds, ; spatial
2 : uint, ; speed_bound -- max instantaneous speed, mm/s
3 : hazard-window, ; window
}
; A signed physical-hazard claim, carried as cext key 16 on the acting
; object. `class` and `envelope` are BOTH mandatory (HazardMalformed
; otherwise).
naalp-hazard-claim = {
1 : hazard-class, ; class -- the declared hazard class
2 : hazard-envelope, ; envelope -- the bounds this claim asserts
}
; A signed physical-hazard authorization (a grant): a standalone
; Governance (0x0004) kind 7 object. Same {class, envelope} shape as
; the claim; coverage is the exact-class-match + full-containment rule
; above (any failing dimension => HazardNotCovered).
naalp-hazard-authorization = {
1 : hazard-class, ; class -- the class this authorization covers
2 : hazard-envelope, ; envelope -- the bounds this authorization covers
}
Appendix B. Acknowledgments
N-AALP builds on the N-PAMP substrate.
Author's Address
Shawn Sammartano
BubbleFish Technologies, Inc.
Email: naalp-editor@bubblefish.sh
Sammartano Expires 10 March 2027 [Page 103]