AgentBlueprintProtocol.Json (Agent Blueprint Protocol v0.1.0)

Copy Markdown View Source

Bounded JSON decoder over Erlang/OTP's :json.decode/3 custom-decoder hook.

It produces a closed, tagged value algebra with order-preserving objects; rejects duplicate members and trailing non-whitespace; enforces every parse ceiling (AgentBlueprintProtocol.Bounds) and the I-JSON integer magnitude bound ±(2^53−1); and never raises to the caller on malformed input (an internal invariant violation still fails loud — see the decode catch). There is exactly one decoder in the package — every artifact and every conformance file passes through here.

Integer window (the amendment of 2026-08-21): a pure-digit lexeme above ±(2^53−1) admits as {:float, f} — the correctly-rounded double Float.parse yields, which is what an ECMAScript peer parses from the same digits — iff that double's canonical ECMAScript serialization (AgentBlueprintProtocol.Canonicalization.number/1) reproduces the lexeme byte-exactly; every other above-bound integer lexeme denies with :number_not_double_expressible. The bound's stated purpose is JS-peer round-trip: exact doubles round-trip, silently-lossy spellings do not. Core fields stay fail-closed against window floats — amended 2026-08-21 by design: under the frozen 2020-12 dialect semantics (Schema), {"type":"integer"} matches zero-fraction numbers tag-blind (validation §6.1.1; the integer/float tag does not survive the wire, so no non-Elixir verifier can implement tag-narrowed integer). The core-field deny therefore lives at the ARTIFACT field typing (the registry engine, which sees the tag), not at the schema layer. Posture unchanged — mechanism relocated.

Errors are value-free: a reason names a category, never the offending bytes or values. The tagged value algebra is decode output — it never authorizes anything. The tagged value algebra is decode output — it never authorizes anything.

Summary

Functions

Decode input under bounds (a Bounds struct or a tighten-only overrides map, defaulting to the profile maxima). Returns the tagged value or a value-free error.

Types

reason()

@type reason() ::
  :invalid_syntax
  | :invalid_encoding
  | :invalid_number
  | :number_not_double_expressible
  | :duplicate_member
  | :trailing_bytes
  | :invalid_type
  | AgentBlueprintProtocol.Bounds.error()

value()

@type value() ::
  :null
  | {:boolean, boolean()}
  | {:integer, integer()}
  | {:float, float()}
  | {:string, binary()}
  | {:array, [value()]}
  | {:object, [{binary(), value()}]}

Functions

decode(input, bounds \\ Bounds.maximum())

@spec decode(binary(), AgentBlueprintProtocol.Bounds.t() | map()) ::
  {:ok, value()} | {:error, reason()}

Decode input under bounds (a Bounds struct or a tighten-only overrides map, defaulting to the profile maxima). Returns the tagged value or a value-free error.