AgentBlueprintProtocol.Negotiation (Agent Blueprint Protocol v0.1.0)

Copy Markdown View Source

Negotiation: the evolution gate an importing host runs after canonical verification, before any semantic read. In a standalone pass it runs immediately after canonical verification; in the composed import (Reconcile.reconcile/3) the pinned stage order is canonical → digest → negotiation → structure → portability → signatures → bind → bounds — the digest stage runs BEFORE negotiation (the supersession of the "integrity last" ordering).

Negotiate reads ONLY protocol machinery — protocol_revision, required_core_fields, the extensions envelope — never semantic members. Malformed machinery values reuse Blueprint's decode reason rules so both entry points agree (absent → :missing_required_field, wrong tag → :invalid_type, revision < 1 → :invalid_constraint; the retired :protocol_revision_invalid stays retired). Internal precedence is PINNED: revision → required_core_fields → extensions — an artifact failing several stages reports the earliest.

Revision support is an explicit SET (Support.revisions, never ranges); a miss denies :protocol_revision_unsupported (above-max and below-min are the same miss). Outcome.protocol_revision is the artifact's own, exact — never negotiated down.

required_core_fields runs three fail-closed checks in order: known (:required_core_field_unsupported) → digest-covered (:required_core_field_not_digest_covered) → supported by this consumer (:required_core_field_unsupported).

Extensions run the positional state machine (criticality match before state handling for LIVE states; reserved/retired labels check state first — held and dead labels do not enforce criticality; every state × position cell decided):

registry stateartifact criticalartifact optional
unregistereddeny :extension_unknown_criticalQUARANTINE (byte-exact, noticed)
criticality mismatchdeny :extension_criticality_conflictdeny :extension_criticality_conflict
active matchingsupportedretained, executable
deprecatedsupported + noticeretained + notice
retireddeny :extension_retiredretained + notice
reserveddeny :extension_unknown_criticalretained + notice

Quarantined namespaces round-trip byte-exactly and are NEVER claimed scanned-or-portable beyond what the structural denylists check.

Critical bodies validate ONLY against a host-supplied schema whose digest matches the registry's pin: none supplied (or the registry entry has none authored) → :extension_schema_unavailable; digest mismatch → :extension_schema_digest_mismatch; match → Schema.validate_instance over the body. Validated critical namespaces are the legitimate channel for encoded content — thread them into Blueprint.from_value/2's :authored_extensions option.

A reserved-semantics denylist runs over ALL extension bodies (any depth, camelCase twins included): bound-shaped member names deny :extension_payload_forbidden — the bound-vocabulary smuggling the portability scan cannot see.

negotiate/2 also accepts %Blueprint{} (reads to_value): a struct from from_value carries no integrity proof — outcomes derive from the given content. Negotiation reports what a support posture accepts; it never authorizes an operation. Negotiation reports what a support posture accepts; it never authorizes an operation.

Summary

Types

reason()

@type reason() ::
  :protocol_revision_unsupported
  | :missing_required_field
  | :invalid_type
  | :invalid_constraint
  | :required_core_field_unsupported
  | :required_core_field_not_digest_covered
  | :extension_unknown_critical
  | :extension_criticality_conflict
  | :extension_retired
  | :extension_schema_unavailable
  | :extension_schema_digest_mismatch
  | :extension_payload_forbidden
  | AgentBlueprintProtocol.Schema.schema_reason()
  | AgentBlueprintProtocol.Schema.instance_reason()

Functions