← Back to Home

Agent Federation Protocol (AFP) Specification

Verifiable, metadata-private federation between tenants

Version 0.3.9-draft | Status: Draft | License: Apache 2.0 | Domain: AFP.dev

Date: 2026-08-16

Abstract

The Agent Federation Protocol (AFP) is the protocol layer for verifiable interaction between tenants. It defines how two tenants coordinate work across organizational boundaries, how an instance proves operational health to an operator without disclosing its work, and how an instance's identity survives a change of deployment topology or operator.

AFP v0.3.0 is a deliberate narrowing of AFP v0.2.0. The v0.2.0 draft was scoped as an open-marketplace federation protocol — third-party agent registration, capability-discovery feeds, task delegation as a central wire feature, observation contribution, reputation accumulation, and a federated-to-native trust ladder. ADAMAS's product scope has since narrowed to a platform for life-sciences work run by named domain entities that are themselves ADAMAS multi-agent systems. There are no third-party agents to integrate and no marketplace to police. Most of v0.2.0's protocol surface addressed problems that no longer exist; v0.3.0 retires it.

What remains is smaller, sharper, and built on three foundations that did not exist in v0.2.0: KERI-aligned self-certifying identity, ZKA and ZKC as substrate (with ZKM as an optional authorization peer), and a structural operator-blindness guarantee — the operator of any AFP transport is cryptographically and topologically incapable of observing tenant work content, and incapable of reconstructing the graph of which tenants coordinate with which.

This specification targets ZKA v0.9.0-draft, ZKC v0.4.1-draft, and AFP-KDC v1.0.2. ZKA resolves the AFP §12.7 bundle dependency by carrying ZKA/Coord work-attestation and profile-aware ZKC proofs inside zka:bundle:v1. ZKC v0.4.1-draft supports anonymous, scoped, and explicitly-consented linkable presentation profiles; AFP selects among them per flow and binds every accepted proof to the KERI-authenticated presenter, credential-subject AID, APR signer, and AFP session context (§5.9). AFP-KDC v1.0.2 retains the v1.0.1 field-domain correction and additionally pins the complete Poseidon2 permutation, sponge, constants digest, and shared ZKA/ZKC/ZKM conformance vectors without changing any derived value (Appendix A). AFP inherits the six Freedom Safeguards defined normatively in ZKA §1.6 — including Safeguard 6 (Verifier Accountability), which is load-bearing for unknown-counterparty federation (ADR-0004).

The machine-checked composition boundary is epoch zka:protocol-family:2026-08-15.2 in ZKA's canonical protocol-family/ package and migration note. AFP vendors a commit-locked snapshot and rejects unknown, unmatched, or ambiguous version/profile combinations.

Version 0.3.0 Changes (relative to v0.2.0-draft):

Version 0.3.1-draft Changes (relative to v0.3.0), per ADR-0003:

Version 0.3.5-draft Changes (relative to v0.3.4-draft):

Version 0.3.6-draft Changes (relative to v0.3.5-draft):

Version 0.3.7-draft Changes (relative to v0.3.6-draft):

Version 0.3.8-draft Changes (relative to v0.3.7-draft):

Version 0.3.9-draft Changes (relative to v0.3.8-draft):

Version 0.3.9-draft (ADR-0005 tenant definition) Changes (relative to v0.3.9-draft), per ADR-0005:

Version 0.3.9-draft (ADR-0005 authority naming) Changes (relative to v0.3.9-draft (ADR-0005 tenant definition)), per ADR-0005 item 4:

Version 0.3.9-draft (ADR-0006 interaction-context fold) Changes (relative to v0.3.9-draft (ADR-0005 authority naming)), per ADR-0006:

Version 0.3.4-draft Changes (relative to v0.3.3-draft), per ADR-0004:

Version 0.3.3-draft Changes (relative to v0.3.2-draft):

Version 0.3.2-draft Changes (relative to v0.3.1-draft):


1. Overview

1.1 Design Goals

  1. Tenant Federation: Two tenants coordinate work across organizational boundaries with neither side revealing more than the work requires.
  2. Operator Blindness: Whoever operates an AFP transport can verify the platform is healthy without learning what tenants do, who they coordinate with, or what moves between them. This is structural, not policy.
  3. Identity Continuity: A tenant's identity, reputation relationships, and attestations survive a change of deployment topology or operator.
  4. Substrate Reuse: AFP defines how ZK-mediated interaction is initiated, authenticated, sequenced, and recorded. It does not reinvent the cryptography of ZKA, ZKC, or ZKM.
  5. Freedom by Default: The six Freedom Safeguards (ZKA v0.9.0-draft §1.6) hold across every AFP interaction.
  6. Minimality: v0.3.0 specifies the smallest protocol surface that delivers the three relationships of §1.3. Anything expressible as tenant-internal behavior is not in the protocol.

1.2 What AFP Is Not

A clean inventory of what v0.3.0 removes, so conforming implementations do not carry retired surface:

v0.2.0 concern v0.3.0 status
Agent manifest schema for third-party agents Removed. Entities have manifests inside their instance; nothing of that shape crosses the protocol boundary.
Capability discovery as a wire protocol / marketplace feed Removed as a feed. A narrower case survives — "what does this tenant expose to a federated counterparty" — under §5 tenant ↔︎ tenant, negotiated bilaterally, not advertised.
Task delegation as a central wire protocol Removed as a central feature. Intra-tenant routing is tenant-internal; cross-tenant work requests are a specific message class under §5.
Observation contribution from external agents Removed. Observations originate only from tenant-internal entities.
Individual-agent reputation accumulation Removed. Instance-level trust is handled by bilateral federation agreements (§5.5).
Trust-level escalation ladder (federated → integrated → native) Removed. There is one trust level — verified conforming instance under a verifier-accepted attestation authority (§6.3.1) — plus whatever trust two instances either negotiate bilaterally (a §5.5 agreement) or establish per-interaction by ZKC attestation (open federation, §5.8). Neither is a portable score, and neither the root class nor the root authority is a rank: an authority is accepted or not accepted by a given verifier, and acceptance confers no tier. Whether a party requires instance attestation at all is that party's own policy (§1.5.1, §6.3.1).
HTTP bridge Removed. Cross-instance traffic is between tenants on a common transport; no HTTP-only third-party use case remains.
Operator-run SaaS transport with transport-observed telemetry Removed. See §3, §7.

Where any retired concern survives, it survives as tenant-internal implementation, never as AFP wire protocol.

The retirements above are restated, not reopened. Generalizing who may implement a tenant (§1.5.1, §1.6, §2.1) changes the class of software that may sit at each end of a session; it restores no row of the table. A conforming implementation MUST NOT read that generalization as authority to:

Surface with any of these effects, other than the constructs §14 expressly permits, is retired v0.2.0 surface under a new name and is outside AFP regardless of the name it carries.

1.3 The Three Relationships

AFP v0.3.0 is the protocol layer for exactly three relationships, all between identifiable, cryptographically verifiable principals:

  1. Tenant ↔︎ tenant — two tenants, each running their own entities, collaborating across organizational lines. The protocol makes the interaction safe, verifiable, and minimally disclosing. This is the only meaning of "federation" in v0.3.0: two instances talking, never a third-party agent integrated into an instance. The two instances may be known to each other — joined by a pre-existing bilateral federation agreement (§5.5) — or mutually unknown, establishing trust per-interaction by ZKC compliance attestation (open federation, §5.8). Both are tenant↔︎tenant federation; they differ only in how trust is established, never in who the principals are.
  2. Tenant ↔︎ operator — the operator of a transport needs to know the platform is healthy without learning what tenants do. The protocol enforces that asymmetry structurally.
  3. Identity continuity — a tenant's identity and accumulated relationships survive a move between deployment topologies (managed transport ↔︎ self-hosted appliance) and a change of operator.
Protocol Relationship
ZKA v0.7 Settlement and coordination substrate. Tenant ↔︎ tenant work artifacts, attestations, and value transfers are ZKA notes and ZKA/Coord proofs. AFP defines the interaction envelope around them. ZKA in turn normatively references the Key Derivation Core (Appendix A) for its key hierarchy and, as of v0.6.2, carries ZKA/Coord work-attestation proofs inside zka:bundle:v1 (§12.7). The AFP↔︎ZKA dependency remains acyclic: ZKA depends only on Appendix A, which depends on nothing else in AFP (§2.2).
ZKM v0.1.0-draft Optional principal-issued spend-authorization layer. An AFP tenant's agent MAY exercise a ZKM mandate inside an AFP session, but AFP owns bilateral session policy while ZKM owns unilateral mandate state. Both constraints must pass independently; neither mutates or replaces the other. ZKM references KDC v1.0.2 for its seed/key-derivation model and owns its additional zkm/* tags.
ZKC v0.4.0 Compliance substrate. Tenant ↔︎ operator telemetry and tenant ↔︎ tenant compliance claims are profile-aware ZKC proofs. AFP defines which claims are proved, their cadence, identity/session binding, accepted presentation profiles, downgrade rules, and failure semantics (§5.9). ZKC references AFP-KDC v1.0.2 for credential binding and owns the v1/v2 proof, rebind, APR, scope, pseudonym, and receipt cryptography.
KERI Identity substrate. AFP identities are KERI Autonomic Identifiers (AIDs) with Key Event Logs (KELs). See §4. KERI machinery sits above the Key Derivation Core: KDC produces seeds and keys; AFP §4 wraps a KERI AID around them.
DGP Out of AFP scope except for the boundary rule in §1.4.1. A tenant may expose a DGP gateway to a third-party tool, but that gateway is not an AFP endpoint, does not receive an AFP discovery feed, and does not expand the AFP trust boundary.
PRP v0.2 · VXP v0.2 AFP profiles, not peer protocols (ADR-0003). PRP (pairwise reconciliation) and VXP (value exchange) are named behaviors over AFP's Bilateral Session Core (§12); each adds its own atom kinds and state machine but reuses the core's session, identity binding, NATS namespace, dispute/arbiter model, commitment + optional-anchor primitive, session-envelope bundle, and Freedom-Safeguards application. See §13 and PROFILE_MIGRATION_NOTE.md.

1.4.1 DGP Gateway Boundary

A tenant may expose a DGP gateway to a third-party tool, internal integration, or adjacent tenant-internal workflow. That gateway is a DGP surface controlled by the tenant; it is not an AFP endpoint and does not become part of AFP federation.

The boundary is:

This rule keeps DGP gateways useful at tenant integration edges without recreating the retired AFP v0.2 discovery, observation, or trust-score surfaces.

1.5 Conformance Terminology

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, MAY, and OPTIONAL are to be interpreted as in RFC 2119 / RFC 8174.

A conforming AFP v0.3.0 implementation satisfies all normative requirements of §3–§8 and all six Freedom Safeguards (ZKA v0.9.0-draft §1.6). Sections or subsections marked Draft state a normative requirement whose concrete mechanism is not yet finalized; a conforming implementation MUST satisfy the requirement and MAY choose any mechanism that does, pending a future revision that fixes the mechanism.

1.5.1 The tenant conformance profile

A conforming AFP tenant satisfies the tenant conformance profile stated below. The two conformance subjects are distinct: a conforming implementation is a codebase that satisfies the normative requirements of §3–§8 and the six Freedom Safeguards (§1.5); a conforming tenant is a deployed runtime, addressed by its tenant root AID (§1.6, §2.1), that federates as a principal (§2.1) and therefore additionally carries the tenant-side obligations enumerated here. The profile is a consolidation of requirements this specification already imposes across §3–§12, stated as one list rather than left to be inferred from the identity of the implementer; with one exception it introduces no requirement that is not already normative elsewhere in this document, and it introduces no wire surface. The exception is item 7: the duty it states was previously implicit — discharged by the identity of the implementer rather than stated as an obligation — and this revision makes it explicit here and in §5.7 simultaneously. Making an implicit duty explicit is not the same as imposing a new one, but the profile does not claim §5.7 as prior authority for it. ADAMAS, published by Pyramidal Inc., is the reference implementation of this profile; that attribution is a statement of fact about one implementation, not a conformance gate, and publishing an implementation is unrelated to standing as an attestation authority (§6.3.1).

A conforming tenant MUST satisfy all of the following:

  1. KERI identity custody (§4). The tenant MUST be identified by a KERI tenant root AID governed by a KEL, with each entity a delegated AID whose inception is anchored in that KEL (§4.2). Pre-rotation is REQUIRED on every KEL event, and the tenant MUST reject a KEL whose rotation events do not honor prior pre-rotation commitments (§4.4). The tenant MUST publish its witness set and threshold as part of its KEL, MUST check that a counterparty's KEL events carry receipts from at least that counterparty's declared witness threshold, and MUST carry its witness configuration across a topology change (§4.5, §4.5.1, §4.5.2). Rotation, recovery, and succession run as KEL events, with guardian designation itself a published KEL event (§4.6); the tenant MUST NOT treat a KEL rotation alone as sufficient while notes or credentials derived from the old seed remain in use, and MUST publish the §4.6.1 rotation presentation when a rotation affects ZKA or ZKC material, under the sequencing and verifier rules of §4.6.2–§4.6.3.
  2. Operator blindness and deployment topology (§3, §7.1, §7.3). The tenant MUST deliver both OB-1 (content) and OB-2 (coordination graph); OB-1 alone does not satisfy the invariant (§3.1). OB is a capability standard, not a conduct standard: the operator must be unable to obtain the protected information (§3.2). The tenant MUST NOT route tenant↔︎tenant coordination traffic through a transport whose operator can correlate the endpoints (§3.3), and MUST NOT deploy in the topology §7.3 excludes — one in which an operator runs the transport that tenant↔︎tenant coordination traffic transits. Its OB-relevant behavior MUST be auditable (§7.1). Where OB-2 is not structurally satisfied, the tenant MUST NOT represent it as satisfied and MUST disclose the limitation (§3.5).
  3. Routing participation, including cover traffic (§8). The tenant MUST route coordination traffic through a layer that satisfies the §8.2 conformance properties, which are the conformance test (§8.1, §8.2). Constant-rate emission is REQUIRED, not optional: the tenant MUST emit coordination-plane traffic into its routing layer at a constant, scheduled rate at all times while federated, whether or not it has real coordination traffic to send, with real afp.* messages (§5.3) slotted into that stream (§8.5). Where the deployment uses the AFP Federation Mixnet — the construction v0.3.0 adopts — §8.4–§8.5 fix the packet form, the indistinguishability of cover from real packets, and a schedule derived from the active directory epoch and the tenant's local scheduler rather than from the presence of application work; missed cover slots are an OB-2 health failure and MUST be disclosed in operator telemetry without identifying counterparties (§8.5). An implementation MAY satisfy §8.2 by another construction, but it MUST satisfy §8.2, including an equivalent constant-emission property (§8). A tenant that does not emit at a fixed rate is not OB-2-conforming and MUST disclose this per §3.5.
  4. The bilateral session core (§12). Every AFP-mediated interaction the tenant takes part in MUST run inside a bilateral session (§12.1): exactly two principals, mutually authenticated against the declared witness threshold, end-to-end encrypted, metadata-private under §8, anchored to one of the three trust bases of §12.1, and context-bound. The tenant MUST carry the core's establishment, lifecycle, and timeout rules (§12.2), identity binding (§12.3), namespace ownership (§12.4), dispute and arbiter model (§12.5), commitment and optional-anchor primitive (§12.6), and session-envelope wire format (§12.7). Any profile the tenant runs MUST run over that core and MUST NOT re-derive or weaken it (§13.4).
  5. Operator telemetry (§6). Toward any operator to which the tenant stands in the §6 tenant↔︎operator relationship, the tenant MUST supply operational assurance only as claims it proves — never by permitting observation of tenant work or the coordination graph (§6.1) — pushed by the tenant on a telemetry subject at the tenant's own timing (§6.5). Every such proof MUST use a registered predicate identifier and supported version from the §6.3 catalog at that predicate's stated cadence; a proof outside the catalog is not AFP-conforming. Every operator-facing catalog proof MUST bind the tenant AID, operator AID, predicate ID, predicate version, telemetry subject, telemetry window, and active ZKC presentation profile into the proof transcript (§6.3), and every self-attested predicate MUST carry the public-sample binding of §6.4. This surface is identical in every deployment topology (§6.7).
  6. The six Freedom Safeguards (§9). The tenant MUST satisfy all six Freedom Safeguards (ZKA v0.9.0-draft §1.6) as §9 applies them to AFP — predicate pluralism, minimal disclosure, no revocability, open-source predicates, agent exit rights, and verifier accountability — and as §12.8 applies them to every bilateral session. A profile the tenant runs inherits the §12.8 application and MUST NOT weaken any safeguard (§12.8, §13.4).
  7. Tenant-internal obligations (§5.6, §5.7). The tenant MUST contain, capability-scope, and observe its own entities; the mechanism is tenant-internal and outside AFP scope (§5.7). Routing an inbound work request to the correct entity within the tenant is likewise tenant-internal, and the tenant MUST keep that boundary: no AFP wire message carries a tenant's internal routing topology (§5.6). These are tenant-side duties discharged behind the tenant boundary; nothing in this item crosses the wire.

Where a provision cited above is itself OPTIONAL or conditional — open federation (§5.8), or §8's allowance for an alternative construction satisfying §8.2 — the profile inherits that condition unchanged and does not strengthen it. The profile confers no discovery, registry, reputation, or capability-publication surface of any kind (§1.2); it states what a tenant must satisfy, not what a tenant may advertise. A tenant that does not meet an OB-relevant obligation of this profile MUST NOT claim the corresponding property and MUST disclose the limitation under §3.5.

A shortfall does not by itself end tenanthood or bar a session. Where §1.2, §12.1, §12.5, §13.1, and §5.8 speak of a verified conforming instance, of a verified conforming instance under a verifier-accepted attestation authority, or of conforming tenants, they state the standard each party is entitled to expect and to verify — not a protocol-level registry check, and not a requirement that every session carry an instance attestation. Whether a party requires instance attestation at all, and which (root class, root authority) pairs it accepts if it does, is that party's own policy (§6.3.1, Safeguard 1); the §12.1 trust bases include one — a pre-existing §5.5 agreement — that may require no attestation whatever. Acceptance remains each counterparty's own policy (§5.5, §5.8, §6.3.1, Safeguard 1): a tenant that has disclosed a shortfall under §3.5 or §8.9 remains a principal (§2.1), and whether a given counterparty opens or continues a session with it is that counterparty's decision. AFP defines no gate that excludes a non-conforming tenant from principalhood, and none that compels a counterparty to transact with one.

Conformance to this profile is asserted and bilaterally verified, not certified: there is no conformance registry, mark, or published implementer list (§1.2), and a counterparty's acceptance of an implementation is its own policy decision (§5.5, §5.8, §6.3.1). An attestation root authority (§6.3.1) is not a conformance certifier: a root authority attests what build a machine subject runs, never that a tenant satisfies this profile. Accepting an authority is not accepting a conformance claim, and no authority — named in this specification or otherwise — certifies conformance. The formal multi-vendor conformance program — published tenant-profile vectors as a normative gate — is deferred (§10).

1.6 Notation

Symbol Meaning
H(domain, x...) The exact AFP-KDC v1.0.2 Poseidon2 sponge over BN254 with a field domain tag derived per Appendix A
AID KERI Autonomic Identifier
KEL Key Event Log
M Tenant master seed (256 bits)
M' Per-entity sub-seed, M' = HKDF(M, "entity/{name}")
π A zero-knowledge proof (UltraHonk / BN254 unless stated otherwise)
dip KERI delegated-inception event
tenant A runtime that federates as a principal, addressed by its own tenant root AID; a tenant MUST satisfy the tenant conformance profile (§1.5.1)
entity A named domain multi-agent system within a tenant, addressed by a delegated AID

2. Architecture

2.1 Principals

AFP recognizes exactly three kinds of principal. All three are KERI AIDs (§4); they differ only in role, never in identity machinery.

HyperTalk sessions are not AFP principals. A HyperTalk session is a tenant-facing surface; when work must cross a tenant boundary, the entity is the principal, never the session. Within-tenant user identity is internal access control and outside AFP scope.

A §6.3.1 attestation root authority is likewise not an AFP principal, and the enumeration above remains closed at three. An authority is an AID whose published trust material a verifier may elect to accept into its own verification policy; it addresses no AFP message, opens no session, holds no role under this section, and holds no protocol privilege. Its AID is ordinary KERI machinery (§4) used outside the principal roles, exactly as the AIDs of a mix node, a witness, or an external digest source are. Standing as an authority is neither a fourth principal class nor a tier of any of the three; where the same legal entity is also an operator, the roles are independent (§8.6).

2.2 Layering

+-------------------------------------------------------------+
|                     AFP v0.3.9                             |
|   - relationship envelopes (tenant<->tenant, tenant<->op)   |
|   - identity continuity (KERI KEL exchange, witnesses)      |
|   - metadata-privacy routing requirements                   |
+-------------------------------------------------------------+
        |                  |                   |
        v                  v                   v
+---------------+  +----------------+  +----------------------+
|   ZKA v0.9    |  |  ZKC v0.4.1   |  |        KERI          |
| notes, coord, |  | predicates,    |  | AIDs, KELs,          |
| bundles       |  | proofs         |  | witnesses, rotation  |
+---------------+  +----------------+  +----------------------+
        |                  |                   |
        +------------------+-------------------+
                           |
                           v
            +-------------------------------+
            |  Key Derivation Core (KDC)    |
            |  v1.0.2 -- AFP Appendix A:     |
            |  master-seed derivation,      |
            |  Poseidon2/BN254 domain tags, |
            |  per-entity HKDF sub-seeds    |
            +-------------------------------+
                           |
                           v
            +-------------------------------+
            |   Transport (NATS / leaf      |
            |   nodes) + metadata-privacy   |
            |   routing layer (§8)          |
            +-------------------------------+

AFP sits above ZKA, ZKC, and KERI and below nothing — it is the top protocol of the federation stack. ZKM is a composition peer rather than a new AFP layer: it can constrain an agent's spend inside a session without replacing AFP policy or ZKA settlement. AFP never reimplements a primitive that ZKA, ZKC, ZKM, or KERI already defines; where this specification needs such a primitive it cites the owning specification.

Note on the Key Derivation Core. Master-seed derivation, the Poseidon2/BN254 field-tag scheme, and the per-entity HKDF sub-derivation are depended on by ZKA, ZKC, ZKM, and AFP alike. They are specified once as the self-contained AFP-KDC v1.0.2 module in Appendix A. v1.0.1 made the deployed Blake2b512 field-tag derivation explicit; v1.0.2 pins the already-deployed Poseidon2 parameters and shared conformance vectors. Neither patch changes a derived value from v1.0.0. KDC owns the six derivation tag strings. Each referencing protocol owns its operational tag strings and MUST derive their field constants with §A.3, save for the ZKA pool-adjacent fixed-byte fold tags carved out in §A.4. A dependent pins AFP-KDC v1.0.2 without depending on any other AFP section, so the dependency graph remains acyclic.

2.3 Transport

AFP messages travel over a NATS-based transport (subjects, JetStream for durable streams, leaf nodes for cross-instance links). Transport selection is otherwise unconstrained. The transport is assumed untrusted for confidentiality purposes: §3 and §8 specify what the transport operator may and may not learn, and those guarantees MUST hold regardless of who runs the transport.

The v0.2.0 HTTP bridge is removed. All AFP traffic is between tenants speaking AFP over the common transport.


3. The Operator-Blindness Invariant

This section is normative and central. It states the property that AFP is taken to promise: a tenant need not trust the operator, because the operator is structurally incapable of compromising tenant confidentiality.

3.1 Statement

Invariant OB (Operator Blindness). No operator of an AFP transport shall be able to learn, from operating that transport:

  1. the content of any tenant's work — work artifacts, payment amounts, counterparty identities, coordination payloads; or
  2. the coordination graph — which tenant is coordinating with which.

OB-1 (content) and OB-2 (graph) are distinct properties with distinct mechanisms. A conforming implementation MUST deliver both. Delivering OB-1 while leaving OB-2 unmet — encrypted payloads over an operator-observable relationship graph — does not satisfy this invariant.

3.2 Capability, not conduct

OB is a statement about capability, not conduct. An implementation satisfies OB only if the operator cannot obtain the protected information, not if the operator chooses not to. "The operator does not look" is not conformance. A design in which the operator could reconstruct the coordination graph by correlation — even if no operator ever does — fails OB.

This follows the same discipline as ZKA Safeguard 3 (No Revocability): the absence of a capability, verifiable by inspection, rather than a promise of good behavior.

3.3 Consequence for deployment topology

OB has a hard consequence. Whoever runs a transport can observe that transport's traffic graph — connection metadata, timing, correlation of endpoints. Therefore, an operator who runs the transport that tenant↔︎tenant traffic transits cannot, by §3.2, satisfy OB-2 for that traffic. Payload encryption (OB-1) is achievable on an operator-run transport; relationship-graph privacy (OB-2) is not.

AFP v0.3.0 resolves this by requiring that the operator is not on the coordination path:

A conforming AFP deployment MUST NOT route tenant↔︎tenant coordination traffic through a transport whose operator can correlate the endpoints. Where the §8 routing layer is not yet available in a given deployment, that deployment MUST NOT claim OB-2 conformance and MUST disclose the limitation to its tenants (§3.5).

3.4 Metadata is in scope

OB-2 exists because encrypting content while leaking metadata is a known and unacceptable failure mode for a confidentiality platform. AFP treats the following as protected metadata, not merely the payloads they carry: the existence of a coordination session between two named tenants; the pairing of tenant AIDs in a session; the timing and volume correlation that would reveal such a pairing; and stable identifiers (NKeys, subject names) that would let an observer link a tenant across sessions. §8 specifies the routing requirements that protect these.

3.5 Honest disclosure

A deployment that delivers OB-1 but not OB-2 — for example, an interim deployment before the §8 routing layer is operational — is a valid lesser tier, but it MUST be labeled as such. An implementation MUST NOT represent OB-2 as satisfied unless it is structurally satisfied per §3.2. Claiming appliance-grade or routing-layer-grade confidentiality for a deployment that does not have it is a conformance violation, independent of any cryptographic correctness.

The duty stated in this subsection is a duty of the party making the claim. Two of its addressees are AFP conformance subjects (§1.5): a deployment discloses the tier it actually delivers, as above, and an implementation that cannot support inspection of its OB-relevant behavior discloses that limitation rather than claiming OB (§7.1). A §6.3.1 attestation root authority is named here as a third addressee, but on a different footing, because an authority is not an AFP principal and not an AFP conformance subject (§1.5, §2.1): the character of its attestation is fixed by §6.3.1's class rules, and the duty to characterize it truthfully in published trust material is given normative effect as a verifier acceptance condition in §6.3.1 rather than as a conformance obligation this subsection could enforce. An authority that wants to be accepted discloses accordingly; nothing here binds a party AFP does not otherwise bind. This subsection is otherwise scoped to OB-1/OB-2 claims and neither imposes an OB obligation on an authority nor converts an attestation into an OB claim.


4. Identity

AFP v0.3.0 adopts KERI-aligned self-certifying identity. This section is normative. It supersedes the hash-derived identifier model and the flat nkey_history array of the v0.2.0 Identity Addendum.

4.1 Why KERI

A self-certifying identifier can be verified from the identifier and its Key Event Log alone, with no registry lookup and no dependency on any operator. This is the only identifier class that survives a change of operator and a move to a self-hosted appliance — the v0.2.0 hash-derived identifier could not, because verifying it required the issuing operator's registry. Identity continuity (§1.3 relationship 3) therefore requires KERI alignment; it is not optional.

4.2 Identifier hierarchy

AFP identity is a two-level KERI delegation tree per tenant, with a master-seed derivation beneath it.

4.3 Relationship to ZKA and ZKC keys

The master-seed derivation tree extends below the KERI AID, and ZKA/ZKC keys derive from the per-entity sub-seed, not from the tenant master directly:

            Tenant master seed  M
                     |
                     |  KERI tenant root AID  (KEL governed by witnesses)
                     |
        +------------+------------+
        |                         |
   M' = HKDF(M,              M' = HKDF(M,
   "entity/viksana")         "entity/alochana")     ...
        |                         |
   KERI delegated AID         KERI delegated AID
   (dip anchored in           (dip anchored in
    tenant KEL)                tenant KEL)
        |                         |
   ZKA/ZKC key hierarchy      ZKA/ZKC key hierarchy
   from M' per Appendix A     from M' per Appendix A
   (KDC v1.0.2):              (KDC v1.0.2):
     sk  = H("zka/spending", M')
     vk  = H("zka/viewing", sk)
     pk  = H("zka/proof", sk)
     cbk = H("zkc/credential", M')

For each entity, the entity's sub-seed M' is the master-seed input to AFP-KDC v1.0.2 (Appendix A) — specifically to deriveKeys (the ZKA key hierarchy) and the credential-binding derivation (the ZKC cbk and private/global holderCommitment). The HKDF sub-derivation M' = HKDF(M, "entity/{name}") is itself defined by KDC §A.5. Consequently:

4.4 Key Event Log

Each tenant root and each entity AID has a KEL — an append-only, self-certifying log of inception, rotation, delegation, and interaction events. The KEL replaces the v0.2.0 flat nkey_history array.

Pre-rotation is REQUIRED, not optional. Every KEL event commits to the digest of the next key set (next_key_digest); a rotation event reveals the pre-committed next key and commits to the one after. An AFP implementation MUST reject a KEL whose rotation events do not honor prior pre-rotation commitments.

4.5 Witnesses

Each tenant chooses a set of KERI witnesses that receipt its KEL events. Witnesses are the mechanism by which a counterparty — including an appliance with no access to the tenant's original operator — can verify that the tenant's key history is consistent and has not forked.

Witnesses receipt KEL events only; they never see AFP work content and are not a data path under §3. A witness learns that a tenant rotated a key, not what the tenant does.

4.5.1 Witness bootstrap record

A new tenant establishes its initial witness configuration with a witness bootstrap record:

interface AfpWitnessBootstrap {
  kind: 'afp:witness-bootstrap:v1';
  tenant_aid: string;                    // tenant root AID being bootstrapped
  kel_inception_dig: string;             // digest of the tenant root inception event
  witness_set: Array<{
    witness_aid: string;
    witness_endpoint: string;            // transport endpoint for KEL receipts only
    witness_kel_dig: string;             // latest witnessed event for the witness AID
  }>;
  threshold: number;                     // minimum receipts required for KEL acceptance
  bootstrap_mode: 'managed-bootstrap' | 'bring-your-own';
  bootstrap_issuer_aid?: string;         // optional candidate-set publisher, never a trust root
  issued_at: datetime;
  expires_at: datetime;
  tenant_signature: string;              // tenant signs all fields above
  witness_receipts: Array<{
    witness_aid: string;
    kel_event_dig: string;               // MUST equal kel_inception_dig
    receipt: string;                     // KERI receipt over the inception event
  }>;
  bootstrap_issuer_signature?: string;   // optional signature over the offered candidate set
}

The tenant root inception event MUST commit to the same witness_set and threshold carried in the bootstrap record. The bootstrap record is a convenience envelope for discovery and verification; the KEL remains authoritative. A conforming implementation MUST reject a bootstrap record whose signed fields do not match the tenant root inception event.

bootstrap_issuer_aid is optional and advisory. In a managed deployment, an operator or consortium MAY publish a signed candidate set to help a tenant find witnesses, but the tenant chooses the witness set and signs the bootstrap record. The operator's signature is never a trust root and never substitutes for witness receipts.

4.5.2 Managed and appliance bootstrap paths

Two bootstrap paths are conforming:

Both paths produce the same verifier-facing artifact. A counterparty MUST NOT distinguish their trust semantics: the only acceptance criteria are the tenant signature, the KEL/inception match, the declared threshold, and valid KERI receipts from the declared witnesses.

4.5.3 Verification and failure semantics

A verifier that receives a tenant's initial KEL state MUST:

  1. verify the tenant root AID from the inception event;
  2. verify the tenant_signature over the bootstrap record;
  3. verify that kel_inception_dig, witness_set, and threshold match the inception event;
  4. verify that at least threshold receipts in witness_receipts are valid KERI receipts from AIDs in witness_set over kel_inception_dig;
  5. verify each witness AID against its own KEL state up to witness_kel_dig; and
  6. reject expired bootstrap records unless the same witness configuration is already established by later KEL events.

Bootstrap failure has exactly one protocol consequence: the verifier treats the tenant identity as not yet established and declines AFP session establishment with that tenant. It MUST NOT trigger operator intervention, key revocation, note invalidation, or any change to tenant state.

After bootstrap, witness changes are ordinary KEL events: the tenant rotates the witness configuration by publishing the new set and threshold in the KEL and obtaining receipts under the old and new threshold policy according to KERI witness-change rules. Appliance migration (§4.7) carries the current KEL and witness configuration; it is not a re-bootstrap unless the tenant intentionally changes its witness set.

4.6 Rotation, recovery, succession

Identity-lifecycle operations are re-specified on the KEL:

A master-seed rotation re-derives the entity's ZKA/ZKC keys, which orphans the credential binding (cbk) and any on-chain ZKA notes bound to the old keys. ZKC v0.4.0 owns both credential-side continuity primitives: scoped zkc:proof:rebind:v2 exposes only old/new presentation pseudonyms plus their shared scopeId; the frozen zkc:proof:credential_rebind:v1 exposes old/new global holder commitments and is therefore the explicitly-consented linkable profile. There is no anonymous rebind, because a rebind is a continuity claim. Issuer migration tokens MAY optimize integrations but do not replace the ZKC-Core proof. ZKA §5.7 owns note migration.

AFP owns the upper-layer sequencing and presentation of those facts to counterparties. An AFP implementation MUST NOT treat the KEL rotation alone as sufficient when notes or credentials derived from the old seed remain in use.

4.6.1 Rotation presentation artifact

When a tenant or delegated entity rotates a seed that affects ZKA or ZKC material, it MUST publish a rotation presentation:

interface AfpRotationPresentation {
  kind: 'afp:rotation-presentation:v1';
  tenant_aid: string;
  entity_aid?: string;                  // absent for tenant-root rotation
  old_kel_event_dig: string;            // last accepted event before rotation
  rotation_event_dig: string;           // KEL rotation event being presented
  new_kel_event_dig: string;            // latest witnessed event after rotation
  affected_material: Array<'kel' | 'zkc-credential-binding' | 'zka-notes'>;
  presentation_profile: 'zkc:presentation:anonymous:v1' | 'zkc:presentation:scoped:v1' | 'zkc:presentation:linkable:v1';
  zkc_credential_rebind?: {
    proof_kind: 'zkc:proof:rebind:v2' | 'zkc:proof:credential_rebind:v1';
    proof: string;
    public_inputs: {
      // scoped v2: the only holder-derived fields permitted
      old_presentation_pseudonym?: string;
      new_presentation_pseudonym?: string;
      scope_id?: string;
      // linkable v1: present only with explicit holder consent
      old_holder_commitment?: string;
      new_holder_commitment?: string;
      presentation_profile: string;
      issuer_id: string;
      credential_schema_id: string;
      context: string;                   // binds AID, KEL rotation, session, APR, profile, and active handles
      timestamp: datetime;
    };
    proof_public_inputs_hash: string;    // hash of the ZKC-owned public statement above
    issuer_migration_token?: string;     // optional optimization; not a substitute for the proof
  };
  zka_note_migration?: {
    status: 'not-applicable' | 'complete' | 'pending-zka-draft';
    proof_kind?: string;                // ZKA-owned when status is complete
    proof?: string;
    migrated_note_root?: string;
  };
  presented_at: datetime;
  tenant_signature: string;
}

The presentation artifact is an AFP envelope over KEL state plus lower-layer proofs. It does not define either ZKC circuit or the ZKA note-migration proof. Its handle surface is profile-exclusive: scoped v2 MUST omit both holder commitments; linkable v1 MUST omit scoped pseudonyms and scope_id; an anonymous flow that does not require continuity omits zkc_credential_rebind entirely and re-establishes the rotated credential as a fresh anonymous presentation.

4.6.2 Sequencing rules

A conforming implementation MUST present rotation in this order:

  1. Pre-rotation commitment. The prior KEL event MUST already commit to the next key set (next_key_digest, §4.4). If the pre-rotation commitment is missing or not honored, the rotation is invalid.
  2. KEL rotation. The tenant publishes the KEL rotation event, obtains the declared witness-threshold receipts, and makes the witnessed KEL state available to counterparties.
  3. Credential rebinding. If affected_material includes zkc-credential-binding and the consuming flow requires continuity, the tenant MUST include scoped zkc:proof:rebind:v2, or linkable zkc:proof:credential_rebind:v1 after explicit consent. The proof context MUST bind the entity AID, KEL rotation, AFP session, authenticated APR signer, active presentation profile, and exactly the active profile's handles. An issuer migration token MAY accompany the proof but MUST NOT replace it. Anonymous flows that do not require continuity MAY present the rotated credential as fresh and MUST NOT publish an old↔︎new link.
  4. ZKA note migration. If affected_material includes zka-notes, the tenant MUST include a zka_note_migration block. While ZKA §5.7 remains Draft, the only conforming unresolved status is pending-zka-draft; counterparties MUST treat notes bound to the old seed as not yet migrated for new AFP work, but MUST NOT invalidate, freeze, or redirect them. When ZKA finalizes the note-migration proof, status: 'complete' MUST carry the ZKA-owned proof and migrated note root.
  5. Presentation binding. The tenant signs the complete afp:rotation-presentation:v1 artifact and presents it on the relevant tenant↔︎tenant or tenant↔︎operator channel before relying on the rotated material in a new AFP session, telemetry proof, or work bundle.

4.6.3 Verifier behavior

A verifier MUST:

  1. verify the tenant or entity AID against the KEL chain through new_kel_event_dig;
  2. verify witness receipts for rotation_event_dig and new_kel_event_dig under §4.5;
  3. verify that old_kel_event_dig immediately precedes the rotation being presented;
  4. verify the tenant signature over the presentation artifact;
  5. when ZKC credential material is affected, require either (a) a valid profile-specific credential-rebind proof when the consuming flow requires continuity, or (b) the anonymous profile with no zkc_credential_rebind when continuity is unnecessary, treating the rotated credential as a fresh presentation and accepting no old↔︎new linkage; and
  6. require either no affected ZKA notes, zka_note_migration.status == 'not-applicable', or a ZKA-owned completion proof before accepting rotated ZKA notes for new AFP work.

Rotation-presentation failure has the same consequence as other AFP verification failures: the verifier declines the affected session, telemetry proof, or work bundle and emits an alert where applicable. It MUST NOT trigger operator intervention, credential revocation, note invalidation, or any mutation of tenant state.

4.7 Appliance migration

A tenant may move between deployment topologies — for example, from a managed transport to a self-hosted appliance. Because all entity sub-seeds derive from the tenant master seed M, migration is one move: relocating M re-derives every M' and therefore every entity's ZKA/ZKC key hierarchy. The KEL state and witness configuration move with the tenant. No counterparty relationship, attestation, or identifier changes as a result of the move — which is the point of choosing self-certifying identity in §4.1.


5. Tenant ↔︎ Tenant Federation

This section specifies the first relationship: two tenants coordinating work across organizational boundaries.

Relationship to the Bilateral Session Core (§12), added in v0.3.1. The session machinery introduced informally in this section — establishment (§5.2), bilateral federation agreements (§5.5), and the work-artifact envelope (§5.4) — is generalized and made AFP-owned in §12 (Bilateral Session Core). §12 is the canonical, normative home of the session/identity/dispute/commitment/bundle/safeguards machinery that the profiles of §13 (PRP, VXP) reuse. This §5 remains the specification of AFP's base behavior: direct tenant↔︎tenant work exchange, which §13.3 names as the base profile over the same core. Read §5 for the work-exchange behavior; read §12 for the shared substrate that PRP and VXP also build on. Where §5 and §12 describe the same construct (the session of §5.2, the agreement of §5.5), §12 is the consolidated statement and §5 the work-exchange application of it.

5.1 Model

A tenant↔︎tenant interaction is a bilateral, ZKA-mediated work exchange between two entities, one in each tenant. It is not a marketplace transaction: there is no discovery feed, no open call, no solver. The protocol's job is to make the exchange verifiable and minimally disclosing once the two counterparties are in contact.

Trust between the two tenants rests on one of two bases (generalized in v0.3.4, per ADR-0004):

In both cases the counterparty is a specific, identified principal by the time a session is established — open federation removes the prior-agreement assumption, not the identified-principal one. What AFP does not provide in either case is discovery: how two strangers find each other and route first contact is out of scope (§8.8), because a queryable directory of tenants would itself be the coordination graph (§3).

A typical exchange: an entity in tenant A (e.g. Viksana) needs an analysis run by an entity in tenant B; B performs the work; the result returns to A. Neither side reveals more than the work requires.

5.2 Session establishment

A tenant↔︎tenant session is established between two entity AIDs:

  1. Address resolution. The initiating entity resolves the counterparty tenant AID (not the entity AID) to a transport route via the §8 routing layer. The protocol addresses tenants; routing a request to the correct entity inside the receiving tenant is tenant-internal (§5.6).
  2. Mutual authentication. Each side verifies the other's KEL against its declared witness threshold (§4.5). Authentication is of the entity delegated AID, with its delegation chain verified up to the tenant root AID.
  3. Channel encryption. The session establishes an end-to-end encrypted channel between the two entities. All subsequent AFP payloads on the session are ciphertext to any transport operator (OB-1, §3.3). Channel key agreement uses keys derived from the entities' M' hierarchies.
  4. Context binding. The session has a context H(initiatorEntityAID, responderEntityAID, sessionId, timestamp) that binds every proof and message in the session and prevents replay to another session or counterparty. This context is the value supplied to the ZKA and ZKC context public inputs (§5.4, §6).

Open-federation variant (§5.8). For an unknown-counterparty session the entity AIDs are not drawn from a pre-existing agreement; they are the attested identities established at first interaction (the ZKC verifier/holder identities each party authenticates under §5.8). Steps 2–4 are otherwise unchanged: the same KEL verification (step 2), the same channel encryption (step 3), and the same context-binding hash (step 4) over those attested AIDs — additionally binding the shared ZKC proof context so the session is anchored to the specific attestation that opened it. The binding shape is identical; only the provenance of the two AIDs differs (attested-at-first-contact vs. carried-in-agreement).

5.3 Message classes

AFP defines a deliberately small set of message classes for the tenant↔︎tenant session. All are carried inside the encrypted channel of §5.2. These are the message classes of the base work-exchange behavior; under the consolidated NATS namespace of §12.4 they are addressed as leaves of the base profile's subtree, afp.{tenant}.work.* (with afp.{tenant}.session.* for the lifecycle classes). The bare names below are retained for readability; §12.4 is normative for their full tenant-scoped addressing.

Class Purpose
afp.session.open / afp.session.close Establish / tear down a session per §5.2
afp.work.request An entity requests cross-tenant work. Carries a work descriptor and the requester's bilateral-agreement reference (§5.5).
afp.work.result The performing entity returns a result, carried as ZKA note(s) and/or a ZKA/Coord proof (§5.4).
afp.disclose.request / afp.disclose.response One side requests, and the other provides, a selective disclosure or compliance bundle (§5.4, §6.1).
afp.attest An entity contributes a coordination attestation (ZKA/Coord proof) for a completed multi-step workflow.

There is no afp.register, no afp.discover, no afp.delegate as a marketplace primitive. Work requests are a message inside an already-established bilateral relationship, not an open call.

5.4 Work artifacts ride on ZKA

Cross-tenant work artifacts, attestations, and value transfers are ZKA constructs; AFP carries them and defines their interaction envelope but does not redefine them.

A tenant↔︎tenant compliance requirement is an interaction-boundary concern (Safeguard 5): it may cause the requesting tenant to decline the interaction, and nothing more. It MUST NOT gate either tenant's access to its own funds or notes, and a failed bundle verification has exactly one permitted consequence — the counterparty declines — per ZKA v0.9.0-draft §5.4 and Safeguard 3.

5.5 Bilateral federation agreements

v0.3.0 does not define individual-agent reputation or a portable reputation score. Instead, trust between two tenants is a bilateral federation agreement: an explicit, mutually signed record that two tenant AIDs have agreed to federate, optionally scoped (which entities, which work classes, which compliance requirements apply).

The bilateral federation agreement is the trust basis for the known-counterparty path. As of v0.3.4 (ADR-0004) it is one of two trust bases: two tenants with no prior agreement may instead establish trust per-interaction by ZKC compliance attestation — the open-federation path of §5.8. Both are bilateral and private; neither is a portable reputation score. Where this section says "the agreement," an open-federation session substitutes the per-interaction attestation and its policy block.

5.6 Internal routing boundary

When an afp.work.request arrives addressed to a tenant, routing that request to the correct entity within the receiving tenant is tenant-internal and outside AFP scope. The protocol sees tenant AIDs as session endpoints and entity AIDs as the principals named inside the encrypted channel; it does not see, and does not specify, how a tenant dispatches an inbound request across its own entities. A conforming implementation MUST keep this boundary: no AFP wire message carries a tenant's internal routing topology.

5.7 Rogue-agent threats are out of scope

A compromised or misbehaving entity inside a tenant operates with that tenant's legitimate keys and legitimate access. Protecting a tenant against its own rogue entity is a containment, capability-scoping, and observability problem handled within the tenant: a conforming tenant MUST contain, capability-scope, and observe its own entities (§1.5.1 item 7), and the mechanism is tenant-internal. ADAMAS, the reference implementation, discharges this through policy enforcement, per-entity capability scoping, and the Drasta observability entity acting on the tenant's behalf. It is explicitly not an AFP concern. AFP's guarantees — operator blindness, federation verifiability, identity continuity — MUST NOT be read as covering an internal rogue agent. This boundary is stated so that conforming implementations do not conflate the two.

5.8 Open federation — trust by ZKC attestation

Added in v0.3.4 (ADR-0004). Supporting open federation is OPTIONAL; a conforming deployment MAY support only the bilateral path of §5.5. Where a deployment supports open federation, the requirements in this subsection are normative (MUST/MUST NOT as stated).

Open federation is a tenant↔︎tenant session (§5.1, §5.2) between two conforming tenants that hold no prior bilateral federation agreement. In place of the agreement, trust is established per-interaction by mutual ZKC compliance attestation. This is the second of the two trust bases of §5.1; everything else about the session — its message classes (§5.3), its work-on-ZKA carriage (§5.4), its routing (§8), its session core (§12), and all six Freedom Safeguards (§9) — is unchanged.

Trust establishment. To open an open-federation session, each party in turn:

  1. Authenticates a scoped proof request. The requesting tenant issues a ZKC v0.4 Authenticated Proof Request declaring the predicates, purpose/proof context, presentation.profile = zkc:presentation:scoped:v1, and scopePolicy = protocol:afp:<profile_policy_hash>. The APR is signed by the current KEL-anchored key of the requesting entity AID. An unauthenticated request is rejected — no signature ⇒ no proof.
  2. Answers with a subject-bound ZKC compliance bundle. The responder answers with zkc:proof:v2 inside ZKA v0.9.0-draft zka:bundle:v1, wrapped in afp:envelope:v1, or declines. Every proof is atomic and its verified credential-subject AID, scoped pseudonym, and proof context MUST satisfy §5.9. A borrowed anonymous proof is not an open-federation trust basis.
  3. Retains a profile-minimized Disclosure Receipt. Each party retains a holder-custodied ZKC Disclosure Receipt containing the scoped pseudonym and scopeId, never holderCommitment, unless the holder separately and explicitly consented to the linkable v1 profile.

Because each side both requests and answers, trust is mutual: neither party is a pure verifier nor a pure holder, and each ends the exchange holding a receipt of the other's demand.

Acceptance is each party's own policy. There is no global registry of who-may-transact-with-whom, no canonical predicate set, and no canonical or required attestation authority. Each party decides, under its own acceptance policy, whether the bundle it received satisfies it (Predicate Pluralism, Safeguard 1; verifier flexibility, ZKC §1.1). Where a party's acceptance policy includes afp.telemetry.instance_attestation.v2, that policy names the (root class, root authority) pairs it accepts per §6.3.1 and the counterparty proves under one accepted pair, exactly as §14.2 requires on the introduction path; a party MAY accept an authority the other party rejects, and neither outcome is a conformance judgment about the other. A satisfied policy opens the session; an unsatisfied one declines it — and declining is the only permitted consequence of a failed attestation, of a rejected authority, or of an authority found stale or revoked (Safeguard 5; see below).

Identity binding. The session principals are the attested entity AIDs from step 1–2. For each direction the KERI-authenticated presenter AID, ZKC-verified credential-subject AID, and responder AID MUST be identical; the APR signer key MUST resolve through the verifier AID's current KEL; and the scope and proof context MUST recompute under §5.9. This is the proof-borrowing guard: a proof produced for another holder/AID, verifier, policy, profile, or session is rejected even if its predicate result is otherwise true.

Safeguard 6 is what makes this safe, and is why the path is viable. The party demanding "prove you are compliant to transact with me" is a verifier in the ZKC sense; Safeguard 6 requires it to authenticate the demand under a stable identity and leaves the responder with portable evidence of the demand. ZKC v0.4 scoped presentation further prevents the accountability record from forcing global holder linkability.

Interaction-boundary only (Safeguard 5). A declined or failed attestation means the counterparty declines the interaction, and nothing more. It MUST NOT gate, freeze, redirect, or seize either party's funds or notes; it MUST NOT touch ZKA settlement or ZKC credential validity; and exit remains unconditional. Open federation changes who may open a session with whom and how that trust is shown — it changes nothing about value.

What open federation does NOT provide — discovery and rendezvous (§8.8). This subsection covers trust establishment once the two parties are in contact. It does not specify how two strangers find each other or how first contact is routed. A queryable directory mapping tenant AIDs to routes would itself be the coordination graph that operator-blindness (OB-2, §3, §8) exists to prevent. Until a dedicated discovery/rendezvous layer with its own privacy analysis exists (a §10 open item), open federation is usable only where first contact is established out-of-band. AFP deliberately solves the trust half and leaves the discovery half explicitly out of scope, rather than admitting a directory that would weaken OB-2.

Relationship to the Introduction Tier (§14). Open federation and the introduction tier (§14) are the two ways AFP admits a session between tenants with no pre-existing agreement; a deployment MAY offer either or both. They differ in what they leave behind: §5.8 establishes trust per interaction with no agreement (lightest; suited to one-off or low-stakes exchange), whereas §14 runs an introduction session that mints a signed provisional §5.5 agreement and grows a progressive, ZK-provable reputation envelope (heavier; suited to an ongoing relationship). Both inherit all six safeguards, are interaction-boundary-only (Safeguard 5), and share the same unresolved stranger-rendezvous problem (§10). The three trust bases are enumerated together in §12.1.

5.9 ZKC v0.4 presentation profiles and AFP identity continuity

AFP consumes ZKC v0.4.0 presentations through zka:bundle:v1; ZKC remains authoritative for the v1/v2 envelopes, circuits, APR signatures, pseudonym/scope derivation, and proof verification. AFP is authoritative for selecting the privacy profile a flow may consume and binding the verified ZKC result to AFP identity and session state.

AFP flow Accepted profiles Default Downgrade rule
Tenant→operator telemetry (§6) anonymous, scoped, linkable anonymous scoped→anonymous MAY be accepted when the predicate and policy window require no cross-presentation continuity
Known-counterparty bilateral (§5.5 / §12) anonymous, scoped, linkable anonymous scoped→anonymous MAY be accepted when KERI/session authentication already supplies all required continuity
Introduction (§14) scoped, linkable scoped no anonymous downgrade; the presentation becomes the credential basis of an ongoing provisional agreement
Open federation (§5.8) scoped, linkable scoped no anonymous downgrade; the presentation is the per-interaction trust basis that opens the session

In every row, zkc:presentation:linkable:v1 is the frozen v1 proof path and requires an explicit, recorded holder consent for that presentation. It is never selected by fallback, downgrade, verifier preference, or an absent APR presentation block.

Scoped policy derivation. AFP scoped requests use the signed ZKC scopePolicy string protocol:afp:<profile_policy_hash>, where profile_policy_hash is the exact session, telemetry, or introduction policy hash authenticated for the interaction. ZKC derives scopeId from the authenticated APR signer public key, that scope-policy string, and its declared rotation epoch (ZKC v0.4.0 §5.9.1). The holder derives the identifier itself; AFP MUST reject a verifier-supplied literal scope. The APR signing key MUST be the current KEL-anchored key for apr_verifier_aid; swapping either the AID or key fails verification.

AID, credential subject, and context binding. A proof accepted for an AFP responder MUST cryptographically prove the credential's subject-AID claim equals that responder's KERI entity AID. AFP then requires the KERI-authenticated envelope presenter to be the same AID. The ZKC proof context is the canonical digest of:

interface AfpZkcPresentationContextV1 {
  kind: 'afp:zkc-presentation-context:v1';
  credential_subject_aid: string;       // == KERI-authenticated presenter entity AID
  session_context: string;              // exact §5.2 / §12.3 context
  apr_verifier_aid: string;             // APR signer key resolves here through its KEL
  presentation_profile: string;         // active, not merely requested, profile
  scope_policy: string | null;           // protocol:afp:<profile_policy_hash> iff scoped
}

The credential issuer signature binds the subject-AID claim to the credential and private holder commitment; the ZKC proof binds that credential subject and the active profile handle to this context; the AFP envelope signature binds the same result to the presenter's KERI AID. Consequently a proof borrowed from an unrelated holder, AID, APR signer, scope, profile, or session cannot be repackaged: at least one issuer claim, ZKC public input, KEL key binding, or AFP context comparison fails. A boolean envelope label that merely says “verified” is insufficient; a conforming AFP implementation MUST invoke a ZKC v0.4 verifier and consume its verified public statement.

Version and handle exclusivity. Anonymous and scoped presentations use zkc:proof:v2; linkable presentations use the frozen zkc:proof:v1. Every one is carried in ZKA v0.9.0-draft zka:bundle:v1. AFP receipts, rotation presentations, and other AFP-authored artifacts expose no holder-derived handle beyond the active profile:

Active profile Permitted holder-derived fields
anonymous none in AFP-authored artifacts; presentationPseudonym == 0 and scopeId == 0 occur only as sentinels in the ZKC v2 proof envelope
scoped presentationPseudonym, scopeId
linkable holderCommitment only

Mixing handle families is a conformance failure. In particular, a scoped receipt or rotation artifact MUST NOT carry holderCommitment. An anonymous ZKC v2 proof envelope MUST carry the two zero sentinels required by ZKC, while an AFP-authored receipt or rotation artifact MUST omit all three holder-derived fields; implementations MUST NOT serialize the proof-envelope sentinels into those AFP artifacts.

Conformance. Implementations MUST include negative cases for a swapped presenter AID, credential subject/holder, scope, APR signer key or AID, session/proof context, and presentation profile. They MUST also demonstrate that anonymous presentations remain accepted in telemetry and known-counterparty bilateral flows where continuity is unnecessary, that an anonymous rotation presents the credential as fresh with no rebind or artifact handles, that scoped continuity requires zkc:proof:rebind:v2, and that introduction/open-federation anonymous downgrades and unconsented linkable presentations are rejected.


6. Tenant ↔︎ Operator Telemetry

This section specifies the second relationship: an operator learning that the platform is healthy without learning what tenants do. It rides on ZKC.

6.1 Model

The operator needs operational assurance — that instances are running supported, attested builds and behaving within agreed bounds. Under the operator-blindness invariant (§3), the operator obtains this assurance only through claims a tenant proves, never through observation of tenant work or the coordination graph.

v0.3.0 retires the v0.2.0 two-layer telemetry model in which "Layer 1" was transport-observed per-tenant metadata. Under §3.2, per-tenant transport observation is not a capability the operator is permitted to have. Therefore all per-tenant operator knowledge is ZKC-proved. Nothing in the operator-health story may be load-bearing on the operator watching individual tenant traffic.

Telemetry uses the §5.9 flow policy. zkc:presentation:anonymous:v1 is the default because the authenticated tenant→operator channel and the predicate's public tenant/operator/window inputs already provide interaction identity. An operator MAY request scoped continuity across one declared telemetry policy window; the tenant MAY downgrade that request to anonymous when the predicate does not require cross-presentation continuity. Linkable v1 is accepted only after explicit holder consent and MUST NOT be required as the ambient telemetry mode.

6.2 What the operator may learn

The operator's per-tenant knowledge is exactly the set of claims in the telemetry predicate catalog (§6.3), each delivered as a ZKC proof. Separately, the operator MAY compute aggregate operational metrics across the whole tenant base (for example, total compute consumed, total error count) provided the aggregate is computed so that no per-tenant value is recoverable from it — by aggregation with a sufficient tenant count, or by differential privacy. Aggregate metrics MUST NOT be derived in a way that reveals or narrows any individual tenant's value, and MUST NOT reveal the coordination graph.

6.3 Telemetry predicate catalog

Operator-facing claims are ZKC proofs. Each claim is one atomic predicate (Safeguard 2). AFP defines the starting predicate catalog below; each predicate circuit MUST be open-source, versioned, and published with a public review period before it is accepted by an operator (§9 Safeguard 4). A telemetry proof that uses an unregistered predicate identifier, an unsupported predicate version, or a predicate outside this catalog is not AFP-conforming.

Predicate ID Claim Issuer model Public inputs Private witness Cadence Failure signal
afp.telemetry.instance_attestation.v1 "I am a verified ADAMAS instance, build hash X" Third-party-issued: the ADAMAS publisher signs a build manifest; the tenant proves possession. Tenant AID, entity AID or instance AID, build manifest digest, publisher AID, predicate version, telemetry window, operator AID. Publisher-issued ZKC credential, holder binding key, instance signing key. At bootstrap, after build change, and at least once per operator policy window. instance_attestation_missing or instance_attestation_invalid.
afp.telemetry.instance_attestation.v2 "This machine subject runs the attested manifest under verifier-accepted root class R and authority A." Pluggable, as classified by §6.3.1: third-party-verifiable build provenance or TEE attestation, or a self-attested publisher manifest. Tenant AID; entity AID or instance AID; attested manifest digest; root class; root-authority identifier; root-material digest; attestation-evidence digest; verification-policy digest; public-sample-binding digest (the zero sentinel only where §6.3.1 permits it); predicate ID and version; attestation window; verifier AID; active presentation profile. ZKC machine build/runtime credential and holder binding key; instance signing key; attested manifest; root-specific provenance, inclusion, quote, endorsement, or publisher-signature evidence and openings; public-sample-binding witness when required. At bootstrap, after any manifest, root authority, root material, measurement, endorsement/collateral, or verification-policy change, and at least once per verifier policy window. instance_attestation_missing, instance_attestation_invalid, instance_attestation_unsupported_root, instance_attestation_untrusted_authority, instance_attestation_provenance_not_in_log, instance_attestation_measurement_mismatch, or instance_attestation_sample_binding_missing.
afp.telemetry.build_currency.v1 "I run a supported build" Third-party-issued: the publisher signs the supported-build set; the tenant proves set membership. Tenant AID, build manifest digest, supported-build-set root, publisher AID, support-policy version, telemetry window, operator AID. Membership path for the build digest and publisher-issued support credential. At bootstrap, after build change, and whenever the supported-build-set root changes. build_currency_missing, build_currency_expired, or unsupported_build.
afp.telemetry.compute_quota.v1 "My consumption is within agreed bounds" Self-attested: the tenant signs a usage-log root and proves the bound. Tenant AID, usage-log root, quota policy digest, committed usage upper bound, telemetry window, operator AID, afp:telemetry-sample-binding:v1 digest. Usage-log inclusion/range witnesses, tenant usage signing key, and sample-binding witness from §6.4. Once per quota window and after any policy-window close. compute_quota_missing, compute_quota_invalid, or compute_quota_over_bound.
afp.telemetry.audit_log_integrity.v1 "My audit log has not been truncated" Self-attested: the tenant signs the current append-only audit-log head. Tenant AID, previous accepted log head, current log head, append-only accumulator root, telemetry window, operator AID, afp:telemetry-sample-binding:v1 digest. Append-only proof from previous to current head, tenant audit signing key, and sample-binding witness from §6.4. Every audit window and after recovery from a missed window. audit_log_missing, audit_log_invalid, or audit_log_truncated.

Every operator-facing catalog proof MUST bind the tenant AID, operator AID, predicate ID, predicate version, telemetry subject, telemetry window, and active ZKC presentation profile into the proof transcript. For v2, these values map to verifier_aid = operator AID and attestation_window = telemetry window under §6.3.1. Scoped proofs additionally bind protocol:afp:<telemetry-policy-hash>, the APR signer, pseudonym, and scopeId per §5.9. This prevents replay across verifier, subject, window, policy, profile, or scope. Predicate versions are monotonic: a verifier MAY accept multiple versions during a migration window, but it MUST publish the accepted version set and MUST NOT reinterpret a v1 predicate or linkable presentation under later semantics.

Third-party-issued predicates prove what a tenant is. Self-attested predicates prove how a tenant behaves and MUST carry the public-sample binding of §6.4. build_currency, compute_quota, and audit_log_integrity retain those classifications. Instance attestation is versioned separately: v1 retains its frozen publisher-issued classification and semantics, while v2 uses the root-class taxonomy and binding rules below. Both versions are usable operator-facing; new counterparty-facing policy in §14 uses v2 so it can select a vendor-neutral root. The delegated, product-scoped publisher construction of §6.3.1 is v2-and-forward only: v1's publisher AID is a frozen, hard-wired field and acquires no delegation, organizational-root, or authority semantics from it.

6.3.1 Instance-attestation root classes

afp.telemetry.instance_attestation.v1 is frozen. Its public inputs, circuit, private witness, cadence, failure signals, and catalog row above remain unchanged; implementations MUST NOT reinterpret its publisher AID as a generic authority field, as an organizational root AID, or as the delegated product-scoped publisher AID of the v2 construction below, and MUST NOT attach v2 semantics to a v1 proof. A v1 publisher AID denotes exactly what it denoted when v1 was frozen. The freeze is semantic as well as textual: a v1 proof reinterpreted under the delegated-publisher construction produces no wire-visible difference, so the prohibition, not a wire check, is what preserves the frozen meaning.

afp.telemetry.instance_attestation.v2 fixes the generic statement. Its wire-level public statement is:

interface AfpInstanceAttestationPublicInputsV2 {
  kind: 'afp:instance-attestation-public-inputs:v2';
  tenant_aid: string;
  subject_aid: string;                       // entity AID or instance AID
  attested_manifest_digest: string;          // canonical digest, defined below
  root_class: 'root:build-provenance' | 'root:tee-attestation' | 'root:publisher-manifest';
  root_authority_identifier: string;
  root_material_digest: string;
  attestation_evidence_digest: string;
  verification_policy_digest: string;
  public_sample_binding_digest: string;      // canonical digest or exact zero sentinel
  predicate_id: 'afp.telemetry.instance_attestation.v2';
  predicate_version: '2';
  attestation_window: { starts_at: datetime; ends_at: datetime };
  verifier_aid: string;
  presentation_profile: string;              // active §5.9 profile
}

For tenant→operator telemetry, verifier_aid MUST equal the telemetry operator AID and attestation_window MUST equal the telemetry window. For §14 counterparty use, verifier_aid MUST equal the APR's apr_verifier_aid; the APR signing key MUST resolve through that AID's current KEL, and a delegated verifier AID MUST resolve through its delegation chain to the counterparty tenant AID. attestation_window MUST equal the explicit attestation_window on afp:introduction-request:v1 and MUST be bounded by the session expiry. The proof context additionally binds the APR signer, session context, scope policy, and active presentation profile. A proof made for one flow, verifier, or window MUST NOT verify in the other.

Symmetrically, where an authority delegates product-scoped signing (below), two obligations apply and they are discharged differently. The delegated signing AID's delegation chain MUST resolve to the AID named by root_authority_identifier, and the verifier performs that resolution out of circuit, before acceptance, exactly as it resolves a delegated verifier AID above — that resolution reads only published KEL events, which the verifier holds. The manifest signing key MUST be the current signing-key state of that delegated AID, and that obligation is discharged indirectly, because the signed manifest and its signature stay in the private witness by design and the verifier never sees them: the verifier recomputes root_material_digest from the signing-key state it resolves (below), and the circuit proves the hidden signature verifies under exactly that committed state. The circuit continues to bind only the leaf signing-key state committed by root_material_digest, and no branch obligation below is changed by the presence of a delegation. A verifier that cannot resolve the chain to the named authority root MUST reject the proof with instance_attestation_untrusted_authority.

Canonical encoding (normative). Every non-sentinel *_digest is the ASCII string sha256: followed by exactly 64 lowercase hexadecimal characters. AFP-authored JSON artifacts use RFC 8785 JSON Canonicalization Scheme (JCS) bytes and MUST contain a kind discriminator; AFP's datetime strings first normalize to whole-second UTC YYYY-MM-DDTHH:MM:SSZ. External provenance statements, transparency-log entries/checkpoints, hardware reports/collateral, and publisher-signed manifests MUST NOT be re-encoded as AFP JSON. Their digest is SHA-256 over ASCII("AFP-External-Artifact-v2\0") || u32be(len(kind)) || UTF8(kind) || u32be(len(scheme)) || UTF8(scheme) || u64be(len(signed_bytes)) || signed_bytes, where signed_bytes are the exact scheme-native signed bytes and kind and scheme are nonempty ASCII identifiers fixed by the accepted verification policy. This wrapper domain-separates artifact types without changing the signed payload.

Publisher signing-key state (normative). For root:publisher-manifest, root_material_digest commits to the publisher's signing-key state. That state is neither an external provenance artifact nor a transparency-log checkpoint nor hardware collateral, so it is AFP-authored and therefore carries a kind under the rule above. Its preimage is the RFC 8785 JCS bytes of exactly:

interface AfpPublisherKeyState {
  kind: 'afp:publisher-key-state:v1';
  authority_aid: string;                 // == root_authority_identifier
  signing_aid: string;                   // delegated signing AID; == authority_aid when signing is not delegated
  delegation_anchor_dig: string | null;  // said of the authority-KEL event anchoring the delegated inception; null iff signing_aid == authority_aid
  key_state_event_dig: string;           // said of the establishment event, in the signing AID's KEL, fixing the state below
  key_state_sequence_number: number;     // that event's sequence number in the signing AID's KEL
  signing_public_keys: string[];         // ordered current signing keys, exactly as the establishment event lists them
  signing_threshold: string | string[] | string[][];  // that event's `kt`, copied verbatim from the establishment event
  next_key_digests: string[];            // ordered pre-rotation commitments from the same event, verbatim
  next_threshold: string | string[] | string[][];     // that event's `nt`, copied verbatim from the establishment event
}

Field encodings are KERI-native and are never re-encoded. delegation_anchor_dig and key_state_event_dig carry the KERI SAIDs of those events exactly as the KEL lists them (CESR-qualified), and each element of signing_public_keys and next_key_digests carries the corresponding key or pre-rotation commitment exactly as the establishment event lists it. signing_threshold and next_threshold carry the establishment event's kt and nt values verbatim — a hex string for a simple threshold, or the nested list of fraction strings KERI uses for a weighted multisig threshold, which is why the type is a union. These fields are KERI-native identifiers and values, they follow the _dig convention this specification already uses for KEL event digests (§4.5.1, §4.6.1, §12.2.1), and they are NOT re-encoded under the sha256:-prefixed *_digest rule above. RFC 8785 JCS then canonicalizes the record — including a nested threshold — deterministically.

root_material_digest is sha256: followed by the lowercase hex SHA-256 of those JCS bytes. A verifier MUST recompute this digest from the key state it independently resolves out of the signing AID's KEL, reached through the delegation anchors in the authority's KEL (§4.2, §4.4), and MUST reject a mismatch with instance_attestation_untrusted_authority. Where signing is not delegated the signing AID is the authority AID and the two KELs coincide. Every field is derived from published KEL events, so the record is a re-encoding of state the verifier already holds; it is never transmitted as an AFP message, adds no public input, and adds no wire field. For root:publisher-manifest, the same record MUST also appear in the circuit's private witness: the circuit reconstructs its RFC 8785 JCS bytes, opens root_material_digest as their SHA-256 digest, and uses that record's signing_public_keys and signing_threshold for publisher-signature verification. The circuit binding and the verifier's independent recomputation are both mandatory and prove different facts: the former binds the hidden signature to the public digest, while the latter binds that digest to authority-anchored public KEL state. For root:build-provenance and root:tee-attestation, root_material_digest retains the external-artifact meaning fixed in their branches below and MUST NOT use this record.

The exact absent-sample sentinel is sha256:0000000000000000000000000000000000000000000000000000000000000000, which MUST NOT be accepted as an artifact digest. The circuit decodes each real digest to 32 bytes and exposes it as two unsigned 128-bit big-endian limbs (high limb, then low limb). It maps root_class to 1, 2, or 3 in the order shown above. Each string public input maps separately to two limbs as SHA-256 over u32be(byte_length) || UTF8(NFC(string)). The attestation_window object maps to two limbs as SHA-256 over its RFC 8785 JCS bytes and exposes starts_at and ends_at additionally as unsigned 64-bit Unix seconds. Verifiers MUST recompute every mapping and reject noncanonical strings, timestamps, digests, sentinels, or limb values. This construction uses SHA-256, not H(domain, x...), and therefore adds no AFP-KDC field-domain tag.

The root class MUST be one of the three literal values above. The root-authority identifier MUST resolve, under the verifier's authenticated verification-policy digest, to trust material the verifier accepted before receiving the proof. It is not a prover-selected display name.

For an organizational attestation root authority — the case root:publisher-manifest presents, and the case any KERI-rooted authority presents under any class — the authority is the accountable party that stands behind the attestation, identified by its organizational root AID, never a product, brand, build line, or software distribution. For such an authority, a root-authority identifier naming a product rather than the party accountable for it MUST NOT be accepted. This does not disturb the other two classes: for root:tee-attestation and root:build-provenance the root-authority identifier retains the branch-specific meaning fixed below — a hardware vendor's product root policy resolved from the verifier's own pinned vendor-root configuration, and a transparency log's checkpoint signer under the verifier's accepted log policy — and this paragraph neither restates nor overrides it.

An organizational authority MAY delegate product-scoped signing: the signing AID is then a delegated AID whose inception is anchored in the authority's KEL, exactly as an entity is delegated from a tenant root (§4.2), and root-material digest MUST commit to that delegated signing-key state. Rotation, compromise handling, and succession of a delegated signing key MUST run as explicit, published events — the delegated inception and each delegated rotation as events in the signing AID's KEL, each anchored by a seal in the authority's KEL (§4.2, §4.4, §4.6). It is through those anchors that the authority remains the authoritative source for the signing key's state and for the stale/revoked determination below, and this holds for any authority identified by a KERI AID without reference to any registry, advisory feed, or vendor document. For root:build-provenance and root:tee-attestation the equivalent determination is made against the log-policy and pinned vendor-root material fixed in those branches. What holds for every class, without exception, is that no authority-published feed is ever on the normative path. The binding between an authority AID and the legal entity it denotes is established out-of-band, at the moment a verifier accepts that authority's trust material into its verification policy; AFP defines no on-wire representation of that binding and no protocol step that establishes it.

root-material digest commits to the exact accepted transparency-log checkpoint, hardware endorsement/collateral set, or publisher signing-key state used for this proof. attestation-evidence digest has the branch-specific meaning fixed below and commits to evidence without exposing the raw provenance bundle, TEE quote, or signed manifest. The separately versioned ZKC machine build/runtime credential schema defines the provider-neutral claim consumed here; AFP binds that claim to these public inputs and does not redefine the credential schema or issuer-adapter stages.

The v2 circuit MUST prove all common properties: the prover holds an authentic, unexpired machine build/runtime credential and its holder binding; the credential's machine subject equals the KERI-authenticated entity or instance AID; the instance signing key is controlled by that subject; the hidden manifest opens to attested manifest digest; the hidden root evidence opens to attestation-evidence digest; the credential claim, root evidence, and manifest agree on the root class, root authority, machine subject, and manifest; and the proof transcript binds every v2 public input plus the active §5.9 presentation profile. It MUST then enforce the branch below for the declared root class. A verifier MUST reject an unknown class, a class/authority mismatch, a stale or revoked authority, a branch proof produced for another class, or a nonzero/zero public-sample-binding digest that violates that class's rule. It MUST also reject a root-material digest absent from its accepted policy for root:build-provenance and root:tee-attestation, where that digest names an externally pinned checkpoint or vendor-collateral set. For root:publisher-manifest the accepted policy binds the authority AID, not a key-state digest: root_material_digest is a per-key-state value that changes at every published rotation, and it is validated by recomputation from the resolved KEL state under the publisher-key-state rule above rather than by presence in a pre-signed list. A verifier's policy therefore does not have to be re-signed on every publisher rotation, and a policy that does additionally pin a specific publisher key-state digest pins that key state and nothing more.

Two clarifications govern that list where signing is delegated. Agreement on the root authority is agreement on the identifier value: the credential claim, root evidence, and manifest MUST agree on the root-authority identifier the proof declares, and the circuit is not required to prove the delegation chain behind it — the verifier resolves that chain out of circuit before acceptance, as stated above. For a KERI-rooted authority, staleness and revocation are resolved from its KEL: where the root-authority identifier is a KERI AID — the organizational case, and the only case root:publisher-manifest admits — a verifier MUST determine that authority's current key state, revocation, and succession from the authority's KEL and the KELs it anchors, under §4.2, §4.4, and §4.6, and MUST reject a proof whose committed signing-key state is superseded by a published rotation, whose delegated signing AID's delegation has been revoked, or whose authority root key state the verifier can no longer resolve. This parallels the kel_keystate_stale rejection of §12.3. For root:build-provenance and root:tee-attestation — whose authorities are a transparency log's checkpoint signer and a hardware vendor's product root, neither of which is required to be a KERI AID with a KEL — the same determination is made instead against the checkpoint-signer key and rotation state under the verifier's accepted log policy, and against the verifier's pinned vendor root configuration with its collateral freshness and revocation state, exactly as those branches already fix below. In every class the material is either self-certifying KERI state or material the verifier itself pinned; in no class is an authority-published feed on the normative path.

Authority-KEL integrity is not assumed. An authority is not a tenant (§2.1), so the §4.5 tenant-scoped witness duties do not attach to it of their own force. A verifier MUST therefore verify authority-KEL events — and the KEL events of any AID that authority anchors — against a witness set and threshold pinned in the authority trust material it accepted into its verification policy, applying the receipt-threshold rule of §4.5. A KEL that does not meet the pinned threshold, or that forks against events the verifier previously accepted, is treated as unresolvable authority key state and routes to exactly the consequence stated below, never to any other.

An authority MAY additionally publish an advisory or status feed covering its own key state and revocation status as a convenience; such a feed MUST NOT enumerate, rank, endorse, or report on any other authority (§1.2). No verifier is required to consult one, and no such feed is a normative path for discharging this obligation.

The consequence of any authority-related failure — an authority the verifier does not accept, an authority found stale or revoked, an unresolvable delegation, or unresolvable authority key state — is exactly the consequence of any other failure in this section and nothing more: in tenant→operator telemetry an alert under §6.6, and in every counterparty-facing use — a §5.4/§5.5 bilateral interaction, open federation (§5.8), and the introduction tier (§14.3) — declining the interaction and nothing beyond it. A rejected, stale, or revoked authority MUST NOT gate, quarantine, freeze, or otherwise act upon tenant state, a tenant's funds or notes, or any prior session, and MUST NOT force a downgrade of tenant state (Safeguards 3 and 5). A §14.7 contraction of the go-forward envelope is a form of declining to extend further credit and is unaffected by this sentence; it touches no prior session and no tenant state.

The operator-facing predicate catalog remains closed. Safeguard 1 permits each bilateral verifier to choose which of the three registered root classes and which authorities it accepts; it does not permit an implementation to invent a fourth class or accept an unregistered telemetry predicate. Extending the root-class taxonomy requires a revision to this section.

Standing up a new root authority does not. The set of authorities is open and unenumerated: any organization MAY stand as an authority under any of the three classes, on the identical terms this section states, and does so by publishing trust material a verifier can accept — not by registration with, permission from, notification to, or a grant by any party, and not by an amendment to this specification. Whether a given verifier accepts a given authority is that verifier's policy decision alone (Safeguard 1, §9). AFP maintains no authority registry and defines no procedure for admitting, ranking, suspending, or removing an authority. Naming an authority in this specification records an instance of the construction; it neither privileges that authority nor closes the set.

Shipped defaults are product configuration (normative). An implementation MAY ship a default verification policy naming authorities it recommends. Such an entry MUST be visible to, editable by, and removable by the deployment operating it; it MUST NOT be required for conformance, and an implementation MUST NOT hardcode an authority entry a deployment cannot remove. It is product configuration and not a default of this specification: this specification defines no default authority and no default (root class, root authority) pair. A shipped entry applies to an interaction only once the party operating the deployment signs it into that interaction's policy (§5.5, §14.4). The §9 Safeguard 1 and §12.8 rows state how this bears on the safeguards; this paragraph is where the obligation itself lives.

Non-normative — the first named authority. None of the following is yet operational. Pyramidal Inc.'s organizational root AID has not been incepted, and no AID value is asserted here or anywhere else in this specification. What follows records the intended instantiation of the construction above so that the normative text can stay authority-agnostic, exactly as ADAMAS is cited as the reference implementation without becoming the definition (§1.5.1, §2.1). Nothing in this note is a requirement of this specification; every requirement it alludes to is stated in the normative text above and binds there, not here.

  • Pyramidal Inc. is the first named attestation root authority. For proofs it stands behind, root_authority_identifier is intended to resolve to Pyramidal Inc.'s organizational root AID — the accountable legal entity, not the ADAMAS product.
  • ADAMAS build manifests are to be signed by a delegated, product-scoped ADAMAS publisher AID whose inception is to be anchored in Pyramidal's KEL, mirroring the tenant→entity delegation of §4.2. root_material_digest would then commit to that delegated signing-key state as afp:publisher-key-state:v1 above, with rotation, compromise handling, and succession of the publisher key running as explicit events in the publisher AID's KEL, each anchored by a seal in Pyramidal's organizational KEL.
  • The ADAMAS product line attests under root:publisher-manifest and is therefore self-attested at the platform layer, with the mandatory §6.4 public-sample binding and both halves of the disclosure duty above. Pyramidal attests only ADAMAS builds it produces; a fork or third-party distribution stands up its own authority under the same rules.
  • Once incepted, Pyramidal Inc.'s organizational root AID is intended to ship as the default entry in ADAMAS verification policies. That would be a shipped product configuration — visible, editable, and removable by the deployment operating it, per the normative paragraph above — never a default of this specification and never a requirement of it (§9, §12.8, §14.4).
  • The AID↔︎legal-entity binding would be established out-of-band when a verifier accepts Pyramidal's trust material, per the rule above.
  • Because the authority's KEL and published trust material do not yet exist, the operational-status disclosure stated normatively above applies today to any deployment selecting this root path, and §14.9 applies additionally where that deployment runs the introduction tier.
  • Pyramidal Inc. is also an AFP operator and is constrained as one by §8.6. The two roles are independent in both directions; neither transfers trust to the other.

6.4 Public-sample consistency

A self-attested claim is only as good as its resistance to a lying tenant. AFP requires that each self-attested predicate be paired with a public sample the verifier can check the signed assertion against — for example, an aggregate count or a digest the verifier independently holds — and that the proof demonstrate cryptographic consistency between the signed assertion and the sample.

For a root:publisher-manifest proof (§6.3.1), the mandatory public-sample binding is produced and signed by the tenant proving its own instance, as this section's artifact requires; it is not produced or signed by the attestation authority, and the authority's separate duty to characterize its own attestation (§6.3.1) is discharged in that authority's published trust material rather than here.

The consistency artifact is a ZKC proof over a tenant-signed sample binding:

interface AfpTelemetrySampleBinding {
  kind: 'afp:telemetry-sample-binding:v1';
  tenant_aid: string;
  predicate_id: string;                 // ZKC predicate identifier from §6.3
  predicate_version: string;
  telemetry_window: {
    starts_at: datetime;
    ends_at: datetime;
  };
  assertion_commitment: string;          // commitment to the private self-attested value
  sample_kind: 'aggregate' | 'selective-reveal' | 'external-digest';
  sample_commitment: string;             // commitment to the verifier-checkable sample
  sample_policy: {
    min_population?: number;             // REQUIRED for aggregate samples
    differential_privacy?: {
      epsilon: number;
      delta: number;
    };
    revealed_fields?: string[];          // REQUIRED for selective-reveal samples
    external_source_aid?: string;         // REQUIRED for external-digest samples
  };
  sample_context_hash: string;           // hash of cadence, operator, and predicate context
  issued_at: datetime;
  tenant_signature: string;
  zkc_proof: string;                     // proves consistency without revealing hidden values
}

The zkc_proof MUST prove all of the following:

  1. the prover controls tenant_aid and signed the binding;
  2. assertion_commitment commits to the same private value used by the self-attested telemetry predicate;
  3. sample_commitment commits to a verifier-checkable sample whose declared sample_kind and sample_policy are satisfied;
  4. the assertion is included in, bounded by, or equal to the public sample according to the predicate-specific rule; and
  5. the proof binds predicate_id, predicate_version, telemetry_window, sample_context_hash, and the operator-facing telemetry subject so the sample cannot be replayed across predicates, operators, or windows.

v2 counterparty mapping (normative). The afp:telemetry-sample-binding:v1 field names remain frozen for telemetry compatibility. For v2 sample bindings, including mandatory root:publisher-manifest bindings used in §14, telemetry_window MUST equal the v2 attestation_window; the operator component inside sample_context_hash MUST be replaced by verifier_aid; and the operator-facing telemetry subject component MUST be replaced by the exact AFP introduction session context. Requirement 5 above is evaluated with those substitutions, so the binding cannot replay across counterparty verifier, APR, session, or window. The binding's tenant signature and proof context MUST also bind afp.telemetry.instance_attestation.v2, its active §5.9 profile, and the selected root class/authority. These mappings do not change the v1 artifact's wire fields and MUST NOT be applied to frozen instance_attestation.v1.

The allowed sample forms are intentionally narrow:

Under §3, the operator has no per-tenant transport observation to use as a sample. A public sample for a self-attested predicate MUST therefore be one of the forms above; it MUST NOT be derived from operator surveillance of tenant traffic, message timing, counterparty identity, coordination volume, or any other coordination-plane observation.

If the sample binding is missing, expired, inconsistent with the predicate, or uses a sample form outside this section, the proof fails. In tenant→operator telemetry, §6.6 applies: the result is an alert only, never operator intervention in tenant state. In §14 counterparty use, the verifier declines the introduction interaction under §14.3; it MUST NOT form the provisional agreement, gate tenant state, or impose any other consequence.

6.5 Push model and cadence

Telemetry is tenant-push. A tenant publishes telemetry proofs on a telemetry subject the operator subscribes to; the tenant controls timing. The operator monitors cadence — a missing proof within an expected window is itself a meaningful operational signal — but cadence is established by the presence or absence of pushed proofs, not by transport observation of the tenant's other traffic.

6.6 Failure semantics

A failed or missing telemetry proof MUST NOT trigger any automated operator intervention in tenant state. Per Safeguard 3 and the operator-blindness invariant, the operator has no such capability and the protocol grants none. A telemetry-proof failure propagates as an alert on a separate operator-monitored subject; human investigation follows. This is the structural difference between AFP operator telemetry and a ZKC v0.4 compliance gate: a gate is allow-or-deny; AFP telemetry is observe-and-alert. AFP defines no gate over tenant state for any party.

6.7 Topology independence

The tenant↔︎operator protocol surface is identical whether the tenant runs on a managed transport or a self-hosted appliance. In every topology, the operator learns only what the tenant proves. There is no deployment in which the operator learns more by observation; §3 forbids it. The appliance case is therefore not a privileged tier for telemetry — it is the same protocol — and the managed-transport case is held to the same structural standard.


7. Deployment Topologies

7.1 Auditable implementation

Every topology in this section is conformance-relevant only to the extent that the software realizing it can be checked: operator blindness is a claim about capability (§3.2), and a capability claim is verifiable only against an implementation open to inspection. This subsection states that requirement; it applies to §7.2 and §7.3 alike. Two consequences are normative:

An implementation that cannot support inspection of its OB-relevant behavior MUST NOT claim OB as a capability (§3.2). Such an implementation falls under the honest-disclosure duty of §3.5: it MUST disclose the limitation to its tenants rather than represent OB as satisfied. Inability to support inspection is a limitation of the implementation, never a lesser standard of conformance — §3.2 admits no substitute for structural verifiability.

7.2 Appliance

An appliance is a self-hosted deployment of a conforming tenant that does not connect to an operator-run transport — not a distinct protocol target. For the reference implementation, it is offered as a hardware recommendation plus a licensed ADAMAS image and support plan. An appliance tenant runs the same AFP v0.3.0 as a managed-transport tenant. The appliance reaches the rest of the federation through the §8 routing layer; it does not connect to an operator-run transport that could observe it. Identity migration to or from an appliance is the one-move operation of §4.7.

7.3 No operator-run coordination transport

v0.3.0 does not define a deployment in which an operator runs the transport that tenant↔︎tenant coordination traffic transits. Such a deployment cannot satisfy OB-2 (§3.3) and is therefore not a conforming AFP v0.3.0 topology. Operators may run telemetry-subject infrastructure (§6.5) and may participate in the §8 routing layer as one relay among several, but no operator may occupy a position from which the coordination graph is observable.


8. Metadata-Privacy Routing

This section delivers OB-2 — the coordination-graph privacy of §3. It states the normative requirement (§8.1), the conformance properties any routing layer must satisfy (§8.2), the AFP Federation Mixnet — the concrete routing design v0.3.0 adopts (§8.3–§8.7) — private route resolution (§8.8), the relationship to honest disclosure (§8.9), and residual limitations (§8.10). The §8.2 properties are the conformance test; the §8.3–§8.8 design is the means by which v0.3.0 meets them. An implementation MAY satisfy §8.2 by another construction, but it MUST satisfy §8.2.

The threat model is explicit and was decided deliberately: the OB-2 adversary includes a passive observer of the network links between relays — a transit provider, an infrastructure operator, or a national-scale link observer — not merely a curious party running one piece of infrastructure. AFP's flagship use case is cross-jurisdictional collaboration between identifiable institutions; the existence and intensity of a coordination relationship is itself commercially sensitive, sometimes materially so, and the platform cannot ask a tenant to predict in advance which relationships are sensitive. The routing layer is therefore designed to defeat statistical traffic analysis by a global passive adversary, not only single-relay correlation.

8.1 Requirement

Tenant↔︎tenant coordination traffic MUST be routed such that no single transport operator, no single relay, no colluding minority of relays, and no passive observer of the links between relays can learn the pairing (tenant A AID, tenant B AID) for any coordination session, nor reconstruct it from timing or volume correlation.

8.2 Properties the routing layer must provide (conformance test)

A conforming routing layer MUST provide:

  1. Endpoint unlinkability. A relay forwarding a message sees, at most, one hop in each direction — never both the originating and terminating tenant of a session.
  2. No cleartext relationship in addressing. Routing headers and any identifier visible to a relay MUST NOT encode the tenant pair, and MUST NOT be stable across sessions in a way that lets a relay link a tenant over time.
  3. Correlation resistance. Timing and volume patterns MUST be treated so that a relay, a colluding minority of relays, or a passive observer of inter-relay links cannot reconstruct the pairing by correlation. This is the discriminating property and the reason a mixnet, not a simpler relay, is required.
  4. No single trusted relay. The layer MUST NOT contain a relay whose compromise reveals the graph. This is the §3.2 capability standard applied to the routing layer itself: decentralization of the relay set is required, not optional.

8.3 The AFP Federation Mixnet

v0.3.0 satisfies §8.2 with a self-contained, consortium-operated mix network of the Loopix family. "Self-contained" means the mixnet is operated by and for the federation; it is not a dependency on any public, token-incentivized, third-party anonymity network. "Loopix family" means the design class characterized by the Sphinx packet format, a stratified mix topology, continuous-time (exponential-delay) mixing, and mandatory cover traffic — the class with formal, published anonymity bounds against a global passive adversary.

Reference implementation. The preliminary reference implementation is Katzenpost (katzenpost/katzenpost, AGPLv3) — a Loopix-family mixnet using client-selected exponential mixing delays, a stratified topology, a configurable-geometry Sphinx, post-quantum cryptographic agility, and a decentralized directory-authority system. AFP v0.3.0 adopts Katzenpost as the reference implementation for development, not as a frozen normative dependency: Katzenpost is itself mid-evolution toward a next-generation protocol (the Echomix design) and has no confirmed large-scale production deployment as of this writing. Accordingly, §8 binds to the Loopix-family architecture (§8.2 properties), not to a specific Katzenpost version. An implementation tracks a named Katzenpost release; the AFP conformance test remains §8.2.

Reference maturity rule. A deployment that uses Katzenpost MUST publish the exact release, commit, configuration profile, and any local patches in its AFP conformance statement. Echomix is tracked as the expected next-generation Katzenpost protocol, but it is not a normative AFP dependency until AFP publishes a successor mixnet profile. A deployment MAY pilot Echomix-derived behavior only if the deployed behavior still satisfies §8.2 and the conformance statement identifies the deviation from afp.mixnet.profile.ob2-baseline.v1.

Why a mixnet and not a lighter relay. Simpler designs — VPNs with rotating exits, onion-routed relays without mixing, or queue-relay systems without cover traffic — defeat single-relay and IP-level correlation but do not defeat §8.2 property 3 against a link observer: a burst of real traffic leaving tenant A still correlates, by timing and volume, with the burst arriving at tenant B, regardless of how the source address is obscured. Only mixing delay plus cover traffic breaks that correlation. This is the property that selects the Loopix family.

8.4 Topology and packet format

The AFP baseline implementation profile is afp.mixnet.profile.ob2-baseline.v1. A deployment MAY define a stricter profile, but it MUST disclose any deviation and still satisfy §8.2.

Parameter ob2-baseline-v1 value Conformance rule
Packet format Sphinx, fixed-size packets Every coordination packet has identical observable size and form.
Hop geometry 3 mix hops, one hop per layer A deployment MUST NOT use fewer than 3 hops for OB-2 claims.
Layers 3 stratified layers No operator may control all nodes in any layer or enough adjacent-layer positions to observe a full path.
Payload class 4 KiB encrypted AFP payload fragment per packet Larger AFP messages are fragmented and padded into indistinguishable packets.
Per-hop delay Exponential delay, mean 30 s The delay is independently sampled per hop by the sender.
Maximum packet TTL 15 min Expired packets are dropped without revealing whether they were real or cover packets.
Tenant cover rate At least 1 packet every 10 s per federated tenant Real traffic is slotted into the same schedule; deployments may raise but not lower the rate.
Mix-node loop cover At least 1 loop packet every 10 s per mix node per adjacent layer Loop cover is independent of tenant activity.
Directory epoch At most 24 h Tenants MUST reject expired directories unless an emergency extension is KERI-witnessed.

8.5 Mandatory cover traffic

Cover traffic is REQUIRED, not optional, and is the property on which OB-2 against a link observer depends.

8.6 Relay operation — no single trusted relay

8.7 Mix-node directory

The set of mix nodes, their layer assignment, and their public keys MUST be published as a directory that every tenant can verify without trusting any single party.

The directory document has the following verifier-facing shape:

interface AfpMixnetDirectory {
  kind: 'afp:mixnet-directory:v1';
  profile: 'afp.mixnet.profile.ob2-baseline.v1';
  epoch: number;
  valid_from: datetime;
  valid_until: datetime;
  keri_anchor_event_dig: string;
  sphinx: {
    packet_payload_bytes: 4096;
    hop_count: 3;
  };
  mixing: {
    delay_distribution: 'exponential';
    mean_delay_seconds: 30;
    packet_ttl_seconds: 900;
  };
  cover: {
    tenant_packet_interval_seconds: 10;
    mix_loop_interval_seconds: 10;
  };
  layers: Array<{
    layer_index: number;
    nodes: Array<{
      mix_node_aid: string;
      operator_aid: string;
      endpoint: string;
      sphinx_public_key: string;
      directory_signing_key: string;
      software_ref: string;
    }>;
  }>;
  quorum_signatures: Array<{
    witness_aid: string;
    signature: string;
  }>;
}

A tenant MUST reject a directory if the KERI anchor is absent, the witness quorum is below the federation threshold, the profile parameters are weaker than ob2-baseline-v1, any layer is empty, or the operator-control constraints of §8.6 are violated.

8.8 Private route resolution

§5.2 step 1 requires an initiating entity to resolve a counterparty tenant AID to a transport route. This subsection specifies how, without any party learning the pairing.

8.9 Relationship to OB and to honest disclosure

§8.3–§8.8 are the mechanism by which OB-2 (§3.1) is satisfied. OB-1 (content) does not depend on this section — it is delivered by §5.2 channel encryption in every topology. A deployment whose mixnet is not yet operational, or which cannot yet run mandatory cover traffic across a sufficient mix-node set, does not satisfy OB-2 and MUST disclose this per §3.5: such a deployment claims OB-1 and states OB-2 as not-yet-met. Because OB-2 has little value at very small federation sizes (with few tenants there is almost no coordination graph to hide) and accrues value as the federation grows, an honest-disclosure interim during early deployment is consistent with this specification.

8.10 Residual limitations

Two limitations MUST be disclosed honestly rather than overclaimed:


9. Freedom Safeguards

AFP v0.3.0 inherits the six Freedom Safeguards defined normatively in ZKA v0.9.0-draft §1.6. They are not re-declared here; this section states only how each bears on AFP.

# Safeguard Bearing on AFP
1 Predicate Pluralism AFP carries ZKC compliance bundles (§5.4) but privileges no issuer or predicate set. A tenant's compliance requirements in a federation agreement (§5.5) are that tenant's bilateral choice; AFP defines no canonical or default set — of issuers, of predicates, of root classes, or of root authorities (§6.3.1). An implementation MAY ship a default verification policy naming authorities it recommends; that is implementation-side product configuration, and §6.3.1 states normatively that such an entry must be visible to, editable by, and removable by the deployment operating it and is not a default of this specification. Naming an authority in this specification privileges it in no way.
2 Minimal Disclosure Every selective disclosure and every telemetry claim (§6.3) is atomic — one property per proof. AFP message classes (§5.3) carry single-predicate proofs; composite disclosure requires explicit per-predicate consent.
3 No Revocability AFP defines no gate over tenant state for any party. Operator telemetry failure is observe-and-alert, never intervene (§6.6). No AFP message can invalidate, freeze, or redirect a ZKA note.
4 Open-Source Predicates Telemetry predicate circuits (§6.3) MUST be open-source, versioned, and published with a public review period, exactly as ZKC §1.4 requires of any predicate.
5 Agent Exit Rights A tenant's withdrawal of its assets to a base layer is a ZKA operation gated by nothing in AFP. Leaving a federation, leaving a managed transport for an appliance (§4.7), and changing operator are all unconditional. AFP adds no exit gate.
6 Verifier Accountability The first safeguard to constrain the counterparty rather than the operator, and load-bearing for open federation (§5.8). A tenant that requests a compliance proof is a verifier: it MUST authenticate the request under a stable KERI identity, and the holder retains a profile-minimized Disclosure Receipt of what was demanded, by whom, and for what purpose (ZKC v0.4.0 §5.5–§5.9; AFP §5.9). Anonymous demands are rejected; global linkability requires explicit consent.

Operator blindness (§3) is an AFP-level invariant in the same spirit as the Safeguards: a structural guarantee, verifiable by inspection (§7.1), not a policy promise.


10. Open Items

Carried forward for the v0.3.x spec series; surfaced here so they are not lost.

Generalized in v0.3.9-draft (ADR-0005 tenant definition). A tenant is no longer "an ADAMAS instance": it is a runtime that federates as a principal under its own tenant root AID and MUST satisfy the tenant conformance profile (§1.5.1) (§1.6, §2.1), with ADAMAS the reference implementation and the worked example rather than the definition. Tenanthood is structural, not a certificate: a runtime that falls short of an obligation is a non-conforming tenant under the honest-disclosure regime of §3.5 and §8.9, never a non-principal. Boundaries the spec drew as ADAMAS-internal are tenant-internal, and the duties they implicitly delegated are now stated as normative tenant obligations (§5.6, §5.7); the single trust level reads verified conforming instance (§1.2, §12.1, §12.5, §13.1), realized entirely by the existing §6.3.1 root-class and verifier-policy machinery; and §7.1's auditability requirement binds any conforming implementation rather than one vendor's licensing posture. This generalizes who may implement a tenant, never what the protocol does — no new message class, public input, or envelope revision, and every §1.2 retirement intact (restated there). The formal multi-vendor conformance program is deferred to the second-implementation milestone (the new open item above).

Completed in v0.3.9-draft (ADR-0005 authority naming) — the attestation authority. The note above deferred one half of ADR-0005; this revision lands it. The single trust level now reads verified conforming instance under a verifier-accepted attestation authority (§1.2, cross-referenced from §12.1, §12.5, and §13.1), still realized entirely by the existing §6.3.1 root-class and verifier-policy machinery and still with no wire change. §6.3.1 fixes what an authority is — the accountable party, for an organizational authority its organizational root AID, never a product — permits KEL-anchored product-scoped delegated signing on the §4.2 shape, assigns delegation resolution to the verifier rather than the circuit, and makes the stale/revoked determination dischargeable, for a KERI-rooted authority, from that authority's own KEL and the KELs it anchors (§4.2, §4.4, §4.6) — with the transparency-log and hardware classes keeping their existing verifier-pinned material, and no authority-published feed on the normative path in any class. The anti-gatekeeping guard is the point of the change: extending the root-class taxonomy requires a revision to §6.3.1, but standing up a new authority does not — the set of authorities is open, entered by publishing acceptable trust material rather than by registration, grant, or amendment, and acceptance is always the verifier's own policy (§1.2, §5.5, §5.8, §9, §12.8). Pyramidal Inc. is named as the first such authority non-normatively (§6.3.1), with no AID value asserted and the authority not yet operational (§14.9); an authority named here or shipped as an implementation's default holds no protocol privilege, is not a principal (§2.1), and certifies no conformance (§1.5.1). Requiring instance attestation at all remains each party's own policy: nothing in this revision makes an attestation a precondition of a session.

Generalized in v0.3.4 — open-federation trust basis (ADR-0004). AFP's trust basis is no longer "a bilateral federation agreement is the sole precondition for a session." A session MAY be opened on either a §5.5 agreement (known counterparty) or per-interaction ZKC compliance attestation (§5.8, open federation). This unlocks ADAMAS-to-ADAMAS transactions between unknown counterparties with no new protocol and no weakening of operator-blindness — only trust establishment is generalized (attestation, compatible with OB-2); discovery/rendezvous stays out (the new open item above). Safeguard 6 (Verifier Accountability) is what keeps stranger-attestation accountable, and all six safeguards are inherited identically by both paths.

Resolved in v0.3.0; reconciled in v0.3.7 and fully pinned in v0.3.9 — shared cryptographic core. AFP-KDC v1.0.2 (Appendix A) is the self-contained shared derivation module referenced by ZKA, ZKC, and ZKM. It retains every v1.0.0 value while making field-tag derivation and operational-tag ownership unambiguous and fully parameterizing the deployed Poseidon2 sponge.

Resolved in v0.3.1 — shared bilateral-session core (ADR-0003). The session machinery that PRP v0.2 and VXP v0.2 each re-derived is consolidated into AFP as the Bilateral Session Core (§12), and the Profile concept (§13) names PRP and VXP as the two v0.2 profiles. Two seams the profiles flagged are resolved here: (a) NATS namespace — profiles live under afp.{tenant}.{profile}.*; vxp.* is a subtree under AFP, not standalone (§12.4); (b) the federation bundle home — ZKA owns the cryptographic bundle (zka:bundle:v1, incl. the ZKA/Coord proof), AFP owns the session envelope (afp:envelope:v1), and vxp:coordbundle:v1 / any PRP wrapper are replaced by envelope-over-bundle (§12.7). ZKA v0.7 now supplies the required bundle member, so this is no longer an open cross-protocol item. The mechanical trim of PRP/VXP to reference §12/§13 is tracked in PROFILE_MIGRATION_NOTE.md.

Resolved in v0.3.2 — ZKA bundle dependency. The bundle dependency recorded by §12.7 is resolved by ZKA v0.7 §5.4: zka:bundle:v1 now carries an optional coord_proof member and supports pure work-attestation bundles. AFP's afp:envelope:v1 therefore wraps a single ZKA-owned bundle for payment and non-payment work-attestation flows.

Resolved in v0.3.2 — DGP gateway boundary. The DGP seam named in earlier drafts is resolved by §1.4.1: tenant-exposed DGP gateways are non-AFP integration surfaces. AFP provides no discovery feed to them, grants them no coordination-traffic visibility, and does not expand the AFP trust boundary around them.

Resolved in v0.3.2 — witness bootstrapping. §4.5 now defines afp:witness-bootstrap:v1, managed and bring-your-own witness bootstrap paths, verifier checks, failure semantics, and migration behavior.

Resolved in v0.3.2 — public-sample consistency. §6.4 now defines afp:telemetry-sample-binding:v1, the allowed public-sample forms, proof obligations, anti-replay context binding, and failure semantics for self-attested telemetry predicates.

Resolved in v0.3.2 — operator telemetry predicate catalog. §6.3 now defines AFP predicate IDs, issuer models, public inputs, private witnesses, cadence, and failure signals for instance attestation, build currency, compute-quota compliance, and audit-log integrity.

Resolved in v0.3.2 — AFP rotation sequencing. §4.6 now defines afp:rotation-presentation:v1, the required ordering from KEL rotation through ZKC credential rebinding and ZKA note-migration status, verifier behavior, and failure semantics. ZKA note migration itself remains a ZKA-owned Draft seam.

Resolved in v0.3.2 — metadata-private routing parameters. §8 now defines afp.mixnet.profile.ob2-baseline.v1, including Sphinx geometry, mixing delay, cover-traffic floors, directory epoch rules, the afp:mixnet-directory:v1 KERI-bound directory artifact, and Katzenpost/Echomix reference-maturity disclosure.

Resolved in v0.3.x — instance-level reputation / stranger onboarding. §14 (Introduction Tier) defines how two instances with no prior relationship reach a first §5.5 agreement: a credential + ZKC-proof handshake auto-negotiates a scoped, collateral-priced provisional agreement, and limits then grow with a ZK-provable, non-replayable reputation signal bound to that agreement. This supersedes the §5.5 "working assumption is no" for the stranger case — the reputation signal is progressive, ZK-provable, and bilateral (per-agreement), not a portable global score, and it grows the envelope rather than gating any tenant state (§14.6, Safeguards 3/5). The reputation circuit and the bonded-escrow rail are honestly-disclosed companion-requirement seams (§14.9).


11. Relationship to Prior AFP Documents

Document Status under v0.3.0
Agent_Federation_Protocol_AFP.md (v0.2.0-draft) Superseded. Retain as historical reference; do not implement against it. NATS subject and JetStream patterns are partially reusable as implementation reference; the semantic model is replaced.
AFP_AGENT_IDENTITY_ADDENDUM.md (v0.1.1-draft) Superseded. Identity-lifecycle concepts survive in KERI-aligned form (§4); the hash-derived identifier and flat nkey_history model are retired.
keri_afp_mapping.md (v0.1.0-draft) Promoted to canonical: the basis of §4.
afp_v0_3_scope_note.md (v0.3-scope) Realized by this specification.
afp_v0_3_scope_addendum.md (v0.3-scope) Realized by this specification: Decision 1 (identity) is §4; Decision 2 (telemetry) is §6, revised per the operator-blindness invariant of §3 to remove the transport-observed telemetry layer.

Revision note. The scope addendum's Decision 2 specified a two-layer telemetry model whose Layer 1 was transport-observed per-tenant metadata. This specification supersedes that: under the operator-blindness invariant (§3), per-tenant transport observation is not a capability the operator is permitted to have, so all per-tenant operator knowledge is ZKC-proved (§6.1). The addendum's Layer 2 survives as §6.3; its Layer 1 is removed. This change follows the decision, taken after the addendum was written, to make ADAMAS source-available and to keep the operator off the coordination path entirely.


12. Bilateral Session Core

This section group is the consolidation mandated by ADR-0003. It is normative, added in v0.3.1. Two tenant-level behaviors specified separately — PRP pairwise reconciliation and VXP value exchange — each independently re-derived the same bilateral-session substrate: two verified instances open a session over AFP, bind it to KERI identities, exchange signed atoms under bilateral policy, resolve disputes bilaterally with an optional pre-agreed arbiter, commit with both signatures and an optional DLT anchor, and inherit the Freedom Safeguards. AFP v0.3.1 lifts that substrate here so it is defined once. AFP's own §5 tenant↔︎tenant work exchange is an instance of it; PRP and VXP are profiles (§13) over it.

The core does not replace §4 (identity), §3 (operator blindness), §8 (routing), or §9 (safeguards); it composes them into the session abstraction that profiles consume. Where a §12 subsection restates a construct already introduced in §5, §12 is the consolidated, profile-facing statement and §5 is its work-exchange application.

12.1 The bilateral session

A bilateral session is the unit of all AFP-mediated interaction between two verified conforming instances (§1.2). Where — and only where — a party requires instance attestation, "verified" is verified under a root class and root authority that party accepts (§6.3.1); trust basis (a) below imposes no attestation requirement of itself — whether the agreement carries one is bilateral (§5.5) — and this section imposes none. It is, by construction:

A profile (§13) does not define a new session; it runs inside a bilateral session, contributing its own atom kinds and state machine while the session establishment, identity, transport, dispute, commitment, envelope, and safeguard machinery are the core's. The introduction tier (§14) is the one place a profile is permitted to drive session opening itself, and only because closing the stranger-onboarding gap requires it; the carve-out is bounded to that profile and stated normatively in §14.2.

12.2 Session establishment, lifecycle, and timeout

12.2.1 The session atom

A bilateral session is durable, queryable AKG state, represented by a core SessionAtom (afp:session:v1). A profile MAY specialize/extend this atom (PRP's prp:session:v2 and a VXP transaction context are profile specializations), but every profile session carries the core fields:

interface AfpSessionAtom {
  kind: 'afp:session:v1';
  id: string;                       // session id; component of the session context (§5.2 step 4)

  // Principals — KERI-aligned (§4). The session is between two ENTITY delegated AIDs,
  // each verified up to its TENANT root AID.
  initiator: AfpPartyRef;
  responder: AfpPartyRef;

  // Governing bilateral federation agreement (§5.5) and the profile this session runs.
  agreement_ref: string;            // AFP bilateral federation agreement id
  agreement_version: string;        // pinned at session open
  profile: string;                  // registered profile name (§13.2), e.g. 'prp' | 'vxp' | 'work'
  profile_policy_hash: string;      // hash of the profile policy block exchanged at open

  // Lifecycle (§12.2.2)
  status: AfpSessionStatus;

  // Timing / timeout (§12.2.3)
  opened_at: datetime;
  expires_at: datetime;             // hard session timeout; REQUIRED
  closed_at?: datetime;

  // Binding
  session_context: string;          // H(initiatorEntityAID, responderEntityAID, sessionId, timestamp)
}

interface AfpPartyRef {             // the canonical session identity binding (§12.3)
  tenant_aid: string;               // §4 tenant root AID (self-certifying)
  entity_aid: string;               // §4 entity delegated AID — the acting principal
  kel_event_dig: string;            // KEL event the signing key is pinned to (key-state pinning)
  signature: string;                // KEL-anchored signature over the atom content hash
}

enum AfpSessionStatus {
  PROPOSED = 'proposed',            // open requested, not yet accepted
  OPEN     = 'open',                // mutually authenticated, channel established
  ACTIVE   = 'active',              // profile exchange in progress
  CLOSING  = 'closing',             // graceful teardown initiated
  CLOSED   = 'closed',              // completed normally
  EXPIRED  = 'expired',             // expires_at reached before close
  ABORTED  = 'aborted'              // torn down on error or by either party's exit (§12.8, Safeguard 5)
}

The profile field is recorded in the session so the transport layer and the counterparty know which atom kinds and state machine govern the exchange. afp:session:v1 carries no profile-specific fields; those live in the profile's own atoms.

12.2.2 Lifecycle

The core session lifecycle is the part of every profile's state machine that concerns the session itself, distinct from the profile's exchange state machine (which the profile defines over ACTIVE):

 propose ──▶ PROPOSED ──accept──▶ OPEN ──begin──▶ ACTIVE ──close──▶ CLOSING ──▶ CLOSED
                │                                     │                          ▲
            reject/timeout                   either-party exit (§12.8 SG-5)      │
                │                                  or error                      │
                ▼                                     ▼                  profile reports done
            ABORTED                                ABORTED ──────────────────────┘
   (EXPIRED reachable from any non-terminal state when expires_at passes)
  1. Propose / accept. The initiator proposes a session under a named profile and a pinned federation-agreement version; the responder accepts (→ OPEN) or rejects (→ ABORTED). Acceptance completes the mutual authentication and channel establishment of §5.2.
  2. Activate. On the first profile exchange message the session moves to ACTIVE. The profile's own state machine (PRP Compare→…→Commit; VXP Intent→…→Settlement) runs entirely within ACTIVE.
  3. Close. When the profile reports its exchange complete, the session moves CLOSING → CLOSED. A core CommitmentAtom (§12.6), where the profile produces one, is the artifact that justifies a clean close.
  4. Abort / expire. Either party MAY exit at any time (§12.8, Safeguard 5) — the session moves to ABORTED, leaving whatever signed atoms exist as the durable record. expires_at elapsing moves any non-terminal session to EXPIRED. No core transition can be forced by an operator (§12.8, Safeguard 3).

A profile MAY name additional terminal annotations for its exchange (e.g. PRP ESCALATED); those describe a session that closes or aborts at the core level — the core recognizes only the states above.

12.2.3 Timeout

Every session MUST carry an expires_at. A session that has not reached a terminal state by expires_at is EXPIRED; a fresh session must be proposed to continue. Profiles MAY set finer-grained per-phase deadlines (PRP evidence_deadline_seconds, VXP offer valid_until) within the federation agreement's policy block, but the core session timeout is the outer bound and is non-optional. Timeout is observe-and-resolve, never operator intervention: an expired session simply ends; nothing in tenant state is frozen or rolled back (§12.8, Safeguard 3).

12.3 Identity binding for sessions

Session identity binding is §4 applied to the session, stated once here so profiles cite it rather than re-derive it:

A profile's party reference (PRP PartyRef; VXP created_by: EntityAID + the §1.3 derivation) is a restatement of AfpPartyRef; under v0.3.1 it is AfpPartyRef, and the profile carries no additional identity machinery.

12.4 NATS namespace ownership

AFP owns the federation subject tree. No profile defines a standalone namespace. The canonical tree is:

afp.{tenant}.{profile}.*

where {tenant} scopes the subtree to a tenant and {profile} is a registered profile name (§13.2). Concretely:

afp.{tenant}.work.*            # base work-exchange behavior (the §5.3 message classes)
afp.{tenant}.session.*         # core session lifecycle (propose / accept / close / status)
afp.{tenant}.prp.*             # PRP profile subtree
afp.{tenant}.vxp.*             # VXP profile subtree
afp.{tenant}.telemetry.*       # tenant↔operator telemetry (§6.5) — operator-facing, not a bilateral profile

Normative rules:

  1. A profile's subjects MUST live under afp.{tenant}.{profile}.* and nowhere else. A standalone top-level namespace (a bare vxp.*, or the retired prp.{org}.*) is non-conforming.
  2. A profile name occupies exactly one {profile} label and MUST be registered (§13.2) before its subtree is used.
  3. The subjects MUST remain metadata-private under §8: the {tenant} label and any subject string visible to a relay is carried only inside Sphinx-wrapped packets and MUST NOT be a stable cleartext identifier of the tenant pair (§8.2 property 2). The tree above is the logical namespace; its on-the-wire form is the routing layer's concern.

Resolves the VXP flag. VXP v0.2 §1.4 / §16 flagged that its vxp.exchange.* / vxp.channel.* subjects sat in a standalone vxp.* namespace pending an AFP decision on ownership. The decision is: vxp.* is a profile subtree under AFP, addressed afp.{tenant}.vxp.*. VXP's vxp.exchange.intent becomes afp.{tenant}.vxp.exchange.intent, and so on. PRP v0.2 already adopted afp.{tenant}.prp.*; v0.3.1 makes that ownership normatively AFP's, not a PRP-side assumption.

12.5 Bilateral dispute and arbiter model

PRP §8 and VXP §9 specify the same dispute model in different words. It is consolidated here; both profiles reference this section.

Primary — bilateral-only, no operator mediation. A dispute within a bilateral session is resolved bilaterally, between the two principals, or not in-protocol at all. There is no operator-mediated dispute: the operator of any AFP transport cannot adjudicate, freeze, reverse, or force any outcome. This is Safeguard 3 (No Revocability) made concrete at the session layer (§12.8): a core CommitmentAtom (§12.6) is valid only with both parties' KEL-anchored signatures; no admin, governance, or operator key can produce or invalidate one. When the two parties cannot agree in-protocol, the session aborts/escalates with a signed divergence record (the profile's dispute atoms) as the durable artifact, and the parties resolve off-protocol — through their own entities, then the federation agreement's governance process — and, failing that, sever the relationship. Exit is unconditional (§12.8, Safeguard 5).

Optional — a pre-agreed third-party arbiter. A federation agreement (§5.5) MAY name, in advance, a single arbiter — a third entity AID, itself a verified conforming instance (§1.2) — to break a tie. The two parties decide bilaterally what verification they require of the arbiter, exactly as for any counterparty; this section imposes no instance-attestation requirement on an arbiter and gives no attestation authority any standing over a dispute. The arbiter:

For a monetary leg (a VXP concern), an arbiter MAY act as a co-signer on a pre-agreed 2-of-3 escrow release — able to break a tie only within the funds the parties escrowed, never against base-layer custody (Safeguard 5). This is the one arbiter capability that touches value, and it is bounded by what the parties escrowed in advance.

There is no global arbiter registry, no min_tvf_level, no capability query, no random-from-pool selection — those are marketplace mechanisms, retired with the v0.2.0 marketplace (§1.2). A profile MAY add profile-specific escalation steps (PRP's entities→governance ladder, VXP's per-leg breach conditions) on top of this model, but the bilateral-only-primary + optional-pre-agreed-arbiter shape, and the no-forced-outcome guarantee, are the core's.

12.6 Commitment and optional-anchor primitive

Both profiles end an interaction with a bilaterally-signed commitment to an agreed outcome, chained for replay-resistance, with an optional DLT anchor. Consolidated:

12.6.1 Bilateral commitment

A core CommitmentAtom (afp:commitment:v1) is the record that two principals agreed to an outcome:

interface AfpCommitmentAtom {
  kind: 'afp:commitment:v1';
  id: string;
  session_ref: string;                 // the afp:session:v1 this commits

  outcome_root: string;                // Poseidon2 commitment to the agreed outcome
                                       // (profile-defined: PRP reconciled-state root; VXP settled-legs root)
  profile: string;                     // which profile produced this outcome

  // Bilateral signatures — BOTH required (§12.5, Safeguard 3)
  initiator_signature: AfpCommitSignature;
  responder_signature: AfpCommitSignature;

  // Chaining — replay-resistant ordered history per AID-pair
  previous_commitment_ref?: string;
  sequence_number: number;             // monotonically increasing per AID-pair

  anchor_ref?: string;                 // afp:anchor:v1, if anchored (§12.6.2)

  // Optional pre-agreed arbiter co-signature (§12.5), present only if an arbiter acted
  arbiter_signature?: AfpCommitSignature;
}

interface AfpCommitSignature {
  signed_by: string;                   // tenant/entity AID
  kel_event_dig: string;               // pins key state at signing (§12.3)
  signature: string;                   // Ed25519, KEL-anchored
  signed_at: datetime;                  // observational metadata; not in signature payload
}

Normative properties:

A profile's commitment atom (PRP prp:commitment:v2; a VXP SettlementAtom) is a specialization of this primitive: it MAY add profile-specific fields (PRP bilateral_metrics, VXP legs[]) but MUST carry the bilateral-signature, chaining, and outcome-root machinery defined here.

12.6.2 Optional anchor

Anchoring is optional. The protocol is fully functional with bilateral KEL-anchored signatures alone, which already give a signed, ordered, replay-resistant history. For records whose disputes might later be adjudicated by a party outside the bilateral relationship (a regulator, an auditor), a commitment MAY be anchored to a DLT for an independent, tamper-evident timestamp:

interface AfpAnchorAtom {
  kind: 'afp:anchor:v1';
  id: string;
  commitment_ref: string;

  chain: string;                       // e.g. 'ethereum:arbitrum' | 'ethereum:base' | 'ethereum:optimism'
  via: 'apl';                          // ADAMAS Payment Layer
  transaction_id: string;
  block_number: number;
  block_timestamp: datetime;

  anchored_data: {                     // hashes only — never atom content
    commitment_hash: string;
    outcome_root: string;
    parties: string[];                 // AIDs, hashed for privacy
    sequence_number: number;
  };
  inclusion_proof: string;             // either party verifies independently
}

A profile's anchor policy (PRP policy.anchor.mode, VXP channel settle_to) selects when to anchor; the anchor atom and the Ethereum-L2-baseline rule are the core's.

12.7 The federation bundle wire format — session envelope over ZKA bundle

This subsection resolves the bundle-home ambiguity that VXP v0.2 (its vxp:coordbundle:v1, §11/§16) and PRP v0.2 (its direct zka:bundle:v1 usage) both surfaced. Decision, stated explicitly:

ZKA owns the cryptographic bundle; AFP owns the session envelope.

  • The ZKA Compliance Bundle zka:bundle:v1 (ZKA v0.9.0-draft §5.4) is the cryptographic bundle: it carries the ZKA/Pay transaction proof (where present), ZKC v0.4 zkc:proof:v2 anonymous/scoped proofs or explicitly-consented linkable zkc:proof:v1 proofs, the applicable identity/presentation binding proof, and the ZKA/Coord recursive work-attestation proof. AFP does not introduce a replacement bundle version.
  • AFP owns the session envelope afp:envelope:v1: the construct that carries a zka:bundle:v1 across a tenant boundary within a bilateral session, binding it to the session context (§12.3) and routing it on the namespace of §12.4. The envelope is session-level framing; the bundle is cryptographic payload.
  • Therefore VXP's vxp:coordbundle:v1 and any PRP bundle wrapper are REPLACED by AFP envelope + ZKA bundle. A profile never authors a bundle wire format; it places a ZKA bundle inside an AFP envelope.
interface AfpEnvelope {
  kind: 'afp:envelope:v1';
  session_ref: string;                 // the bilateral session (§12.2) this crosses within
  session_context: string;             // H(initiatorEntityAID, responderEntityAID, sessionId, timestamp) (§12.3)
  profile: string;                     // profile placing the envelope (e.g. 'vxp' | 'prp' | 'work')

  bundle: ZkaBundle;                   // zka:bundle:v1 (ZKA §5.4) — cryptographic payload, carried verbatim
  bundle_version: 'zka:bundle:v1';

  message_class: string;               // which AFP message class / subject this rides (§5.3, §12.4)
  generated_at: datetime;
  expires_at: datetime;
}

Verification is two-layer and unambiguous: AFP verifies the envelope (its session_context matches the live session, the envelope has not expired, the profile/message_class are valid for the session); ZKA verifies the bundle (every proof inside zka:bundle:v1 — transaction, compliance, binding, coordination — verifies, and its required context bindings are valid). The bundle metadata.context MUST equal the envelope's session_context in every AFP flow, including pay. The pay profile additionally derives a distinct interaction_context from that session context and the §15.3 quote hash — computed over signature-free quote content, before either signature (§15.4); the ZKA payment proof/request for that quote MUST bind the derived value, without changing the bundle/envelope equality, changing afp:envelope:v1, or overloading its session_context. No proof system lives in the AFP layer; AFP frames and binds, ZKA proves.

Resolved interface. ZKA v0.9.0-draft §5.4 satisfies this requirement by adding an optional coord_proof member to zka:bundle:v1 and by permitting pure work-attestation bundles where coord_proof is present and transaction is absent. The bundle's metadata.context is the shared replay/context binding and always equals the AFP envelope's session_context. For pay, §15.4 defines the additional quote-bound interaction context supplied to the ZKA payment proof/request — whose binding the pool and proof enforce; the derivation itself is a producer obligation, checked by any party that recomputes the §15.4 fold. AFP therefore has no transitional profile-authored bundle: a profile carries an AFP envelope over zka:bundle:v1, and ZKA verifies the transaction, compliance, binding, and coordination proofs that are present.

12.8 Freedom Safeguards at the session layer

The six Freedom Safeguards are defined normatively in ZKA v0.9.0-draft §1.6 and inherited by AFP at the protocol level (§9). This subsection maps them onto the bilateral session so that every profile inherits them by building on the core — PRP and VXP need not, and under v0.3.1 do not, re-derive a safeguards mapping. (PRP §14 and VXP §14 become references to this subsection; see PROFILE_MIGRATION_NOTE.md.)

# Safeguard (ZKA §1.6) Application to an AFP bilateral session — inherited by every profile
1 Predicate Pluralism A session privileges no issuer or predicate set. Compliance requirements are the bilateral choice recorded in the federation agreement (§5.5); the core defines no canonical or default predicate set and no canonical or default root authority (§6.3.1), and a profile MUST NOT introduce one. A default entry an implementation ships in its verification policy is product configuration, not a core or profile default; it is visible, editable, and removable by the deployment operating it (the duty is normative in §6.3.1), and it applies to an interaction only when signed into that interaction's policy (§9, §14.4).
2 Minimal Disclosure Every in-session proof is atomic — one predicate per proof (§5.4). A core CommitmentAtom commits to an outcome_root, not cleartext (§12.6.1). The session reveals only what the exchange requires; profiles enforce this structurally (PRP's Merkle-subtree pruning, VXP's value commitments) but inherit the default from the core.
3 No Revocability No operator, arbiter, or platform can force, freeze, reverse, or invalidate a session outcome. A CommitmentAtom requires both parties' KEL-anchored signatures (§12.6.1); dispute is bilateral-only with at most a pre-agreed arbiter that still needs both signatures (§12.5); timeout/expiry is observe-and-resolve, never intervention (§12.2.3). This is the session-layer statement of the §3 operator-blindness invariant.
4 Open-Source Predicates Any predicate a session carries MUST be open-source, versioned, and published with a public review period, exactly as ZKC §1.4 / ZKA §1.6 require. A profile adds no opaque predicate.
5 Agent Exit Rights Either party MAY exit a session, and the relationship, at any time (§12.2.2 → ABORTED); exit is unconditional and carries no protocol penalty. Leaving a federation, leaving a managed transport for an appliance (§4.7), and changing operator are all unconditional. A compliance-proof rejection blocks the interaction (the session declines), never exit. A profile's recourse for abandoning a confirmed commitment is only the bilaterally-agreed terms in the agreement, never an operator-enforced lock.
6 Verifier Accountability A proof or attestation request MUST be authenticated under the requesting party's stable KERI identity and the holder MUST retain a portable receipt of what was requested, for which declared purpose, and by whom (§5.8). Anonymous or unreceipted demands are rejected. A profile MUST preserve this accountability for every proof it requests.

A profile inherits this table by running over the core. A profile MAY note how it realizes a safeguard structurally (as PRP's Localize algorithm realizes Safeguard 2), but it MUST NOT weaken any safeguard, and it does not re-establish them — they hold because the session does.


13. Profiles

13.1 What an AFP profile is

An AFP profile is a named behavior over the Bilateral Session Core (§12). ADR-0003 establishes the category: PRP and VXP are not peer protocols (a protocol is a wire contract between independent, mutually-distrusting implementations — strangers implement ZKA/ZKC/AFP), because both endpoints of PRP and VXP are always verified conforming instances (§1.2) — that is, AFP tenants. They are also not purely-internal components. They are profiles: standardized behaviors layered on AFP, shipped within AFP rather than as independent standards with their own governance.

A profile:

The test for profile vs. protocol: if both endpoints are always AFP tenants (§1.5.1) and the behavior is a named way of using a bilateral session, it is a profile. ADR-0005's generalization of the tenant definition does not fire this test: a second, non-ADAMAS tenant implementation is still an AFP tenant, so PRP and VXP remain profiles and their trust posture is unchanged. Nor does the naming of an attestation authority (§6.3.1) fire it: a tenant attesting under any accepted authority, or under none, is still an AFP tenant. Consistent with ADR-0005's own out-of-scope clause for PRP/VXP, this sentence carries the unqualified phrase deliberately — the endpoint class it names is AFP tenants, and narrowing it to attested endpoints would change the profiles' scope, which ADR-0005 disclaims. If a behavior is later opened to counterparties that are not AFP tenants, promote that profile to a standalone standard then — promotion is cheap; premature standardization is expensive (ADR-0003).

13.2 Profile registration and extension

Profiles are registered lightly — AFP deliberately avoids a heavyweight registry (that would re-introduce marketplace machinery, §1.2). A profile is defined by a small, declarative profile descriptor:

interface AfpProfileDescriptor {
  name: string;                 // the {profile} namespace label (§12.4); unique; lowercase; e.g. 'prp'
  version: string;              // profile spec version, e.g. '0.2'
  spec_ref: string;             // URI / citation of the profile specification
  atom_kinds: string[];         // the profile's atom-kind discriminators, e.g. ['prp:session:v2', …]
  policy_schema_ref: string;    // the policy block this profile adds to the federation agreement (§5.5)
  status: 'reference' | 'experimental' | 'deprecated';
}

Registration rules:

  1. A profile name occupies exactly one {profile} label in the §12.4 tree and MUST be unique across registered profiles.
  2. The registry is the set of descriptors published with the AFP specification plus any a federation agrees bilaterally to recognize. There is no global, queryable, marketplace-style profile directory (§8.8's reasoning applies: such a directory would itself leak the coordination graph). Two tenants recognize a profile by naming it in their federation agreement; the descriptor is how they agree on its atom kinds and policy schema.
  3. A profile MUST register its atom-kind discriminators so they do not collide with another profile's or with the core's afp:*:v1 kinds.
  4. Extension is by versioning the profile (a new descriptor version), not by mutating the core. A profile MUST NOT add to or alter the core's §12 machinery; if a behavior needs a core change, that is an AFP-spec change, not a profile change. This keeps the core stable and the dependency acyclic: profiles depend on the core; the core depends on no profile.
  5. A profile descriptor's status is reference (specified with AFP, the recommended set), experimental (recognized bilaterally, not yet a reference profile), or deprecated.

13.3 Published profiles

AFP publishes the three v0.2 reference behaviors and two experimental profiles: the v0.3.x afp.introduction profile (§14), which remains experimental until its lower layers are operational (§14.9), and the pay v1.0 profile (§15), which remains experimental pending independent production-audit evidence for its Base settlement dependencies.

Profile (name) Behavior Atom kinds Spec Status
work (base) AFP's own tenant↔︎tenant work exchange — §5: a bilateral cross-tenant work request and a ZKA-carried result / attestation. The base behavior of the core, not a separate document. afp:session:v1, afp:commitment:v1 + the §5.3 message classes This spec, §5 + §12 reference
prp Pairwise Reconciliation Protocol — bilateral state reconciliation: Compare → Localize → Evidence → Resolve → Commit (+ optional Anchor). prp:session:v2, prp:comparison:v2, prp:divergence:v2, prp:evidence:v2, prp:resolution:v2, prp:commitment:v2, prp:anchor:v2 PRP v0.2 reference
vxp Value eXchange Protocol — bilateral value exchange: Intent → Negotiation → Commitment → Fulfillment → Settlement, across reciprocal / compute / data / capability / money domains. vxp:* discriminators over IntentAtom, NegotiationAtom, CommitmentAtom, FulfillmentAtom, SettlementAtom, DisputeAtom, ChannelAtom, SubscriptionAtom VXP v0.2 reference
afp.introduction Introduction Tier — stranger onboarding: a credential + ZKC-proof handshake that auto-negotiates a scoped, collateral-priced provisional §5.5 agreement, with reputation-driven limit growth, graduation, and safeguard-compatible downgrade/termination. afp:introduction-offer:v1, afp:introduction-request:v1, afp:credential-presentation:v1, afp:provisional-agreement:v1, afp:limit-adjustment:v1, afp:downgrade:v1, afp:termination:v1 This spec, §14 experimental
pay USD-obligation/WETH-settlement — a bilaterally authenticated USD commercial obligation and exact WETH-wei quote bound to a Base ZKA-native pool, with finality-aware receipt evidence. afp:payment-quote:v1, afp:payment-receipt:v1 AFP v0.3.6-draft, §15 experimental

Each of PRP and VXP keeps its own behavior-specific machinery — PRP's O(D·log N) Merkle localization, named scope filters, star / mesh multi-party composition; VXP's value-domain taxonomy, exchange channels, subscriptions, per-domain breach conditions. Those are the profiles' value and stay in the profile specs. What they stop specifying is the shared core: session, identity, namespace, dispute / arbiter, commitment + anchor, bundle, safeguards — all now §12, referenced. PROFILE_MIGRATION_NOTE.md maps the specific PRP / VXP v0.2 sections that become references.

Multi-party note. PRP composes N-party reconciliation from bilateral sessions in a star (a coordinating center runs one session per site) or a small mesh. This is a profile composition of core bilateral sessions, not a core N-way construct — the core is bilateral by definition (§12.1), and any multi-party behavior is a profile arranging bilateral sessions. The optional pre-agreed arbiter of §12.5 (e.g. a trial coordinating center) and PRP's star hub are frequently the same entity, but the roles are distinct: arbiter (tie-break authority granted in the agreement) vs. hub (the party that holds the converged snapshot).

13.4 Profile conformance

A conforming AFP profile:

A "profile" that defines its own session establishment, its own identity binding, its own standalone namespace, its own dispute model, or its own bundle wire format is not a conforming AFP profile under v0.3.1 — it is duplicating the core, which is exactly what this consolidation removes.


14. Introduction Tier

This section is normative, added in v0.3.x. It specifies how two tenants with no prior relationship bootstrap a working bilateral relationship automatically — a credential + ZKC-proof handshake that auto-negotiates a scoped, collateral-priced provisional federation agreement (§5.5), grows the envelope with provable history, graduates to a standard agreement, and degrades or terminates safely — without weakening operator blindness (§3), OB-2 routing (§8), or the Freedom Safeguards (§9 / §12.8).

It closes a gap three earlier sections leave open. §5.1 assumes "the counterparty is already known … there is no discovery feed, no open call, no solver." §12.1 opens a session "only between two tenants that already hold a bilateral federation agreement." §10 lists instance-level reputation as open, "working assumption is no." The introduction tier is the specified path from strangers to a first agreement, expressed as an afp.introduction.v1 profile (§13) over the Bilateral Session Core (§12) plus the narrow §12.1 bootstrap exception. The progressive-trust model has four steps: (1) open discovery + credential handshake → a scoped small-limit provisional agreement; (2) first value interactions settle trustless on the ZKA-direct rail or under collateral-anchored small-limit channels; (3) limits grow with ZK-provable interaction history; (4) graduation to a standard §5.5 agreement and fast APL channels.

14.1 The willingness-to-federate directory

Discovery is opt-in. An instance that wishes to be approachable by strangers MAY publish an offer record to a KERI-anchored, witness-replicated directory, structurally the same object as the §8.7 mix-node directory:

interface AfpIntroductionOffer {           // afp:introduction-offer:v1
  kind: 'afp:introduction-offer:v1';
  tenant_aid: string;                       // who is open to being approached
  accepted_credential_classes: string[];    // e.g. ['zkc:proof:jurisdiction:v2',
                                            //       'zkc:proof:kyc:v2',
                                            //       'afp.telemetry.instance_attestation.v2']
  entertained_envelope: {                   // the CEILING it will negotiate within — not an offer of credit
    assets: string[];
    max_per_interaction: string;            // decimal string
    max_cumulative: string;
    collateral_required: boolean;
  };
  route_handle: string;                     // §8.8 private-route resolution handle (not a clear address)
  directory_epoch: number;                  // binds to the directory epoch (anti-replay), as §8.7
}

The directory is KERI-anchored and verified exactly as §8.7's mix-node directory: a tenant MUST reject an offer whose KERI anchor is absent, whose witness quorum is below the federation threshold (§4.5), or whose directory_epoch does not match the directory's current epoch exactly (a stale epoch is replay; a future epoch cannot yet be validated).

Reconciliation with §5.1 and §8 (normative). This directory does not reintroduce the v0.2.0 marketplace (§1.2) and does not weaken OB-2 (§3, §8):

The directory's hosting, epoch rotation, and gossip operation are a federation-operational concern outside AFP scope, not a tenant-internal one — the record is witness-replicated and verifiable without trusting any single party, structurally like the §8.7 mix-node directory, so no single tenant discharges it (a companion requirement); this section specifies the record shape, the OB reconciliation, and the conformance rules only.

14.2 The afp.introduction.v1 profile and the bootstrap exception

The introduction tier is an AFP profile (§13), registered with the descriptor:

{
  name: 'afp.introduction',
  version: '1.0',
  spec_ref: 'AFP v0.3.x §14',
  atom_kinds: [
    'afp:introduction-offer:v1',
    'afp:introduction-request:v1',
    'afp:credential-presentation:v1',
    'afp:provisional-agreement:v1',
    'afp:limit-adjustment:v1',
    'afp:downgrade:v1',
    'afp:termination:v1',
  ],
  policy_schema_ref: 'afp:introduction-policy:v1',
  status: 'experimental',                   // promoted to 'reference' once the lower layers are operational federation-wide (§14.9)
}

The bootstrap exception is a core change, not a profile-local one. §13.2 rule 4 forbids a profile from altering the core; a behavior that needs a core change is an AFP-spec change. The introduction tier needs exactly one: §12.1's agreement-scoped precondition must permit the introduction session to open under a provisional agreement. That amendment is stated in §12.1 and is bounded to this profile: the introduction session is the sole session type permitted to open by minting a provisional agreement in place of a pre-existing one, and every other session guarantee (mutual authentication §5.2 step 2, end-to-end encryption, OB-2 routing §8, context binding §5.2 step 4) is unchanged and still REQUIRED. (One other trust basis admits a session with no pre-existing agreement — open federation (§5.8, ADR-0004) — but it is not an exception to this profile carve-out: it opens with no agreement at all, via per-interaction ZKC attestation, rather than minting a provisional one, and is base behavior of §5, not a profile driving session opening. §12.1 enumerates all three trust bases.)

The instance-attestation credential named below is §6.3 afp.telemetry.instance_attestation.v2. Each side's introduction policy MUST name the root classes and root-authority identifiers it accepts; the proving side selects one accepted pair and proves the v2 predicate under that branch. The verifier performs the matching §6.3.1 root-specific obligations in addition to ordinary ZKC v0.4 verification, including out-of-circuit resolution of any delegated publisher signing AID to the named authority root. Neither side is required to accept any particular authority; a policy naming no authority declines v2 attestation rather than defaulting to one, and an authority an implementation ships as a default entry has this standing only once that party signs it into the policy (§14.4). A v1 proof does not satisfy a v2 request and MUST NOT be reinterpreted as carrying a generic root class.

14.3 The handshake

The handshake runs inside one §12 bilateral session (profile: 'afp.introduction') and drives it from zero trust to a signed provisional agreement:

PROPOSED ──▶ CREDENTIALING ──▶ NEGOTIATING ──▶ BONDING ──▶ ACTIVE
   │              │                  │             │          │
   └─▶ABORTED     └─▶DECLINED        └─▶DECLINED   └─▶ABORTED  ├─▶ DOWNGRADED ──▶ (ACTIVE)
       /ABORTED       /ABORTED                                 ├─▶ TERMINATED
                                                               └─▶ GRADUATED

The opener fixes the credential-validity interval used by any v2 instance-attestation APR:

interface AfpIntroductionRequest {          // afp:introduction-request:v1
  kind: 'afp:introduction-request:v1';
  initiator_tenant_aid: string;
  responder_tenant_aid: string;
  offered_credential_classes: string[];
  required_credential_classes: string[];
  proposed_envelope: object;                // the §14.4 envelope fields
  attestation_window: { starts_at: datetime; ends_at: datetime };
}

Every introduction request MUST carry attestation_window; starts_at MUST precede ends_at, and both endpoints MUST lie within the introduction session lifetime. If v2 is requested, its APR and proof MUST copy this exact window. Requiring the field universally keeps the v1 request shape deterministic even when a request does not ask for instance attestation.

Relationship to ZKM mandates. The provisional agreement produced here is a bilateral AFP policy envelope; a ZKM mandate is a unilateral, principal-issued authorization consumed by an agent. An interaction MAY be subject to both. The AFP per-interaction/cumulative limits and the ZKM cap, expiry, policy, and revocation checks are evaluated independently, and both must accept. Forming, downgrading, terminating, or graduating an AFP agreement does not issue, widen, revoke, or consume a ZKM mandate. Conversely, ZKM revocation does not mutate the AFP agreement or any underlying ZKA note.

Terminal states: DECLINED (credential or negotiation failure; no agreement is formed), ABORTED (unconditional exit, §12.8 Safeguard 5), and from ACTIVE: DOWNGRADED (the envelope contracts, then the session returns to ACTIVE under the new terms; §14.7), TERMINATED (§14.7), and GRADUATED (replaced by a standard §5.5 agreement; §14.6).

14.4 The provisional agreement and the introduction policy block

interface AfpProvisionalAgreement {         // afp:provisional-agreement:v1
  kind: 'afp:provisional-agreement:v1';
  tier: 'introduction';                     // distinguishes it from a standard §5.5 agreement

  // §5.5 agreement core: mutually signed by both tenant root (or delegated) AIDs,
  // using the §12.6.1 bilateral-signature shape.
  initiator_signature: AfpCommitSignature;  // §12.6.1: signed_by, kel_event_dig, signature, signed_at
  responder_signature: AfpCommitSignature;

  policy: AfpIntroductionPolicy;            // afp:introduction-policy:v1

  reputation_state: {                       // the running, signed basis for §14.6 growth
    settled_interactions: number;
    cumulative_settled_value: string;
    slash_events: number;
    last_commitment_ref?: string;           // chains into the §12.6.1 commitment history
  };
}

interface AfpIntroductionPolicy {           // afp:introduction-policy:v1
  permitted_entities: string[];
  permitted_assets: string[];
  max_per_interaction: string;
  max_cumulative_outstanding: string;
  bond_ref?: string;                        // escrow reference; absent only for a trustless-ZKA-direct-only envelope
  accepted_credentials: { class: string; valid_until: string }[];
  accepted_instance_attestation_roots: {
    root_class: 'root:build-provenance' | 'root:tee-attestation' | 'root:publisher-manifest';
    root_authority_identifier: string;
  }[];                                      // signed v2 verifier policy; empty only when v2 is not required
  rotation_policy: string;                  // KEL rotation expectations (§4.6)
  growth_curve: object;                     // negotiated step / cap / decay (§14.6) — not protocol-fixed
  agreement_expires_at: string;
}

A provisional agreement is a specialization of the §5.5 federation agreement, distinguished by tier: 'introduction' and a reputation state — and, for a bonded envelope, a bond reference (bond_ref is absent for a trustless-ZKA-direct-only envelope, §14.5). When accepted_credentials contains afp.telemetry.instance_attestation.v2, accepted_instance_attestation_roots MUST contain at least one pair, and the verifier MUST accept only a proof whose public root_class and root_authority_identifier exactly match one signed pair. If v2 is not required the array MUST be empty; it MUST NOT silently authorize an attestation predicate. A pair that a party's implementation ships as a default entry has no different status here: it is signed into the agreement by that party or it does not apply. An empty array means the predicate is not required — never that a shipped default applies. Like every §5.5 agreement the complete policy is private to the two tenants, bilaterally signed, not published, and not discoverable. It is referenced by the afp.work.request (§5.3) / VXP intent messages of the value interactions it scopes.

14.5 Settlement safety floor

Every value interaction under an introduction-tier agreement MUST be either:

A conforming implementation MUST NOT allow uncollateralized credit at the introduction tier. Credentials gate which counterparties and assets are eligible; the bond is what bounds loss. (Instance identity is not sybil resistance; collateral prices the risk.)

Enforcement location. The floor is enforced at the agreement / channel-policy layer: the provisional agreement's envelope populates the channel policy block (the APL channel policy block for bonded and graduated channels), and the layer that authorizes or opens a value channel enforces "ZKA-direct, or bonded ≤ collateral" before any interaction. This section defines the conformance property; the channel-policy layer implements it. A value profile (e.g. VXP) is not special-cased to carry introduction-tier validation — cross-cutting policy lives at the core/agreement layer (§13.2 rule 4).

14.6 Progressive limit growth and graduation

Mechanism (normative). A party MAY present an afp:reputation-proof — a zero-knowledge proof of N settled interactions and cumulative settled value with zero slash/default events, bound to this agreement and counterparty and non-replayable (bound to the agreement id and a monotonic counter, mirroring the §12.6.1 chaining). On verification, the presenter proposes an afp:limit-adjustment:v1 raising the envelope; both parties sign it with the §12.6.1 machinery. A rejected, stale, or slash-bearing proof yields no adjustment — never a freeze.

Curve (policy, not protocol). The growth curve — step size, caps, decay on inactivity — is a negotiated parameter in the afp:introduction-policy:v1 block. AFP fixes the predicate and the adjustment handshake; the numbers are the bilateral choice. This mirrors the house rule that the mechanism is normative and the parameters are negotiated (cf. §8.2).

Graduation. When the parties choose, they replace the provisional agreement with a standard §5.5 agreement — no introduction tier, no mandatory bond, eligible for fast APL channels. Graduation is itself a bilateral signed act; the §12.6.1 chained-commitment history carries forward as the relationship's provable basis. The reputation proof and the on-chain escrow are lower-layer constructs (§14.9); this section owns the predicate's interface and the adjustment/graduation handshake, not the circuit.

14.7 Downgrade and termination

Both are safeguard-compatible: nothing here freezes, claws back, or redirects tenant state (Safeguard 3), and exit is unconditional (Safeguard 5).

Downgrade. The go-forward envelope contracts — lower limits, higher collateral, narrower assets. Triggers include a lapsed credential validity (the policy-block expiry), a late or missed settlement, a signed dispute-divergence record (§12.5), partial collateral consumption, or reputation decay. A downgrade is realized either as a re-negotiation both parties sign (an afp:downgrade:v1 followed by a new afp:limit-adjustment:v1) or as a unilateral protective contraction: a party MAY always decline to extend further credit or offer a smaller envelope (declining an interaction is always permitted, §5.4 / Safeguard 5). A party can never unilaterally compel the counterparty to act.

Termination. Recorded as an afp:termination:v1; either party MAY terminate unconditionally and with no protocol penalty (Safeguard 5). Residual obligations settle against the pre-posted escrow only — the §12.5 arbiter-co-signed release, bounded by what was escrowed, never against base-layer custody (Safeguard 5). Any unsettled dispute leaves a signed divergence record (§12.5).

Default consequence. A default during the relationship additionally yields a portable negative-history record — a signed statement of the default, usable as anti-reputation input to the counterparty's future introductions. It is the only persistent consequence beyond loss of the bond, and it is itself only a signed record: not a freeze, not a clawback (Safeguard 3).

14.8 Composition and Freedom Safeguards

14.9 Open seams and honest disclosure

The introduction tier is the AFP-owned protocol contract; several lower layers it composes are owned and tracked elsewhere. AFP owns the handshake, the provisional-agreement object, the introduction policy block, and the conformance properties of this section. It references — and does not redefine — the following:

Construct Owner / status
Trustless ZKA-direct settlement rail ZKA (transfer + unconditional withdrawal); operational.
zka:bundle:v1 carrying the credential presentation ZKA §5.4 (credential-only bundles are supported; no payment leg required).
ZK reputation-proof circuit (§14.6) ZKA/Reputation — companion requirement; not yet operational.
On-chain bilateral escrow with mutually-chosen arbiter (§14.5, §14.7) Companion requirement (ADAMAS.Network / ZKA escrow contract); not yet operational.
Instance-attestation predicate (§14.2) §6.3 afp.telemetry.instance_attestation.v2; AFP fixes the public statement and root-specific verifier obligations. The separately versioned ZKC machine build/runtime credential schema and issuer-adapter registry entry are companion work; raw provenance bundles and TEE quotes remain provider-side. An authority's key ceremony, published trust material, and the out-of-band AID↔︎legal-entity binding (§6.3.1) are likewise companion work owned by that authority rather than by AFP; for the ADAMAS product line these are Pyramidal Inc.'s and are not yet operational.

Honest disclosure. A deployment whose selected §6.3.1 v2 root path, escrow, or ZK-reputation lower layer is not yet operational MUST disclose the introduction tier as spec-available but not fully operational rather than claim it, exactly as §3.5 and §8.9 require for OB-1/OB-2. The profile descriptor's status: experimental (§14.2) reflects this until the lower layers are operational federation-wide. Claiming a fully operational introduction tier without a realized v2 credential/circuit/verifier path, the reputation circuit, and the bonded-escrow rail in place is a conformance violation, independent of the correctness of any one available component.


15. USD Obligation / WETH Settlement Profile (pay)

This section is normative, added in v0.3.6-draft. It defines the AFP pay v1.0 profile: two principals agree on a commercial obligation denominated in USD minor units and an exact settlement amount denominated in WETH wei, then bind that bilateral quote to a private ZKA/Pay transfer. The profile is additive: afp:session:v1, afp:envelope:v1, and zka:bundle:v1 are unchanged; AFP-KDC v1.0.2 preserves the v1.0.1 domain-tag clarification while pinning the already deployed Poseidon2 parameters, and therefore leaves every payment derivation and cryptographic artifact unchanged.

15.1 Ownership boundaries and registration

AFP owns the principal-readable USD obligation; the exact WETH-wei quote; its source, time, expiry, slippage bound, nonce, and bilateral authentication; its binding to a live AFP session and a specific immutable ZKA pool; and the observational settlement receipt. ZKA owns notes, nullifiers, roots, proofs, conservation, pool state, pool_context, the payment proof's interaction_context, unconditional spend/withdrawal semantics, and each pool's asset-risk profile. APL owns selection of the configured :zka_direct route, construction and submission of the ZKA SDK request, finality observation, and route evidence returned to AFP.

AFP does not verify a ZKA proof, query an exchange-rate oracle, decide whether a price is fair, custody value, define a multi-asset pool, or reproduce ZKA calldata or circuit schemas. ZKA does not interpret USD, choose a pricing source, or enforce quote fairness. APL does not alter a signed quote.

The published descriptor is:

{
  name: 'pay',
  version: '1.0',
  spec_ref: 'AFP v0.3.6-draft §15',
  atom_kinds: [
    'afp:payment-quote:v1',
    'afp:payment-receipt:v1',
  ],
  policy_schema_ref: 'afp:payment-policy:v1',
  status: 'experimental'
}

experimental reflects the absence of the independent-audit evidence required to claim the launch settlement dependencies as production-ready. It does not weaken this wire contract or any conformance requirement below.

The federation agreement names the profile through this policy block:

interface AfpPaymentPolicyV1 {
  kind: 'afp:payment-policy:v1';
  deployment: {
    chain_id: 8453;
    asset: '0x4200000000000000000000000000000000000006';
    pool: string;
    pool_context: string;
    asset_risk_profile: 'zka:asset-risk:s3-hard:v1';
  };
  accepted_rate_sources: string[];
  max_quote_lifetime_seconds: number;
  max_slippage_bps: number;
  required_finality: 'safe' | 'finalized';
}

accepted_rate_sources MUST be non-empty and contain unique, non-empty names. max_quote_lifetime_seconds MUST be a positive integer; max_slippage_bps MUST be an integer in 0..10000. The deployment fields use the canonical encodings of §15.2 and identify one immutable pool. An accepted quote's deployment and rate source MUST match this policy exactly, its lifetime MUST NOT exceed the policy maximum, and its slippage bound MUST NOT exceed the policy maximum. Receipt finality MUST reach required_finality before the application represents the interaction as settled. The Bilateral Session Core's profile_policy_hash MUST equal qb64("D", SHA-256(canonical_json(AfpPaymentPolicyV1))), and a pay quote MUST be accepted only inside a live session whose profile is exactly pay and whose policy hash matches the policy used for validation.

15.2 Canonical payment quote

interface AfpPaymentQuoteV1 {
  kind: 'afp:payment-quote:v1';
  quote_id: string;
  session_ref: string;
  session_context: string;

  obligation: {
    currency: 'USD';
    amount_minor: string;
  };

  settlement: {
    chain_id: 8453;
    asset: '0x4200000000000000000000000000000000000006';
    pool: string;
    amount_wei: string;
    pool_context: string;
    asset_risk_profile: 'zka:asset-risk:s3-hard:v1';
  };

  rate: {
    source: string;
    usd_minor: string;                  // settlement-rate numerator
    weth_wei: string;                   // settlement-rate denominator
    reference_usd_minor: string;        // signed source-rate numerator
    reference_weth_wei: string;         // signed source-rate denominator
  };

  quoted_at: string;
  expires_at: string;
  max_slippage_bps: number;
  quote_nonce: string;

  initiator_signature?: AfpCommitSignature;
  responder_signature?: AfpCommitSignature;
}

obligation.amount_minor, settlement.amount_wei, and all four rate integers MUST be positive canonical unsigned base-10 integer strings: digits only, with no sign, whitespace, decimal point, exponent, or leading zero. USD is represented in ISO 4217 minor units (cents for USD) and WETH in wei. A wire implementation MUST NOT use a binary floating-point value for any monetary or rate component. The Base launch profile additionally requires settlement.amount_wei <= 2^120 - 1, matching ZKA's frozen MAX_NOTE_VALUE_WEI; quote construction and validation MUST reject a larger amount before signing or routing it.

The rate is the exact positive rational usd_minor / weth_wei. Quote construction and validation MUST use arbitrary-precision integer arithmetic without overflow or truncation and the deterministic ceiling rule:

settlement.amount_wei = ceil(
  obligation.amount_minor * rate.weth_wei / rate.usd_minor
)

Equivalently, implementations MAY compute (amount_minor * weth_wei + usd_minor - 1) // usd_minor; any different result MUST be rejected, and a result above the frozen ZKA note-value bound MUST be rejected. max_slippage_bps MUST be an integer in 0..10000. The reference rational is reference_usd_minor / reference_weth_wei; the settlement rational is usd_minor / weth_wei. Validation MUST also enforce, with arbitrary-precision integer arithmetic:

abs(usd_minor * reference_weth_wei
    - reference_usd_minor * weth_wei) * 10000
  <= max_slippage_bps * weth_wei * reference_usd_minor

This is the absolute settlement-price deviation divided by the signed reference price. It constrains pre-signing quote construction, never permits execution above the signed settlement.amount_wei, and no post-signing repricing is allowed. AFP fixes no global rate source; rate.source names the bilateral source or policy whose signed reference observation the parties accepted.

Every address in canonical quote content MUST be normalized before hashing to lowercase 0x followed by exactly 40 hexadecimal digits. Mixed-case, checksum-case, short, overlong, or otherwise non-canonical encodings MUST be rejected, not silently normalized during verification. The Base launch profile fixes chain_id to 8453 and settlement.asset to the lowercase Base WETH9 address shown above. settlement.pool, settlement.pool_context, and settlement.asset_risk_profile MUST exactly match the configured immutable deployment. pool_context MUST use the ZKA SDK's canonical nonzero BN254 field encoding: lowercase 0x followed by exactly 64 hexadecimal digits whose big-endian integer is in 1..r-1, for the modulus r in §15.4. session_context MUST be a qb64-encoded 32-byte digest (§15.4); any other form is out of profile and MUST be rejected.

quoted_at and expires_at MUST have exactly the whole-second UTC form YYYY-MM-DDTHH:MM:SSZ; fractional seconds and numeric UTC offsets are invalid. Both timestamps MUST represent valid instants, and expires_at MUST be strictly later than quoted_at. The quote is expired when the verifier's current time is greater than or equal to expires_at.

quote_nonce MUST be lowercase 0x followed by exactly 64 hexadecimal digits (32 bytes). A signing party MUST NOT sign two different quote contents with the same nonce. quote_id is a caller-supplied, non-empty stable identifier. An acceptor MUST atomically and durably reserve the exact (quote_id, quote_hash, quote_nonce, acceptance_record_hash) acceptance tuple when accepting a quote. This durable acceptance ledger is the authority consumed by route and receipt tracking code. A repeated accepted quote is a duplicate and MUST NOT execute again; the same ID with different canonical content or the same nonce with a different quote is a terminal validation failure. A rejected quote MUST NOT consume either replay key. A restart or process crash MUST NOT make an accepted replay key available again.

15.3 Quote hash and bilateral authentication

quote_content is the quote object with initiator_signature and responder_signature absent (not null). AFP uses the canonical JSON serialization already used by the Bilateral Session Core: UTF-8, lexicographically sorted object keys, and compact separators. Hashes use SHA-256 and AFP's D qb64 digest convention:

quote_hash = qb64("D", SHA-256(canonical_json(quote_content)))

Both principals MUST sign this exact quote_hash using the AfpCommitSignature shape of §12.6.1. Both signatures are required. Each signature MUST be genuine under the signer's KEL-current Ed25519 key identified by kel_event_dig; a stale, unknown, malformed, or mismatched key state MUST be rejected. AfpCommitSignature.signed_at is untrusted observational metadata: it is not covered by the Ed25519 payload, MUST NOT be used for freshness or authority, and changing it neither invalidates nor strengthens quote authentication. Freshness derives only from the signed quoted_at and expires_at fields compared with the verifier's current time. The initiator and responder AIDs MUST be the two principals of the live session, and session_ref and session_context MUST exactly match that session. The session context MUST recompute from the signed §12.2.1 session fields, and both session AfpPartyRef signatures MUST verify against KEL-current delegated identity and the declared witness thresholds before quote acceptance. An operator, arbiter, application administrator, or one party alone cannot substitute for either signature.

Signatures authenticate the exact settlement amount and all quote controls, including the rate source, pool, pool context, risk profile, timestamps, slippage bound, ID, and nonce. Any mutation after either signature was made invalidates authentication.

After both signatures verify, the acceptor MUST compute acceptance_record_hash = qb64("D", SHA-256(canonical_json(full_quote))), where full_quote includes both complete signature objects. This is distinct from the content-only quote_hash. The exact signed-record hash is stored in the durable acceptance tuple, retained by AcceptedPaymentQuote, and recomputed from its sealed canonical wire on every use. Removing, replacing, or changing either signature therefore cannot reuse a content-only acceptance reservation.

15.4 Interaction-context derivation

The ZKA payment for an accepted quote MUST bind the canonical nonzero BN254 field value derived below. The derivation is the ZKA/Pay Interaction-Context Binding Profile v1 fold — restated here normatively and in full, so this specification stays auditable standalone, and pinned to its frozen upstream definition: ZKA docs/sdd/sdd_pay_interaction_binding.md §2 at ZKA.dev db39afd, KAT fixture SHA-256 e0d7fd0442b3cfd14c21c949ae13bcf9fba4250b2067300f729aaad2d38a2a8d. On any divergence the pinned v1 governs. A future upstream v2 does not bind AFP; adopting one requires a new revision of this specification and a superseding decision record (ADR-0006).

Both fold inputs are 32-byte digests mapped into the scalar field by plain big-endian reduction:

r = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001

afp_quote_hash_field  = big_endian_uint256(quote_digest)   mod r
session_context_field = big_endian_uint256(session_digest) mod r

Here r is the BN254 scalar-field modulus. quote_digest is the 32 SHA-256 digest bytes the §15.3 quote_hash encodes — its qb64 payload: the digest itself, never its string encoding. session_digest is the digest payload of the §12.3 session context: for this profile the session context MUST be a qb64-encoded 32-byte digest, whatever its derivation code, and a session context that is not one is out of profile and MUST be rejected fail-closed, never mapped by a fallback. This mapping is a reading of the same §5.2/§12.3 session context into field form — not a parallel context, and the §12 envelope/bundle context equality is unaffected. The wire stays unreduced: the quote digest carried to ZKA or an APL adapter is the raw 32 bytes, and any party recomputing the fold MUST apply the reduction above first — roughly four of five uniformly distributed digests exceed r, so skipping it is the common-case error. Truncation is non-conforming. A reduction yielding zero MUST be rejected fail-closed (probability 6/2²⁵⁶ ≈ 2⁻²⁵³; never substituted). Both inputs exist before either quote signature, so the producer MUST perform the full derivation at quote-construction time, before signature exchange and nonce reservation — a zero reduction there costs only a reissue with a fresh quote_nonce; at dispatch it is a terminal §15.5 fail-closed condition.

The derivation is the field-count-prefixed Poseidon2 fold over the §A.3 instance's two-field hash P(a, b):

INTERACTION_CONTEXT_TAG =
  0x00007a6b612f7061792f696e746572616374696f6e2d636f6e746578742f7631
  // the UTF-8 bytes "zka/pay/interaction-context/v1",
  // right-aligned in a 32-byte word — a fixed byte tag (§A.4 carve-out)

state = P(INTERACTION_CONTEXT_TAG, Field(2))
state = P(state, afp_quote_hash_field)
state = P(state, session_context_field)
interaction_context = "0x" || lowercase_hex_64(state)

Field(2) is the field element two — the count of folded input fields, absorbed as an ordinary second sponge input to the first call, not an arity annotation. The input order is normative — quote hash first; order sensitivity is proven by the upstream asymmetry-pair vector. A zero fold output MUST be rejected fail-closed, matching the ZKA circuit's interaction_context != 0 assertion. The tag is a raw fixed byte tag consumed directly as a field element under the §A.4 carve-out for ZKA pool-adjacent fold tags; it is not a §A.3 deriveFieldTag domain, and it is currently registered in no family registry — its defining authorities are the pinned SDD §2 and the ZKA SDK's INTERACTION_CONTEXT_TAG_HEX constant. Registration in ZKA's operational-domains registry is tracked as req_zka_register_zkapayinteractioncontextv1__923e6c97, with AFP-side vendoring and drift-gate coverage as req_afp_vendor_zka_operationaldomains_regist_c22a7062.

The application or APL adapter MUST provide the 32-byte lowercase hex field value, not a digest or a qb64 encoding, as the ZKA payment request/proof's interaction_context; the pool and proof MUST enforce the corresponding binding. The envelope retains the original session_context. Implementations MUST NOT replace, concatenate, reinterpret, or overload that session field, and MUST NOT use a raw quote hash or raw session context in place of the derived field.

Conformance vectors. An implementation MUST reproduce, byte-identically, all five vectors of the pinned upstream KAT fixture — small-ints, one-one, max-canonical, keccak-derived-quote, asymmetry-pair (published verbatim as the normative artifact docs/pay_interaction_context_kat_v1.json) — and the AFP-native end-to-end vector below, which pins the qb64→field seam the upstream vectors cannot exercise. Both of its digests exceed r, so the reduction leg is vector-pinned rather than prose-pinned. For the §15.2 canonical quote content

{"expires_at":"2026-07-11T12:05:00Z","kind":"afp:payment-quote:v1","max_slippage_bps":100,"obligation":{"amount_minor":"2500","currency":"USD"},"quote_id":"quote-2026-08-16-0002","quote_nonce":"0xabababababababababababababababababababababababababababababababab","quoted_at":"2026-07-11T12:00:00Z","rate":{"reference_usd_minor":"250000","reference_weth_wei":"1000000000000000000","source":"bilateral:base-weth-usd:v1","usd_minor":"250000","weth_wei":"1000000000000000000"},"session_context":"X_eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","session_ref":"session-0001","settlement":{"amount_wei":"10000000000000000","asset":"0x4200000000000000000000000000000000000006","asset_risk_profile":"zka:asset-risk:s3-hard:v1","chain_id":8453,"pool":"0x1234567890abcdef1234567890abcdef12345678","pool_context":"0x0101010101010101010101010101010101010101010101010101010101010101"}}

the derived values are:

quote_hash            = D_f5637ad946fbcc4ca76a7bb137a1796c1eb60c24590d773231d36f4759e8462d
afp_quote_hash_field  = 0x036df29ae103ab7c0dd91f20b01abf9a55b282b9f86e445bde69a363a9e84628
session_context_field = 0x2d5db5236a286e480dadd814e8e98d7a4e1f4dcd08092ca9df67189f2eeeeeea
interaction_context   = 0x26232f5bff7eb274b601df155b7b47abf504fc5778bdc2b35b961d14e5140d6d

Implementations MUST reproduce this vector exactly.

15.5 Acceptance and route execution

Before dispatching a route, an implementation MUST fail closed unless all of the following hold:

  1. the quote has the exact kind and canonical field encodings of §15.2;
  2. the amount satisfies the deterministic ceiling-rate calculation;
  3. the caller presents an immutable live-session capability minted by the AFP session core only after its owning instance completed the normal propose/accept-or-confirm/activate lifecycle; the detached session snapshot, identifiers, both AfpPartyRef signatures, and both quote signatures all verify KEL-current;
  4. the quote is unexpired and its ID and nonce pass atomic replay checks;
  5. chain, WETH asset, immutable pool address, pool_context, and zka:asset-risk:s3-hard:v1 match configured Base launch policy; and
  6. the derived interaction_context is supplied unchanged to the ZKA request, and no §15.4 field reduction of either fold input yielded zero — a terminal fail-closed condition at dispatch, reachable only if the mandatory quote-construction-time derivation (§15.4) was skipped.

The live-session capability is owner-bound and non-serializable. Every access MUST revalidate the exact current FSM, normal activation provenance, and expiry against the owning session manager's clock. Closing, aborting, expiring, or replacing that FSM invalidates every previously minted handle. A restarted manager MUST rehydrate the durable session/FSM state, re-establish its lifecycle provenance, and mint a new handle; it MUST NOT persist or restore the handle as standalone authority.

Unknown or unsupported chain, asset, pool, context, risk profile, signature, or finality state MUST be rejected. APL MUST submit exactly the signed settlement.amount_wei; it MUST NOT reprice, substitute an asset or pool, or route through a mutable/multi-asset settlement contract. A failed proof or submission leaves the interaction unsettled.

Successful acceptance MUST produce an immutable, non-wire AcceptedPaymentQuote capability minted only by the acceptance boundary after all checks above succeed. Its constructor MUST NOT be public, and it MUST NOT accept a caller-supplied quote hash. The minting path MUST canonically reconstruct the detached quote and recompute and verify quote_hash before retaining the quote ID, nonce, interaction context, and canonical quote wire. Receipt or route code MUST require this capability and a store-issued limited acceptance/route authority. It MUST fail closed unless the ledger contains the exact capability (quote_id, quote_hash, quote_nonce, acceptance_record_hash) tuple; verifier absence, false, or error is rejection. The tracker MUST recheck this reservation at initialization. A copied capability for the same sealed wire and exact durable tuple is equivalent; an unreserved capability is not accepted authority.

The deployment composition root MUST construct one PaymentReceiptVerificationContext that binds a store-issued limited acceptance/route authority and the deployment's finality-evidence verifier. The writer/replay store itself, an arbitrary structurally similar object, and an untrusted request-selected verifier MUST be rejected. Route evidence creation, route decoding, and tracker creation MUST use only this preconfigured context; their public request-facing calls MUST NOT accept a verifier parameter. A durable SQLite authority uses a separate connection exposing only exact-tuple verification and atomic route claim, never quote reservation; the in-memory test authority exposes the same limited operations under the store lock. This boundary prevents request data from substituting a fresh attacker ledger while leaving deployment owners free to choose their trusted composition root.

Before returning route evidence, the context MUST atomically claim one transaction_id against the exact accepted four-field tuple. The first claim wins, the same transaction is idempotent, and every different transaction for that quote MUST fail. Route decoding and tracker initialization MUST verify the exact claimed transaction. The supported reference API registers store-issued authority instances through a closure-held issued-instance set and rejects direct construction, nominal object construction, and structurally similar fakes. This is API misuse hardening inside a trusted implementation, not a sandbox boundary against arbitrary code already executing in the same interpreter.

The reference capability is process-local and MUST carry an HMAC-SHA-256 seal over its canonical accepted wire under an unpredictable key held only by the acceptance closure. Every capability use MUST compare the seal in constant time, reparse the sealed wire, recompute quote_hash and interaction_context, and match the retained quote ID, nonce, hash, and context before returning authority. This seal provides process-local tamper detection only; it is not authorization. The durable acceptance-ledger reservation above is the authorization source for untrusted wire/request inputs when accessed through the deployment-bound context.

In-process trust boundary. AFP does not claim to sandbox arbitrary code that already executes inside the trusted composition-root process. Such code can introspect Python closures, monkey-patch implementations, or access storage credentials and therefore can subvert any in-process reference guard. A deployment MUST keep request data, tenant code, plugins, and other untrusted extensions outside that authority boundary. If same-process code itself is in the adversary model, the acceptance/route authority MUST move behind a separate process, KMS/HSM-backed signer, or equivalently isolated service boundary; the in-process reference objects are not sufficient for that threat model. The capability MUST NOT serialize across a process boundary. Restart does not clear the durable replay reservation or permit the same quote to execute again: normal acceptance requires a new quote, while any implementation-specific recovery path must verify a durable accepted record without weakening replay. Long-running receipt observation should persist its route/receipt state rather than persist this capability.

15.6 Payment receipt and finality

interface AfpPaymentReceiptV1 {
  kind: 'afp:payment-receipt:v1';
  quote_ref: string;
  quote_hash: string;
  interaction_context: string;
  chain_id: number;
  pool: string;
  asset: string;
  amount_wei: string;
  transaction_id: string;
  observed_at: string;
  finality: 'pending' | 'safe' | 'finalized';
  finality_evidence: object;
}

A receipt is observational evidence, not settlement authority. quote_ref MUST equal the accepted quote's quote_id; its quote hash, interaction context, chain, pool, asset, and amount MUST exactly match the accepted quote and the immutable route evidence. transaction_id MUST be non-empty and MUST identify the submitted transaction. observed_at uses the exact whole-second UTC format of §15.2. finality_evidence MUST be a non-empty object appropriate to the deployment's documented finality policy; unknown or empty evidence is insufficient.

Receipt tracking MUST be configured with a fail-closed, deployment-specific finality-evidence verifier. For every observation, that verifier MUST validate the evidence against the configured chain and immutable deployment, the exact submitted transaction, and the claimed pending, safe, or finalized level. Verifier absence, failure, an unknown evidence shape, or evidence for another transaction, deployment, or level leaves the interaction unsettled. Retained evidence MUST be recursively immutable; serialization returns a detached JSON copy and cannot expose mutable nested aliases.

For one quote and transaction, receipt state MAY advance monotonically pendingsafefinalized. It MUST NOT regress, skip to an unknown state, change the transaction, or rewrite any quote/route binding. pending MUST NOT be represented as final settlement. A deployment MAY require safe or finalized for application completion; until that threshold is supported by matching evidence, the AFP interaction remains unsettled. Concurrent observations for one tracker MUST serialize the finality comparison and state update atomically; a lower or equal-rank observation cannot overwrite a higher state, regardless of verifier completion order.

15.7 Asset-risk and safeguard requirements

The flagship profile is WETH in one separately deployed immutable, single-asset ZKA pool declaring zka:asset-risk:s3-hard:v1. Settlement contracts MUST be single-asset and immutable. Every additional asset requires a separate pool address, pool_context, note/nullifier/root state, escrow, certification record, and explicit asset-risk profile. An application MUST NOT treat a shared or dynamically mutable asset registry as equivalent. A native stable unit is an independent future protocol and is not a dependency of AFP pay, ZKA/Pay, or the Base WETH launch.

The profile inherits all six Freedom Safeguards (§12.8). In particular, expiry, dispute, rejection, replay failure, insufficient finality, or route failure affects only acceptance of this interaction. It MUST NOT freeze, revoke, redirect, delay, seize, or otherwise gate a holder's existing ZKA notes or unconditional withdrawal (Safeguards 3 and 5). AFP applications and APL adapters MUST NOT add an administrator recovery, override, clawback, or forced-reroute path. Any compliance proof requested alongside payment remains atomic and requester-authenticated with a holder-retained receipt (Safeguards 2 and 6).

15.8 Conformance

A conforming pay v1.0 implementation:

An implementation that obtains its own price, silently canonicalizes invalid wire content, reprices after signature, permits one-party authorization, treats pending as final, reuses a mutable/multi-asset settlement contract, or adds an administrative note/withdrawal gate is not conforming.


Appendix A: Key Derivation Core (KDC)

Module: Key Derivation Core KDC Version: 1.0.2 Status: Stable Owning specification: AFP (this document) Referenced normatively by: ZKA v0.9.0-draft (§2.1–2.2), ZKC v0.4.1-draft (§1.3, §2.1, §5.9), ZKM v0.1.0-draft (§2), AFP v0.3.9 (§2.2, §4.3, §5.9)

This appendix specifies the Key Derivation Core (KDC) — the master-seed derivation, hash-and-domain-separation scheme, domain-tag registry, and per-entity sub-derivation shared by ZKA, ZKC, ZKM, and AFP. It is a self-contained module: it is published as part of the AFP specification for packaging reasons (AFP is the identity protocol of the family and carries the most complete derivation model), but it depends on no other section of this specification and on no construct specific to AFP, ZKA, ZKC, or ZKM. Sibling protocols reference this appendix without thereby depending on the remainder of AFP.

KDC carries its own version line, independent of the AFP specification version. A dependent specification cites AFP-KDC v1.0.2, not an AFP version. v1.0.1 resolved the domain-tag prose in favor of the already-deployed Blake2b512 reduction. v1.0.2 completely parameterizes the already-deployed Poseidon2 instance and makes its cross-repository vectors normative. Neither patch changes a derived value.

A.1 Scope

In scope (normative for KDC):

Out of scope (owned by the referencing specifications):

KDC defines derivation. It does not know what the derived keys are used for.

A.2 Master Seed

A master seed is a 256-bit (32-byte) secret. It is the root of a key hierarchy and MUST be generated from a cryptographically secure random source and stored securely.

A master seed may be:

The two cases are interchangeable as far as §A.6 is concerned: §A.6 takes a 256-bit seed and does not distinguish how that seed was obtained. This is what makes a sub-seed M' a drop-in master seed for the key-derivation functions.

A.3 Hash Function and Domain Separation

KDC uses Poseidon2 over the BN254 scalar field for all key-derivation hashing. Poseidon2 is the hash provided by Noir's standard library (std::hash::poseidon2) and is optimized for UltraHonk constraints, so derivations are efficiently provable inside circuits — required because the ZKA identity-binding circuit (ZKA §5.2) re-derives keys from a seed in-circuit.

The BN254 scalar field modulus is

r = 21888242871839275222246405745257275088548364400416034343698204186575808495617

All KDC hashes use domain separation via a leading domain-tag field element:

H(domain, x...) = Poseidon2( deriveFieldTag(domain), x... )

deriveFieldTag is the one field-domain algorithm for both KDC derivation tags and protocol-owned operational tags:

deriveFieldTag(tag) =
  LEInteger(Blake2b-512(UTF8("ZKA-Domain-") || UTF8(tag))) mod r

The 64-byte digest is interpreted as a little-endian non-negative integer before reduction. Implementations MUST NOT substitute direct UTF-8-to-integer encoding, big-endian digest interpretation, a different Blake2 variant, or an unprefixed hash. The historical ZKA-Domain- prefix is frozen compatibility material; it does not assign ownership of an operational tag to ZKA.

encodeFixedTag(tag) is a separate non-field operation. Its input domain is a non-empty string whose UTF-8 encoding is 1–32 bytes and whose first encoded byte is non-zero; implementations MUST reject a leading NUL byte so left-zero-padding remains injective. After validation, left-zero-pad the encoding to exactly 32 bytes. It is used for the §A.5 HKDF salt. A field tag MUST NOT be substituted as HKDF salt. A fixed byte tag MUST NOT be interpreted as a circuit field separator, with exactly one exception: the fixed-byte fold-tag class carved out in §A.4, whose externally-owned tags are consumed directly as field-element domain separators by their owning specification.

A 256-bit seed supplied as a hash input is interpreted as a big-endian integer and reduced modulo r before use as a field element.

Behavioral-identity requirement. §A.6 is behaviorally identical to the deployed ZKA/ZKC derivation and all v1.0.0 vectors. v1.0.1 removed the domain-tag prose/vector ambiguity; v1.0.2 pins the exact permutation and sponge already used by every consumer. Existing field constants, seeds→keys, proofs, verification keys, hashes, and HKDF vectors MUST remain byte-for-byte unchanged.

A.3.1 Poseidon2 Instance

KDC uses exactly one Poseidon2 instance. Any other parameterization is non-conformant even if it is also called “Poseidon2 over BN254.”

Parameter Normative value
Field BN254 scalar field Fr with modulus r above
State width t = 4
Rate 3, lanes 0–2
Capacity 1, lane 3
S-box x^5
Full rounds 8, split 4 before and 4 after the partial rounds
Partial rounds 56
External matrix [[5,7,1,3],[4,6,1,1],[1,3,5,7],[1,1,4,6]]
Internal matrix all-ones plus the pinned diagonal-minus-one values

Treat the state as a column vector and reduce every operation modulo r. Apply the external matrix once before round 0. In rounds 0–3, add all four round constants, apply x^5 to all lanes, then apply the external matrix. In rounds 4–59, add the lane-0 round constant, apply x^5 only to lane 0, then apply the internal matrix. In rounds 60–63, repeat the full-round operation.

docs/kdc_v1_0_2_poseidon2_parameters.json is the normative, self-contained parameter artifact. It fixes all 64 four-lane constant rows, the four internal diagonal-minus-one values, both matrices, and operation order. Its canonical constant encoding concatenates every constant row in round/lane order, followed by the four internal diagonal-minus-one values, with each field encoded as canonical 32-byte big-endian. The encoding contains 260 fields and 8,320 bytes and MUST have SHA-256:

8cb18652f4afeac5b6819b156c13fbbc327deacba568b9d8d961ab134123214c

An implementation that imports, embeds, or loads an explicit parameter table MUST verify this digest before use. An opaque backend intrinsic MUST be immutably identified and reproduce every §A.3.3 KAT.

A.3.2 Sponge Mode

Let P(x[0], …, x[n-1]) be the raw length-bound sponge. Its input is a sequence of canonical Fr elements with 0 <= n <= 2^32 - 1. Initialize [0, 0, 0, n * 2^64]; the length IV is in capacity lane 3. For each complete three-element chunk, add it to rate lanes 0–2 and permute. Add any one- or two-element remainder to the corresponding leading rate lanes. Apply one final permutation if and only if n = 0 or n mod 3 != 0. Return lane 0. There is no delimiter or padding field, and a positive multiple of three receives no additional trailing permutation.

Absorption is additive. P accepts canonical field elements rather than arbitrary integers or bytes. KDC semantic domain separation is explicit and is not supplied merely by arity:

H(domain, x...) = P(deriveFieldTag(domain), x...)

A.3.3 Conformance Known-Answer Tests

For raw inputs consisting of the field elements 1, 2, 3, ... in order, a conforming implementation MUST reproduce:

n Input P output
0 [] 0x18dfb8dc9b82229cff974efefc8df78b1ce96d9d844236b496785c698bc6732e
1 [1] 0x168758332d5b3e2d13be8048c8011b454590e06c44bce7f702f09103eef5a373
2 [1,2] 0x038682aa1cb5ae4e0a3f13da432a95c77c5c111f6f030faf9cad641ce1ed7383
3 [1,2,3] 0x23864adb160dddf590f1d3303683ebcb914f828e2635f6e85a32f0a1aecd3dd8
4 [1,2,3,4] 0x130bf204a32cac1f0ace56c78b731aa3809f06df2731ebcf6b3464a15788b1b9
6 [1,2,3,4,5,6] 0x07f57fcda925c06dc0a311f3f17fa0218e079b514552744a25ba8a74ee8c9e7a
7 [1,2,3,4,5,6,7] 0x16f929bc0d216df4b05bdc44222463edf2b9791bd949ab926eebda06a502d238

n = 0, 3, 6 exercise the conditional trailing-permutation branch in both directions. n = 2, 3, 4 are the historical H2/H3/H4 raw wrappers and remain unchanged.

docs/kdc_v1_0_2_conformance_vectors.json is the normative machine-readable package. Its rawSponge section contains the table above. Its kdc section covers seed values 0, 1, r-1, r, r+1, the high bit, and an asymmetric 32-byte seed, and pins all six §A.6 outputs. The r/zero and r+1/one aliases are intentional consequences of the normative big-endian reduction.

Every conforming runtime MUST reproduce every shared vector applicable to its published interface. Solidity conformance is limited to the raw arities its published interface implements; it MUST NOT claim unsupported higher arities. Noir, Rust, ZKC, ZKM, and Aztec runners MUST execute every applicable vector rather than merely copying the expected values.

A.4 Domain-Tag Registry

The following are the complete KDC-owned derivation tags in v1.0.2:

Domain tag Used in Purpose
zka/spending §A.6 Derive spending key from seed
zka/viewing §A.6 Derive viewing key from spending key
zka/proof §A.6 Derive proof key from spending key
zka/address §A.6 Derive address from viewing key
zkc/credential §A.6 Derive credential binding key from seed
zkc/binding §A.6 Derive credential commitment from credential binding key

The zka/ and zkc/ tag prefixes are retained for continuity with the pre-KDC ZKA and ZKC specifications and with existing test vectors; the prefix is historical and does not imply ownership by ZKA or ZKC. All six tags are owned by KDC.

Ownership and collision rule. A tag string is owned by the specification that defines its operation, irrespective of its prefix. A referencing specification MAY define additional operational tags. Every such tag:

Fixed-byte fold-tag carve-out (ADR-0006). The ZKA zka/pay/* and zka/coord/* tag families are raw fixed byte tags: each tag's UTF-8 bytes right-aligned in a 32-byte word and consumed directly as a field element, never derived through deriveFieldTag. Like ZKC's frozen aggregation literals below, they are deliberate compatibility constants outside the deriveFieldTag regime and MUST NOT be reinterpreted through it. They are owned by ZKA as the specification defining their folds. A new tag in this class MUST NOT collide with any registered tag string, and collision checking for the class is against ZKA's protocol-family/poseidon2-operational-domains-v1.json (active_fixed_byte rows) in addition to the 27-tag snapshot below. That registry holds the class's members with one known gap: zka/pay/interaction-context/v1 is not yet registered there — its normative definition lives in ZKA's pay interaction-binding profile; registration is tracked as req_zka_register_zkapayinteractioncontextv1__923e6c97, and AFP-side vendoring of the registry with drift-gate coverage as req_afp_vendor_zka_operationaldomains_regist_c22a7062. This carve-out records an existing externally-owned boundary: no KDC-owned tag, derivation, value, or vector changes, and the AFP-KDC version string is unchanged; folding the clarification into the next coordinated AFP-KDC patch release is tracked with the vendoring follow-up.

The machine-readable interoperability snapshot docs/kdc_v1_0_2_domain_registry.json pins all 27 currently tagged constants: six KDC derivation tags, four ZKA operational tags, eight ZKC operational tags, and nine ZKM operational tags. It is a KAT and collision registry, not a transfer of tag-string ownership to KDC. A new tag MUST be checked against the complete snapshot before publication. ZKC's frozen aggregation seed/verify literals are not string tags; they remain explicit compatibility constants outside this registry and MUST NOT be reinterpreted through deriveFieldTag. Replacing them with tagged constants requires a versioned circuit/VK migration. docs/kdc_v1_0_1_domain_registry.json remains published as the historical v1.0.1 artifact; every tag and field_hex value is byte-for-byte identical between the two registry versions.

ZKA, ZKC, and ZKM operational field tags — the deriveFieldTag class — all use the same §A.3 derivation; ZKA's pay/coord fixed-byte fold tags sit outside that class under the carve-out above. The v1.0.2 registry is the active 27-tag KAT and collision snapshot; the preserved v1.0.1 artifact records the version in which those values were introduced.

A.5 Per-Entity Sub-Derivation

A tenant with master seed M derives a distinct sub-seed M' for each entity:

M' = HKDF(M, "entity/{name}")

This sub-derivation is new in KDC v1.0.0: it originates in the AFP entity model (AFP §4.3) and has no pre-KDC ancestor, so KDC is its first and authoritative definition.

Concrete construction. The sub-derivation uses HKDF per RFC 5869 with SHA-256:

M' = HKDF-SHA256(
        IKM  = M,                            // 32-byte tenant master seed
        salt = encodeFixedTag("kdc/entity-subseed"), // fixed 32-byte domain salt
        info = utf8("entity/" || name),       // entity name, UTF-8
        L    = 32                             // output length in bytes
     )

The output M' is a 32-byte value and is itself a valid §A.2 master seed: it is supplied directly to the §A.6 derivation functions. name is the entity's name as used in its AFP delegated-AID label (AFP §4.2).

Rationale — two primitives by design. KDC deliberately uses HKDF-SHA256 for the seed-tree derivation (M → M') and Poseidon2/BN254 for the key derivation (M'/seed → keys, §A.6). The seed-tree derivation is never performed inside a zero-knowledge circuit — it is wallet- and key-management-layer work — so the well-audited RFC 5869 standard KDF is the appropriate, conservative choice. The key derivation of §A.6 is performed in-circuit (ZKA §5.2 re-derives keys from a seed as circuit constraints), so it must use the circuit-efficient Poseidon2. The boundary between the two primitives is exactly the boundary between non-circuit and circuit derivation. An implementation MUST NOT substitute one primitive for the other.

Implementation note. HKDF-SHA256 remains fixed and normative. v1.0.1 retains the docs/kdc_v1_0_0_test_vectors.json sub-seed vectors unchanged; encodeFixedTag("kdc/entity-subseed") is exactly the byte encoding that those vectors already pin.

A.6 Key-Derivation Functions

Given a 256-bit seed (a standalone master seed per §A.2, or a per-entity sub-seed M' per §A.5), KDC derives the key hierarchy:

                    +-----------------+
                    |   Seed (256b)   |   standalone master seed,
                    +--------+--------+   or per-entity M' (§A.5)
                             |
         +-------------------+-------------------+
         |                   |                   |
         v                   v                   v
  +--------------+   +--------------+   +--------------+
  | Spending key |   | Viewing key  |   |  Proof key   |
  |     (sk)     |   |     (vk)     |   |     (pk)     |
  +--------------+   +--------------+   +--------------+
         |                   |
         |                   v
         |            +--------------+
         |            |   Address    |
         |            |    (addr)    |
         |            +--------------+
         v
  +-----------------------+
  | Credential binding    |
  | key (cbk)             |
  +-----------+-----------+
              |
              v
  +-----------------------+
  | Credential commitment |
  | (holderCommitment)    |
  +-----------------------+
interface KeyHierarchy {
  seed:                 Uint8Array;  // 256 bits — store securely
  spendingKey:          Hash;        // sk
  viewingKey:           Hash;        // vk
  proofKey:             Hash;        // pk
  address:              Hash;        // addr
  credentialBindingKey: Hash;        // cbk
  credentialCommitment: Hash;        // public — holderCommitment in ZKC binding
}

// KDC v1.0.2 key derivation. All hashes: the exact §A.3 Poseidon2 sponge with
// domain separation per §A.3. Behaviorally identical to ZKA v0.6.0 §2.2.
function kdcDeriveKeys(seed: Uint8Array): KeyHierarchy {
  const sk   = H("zka/spending",  seed);   // from seed
  const vk   = H("zka/viewing",   sk);     // from sk
  const pk   = H("zka/proof",     sk);     // from sk
  const addr = H("zka/address",   vk);     // from vk
  const cbk  = H("zkc/credential", seed);  // from seed — NOT from sk
  const cc   = H("zkc/binding",   cbk);    // credential commitment

  return { seed, spendingKey: sk, viewingKey: vk, proofKey: pk,
           address: addr, credentialBindingKey: cbk,
           credentialCommitment: cc };
}

Two derivation facts are normative and load-bearing for the referencing specifications:

  1. cbk derives from the seed, not from sk. The credential binding key is H("zkc/credential", seed), a sibling of the spending key, not a descendant of it. This is what lets a holder prove credential ownership without exposing spending authority.
  2. Address and credential commitment are derived along separate paths. address = H("zka/address", H("zka/viewing", H("zka/spending", seed))) and credentialCommitment = H("zkc/binding", H("zkc/credential", seed)) share only the seed. Given one, the other cannot be computed or linked without the seed. ZKA's identity-binding circuit (ZKA §5.2) proves the link in zero knowledge precisely because the link is otherwise unrecoverable. A KDC implementation MUST preserve this separation.

The names of the derived keys are KDC's; how each key is usedsk to spend ZKA notes, vk to decrypt incoming notes, cbk to bind ZKC credentials — is specified by the referencing protocols, not here.

A.7 Conformance and Versioning

A conforming AFP-KDC v1.0.2 implementation:

Test vectors. docs/kdc_v1_0_0_test_vectors.json remains normative for HKDF entity sub-seeds. docs/kdc_v1_0_2_domain_registry.json is normative for field tags and the complete cross-protocol collision scan. docs/kdc_v1_0_2_poseidon2_parameters.json and docs/kdc_v1_0_2_conformance_vectors.json are normative for Poseidon2 and the complete KDC hierarchy. All four artifacts are required for v1.0.2 conformance; the v1.0.1 registry remains available as historical compatibility evidence.

Versioning. v1.0.2 is a patch because it completely specifies and tests the already-pinned implementation without altering a value. Any future change to §A.3–§A.6 that alters a derived field, seed, key, proof, VK, or hash is a major KDC version bump and requires an explicit compatibility/migration plan across every consumer. A referencing specification cites a specific AFP-KDC version and may assume its derived values are immutable.


Document History

Version Date Changes
0.1.0 2025-11 Initial AFP draft (open-marketplace federation)
0.2.0 2025-12 Marketplace federation: third-party agent registration, capability discovery, task delegation, observation, reputation, trust ladder, HTTP bridge
0.3.0 2026-05-16 Scope narrowed to instance-level federation (three relationships). Marketplace surface removed. KERI-aligned identity adopted (§4). ZKA / ZKC reframed as substrate (§5, §6). Operator-blindness elevated to a structural invariant covering both content and the coordination graph (§3). Telemetry redesigned with no dependence on transport observation (§6). Metadata-privacy routing specified (§8): the AFP Federation Mixnet — a self-contained, consortium-operated Loopix-family mix network with mandatory cover traffic, KERI-witnessed mix-node directory, and private route resolution — adopted as the concrete OB-2 mechanism against a global passive link observer; Katzenpost named as preliminary reference implementation. Key Derivation Core (KDC) v1.0.0 incorporated as Appendix A — the shared master-seed derivation consolidated from ZKA §2.2 and ZKC §2.1 into a self-contained, independently versioned module.
0.3.1-draft 2026-06-05 Bilateral Session Core consolidated into AFP (§12), Profile concept defined (§13), per ADR-0003. Lifted the session substrate that PRP v0.2 and VXP v0.2 each independently re-derived — bilateral session establishment / lifecycle / timeout (§12.2), session identity binding (§12.3), the afp.{tenant}.{profile}.* NATS namespace (§12.4), the bilateral dispute / optional-pre-agreed-arbiter model (§12.5), the bilateral commitment + optional Ethereum-L2 anchor primitive (§12.6), the session-envelope-over-ZKA-bundle wire format (§12.7), and the Freedom-Safeguards application at the session layer (§12.8) — into one AFP-owned section group. Defined the profile mechanism and lightweight registration (§13); named work (base, §5), prp, and vxp as the v0.2 profiles. Resolved two profile-flagged seams: VXP's vxp.* namespace is a subtree under AFP (§12.4); the CoordBundle home is ZKA-owns-bundle + AFP-owns-envelope, replacing vxp:coordbundle:v1 (§12.7). Companion PROFILE_MIGRATION_NOTE.md maps the superseded PRP/VXP sections. No change to §1–§9 normative content beyond pointer notes; PRP/VXP specs not edited.
0.3.2-draft 2026-06-10 Substrate context refreshed; witness bootstrap, rotation sequencing, public-sample consistency, telemetry predicate catalog, KDC vectors, mixnet implementation parameters, and DGP gateway boundary specified. AFP now targets ZKA v0.6.2 and ZKC v0.2.4. ZKA v0.6.2 resolves the §12.7 ZKA-side bundle dependency by adding optional coord_proof carriage to zka:bundle:v1 and permitting pure work-attestation bundles. ZKC v0.2.4 adds the ZKC-Core credential rebind circuit (zkc:proof:credential_rebind:v1), narrowing AFP's remaining rotation seam to ZKA note migration; issuer migration tokens may optimize integrations but do not replace the proof, and KDC derivation values are unchanged. §1.4.1 defines the non-AFP boundary for tenant-exposed DGP gateways: no AFP discovery feed, no coordination-traffic visibility, and no trust-boundary expansion. §4.5 defines afp:witness-bootstrap:v1; §4.6 defines afp:rotation-presentation:v1; §6.3 defines AFP operator telemetry predicate IDs and verification shape; §6.4 defines afp:telemetry-sample-binding:v1; §8 defines afp.mixnet.profile.ob2-baseline.v1 and afp:mixnet-directory:v1; §10 tracks operator↔︎operator federation as deferred future multi-operator ADAMAS scope without making it AFP v0.3.2 conformance. docs/kdc_v1_0_0_test_vectors.json publishes HKDF entity sub-seed vectors and records unchanged ZKA seed-to-key vector verification. §10 and PROFILE_MIGRATION_NOTE.md are updated so resolved dependencies are not carried as open work.
0.3.3-draft 2026-06-15 Co-publication harmonization + safeguard-count correction. Retargeted to ZKA v0.7 and ZKC v0.3.0 (Abstract, §1.4 diagram, Appendix A KDC consumer list). Corrected the inherited Freedom Safeguard count from five to six (Abstract, §1.1, §1.5, §9, §12.8) — ZKA v0.7 §1.6 / ZKC v0.3.0 §1.4 add Safeguard 6 (Verifier Accountability), load-bearing for the unknown-counterparty direction (ADR-0004). No session/envelope/routing/identity/telemetry/profile mechanism change. The full inline ZKA/ZKC citation sweep and the open-federation trust-model change land with the ADR-0004 revision.
0.3.4-draft 2026-06-16 Open-federation trust basis (ADR-0004, ratified). Generalized AFP's trust basis from "a bilateral federation agreement is the sole precondition for a session" to either a §5.5 agreement (known counterparty) or per-interaction ZKC compliance attestation (open federation, new §5.8) — unlocking ADAMAS-to-ADAMAS transactions between unknown counterparties with no new protocol and no weakening of operator-blindness. Revised: Abstract; §1.2 (trust-ladder row), §1.3 (relationship 1 admits unknown counterparties); §5.1 (two trust bases), §5.2 (open-federation session context), §5.5 (agreement is one of two bases), §5.8 (the attestation path — authenticated proof request, holder-retained Disclosure Receipt, own-policy acceptance, Safeguard-6 accountability, interaction-boundary-only); §8.8 (route resolution reconciled — trust generalized, discovery/rendezvous stays out); §10 (new stranger-rendezvous-under-OB-2 open item + generalization note); §12.1/§12.3 (session trust-anchored on agreement or attestation; identity binding admits attested AIDs). Supporting open federation is OPTIONAL; when supported, §5.8's requirements are normative. All six Freedom Safeguards inherited identically by both paths; ZKA settlement and ZKC credential semantics unchanged. Also corrected the §1.4 table version labels left at ZKA v0.6.2 / ZKC v0.2.4 after the v0.3.3 bump to ZKA v0.7 / ZKC v0.3.0. Reconciled with the parallel §14 Introduction Tier: §12.1 now enumerates three trust bases (pre-existing agreement / §14 provisional agreement / §5.8 attestation-no-agreement) and §14.2's "sole … without a pre-existing agreement" claim is scoped to provisional-agreement minting so it no longer contradicts §5.8.
0.3.5-draft 2026-07-02 Canonical filename established as docs/afp_specification.md. Clarified that the ZKA-Aztec v5/AZUP-2 migration is complete in ZKA.dev and sandbox-accepted, while AFP profile anchoring still inherits the Ethereum-L2-via-APL conformance baseline unless a deployment explicitly wires Aztec as an APL rail. No protocol semantics changed.
0.3.6-draft 2026-07-11 Experimental AFP pay v1.0 profile added (§15). Defines a bilaterally authenticated USD-minor-unit obligation and exact WETH-wei settlement quote bound by a domain-separated interaction context to the immutable Base ZKA-native WETH pool; fixes canonical integer, lowercase-address, 32-byte nonce, whole-second UTC timestamp, deterministic ceiling-rate, replay, receipt, and monotonic finality rules. Keeps AFP pricing/orchestration, ZKA settlement/proofs, and APL route/finality ownership separate; requires hard Safeguard 3, single-asset immutable settlement pools, and per-asset risk profiles; and records that a native stable unit is not a prerequisite. Corrects stale §12.8/§14.8 safeguard mappings to all six safeguards. KDC v1.0.0 and existing session/envelope/bundle versions are unchanged.
0.3.6-draft (ZKM alignment) 2026-08-04 Adds ZKM as an optional authorization composition peer and KDC seed-model consumer; states the independent AFP §14 bilateral-policy versus ZKM unilateral-mandate checks; and discloses the unresolved KDC encodeTag versus deployed operational-separator conflict under its tracked reconciliation requirement. No AFP envelope, session, profile, KDC constant, or test vector changes.
0.3.7-draft 2026-08-04 ZKC v0.4 presentation profiles + AFP-KDC v1.0.1. Adds §5.9's per-flow anonymous/scoped/linkable policy, AID/credential-subject/APR/session binding, open-federation proof-borrowing rejection, consent-gated linkability, profile-minimized receipts/rotation artifacts, and explicit zka:bundle:v1 carriage. Updates telemetry, bilateral, introduction, open federation, rotation, and conformance language. Reconciles field-domain tags with the deployed Blake2b512 little-endian reduction, separates fixed byte tags, and publishes the 27-tag KDC/ZKA/ZKC/ZKM KAT/collision registry without changing any existing constant, proof, VK, hash, or vector.
0.3.8-draft 2026-08-08 Vendor-neutral instance attestation. Freezes afp.telemetry.instance_attestation.v1 unchanged and fixes the non-Draft v2 public statement, witness, circuit, cadence, failures, and verifier obligations for build-provenance transparency logs, TEE measurement-to-manifest binding, and publisher-manifest self-attestation. Requires §6.4 public-sample binding plus §3.5 disclosure for publisher-manifest roots and moves §14 counterparty use to v2. No ZKC credential-schema or issuer-adapter contract is redefined.
0.3.9-draft 2026-08-14 AFP-KDC v1.0.2 complete Poseidon2 conformance. Publishes the exact width-4 BN254 parameter table and digest, length-bound sponge, raw edge KATs, boundary/asymmetric full-hierarchy vectors, standard Noir/Rust/ZKC/ZKM/Aztec/Solidity execution gates, and scheduled cross-repository copied-code and artifact drift checks. No derived value or existing cryptographic artifact changes.
0.3.9-draft (ADR-0005 tenant definition) 2026-08-15 Platform-agnostic tenant definition (ADR-0005, ratified). A tenant is now a runtime that federates as a principal under its own tenant root AID and MUST satisfy the tenant conformance profile, rather than "an ADAMAS instance"; ADAMAS becomes the reference implementation. Tenanthood stays structural so that "Every tenant MUST …" remains enforceable against a shortfall (§3.5, §8.9). New §1.5.1 consolidates the tenant conformance profile — KERI identity custody (§4), operator blindness and topology (§3, §7.1, §7.3), routing participation incl. mandatory cover traffic (§8), the bilateral session core (§12), operator telemetry (§6), the six Freedom Safeguards (§9), and the tenant-internal obligations (§5.6, §5.7) — introducing no requirement not already normative elsewhere. Boundaries previously labeled "ADAMAS-internal" are tenant-internal, with §5.7's implicitly delegated containment/capability-scoping/observability duty restated as a normative tenant obligation. The single trust level reads verified conforming instance (§1.2, §12.1, §12.5, §13.1). §7.1 generalized from "Source-available ADAMAS" to "Auditable implementation": OB-relevant behavior MUST be auditable, an implementation that cannot support inspection MUST NOT claim OB and falls under §3.5. §1.2 restates every retirement as a guard against reading the generalization as reopening registration, discovery, reputation, the trust ladder, or the HTTP bridge. §10 adds the deferred multi-vendor conformance program open item (gated on a second serious implementer) and the ADR-0005 generalization note. Naming Pyramidal Inc. as the attestation root authority is tracked separately and is not in this revision. No wire change: no new or renamed message class, predicate ID, public input, or envelope revision; afp.telemetry.instance_attestation.v1 remains frozen. The interface version is unchanged at 0.3.9-draft because no wire surface moved; the numbered 0.3.10-draft release is a coordinated protocol-family action tracked separately.
0.3.9-draft (ADR-0005 authority naming) 2026-08-16 Attestation authority named; the authority set stated open (ADR-0005 item 4). Closes the half of ADR-0005 the 2026-08-15 revision deferred. The single trust level is qualified to verified conforming instance under a verifier-accepted attestation authority — stated canonically in §1.2 and cross-referenced from §12.1, §12.5, and §13.1; §13.1 deliberately keeps the unqualified phrase per ADR-0005's PRP/VXP out-of-scope clause. §1.5.1 is extended so the qualified phrase cannot be read as making instance attestation a precondition of a session; it is not. §6.3.1 fixes an organizational authority as the accountable party — its organizational root AID, never a product, brand, or build line — while leaving the root:tee-attestation and root:build-provenance identifier meanings (hardware vendor product root policy; transparency-log checkpoint signer) exactly as their branches already fix them. It permits KEL-anchored, product-scoped delegated signing on the §4.2 tenant→entity shape, ties root_material_digest to the delegated signing-key state, routes rotation/compromise/succession through the signing AID's KEL anchored by seals in the authority's KEL, and assigns delegation resolution and root_material_digest recomputation to the verifier, out of circuit, before acceptance, mirroring the existing delegated-verifier-AID rule — so no circuit, branch obligation, or verification key changes, and the attest-own-builds-only property is stated as resting on that recomputation rather than on the circuit alone. The previously unimplementable "stale or revoked authority" check is made dischargeable, for a KERI-rooted authority, from its own KEL and the KELs it anchors (§4.2, §4.4, §4.6) — with the transparency-log and hardware classes retaining their verifier-pinned log-policy and vendor-root material, and authority-KEL events checked against a witness set and threshold pinned in the accepted trust material (§4.5). No authority-published registry or feed is on the normative path in any class; authority failure routes to decline-or-alert and nothing more, on every counterparty-facing path (§5.4/§5.5, §5.8, §14.3) and in telemetry (§6.6) (Safeguards 3, 5). root:publisher-manifest gains an explicit attest-own-builds-only scope and a split disclosure duty — the deployment's §3.5 duty unchanged, and the authority's half stated as a verifier acceptance condition so no MUST lands on a party that is neither a principal nor a conformance subject — with class rules otherwise unchanged and the §6.4 binding staying tenant-signed. The operational-status disclosure is lifted into the normative body and stated path-independently. The authority set is open: extending the root-class taxonomy requires a revision to §6.3.1, standing up a new authority does not — no registration, permission, notification, grant, or amendment, and no AFP authority registry (§1.2, §5.5, §5.8, §6.3.1, §9, §10, §12.8). An authority is not a principal (§2.1, which remains closed at three) and certifies no conformance (§1.5.1). Pyramidal Inc. is named non-normatively as the first authority, with a delegated ADAMAS publisher AID, no AID value asserted, and the authority not yet operational (§6.3.1, §14.9); its dual role as operator and authority is stated as independent (§8.6). A shipped default verification policy is implementation-side product configuration — visible, editable, removable, never hardcoded beyond removal, conferring no protocol privilege — stated normatively in §6.3.1 and cross-referenced from §9, §12.8, and §14.4, and this specification defines no default authority. §10 adds the transparency-log upgrade path and the out-of-band authority-binding item. One bounded encoding amendment: afp:publisher-key-state:v1 names the JCS record whose digest root_material_digest already carried for this class, fixing an underdetermined preimage of an existing public input — with KEL SAIDs, keys, and pre-rotation commitments carried verbatim rather than re-encoded under the sha256: *_digest rule, and kt/nt typed to admit KERI's weighted-threshold form; it adds no public input and no wire field. No wire change: no new or renamed message class, predicate ID, public input, envelope revision, or NATS subject; AfpInstanceAttestationPublicInputsV2 and AfpIntroductionPolicy.accepted_instance_attestation_roots are byte-identical. afp.telemetry.instance_attestation.v1 remains frozen textually and semantically — implementations MUST NOT map its publisher AID onto the organizational-root/delegated-publisher construction. The interface version is unchanged at 0.3.9-draft.
0.3.9-draft (ADR-0006 interaction-context fold) 2026-08-16 Payment interaction context is the ZKA Poseidon2 fold (ADR-0006, ratified). §15.4 rewritten: interaction_context is derived by the frozen ZKA/Pay Interaction-Context Binding Profile v1 field-count-prefixed Poseidon2 fold under the fixed-byte tag zka/pay/interaction-context/v1, restated self-containedly and pinned to ZKA.dev db39afd (fixture SHA-256 committed; an upstream v2 binds AFP only via a superseding ADR). The SHA-256/canonical-JSON derivation, its afp:payment-context:v1 kind, its published vector, and the 1 + (x mod (r-1)) mapping are retired (no external consumer recomputed them). Fold inputs are the 32-byte digest payloads of quote_hash and the session context — code-agnostic qb64 extraction, plain big-endian mod-r reduction, zero rejected fail-closed at quote-construction time; the wire stays unreduced and recomputing parties MUST reduce first; the pay profile requires the session context be a qb64-encoded 32-byte digest (base-spec encoding: new §10 open item). Conformance re-anchored on the five vendored upstream KATs plus an AFP-native end-to-end vector whose two digests both exceed r. §15.5 item 6 gains the terminal zero-reduction condition; §15.8 wording follows. Appendix A reconciled: new §A.4 fixed-byte fold-tag carve-out (mirroring the ZKC-literals exemption), §A.3 prohibition scoped to KDC key derivation, §2.2 note and the §A.4 blanket same-derivation claim corrected; the tag is registered in no family registry today — ZKA-side registration, protocol-family vendoring/drift-gate, and the APL verification leg are tracked ADR-0006 follow-ups. No wire change: no new or renamed message class, quote field, public input, or envelope revision; the merged downstream carriers stay wire-compatible — only the opaquely carried derived value changes. The interface version is unchanged at 0.3.9-draft.

License

This specification is released under the Apache 2.0 License.


AFP: Verifiable federation between tenants — with nothing to trust.