Patch release for one fix landing in #22.
Fixed
Normandy.LLM.JsonDeserializernow recovers from tool-use-style response envelopes (#22) — some vision/instruction-following LLMs (Nemotron-Nano-12B-VL on DigitalOcean Inference, some Llama variants) wrap their JSON in{"name": "...", "arguments": {...}}even when givenresponse_format: {"type": "json_object"}and a system prompt asking for the bare object shape.parse_and_validate/3previously cast such payloads to an all-defaults struct and returned:ok, so downstream consumers silently dropped every populated field.parse_and_populate/3now retries the cast once againstparsed["arguments"]when the outer attempt either succeeded with all-defaults OR returned a validation error, and the"arguments"value is itself a map. If the retry yields any populated field it wins; if it still yields all-defaults the original result is preserved so bare-shape responses see no behaviour change. One level only —{"arguments": {"arguments": {...}}}is not unwrapped. Inner cast errors are propagated when the inner map carries at least one permitted key (atom or string form), so e.g.{"arguments":{"count":"not_a_number"}}againstcount: :integersurfaces the validation error instead of returning an empty struct; inner errors are still suppressed when no permitted keys are present, so unrelated envelopes don't manufacture new failures. The{:ok, struct}/{:error, reason}contract is unchanged for every pre-existing shape.get_required_fields/1inJsonDeserializernow reads required fields from the correct source (#22) — the helper was filtering__specification__/0entries as if each were a metadata map, butNormandy.Schemastores{name, type}tuples there, so the filter never matched andvalidate_required/2was being called with an empty list for every schema. It now reads__schema__(:required)(the source of truth) and falls back to the old scan for schemas that don't expose that callback. Required-field validation now actually fires for schemas declaringfield :foo, _, required: true.
Full Changelog: v0.6.1...v0.6.2