Skip to content

fix(translator): treat contentSchema and unevaluatedItems as schema slots - #13110

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.51from
ntdatt812:fix/schema-slot-keys
Sep 10, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.51from
ntdatt812:fix/schema-slot-keys

Conversation

@ntdatt812

Copy link
Copy Markdown
Contributor

The bug

stripInvalidSchemaConstructs() replaces a truncation placeholder with the permissive {} in every position where a subschema belongs. Forwarding the string instead is invalid JSON Schema, and that 400 is the thing this sanitizer exists to prevent.

SCHEMA_SLOT_KEYS listed ten of the draft 2020-12 applicators and missed two. Measured on release/v3.8.51, feeding { type: "object", [key]: "[MaxDepth]" } through the walker one keyword at a time:

keyword before
items, additionalProperties, propertyNames, contains, not, if, then, else, unevaluatedProperties, additionalItems {}
contentSchema "[MaxDepth]"
unevaluatedItems "[MaxDepth]"

Those two fall through to the scalar branch at the bottom of the walker — the one that deliberately leaves annotation keywords (description, title, pattern, format) as strings — and are forwarded untouched.

The placeholders are real: logTruncation.ts emits [MaxDepth], [Truncated], [Circular], [Object] and [Array] once a schema is deeper than the log depth limit, and SCHEMA_PLACEHOLDER_PATTERN here matches all five.

The repository already disagrees with itself

The opencode plugin's own walker lists both:

// @omniroute/opencode-plugin-v2/src/shared/gemini.ts
/** Keys whose value is itself a schema. */
const SCHEMA_VALUE_KEYS = [
  "items", "additionalItems", "contains", "not", "if", "then", "else",
  "propertyNames", "contentSchema", "unevaluatedItems", "unevaluatedProperties",
];

Two lists of the same thing, kept in step by hand. They agreed on ten entries and diverged on two, and nothing failed when they did — both files compile, both walkers answer, they just answer differently.

The change

Two entries. Nothing else about those keywords changes: an object or array in either position was already recursed into by the fallback branch, a boolean is still preserved rather than widened to {} (contentSchema: false is restrictive and must stay so), and a string that is not a placeholder is still forwarded untouched.

Tests

tests/unit/translator/schema-slot-keys-drift.test.ts, 17 tests. Rather than testing the two new keywords alone, it drives all twelve slots through all five placeholder shapes, so the next keyword to go missing fails here too. It also pins the three properties that keep the fix from being a widening: booleans preserved, non-placeholder strings untouched, and a property named contentSchema still treated as a property.

mutation result
remove both keys again 3 fail
remove only unevaluatedItems 2 fail

The second mutation matters: it shows the coverage test is not an all-or-nothing check.

tests/unit/translator/schema-coercion.test.ts, bailian-schema-validation, agentSkills-schemas — 32 passed. eslint --suppressions-location config/quality/eslint-suppressions.json clean.

Independent of #13101 and #13104; different files.

…lots

stripInvalidSchemaConstructs() replaces a truncation placeholder with the
permissive {} in every position where a subschema belongs, because forwarding
the string is invalid JSON Schema and is the 400 the sanitizer exists to
prevent. SCHEMA_SLOT_KEYS listed ten of the draft 2020-12 applicators and
missed two, so a placeholder in either fell through to the scalar branch at
the bottom of the walker and was forwarded as a string:

  items, additionalProperties, propertyNames, contains, not,
  if, then, else, unevaluatedProperties, additionalItems  ->  {}
  contentSchema, unevaluatedItems                         ->  "[MaxDepth]"

The repository already disagrees with itself here: the opencode plugin's own
walker (@omniroute/opencode-plugin-v2/src/shared/gemini.ts) lists both under
SCHEMA_VALUE_KEYS, "keys whose value is itself a schema".

Nothing else about the two changes: an object or array in either position was
already recursed into by the fallback, a boolean is still preserved rather
than widened to {}, and a string that is not a placeholder is still forwarded
untouched.
@diegosouzapw
diegosouzapw merged commit 0a314c8 into cheaper-inference:release/v3.8.51 Sep 10, 2026
9 of 16 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…lots (cheaper-inference#13110)

Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings.

Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants