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
@type reason() :: :invalid_syntax | :invalid_encoding | :invalid_number | :number_not_double_expressible | :duplicate_member | :trailing_bytes | :invalid_type | AgentBlueprintProtocol.Bounds.error()
Functions
@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.