# Broker Profile v1 — Brokered Authority Token Exchange # KNO Schema Version: 0.1.0 # Status: Draft # Last Updated: 2026-08-24 # # The production wire contract for brokered authority: how a caller # exchanges a subject credential for a short-lived, audience-bound, # sender-constrained token bound to a one-shot, content-addressed # intent. Ratified by the 2026-08-24 Authorization Prior-Art # Reconciliation council (Q3/D4 — RFC 8693 token exchange, hardened; # 3-2 vote + consensus round) and specified here per pspace#3819 # Phase 5. Implementation and deployment are tracked in # pfi/pfi-infra#76 D4 — this spec owns the CONTRACT and its vectors, # never the build. # # WHY TOKEN EXCHANGE (not AAuth, not bare RAR): the council reviewed # AAuth (draft-hardt-oauth-aauth-protocol) and deferred every wire # mode to Tier 3 PREPARE behind tripwires — no production endpoint, # credential, or manifest advertisement. RFC 8693 is the externally # audited exchange grammar with mature implementations; RAR (RFC 9396) # rides it as TRANSPORT ONLY, carrying resource-declared generated # operation types (RFC-020 v0.2.0: RAR is transport, not authority). # # WHY CONTENT-ADDRESSED ONE-SHOT INTENTS: a token that merely names a # scope authorizes a CLASS of actions for its lifetime; an intent # binds ONE normalized operation by hash, is claimed atomically, and # is never executed twice. The authorization evidence is the platform # derivation (both RFC-020 gates over the exact operation), never a # client-supplied digest or a raw frame trace. # # CANONICALIZATION PRECEDENT: the hash pipeline (I-JSON rejection at # the parse boundary, schema-driven semantic normalization, RFC 8785 # JCS, domain-separated SHA-256) follows the estate's KCF v1 shape # (specs/kcf-spec.kno) with its own domain strings and its own # semantic-normalization stage — the two profiles version # independently and never share digests. # # ============================================================================= # @kno:manifest # ============================================================================= $schema: kno@0.0.9 id: 01M0T8YEZM3WTKACHXR4BSNNE8 slug: broker-profile-v1 type: spec version: 0.1.0 title: "Broker Profile v1 — Brokered Authority Token Exchange" purpose: | Define the wire contract a conforming authority broker exposes and a conforming execution service enforces: RFC 8693 token exchange with mandatory sender constraint, RFC 9396 `authorization_details` restricted to generated operation types, both RFC-020 authorization gates run before intent creation, and a one-shot content-addressed intent whose operation and binding are hashed under this profile's canonicalization. **The pipeline:** ``` request operation ──(§3.1 I-JSON parse, fail-shut)──▶ raw value ──(§3.2 semantic normalization)──▶ normalized_operation ──(§3.3 JCS / RFC 8785)──▶ canonical bytes ──(§4 domain-separated SHA-256)──▶ operation_hash binding_document ──(same §3.3 + §4 pipeline)──▶ intent_binding_hash ``` Repeating the same operation produces the same `operation_hash` and a DIFFERENT `intent_binding_hash` (the binding carries the unique intent id and expiry) — the invariant the vectors pin. status: draft created: "2026-08-24" authors: - "Possibility Contributors" provenance: origin: id: 01M0T8YEZM3WTKACHXR4BSNNE8 timestamp: "2026-08-24T15:40:00Z" emerged_from: - kno://research/council-runs/2026-08-24-auth-prior-art-reconciliation - kno://content/rfcs/020-oauth-agent-auth - kno://content/rfcs/022-a2a-adapter-contract - kno://specs/kcf-spec tool: ai-assisted-authoring issue: "forge#3819" taxonomy: topics: - authorization - token-exchange - canonicalization - broker - integrity keywords: - rfc-8693 - rfc-9396 - rfc-9449 - dpop - mtls - jcs - i-json - intent - content-addressed relationships: conforms_to: - xri: "kno://specs/kno-spec" reason: "RFC-001 defines kno@0.0.9 schema" depends_on: - xri: "pspace://rfc:rfc-020-oauth-agent-auth" reason: "The broker runs RFC-020 v0.2.0's conjunctive gates (protocol AND frames) before creating any intent; denial codes, decision assertions, freshness leases, and the custody invariant are defined there and cross-referenced, never redefined" related_to: - xri: "pspace://rfc:rfc-022-a2a-adapter-contract" reason: "The chartered RFC-022 next-major dispatch-credential profile consumes this exchange contract for privileged adapters" - xri: "kno://specs/kcf-spec" reason: "Canonicalization sibling — same I-JSON + JCS + domain-separated SHA-256 shape, independent domain strings and normalization stage; digests are never interchangeable" # Public reference knowledge: adopters implementing a conforming broker # or execution service need the contract and its vectors. visibility: public quality: completeness: 0.7 review_status: draft _history: retention: full format: snapshot versions: - version: "0.1.0" date: "2026-08-24" author: "Possibility Contributors" summary: "Initial draft from the council ratification unit (pspace#3819 Phase 5): RFC 8693 exchange contract with mandatory sender constraint, generated-vocabulary-only authorization_details, dual-gate validation, one-shot content-addressed intents (I-JSON fail-shut parse, schema-driven semantic normalization, RFC 8785 JCS, domain-separated SHA-256), intent lifecycle with atomic claim + custody execution, replay tracking, and the cross-language vector suite (specs/broker-profile-v1-vectors/)." # ============================================================================= # NORMATIVE CONTENT # ============================================================================= content: | ## 1. Definitions and profile label - **Broker Profile v1** — this profile. Versioned and hash-pinned: consumers reference it as `pfi-broker/1` with this spec entity's own content hash. Any change to the rules below is a new profile version; published profiles are immutable. - **Broker** — the token-exchange endpoint (§2). Holds no standing substrate credential reachable from inbound traffic (RFC-020 custody invariant, cross-referenced §5.6). - **Execution service** — the endpoint that consumes an intent (§5). - **Generated operation type** — an RFC 9396 `authorization_details` type emitted by the adopter's generated vocabulary profile (RFC-020 § One generated vocabulary profile). Only generated, recognized types exist on this wire; anything else fails shut. - **Hash encoding** — both profile hashes are unpadded base64url (RFC 4648 §5, no `=`) over raw SHA-256 output. - **Domain strings** — `pfi-broker-v1:operation\0` and `pfi-broker-v1:intent\0` (`\0` = one NUL byte, U+0000), prepended as UTF-8 before the JCS bytes. Domains never mix: an operation digest can never collide into a binding digest. ## 2. Token-exchange wire contract (RFC 8693) ### 2.1 Request A conforming broker accepts `POST` to its token endpoint with `grant_type=urn:ietf:params:oauth:grant-type:token-exchange` and: | Member | Requirement | |--------|-------------| | `subject_token` + `subject_token_type` | REQUIRED — the credential of the subject the requested authority is exercised for | | `actor_token` + `actor_token_type` | REQUIRED whenever the caller acts for another subject; FORBIDDEN otherwise | | `resource` | REQUIRED — exactly ONE privileged resource; a request naming zero or multiple privileged resources is rejected | | `authorization_details` | REQUIRED — RFC 9396 array containing ONLY generated, recognized operation types under the pinned vocabulary profile | RFC 9396 `locations` MUST NOT be used as adopter-defined frame scope (RFC-020 rule, restated here because this is the wire it rides). ### 2.2 Sender constraint Every exchange requires sender proof: an RFC 9449 DPoP proof, or — where the deployment explicitly configures it — RFC 8705 mutual-TLS client-certificate binding. One of the two is MANDATORY; a bare request is rejected. The proof's key thumbprint becomes `sender_key_thumbprint` in the intent binding (§4.2), and the issued token is constrained to that key (`cnf`). ### 2.3 Validation before intent creation The broker validates, in an order that never leaks which later check would have failed: 1. credential validity and the COMPLETE grant chain; 2. actor/subject semantics (the actor is entitled to act for the subject under a current, active grant); 3. active status of every chain member; 4. audience (the single privileged `resource` maps to a declared audience); 5. vocabulary profile pin (the request's operation types belong to the pinned profile hash); 6. sender proof (§2.2). It then normalizes the requested operation (§3), runs BOTH RFC-020 gates — `ALLOW = protocol_gate AND frames_gate` — over the normalized operation, and only on a dual allow creates the intent (§5.1). Any deny surfaces an RFC-020 denial code; the broker never creates an intent for a partially validated request. ### 2.4 Response On success the broker returns: - a short-lived, audience-bound, sender-constrained access token whose lifetime never exceeds the intent expiry; - an opaque `intent_handle` response member (top-level JSON member beside `access_token`), REQUIRED at execution and meaningless anywhere else — it encodes nothing and derives from nothing the client supplied. Token and intent both carry `min_policy_revision` — a floor, never a substitute for execution-time status, receipt, and freshness checks (RFC-020 § Freshness). ## 3. Normative operation canonicalization ### 3.1 Raw parse (fail-shut, before object construction) The operation object inside `authorization_details` is parsed from raw JSON text with these rejections raised BEFORE any object exists (rejection codes in §6): - duplicate member names anywhere in the document; - non-I-JSON values: numbers outside the exactly representable IEEE-754 domain, integers beyond ±(2^53 − 1), `NaN`/`Infinity`; - malformed Unicode (lone surrogates, ill-formed escapes) in any string; - unknown fields — operation types validate CLOSED against their generated schema. ### 3.2 Semantic normalization (schema-driven) The pinned generated schema then normalizes the parsed value: - **Default materialization** — every schema-declared default is materialized into the normalized form (an absent-with-default and an explicitly supplied default value normalize identically). - **Canonical URI/identifier handling where declared** — a field the schema declares canonical-URI is normalized per its declared rule (at minimum: lowercase scheme and host); fields without a declared rule are untouched. - **Set-valued paths** — each element is normalized element-wise, the set is sorted by the canonical byte representation of its elements (the UTF-8 bytes of each element's JCS serialization), and exact duplicates are removed. - **Ordered arrays** — order is PRESERVED; only schema-declared sets sort. - **No implied Unicode normalization** — strings are never NFC/NFD normalized unless the schema explicitly requires it for a field. ### 3.3 Canonical serialization RFC 8785 (JCS) is applied to the semantically normalized value — member sorting, string escaping, and number serialization exactly per that RFC. Semantic normalization ALWAYS precedes JCS; an implementation that JCS-serializes the raw parse is non-conformant even when the bytes coincide. ## 4. Hashes and the binding document ### 4.1 Definitions ```text operation_hash = base64url(SHA-256(UTF8("pfi-broker-v1:operation\x00") || JCS(normalized_operation))) intent_binding_hash = base64url(SHA-256(UTF8("pfi-broker-v1:intent\x00") || JCS(binding_document))) ``` ### 4.2 `binding_document` A JSON object with EXACTLY these members (closed set — §3.1 unknown- field rejection applies to it too): | Member | Content | |--------|---------| | `operation_hash` | §4.1 value for the normalized operation | | `intent_id` | unique intent identifier (the idempotency key, §5.4) | | `actor` | acting principal identifier | | `subject` | subject principal identifier | | `audience` | the single declared audience (§2.1 `resource` mapping) | | `grant_xri` | exact origin grant XRI | | `grant_generation` | integer origin generation | | `profile_hash` | pinned vocabulary profile hash | | `exp` | integer expiry (seconds since epoch) | | `sender_key_thumbprint` | §2.2 proof-key thumbprint | ### 4.3 Client digests are transport, never evidence A digest supplied by the client — in the operation, a header, or anywhere else — is IGNORED as authorization evidence. The broker computes both hashes itself from the bytes it validated; the execution service recomputes `operation_hash` from the body it received (§5.2). Nothing downstream ever trusts a hash it did not derive. ## 5. Intent lifecycle and execution ### 5.1 Storage Each approved intent is stored with: actor, subject, audience, grant generation, profile hash, sender-key thumbprint, expiry, `min_policy_revision`, `operation_hash`, and `intent_binding_hash`. State begins at `approved`. ### 5.2 Execution-time validation The execution service validates, before consuming anything: 1. the token (audience, lifetime, sender binding); 2. a FRESH sender proof (replay-tracked, §5.5); 3. the opaque `intent_handle` resolves to a stored intent; 4. the request body, run through §3, reproduces the stored `operation_hash` — a mismatch is rejected BEFORE the intent is consumed or claimed (`intent_mismatch`). It then revalidates the grant chain, all contributing origins, projection receipts, policy revision, status leases, and snapshot freshness at execution time (RFC-020 `deriveAccessWithEvidence` semantics — the revision floor never substitutes for these). ### 5.3 Atomic claim On success the intent transitions `approved → claimed` ATOMICALLY, appending a signed immutable work item in the same transaction. A lost race observes `claimed` and stops (`intent_replayed`). ### 5.4 Custody execution Execution happens through the custody component (RFC-020 custody invariant) using `intent_id` as the idempotency key. A claimed intent is NEVER executed twice; after an ambiguous failure the intent exposes a queryable terminal status rather than becoming re-claimable. Long-running work moves to server-side task records after the dispatch credential is consumed. ### 5.5 Replay tracking DPoP proofs (by `jti`) and intent handles are replay-tracked for at least their validity window plus maximum clock skew. A repeated proof or handle is rejected even when byte-identical. ### 5.6 Evidence discipline Raw frame traces and client-supplied digests are never accepted as authorization evidence. When a remote party legitimately needs decision evidence, the RFC-020 signed decision-assertion rules apply UNCHANGED — this profile defines no alternate remote-evidence mechanism. ## 6. Fail-shut rejection list Parse-boundary rejections (before object construction, §3.1): | # | Rejection | |---|-----------| | BR1 | Duplicate member names anywhere in the document | | BR2 | Malformed Unicode (lone surrogates, ill-formed escapes) in any string | | BR3 | Non-I-JSON numbers (NaN/Infinity, precision loss, integers beyond ±(2^53 − 1)) | | BR4 | Unknown fields under the closed generated schema (binding documents included) | | BR5 | Unrecognized or non-generated operation type; profile-hash mismatch | Exchange and execution rejections surface RFC-020 denial codes (`outside_grant_envelope`, `frame_denied`, `grant_status_stale`, `snapshot_stale`, `projection_missing`, `projection_ineligible`, `intent_mismatch`, `intent_replayed`) — defined there, not here. ## 7. Test vectors Cross-language vectors are a REQUIRED companion artifact (`specs/broker-profile-v1-vectors/`): input octets, normalized JSON, JCS octets, both expected hashes, and expected rejection codes, covering object-key order, escapes, duplicate names, set order and deduplication, ordered arrays, numeric representations and bounds, Unicode edge cases, unknown fields, profile changes, and mutation of every §4.2 binding member — plus the invariant that repeating the same operation reproduces `operation_hash` while `intent_binding_hash` differs. An implementation conforms iff it reproduces every accept vector's hashes and rejects every rejection vector at the declared boundary. ## 8. What Broker Profile v1 does NOT cover - **Implementation and deployment** — the net-authority broker build is pfi/pfi-infra#76 D4 (ADR-0041 §3); this spec may be complete while that build is not. - **AAuth wire modes** — Tier 3 PREPARE/deferred behind tripwires (pspace#3819 Phase 7 register entry); no production endpoint, credential, or manifest advertisement. - **Delegation chains** (RFC-015 re-charter) and **consent interaction profiles** (RFC-016 re-charter) — they consume this contract; they are specified separately. - **The custody topology itself** — the credential-isolation invariant and the ingress/custody-worker reference architecture are RFC-020 normative text; equivalent topologies conform by demonstrating the same isolation there, not here. - **Non-JSON content types** — a separate versioned canonicalization profile is required before any non-JSON operation rides this wire (council guardrail: no JSON-LD/CBOR-LD canonicalization in this profile).