Repository navigation
fix(downgrader): replace a $ref into a schema root's key that becomes an extension - #377
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
…to claude/project-thread-ttohne
YAML reads `type: [string, null]` with a JSON null, not the string "null". The 3.0 output dropped that entry, so it rejected null; TCGdex writes its nullable fields this way. Count it as "null" when checking a type list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
NocoDB's 3.1 document groups schemas under one component and refers to them as `#/components/schemas/FieldOptions/SingleLineText`. 3.0 allows no such keys, so they became `x-` extensions and all 35 of those `$ref`s dangled. Replace a `$ref` through such a key, right under a component schema, with its converted target, as for `$defs`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…to claude/project-thread-ttohne # Conflicts: # packages/downgrader/tests/v3.1-to-v3.0.test.ts
…ches Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
… an extension downgradeSchemaV31ToV30 renames a root key that no keyword names, such as SchemaStore-style `CustomTypes`, to `x-CustomTypes`, but left a `$ref` such as `#/CustomTypes/Path` as written, so it pointed at nothing. #374 already replaces such refs under a component schema; the same rule now applies to the root of a converted schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs
…to claude/project-thread-ttohne
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.
ℹ️ Minor suggestions only — one stale docs bullet for the newly documented scope.
Reviewed changes
- Root-key extension refs —
isInlinednow inlines a standalone schema's$refwhen the first path key becomes a 3.0 extension, so#/CustomTypes/Pathno longer dangles afterCustomTypesis renamed tox-CustomTypes. - Shared helper — the keyword-rename test is factored into
becomesExtensionand reused by the existing component-schema branch. - Per-run state —
schemaRootrecords the schema entry point and is cleared with the other module-level pass state in the samefinally. - Docs and test — the README row is updated and a test pins the new behavior alongside a kept
#/properties/...ref and#.
The code path checks out: downgradeSpecV31ToV30 leaves schemaRoot undefined, so document conversion is untouched, and refs into removed keywords (#/prefixItems/0) still dangle as documented. All 313 downgrader tests and uncheck on the touched files pass.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
…tation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7PbWyR4kZ18CjgHzqs

Requested by dinwwwh · project thread
Problem
#374 fixed
$refs into a key that 3.0 renames to an extension, but only under a component schema in a document.downgradeSchemaV31ToV30renames a root key that no keyword names in the same way, yet left the$refas written:The output
$refpoints at nothing. This happens in real schemas. I ran about 106k standalone schemas from the hunt corpora throughdowngradeSchemaV31ToV30, and also converted each one as a component of a document. They differ in 7 schemas from 2 projects, and every difference is this case (#/CustomTypes/Path,#/CustomTypes/agentArgument,#/Assets).Fix
isInlinednow gives a$ref's first key the same test when the root being converted is a schema, which is the test #374 gives the key after#/components/schemas/<name>/. That test moves into a smallbecomesExtensionhelper. The root is recorded in module state beside the other per-run sets and cleared in the samefinally.What stays the same:
#/properties/aor#/not, stay bare.$ref: '#'stays as written.#/paths/...or a custom root key of the document, are unaffected.I updated the README row on keywords 3.0 does not define.
Checks
pnpm run checkpasses.Generated by Claude Code