Skip to content

fix(downgrader): count examples and $comment as annotations beside a tagged $ref - #204

Merged
dinwwwh merged 1 commit into
mainfrom
claude/project-thread-efhq6v
Oct 29, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/project-thread-efhq6v

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 29, 2026

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

What

#150 and #174 keep a $ref with only annotations beside it bare in 3.0 when it is a branch of a tagged union, or points to a schema whose discriminator stays. The check, isAnnotatedRef, reads the 3.1 input but only counted the 3.0 annotation names (example, description, ...) and x- keys. So the 3.1 annotations examples and $comment made it fail, and { $ref: Tagged, examples: [...] } became { allOf: [{ $ref: Tagged }], example: ... }.

Real case: the Anthropic API's BetaWebhookEvent.data is { $ref: BetaWebhookEventData, examples: [...] }, where BetaWebhookEventData is a 22-way oneOf with a discriminator and mapping. On main, openapi-typescript then writes data: Omit<components["schemas"]["BetaWebhookEventData"], "type">, dropping the tag so code can't narrow on type. With this change it writes data: components["schemas"]["BetaWebhookEventData"].

Change

  • isAnnotatedRef also accepts examples and $comment. As with example, they are dropped beside the bare $ref.
  • The existing tests for both cases (tagged branch, $ref to a tagged target) now include examples and $comment; both fail on main.
  • README $ref row names the examples the Anthropic API writes there.

Checks

  • pnpm run check, pnpm test (364 tests), coverage 100%.
  • Over the hunt corpus (19,775 docs) only this one site changes; a value oracle on the Anthropic doc finds no accepted value lost.

Generated by Claude Code

…tagged $ref

A `$ref` to a tagged union with only annotations beside it, and such a
branch of a tagged union, stays the bare `$ref` in 3.0. The check read the
3.1 input but counted only 3.0 annotation names, so the `examples` the
Anthropic API writes beside `BetaWebhookEvent.data` kept the `allOf`
wrapper, and openapi-typescript then left out the `type` tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VVaw7Pe42johGviNe9drKR
@dinwwwh dinwwwh self-assigned this Oct 29, 2026
@codecov

codecov Bot commented Oct 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 1 benchmark is dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 29 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/project-thread-efhq6v (5e22405) with main (a16a798)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • isAnnotatedRef accepts examples and $comment — packages/downgrader/src/v3.1-to-v3.0.ts:358 now treats the two 3.1 annotation keys as annotation-only siblings, so a $ref beside them stays bare in a tagged union or when pointing at a schema whose discriminator survives.
  • Tests extended — both tagged-ref tests in packages/downgrader/tests/v3.1-to-v3.0.test.ts now include examples/$comment on the ref, and the branch/property cases assert the bare $ref.
  • README $ref row — names the Anthropic examples written beside the property holding a tagged union.

Both call sites are tagged-only (convertTaggedBranch, and convertSchema's isAnnotatedRef && isTaggedRef guard), and examples/$comment are validation-neutral, so the change cannot widen acceptance — it only drops the siblings as documented. Verified locally: vitest run packages/downgrader → 287 passed.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit e92cd8f into main Oct 29, 2026
11 checks passed
dinwwwh added a commit that referenced this pull request Oct 29, 2026
…nd nullable targets (#205)

<!-- claude-projects-attribution: {"github_login":"dinwwwh"} -->
_Requested by **dinwwwh** · [project
thread](https://claude.ai/code/project/chan_01UcKMz91tMarPV1kWLVgLYE?thread=cmsg_01UcKMz91tMarPV1kWLVgLYE7TKScycrTLZfzZMNY1F8sa)_

Three spots in the 3.1 to 3.0 converter check 3.1 input keys for
annotations, and two of them used a shorter list than "every
annotation":

1. **Records keyed by an enum** (`enumNames`, from #100) accepted only
`enum`, `const`, `title`, `description`, and `type: "string"` in the key
schema. Any other annotation, such as `example`, `examples`, `default`,
`deprecated`, `$comment` or an `x-` key, made it bail, so the 3.0 record
allowed any key.
- Meilisearch 1.15.2 (utoipa): `BatchStats.status` and
`BatchStats.types` are keyed by an enum with `example: "processing"`.
openapi-typescript gave `[key: string]: number`. With this change it
gives `enqueued?: number; processing?: number; ...`.
- ElevenLabs (Pydantic): `ConversationInitiationSource` has `default:
"unknown"` beside its 27-value enum. The records keyed by it now get 27
properties, and the discriminated `oneOf` responses that hold them keep
their `oneOf` instead of becoming `anyOf`.
- oRPC with Zod: `z.record(E, V)` where `E` has `.meta({ id, examples
})` or `.meta({ id, deprecated: true })` now gets one property per name,
as a described `E` already did.
2. **A nullable `$ref` to a nullable `anyOf`** (`admitsNull`, from #203)
allowed only 3.0 annotations beside the target's `anyOf`, so the
`examples` that Zod's `.meta` writes there kept the `anyOf`. NSwag reads
that as an empty class.

`isAnnotatedRef` (#204) already had the longer list. A new
`isInputAnnotation` helper (`examples`, `$comment`, or a 3.0 annotation)
now serves all three.

The README row for enum-keyed records now says that annotations beside
the names are dropped. The nullable-reference row already said "only
annotations", so it is unchanged.

Evidence from the issue hunt: on a 19,775-doc corpus the `enumNames`
change touches 7 docs at 64 sites, with no throws and no values lost per
the value oracle. The `admitsNull` change touches none of those docs,
but it fixes the oRPC repro.

### Tests

- `pnpm run check` and `pnpm test` pass (364 tests). Downgrader coverage
stays at 100%.
- The new assertions fail on main: the enum-key test, the `$ref`-to-enum
test, and the nullable `anyOf` target test.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VVaw7SWqGqen9de9vgJcko)_

Co-authored-by: Claude <[email protected]>
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