Conversation
Inlining shared one converted target across every reference site, and re-converted targets whose original survives (a moved itemSchema, a shifted parameter list), so $id, $anchor, and $dynamicAnchor appeared several times and the official validator and Ajv rejected the 3.1 output. Only the first copy of a schema now keeps its identifiers; later copies drop them, so the chained 3.0 output also stays valid.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — reviewed the full 3.2 → 3.1 identifier-uniqueness fix at head becce35, including the shared-engine cache change, the converter-side stripping, the README updates, and all new tests.
Context.identifiedset added — source-schema identity tracked per pass (downgrade) and per speculative probe (convertsToDrop).inlinecache gate — recordsctx.identified.sizebefore converting an inline target and caches the result only when the size is unchanged, so an identifier-bearing target is converted a second time (stripped) and shared by later sites.finishSchemastrips repeats — a repeated source schema loses$id/$anchor/$dynamicAnchor; the first-converted copy keeps them.- README — documents the stripping and the relative-
$refconsequence for a copy that loses its$id. - Tests — new
shared.test.tsinline-cache regression, twov3.2-to-v3.1.test.tscases (repeatedmediaTypes/$refsiblings, moveditemSchema+ shifted parameter list), a new chained 3.1 → 3.0e2e.test.tsdocument, and an$id/$anchorenrichment of the existing shared-schema test.
I verified vitest run (491 passed), eslint ., and pnpm type:check are clean at this commit. To confirm the new tests are real coverage rather than theatre, I reverted the finishSchema block and the inline cache gate: exactly the four new tests fail and the rest pass. I also probed a suspected over-strip edge (a schema first converted inside a parameter that is then dropped, and also present in components.schemas) — the surviving component copy kept its $anchor, since ctx.seen memoization reuses the converted schema even when the parameter is discarded, so no over-strip occurs.
One non-blocking observation for context: the identifier attaches to whichever copy converts first (document order), so with paths serialized before components the $id survives on the inlined copy rather than the component. Uniqueness still holds and the README's "first copy" wording covers it, so no change requested.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

Previously, 3.2 → 3.1 could write the same
$id,$anchor, or$dynamicAnchorinto its output more than once. That happened when a schema was inlined in several places, or inlined while its original was still in the output. The official validator then rejected the 3.1 document ($id ... defined more than once), and Ajv refused to compile it (resolves to more than one schema). Now only the first copy of a schema keeps its identifiers, so the output validates as 3.1 and, chained, as 3.0.Fixes
components.mediaTypesentry, or to a schema inside it, no longer repeat its identifiers.itemSchema, or to a parameter list that lost aquerystringentry, no longer duplicates the identifiers the original keeps.$ref: <$id>, so the 3.2 → 3.1 → 3.0 chain adds no reference that 3.0 cannot resolve.Performance
inchecks per schema.Testing
packages/downgrader/src; lint and type-check pass.main. They cover repeated media types,$refwith siblings, a moveditemSchema, a shifted parameter list,$dynamicAnchor, and the inline cache rule.$idand$anchorpasses the official validator as 3.1 and, chained, as 3.0.Known limits
$idresolves relative$refs inside it, such as#/$defs/x, against the enclosing base. This is documented in the README. Such documents were already invalid before this change.