Agent Blueprint Protocol — specification

Copy Markdown View Source

Status: published pre-1.0 protocol (0.x release line). This document is the normative specification of the Agent Blueprint Protocol. The reference implementation and its conformance corpus (digest sha-256:sg6Fo7p8nZpJDzxFn4dXHBWgbGvEvtOk-7t3m7OT7Yo, 94 cases) are certified against this document at every release through the release identity chain — the specification digest, the package version, and the corpus and registry digests are pinned together per release, and the release-candidate check asserts the chain from live state. Where this document and the implementation disagree, one of them is defective and the release does not ship until the chain is reconciled.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. Keywords apply to what a conforming implementation (the Conformance clause) is required to do; prose that describes the reference implementation without stating a requirement is informative.

The protocol is non-authorizing. It validates structure, canonical bytes, bounds, compatibility, and evidence. Protocol validity never grants authority: a consuming host remains responsible for identity, tenancy, live policy, effect ownership, execution, and evidence retention. Every verification result — including a fully green one — is evidence, never a decision.

1. Scope, non-goals, and terminology

Two language-neutral artifacts:

  • Blueprint Core — stable identity, typed ports, logical capability requirements, bounds, evidence commitments, registered extensions.
  • Deployment Manifest — environment-local tool, principal, data, authority, effect, evaluation, and exact-build bindings for one Blueprint release digest.

Deliberately out of scope (host-owned): runtime kernel, live authority and grants, effect admission/replay/recovery, product schemas, transport clients, billing, evaluation truth. The package compiles to an inert library: no supervision tree, no application callback, zero third-party production dependencies.

Terminology

  • Artifact — a Blueprint or Deployment Manifest value together with its exact canonical bytes; the artifact's identity is its content digest — the digest of the artifact's digest-covered members over those canonical bytes (the digest section defines the exact input; the digest member itself, signatures, and attestations are excluded).
  • Host — the consuming runtime. Every non-establishment obligation in this protocol belongs to a host, and a green result never transfers one to the package.
  • Deny — the fail-closed outcome: a typed error code with subject and optional detail. A deny is never a repair and never a silent skip; where this document states a requirement as a deny, the requirement is absolute (MUST-level) and the error code named is the code that enforces it.
  • Quarantine — retain byte-exactly without validating or executing: an unknown optional extension's payload round-trips, is typed as unscanned, and confers no portability claim.
  • Clamp — the effective narrowing of an operational bound, emitted with evidence whenever effective differs from requested; protected bounds deny instead of clamping.
  • Ceiling vs bound — operational bounds are magnitude limits that narrow pointwise; protected ceilings are ordinal-lattice positions (classification, disclosure, effect impact, authority, approval) that never widen and deny narrowing by default (both in the evolution laws). A parse ceiling is neither: it is a tighten-only decoder limit, never an intersection input.
  • Evidence — a structured observation about bytes or structure: per-surface check results, clamp records, and the non-empty not_verified set naming what was NOT established. Evidence is input to a host decision; it is never a decision.
  • Portability — the property that an artifact carries no environment-bound values (secrets, keys, grants, tenant ids, endpoints, engine ids); enforced structurally and honestly limited (the portability section).
  • Posture — the declared stance of a surface: fail-closed (deny), acknowledge-and-record (clamp with evidence, or a typed notice), or opt-in (a host-supplied policy).
  • Authored channel — the :authored_extensions path by which a producer-declared critical extension body, validated against a digest-pinned host schema, is treated as authored content and spared the generic value-shape heuristics (still covered by the artifact's digest).

2. Artifacts and the closed world

Both artifacts MUST decode as JSON objects under the bounded ceilings (depth, members, items, nodes, string bytes, key bytes, number lexemes — the ceiling family). Decoding MUST be fail-closed under the pinned failure precedence: unknown member → missing required → type → constraint → cardinality → nested → cross-field hooks.

The registries are closed worlds — the exact member sets the shipped tables define:

  • Blueprint Core (18 members). Required: blueprint_id, protocol_revision, producer, release_number, capability_requirements, input_ports, output_ports, output_contract, triggers, ceilings, classification_ceiling, effect_intents, evaluation_assertions, extensions, required_core_fields, content_digest. Optional: signatures, attestations.
  • Deployment Manifest (19 members). Required: protocol_revision, blueprint_release, scope_projection, tool_bindings, data_bindings, authority_requirement, effect_owner, signer_custody, eligibility, model_policy, host_bounds, lifecycle, build_identities, evaluation_binding, extensions, required_core_fields, deployment_digest. Optional: signatures, attestations.

Every core member addition MUST be accompanied by a protocol revision increment: an older consumer rejects an unknown member, so the vocabulary can only evolve at a revision boundary. No identifier in the package — module, function, path, corpus, or config — carries a version token; the Hex package version is the sole version number.

A complete, valid Blueprint as it travels on the wire (the conformance corpus's blueprint-decode-valid case, byte-exact — every fenced JSON example in this specification is a corpus case):

json corpus:blueprint-decode-valid {"blueprint_id":"example.demo/echo","capability_requirements":[{"approval_trait":"none","argument_schema":{"type":"object"},"authority_trait":"none","classification_ceiling":"internal","impact_class":"ordinary","operation_family":"example.demo.read_shape","operation_kind":"read","result_schema":{"type":"object"}}],"ceilings":{"max_attempts":3,"max_concurrency":2,"max_cost":{"amount":1000,"currency":"USD"},"max_depth":8,"max_descendants":64,"max_elapsed_ms":60000,"max_fan_out":4,"max_tokens":100000},"classification_ceiling":"internal","content_digest":"sha-256:b1Aw4cU5AbV9k8bdbZkRCsySDHGpTAwB-aQm57Wh7B8","effect_intents":[{"impact_class":"ordinary","logical_operation":"record_summary","operation_kind":"mutation"}],"evaluation_assertions":[{"kind":"output_schema","port":"result","schema":{"type":"object"}}],"extensions":{"critical":{},"optional":{}},"input_ports":[{"classification_ceiling":"internal","name":"request","required":true,"schema":{"type":"object"}}],"output_contract":{"classification_ceiling":"internal","port":"result"},"output_ports":[{"classification_ceiling":"internal","name":"result","required":true,"schema":{"type":"object"}}],"producer":{"created_at":"2026-08-20T00:00:00Z","identity":"example.demo","toolchain":"example.demo.toolchain"},"protocol_revision":1,"release_number":1,"required_core_fields":[],"triggers":["manual"]}

### Member grammar — Blueprint Core (18 members)

Cardinality 1 members are required; 0..1 members are optional. The failure precedence is the pinned closed-world order: unknown member → missing required → type → constraint → cardinality → nested → cross-field hooks. Every sub-member listed inside an object/array type is required unless marked (opt). The machine- readable grammar artifacts (CDDL under spec/grammar/, with the derived JSON Schema beside them) are bound to these tables by the grammar-derivation gate: root member sets and required flags are cross-read against the compiled registries, and the corpus goldens validate under the derived schemas. The derived schemas close the artifact root; sub-object closure and cross-member hooks remain the reference implementation's to enforce.

| Member | Card. | Type | Constraints | |---|---|---|---| | blueprint_id | 1 | string | value: producer-qualified name | | capability_requirements | 1 | array<object{ operation_family:string; argument_schema:custom; result_schema:custom; operation_kind:enum(computation\|mutation\|read); impact_class:enum(authority\|money\|ordinary\|secret); classification_ceiling:enum(confidential\|internal\|public\|restricted); approval_trait:enum(human_required\|none\|separated_human_required); authority_trait:enum(external_authority_required\|local_policy\|none) }> | max 64, unique by operation_family | | ceilings | 1 | object{ max_attempts:integer; max_concurrency:integer; max_depth:integer; max_descendants:integer; max_elapsed_ms:integer; max_fan_out:integer; max_tokens:integer; max_cost:object{ amount:integer; currency:string } } | — | | classification_ceiling | 1 | enum(confidential\|internal\|public\|restricted) | — | | effect_intents | 1 | array<object{ logical_operation:string; operation_kind:enum(computation\|mutation\|read); impact_class:enum(authority\|money\|ordinary\|secret) }> | max 64, unique by logical_operation | | evaluation_assertions | 1 | array<custom> | max 128 | | extensions | 1 | custom | — | | input_ports | 1 | array<object{ name:string; schema:custom; classification_ceiling:enum(confidential\|internal\|public\|restricted); required:boolean }> | max 64, unique by name | | output_contract | 1 | object{ port:string; classification_ceiling:enum(confidential\|internal\|public\|restricted) } | — | | output_ports | 1 | array<object{ name:string; schema:custom; classification_ceiling:enum(confidential\|internal\|public\|restricted); required:boolean }> | max 64, unique by name | | producer | 1 | object{ identity:string; created_at:string; toolchain:string } | — | | protocol_revision | 1 | integer | value: positive integer | | release_number | 1 | integer | value: positive integer | | required_core_fields | 1 | array<string> | unique by value | | triggers | 1 | array<enum(condition\|delegated\|evaluation\|manual\|schedule)> | min 1, unique by value | | signatures | 0..1 | array<custom> | max 16 | | attestations | 0..1 | array<any> | max 16 | | content_digest | 1 | string | value: tagged digest |

### Member grammar — Deployment Manifest (19 members)

Cardinality 1 members are required; 0..1 members are optional. The failure precedence is the pinned closed-world order: unknown member → missing required → type → constraint → cardinality → nested → cross-field hooks. Every sub-member listed inside an object/array type is required unless marked (opt). The machine- readable grammar artifacts (CDDL under spec/grammar/, with the derived JSON Schema beside them) are bound to these tables by the grammar-derivation gate: root member sets and required flags are cross-read against the compiled registries, and the corpus goldens validate under the derived schemas. The derived schemas close the artifact root; sub-object closure and cross-member hooks remain the reference implementation's to enforce.

| Member | Card. | Type | Constraints | |---|---|---|---| | authority_requirement | 1 | object{ adapter_identity:string; profile_identity:string } | — | | blueprint_release | 1 | object{ blueprint_id:string; release_number:integer; content_digest:string } | — | | build_identities | 1 | array<object{ kind:enum(adapter\|build\|extension\|package); name:string; version:string; digest:string }> | max 128, min 1, unique by name | | data_bindings | 1 | array<object{ logical_dataset:string; classification_ceiling:enum(confidential\|internal\|public\|restricted); as_of:object{ mode:enum(none\|required); max_age_ms:custom } }> | max 64, unique by logical_dataset | | effect_owner | 1 | object{ adapter_identity:string; idempotency:object{ key_derivation:enum(host); recovery:enum(authoritative\|none) } } | — | | eligibility | 1 | object{ owner:custom; beneficiary:custom; runtime_principal:custom } | — | | evaluation_binding | 1 | object{ adapter_identity:string; corpus:object{ name:string; digest:string } } | — | | extensions | 1 | custom | — | | host_bounds | 1 | object{ approval_trait:enum(human_required\|none\|separated_human_required); authority_trait:enum(external_authority_required\|local_policy\|none); classification_ceiling:enum(confidential\|internal\|public\|restricted); disclosure_ceiling:enum(detail\|full\|none\|summary); effect_impact_ceiling:enum(authority\|money\|ordinary\|secret); max_attempts:integer; max_concurrency:integer; max_cost:object{ amount:integer; currency:string }; max_depth:integer; max_descendants:integer; max_elapsed_ms:integer; max_fan_out:integer; max_tokens:integer } | — | | lifecycle | 1 | object{ state:enum(active\|draft\|retired); activated_at (opt):string; retired_at (opt):string } | — | | model_policy | 1 | object{ allowed_model_roles:array<string>; max_tokens:integer; max_cost:object{ amount:integer; currency:string } } | — | | protocol_revision | 1 | integer | value: positive integer | | required_core_fields | 1 | array<string> | unique by value | | scope_projection | 1 | object{ adapter_identity:string } | — | | signer_custody | 1 | enum(external_kms\|holder_edge\|host_managed) | — | | tool_bindings | 1 | array<object{ logical_operation:string; adapter_identity:string; descriptor_digest:string; schema_digest:string; attested_at:string }> | max 128, unique by logical_operation | | signatures | 0..1 | array<custom> | max 16 | | attestations | 0..1 | array<any> | max 16 | | deployment_digest | 1 | string | value: tagged digest |

## 3. Bytes

- Encoding: interchange bytes MUST be UTF-8 JSON within the decoder ceilings. Duplicate members MUST deny :duplicate_member (I-JSON); trailing bytes MUST deny :trailing_bytes. - Integer window: a pure-digit lexeme above ±(2^53−1) decodes as a float iff that double's canonical ECMAScript serialization reproduces the lexeme byte-exactly; every other above-bound lexeme MUST deny :number_not_double_expressible. Core fields are integer-typed by schema: a float-tagged window value in a core field MUST deny :invalid_type. - base64url: unpadded, exact alphabet; padded input MUST deny :base64url_padded, non-alphabet input MUST deny :base64url_invalid. - Canonicality: interchange bytes MUST already be canonical — decode → re-encode → byte-compare; non-canonical bytes MUST deny :non_canonical_bytes before any semantic read, and digests MUST be computed only over the exact received bytes.

## 4. Canonicalization

The canonical form MUST be RFC 8785 JSON Canonicalization Scheme:

- No whitespace between tokens (§3.2.1). - Control characters U+0000–U+001F escaped as lowercase \uXXXX except the five short escapes; " and \ escaped; a lone surrogate denies (§3.2.2.2). - Numbers per ECMA-262 7.1.12.1 including the Note 2 enhancement: 1.0 → "1", 1.0e22 → "1e+22", -0.0 → "0", 5.0e-324 → "5e-324", 1.7976931348623157e308 → "1.7976931348623157e+308"; NaN and Infinity deny (§3.2.2.3). - Object members sorted as UTF-16 code units compared as unsigned integers (§3.2.3) — NOT by UTF-8 byte order: for U+FF3A vs U+10000, UTF-16 order puts U+10000 first.

## 5. Digest

digest_bytes = SHA-256( domain_separator || <<0>> || JCS(digest_input) )

digest_input is the artifact object minus content_digest (or deployment_digest), signatures, and attestations — everything else is covered, including extensions and unknown-but-optional extension payloads retained verbatim.

| Purpose | Separator | |---|---| | Blueprint content | agent-blueprint-protocol/blueprint-content | | Deployment content | agent-blueprint-protocol/deployment-content | | Federation envelope | agent-blueprint-protocol/federation-envelope | | Signature input | agent-blueprint-protocol/signature | | Conformance report | agent-blueprint-protocol/conformance-report | | Corpus index | agent-blueprint-protocol/corpus-index |

Separators carry no version token: protocol_revision is itself a covered member. The wire form MUST be the self-identifying tagged string sha-256:<43-char unpadded base64url>, never a bare hex blob; an unknown algorithm tag MUST deny :digest_algorithm_unsupported, a malformed body MUST deny :digest_encoding_invalid. Algorithm succession is therefore a data change, not a format break.

## 6. Signature envelope

Signatures are detached and evidence-only. The package verifies; it MUST NOT sign, MUST NOT accept a private key on any public function, and MUST NOT embed public keys in artifacts (the host supplies trusted keys).

- Format: RFC 7515 compact + RFC 7797 b64=false unencoded detached payload. Protected header: {alg: EdDSA, kid, crit: ["b64"], b64: false}. Ed25519 only; an unknown alg MUST deny. - signed_attributes = {algorithm, content_digest, created_at, key_id, purpose} with purpose one of blueprint | deployment | federation-envelope; the signing input is the signature domain separator, a zero byte, and JCS(signed_attributes). - Because the digest covers protocol_revision, identity members, extensions, and required_core_fields, a signature cannot be lifted onto a different artifact, revision, or purpose; key_id prevents key substitution. A key_id matching no supplied key MUST produce a verified: false check entry with :signature_key_unsupported — the package performs no key discovery, fetch, or trust selection. - Attestations use the identical envelope with a registered kind and a statement_digest. The attestation kind registry is empty by design in this release. - Small-order Ed25519 public keys (identity and order-2 encodings) MUST reject :signature_key_unsupported.

## 7. Evolution and negotiation

protocol_revision is a digest-covered body member. This release is revision 1. A consumer's support MUST declare an explicit revision SET (never a range); a revision outside the set MUST deny :protocol_revision_unsupported — above-max and below-min alike, both fail-closed.

required_core_fields is the producer's declaration of which covered core members a consumer must honor. Three checks, all fail-closed: every entry names a known core member (:required_core_field_unsupported), is digest-covered (:required_core_field_not_digest_covered — an evidence-only member laundered into a requirement is a tamper blind spot), and is in the consumer's supported set.

### Extension criticality

For each namespace → {criticality, payload} in extensions, the outcomes in this table are absolute requirements:

| Registry state | Declared critical | Declared optional | |---|---|---| | unregistered | deny :extension_unknown_critical | retained verbatim, quarantined, never executed | | reserved | deny :extension_unknown_critical | retained verbatim; typed notice | | active, criticality matches | supported | retained and executable | | active, criticality mismatch | deny :extension_criticality_conflict | deny :extension_criticality_conflict | | deprecated | supported + typed notice | supported + typed notice | | retired | deny :extension_retired | retained verbatim; typed notice |

An unknown optional extension MUST round-trip byte-exactly (decode → to_value → encode is a fixed point, property-tested). Lifecycle asymmetry: optional→critical promotion requires a revision increment; demotion does not. A retired namespace MUST never be reused.

## 8. Extension registry

Namespace form: reverse-DNS-plus-path (com.example.commerce/graph), lowercase, one /, length-ceilinged, and not parseable as an absolute URI with a network authority (a namespace can never double as an endpoint). The registry MUST ship as compiled-in data in the package — registry content is a code release; there is no runtime registry file, and drift between shipped code and shipped registry is unrepresentable. The governance-canonical SOURCE is the specification tree's registry document (spec/registry/registry.json), bound to both compiled twins and the corpus index by the registry equality gate. Registered at this release:

| Namespace | Owner | Criticality | State | |---|---|---|---| | com.example.commerce/graph | ExampleCommerce | critical | active | | com.example.commerce/classification-labels | ExampleCommerce | optional | active | | com.example.commerce/rubric-assertion | ExampleCommerce | optional | active | | com.example.platform/estate | ExamplePlatform | optional | deprecated | | com.example.platform/estate-contract | ExamplePlatform | critical | active | | com.example/federation | Agent Blueprint Protocol | critical | active |

com.example.platform/estate-contract is the product-extension registration: the first product-owned critical namespace with an authored schema pin. The document ships as corpus data (priv/conformance/schemas/estate-contract.schema.json); lib/ carries only the digest pin, test-bound to the shipped file. The deprecated com.example.platform/estate placeholder retains its optional bodies with a typed notice — existing artifacts stay verifiable.

Critical-extension bodies validate only against a host-supplied schema whose digest matches the registry's schema_digest; a missing schema denies :extension_schema_unavailable, a mismatched digest denies :extension_schema_digest_mismatch. Entry changes are owner-made, changelog-recorded, and ADR-recorded. The registry's own digest is bound into the corpus index but not into artifact digests: an artifact must remain verifiable against a registry that legitimately gained entries after it was produced; hosts wanting pinning SHOULD use build_identities.

## 9. Bounds algebra

Two bound families, never conflated with parse ceilings (those are tighten-only decoder limits):

- Operational (8 members, all REQUIRED — absent is :missing_ceiling, never infinity): max_attempts, max_concurrency, max_cost, max_depth, max_descendants, max_elapsed_ms, max_fan_out, max_tokens. Pointwise narrowest meet; every narrowing MUST emit clamp evidence. - Protected (5 members: classification_ceiling, disclosure_ceiling, effect_impact_ceiling, authority_trait, approval_trait): ordinal lattices (plus set-monotone markers {pci, phi} on classification). Narrowing a protected bound MUST deny :protected_bound_clamp_denied by default; a host MAY opt into an acknowledge posture, which MUST always record evidence. Obligation families (authority, approval, effect impact) meet at the STRICTEST value; markers are retained regulatory obligations whose effective set is the UNION of sources — dropping one is a widening.

Laws (property-tested): meet is idempotent, commutative, associative; effective bounds MUST NOT widen host policy (not widens?(effective, host) universally); a clamp is emitted iff effective ≠ requested on an operational field. model_policy and data_bindings are not intersection inputs.

## 10. Compatibility and binding

A manifest's blueprint_release MUST bind to exactly one Blueprint content digest (digest equality only — no fuzzy match). build_identities carry exact identities; compatibility verification MUST be identity-exact or deny: a range expression, missing entry, or duplicate denies (:compatibility_identity_inexact, :compatibility_entry_missing, :compatibility_duplicate_entry). Binding verification is a pinned deny-ordered check including attestation freshness (:binding_attestation_stale) and descriptor-digest equality (:binding_descriptor_mismatch — the tool rug-pull case).

## 11. Federation profile

A 23-member task envelope carried in A2A Task.metadata / MCP _meta under the registered com.example/federation extension; the full field-by-field A2A/MCP mapping (3 native, 5 partial, 15 extension members) also ships with the reference implementation's document set. Zero native wire fields; no native transport. State codecs MUST be lossy-aware: A2A REJECTED/AUTH_REQUIRED deny crossing into MCP, UNSPECIFIED is unmapped (:federation_state_unmappable); cancellation MUST remain a request, never a terminal receipt. A Terminal Commitment digests task identity, terminal state, result digest, result classification, compatibility ref, authority-proof refs, and checkpoint-history commitment; ANY divergence between receipts for one task identity MUST deny :federation_terminal_conflict — not just state divergence. Federation.verify_commitment compares issuer/subject/audience against the receiving context (:audience_mismatch). AgentCard signing carries a protobuf field-presence pre-normalization on top of JCS (documented in the mapping so adapters do not inherit silent verification failure). Correlation grants nothing.

### Member grammar — Federation TaskEnvelope (23 members)

The envelope is a closed world: one wire member per logical field, carried as the extension body under the com.example/federation namespace on both transports. Cardinality 1 members are required; 0..1 members are optional (absent — there is no null form). The machine-readable grammar ships as the CDDL artifact under spec/grammar/ with the derived JSON Schema beside it. Cross- member hooks: checkpoint_status MUST NOT coexist with terminal_state (:invalid_constraint), and the terminal hooks bind terminal_state/evidence_receipt presence together. The receipt's terminal_commitment is the domain-separated digest of the seven Terminal-Commitment components.

| Member | Card. | Wire form | |---|---|---| | task_identity | 1 | identifier string | | idempotency_identity | 1 | identifier string | | parent_execution_reference | 0..1 | identifier string | | initiating_subject | 0..1 | identifier string | | blueprint_digest | 1 | tagged digest | | deployment_digest | 1 | tagged digest | | input_commitment | 1 | tagged digest | | result_schema | 1 | tagged digest (result schema identity) | | result_classification_ceiling | 1 | classification enum element | | time_policy | 1 | object{ elapsed_ms: positive integer } | | resource_policy | 1 | object{ attempts, concurrency, tokens, cost: positive integers } | | recovery_handle | 1 | identifier string | | issuer | 1 | identifier string | | subject | 1 | identifier string | | audience | 1 | identifier string | | identity_mapping_evidence | 1 | identity-evidence object (correlation only) | | checkpoint_request | 0..1 | object{ kind: checkpoint enum; request_digest: tagged digest } | | checkpoint_status | 0..1 | checkpoint-status enum | | checkpoint_commitment | 0..1 | tagged digest | | terminal_state | 0..1 | terminal-state enum | | evidence_receipt | 0..1 | object{ result_digest: tagged; checkpoint_history_commitment: tagged; terminal_commitment: tagged; signature: detached JWS envelope } | | compatibility_reference | 1 | array (min 1, unique by name) of object{ name: identifier; identity: nonempty string } | | authority_proof_references | 1 | array of tagged digests |

## 12. Error vocabulary

A closed typed set — %Error{code, subject, detail}. An implementation MUST NOT emit an undeclared code, and every code it declares MUST be reachable (enforced two-directionally by a build gate). The 74 codes of this release, with semantics (when the code is raised, the subject it names, and the host action it demands):

| Code | Raised when | Subject | Host action | |---|---|---|---| | attestation_malformed | an attestation envelope fails its structural parse | attestations | reject the artifact | | audience_mismatch | a federation receipt's issuer/subject/audience does not match the receiving context | federation envelope | reject the receipt | | base64url_invalid | a base64url lexeme carries a non-alphabet character | offending member | reject the artifact | | base64url_padded | a base64url value arrives padded | offending member | reject the artifact | | binding_attestation_stale | a tool-binding attestation is older than the pinned freshness window | tool_bindings | reject the import as stale | | binding_descriptor_mismatch | a bound tool's descriptor digest differs from the observed descriptor | tool_bindings | halt the binding (rug-pull guard) | | binding_incomplete | the binding check set is incomplete where completeness is required | bind surface | halt the import (reconcile denies) | | bound_source_missing | an intersection input names a bound source that is absent | bounds sources | reject the intersection call | | bound_unit_mismatch | two bounds meet with incompatible units | bounds | reject the intersection call | | bound_unknown | an unknown bound name appears | bounds | reject the artifact | | bound_value_invalid | a bound value fails its shape or range check | bounds | reject the artifact | | compatibility_duplicate_entry | build_identities carries a duplicate identity | build_identities | reject the manifest | | compatibility_entry_missing | verification names an identity with no manifest entry | build_identities | deny compatibility | | compatibility_identity_inexact | a build identity is a range or fuzzy form | build_identities | reject the manifest | | corpus_applicability_incomplete | the corpus applicability floor has uncovered cells | corpus index | operator: fix the corpus; never a wire condition | | corpus_case_id_duplicate | two corpus cases share an id | corpus | operator: fix the corpus | | corpus_case_invalid | a corpus case fails its own schema | corpus | operator: fix the corpus | | corpus_count_mismatch | the index case total disagrees with the case set | corpus index | operator: regenerate the index | | corpus_empty | the corpus carries no cases | corpus | operator: fix the corpus | | corpus_file_set_mismatch | the corpus file set differs from the index (both directions) | corpus | operator: fix the corpus | | corpus_hash_mismatch | a corpus file's hash differs from the index entry | corpus | operator: fix the corpus | | corpus_index_invalid | the corpus index fails its structural parse | corpus index | operator: regenerate the index | | deployment_digest_mismatch | the declared deployment digest differs from the computed digest | deployment_digest | reject the manifest (tamper) | | digest_algorithm_unsupported | an unknown digest algorithm tag | digest member | reject the artifact | | digest_encoding_invalid | a malformed digest body | digest member | reject the artifact | | digest_mismatch | a content digest differs from the computed digest | content_digest | reject the artifact (tamper) | | duplicate_member | a duplicate JSON member name (I-JSON) | bytes | reject the artifact | | extension_criticality_conflict | declared criticality conflicts with the registry | extensions | reject the artifact | | extension_duplicate | one namespace appears twice in extensions | extensions | reject the artifact | | extension_namespace_invalid | a namespace fails the reverse-DNS-plus-path form | extensions | reject the artifact | | extension_payload_forbidden | a payload appears where the registry forbids one | extensions | reject the artifact | | extension_retired | a retired namespace is declared | extensions | reject the artifact | | extension_schema_digest_mismatch | a critical body's validating schema digest differs from the registry pin | extensions | reject the artifact | | extension_schema_unavailable | no host schema is supplied for a digest-pinned critical body | extensions | reject the artifact | | extension_unknown_critical | an unregistered or reserved namespace is declared critical | extensions | reject the artifact | | federation_mapping_conflict | a state has no consistent mapping on the target transport | federation codec | reject the crossing | | federation_state_unmappable | a state is unmappable (e.g. A2A UNSPECIFIED) | federation codec | reject the crossing | | federation_terminal_conflict | two receipts for one task identity diverge in any commitment component | federation envelope | reject the later receipt | | forbidden_portable_value | a value shape trips the portability denylist | offending member | reject the artifact | | integer_magnitude | an integer above the parse window | offending lexeme | reject the artifact | | invalid_cardinality | an array violates min/max items | offending member | reject the artifact | | invalid_constraint | a member fails its check constraint | offending member | reject the artifact | | invalid_encoding | a member fails its encoding form | offending member | reject the artifact | | invalid_number | a number lexeme is malformed | bytes | reject the artifact | | invalid_syntax | the JSON text is syntactically invalid | bytes | reject the artifact | | invalid_type | a member's JSON type disagrees with the schema | offending member | reject the artifact | | lifecycle_state_invalid | a deployment lifecycle state is unknown | lifecycle | reject the manifest | | missing_ceiling | an operational bound is absent (never infinity) | ceilings | reject the artifact | | missing_required_field | a required member is absent | the member | reject the artifact | | no_authoritative_recovery | a mutation-kind operation is bound while the effect owner's recovery is none | effect_owner | reject the import | | non_canonical_bytes | interchange bytes are not already canonical | bytes | reject before any semantic read | | nonportable_content | an authority-shaped claim rides portable content | offending member | reject the artifact | | number_not_double_expressible | an above-window integer is not exactly double-expressible | offending lexeme | reject the artifact | | predicate_nodes_exceeded | a predicate AST exceeds the node ceiling | predicate | reject the artifact | | predicate_op_unknown | a predicate names an unknown operator | predicate | reject the artifact | | predicate_path_unresolved | a predicate path resolves against no declared port | predicate | reject the artifact | | protected_bound_clamp_denied | a protected bound narrows without the acknowledge opt-in | bounds | reject, or opt in and record evidence | | protocol_revision_unsupported | the revision is outside the declared set (both directions) | protocol_revision | reject the artifact | | required_core_field_not_digest_covered | a required core field names an evidence-only member | required_core_fields | reject the artifact | | required_core_field_unsupported | a required core field names no known core member | required_core_fields | reject the artifact | | schema_complexity_exceeded | a schema exceeds the complexity meter | schema document | reject the artifact | | schema_dialect_unknown | a schema names an unknown dialect | schema document | reject the artifact | | schema_invalid_shape | a schema is structurally malformed | schema document | reject the artifact | | schema_keyword_not_allowed | a schema uses a keyword outside the bounded dialect | schema document | reject the artifact | | schema_keyword_value_invalid | a schema keyword carries an invalid value | schema document | reject the artifact | | schema_ref_cycle | a schema $ref cycle | schema document | reject the artifact | | schema_ref_unresolvable | a schema $ref resolves nowhere | schema document | reject the artifact | | signature_algorithm_unsupported | an unknown signature algorithm | signatures | halt the import (reconcile denies) | | signature_key_unsupported | no supplied key matches, or a small-order key | signatures | halt the import (reconcile denies) | | signature_malformed | a signature envelope fails its parse | signatures | halt the import (reconcile denies) | | signature_not_verified | the cryptographic verification fails | signatures | halt the import (reconcile denies) | | trailing_bytes | bytes follow the JSON value | bytes | reject the artifact | | unknown_bound | a bounds member name is unknown | bounds | reject the artifact | | unknown_member | a member is outside the closed world | the member | reject the artifact | Plus the parameterized ceiling family {:ceiling, key} over the eight decoder limit names. No authorization vocabulary exists in any identifier — no :unauthorized in either polarity (source-scanned gate).

## 13. Evidence and reconcile

reconcile(blueprint, deployment, inputs) is the one call per import: canonical → digest → negotiation → structure → portability → signatures → bind → bounds, in that pinned order. Its result is an %Evidence{} of per-surface checks, effective bounds, clamp evidence, and not_verified — which MUST be non-empty BY CONSTRUCTION, always naming at least the seven host-owned surfaces this protocol structurally cannot establish: tenancy, live_policy, authority, effect_ownership, execution, billing, evaluation_truth. A caller SHOULD NOT read an Evidence record and conclude "everything is fine": the record itself names what it did not check.

## 14. Portability

Portable artifacts MUST NOT contain secrets, private keys, live grants, tenant identifiers, raw endpoints, database primary keys, or backend engine identifiers — enforced structurally (member-name and value-shape denylists over the open regions, at any depth, name-spelling-normalized) and red-cased per class in the corpus. A portability pass is not sufficiency — the guard is necessary, not sufficient: opaque quarantined extension bodies are typed as unscanned, and portability claims attach only to schema-validated content. Concrete provider/model names, credentials, engine ids, and endpoints resolve host-side and are unrepresentable in self-fulfilling artifacts.

## 15. Conformance

The package ships a portable conformance corpus (priv/conformance/): 94 cases covering every required cell of the 16-surface × 31-class applicability floor, full-registry golden artifacts, RFC 8785 number vectors, and deterministic Ed25519 fixtures, at corpus digest sha-256:sg6Fo7p8nZpJDzxFn4dXHBWgbGvEvtOk-7t3m7OT7Yo. Corpus identity is the digest of the domain-separated index — versioned by digest, not by name. The loader is pure over %{path => binary} and verifies per-file hashes, exact file set (both directions), counts, id-uniqueness, and applicability totality; the report refuses a vacuous green.

A repo-side second-language verifier (conformance/verifier/, TypeScript on Node ≥ 24, node: builtins only, never in the Hex archive) independently recomputes every corpus verdict and integrity check with its own scanner, canonicalizer, and node:crypto Ed25519 path; the release gate requires its report to be byte-identical to the Elixir escript's over both the repo corpus and the built archive's corpus. A mutation gate breaks the implementation at seven named points and requires the corpus to go red on each — a vacuous case set is a build failure.

### Conforming implementations (normative)

An implementation conforms to this specification at a given release when all three of the following hold. Partial conformance is not conformance: an implementation that skips applicable corpus cells does not conform at this release.

1. Corpus pass. It executes every case of the released conformance corpus — pinned by the digest above, every applicable cell of the applicability floor — and produces the expected verdict for each, refusing a vacuous pass. 2. Report agreement. Its case-report document over that corpus is byte-identical to the reference implementation's report; the report format is part of the wire contract, and the second-language verifier carries the discipline. 3. Release identity. Its release pins the corpus digest and the registry digest it certifies against, so specification, evidence, and implementation co-version; a release that cannot name its evidence is not a release of this protocol.

## 16. The shipped surface (informative — the reference implementation)

The facade (AgentBlueprintProtocol) delegates and never implements; every public function carries a @spec (build-gated), every module a boundary-stating moduledoc:

| Function | Contract | |---|---| | decode_blueprint/1,2 | bounded, canonical-first Blueprint decode | | decode_deployment/1,2 | bounded, canonical-first Deployment decode | | decode_federation_envelope/1,2 | bounded TaskEnvelope decode | | canonical_bytes/1 | the artifact's canonical JCS bytes | | negotiate/2 | revision/required-field/extension evolution gate | | intersect/1 | bounds meet over blueprint/deployment/host sources | | verify_compatibility/2 | identity-exact build compatibility | | reconcile/3 | the one composed non-authorizing pass per import | | federation_mapping/0 | the 23-row A2A/MCP mapping as data |

Layered under it: the bytes layer (Json, Canonicalization, Base64Url, Digest, Bounds), the artifacts (Blueprint, Deployment, Extension, Registry as the one generic table-driven engine, Schema as the bounded 2020-12 dialect + instance validator, Predicate, Portability), the algebra (BoundsAlgebra, Negotiation, Compatibility, Signature, Federation, Error, Evidence, Reconcile, ExtensionRegistry), and the conformance tooling (Conformance.Corpus/Runner/Report/Cli + the escript entry). The module tree is strictly downward (bytes → algebra → artifacts → conformance); the engine knows tables, never domains.

### Producer surface

Hosts that render artifacts produce them through the per-artifact constructors, never through facade-level minting: compose the member value, construct it with Blueprint.from_value/2 or Deployment.from_value/2 (threading :authored_extensions for critical namespaces whose bodies negotiation validated against a digest-pinned schema), compute the release identity with the artifact's content_digest/1, and serialize with its canonical_bytes/1. The round-trip property (decode → to_value/1 → encode is a fixed point) is the byte-exactness guarantee a producer relies on. The facade stays a verification facade: it delegates and never implements, and no facade-level producer functions grow (the accepted producer-surface decision record).

## 17. Security considerations (normative)

The threat model is not invented for this section: every threat below is exercised by the conformance corpus as a named red class, and the gate in the reference implementation reds a threat row that cites a class the corpus does not carry.

| Threat | Vector | Corpus class | Mitigation | |---|---|---|---| | Artifact tamper | bytes edited after signing | tamper_meaningful_byte digest_mismatch | content digests over exact received bytes; :digest_mismatch denies (checked after structural validation, before signatures and binding) | | Canonicality laundering | non-canonical spellings of the same value | invalid_duplicate | MUST-already-be-canonical interchange bytes; :non_canonical_bytes | | Signature substitution | a signature lifted onto another artifact/purpose | signature_invalid | purpose-pinned signed attributes; digest covers revision + identity members | | Protocol downgrade | an older/newer revision forced on a consumer | revision_above_max revision_below_min | explicit revision SETs, both directions deny :protocol_revision_unsupported | | Extension rug-pull via registry | unknown/retired namespace forced critical | extension_unknown_critical | registry-state table; deny with typed codes | | Schema-pin evasion | critical body validated against a different schema | extension_schema_unavailable | digest-pinned schemas; :extension_schema_digest_mismatch | | Tool rug-pull | a bound tool's descriptor replaced out-of-band | binding_stale compatibility_range_rejected | binding verification: descriptor-digest equality + attestation freshness + identity-exact entries | | Ceiling widening | a bound or ceiling raised after the fact | bound_widening_operational bound_widening_protected | bounds may narrow only; protected narrowing denies by default; effective bounds never widen host policy | | Quarantined-content laundering | unscanned payload smuggled into a claim | extension_unknown_optional_roundtrip forbidden_portable_value | quarantine is typed unscanned; portability claims attach only to schema-validated content | | Receipt equivocation | two diverging terminal receipts for one task | terminal_equivocation federation_terminal_conflict | Terminal Commitment over seven components; ANY divergence denies | | Misaddressed execution | a receipt verified by the wrong receiver | audience_mismatch | issuer/subject/audience checked against the receiving context | | Required-field laundering | an evidence-only member promoted to a requirement | required_field_not_covered | required core fields MUST be digest-covered |

The non-authorizing boundary is itself a security property: every verification result is evidence, the not_verified set is non-empty by construction naming the host-owned surfaces, and no code path grants authority. Small-order Ed25519 keys reject; the package performs no key discovery or trust selection — a wrong key is a typed denial, never a silent pass.

## 18. Privacy considerations (normative)

The portability guard is the protocol's data-minimization profile, stated as a normative constraint set: portable artifacts MUST NOT carry secrets, private keys, live grants, tenant identifiers, raw endpoints, database primary keys, or backend engine identifiers — enforced structurally at any depth (member-name and value-shape denylists, name-spelling-normalized) and exercised per class by the corpus (forbidden_portable_value). The honest limits are part of the contract: identifier-shaped values with the UUID grammar are exempt by shape, so the guard is structural, not semantic — a tenant identifier encoded as a UUID is not caught by the value-shape arm; quarantined extension bodies are typed as unscanned (extension_unknown_optional_roundtrip carries the round-trip obligation), and portability claims attach only to schema-validated content — a producer MUST NOT present a portability pass as a privacy guarantee. Federation identity members (issuer, subject, audience, identity_mapping_evidence) are correlation material only: an authority-shaped claim in the evidence block denies :nonportable_content, and possession of a key is never an identity claim.

## 19. Positioning — protocol neighbors (informative)

The Agent Blueprint Protocol does not compete with the transport and discovery protocols; it completes them. AI Catalog owns discovery, verifiable identity, and attestation; A2A owns agent-to-agent tasks; MCP owns tools and data. ABP owns the portable CONTRACT layer — the artifact that says what an agent is, what it may do (bounds, ceilings), what it must prove (evidence), and how a host verifies all of it fail-closed without granting authority. A blueprint rides IN A2A task metadata and MCP _meta (the federation profile proves the carriage); a Trust-Manifest-style discovery record can reference a blueprint by digest without either protocol subsuming the other. Where a neighbor mandates a signature envelope, ABP already speaks it: detached JWS over JCS canonical bytes is the shared primitive.