The federation profile: the 23-member TaskEnvelope, the
lossy-aware A2A/MCP state codecs, and the carrier placement laws — the
executable half of the field-by-field mapping published as
federation_mapping/0 data.
The envelope is a closed world: one wire member per logical field of the
protocol's federation seam, decoded through the shared Registry engine
under this module's field table. Transport clients and wire I/O stay with
host adapters; the carriers built here are pure JSON shapes — the
Task-level placement on each transport (Task.metadata[a2a_uri] on A2A,
_meta[namespace] on MCP; the _meta key grammar forbids a URI key, so
the two transports key the same JCS object differently).
Placement verdicts (live-re-derived against the pinned sources; see
docs/federation-mapping.md): task_identity/recovery_handle map to the
task id on both transports (a divergent handle is unmappable —
:federation_mapping_conflict), the observed state maps to
status.state, and everything else rides the extension body. A Task
always carries a status, so an envelope with no observed state has no
Task image — message-context payloads are host-adapter territory (rows
3/7 document the Message-level homes).
Lossy-state rules: A2A TASK_STATE_UNSPECIFIED denies at intake;
to_mcp denies :rejected (terminal with no MCP counterpart) and
:submitted/:auth_required (no MCP counterpart) with
:federation_state_unmappable — never a silent degrade. Cancellation is
a request, never a terminal receipt: no codec path synthesizes a terminal
member from a cancel request or acknowledgment.
This is a NEW surface, so failures are %Error{} records from day one
("the only failure shape"; the older decode
surfaces' bare atoms are the recorded migration).
Federation receipts are evidence records — verification never authorizes anything.
Federation receipts are evidence records — verification never authorizes anything.
Summary
Functions
The A2A-side carrier key: the registry entry's a2a_uri (A2A's own URI-keyed metadata convention).
The canonical bytes of the whole envelope (the raw Canonicalization
result, matching Blueprint.canonical_bytes/1 — the encode-error shape
is that layer's to report).
Decode and fully verify envelope bytes: canonical verify → registry
validation → the federation portability pass. Total and never-raising;
failures are %Error{} records.
The envelope's closed world: the 23 wire member names, table order.
Rebuild the envelope from an A2A Task carrier: the native homes supply
task_identity/recovery_handle/the observed state, the body supplies
the rest, and a native member appearing in the body denies
:federation_mapping_conflict (a double placement exists only for
tampering to exploit).
An A2A TaskState spelling into the logical state. The nine proto
values map 8-up; TASK_STATE_UNSPECIFIED denies
:federation_state_unmappable (never a guess), and an unknown spelling
denies :invalid_constraint.
Rebuild the envelope from an MCP Task carrier (the upward state mapping; cancelled folds to :canceled).
An MCP task status into the logical state (all five map upward; cancelled folds to :canceled).
Validate an already-decoded tagged value (registry walk + portability
pass; no canonicality — there are no bytes). The codec reconstruction
path; values from the wire go through decode/2.
The 23-row field-by-field A2A/MCP Tasks mapping as data (the
published table in docs/federation-mapping.md mirrors these rows).
The MCP-side carrier key: the registry namespace (the _meta key grammar forbids a URI key).
The envelope's members as a map (wire names → tagged values).
The Terminal Commitment: the domain-separated digest over exactly the seven named components — task identity, terminal state, result digest, result classification, compatibility reference, authority-proof references, checkpoint-history commitment. Two receipts for one task identity diverge in commitment iff they diverge in any component, which is why equivocation needs no separate code.
The Task-level A2A carrier: id, status.state, and the extension body
under metadata[a2a_carrier_key/0]. Native members never ride the body.
Requires an observed state (a Task always has one) and
recovery_handle == task_identity (the task id IS the handle on both
transports — a divergent handle is unmappable).
The logical state into its A2A spelling (all eight map; there is no logical UNSPECIFIED).
The Task-level MCP carrier: taskId, status, and the extension body
under _meta[mcp_carrier_key/0] (the flat Result & Task merge the
CreateTaskResult shape defines). Requires an observed state; the lossy
states deny before placement.
The logical state into its MCP spelling — the lossy edge: :rejected
(terminal with no MCP counterpart) and :submitted/:auth_required (no
counterpart) deny :federation_state_unmappable rather than silently
degrading.
Verify a terminal receipt against the receiving context. Facts, never a decision — correlation grants nothing, so the success value carries the task identity and terminal commitment and nothing else.
Types
@type t() :: %AgentBlueprintProtocol.Federation{ value: AgentBlueprintProtocol.Json.value() }
Functions
@spec a2a_carrier_key() :: binary()
The A2A-side carrier key: the registry entry's a2a_uri (A2A's own URI-keyed metadata convention).
The canonical bytes of the whole envelope (the raw Canonicalization
result, matching Blueprint.canonical_bytes/1 — the encode-error shape
is that layer's to report).
@spec decode(binary(), AgentBlueprintProtocol.Bounds.t() | map()) :: {:ok, t()} | {:error, AgentBlueprintProtocol.Error.t()}
Decode and fully verify envelope bytes: canonical verify → registry
validation → the federation portability pass. Total and never-raising;
failures are %Error{} records.
@spec envelope_members() :: [binary()]
The envelope's closed world: the 23 wire member names, table order.
@spec from_a2a_carrier(AgentBlueprintProtocol.Json.value()) :: {:ok, t()} | {:error, AgentBlueprintProtocol.Error.t()}
Rebuild the envelope from an A2A Task carrier: the native homes supply
task_identity/recovery_handle/the observed state, the body supplies
the rest, and a native member appearing in the body denies
:federation_mapping_conflict (a double placement exists only for
tampering to exploit).
@spec from_a2a_state(binary()) :: {:ok, atom()} | {:error, AgentBlueprintProtocol.Error.t()}
An A2A TaskState spelling into the logical state. The nine proto
values map 8-up; TASK_STATE_UNSPECIFIED denies
:federation_state_unmappable (never a guess), and an unknown spelling
denies :invalid_constraint.
@spec from_mcp_carrier(AgentBlueprintProtocol.Json.value()) :: {:ok, t()} | {:error, AgentBlueprintProtocol.Error.t()}
Rebuild the envelope from an MCP Task carrier (the upward state mapping; cancelled folds to :canceled).
@spec from_mcp_state(binary()) :: {:ok, atom()} | {:error, AgentBlueprintProtocol.Error.t()}
An MCP task status into the logical state (all five map upward; cancelled folds to :canceled).
@spec from_value(AgentBlueprintProtocol.Json.value()) :: {:ok, t()} | {:error, AgentBlueprintProtocol.Error.t()}
Validate an already-decoded tagged value (registry walk + portability
pass; no canonicality — there are no bytes). The codec reconstruction
path; values from the wire go through decode/2.
@spec mapping() :: [AgentBlueprintProtocol.Federation.Mapping.Row.t()]
The 23-row field-by-field A2A/MCP Tasks mapping as data (the
published table in docs/federation-mapping.md mirrors these rows).
@spec mcp_carrier_key() :: binary()
The MCP-side carrier key: the registry namespace (the _meta key grammar forbids a URI key).
@spec member_map(t()) :: %{optional(binary()) => AgentBlueprintProtocol.Json.value()}
The envelope's members as a map (wire names → tagged values).
@spec terminal_commitment(t()) :: {:ok, AgentBlueprintProtocol.Digest.t()} | {:error, AgentBlueprintProtocol.Error.t()}
The Terminal Commitment: the domain-separated digest over exactly the seven named components — task identity, terminal state, result digest, result classification, compatibility reference, authority-proof references, checkpoint-history commitment. Two receipts for one task identity diverge in commitment iff they diverge in any component, which is why equivocation needs no separate code.
@spec to_a2a_carrier(t()) :: {:ok, AgentBlueprintProtocol.Json.value()} | {:error, AgentBlueprintProtocol.Error.t()}
The Task-level A2A carrier: id, status.state, and the extension body
under metadata[a2a_carrier_key/0]. Native members never ride the body.
Requires an observed state (a Task always has one) and
recovery_handle == task_identity (the task id IS the handle on both
transports — a divergent handle is unmappable).
@spec to_a2a_state(atom()) :: {:ok, binary()} | {:error, AgentBlueprintProtocol.Error.t()}
The logical state into its A2A spelling (all eight map; there is no logical UNSPECIFIED).
@spec to_mcp_carrier(t()) :: {:ok, AgentBlueprintProtocol.Json.value()} | {:error, AgentBlueprintProtocol.Error.t()}
The Task-level MCP carrier: taskId, status, and the extension body
under _meta[mcp_carrier_key/0] (the flat Result & Task merge the
CreateTaskResult shape defines). Requires an observed state; the lossy
states deny before placement.
@spec to_mcp_state(atom()) :: {:ok, binary()} | {:error, AgentBlueprintProtocol.Error.t()}
The logical state into its MCP spelling — the lossy edge: :rejected
(terminal with no MCP counterpart) and :submitted/:auth_required (no
counterpart) deny :federation_state_unmappable rather than silently
degrading.
@spec verify_commitment(t(), AgentBlueprintProtocol.Federation.Context.t()) :: {:ok, AgentBlueprintProtocol.Federation.Context.receipt_fact()} | {:error, AgentBlueprintProtocol.Error.t()}
Verify a terminal receipt against the receiving context. Facts, never a decision — correlation grants nothing, so the success value carries the task identity and terminal commitment and nothing else.
The numbered steps (design note §4): terminal members present → JWS
verify → the BINDING step (the signed content_digest must name THIS
envelope's covered bytes — an honestly-signed statement naming a
different digest is evidence over THAT digest, not this envelope) →
terminal-commitment recompute → issuer/subject/audience comparison →
conflict against prior receipts for the same task identity.