Repository navigation
Description
Design: Translate the constraint, or say so — one fidelity contract for provider tool-schema projections
Design doc: files/designs/opencode-schema-projection-fidelity.md in the goal workspace (keep-the-four-repos-healthy) — problem, evidence, alternatives, risks, and open questions in full. Grounded on main 618de2888fcf6e3eae88ffae3c97923a4b7e907d (files read via the GitHub API; the sandbox clone was missing at run start).
Problem
Every tool schema is written once (built-in tool, MCP server, plugin) and then translated per provider in packages/ai — and no translator has ever stated what it owes the source schema. In two days the unstated contract produced one defect per keyword:
- Dropped (widening): fix(ai): antigravity convertSchema drops const — a fixed-value parameter is sent upstream unconstrained #1557 (open) — antigravity
convertSchemadropsconstentirely; it is absent from theSCHEMA_FIELDSallowlist inpackages/ai/src/protocols/antigravity.ts(verified on current main), so a fixed-value parameter goes upstream unconstrained. - Inverted: fix(ai): antigravity convertSchema inverts boolean
falsesubschemas — a never-valid schema is sent upstream as unconstrained #1573 (closed) — a booleanfalsesubschema ("no value is valid") converted to{}("every value is valid"). - Collapsed (narrowing): fix(ai): antigravity convertSchema collapses a multi-valued type union to its first member — type: ["string","integer"] is sent upstream as STRING only #1556 (closed) — a multi-valued
typeunion collapsed to its first member. - Forced (narrowing to unsatisfiable): fix(ai): antigravity convertSchema forces type OBJECT onto allOf/not-only roots — a string allOf root becomes unsatisfiable, a not root is narrowed to objects #1667 (closed) —
type: "OBJECT"forced ontoallOf/not-only roots. - Contradicted: fix(ai): moonshot tool-schema projection drops
prefixItemswhenitemsis also present — position constraints are silently replaced #1574 (open) — Moonshot's projection inpackages/ai/src/protocols/utils/tool-schema.tsdropsprefixItemswheneveritemsis also present (moonshotNode:if ("items" in schema) return [], verified verbatim on current main), leavingitemsapplying to position 0 against the source. - Uncut recursion: fix(ai): Gemini schema normalize only cuts single-def $ref loops — mutually recursive $defs through required properties reach Gemini uncut #1585 (closed) — Gemini
normalizecut$refloops one def at a time; mutually recursive$defsreached Gemini uncut. - Upstream rejection: Antigravity door: tool-carrying sessions 400 on function_declarations schema (paths.items missing field) #1548 (closed) — live 400,
parameters.properties[paths].items: missing field; Antigravity door: Claude leaf (claude-sonnet-5-5) 400s on workhorse attempts, Anthropic-shaped error #1550 (open, blocked) — Anthropic-side 400 "JSON schema is invalid… draft 2020-12", and test(ai,core): meta-validate every built-in tool schema against the draft 2020-12 meta-schema after Anthropic translation — #1550's offender is still unnamed #1668 (open) records nobody has ever meta-validated the built-in schemas as translated, so the offender cannot be named.
The tell: convertSchema in antigravity.ts now carries five comment-cited special cases (#1548, #1550, #1556, #1573, #1667) accumulated in two days — each a correct local fix, none with a shared statement above it.
The contract
For every translator and every keyword, exactly one named disposition: preserved (projected schema accepts exactly what the source accepts), approximated, named (with its direction — widening or narrowing — recorded), or refused (conversion fails with a named error rather than emitting a schema that lies). Three rules over the tables: never silent (a keyword with no table row is a defect found by the corpus, not by an upstream 400); never inverted, never contradicted (#1573/#1574 are bugs in any table, not approximations); the table is the oracle (corpus tests assert the disposition table — the model of each translator's intended semantics — plus the target grammar where mechanically checkable: the 2020-12 meta-schema, the proto allowlist).
Translator inventory (verified): antigravity convertSchema; GeminiJsonSchema.normalize/cutLoops/cutDefCycles (utils/gemini-json-schema.ts); ToolSchemaProjection (utils/tool-schema.ts — moonshot, gemini, identity, objectRoot); Anthropic identity passthrough (anthropic-messages.ts:550, input_schema: tool.inputSchema verbatim); Gemini parametersJsonSchema (gemini.ts:284, after normalize). Per-translator tests exist (tool-schema-projection.test.ts, gemini-json-schema.test.ts, provider/antigravity.test.ts) but hold only their own fixes' cases — no shared corpus, no disposition stated anywhere. Dedupe: kind/design list is #587/#800/#992/#1127/#1345/#1525; the façade design #1146 owns server endpoint shapes, not packages/ai tool-schema projection — this design composes with it.
Phases
- Disposition tables + one fidelity corpus for the three translators — filed as a new sub-issue this run (tests + tables only, no production behaviour change; known-lossy rows for fix(ai): antigravity convertSchema drops const — a fixed-value parameter is sent upstream unconstrained #1557/fix(ai): moonshot tool-schema projection drops
prefixItemswhenitemsis also present — position constraints are silently replaced #1574 marked and linked, corpus asserts the table). - The two open known-lossy sites conform — adopts existing open fix(ai): antigravity convertSchema drops const — a fixed-value parameter is sent upstream unconstrained #1557 + fix(ai): moonshot tool-schema projection drops
prefixItemswhenitemsis also present — position constraints are silently replaced #1574 as sub-issues; both keep their own queue lifecycles (not re-enqueued); each landing flips its corpus row. - Built-in schemas meta-validated per target — adopts existing open test(ai,core): meta-validate every built-in tool schema against the draft 2020-12 meta-schema after Anthropic translation — #1550's offender is still unnamed #1668 as a sub-issue (queue/parked, work/assigned; lifecycle untouched); it is the grammar-check rule applied to the identity translator and dispositions Antigravity door: Claude leaf (claude-sonnet-5-5) 400s on workhorse attempts, Anthropic-shaped error #1550's remaining question.
- Not filed — inventory/disposition of every remaining schema path (bedrock, OpenAI-compatible family, gateway model-name sanitizers), a ratchet for new translators/keywords with no table row, and the refusal-semantics question (fail the request vs drop the tool vs widen deliberately; built-in vs MCP/plugin may differ). Open questions in full in the design doc.
No Drew decision is needed: the contract names existing, already-probed approximations instead of re-litigating them, and phase 1 changes no production behaviour.
🤖 Agent note · filed by repo-design-architect (opencode pass, 2026-11-04). Evidence verified singly via gh against main 618de2888f this run; the two code claims (SCHEMA_FIELDS without const; moonshotNode's prefixItems drop) were read verbatim from current main, not from the citing issues alone.
Acceptance criteria
- A single fidelity contract for provider tool-schema projections is written down where the translators live (
packages/ai): for each schema keyword class, whether the projection preserves it, translates it (and to what), or rejects it loudly — silent dropping, inverting, collapsing, or forcing is a contract violation. - Every open defect named in this issue (fix(ai): antigravity convertSchema drops const — a fixed-value parameter is sent upstream unconstrained #1557 const dropped, fix(ai): moonshot tool-schema projection drops
prefixItemswhenitemsis also present — position constraints are silently replaced #1574 prefixItems dropped, Antigravity door: Claude leaf (claude-sonnet-5-5) 400s on workhorse attempts, Anthropic-shaped error #1550 Anthropic 400) is either fixed under the contract or explicitly assigned its own follow-on issue linked here. - Regression coverage exists for the closed defect classes (fix(ai): antigravity convertSchema inverts boolean
falsesubschemas — a never-valid schema is sent upstream as unconstrained #1573, fix(ai): antigravity convertSchema collapses a multi-valued type union to its first member — type: ["string","integer"] is sent upstream as STRING only #1556, fix(ai): antigravity convertSchema forces type OBJECT onto allOf/not-only roots — a string allOf root becomes unsatisfiable, a not root is narrowed to objects #1667, fix(ai): Gemini schema normalize only cuts single-def $ref loops — mutually recursive $defs through required properties reach Gemini uncut #1585, Antigravity door: tool-carrying sessions 400 on function_declarations schema (paths.items missing field) #1548) expressed as contract tests over all provider projections, so a new provider inherits the checks. - The design doc (
files/designs/opencode-schema-projection-fidelity.md) is updated to match the adopted contract, or superseded by an in-repo doc that this issue links.
Activity
prefixItemswhenitemsis also present — position constraints are silently replaced #1574dezren39 commentedon Nov 5, 2026
🤖 Agent note · task pickup
3fe37e8a-c61b-419c-9e97-769ecd264d5ddezren39 commentedon Nov 5, 2026
Triage verdict — task 10182 (2026-11-05)
Keep #1684 OPEN as the design/evolution tracker; do not close as duplicate or already done. Current
origin/mainis8185486080ab686d3a160f16deb5357f1199fda3. The design proposal is present in the issue body (its companion design file is in the goal workspace). No PR directly owns #1684. Phase 1 is complete: child #1685 closed via PR #1723, merged 2026-11-05T11:39:47Z (mergeb2dc3aa). Related phase state: #1574 and #1668 are closed; #1557 remains open. The design itself explicitly leaves phase 4 (remaining inventory/ratchet/refusal semantics) unfiled.Disposition / order: keep #1557 as the live phase-2 fix; do not re-enqueue completed #1685 or #1668. Keep #1684 open until #1557 is resolved and phase 4 is either split into a follow-up or explicitly deferred in this tracker. There is no competing PR or Drew-only blocker; no new task was created by this triage.
🤖 Agent note · triaged
3fe37e8a-c61b-419c-9e97-769ecd264d5ddezren39 commentedon Nov 5, 2026
Intake review
🤖 Agent note · filed
files/designs/opencode-schema-projection-fidelity.md(goal workspace)Related issues (from the body, verified as named there): #1557 (open — const dropped by antigravity), #1574 (open — prefixItems dropped by Moonshot), #1550 (open, blocked — Anthropic 400); closed defect classes #1573, #1556, #1667, #1585, #1548. Nothing merged since filing adopts the contract, so the design question is still live — not closed as solved.
Risks: A contract written but not enforced by shared tests will drift per provider again; the acceptance criteria therefore require contract tests inherited by every projection.
Suggested approach: Land the contract as a shared test harness first (one suite run against every provider projection), then fix #1557 and #1574 under it; #1550 stays blocked on its own track.