AgentBlueprintProtocol.Schema (Agent Blueprint Protocol v0.1.0)

Copy Markdown View Source

Bounded JSON Schema 2020-12 dialect + instance validator.

A closed 16-keyword subset — type properties required items enum const minimum maximum minLength maxLength minItems maxItems additionalProperties oneOf $defs plus document-local $ref — parsed from Json's tagged algebra and evaluated as pure, zero-dep instance validation. Serves port payloads, output_schema assertions, and binding checks; evaluation and execution TRUTH stays host-owned. Semantics follow the 2020-12 core and validation specifications: assertions auto-pass instance types they do not target (core §7.6.1), missing keywords never fail (core §7.3), oneOf is exactly-one (core §10.2.1.3), boolean schemas are the substrate (core §4.3.2), numbers compare by mathematical value across the integer/float tags (core §4.2.2 — the tags do not survive the wire, so a non-Elixir verifier cannot and must not distinguish them), and string lengths count RFC 8259 characters (codepoints — not graphemes, not UTF-16 units).

Everything outside the frozen subset denies :schema_keyword_not_allowed (fail-closed closed world: a regex-bearing or network-fetching schema is a DoS/SSRF surface in a portable artifact). Keyword recognition is POSITIONAL — only members of schema-position objects are keywords; enum/const values and properties/$defs member names are instance data.

Resource posture: schema complexity is metered as nodes + keywords + Σ(oneOf branches) + depth × 4 against the profile ceiling 512 (:schema_complexity_exceeded); evaluation memoizes (schema-node, instance-location) results — sound because assertion results are context-free in this annotation-free dialect — which bounds evaluation work to distinct pairs and closes the acyclic-$ref-DAG blowup; references resolve only to document-local JSON Pointers landing on schema positions, and any application-reachable reference cycle denies :schema_ref_cycle (core §9.4.1 — a dead $defs entry is never applied and may self-reference). validate_instance/3 bounds no instance itself: termination and cost rely on the caller-side parse ceilings upstream (Json), the same posture as the decoder.

Both parse/2 and validate_instance/3 are total and never raise on any input: malformed tagged shapes deny (:invalid_type instance-side, :schema_keyword_value_invalid schema-side), never crash. Errors are value-free; no wire string becomes an atom before a closed-set check. Schema validation is a structural fact — it never authorizes content.

Summary

Functions

The profile complexity ceiling (declared profile maximum).

Meter a schema document: nodes + keywords + Σ(oneOf branch counts) + depth × 4. Pure metering over the positional walk — unknown keywords count as keywords, their values as data; no validation is performed.

The single accepted dialect identifier — the 2020-12 dialect meta-schema URI (validation §5). 2020-12 is the only published dialect; the v1/2026 URI 404s.

Structural equality of two tagged values per core §4.2.2: numbers compare by mathematical value across the integer/float tags, strings codepoint-for-codepoint, booleans and null by kind, arrays pairwise order-sensitive, objects order-blind with equal member counts. Mismatched kinds are unequal, never an error. Public so the artifact layers consume the one equality law rather than restating it.

Parse a schema document (the tagged algebra value) under dialect. Validates the closed keyword subset, keyword-value syntax, document-local $ref resolution, application-edge acyclicity, and the complexity ceiling. Accepts only the exact 2020-12 dialect URI.

Validate instance (a tagged algebra value) against schema under dialect. schema is the raw tagged document or a parse/2 struct (structs are re-parsed, not trusted — the Bounds.coerce precedent). Total and never-raising on any input: malformed tagged values deny :invalid_type, malformed schemas deny a :schema_* reason.

Types

instance_reason()

@type instance_reason() :: :invalid_type | :invalid_constraint | :invalid_cardinality

schema_reason()

@type schema_reason() ::
  :schema_dialect_unknown
  | :schema_keyword_not_allowed
  | :schema_keyword_value_invalid
  | :schema_complexity_exceeded
  | :schema_ref_unresolvable
  | :schema_ref_cycle
  | :schema_invalid_shape

t()

@type t() :: %AgentBlueprintProtocol.Schema{
  complexity: non_neg_integer(),
  dialect: binary(),
  pointers: pointers(),
  root: AgentBlueprintProtocol.Json.value()
}

Functions

ceiling()

@spec ceiling() :: pos_integer()

The profile complexity ceiling (declared profile maximum).

complexity(value)

Meter a schema document: nodes + keywords + Σ(oneOf branch counts) + depth × 4. Pure metering over the positional walk — unknown keywords count as keywords, their values as data; no validation is performed.

dialect()

@spec dialect() :: binary()

The single accepted dialect identifier — the 2020-12 dialect meta-schema URI (validation §5). 2020-12 is the only published dialect; the v1/2026 URI 404s.

equal?(arg1, arg2)

Structural equality of two tagged values per core §4.2.2: numbers compare by mathematical value across the integer/float tags, strings codepoint-for-codepoint, booleans and null by kind, arrays pairwise order-sensitive, objects order-blind with equal member counts. Mismatched kinds are unequal, never an error. Public so the artifact layers consume the one equality law rather than restating it.

parse(value, dialect)

@spec parse(AgentBlueprintProtocol.Json.value(), binary()) ::
  {:ok, t()} | {:error, schema_reason()}

Parse a schema document (the tagged algebra value) under dialect. Validates the closed keyword subset, keyword-value syntax, document-local $ref resolution, application-edge acyclicity, and the complexity ceiling. Accepts only the exact 2020-12 dialect URI.

validate_instance(schema, instance, dialect)

@spec validate_instance(
  t() | AgentBlueprintProtocol.Json.value(),
  AgentBlueprintProtocol.Json.value(),
  binary()
) :: :ok | {:error, instance_reason() | schema_reason()}

Validate instance (a tagged algebra value) against schema under dialect. schema is the raw tagged document or a parse/2 struct (structs are re-parsed, not trusted — the Bounds.coerce precedent). Total and never-raising on any input: malformed tagged values deny :invalid_type, malformed schemas deny a :schema_* reason.