fix: address Copilot review round 26 — a false property schema declines the branch

JSON Schema's boolean form is legal, but only `true` is harmless: it
constrains nothing, while `false` admits no value at all — so a field
declared with it can never be filled, and a required one makes the whole
branch unsatisfiable. Offering it contradicted every other faithfulness check
in the resolver, so such a branch is declined.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-26 09:42:50 -04:00
commit f31589a8f9
2 changed files with 25 additions and 10 deletions

View file

@ -764,9 +764,9 @@ describe("resolveRootUnion", () => {
} }
}); });
it("offers a member carrying a boolean property schema", () => { it("offers a member carrying a `true` property schema", () => {
// JSON Schema's boolean form is legal and answers every keyword lookup // `true` constrains nothing and answers every keyword lookup with
// with `undefined`, so it renders through the JSON fallback harmlessly. // `undefined`, so it renders through the JSON fallback harmlessly.
const { branches } = resolveRootUnion({ const { branches } = resolveRootUnion({
type: "object", type: "object",
anyOf: [ anyOf: [
@ -777,6 +777,19 @@ describe("resolveRootUnion", () => {
expect(branches).toHaveLength(2); expect(branches).toHaveLength(2);
}); });
it("declines a member carrying a `false` property schema", () => {
// `false` admits no value at all, so the field can never be filled — and
// a required one makes the branch unsatisfiable.
const { branches } = resolveRootUnion({
type: "object",
anyOf: [
{ type: "object", properties: { nothing: false } },
{ type: "object", properties: { other: { type: "string" } } },
] as unknown[],
});
expect(branches).toEqual([]);
});
it("declines a member whose properties are not an object", () => { it("declines a member whose properties are not an object", () => {
// Members arrive as `unknown`, so this is reachable and must not throw. // Members arrive as `unknown`, so this is reachable and must not throw.
expect( expect(

View file

@ -180,15 +180,17 @@ function isOfferable(branch: RootUnionSchema): boolean {
const properties = Object.values(propertiesOf(branch)); const properties = Object.values(propertiesOf(branch));
return ( return (
properties.length > 0 && properties.length > 0 &&
// Every value has to be something a renderer can read. JSON Schema's // Every value has to be something a renderer can read AND something a
// boolean form is legal and harmless — `true`/`false` answer every keyword // caller can satisfy. A `null` or an array is not a schema at all, and the
// lookup with `undefined` — but a `null` or an array is not a schema at // web form dereferences one on the way to choosing a widget. JSON Schema's
// all, and the web form dereferences one on the way to choosing a widget. // boolean form is legal, but only `true` is harmless — it constrains
// A branch carrying one is declined rather than handed on to crash a tool // nothing and answers every keyword lookup with `undefined`, while `false`
// panel that would otherwise have rendered. // admits no value whatsoever, so a field declared with it can never be
// filled and a required one makes the whole branch unsatisfiable. Either
// way the branch is declined rather than offered as a callable shape.
properties.every( properties.every(
(property) => (property) =>
typeof property === "boolean" || property === true ||
(typeof property === "object" && (typeof property === "object" &&
property !== null && property !== null &&
!Array.isArray(property)), !Array.isArray(property)),