Skip to content

fix(downgrader): drop the content keywords of an untyped schema with subschemas in 3.0 - #149

Merged
dinwwwh merged 2 commits into
mainfrom
claude/project-thread-o4731q
Oct 27, 2026
Merged

dinwwwh merged 2 commits into
mainfrom
claude/project-thread-o4731q

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

What

Some real 3.1 documents mark a JSON or XML body schema with its media type:

{ "allOf": [{ "$ref": "#/components/schemas/AbuseTicketCreate" }, { "description": "..." }], "contentMediaType": "application/json" }

In 2020-12, contentMediaType only applies to strings, so this is just the object. The schema has no type, so finishSchema gave it format: "binary" and type: "string". In 3.0 nothing matched that (a value had to be a string and the object at once), and tools read the body as a file upload. A bare { $ref, contentMediaType } went the same way.

Change

An untyped schema gains format and type: "string" from the content keywords only when it has none of $ref, allOf, anyOf, oneOf, properties, items or prefixItems. Otherwise the content keywords are just dropped, as they already were beside type: "object". Bare file parts such as { contentMediaType: "image/png" } keep becoming a binary string. The README row for contentEncoding and contentMediaType says so; the Limitations line on untyped schemas gaining type: "string" still holds as written.

Checks

  • New test covers each of the seven keywords; it fails on main. pnpm run check, pnpm test pass, coverage stays 100%, no snapshot changes.
  • Itential's GoDaddy adapter (@itentialopensource/adapter-godaddy 1.0.12, report/adapter-openapi.yaml, 3.1.0): format: binary beside allOf goes from 606 to 0, and request bodies that were binary strings from 21 to 0. The 594 left are bare { contentMediaType: "application/javascript" } responses, which stay as they were.
  • Perf: the new check runs only when a schema has content keywords. GoDaddy median in one process with gc, interleaved, 100 rounds: 7.28 ms before, 7.06 ms after (noise).

🤖 Generated with Claude Code

https://claude.ai/code/session_01VVaw7RSkvUE8NjM89RakVV

…subschemas in 3.0

An untyped schema with `contentMediaType` gained `format: binary` and
`type: string`, even when a `$ref`, `allOf`, `anyOf`, `oneOf`,
`properties` or `items` beside it showed it holds an object, as
Itential's adapter documents mark JSON and XML bodies with their media
type. In 3.0 nothing matched it, and tools read the body as a file
upload. Such a schema now drops the content keywords, as one with
`type: object` already did. Bare file parts keep the binary string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7RSkvUE8NjM89RakVV
@dinwwwh dinwwwh self-assigned this Oct 27, 2026
@codecov

codecov Bot commented Oct 27, 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 27, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing claude/project-thread-o4731q (11d886a) with main (3676052)

Open in CodSpeed

@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 critical issues — the core fix is correct. One same-class omission noted inline.

Reviewed changes

  • Predicate for untyped content-keyword schemas — hasSubschemas (v3.1-to-v3.0.ts:1090) gates the format/type: "string" injection on $ref, allOf, anyOf, oneOf, properties, items; the rest of the content-keyword handling is unchanged.
  • README row — documents that the keywords are dropped (rather than turned into a binary string) when the schema holds subschemas.
  • Test — covers all six keywords, plus the bare { $ref, contentMediaType } case.

The change is correct and well-targeted: the content keywords are string-only annotations in 2020-12, so forcing type: "string" on a schema that already describes an object/array via subschemas both rejected values the source accepted and made 3.0 tools read the body as a file upload. I ran vitest run packages/downgrader/tests/v3.1-to-v3.0.test.ts (113 passing) and separately probed the predicate.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread packages/downgrader/src/v3.1-to-v3.0.ts Outdated
@pullfrog

pullfrog Bot commented Oct 27, 2026

Copy link
Copy Markdown

Correction to my review above: the inline suggestion attached to my first review (packages/downgrader/src/v3.1-to-v3.0.ts) was garbled — it contained stray placeholder functions and would break the file. Please ignore that suggestion block entirely. The intended, corrected change replaces the two lines at v3.1-to-v3.0.ts:1098-1099 with:

    schema.properties !== undefined ||
    schema.items !== undefined ||
    schema.prefixItems !== undefined

Rationale (unchanged): hasSubschemas omits prefixItems, so an untyped schema such as { prefixItems: [{ type: "string" }], contentMediaType: "application/json" } still gains format: "binary" + type: "string". Since convertTuple folds prefixItems into items in the output, this is the same failure mode the PR fixes — the output ends up { items, format: "binary", type: "string" }, rejecting the arrays the source accepted. patternProperties/not are same-class omissions, but prefixItems is the direct analog to the already-listed items.

Otherwise the fix is sound and the tests pass (vitest run packages/downgrader/tests/v3.1-to-v3.0.test.ts, 113 passing).

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

… schema

A tuple with no `items`, written with `prefixItems` alone, still gained
`format: binary` and `type: string` from `contentMediaType`, while one
with `items: false` beside it did not.

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

@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

  • prefixItems added to the subschema predicate — hasSubschemas (v3.1-to-v3.0.ts:1092) now also returns true for prefixItems, so an untyped schema with a content keyword and a tuple keeps its converted items instead of being forced to type: "string".
  • Test — covers { prefixItems: [...], contentMediaType } yielding { items: {} }, completing the set of keywords the earlier review noted as missing.
  • README row — now lists prefixItems among the keywords that suppress the type: "string" injection.

This delta directly addresses the one prior inline thread (prefixItems omitted from hasSubschemas); the author's fix is correct — the tuple is an array, not a binary string, so dropping the content keywords is the right outcome. The full diff remains well-targeted, and the change is only consulted for untyped schemas. Ran vitest run packages/downgrader/tests/v3.1-to-v3.0.test.ts (113 passing, including the new case).

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

@dinwwwh
dinwwwh merged commit 9f9e19b into main Oct 27, 2026
11 checks passed
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