Skip to content

fix(downgrader): inline 3.1 to 3.0 $refs to a boolean additionalProperties - #46

Closed
dinwwwh wants to merge 3 commits into
mainfrom
claude/youthful-gates-hwcl0r
Closed

dinwwwh wants to merge 3 commits into
mainfrom
claude/youthful-gates-hwcl0r

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 2, 2026

Copy link
Copy Markdown
Member

Problem

3.0 keeps a boolean additionalProperties, the one place it allows a boolean. But a 3.0 Schema Object is never a boolean, so a $ref must not resolve to one.

danglesIn counted a boolean in the output as present, so a $ref to that location was kept and resolved to true or false:

downgradeSchemaV31ToV30({
  additionalProperties: false,
  properties: { a: { $ref: '#/additionalProperties' } },
})
properties.a valid 3.0
3.1 input { $ref: '#/additionalProperties' }, which finds false (matches nothing) n/a
main unchanged, still resolves to false no
this PR { not: {} } (matches nothing) yes

The same happens:

  • in downgradeSpecV31ToV30, for components.schemas = { M: { type: 'object', additionalProperties: false }, X: { $ref: '#/components/schemas/M/additionalProperties' } }, where X stayed a $ref to false.
  • under not, where not: { $ref } pointing at a true resolved to not: true.

Everywhere else, boolean schemas already become {} or { not: {} }, including a false inlined from $defs. The README also says $refs to boolean schemas are handled.

Fix

  • downgrade (packages/downgrader/src/shared.ts) takes an optional isTarget predicate. danglesIn treats an output value that fails it like a missing one, so the $ref is inlined with the converted target.
  • 3.1 → 3.0 passes isRefTarget, which rejects booleans. 3.2 → 3.1 keeps the default, since boolean schemas are valid 3.1 targets.
  • The check looks at the output, so a $ref is inlined only when it must be. items: false becomes { not: {} } in place, so { $ref: '#/items' } stays as written. A $ref to a component like Never: false stays as written too.
  • PLACEHOLDERS and placeholder() were only used by 3.1 → 3.0, so they move from shared.ts into v3.1-to-v3.0.ts and join isRefTarget. danglesIn is left with one rule: to === undefined || !isTarget(to).

Tests

  • schema/references.test.ts, new describe('references to a boolean additionalProperties'):
    • a $ref to false
    • not over a $ref to true
    • a $ref with siblings, which moves into allOf
    • a $ref to the location that now holds the inlined schema, which stays as written
    • a $ref to items: false converted in place, which stays as written
  • spec/components.test.ts: the M/X case from components.schemas.
  • The 5 inlining tests fail on main. pnpm test (688 passed), pnpm lint, and pnpm type:check are clean on the merge with main.

Side effects

The rule covers every kind of reference in 3.1 → 3.0, because no 3.0 reference may resolve to a boolean. Both cases below were already invalid 3.0 output before this PR.

  • A discriminator mapping entry pointing at a boolean additionalProperties is now removed. Entries pointing into an inlined $defs are already removed the same way.
  • A non-schema Reference Object pointing at a boolean (for example a Parameter $ref), which is already invalid in 3.1, is now replaced by the boolean instead of kept. $refs to boolean targets in removed parts, such as webhooks, already behaved this way.
  • A document with such a $ref takes one more conversion pass of the downgrade loop. This is the same cost as other $refs that are found to dangle only after the first pass.

Interaction with middleapi/openapi-spec#44

That PR replaces placeholder() with replace() and also edits danglesIn, so whichever PR merges second has a conflict:

replace() cannot cover this case: additionalProperties: false is not replaced, it is kept as written.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JZezsg72P27RPnwVijmprt


Generated by Claude Code

claude added 3 commits October 2, 2026 09:47
…alProperties

3.0 keeps a boolean `additionalProperties`, but a Schema Object is never a
boolean, so a `$ref` kept pointing at one resolved to `true` or `false`.
The 3.1 to 3.0 converter now treats a location whose output is a boolean
as dangling, so such a `$ref` is replaced by `{}` or `{ not: {} }`, like
any other boolean schema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZezsg72P27RPnwVijmprt
…t check

`danglesIn` asked two questions for one rule: a placeholder check
hard-coded in shared.ts, and the converter's `isTarget`. Placeholders are
3.1 to 3.0 only, so the WeakSet moves into that converter and joins
`isRefTarget`, leaving `danglesIn` with the injected check alone. Also
trims the new tests to one table and the reported spec case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZezsg72P27RPnwVijmprt
…-hwcl0r

# Conflicts:
#	packages/downgrader/src/shared.ts
#	packages/downgrader/src/v3.1-to-v3.0.ts
#	packages/downgrader/tests/v3.1-to-v3.0/schema/references.test.ts
@pullfrog

pullfrog Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

your Pullfrog Router balance is empty, and this repo has no provider key to fall back on, so the agent never ran.

To fix, any one of: add a payment method or top up your Router balance · add a provider API key (GitHub Actions secret or Pullfrog secret) · switch this repo to a free model.

Top up Router → · Model settings → · Setup docs → · Ask in Discord →

Pullfrog  | Rerun failed job ➔ | View workflow run | via Pullfrog | 𝕏

dinwwwh commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

The pullfrog check is red, but this PR didn't cause it. Its comment above says the Pullfrog Router balance is empty and the repo has no provider key, so the review agent never ran. pullfrog fails the same way on #45.

No code change can fix this. It needs a top-up, a provider key, or a free model in the Pullfrog settings. I didn't re-run the check, because it would fail the same way until that's done.

The rest of CI is green on 5be01b5: lint_and_typecheck, and test_matrix on Node 20, 22, 24 and 26.


Generated by Claude Code

@codecov

codecov Bot commented Oct 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@dinwwwh dinwwwh closed this Oct 3, 2026
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