AgentBlueprintProtocol.Registry (Agent Blueprint Protocol v0.1.1)

Copy Markdown View Source

The generic field-registry decode/validate engine: ONE table-driven walk, parameterized by the registry table an artifact layer supplies — Blueprint today, Deployment next. The engine knows tables, not domains: every domain judgment (bounded schemas, signature envelopes, predicates, extension forms, cross-field rules) arrives as data — checker functions carried in the table and defined in the owning artifact module — so the dependency direction is always artifact → engine.

A table is a list of field specs (maps):

%{
  name: "member_name",            # binary()
  required: true,                 # absence → :missing_required_field
  kind: kind(),                   # structural check below
  check: fn value -> :ok | {:error, reason()} end,   # optional, per-field
  root_hook: fn members -> :ok | {:error, reason()} end, # optional, cross-field
  min_items: 1,                   # array cardinality floor (kind arrays)
  max_items: 64,                  # array cardinality ceiling
  unique_by: "id"                 # array element uniqueness member name
}

Kinds: :string :integer :float :boolean :number (either number tag) {:enum, MapSet.t()} {:array, element_spec} {:object, %{members: [spec()]}} (closed world recursively) :any (any well-formed tagged value) :custom (the spec's check is the whole judgment). :integer is TAG-STRICT: a {:float, f} — including a zero-fraction window float — denies :invalid_type. This is the artifact layer's typing; the wire cannot carry the tag, so only this layer can see it (the artifact-typing relocation).

Failure precedence is pinned so two implementations pick the same reason. Per object: member-list well-formedness (pairs, duplicates — hand-built values only; the decoder already denies these) → :unknown_member (document order) → :missing_required_field (table order) → :invalid_type (table order) → :invalid_constraint (table order: enum membership and check results that are not pass-through reasons of earlier stages) → :invalid_cardinality (table order: counts and uniqueness over raw keys) → nested recursion (children in document order; child reasons propagate) → root_hooks (table order). Array elements are recursed after cardinality, and uniqueness runs on raw member values so it never depends on element validity.

Total and never-raising: malformed tagged shapes deny :invalid_type. The engine validates structure — validation never authorizes an operation.

Summary

Functions

Validate value against table under the pinned precedence. The root must be an object; every stage is fail-closed and value-free.

Types

check()

@type check() :: (AgentBlueprintProtocol.Json.value() -> :ok | {:error, reason()})

kind()

@type kind() ::
  :string
  | :integer
  | :float
  | :boolean
  | :number
  | :any
  | :custom
  | {:enum, MapSet.t()}
  | {:array, spec()}
  | {:object, %{members: [spec()]}}

reason()

@type reason() ::
  :unknown_member
  | :missing_required_field
  | :invalid_type
  | :invalid_constraint
  | :invalid_cardinality
  | term()

root_hook()

@type root_hook() :: (%{optional(binary()) => AgentBlueprintProtocol.Json.value()} ->
                  :ok | {:error, reason()})

spec()

@type spec() :: %{
  optional(:name) => binary(),
  optional(:required) => boolean(),
  optional(:kind) => kind(),
  optional(:check) => check(),
  optional(:root_hook) => root_hook(),
  optional(:min_items) => non_neg_integer(),
  optional(:max_items) => pos_integer(),
  optional(:unique_by) => binary()
}

Functions

validate(table, arg2)

@spec validate([spec()], AgentBlueprintProtocol.Json.value()) ::
  :ok | {:error, reason()}

Validate value against table under the pinned precedence. The root must be an object; every stage is fail-closed and value-free.