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
@type instance_reason() :: :invalid_type | :invalid_constraint | :invalid_cardinality
@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
@type t() :: %AgentBlueprintProtocol.Schema{ complexity: non_neg_integer(), dialect: binary(), pointers: pointers(), root: AgentBlueprintProtocol.Json.value() }
Functions
@spec ceiling() :: pos_integer()
The profile complexity ceiling (declared profile maximum).
@spec complexity(AgentBlueprintProtocol.Json.value()) :: non_neg_integer()
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.
@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.
@spec equal?(AgentBlueprintProtocol.Json.value(), AgentBlueprintProtocol.Json.value()) :: boolean()
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.
@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.
@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.