# Agent Federation Protocol (AFP) Specification **Version:** 0.3.9-draft **Interface version:** 0.3.9-draft **Document revision:** 3 (2026-08-16, ADR-0005 authority naming) **Status:** Draft Specification **License:** Apache 2.0 **Domain:** AFP.dev **Date:** 2026-08-16 **Canonical file:** `docs/afp_specification.md` **Interface surface:** `protocol-family/afp-interface-surface-v1.json` > **Versioning axes.** This document carries two independent version axes, and they move independently. > > **Interface version** — AFP's wire and interface surface: the session envelope, the §5.3 message classes, the §6.3 telemetry predicates, the typed `afp:*` wire records, the §5.9 presentation profiles, the §13.3 published profiles, the §12.4 subject namespace, and the ZKA/ZKC/AFP-KDC versions this specification composes with. `**Version:**` above is its canonical spelling and is restated as `**Interface version:**`. It is the axis the protocol family pins, and it moves **only** when that surface moves. Its machine-readable form is `protocol-family/afp-interface-surface-v1.json`, whose `interface_set_id` digests the surface itself; `scripts/check_afp_interface_surface.py` re-derives every claim in that file from this specification on every CI run. > > **Document revision** — the editorial axis. It moves on every published revision of this prose, **including revisions that change no wire surface at all**. It is deliberately referenced by nothing in `protocol-family/interface-manifest-v1.json`, and a revision that moves only this axis is not a protocol-family action: no epoch, no snapshot re-vendor, no coordinated release. The qualified same-version rows in the Document History table below (`0.3.9-draft (ADR-0005 tenant definition)`, `0.3.9-draft (ADR-0005 authority naming)`) predate this axis and are what its absence forced. --- ## 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](https://github.com/pyramidal-io/ZKA.dev/blob/main/docs/ZKA_ZKC_AFP_MIGRATION.md). 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):** - Scope narrowed from open-marketplace federation to instance-level federation. The agent-manifest schema, capability-discovery feed, task-delegation wire protocol, external observation contribution, individual-agent reputation, the federated→integrated→native trust ladder, and the HTTP bridge are **all removed** (§1.4). - KERI-aligned self-certifying identity adopted as load-bearing; hash-derived identifiers and the flat `nkey_history` model are retired (§4). - ZKA and ZKC reframed as substrate: tenant ↔ tenant rides on ZKA, tenant ↔ operator rides on ZKC (§5, §6). - Operator-blindness elevated to a structural protocol invariant (§3, §7). The SaaS deployment model of v0.2.0 — operator-run transport with transport-observed per-tenant telemetry — is **retired**; AFP v0.3.0 assumes a source-available ADAMAS with no operator on the coordination path. - Telemetry redesigned: no load-bearing dependency on per-tenant transport observation; all per-tenant operator knowledge is ZKC-proved (§7). - Metadata privacy added as an explicit protocol surface (§3.4, §8): the coordination relationship graph is protected, not merely the payloads. §8 specifies the **AFP Federation Mixnet** — a self-contained, consortium-operated Loopix-family mix network with mandatory cover traffic — as the concrete mechanism delivering OB-2 against a global passive link observer. - **Key Derivation Core (KDC) v1.0.0** incorporated as Appendix A: the master-seed derivation, Poseidon2/BN254 domain-tag scheme, domain-tag registry, and per-entity HKDF sub-derivation, previously inline in ZKA §2.2 and ZKC §2.1, are consolidated into a single self-contained, independently versioned module that ZKA, ZKC, and AFP all normatively reference (§2.2, §4.3, Appendix A). **Version 0.3.1-draft Changes (relative to v0.3.0), per ADR-0003:** - **Bilateral Session Core consolidated into AFP (§12).** The session machinery that PRP v0.2 and VXP v0.2 each independently re-derived — bilateral session establishment, lifecycle and timeout, identity binding, the NATS subject tree, the dispute/arbiter model, the bilateral commitment + optional-anchor primitive, the session-envelope wire format, and the application of the Freedom Safeguards at the session layer — is lifted into a single AFP-owned section group. Profiles reference it rather than re-specify it. - **The Profile concept defined (§13).** An *AFP profile* is a named behavior built over the Bilateral Session Core. PRP (pairwise reconciliation) and VXP (value exchange) are named as the two v0.2 profiles; AFP's own §5 tenant↔tenant work exchange is the base behavior. A lightweight profile-registration mechanism is specified. This realizes ADR-0003's "3 open protocols + 2 AFP profiles" framing: PRP and VXP are not peer protocols but profiles over AFP. - **NATS namespace ownership resolved (§12.4).** AFP owns the subject tree; every profile lives under `afp.{tenant}.{profile}.*` (e.g. `afp.{tenant}.prp.*`, `afp.{tenant}.vxp.*`). This resolves the flag VXP v0.2 raised: `vxp.*` is a profile subtree **under** AFP, not a standalone namespace. - **Federation bundle / CoordBundle home resolved (§12.7).** ZKA owns the cryptographic bundle (`zka:bundle:v1`, carrying ZKA/Coord work-attestation and ZKC compliance proofs); AFP owns the **session envelope** (`afp:envelope:v1`) that carries a ZKA bundle across a tenant boundary within a session and binds it to the session context. VXP's `vxp:coordbundle:v1` and any PRP bundle wrapper are **replaced** by AFP-envelope + ZKA-bundle. - A companion **`PROFILE_MIGRATION_NOTE.md`** maps, per profile, which PRP/VXP v0.2 sections are now superseded by a reference to a §12/§13 core section, so the follow-up trim of PRP/VXP is mechanical. This revision does **not** edit the PRP or VXP specs. **Version 0.3.5-draft Changes (relative to v0.3.4-draft):** - **Canonical filename established.** The live normative AFP specification is now `docs/afp_specification.md`; versioning lives in this header and the Document History table, not the filename. - **ZKA-Aztec v5 status clarified.** ZKA-Aztec is v5/AZUP-2 migrated and sandbox-accepted in ZKA.dev, but AFP's anchor conformance baseline remains Ethereum L2 via APL unless a deployment explicitly integrates Aztec as an APL rail. No AFP session, envelope, identity, routing, or safeguard semantics change. **Version 0.3.6-draft Changes (relative to v0.3.5-draft):** - **ZKM/KDC and §14 composition alignment.** ZKM is added as an optional principal-issued authorization peer and as a consumer of the KDC seed model. AFP §14 bilateral envelopes and ZKM unilateral mandates are independent constraints: neither subsumes or mutates the other. The existing mismatch between KDC §A.4's `encodeTag` requirement and deployed ZKA/ZKM operational separator derivation is disclosed as unresolved rather than silently normalized. No AFP envelope, session, profile, KDC constant, or test vector changes. - **USD-obligation/WETH-settlement profile added (§15).** The experimental `pay` v1.0 profile lets two principals bilaterally authenticate a USD-minor-unit obligation and an exact WETH-wei settlement amount for an immutable Base ZKA pool. AFP owns quote, expiry, replay, and receipt semantics; ZKA owns private settlement and pool state; APL owns route execution and finality observation. - **Settlement boundaries made explicit.** The launch profile is Base WETH9 in a separately deployed immutable single-asset pool with `zka:asset-risk:s3-hard:v1`. Any additional asset requires a separate pool and explicit risk profile. A native stable unit is an independent future protocol, not an AFP `pay` or ZKA/Pay prerequisite. - **Safeguard inheritance corrected.** The stale §12.8/§14.8 five-safeguard mappings now include all six safeguards, including Safeguard 6 (Verifier Accountability). **Version 0.3.7-draft Changes (relative to v0.3.6-draft):** - **ZKC v0.4.0 presentation profiles adopted (§5.9).** Telemetry and known-counterparty bilateral flows default to anonymous presentations and permit a scoped→anonymous downgrade when continuity is unnecessary. Introduction and open-federation flows require scoped continuity; linkable v1 remains available only with explicit holder consent. Every accepted presentation binds the KERI presenter AID, ZKC credential-subject AID, authenticated APR signer, AFP session context, session-policy-derived scope, and active profile. - **Rotation and receipts minimize handles (§4.6, §5.9).** Anonymous artifacts expose no holder-derived handle; scoped artifacts expose only `presentationPseudonym` and `scopeId`; linkable artifacts expose only the consented legacy `holderCommitment`. ZKC v2 rebind is used for scoped continuity, while the frozen v1 path remains the explicitly linkable compatibility profile. ZKA bundle carriage remains explicitly `zka:bundle:v1`. - **AFP-KDC v1.0.1 reconciles domain tags (Appendix A).** Circuit field tags now unambiguously derive as `LEInteger(Blake2b-512("ZKA-Domain-" || tag)) mod BN254.Fr`; fixed 32-byte tags such as the HKDF salt remain a separate byte-encoding class. KDC owns only the six derivation tag strings; ZKA, ZKC, and ZKM own their operational tag strings. `docs/kdc_v1_0_1_domain_registry.json` KAT-pins and collision-checks the complete 27-tag family. This is a compatibility-only patch: no existing constant or cryptographic artifact changes. **Version 0.3.8-draft Changes (relative to v0.3.7-draft):** - **Pluggable instance-attestation roots fixed (§6.3.1).** `afp.telemetry.instance_attestation.v2` replaces v1's hard-wired publisher AID with a closed root-class taxonomy and verifier-selected root authority: `root:build-provenance`, `root:tee-attestation`, or `root:publisher-manifest`. The v2 public statement, private witness, circuit branches, cadence, failure signals, and root-specific verification obligations are normative rather than Draft. - **Publisher manifests reclassified without changing v1.** A platform publisher signing its own build manifest is `root:publisher-manifest` and **self-attested at the platform layer**. Such a v2 proof carries a mandatory §6.4 public-sample binding and the §3.5 honest-disclosure duty. The complete `afp.telemetry.instance_attestation.v1` catalog row and its wire/circuit semantics remain byte-identical for compatibility. - **Counterparty use moved to v2 (§14.2, §14.9).** Introduction policy now names accepted root classes and root authorities and consumes the vendor-neutral v2 predicate. AFP references the separately versioned ZKC machine build/runtime credential schema; it does not redefine that credential or expose raw provenance bundles or TEE quotes. **Version 0.3.9-draft Changes (relative to v0.3.8-draft):** - **AFP-KDC v1.0.2 parameterization (§A.3).** Pins BN254 `Fr`, Poseidon2 width 4, rate 3, capacity 1, `x^5`, 8 full and 56 partial rounds, both matrices, all round constants, the length IV, exact absorption/trailing-permutation rule, and output lane. The canonical 8,320-byte constant table is SHA-256 committed. - **One conformance authority.** `docs/kdc_v1_0_2_conformance_vectors.json` covers raw sponge edge arities plus boundary and asymmetric 32-byte seeds across every KDC branch and defines the required execution contract for consumer gates. Standard Noir, Rust, ZKC, ZKM, Aztec Nargo, and the applicable Solidity H2 surface execute the shared values in CI. - **Cross-repository drift gate.** A scheduled and change-triggered workflow compares copied parameter tables, runs sibling runtimes, verifies the upstream Poseidon source identity, and invokes circuit/vector/artifact binding validators so parameter, vector, circuit-ID, and VK drift fails closed. No derived field, proof, VK, hash, or prior vector changes. **Version 0.3.9-draft (ADR-0005 tenant definition) Changes (relative to v0.3.9-draft), per ADR-0005:** - **Tenant definition generalized (§1.6, §2.1).** A `tenant` is a runtime that federates as a principal under its own tenant root AID and MUST satisfy the tenant conformance profile — no longer "an ADAMAS instance." ADAMAS becomes the **reference implementation**, holding no protocol privilege. - **Tenant conformance profile consolidated (new §1.5.1).** Seven tenant-side obligations — KERI identity custody (§4), operator blindness and topology (§3, §7.1, §7.3), routing participation including constant-rate emission (§8), the bilateral session core (§12), operator telemetry (§6), the six Freedom Safeguards (§9), and the tenant-internal obligations (§5.6, §5.7) — gathered in one place. Every item restates a requirement already normative elsewhere; the profile introduces none and adds no wire surface. - **"ADAMAS-internal" is now "tenant-internal" (§1.1, §1.2, §5.2, §5.6, §5.7).** Where the prior text implicitly delegated a duty to ADAMAS, the duty is restated as a normative tenant obligation — §5.7's containment, capability-scoping, and observability of a tenant's own entities most notably. - **Trust level reads *verified conforming instance* (§1.2, §12.1, §12.5, §13.1).** Naming an attestation root authority is tracked separately and is **not** in this revision. - **§7.1 generalized to "Auditable implementation."** OB-relevant behavior of any conforming implementation MUST be auditable; an implementation that cannot support inspection MUST NOT claim OB and falls under §3.5 honest disclosure. Source availability is the canonical means, and the reference implementation's Business Source License is the worked example rather than the definition. - **Retirement guard restated (§1.2).** Generalizing *who may implement a tenant* confers no authority to reopen third-party agent registration, discovery or marketplace surface, portable positive reputation, a trust ladder, or the HTTP bridge. The §14 introduction tier's opt-in willingness-to-federate directory and the §14.7 negative-history record are expressly unaffected. - **New open item (§10).** The formal multi-vendor conformance program — tenant-profile vectors as a normative gate, governance, certification — is deferred until a second serious implementer commits; until then conformance is asserted against the spec text and judged bilaterally. - No wire change: no new or renamed message class, predicate ID, public input, envelope revision, or NATS subject. `afp.telemetry.instance_attestation.v1` remains frozen. **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:** - **Trust level qualified (§1.2).** The single trust level reads *verified conforming instance under a verifier-accepted attestation authority* (§6.3.1). §1.2 is the canonical statement; §12.1, §12.5, and §13.1 cross-reference it rather than restate it, and none of them makes instance attestation a precondition of a session (§1.5.1). - **An organizational attestation root authority is the accountable party, not a product (§6.3.1).** For an organizational authority — the case `root:publisher-manifest` presents — `root_authority_identifier` MUST resolve to the party accountable for the attestation, its organizational root AID, never to a product, brand, build line, or distribution. `root:tee-attestation` and `root:build-provenance` keep the branch-specific meanings already fixed for their identifiers (a hardware vendor's product root policy; a transparency log's checkpoint signer). - **Product-scoped signing MAY be delegated and KEL-anchored (§6.3.1).** An organizational authority MAY sign manifests under a delegated AID whose inception is anchored in the authority's KEL, exactly as an entity is delegated from a tenant root (§4.2); `root_material_digest` commits to that delegated signing-key state, and rotation, compromise handling, and succession run as published events in the signing AID's KEL, each anchored by a seal in the authority's KEL (§4.2, §4.4, §4.6). - **Delegation resolution is a verifier duty, not a circuit obligation (§6.3.1).** The verifier resolves the delegation chain from the signing AID to the named authority root AID out of circuit, before acceptance, mirroring the existing rule for a delegated verifier AID, and independently recomputes `root_material_digest` from the resolved key state. The circuit continues to bind only the leaf signing-key state committed by `root_material_digest`; no branch obligation and no verification key changes. The attest-own-builds-only property rests on that recomputation, not on the circuit alone, and the text says so. - **Stale or revoked authority is dischargeable without a vendor document (§6.3.1).** For a KERI-rooted authority the determination is made from that authority's own KEL and the KELs it anchors (§4.2, §4.4, §4.6); `root:build-provenance` and `root:tee-attestation` keep their existing verifier-pinned log-policy and vendor-root material. Authority-KEL events are checked against a witness set and threshold pinned in the accepted trust material (§4.5). In no class is an authority-published registry or feed on the normative path. - **`root:publisher-manifest` gains an explicit scope and a split disclosure duty (§6.3.1).** An authority under this class attests only manifests it itself signs; the deployment's §3.5 disclosure duty is unchanged, and the authority's half is stated as a **verifier acceptance condition** — a verifier MUST NOT accept an authority under this class unless the trust material it accepted characterizes the attestation as platform-layer self-attestation — so no MUST is placed on a party that is neither a principal nor a conformance subject (§1.5, §2.1, §3.5). - **Operational-status disclosure is path-independent and normative (§6.3.1).** Where a named authority's KEL and trust material do not yet exist, a deployment selecting it MUST disclose the path as spec-available but not operational under §3.5, on the telemetry, bilateral, and open-federation paths alike; §14.9 applies additionally where the introduction tier runs. The duty lives in the normative body, not in the non-normative note. - **A new artifact `kind` fixes an existing preimage (§6.3.1).** `afp:publisher-key-state:v1` names the JCS-encoded signing-key-state record whose digest `root_material_digest` carries for `root:publisher-manifest`, with every field encoding pinned: KEL SAIDs, keys, and pre-rotation commitments verbatim as the KEL lists them (never re-encoded under the `sha256:` `*_digest` rule), and `kt`/`nt` copied verbatim, including KERI's nested weighted-threshold form. This determines the preimage of an existing public input; it adds no public input, no wire field, and no new public statement. - **The set of authorities is open (§1.2, §5.5, §5.8, §6.3.1, §9, §10, §12.8).** Extending the *root-class taxonomy* requires a revision to §6.3.1; standing up a new root authority does not. Any organization MAY stand as an authority on identical terms, by publishing trust material a verifier can accept — never by registration, permission, notification, grant, or amendment to this specification. AFP publishes no authority registry, list, or feed and defines no procedure for admitting, ranking, suspending, or removing an authority. - **An attestation root authority is not a principal (§2.1).** AFP still recognizes exactly three kinds of principal. An authority is an AID whose trust material a verifier may elect to accept; it addresses no AFP message, opens no session, and holds no protocol privilege. Where the same legal entity is also an operator (§8.6), the two roles are independent. - **Shipped defaults are product configuration, not spec defaults (§6.3.1, §9, §12.8, §14.4).** The obligation now has a normative home in §6.3.1: an implementation MAY ship a default verification policy naming authorities it recommends, and such an entry MUST be visible to, editable by, and removable by the deployment operating it, MUST NOT be required for conformance, and MUST NOT be hardcoded beyond removal. It is not a default of this specification. A pair applies to an interaction only when signed into that interaction's policy (§5.5, §14.4). The §9 and §12.8 rows remain the cross-references they claim to be. - **Pyramidal Inc. named non-normatively as the first authority (§6.3.1).** The Pyramidal/ADAMAS instantiation — an organizational root AID with a delegated, product-scoped ADAMAS publisher AID beneath it — is recorded as a clearly-marked non-normative example, written throughout in the intended voice and headed by the not-yet-operational disclosure. No AID value is asserted; the key ceremony has not run. The operational-status duty binds from §6.3.1's normative body on every path, with §14.9 applying additionally on the introduction path. - **`afp.telemetry.instance_attestation.v1` remains frozen, semantically as well as textually (§6.3, §6.3.1).** Implementations MUST NOT map v1's `publisher AID` onto the organizational-root/delegated-publisher construction. The delegated-publisher construction is v2-and-forward only. - **New open items (§10).** The transparency-log upgrade path for the ADAMAS product line, and the out-of-band authority binding material an authority publishes outside this specification. - 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 unchanged. **Version 0.3.9-draft (ADR-0006 interaction-context fold) Changes (relative to v0.3.9-draft (ADR-0005 authority naming)), per ADR-0006:** - **§15.4 rewritten: the `pay` interaction context is the ZKA/Pay Interaction-Context Binding Profile v1 Poseidon2 fold.** The SHA-256/canonical-JSON derivation, its `afp:payment-context:v1` kind discriminator, and its published conformance vector are retired (no external consumer recomputed them; verified across ZKA.dev and ADAMAS at ratification). §15.4 now restates the fold normatively and self-containedly — fixed-byte tag, field-count prefix, three-call expansion, input order — pinned to ZKA.dev `db39afd` with the KAT fixture SHA-256; an upstream v2 binds AFP only via a superseding ADR. - **Field mapping pinned on both legs.** Fold inputs are the 32-byte digest payloads of `quote_hash` and the session context (code-agnostic qb64 extraction), reduced plain big-endian mod `r`, zero rejected fail-closed at quote-construction time; the wire carries the **unreduced** digest, and recomputing parties MUST reduce first. The `pay` profile normatively requires the session context to be a qb64-encoded 32-byte digest (base-spec formalization: new §10 open item). - **Conformance re-anchored (§15.4, §15.8).** All five upstream KAT vectors (vendored verbatim, hash-pinned) plus a new AFP-native end-to-end vector — complete quote content through `quote_hash`, both field reductions (both digests exceed `r`), and the fold output — replace the retired vector as the "MUST reproduce exactly" anchor. - **§15.5 checklist item 6 extended** with the terminal fail-closed zero-reduction condition at dispatch. - **Appendix A reconciled (§2.2 note, §A.3, §A.4).** New §A.4 **fixed-byte fold-tag carve-out** for ZKA's `zka/pay/*`/`zka/coord/*` families, mirroring the existing ZKC-literals exemption; §A.3's field-separator prohibition scoped to KDC key derivation; §A.4's blanket same-derivation claim corrected to the `deriveFieldTag` class. The tag is registered in no family registry today; ZKA-side registration is a tracked follow-up. - **Reference implementation:** `derive_interaction_context` keeps its `(str, str) -> str` signature over the new fold; `canonical_nonzero_bn254` (the retired `1 + (x mod (r-1))` mapping) and `PAYMENT_CONTEXT_KIND` are removed from the public API, replaced by `field_from_digest` (plain mod-`r`, reject-zero), `fold_interaction_context`, and `INTERACTION_CONTEXT_TAG`. - No wire change: no new or renamed message class, quote field, envelope revision, or NATS subject; the two merged downstream carriers (ZKA holder-adapter, ADAMAS APL settlement request) remain wire-compatible — only the derived value they carry opaquely changes. **Version 0.3.4-draft Changes (relative to v0.3.3-draft), per ADR-0004:** - **Open-federation trust basis (the normative change ADR-0004 records).** AFP's trust basis is generalized from "a pre-existing **bilateral federation agreement** is the *sole* precondition for a session" to: a session MAY be opened on **either (a)** a bilateral federation agreement (the existing known-counterparty path, unchanged) **or (b)** per-interaction **ZKC compliance attestation** that satisfies each party's own acceptance policy (the new **open-federation** / unknown-counterparty path). Revised surface: the Abstract; §1.2 (trust-ladder row), §1.3 (relationship 1 admits unknown counterparties); §5.1 (model no longer assumes a known counterparty), §5.2 (open-federation session context), §5.5 (the agreement is now one of two trust bases); a new **§5.8 "Open federation — trust by ZKC attestation"**; §8.8 (route resolution reconciled with the no-prior-agreement path); §12.1 and §12.3 (the session is trust-anchored on an agreement *or* an attestation, and identity binding admits attested AIDs). - **Path (b) is OPTIONAL to support, but constrained when supported.** A conforming deployment MAY support only the bilateral path; if it supports open federation, the §5.8 requirements (authenticated proof request, holder-retained receipt, own-policy acceptance, full safeguard inheritance) are MUST. **Safeguard 6 is what makes path (b) safe:** the requesting tenant is a *verifier* and MUST authenticate its demand under a stable identity (no signature ⇒ no proof), and the responder retains a portable Disclosure Receipt — so "prove you are compliant to transact with me" between strangers cannot degrade into unaccountable coercion. - **Discovery / rendezvous stays out of AFP.** Only *trust establishment* is generalized (attestation, fully compatible with operator-blindness); how two strangers *find* each other and route first contact is **not** moved into AFP — a queryable tenant directory *is* the coordination graph OB-2 (§3, §8) exists to prevent. The residual stranger-rendezvous-under-OB-2 problem is surfaced as a new **§10 open item**, not hidden inside an over-broad "support unknown counterparties" mandate. Path (b) is usable today only where first contact is established out-of-band. - **Freedom Safeguards, settlement, and ZKC/ZKA semantics unchanged.** Both paths inherit all six safeguards identically; compliance attestation remains an interaction-boundary concern (Safeguard 5: a declined/failed attestation means the counterparty declines the interaction — never that funds or notes are gated, frozen, redirected, or seized; exit stays unconditional). ZKA and ZKC need no substantive change — only the cross-reference/safeguard-count harmonization already done in v0.3.3. - **Completes the §1.4 version harmonization.** The §1.4 related-protocols table still carried the v0.3.2 substrate labels (ZKA v0.6.2 / ZKC v0.2.4) after the v0.3.3 Abstract/diagram bump; the two row labels are corrected to **ZKA v0.7 / ZKC v0.3.0**. (The v0.3.2 changelog and Document-History rows retain v0.6.2/v0.2.4 as the accurate record of what that revision targeted.) - **Reconciled with the §14 Introduction Tier (merged in parallel).** §14 (added independently) admits a stranger session by *minting a provisional §5.5 agreement*; §5.8 admits one with *no* agreement (per-interaction attestation). They are **complementary**, not competing. §12.1 is reframed to enumerate **three** trust bases — (a) pre-existing agreement, (b) §14 provisional agreement, (c) §5.8 attestation/no-agreement — and §14.2's "sole session type permitted to open without a pre-existing agreement" is scoped to "sole session type permitted to open by *minting a provisional agreement*," so §5.8 (no agreement) is not a counter-exception. §5.8 gains a "Relationship to the Introduction Tier" note; the §10 rendezvous open item now covers both paths. **Version 0.3.3-draft Changes (relative to v0.3.2-draft):** - **Co-publication cross-reference harmonization.** AFP now targets **ZKA v0.7** and **ZKC v0.3.0** — the co-published revisions of the sibling specs (updated in the Abstract, the §1.4 related-protocols diagram, and the Appendix A KDC consumer list). Inline ZKA/ZKC section citations elsewhere in the body are swept to the same versions in this revision. - **Inherited Freedom Safeguard count corrected to SIX.** AFP previously inherited "five" Freedom Safeguards (a ZKA v0.6.0 count); ZKA v0.7 §1.6 and ZKC v0.3.0 §1.4 both now define **six**, adding **Safeguard 6 — Verifier Accountability**, the first safeguard to constrain the *counterparty* rather than the operator. Corrected in the Abstract, §1.1, §1.5, §9, and §12.8. Safeguard 6 is load-bearing for unknown-counterparty federation (ADR-0004): a tenant requesting a compliance proof from a stranger is a *verifier*, and SG6 (authenticated request + holder-retained receipt) is what keeps "prove you are compliant to transact with me" from becoming silent, unaccountable coercion. - No change to AFP session, envelope, routing, identity, telemetry, or profile mechanics. This revision aligns cross-references and the inherited-safeguard count for simultaneous separate publication of ZKA, ZKC, and AFP; the open-federation trust-model change itself is recorded in **ADR-0004** and lands in a subsequent revision. **Version 0.3.2-draft Changes (relative to v0.3.1-draft):** - **Substrate targets updated (§1.4, §12.7).** AFP now targets ZKA v0.6.2 and ZKC v0.2.4. ZKA v0.6.2 adds optional `coord_proof` carriage to `zka:bundle:v1` and permits pure work-attestation bundles without a payment leg, satisfying the ZKA-side dependency previously flagged by AFP §12.7. ZKC v0.2.4 adds the ZKC-Core credential rebind circuit (`zkc:proof:credential_rebind:v1`), which AFP §4.6 sequences with KEL rotation while ZKA note migration remains Draft. Issuer-specific migration tokens may optimize integrations but do not satisfy AFP rotation presentation without the ZKC-Core rebind proof. - **Open-item status refreshed (§10).** The §12.7 ZKA bundle dependency is no longer an open AFP item. The remaining rotation seam is narrowed to ZKA note migration; ZKC credential rebinding is now available as the credential-side primitive. Witness bootstrapping, public-sample consistency, the operator telemetry predicate catalog, AFP rotation sequencing, KDC vectors, and the AFP Federation Mixnet implementation profile are now specified. --- ## 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: - **Register third-party agents.** An entity is a principal only by delegation from the tenant that runs it: its AID is a delegated AID anchored in that tenant's KEL (§2.1, §4.2), and no other tenant can confer, register, or admit it. No AFP message registers, enrolls, or admits an agent across a tenant boundary; observations still originate only from tenant-internal entities; and intra-tenant dispatch remains tenant-internal (§5.6), never a wire delegation feature. A second tenant implementation is a *tenant*, never a registered agent. - **Publish discovery, marketplace, or capability-registry surface.** What a tenant exposes remains negotiated bilaterally under §5 and is never advertised as a capability- or work-discovery feed — not to counterparties, not to operators, not to a DGP gateway (§1.4.1). The OPTIONAL, opt-in willingness-to-federate directory of §14.1 is the sole permitted published record and is unaffected: it carries a tenant's consent to be approached and its negotiating ceiling, never a work descriptor or an open call for work. Conformance itself is not discoverable: it is asserted and verified per §6.3.1 under the verifier's own policy. Nor is the set of attestation authorities discoverable: AFP publishes no authority registry, list, or feed. A verifier's accepted (root class, root authority) pairs live in its own authenticated verification policy (§6.3.1) and in the bilateral agreement or introduction policy that scopes an interaction (§5.5, §14.4); they are not advertised. - **Carry portable reputation.** Positive trust signals remain per-agreement and bilateral (§5.5, §14.6): no score, rating, or conformance mark accumulates globally or substitutes for a bilateral agreement. The signed negative-history record of §14.7 is unaffected — it is a per-event signed statement of a specific default, not an accumulated score. - **Reintroduce a trust ladder.** There remains exactly one protocol-level trust level. AFP defines no escalating ladder of counterparty classes and no privileged class of implementer; the reference implementation holds no protocol privilege, and a named attestation authority (§6.3.1) holds none either. Naming an authority in this specification, or shipping it as a default entry in an implementation's verification policy, confers no protocol privilege on that authority, imposes no obligation on any verifier to accept it, and creates no standing that AFP could grant or withhold. A conformance shortfall is handled by the honest-disclosure regime of §3.5 and §8.9 — the deployment discloses the limitation and MUST NOT claim the property — never by creating a new principal class or a lesser tier of tenant. - **Reinstate the HTTP bridge.** All AFP traffic remains tenant ↔ tenant over the common transport (§2.3). 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. ### 1.4 Related Protocols | 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: - **No AFP discovery feed.** AFP does not publish tenant, counterparty, capability, route, profile, or telemetry discovery data to a DGP gateway. If a tenant wants a DGP tool to know about an AFP artifact, the tenant exports a tenant-scoped reference explicitly. - **No coordination-traffic visibility.** A DGP gateway MUST NOT receive AFP coordination traffic, mixnet route material, rendezvous handles, counterparty pairing data, session traffic timing, or any other data that would weaken OB-1 or OB-2 (§3). - **No trust-boundary expansion.** Trust in a DGP gateway is never transitive into AFP. A gateway credential, policy decision, or audit event does not authorize AFP session establishment, telemetry acceptance, KEL rotation, note migration, or profile commitment unless the AFP principal independently signs the AFP artifact required by this specification. - **Opaque provenance only.** A DGP gateway MAY carry opaque references to AFP session envelopes, work artifacts, PRP evidence, or VXP lifecycle atoms when the tenant intentionally exports them for provenance or audit. The reference MUST NOT include bilateral agreement contents, counterparty AID pairings, route state, telemetry private inputs, or mixnet metadata unless those fields are already visible to the tenant principal controlling the gateway. 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. - **Tenant.** A runtime that federates as a principal, identified by its own tenant root AID; a tenant MUST satisfy the tenant conformance profile (§1.5.1). The unit of federation. A tenant runs one or more entities. Tenanthood is a structural fact about what a runtime is, not a certificate of conformance: a tenant that falls short of an obligation is a **non-conforming tenant**, subject to the honest-disclosure regime of §3.5 and §8.9, never a non-principal. ADAMAS, published by Pyramidal Inc., is the reference implementation; the protocol names no implementer. §6.3.1 names a first attestation *authority*, which is a different thing: an authority is a party whose trust material a verifier may elect to accept, never a party entitled to implement, admit, or approve a tenant. - **Entity.** A named domain multi-agent system within a tenant (e.g. Viksana, Alochana). Identified by a delegated AID whose inception is anchored in the tenant's KEL. Entities are the principals that actually perform and attribute cross-tenant work; an AFP message that crosses a tenant boundary on behalf of an entity carries the entity's delegated AID. - **Operator.** Whoever runs an AFP transport. Identified by its own root AID, structurally identical to a tenant's. Operator status is an operational fact about who runs infrastructure; it confers **no** protocol-level privilege and **no** data path into tenant state (§3). 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**: - **OB-1 (content)** is delivered in every deployment by end-to-end encryption of all AFP payloads (§5.2) plus the substrate properties of ZKA and ZKC. The operator sees ciphertext envelopes, never contents. - **OB-2 (graph)** is delivered by the metadata-privacy routing layer of §8, under which no single transport operator — including the entity that publishes a tenant's implementation — observes an unobscured tenant↔tenant relationship. 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. - **Tenant root.** Each tenant has one master seed `M`. The tenant root is a KERI AID governed by a KEL. `M` is the tenant's root secret; the tenant root AID is the canonical identifier of the tenant across the federation. - **Entity (delegated AID).** Each entity within the tenant has a sub-seed `M' = HKDF(M, "entity/{name}")`. The entity is a KERI **delegated AID**: its KEL opens with a delegated-inception (`dip`) event anchored in the tenant's KEL. The tenant can rotate, revoke, or replace any entity AID by events in its own KEL. - **Operator.** An operator has its own root AID with its own master seed, structurally identical to a tenant. Operator privileges are operational only; per §3 they confer no protocol-level data path. ### 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: - An entity's ZKA address and its ZKC `holderCommitment` derive along separate paths from `M'` and are unlinkable without `M'`, exactly as Appendix A requires — the §4.2 hierarchy does not weaken that property, it scopes it per entity. - Compromise of one entity's `M'` does not expose sibling entities: each `M'` is an independent HKDF output of `M` (Appendix A §A.5). - Cross-tenant work performed by an entity is attributable to that entity's delegated AID and signable with a key derived from `M'`. ### 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. - A tenant MUST publish its witness set and threshold as part of its KEL. - A verifying counterparty MUST check that KEL events carry receipts from at least the tenant's declared witness threshold. - Witness configuration travels with the tenant across a topology change: an appliance migration (§4.7) carries the witness set. 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**: ```typescript 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: - **Managed bootstrap.** The tenant selects witnesses from a managed or consortium-published candidate set, signs `afp:witness-bootstrap:v1`, obtains receipts from at least `threshold` witnesses over the tenant root inception event, and publishes the record with the KEL. - **Bring-your-own witness bootstrap.** An appliance tenant supplies its own witness AIDs and endpoints, signs `afp:witness-bootstrap:v1`, obtains the same threshold receipts, and publishes the record with the KEL. An appliance MUST be able to complete this flow without contacting the original managed operator. 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: - **Rotation** is a standard KEL rotation event honoring the prior pre-rotation commitment. - **Recovery** (key loss) uses KERI's delegated-recovery / guardian mechanism: a pre-designated guardian set can author a recovery event. Guardian designation MUST itself be a published KEL event. - **Succession** (a tenant or entity handing control to a new principal) is a delegation or rotation event, depending on whether the AID identity is preserved. 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: ```typescript 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): - **Known counterparty (bilateral path).** The two tenants hold, or establish, a bilateral federation relationship (§5.5). This is the original v0.3.0 path, unchanged. - **Unknown counterparty (open-federation path, §5.8).** The two tenants have no prior agreement and establish trust **per-interaction** by mutual ZKC compliance attestation. Each party answers the other's Authenticated Proof Request with a ZKC compliance bundle; acceptance is each party's own policy. 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. - **Value transfer** between tenants (e.g. payment for work performed) is a ZKA/Pay transfer (ZKA v0.9.0-draft §3): the payment is a ZKA note, amounts and parties hidden from observers. When principals price an obligation in USD but settle it in WETH, the AFP `pay` profile (§15) binds the exact signed quote to the ZKA payment; ZKA does not interpret USD or choose a price. - **Verifiable multi-step work** is attested with ZKA/Coord recursive coordination proofs (ZKA v0.9.0-draft §4): an `afp.attest` message carries a `CoordinationProof` proving that an N-step workflow executed correctly without revealing the step inputs. AFP's `context` (§5.2) is bound into the proof's `context` public input. - **Selective disclosure** — proving a property of a work artifact or balance without revealing it — uses ZKA/Pay selective-disclosure circuits (ZKA v0.9.0-draft §6). Each disclosure is atomic (one property per proof) per Safeguard 2. - **Compliance** — where one tenant requires the other to prove a compliance property as a condition of the interaction — uses a ZKA Compliance Bundle (`zka:bundle:v1`, ZKA v0.9.0-draft §5.4), which wraps atomic ZKC v0.4 proofs: `zkc:proof:v2` for anonymous/scoped presentations or frozen `zkc:proof:v1` for an explicitly-consented linkable presentation. AFP applies the flow policy and AID/session binding of §5.9. An `afp.disclose.response` MAY carry a Compliance Bundle. 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). - A federation agreement is signed by both tenant root AIDs and referenced by `afp.work.request` messages. - Agreement scope is bilateral and private to the two tenants; it is not published or discoverable. - Whether any *instance-level* reputation signal beyond the bilateral agreement is needed is **left open** (§10); the working assumption of v0.3.0 is that bilateral agreements supersede portable scoring entirely. - Requiring instance attestation on this path is **OPTIONAL**. Where an agreement's compliance requirements include `afp.telemetry.instance_attestation.v2`, the agreement MUST name the (root class, root authority) pairs each party accepts, with the semantics and verifier obligations of §6.3.1; the §14.4 `accepted_instance_attestation_roots` shape is the reference encoding. An agreement that does not require the predicate names no authority, and the absence of a named authority never selects one by default. Like every other term of the agreement, the accepted pairs are private to the two tenants and are neither published nor discoverable (§1.2). 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:`. 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:`, 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: ```typescript 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: 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:`, 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: ```typescript 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: ```typescript 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. - **`root:build-provenance`.** `attestation_evidence_digest` MUST be the external-artifact digest of the exact signed provenance statement. The private witness MUST include that statement, builder identity/signature evidence, artifact/subject mapping to `attested_manifest_digest`, the accepted scheme-specific log entry and leaf bytes, and the Merkle inclusion path. The verifier MUST authenticate the checkpoint signer named by `root_authority_identifier`, recompute the checkpoint committed by `root_material_digest`, apply its split-view/consistency policy against its last accepted checkpoint, and verify the ZKC circuit whose public statement names the exact statement digest and checkpoint. The circuit proves leaf inclusion: it MUST verify the builder signature and policy, exact manifest subject, that the accepted log's leaf-construction algorithm commits the scheme-specific log entry to `attestation_evidence_digest`, and that the resulting leaf, Merkle path, tree size, and root match the authenticated checkpoint. Together these checks are the required transparency-log inclusion verification; the log entry and path remain hidden. A signature without an authenticated checkpoint and successful inclusion branch is insufficient and MUST fail with `instance_attestation_provenance_not_in_log`. A §6.4 public-sample binding is OPTIONAL because the independently obtained log checkpoint is its external root; the exact zero sentinel denotes absence. If nonzero, the binding MUST verify under §6.4 and commit to the same manifest, verifier, and attestation window. - **`root:tee-attestation`.** `attestation_evidence_digest` MUST be the canonical digest of the exact hardware attestation report. The private witness MUST include that report, endorsement chain, freshness material, measurement, and manifest-to-measurement mapping accepted by policy. Its report-data field MUST equal SHA-256 over the canonical bytes of `{kind:'afp:instance-attestation-report-data:v2', attested_manifest_digest, subject_aid, attestation_window, verifier_aid}`. The circuit MUST prove this measurement-to-manifest binding, report signature/endorsement chain, allowed measurement under `verification_policy_digest`, and freshness for `attestation_window`. `root_authority_identifier` MUST name the hardware vendor/product root policy; `root_material_digest` MUST commit to the exact verifier-accepted vendor certificate roots, revocation status, and endorsement/collateral version. The verifier MUST resolve those values from its own pinned hardware-vendor root configuration, validate collateral freshness and revocation, and MUST NOT trust a vendor root supplied only inside the quote. A §6.4 public-sample binding is OPTIONAL because the independently authenticated report is the external root; the exact zero sentinel denotes absence, and a nonzero binding MUST verify against the same manifest, verifier, and window. - **`root:publisher-manifest`.** `attestation_evidence_digest` MUST be the canonical digest of the exact publisher-signed manifest. Beyond the common witness above, the private witness for this branch MUST include exactly: that manifest, the publisher signature over it, and the `afp:publisher-key-state:v1` JCS bytes for the committed signing-key state. Beyond the common constraints above, this branch adds exactly four: that the hidden manifest opens to `attested_manifest_digest`; that the circuit reconstructs the publisher-key-state JCS bytes from the witnessed record and requires their SHA-256 digest to equal `root_material_digest`; that the hidden signature verifies over the manifest under that record's `signing_public_keys` at its `signing_threshold`; and that the record's `authority_aid` equals `root_authority_identifier`. These are the pre-existing branch obligation to bind signing-key state to both public values, made byte-exact by the record above; delegation does not add another circuit obligation. Establishing that the witnessed record is in fact the current state of the named `signing_aid`, and that the signing AID's delegation chain reaches `root_authority_identifier`, is the verifier's independent out-of-circuit work stated above — recomputation from resolved KEL state, before acceptance. `root_material_digest` for this class is the `afp:publisher-key-state:v1` digest of the (possibly delegated) signing-key state. Because a platform publisher is attesting its own build, this class is **SELF-ATTESTED at the platform layer**, not third-party-issued. Its `public_sample_binding_digest` MUST NOT be the zero sentinel and MUST identify a valid §6.4 public-sample binding over the same manifest, predicate version, attestation window, verifier, and assertion commitment; absence fails with `instance_attestation_sample_binding_missing`. A deployment relying on this class alone MUST make the §3.5 honest disclosure that its instance authenticity rests on publisher self-attestation and a public consistency sample, not an independent build-provenance log or hardware root. It MUST NOT describe the result as third-party attestation. An authority under this class attests **only manifests it itself signs**. It makes no statement, warranty, or attestation about any build it did not produce — including forks, rebuilds, and derivative distributions — and a proof MUST NOT be read as one. **This guarantee is not purely in-circuit, and an implementer MUST NOT treat it as if it were.** The circuit proves that the hidden signature verifies under the exact key state committed by `root_material_digest` and that the witnessed record names the declared authority; it does not prove that the record's `signing_aid` is actually delegated from that authority or that the record reflects current KEL state. Those facts rest on the verifier's **mandatory out-of-circuit recomputation** of `root_material_digest` from the KEL state it resolves through the authority's delegation anchors, together with the policy match on the accepted authority. Absent that recomputation, a party could construct a key-state record naming another organization's root AID as `authority_aid`, commit its own keys in that record, sign its own manifest with them, and satisfy every in-circuit constraint — so a verifier that omits the recomputation forfeits the property and accepts forged attribution. A fork or derivative distribution stands up its **own** publisher authority under the identical rules of this section — it may attest its own builds on exactly the same terms — but it does not inherit, and cannot borrow, the authority of the upstream publisher. The disclosure duty is split and both halves apply. The deployment's §3.5 duty stated immediately above is unchanged and binds a conformance subject (§1.5). The authority's half is stated as a **verifier acceptance condition**, because an authority is not an AFP principal and not an AFP conformance subject (§1.5, §2.1): a verifier MUST NOT accept an authority under this class unless the trust material it accepted characterizes the attestation as platform-layer self-attestation rather than as third-party attestation. An authority that wishes to be accepted under this class therefore characterizes its own attestation truthfully in the trust material it publishes (§3.5) — that is where the duty is discharged, and it is not discharged in, and does not re-home, the §6.4 public-sample binding, which is produced and signed by the tenant. This is one of exactly two acceptance conditions this section states over accepted trust material — the other is the pinned witness set and threshold above — and together they are the whole of the normative standing AFP grants an authority's published material (§10). **Operational-status disclosure, on every path.** Where a named authority's KEL and published trust material do not yet exist, a deployment selecting that authority MUST disclose the path as *spec-available but not operational* under §3.5 and MUST NOT claim an operational attestation path — on the tenant→operator telemetry path, on a §5.4/§5.5 bilateral path, and on the §5.8 open-federation path alike. Where the deployment also runs the §14 introduction tier, §14.9 applies additionally and in its own terms. 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: ```typescript 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: - **Aggregate sample.** The tenant proves that its committed value is included in an aggregate whose population is at least `min_population`, or that the aggregate is differentially private under the declared `(epsilon, delta)`. The aggregate MUST NOT expose any individual tenant value or coordination relationship. - **Selective-reveal sample.** The tenant reveals the minimum fields required for the verifier to recompute the predicate-specific rule and proves that the unrevealed fields remain bound to the same assertion commitment. - **External-digest sample.** A non-operator source, named by `external_source_aid`, publishes a signed digest or Merkle root the verifier independently obtains. The tenant proves consistency with that digest without giving the operator access to the underlying per-tenant log. 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: - **Auditability of OB.** All OB-relevant behavior of a conforming tenant or transport implementation MUST be auditable: a tenant, or a third party, MUST be able to verify *by inspection* that no operator data path into tenant state exists. This is what makes OB a capability claim (§3.2) rather than a promise. Source availability is the canonical way to satisfy this requirement; ADAMAS, the reference implementation published by Pyramidal Inc., satisfies it under a Business Source License with a scheduled conversion to Apache 2.0. The same party publishes the ADAMAS build manifests attested under §6.3.1; auditability under this subsection and attestation under §6.3.1 are independent obligations, and neither substitutes for the other — an attested build is not thereby auditable, and an auditable implementation is not thereby attested. - **No privileged operator build.** A conforming AFP implementation MUST NOT depend on a closed or operator-only build for any OB-relevant behavior. Anything load-bearing for §3 MUST be within the material an auditor can inspect under the first bullet; where that material is source, it MUST be the published source. 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. | - **Packet format.** All coordination traffic is carried as fixed-size **Sphinx** packets. Every packet is identical in size and form; a mix node decrypts exactly one layer and learns only the immediately preceding and following hop. This delivers §8.2 properties 1 and 2 at the packet layer. `ob2-baseline-v1` fixes the hop geometry and payload class above so implementers do not pick weaker deployment defaults. - **Stratified topology.** Mix nodes are arranged in ordered layers; a packet traverses one node per layer. Three layers are REQUIRED for `ob2-baseline-v1`, so no single mix node sees both endpoints and the property is retained against a colluding pair that does not span all three layers. - **Continuous-time mixing.** Each mix node delays each packet by an independent, exponentially distributed random delay (client-selected per hop). This decouples the timing of a packet's arrival from its departure and is the core of §8.2 property 3. AFP coordination is not latency-critical (§5 work requests tolerate delay), so the baseline accepts multi-hop latency in exchange for correlation resistance. ### 8.5 Mandatory cover traffic Cover traffic is **REQUIRED**, not optional, and is the property on which OB-2 against a link observer depends. - Every tenant MUST emit coordination-plane traffic into the mixnet at a constant, scheduled rate at all times while federated, **whether or not it has real coordination traffic to send**. Real `afp.*` messages (§5.3) are slotted into this constant stream; when there is no real message, a cover (loop) message is sent instead. Cover and real packets are indistinguishable to any relay or observer (identical Sphinx form, §8.4). - Mix nodes MUST additionally emit loop cover traffic, so that link-level traffic between mix nodes is constant independent of tenant activity. - The cover schedule MUST be derived from the active mixnet directory epoch and the tenant's local scheduler, not 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. - **Exploiting the bounded tenant set.** Because the federation membership is bounded and enumerable (unlike an open consumer network), AFP can *mandate* constant emission from every participant. The observable consequence is the OB-2 guarantee in its strongest form: every tenant appears, to any link observer, to be emitting a constant stream at all times — an observation **identical whether the tenant is coordinating with another tenant or with no one**. The adversary's observation is the same in every world; the coordination graph is therefore not present in the observable traffic. A conforming deployment MUST run cover traffic at a fixed rate at every tenant and every mix node; a deployment that does not is not OB-2-conforming and MUST disclose this per §3.5. ### 8.6 Relay operation — no single trusted relay - Mix nodes are operated by **mutually distrusting parties** — federated tenants, designated independent operators, or both — such that no single operator controls enough of any path to correlate endpoints. - **The operator (e.g. Pyramidal) MUST NOT occupy a position on the mix path from which the coordination graph is observable.** Consistent with §7.3, the operator publishes the (source-available) mixnet software and MAY run a directory authority or witness, but MUST NOT be the sole operator of any mix layer, and MUST NOT run a mix node in a topological position that, alone, reveals a pairing. This is the §3.2 capability standard applied to the operator's own infrastructure. - **The operator role and the §6.3.1 attestation-authority role are independent.** Where the same legal entity is both an AFP operator (§2.1) and a §6.3.1 attestation root authority — as Pyramidal Inc. is — neither role confers anything on the other: standing as an attestation authority confers no operator privilege and no relaxation of the constraint above or of §3, and operating a transport confers no standing as an authority and no claim on any verifier's policy. A verifier's acceptance of an authority is not a judgment about that party's infrastructure, and §3's operator-blindness invariant is a capability standard that no attestation reputation can substitute for. - The mixnet uses **no token and no cryptocurrency.** Relay operation is a federation membership obligation or a flat contractual arrangement, not a speculative incentive. The federation is a bounded set of identified institutions, not an open population of anonymous relay operators; the token-and-incentive layer that public mixnets require to recruit untrusted relays is therefore unnecessary, and is deliberately excluded. ### 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 MUST be a tamper-evident structure verifiable against the federation's existing KERI substrate (§4): directory documents are signed by a quorum of consortium witnesses, so a tenant — including an appliance with no operator contact — verifies the mix-node set the same way it verifies a peer's KEL. AFP introduces no new trust authority for the **mix-node directory**; it reuses §4. (The §6.3.1 attestation root authority is unrelated to this section: it is a verifier-elective trust root for instance attestation, plays no part in directory verification, and confers nothing here.) - A reference Loopix-family implementation supplies its own decentralized directory-authority mechanism (a quorum-voting protocol). A conforming deployment MAY use that mechanism provided its trust model meets §8.2 property 4 — no single directory authority can forge the mix-node set — and provided the directory is bound into the consortium's KERI-witnessed structure so directory trust and federation identity share one root. The directory document has the following verifier-facing shape: ```typescript 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. - **No directory of tenants.** AFP defines no global, queryable directory mapping tenant AIDs to routes — such a directory would itself be the coordination graph (the same reason §5 defines no capability-discovery feed). This holds for **both** trust paths: open federation (§5.8) generalizes trust *establishment*, not *discovery*, and adds no directory. Mixnet route resolution is possible only between tenants that **already share rendezvous data** — bootstrapped from a bilateral federation agreement (§5.5) for the known-counterparty path, or established out-of-band-then-in-protocol for the open-federation path (see the last bullet). - **Rendezvous data is carried in the federation agreement.** When two tenants establish a §5.5 agreement, that agreement carries — or bootstraps — the rendezvous information each needs to reach the other through the mixnet: mixnet ingress points and a per-relationship rendezvous handle. Because the agreement is held only by the two tenant principals it concerns, the artifact that encodes the A↔B pairing exists nowhere except with A and B. No relay, no directory, and no operator holds it. - **Rendezvous handles MUST NOT be stable cleartext identifiers** of the tenant pair (§8.2 property 2). A handle is per-relationship and is used only inside Sphinx-wrapped packets; it is never exposed to a relay in the clear. - **Finding a counterparty that you cannot yet reach** is out of AFP scope — and this is unchanged by open federation. ADR-0004 generalized *trust establishment* (§5.8: two strangers can establish trust by ZKC attestation with no prior agreement) but deliberately did **not** move *discovery / rendezvous* into AFP. The reason is precise: a queryable directory of who-can-reach-whom is exactly the coordination graph OB-2 (§3.1) exists to deny. So: - For the **known-counterparty** path, rendezvous data is carried in the §5.5 agreement (above); route resolution begins once that agreement exists. - For the **open-federation** path (§5.8), the two tenants have no agreement to carry rendezvous data. **First contact must be established out-of-band** — a real-world decision, optionally assisted by a separate tenant-discovery layer outside this specification — after which the parties establish a per-relationship rendezvous handle and proceed exactly as the known-counterparty path does. AFP provides the trust-by-attestation and the session; it does **not** provide the find-each-other step. - **The genuine unresolved tension** is routing first contact *through the mixnet* for parties who have no agreement from which to bootstrap rendezvous data. This is tracked as an open item (§10) and is the one place where open federation is not yet self-contained. - Any discovery layer, if one exists, MUST NOT have visibility into coordination traffic and is **not** part of the AFP trust boundary. ### 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: - **Membership is not hidden.** The mixnet hides *who coordinates with whom*; it does not hide *that a tenant is a federation participant* — a tenant's constant cover-traffic emission to the mixnet is itself observable. For a bounded consortium this is generally acceptable (membership may be public), but it is a real boundary of the guarantee. - **Long-horizon statistical disclosure.** Constant cover traffic makes any point-in-time observation uninformative, but an adversary observing a *small* network over a *long* period can mount statistical-disclosure attacks that mixing and cover traffic mitigate but do not eliminate. Mandatory cover traffic from every tenant (§8.5) is the principal mitigation; the residual is smaller for larger federations. This limitation is shared by all Loopix-family systems and is inherent to the design class. --- ## 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. - **ZKA note migration (§4.6).** AFP rotation sequencing and ZKC credential rebinding are specified. The remaining lower-layer Draft seam is the ZKA-owned note-migration proof under ZKA §5.7 (ZKA v0.9.0-draft). - **Instance-level reputation (§5.5).** Whether any signal beyond the bilateral federation agreement is needed. **Resolved in v0.3.x (§14)** — see the note below. - **Stranger rendezvous under operator-blindness (§5.8, §8.8, §14).** Two tenants with no *pre-existing* agreement can now establish a trust basis two ways — per-interaction ZKC attestation with no agreement (§5.8, open federation) or a *provisional* agreement negotiated by an introduction session (§14) — but **both still require the parties to be in contact first.** The known-counterparty path bootstraps mixnet *rendezvous data* from the §5.5 agreement, which strangers lack. Routing **first contact** through the mixnet for parties with no agreement, **without** introducing a queryable directory that would itself be the coordination graph (the exact thing OB-2 denies), is unresolved. It needs a dedicated discovery/rendezvous layer with its own privacy analysis, or an out-of-band first-contact bootstrap; it is **out of AFP scope** and MUST NOT be solved by relaxing §8 OB-2. Until then, §5.8 and §14 are usable only where first contact is established out-of-band. - **Operator ↔ operator federation.** Deferred until there are at least two independent operators running conforming tenant infrastructure in production and a concrete need for cross-operator tenant portability, relay governance, directory governance, billing settlement, or incident coordination. This is future multi-operator scope, not AFP v0.3.2 conformance. A future operator↔operator design MUST NOT create an operator-visible coordination graph, an operator trust bridge for tenant↔tenant session establishment, a shared discovery feed, or any telemetry-sharing path that reveals tenant work. It MUST preserve §3 operator blindness, §4 tenant-controlled identity continuity, §6 tenant-pushed telemetry, and §8 OB-2 routing. Until that design exists, AFP deployments remain conforming only for tenant↔tenant, tenant↔operator, and identity-continuity relationships (§1.3); any operator↔operator operational agreement is out of AFP scope and cannot relax AFP requirements for tenant sessions. - **Base-spec `session_context` encoding (§5.2, §12.3, §15.4).** The base spec defines the session context only as `H(initiatorEntityAID, responderEntityAID, sessionId, timestamp)` — no digest algorithm, output width, or input canonicalization is pinned, and the reference implementation's timestamp serialization is not cross-runtime reproducible. §15.4 pins a `pay`-profile payload contract (a qb64-encoded 32-byte digest) as the de facto reading; the base-spec formalization must land and be reconciled against it. Tracked: `req_afp_formalize_sessioncontext_encoding_in_ab9e1b6c` (ADR-0006). - **Multi-vendor conformance program (§1.5.1).** A formal conformance program for the tenant profile — published tenant-profile test vectors as a normative gate, cross-runtime CI that fails closed on drift, program governance, and certification — is deferred until a **second serious implementer commits**. Until that milestone, §1.5.1 states the tenant conformance profile normatively but does **not** gate it on vectors: conformance is asserted against the spec text and judged bilaterally by counterparties and verifiers (§5.5, §5.8, §6.3.1), exactly as it was when the reference implementation was the only implementation. An implementation's operator-blindness claims are therefore held to §3.5 honest disclosure and checked by the §7.1 auditability requirement, not by a vector suite. The AFP-KDC v1.0.2 conformance vectors (Appendix A) are the shape such a suite would take. When the trigger fires, the program MUST NOT become a gate on *who may federate*. Vectors test the §1.5.1 obligations and nothing more; acceptance of a counterparty remains bilateral verifier policy (§6.3.1) rather than a certification grant; and no conformance mark, implementer registry, or published conformance feed may be introduced — that surface is retired (§1.2). The same holds for attestation authorities: standing as a §6.3.1 root authority is not a certification grant, is not gated on this program, and creates no implementer registry. A second implementer's publisher stands as an authority on the same terms as the first, before and after this program exists. - **Transparency-log upgrade for the ADAMAS product line (§6.3.1).** Moving ADAMAS builds into a transparency log would upgrade that product line from `root:publisher-manifest` to `root:build-provenance` — a strictly stronger, third-party-verifiable position inside the same taxonomy and under the same authority root, requiring no revision to this specification and no change to any verifier's accepted authority. Until then the product line attests as self-attested at the platform layer, with the mandatory §6.4 public-sample binding and the §3.5 disclosure duties of §6.3.1. The same upgrade path is open to any authority on identical terms. - **Out-of-band authority binding material (§6.3.1).** The AID↔legal-entity binding of §6.3.1 is established out-of-band. An authority's key ceremony, its published trust material, and any practice statement or relying-party terms it publishes are companion work owned by that authority; AFP defines neither their form nor their content and requires none of them of any verifier. It grants them normative standing in exactly one respect and no other: §6.3.1 states verifier **acceptance conditions** over the trust material a verifier elects to accept — the pinned witness set and threshold for the authority's KEL, and, under `root:publisher-manifest`, the self-attestation characterization. A verifier's obligations under §6.3.1 — including the stale/revoked determination — are dischargeable **for a KERI-rooted authority** from that authority's own KEL and the KELs it anchors (§4.2, §4.4, §4.6); the `root:build-provenance` and `root:tee-attestation` classes keep their existing verifier-pinned log-policy and vendor-root material. In no class is an authority-published advisory or status feed on the normative path. > **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: - **bilateral** — exactly two principals, one entity in each of two tenants. N-party interaction is composed from bilateral sessions (a star or mesh of them, profile-defined; §13.3), never run as a single N-way protocol; - **mutually authenticated** — each side verifies the other's delegated AID and its delegation chain to the counterparty tenant root AID against the declared witness threshold (§4.5, §5.2 step 2); - **end-to-end encrypted** — every payload is ciphertext to any transport operator (OB-1, §3.3); - **metadata-private** — routed through the §8 mixnet so no operator or link observer learns the tenant pairing (OB-2); - **trust-anchored** — every session is anchored to a trust basis, of which there are three (§5.1, §5.8, §14): **(a)** a **pre-existing** bilateral federation agreement (§5.5), whose policy block the session runs under — the original known-counterparty path; **(b)** a **provisional** agreement that an *introduction session* (§14, `afp.introduction.v1`) negotiates and both parties sign *before* any value interaction — the one session type that opens by *minting* an agreement rather than presupposing one; or **(c)** per-interaction **ZKC compliance attestation** with **no** agreement (§5.8, open federation, ADR-0004), whose accepted attestation + declared purpose plays the policy-block role. (Through v0.3.3 the core admitted only (a); §14 added (b); v0.3.4 / ADR-0004 added (c).) Cases (b) and (c) are the two ways to interact without a *pre-existing* agreement — (b) ends in a signed provisional agreement, (c) in none. Every other property in this list — mutual authentication, end-to-end encryption, OB-2 routing, context binding — is unchanged and REQUIRED for all three. A profile MAY restrict itself to the agreement path — PRP and VXP currently do; - **context-bound** — every signed atom and every proof in the session binds the session context of §5.2 step 4. 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: ```typescript 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: - The two session principals are **entity delegated AIDs** (§4.2). Each is authenticated by verifying its KEL to its **tenant root AID** against the tenant's declared witness threshold (§4.5), exactly as §5.2 step 2 specifies. This verification is **identical for both trust paths**: in a bilateral session (§5.5) the two AIDs are carried in the agreement; in an open-federation session (§5.8) they are the AIDs attested at first interaction. The KEL-to-root verification, and everything below, is the same in either case — open federation changes the *provenance* of the two AIDs, not how they are authenticated or bound. - Every signed session atom carries an `AfpPartyRef` (§12.2.1): the tenant root AID, the acting entity delegated AID, the **`kel_event_dig`** pinning the key state the signature is bound to, and a **KEL-anchored signature** over the atom's content hash. A signature bound to a superseded KEL event MUST be rejected (the `kel_keystate_stale` condition the profiles surface). - The **session context** `H(initiatorEntityAID, responderEntityAID, sessionId, timestamp)` (§5.2 step 4) is the value bound into every in-session proof's `context` public input — the same value ZKA/Coord and ZKC proofs bind — preventing replay to another session or counterparty. Profiles MUST bind it and MUST NOT define a parallel context. - A ZKC v0.4 presentation additionally satisfies §5.9: the verified credential-subject AID equals the presenting `AfpPartyRef.entity_aid`, the APR signer key resolves through the requesting party's current KEL, and the ZKC proof context incorporates this session context, active presentation profile, and any AFP scope policy. Known-counterparty bilateral flows default to anonymous and may accept scoped→anonymous downgrade when continuity is unnecessary; they never gain linkability without explicit consent. - Cross-tenant work is attributed to the **entity** delegated AID and signed with a key derived from that entity's sub-seed `M'` (§4.3, Appendix A). The session never addresses HyperTalk sessions or internal users (§2.1); the entity is always the principal that crosses the boundary. 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: - is a **specific, pre-agreed AID** named in the agreement (e.g. a multi-site trial's coordinating center, a consortium secretariat), never selected at dispute time, never discovered from a marketplace, never TVF-ranked; - receives the evidence the two parties already exchanged, over its own bilateral sessions with each; - issues a signed decision — but **cannot force a commitment**. Even an arbiter's decision requires *both* parties' counter-signatures on the resulting CommitmentAtom (or the session aborts). The arbiter's authority is exactly and only what the two parties granted it in writing in the agreement. 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: ```typescript 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: - **Both signatures are REQUIRED.** A CommitmentAtom with one signature is not a commitment. No operator or arbiter signature substitutes for a party signature (§12.5). - **Chaining.** `previous_commitment_ref` + a per-AID-pair monotonic `sequence_number` form a signed, ordered, replay-resistant history between the two AIDs. A gap or non-monotonic sequence is a detectable error (the profiles' `sequence_mismatch`). - **Outcome opacity.** The atom commits to an `outcome_root` (a Poseidon2 commitment), not to the cleartext outcome — minimal disclosure (§12.8, Safeguard 2). What the root commits to is profile-defined. 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: ```typescript 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 } ``` - **Ethereum L2 (Arbitrum / Base / Optimism) via APL is the mandatory baseline** — a conforming deployment that anchors MUST support at least one Ethereum L2 through APL. ZKA-Aztec is v5/AZUP-2 migrated and sandbox-accepted in ZKA.dev, but Aztec, Namada, and Penumbra remain non-baseline anchor targets for AFP profiles until an APL deployment explicitly wires the rail and claims it. - **Only hashes are anchored** — the commitment hash, outcome root, hashed party AIDs, and sequence number. Never atom content. The anchor is a tamper-evident external timestamp, nothing more. - **No-anchor is conformant** and is the default for low-stakes state. 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. ```typescript 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: - **has** its own **atom kinds** and its own **exchange state machine** (PRP: SessionAtom/ComparisonAtom/…/CommitmentAtom over Compare→Localize→Evidence→Resolve→Commit; VXP: IntentAtom/…/SettlementAtom over Intent→Negotiation→Commitment→Fulfillment→Settlement); - **reuses, and MUST NOT re-specify**, the core's: - bilateral session establishment, lifecycle, and timeout (§12.2), - identity binding (§12.3), - NATS namespace, under `afp.{tenant}.{profile}.*` (§12.4), - bilateral dispute / optional-arbiter model (§12.5), - commitment + optional-anchor primitive (§12.6), - session-envelope bundle wire format (§12.7), - Freedom-Safeguards application (§12.8); - runs inside a bilateral session whose `profile` field (§12.2.1) names it, between two entities whose federation agreement (§5.5) carries the profile's policy block. 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**: ```typescript 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: - runs every interaction inside a bilateral session (§12.1) whose `profile` field names it; - carries the core session, identity, commitment, and envelope fields (§12.2–§12.7) on its atoms, adding only profile-specific fields; - addresses its subjects under `afp.{tenant}.{profile}.*` (§12.4) and nowhere else; - resolves disputes under the §12.5 model (bilateral-only primary; at most a pre-agreed arbiter; no operator mediation; no forced outcome); - inherits the §12.8 Freedom-Safeguards application and weakens no safeguard; - registers a profile descriptor (§13.2) whose atom kinds do not collide. 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: ```typescript 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): - It carries **no work descriptor and no open call for work** — only a tenant's *consent to be approached for an introduction*. §5.1 bans a *work*-discovery feed; a willingness directory is a different object and is permitted. - It reveals which tenants are **open**, never which tenants have **paired**. First contact is routed over the §8 mixnet, so the actual tenant pairing remains OB-2-private. The directory is no more graph-revealing than §8.7's published list of mix nodes. - Publishing is OPTIONAL. A non-publishing instance remains reachable by an out-of-band-provided tenant AID; the directory is a convenience for open instances, not a precondition for the handshake. 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: ```typescript { 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: ```typescript 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. - **PROPOSED.** The initiator opens the session to the responder tenant AID (resolved via §14.1 or out-of-band) carrying an `afp:introduction-request:v1`: the credential classes it offers, the classes it requires, the explicit v2 `attestation_window`, and a proposed envelope (assets, limits, collateral, rotation policy, expiries). - **CREDENTIALING.** Each side presents an `afp:credential-presentation:v1` — ZKA v0.9.0-draft `zka:bundle:v1` (§12.7) wrapping **atomic**, scoped ZKC v0.4 `zkc:proof:v2` proofs (jurisdiction `zkc:proof:jurisdiction:v2`, KYC-where-required `zkc:proof:kyc:v2`, instance-attestation `afp.telemetry.instance_attestation.v2`). The APR requests `zkc:presentation:scoped:v1` with `scopePolicy = protocol:afp:` and the accepted instance-attestation root classes and root authorities; the proof satisfies the AID/APR/session binding of §5.9 and the selected §6.3.1 branch. Anonymous downgrade is forbidden because these presentations become the credential basis of the provisional agreement. Linkable v1 is permitted only after explicit holder consent. Each side verifies the other against *its own* policy. Failure only declines the interaction; it never gates tenant state. - **NEGOTIATING.** The two sides converge on a scoped envelope — the intersection of offered/required credentials plus a mutually acceptable {permitted assets, per-interaction limit, cumulative-outstanding limit, collateral requirement, rotation policy, credential validity expiries, agreement expiry}. Either side MAY counter; either side MAY abort unconditionally (Safeguard 5). - **BONDING.** Collateral is posted **against exposure**, which may be asymmetric. Whichever side(s) the negotiated envelope extends credit to — the side that could be left owing if a non-atomic settlement fails — posts the required bond into the pre-agreed escrow (the §12.5 2-of-3 / arbiter-co-signed escrow). A symmetric envelope bonds both sides; an asymmetric one bonds only the credit-taking side. A trustless-ZKA-direct-only envelope skips bonding entirely: settlement is atomic, so neither side is exposed and no credit is extended. - **ACTIVE.** Both parties sign an `afp:provisional-agreement:v1` with the §12.6.1 bilateral, KEL-anchored signature machinery. The provisional agreement is now a valid, narrowly-scoped §5.5 federation agreement carrying an `afp:introduction-policy:v1` block. Subsequent value interactions run as ordinary work (§5) / VXP sessions *scoped by this agreement*, each bounded by the envelope and the posted bond. 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 ```typescript 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) trustless ZKA-direct** — settled atomically on the ZKA rail with no credit extended (the safe default), or - **(b) bonded** — outstanding exposure MUST NOT exceed the collateral posted in the pre-agreed escrow. 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 - **OB-2 (§8).** The introduction handshake and every subsequent interaction are routed through the §8 mixnet. The willingness directory (§14.1) reveals *openness*, never *pairing*; the coordination graph is never operator-visible. - **APL channel policy block.** The envelope's {permitted assets, limits, collateral} populate the channel policy block under which bonded and graduated channels run (§14.5). - **Freedom Safeguards (§12.8).** The introduction profile inherits all six by running over the core and weakens none: - **Predicate Pluralism (1)** — AFP privileges no credential issuer or set; the required credentials are each tenant's bilateral choice recorded in the agreement. - **Minimal Disclosure (2)** — credentials are atomic ZKC proofs, one predicate per proof. - **No Revocability (3)** — bond slashing is bounded to pre-escrowed funds with the pre-agreed arbiter co-signature (§12.5); it never touches tenant state or a ZKA note. - **Open-Source Predicates (4)** — the credential and reputation circuits MUST be open-source, versioned, and published with a public review period (ZKC §1.4). - **Agent Exit Rights (5)** — decline, abort, and terminate are all unconditional and penalty-free. - **Verifier Accountability (6)** — every credential or reputation-proof request is authenticated under the requester's stable identity and leaves the holder a portable receipt (§5.8); no anonymous proof demand is accepted. ### 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: ```typescript { 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: ```typescript 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 ```typescript 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: ```text 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: ```text 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: ```text 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: ```text 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)`: ```text 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 ```json {"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: ```text 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 ```typescript 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 `pending` → `safe` → `finalized`. 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: - registers exactly the descriptor and atom kinds in §15.1; - accepts and emits only the canonical encodings and deterministic arithmetic of §15.2; - derives the exact §15.3 hashes and the §15.4 fold — reproducing, byte- identically, every vector of the published `docs/pay_interaction_context_kat_v1.json` artifact and the inline §15.4 end-to-end vector — and requires two KEL-current signatures bound to the live session; - atomically prevents quote-ID and nonce replay without consuming replay state for a rejected quote; - dispatches only the configured Base/WETH immutable single-asset pool under the hard-S3 risk profile and preserves the signed amount and context; - matches receipt evidence exactly and enforces monotonic finality; and - makes every failure interaction-local while preserving unconditional ZKA spend, withdrawal, and exit. 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):** - the master seed (§A.2); - the hash function and domain-separation scheme (§A.3); - the KDC domain-tag registry (§A.4); - the per-entity HKDF sub-derivation (§A.5); - the key-derivation functions producing the spending, viewing, proof, and credential-binding keys, the address, and the credential commitment (§A.6). **Out of scope (owned by the referencing specifications):** - ZKA note structure, commitments, nullifier *semantics*, Merkle trees, and the `zka/nullifier` operational tag (ZKA §2); - ZKC credential structure, issuance, revocation, and accumulators (ZKC §2–§4); - ZKM mandate structure, issuance, consumption, delegation, revocation, and its mandate-specific `zkm/*` operational tags (ZKM §2–§7); - AFP KERI machinery — AIDs, KELs, witnesses, delegated inception (AFP §4); - key *rotation and recovery* mechanics, including on-chain re-binding of artifacts after a seed rotation (ZKA §5.7). KDC defines how keys are derived from a seed; it does not define how a rotation re-binds artifacts derived from a prior seed. 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: - a **standalone master seed**, used directly as the input to §A.6 (e.g. a standalone ZKA deployment with no AFP entity hierarchy); or - a **per-entity sub-seed** `M'`, derived from a tenant master seed `M` via §A.5 and then used as the §A.6 input. Under AFP entity identity (AFP §4.3), the §A.6 input is always a per-entity sub-seed. 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: ```text 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: ```text 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: ```text 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: - MUST derive its field value with §A.3 `deriveFieldTag`, unless it is a fixed-byte fold tag under the carve-out below; - MUST NOT collide with any KDC derivation tag or any published operational tag in the ZK protocol family; - is owned by the defining specification, not by KDC. **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) | +-----------------------+ ``` ```typescript 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 *used* — `sk` 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**: - implements the exact Poseidon2 permutation, constants digest, raw sponge `P`, tagged wrapper `H`, and all KATs of §A.3; - implements exactly the domain-tag registry of §A.4 for derivation, and honors the tag-collision rule; - implements the HKDF-SHA256 sub-derivation of §A.5; - implements `kdcDeriveKeys` per §A.6, preserving the two normative derivation facts; - reproduces every raw and KDC case in `docs/kdc_v1_0_2_conformance_vectors.json` exactly; - reproduces every `field_hex` in `docs/kdc_v1_0_2_domain_registry.json` with `deriveFieldTag` and rejects any cross-family collision. **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.*