PtcRunner.Kernel.JSONSchema (PtcRunner v0.14.0)

Copy Markdown View Source

Internal compiler for the bounded capability JSON Schema profile.

The accepted profile is a strict subset of JSON Schema 2020-12 containing type, title, description, default, properties, required, additionalProperties, items, enum, const, minimum, maximum, minLength, maxLength, minItems, maxItems, and the single bounded sha256 string format. Types are scalar rather than unions, roots are objects, and a missing additionalProperties on an object is normalized to false.

$schema selects the schema dialect; absence means the MCP default (2020-12). Because the accepted profile is a common subset of the allowlisted dialects, a supported root $schema URI (2020-12, or draft-07 as a deliberate compatibility translation) is accepted and removed, while unknown, malformed, and nested dialect markers are rejected. Vendor x-… extension keys and the standard non-validating default annotation are discarded from every level as a deliberate client policy — mainstream MCP SDKs emit them by default. They do not reach normalized output, encodings, hashes, or runtime argument construction. All other unknown keywords remain rejected.

Each normalized schema is at most 64 KiB with maximum depth 16, 128 properties per object, and 256 enum members. Schemas are compiled once with JSV. Runtime validation delegates to the compiled JSV root. Input rejection may retain a small explanation containing only schema-declared paths, keywords, and bounds; submitted values and undeclared property names never enter that explanation. Validation and explanation projection run together in one time- and heap-bounded worker. Proven schema invalidity is distinct from validator timeout, heap exhaustion, crashes, and malformed validator results.

Rejection reports the first proven fault as a closed rule atom plus the segments locating it inside the submitted schema document. Every segment is a key or index the submitted document actually carries, so the location always resolves in the file the author opens. The profile is deliberately closed, and its edges are not guessable from a bare refusal: an unsupported keyword, a misspelled type, and an unsatisfiable bound are three different authoring mistakes and must not report identically.

Summary

Functions

Returns every rule a rejection can carry, most specific first.

Validates a value and returns only bounded, schema-authored rejection facts.

Types

compiled()

@type compiled() :: JSV.Root.t()

rejection()

@type rejection() :: %{rule: atom(), segments: [segment()]}

segment()

@type segment() :: {:property, binary()} | {:index, non_neg_integer()}

violation()

@type violation() :: %{
  :argument => binary(),
  :constraint => binary(),
  optional(:expected) => term()
}

Functions

compile(schema)

@spec compile(map()) ::
  {:ok, map(), compiled()} | {:error, {:invalid_schema, rejection()}}

rules()

@spec rules() :: [atom()]

Returns every rule a rejection can carry, most specific first.

The set is closed so a diagnostic boundary can render each rule from a fixed literal instead of forwarding compiler prose.

valid?(root, value)

@spec valid?(compiled(), term()) :: boolean()

validate(root, schema, value, timeout_ms, max_heap_words)

@spec validate(compiled(), map(), term(), pos_integer(), pos_integer()) ::
  :ok | {:invalid, [violation()]} | {:unavailable, atom()}

Validates a value and returns only bounded, schema-authored rejection facts.