The Deployment Manifest artifact (base §7, re-derived 2026-08-22): binds
exactly ONE Blueprint release digest to one local environment — portable
as a shape, resolved values local. Decoded and validated through the ONE
generic field-registry engine (AgentBlueprintProtocol.Registry)
parameterized by this module's 19-member table : the table, the
scan's open regions, and the digest domain are the only deltas from the
Blueprint pipeline.
The decode pipeline, in order, each stage fail-closed:
Canonicalization.verify/2— non-canonical spellings deny:non_canonical_bytesbefore any semantic read (the canonicality ordering).AgentBlueprintProtocol.Registry.validate/2againsttable/0— closed world (:unknown_member), 16 required members, tag-strict integer typing, enums, cardinalities (tool_bindings ≤ 128, data_bindings ≤ 64, build_identities ≥ 1 and ≤ 128 — an empty identity manifest pins nothing and is the silent-fallback space the base red case names), the exact-only build-identity versions (:compatibility_identity_inexacton any range vocabulary), and the per-member custom checks (tagged digests, Z-form timestamps, the lifecycle temporal rules:lifecycle_state_invalid, theas_oftotal rule).- The portability scan — extension bodies and eligibility expressions
are the open regions (
Portability.scan/scan_authored); every other string position — including everyadapter_identityandprofile_identity— is value-shape scanned strict: a network URI, PEM armour, a JWS shape, or raw-key entropy denies:forbidden_portable_value(the base's never-portable list, red in deployment positions). - The content-digest comparison over the covered members' canonical
bytes under the
:deployment_contentdomain — everything exceptdeployment_digest,signatures,attestations.
The binding surface
binds?/2 is digest equality AND NOTHING ELSE (base §7's binding rule):
the declared release digest against the paired Blueprint's RECOMPUTED
content digest — never the declared member — via constant-time compare.
Total over the deployment side: malformed deployment input answers
false. (A hand-built malformed %Blueprint{} raises in Blueprint's own
digest surface — that module's contract, not this one's.)
verify_binding/3 is the six-stage deny set, order pinned (the second-language
verifier contract):
- release digest equality →
:deployment_digest_mismatch - release identity →
:binding_incomplete(the deployment names a differentblueprint_id/release_numberthan the Blueprint it digests) - tool-binding completeness →
:binding_incomplete(a boundlogical_operationabsent from the Blueprint's capability families AND effect intents) - mutation/recovery →
:no_authoritative_recovery(a binding whose operation resolves tomutationunderrecovery: "none"; a name in both sources with disagreeing kinds takes the stricter reading) - attestation staleness →
:binding_attestation_stale(age > maxor a FUTUREattested_at— the host's clock is authoritative; fail-closed) - observed rug-pull →
:binding_descriptor_mismatch(the host's observed descriptor digest vs the attested one)
Stages 5-6 run only on the host observations that feed them: now: nil
skips EVERY staleness judgment including the future-deny; an empty
observed map skips the rug-pull. That is the host's governance choice —
the parameters ARE host policy (the base's stale-binding and rug-pull red
cases are host-observation cases); decode-time integrity is never
skippable.
Honest limits
Eligibility expressions are host policy DSLs: member names are
denylist-scanned (tenant_id, user_id, … deny at any depth) but
VALUES carry the authored exemption — a resolved principal UUID passes
every scan mode (shape-exempt; undecidable-by-scan — necessary, not
sufficient, same posture as extension bodies). Timestamps are Z-form
whole-second RFC3339: sub-second attestations are unrepresentable, so
max_attestation_age_ms is effectively second-granular.
A Deployment Manifest is an inert binding description — it never authorizes execution.
A Deployment Manifest is an inert binding description — it never authorizes execution.
Summary
Functions
Digest equality and nothing else (base §7's binding rule): the declared
release digest against the paired Blueprint's RECOMPUTED content digest —
never its declared member. Total over the deployment side: malformed
deployment input answers false (a hand-built malformed %Blueprint{}
raises in Blueprint's own digest surface — its contract).
The one bound release, parsed: %{blueprint_id, release_number, content_digest} with the digest as a Digest.t(). Total: a malformed or
absent release denies :invalid_type (or the digest's own reason).
The canonical bytes of the whole artifact.
The honest content digest over the covered members' canonical bytes.
Decode and fully verify artifact bytes: canonical verify → registry
validation → portability scan → content-digest comparison. Total and
never-raising.
Whether member_name (a wire-level member name) is digest-covered.
The digest input: the artifact minus the three evidence members (§8.2).
Validate an already-decoded tagged value (stages 2-3; no canonicality —
there are no bytes, so the canonicality ordering obligation does not apply here).
For values that came from verified bytes, follow with
verify_content_digest/1 — this function does NOT check the declared
digest. opts carries :authored_extensions — namespaces
whose critical bodies negotiation validated against a digest-pinned host
schema (the validated-extension channel). Those bodies skip the portability value-shape
heuristics; the channel is tied to THIS artifact's critical region, and
the default ([]) keeps the strict posture everywhere.
The 19-member field registry (base §7, re-derived): data for the generic engine. Field order is the engine's precedence anchor for table-order stages.
The held tagged value — the identity, so the round-trip is byte-exact.
The bind-time deny set (order pinned; see the moduledoc): stage 0 validates
the deployment's own shape — a non-object root, DUPLICATE root members
(first-wins reads must never decide a binding), or a malformed
tool_bindings member DENIES before any cross-artifact judgment. Stages
5-6 are observation-gated — absent host inputs skip exactly their stages.
Compare the declared deployment_digest member against the recomputed
digest over the exact received (verified) bytes' canonical form:
:digest_mismatch on divergence.
Types
@type reason() :: AgentBlueprintProtocol.Registry.reason()
@type t() :: %AgentBlueprintProtocol.Deployment{ value: AgentBlueprintProtocol.Json.value() }
Functions
@spec binds?(t(), AgentBlueprintProtocol.Blueprint.t()) :: boolean()
Digest equality and nothing else (base §7's binding rule): the declared
release digest against the paired Blueprint's RECOMPUTED content digest —
never its declared member. Total over the deployment side: malformed
deployment input answers false (a hand-built malformed %Blueprint{}
raises in Blueprint's own digest surface — its contract).
@spec bound_release(t()) :: {:ok, %{ blueprint_id: binary(), release_number: pos_integer(), content_digest: AgentBlueprintProtocol.Digest.t() }} | {:error, reason()}
The one bound release, parsed: %{blueprint_id, release_number, content_digest} with the digest as a Digest.t(). Total: a malformed or
absent release denies :invalid_type (or the digest's own reason).
The canonical bytes of the whole artifact.
@spec content_digest(t()) :: AgentBlueprintProtocol.Digest.t() | {:error, reason()}
The honest content digest over the covered members' canonical bytes.
@spec decode(binary(), AgentBlueprintProtocol.Bounds.t() | map()) :: {:ok, t()} | {:error, reason()}
Decode and fully verify artifact bytes: canonical verify → registry
validation → portability scan → content-digest comparison. Total and
never-raising.
Whether member_name (a wire-level member name) is digest-covered.
@spec digest_input(t()) :: AgentBlueprintProtocol.Json.value()
The digest input: the artifact minus the three evidence members (§8.2).
@spec from_value(AgentBlueprintProtocol.Json.value(), map()) :: {:ok, t()} | {:error, reason()}
Validate an already-decoded tagged value (stages 2-3; no canonicality —
there are no bytes, so the canonicality ordering obligation does not apply here).
For values that came from verified bytes, follow with
verify_content_digest/1 — this function does NOT check the declared
digest. opts carries :authored_extensions — namespaces
whose critical bodies negotiation validated against a digest-pinned host
schema (the validated-extension channel). Those bodies skip the portability value-shape
heuristics; the channel is tied to THIS artifact's critical region, and
the default ([]) keeps the strict posture everywhere.
@spec table() :: [AgentBlueprintProtocol.Registry.spec()]
The 19-member field registry (base §7, re-derived): data for the generic engine. Field order is the engine's precedence anchor for table-order stages.
@spec to_value(t()) :: AgentBlueprintProtocol.Json.value()
The held tagged value — the identity, so the round-trip is byte-exact.
@spec verify_binding( t(), AgentBlueprintProtocol.Blueprint.t(), AgentBlueprintProtocol.Deployment.Observations.t() ) :: :ok | {:error, reason()}
The bind-time deny set (order pinned; see the moduledoc): stage 0 validates
the deployment's own shape — a non-object root, DUPLICATE root members
(first-wins reads must never decide a binding), or a malformed
tool_bindings member DENIES before any cross-artifact judgment. Stages
5-6 are observation-gated — absent host inputs skip exactly their stages.
Compare the declared deployment_digest member against the recomputed
digest over the exact received (verified) bytes' canonical form:
:digest_mismatch on divergence.