Repository navigation
fix(downgrader): keep a $ref to a component named items or additionalProperties - #370
Conversation
3.1 has no default for a missing or unmapped discriminating value, so tools following the kept discriminator (Hey API types and Zod) pin the default schema's tag to its name and reject values 3.2 routes to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
… none of 3.1 lets a Discriminator Object carry specification extensions, 3.0 does not, so an `x-` key there made the 3.0 output invalid. Microsoft.OpenApi's 3.0 reader reports it as an error, for example on the `x-discriminator-default` that SwaggerHub's NPP and APDS specs write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…to claude/project-thread-ttohne
…to claude/project-thread-ttohne
…m tag with a default required Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…he tag default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…to claude/project-thread-ttohne
…lue enum tag Hey API pins each discriminator branch to its tag when the tag is a one-value enum, as a const becomes, so its runtime plugins reject a value that leaves out a tag with a default (Pydantic's Literal default). From 3.1's const it uses the branch as written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…to claude/project-thread-ttohne
…r 3.0 rules Python's openapi-schema-validator and jsonschema-rs with draft 4 rules reject a number with a zero fraction for type integer, as 3.0.3 defined an integer; 3.0.4, which the output names, accepts it as 3.1 does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…e readOnly
Hey API 0.99 drops a `{ type: "object" }` branch with nothing else in it from an
`anyOf` in a schema that holds a readOnly or writeOnly property, so its types,
Zod and Valibot reject an object for a `type` list with "object" in 3.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…to claude/project-thread-ttohne
…Properties
A $ref into `items`, `additionalProperties`, `$defs` or `definitions` is
replaced by its target, but the check read the whole pointer, so a
component named one of those words, as Celigo names a recursive mapping
row `items`, was inlined at every use and its recursion cut to {} one
level down. The segment after `#/components/<type>/` is a name, so only
the part after it is checked now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
isInlinedstrips the component prefix —packages/downgrader/src/v3.1-to-v3.0.ts:298now removes#/components/<type>/<name>before testing for/$defs/,/definitions/, andINTO_REPLACED, so a$refto a component nameditemsoradditionalPropertiesis no longer mistaken for a$refinto a keyword. The%check and the#/webhooks//#/components/pathItems/prefixes still read the whole ref, as documented.- Regression test —
packages/downgrader/tests/v3.1-to-v3.0.test.ts:2935pins component namesitems,additionalProperties,$defs,definitions, a response nameditems, and that a keyword after the name (items/properties/rows/items) is still inlined.
I verified locally: all 309 downgrader tests pass, v3.1-to-v3.0.ts branch coverage is unaffected for the new code (the only uncovered branch, line 1718, is in convertSecurity and pre-existing), and pnpm run check (sherif, oxlint, oxfmt, tsc) passes.
The only behavior narrowed is a $ref pointing directly at a $defs/definitions map under a component whose name is itself $defs/definitions — not a valid schema reference, and refs into removed keywords dangling is already a documented limitation.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…t type stays as written (#375) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01VVaw7RYWCsn8myBE4Rncuq Test-only follow-up to #374. Stryker on #374's `isInlined` lines (`v3.1-to-v3.0.ts:298-325`) left 4 survivors. One is the known equivalent `name === 0` -> `false`, unchanged since #370. The other three are test gaps: - `'#/components/schemas/'` -> `''`: the renamed-key check would then apply to every component type, so a schema `$ref` to `#/components/parameters/Query/schema` (the key `schema` is no Schema keyword) would be inlined. No test kept it as written. - `next === -1` -> `false`, and `-1` -> `+1`: when the ref ends at the key, the key loses its last letter, so `#/components/schemas/Negated/not` reads `no`, counts as renamed and is inlined. No test held a `$ref` that ends at a key 3.0 keeps. This adds both cases to the "replaces a $ref into a key that becomes an extension" test. Each must stay a bare `$ref` (`toStrictEqual`). - `pnpm exec vitest run packages/downgrader`: 311 passed - Stryker on the same lines with this test: 68 of 69 killed (was 65); the one left is the known equivalent - `pnpm run check` passes Separately, refreshing the 72 fuzz dumps on defcf13 changed 0 outputs. --- _Generated by [Claude Code](https://claude.ai/code/session_01VVaw7RYWCsn8myBE4Rncuq)_ --------- Co-authored-by: Claude <noreply@anthropic.com>

Requested by dinwwwh · project thread
What
isInlinedreplaces a$refintoitems,additionalProperties,$defsordefinitionswith its target, because the conversion can replace what sits there. It tested the whole pointer, though, so a$refto a component named one of those words also matched, for example#/components/schemas/items.Such a component was then inlined at every use. When it is recursive, the inlined copy is cut to
{}one level down, so the output loses every constraint below that level and the named type is gone.Now only the part after
#/components/<type>/<name>is checked, since that segment is a name and not a keyword:#/components/schemas/items/properties/rows/itemsis still replaced.%check and the removed-part prefixes still look at the whole$ref.Found in real docs
Celigo's npm specs (Tools, Imports, Exports, APIs, AI Agents, Async Helpers) name their recursive mapping row
items. Before this change,Mappings.items.properties.mappings.itemsbecame the inlined row, with its nestedmappings.itemsset to{}.A new symbolic oracle found it: json-schema-diff, with recursive schemas unrolled 3 levels. It flagged those components, and after this change it flags none.
Repro:
Tests
items,additionalProperties,$defsanddefinitions, plus a response nameditems, all keep their$ref. A$refwith a keyword after the name is still replaced. The test fails on main.srcstays at 100%.pnpm run checkpasses.Generated by Claude Code