Repository navigation
v0.10.3 - validator honours nullable required fields (OpenAI strict mode unblock)
Hot-fix. The schema validator now accepts nil on required-but-nullable fields — fixing a conflation bug that has existed in every published version since 0.2.x.
Why this matters
OpenAI's structured-output strict mode requires every property to be present in required. The canonical way to express "nullable" under strict is therefore required + anyOf-null (or type: ["...", "null"]). Pre-0.10.3 our validator wrongly rejected this combination, conflating "required" (must be present) with "non-nullable" (cannot be null) — JSON Schema treats those as orthogonal.
If you bypassed it by setting required: false and disabling strict: true on every nullable field, you can now restore strict: true and keep the field required — that's the standard OpenAI nullable idiom.
Fixed
SchemaValidatornow honours all three nullability idioms on required fields:type: ["string", "null"](array form)type: "null"(degenerate scalar)anyOf/oneOfcontaining a{type: "null"}branch
Strictly additive — non-nullable required fields still reject nil exactly as before. A regression-guard spec pins that behaviour.
Background
Discovered via dogfooding in a production adopter using OpenAI strict structured output with 17 nullable fields ("set the rest to null" prompt). The bug surfaced now and not earlier because:
- most early-adopter schemas were either fully non-nullable or used
required: falsefor optional fields - it only fires in the exact combo (required + nullable schema + LLM returns null)
- this is precisely the OpenAI strict-mode nullable idiom — so adopters avoiding strict didn't hit it
The matching optional DSL in ruby_llm-schema produces this same shape intentionally (required + anyOf-null), aligned with OpenAI strict mode.
Tests
4 new specs in spec/ruby_llm/contract/contract/schema_validator_spec.rb under "required field with nil value":
- array-form nullable accepted
- anyOf-null accepted
- oneOf-null accepted
- regression guard: plain non-nullable still rejected
Suite: 1406 examples / 0 failures / 7 pending (was 1402/0/7).
Full diff: v0.10.2...v0.10.3