Skip to content

fix: validate discriminated oneOf schemas against only the branch selected by the discriminator in the drift command - #2957

Open
sobanieca-redocly wants to merge 5 commits into
mainfrom
worktree-fix-drift-issues
Open

fix: validate discriminated oneOf schemas against only the branch selected by the discriminator in the drift command#2957
sobanieca-redocly wants to merge 5 commits into
mainfrom
worktree-fix-drift-issues

Conversation

@sobanieca-redocly

@sobanieca-redocly sobanieca-redocly commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Fix for handling oneOf schemas with a discriminator inside drift command. Extended list of well known headers from 3rd party providers.

Reference

Testing

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Low Risk
Scoped to drift validation and header filtering; fallback preserves prior validation when discriminators cannot compile.

Overview
Fixes false positives in drift schema-consistency when traffic matches a oneOf + discriminator payload but Ajv previously validated every branch.

SchemaValidator now compiles with Ajv discriminator: true, so only the branch for the discriminator value is checked. If compilation fails (loosely defined discriminators), a second Ajv instance with discriminator: false compiles the schema so behavior stays like before.

Undocumented-header handling in http.ts adds built-in ignores for x-amzn-*, x-github-*, and x-hub-signature / x-hub-signature-256 (replacing a lone x-amzn-trace-id exact entry with the prefix). Tests cover both areas.

Reviewed by Cursor Bugbot for commit 10645f2. Bugbot is set up for automated code reviews on this repo. Configure here.

…ected by the discriminator in the drift command
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 10645f2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/cli Patch
@redocly/openapi-core Patch
@redocly/respect-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 72.39% (🎯 69%) 8740 / 12072
🔵 Statements 72.07% (🎯 69%) 9092 / 12615
🔵 Functions 77.09% (🎯 73%) 1730 / 2244
🔵 Branches 63.85% (🎯 61%) 5887 / 9219
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/drift/engine/schema-validator.ts 78.08% 64.81% 91.66% 77.77% 19, 33, 39, 45, 87, 91, 100, 164, 172-179, 201-209
packages/cli/src/commands/drift/utils/http.ts 55.67% 47.14% 76.47% 55.67% 81, 91-92, 97, 103-112, 117, 121-124, 137, 143, 149-184, 196, 202, 229-241, 260, 277
Generated in workflow #10875 for commit 10645f2 by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x (Fastest) ▓ 1.00x ± 0.01 ▓ 1.01x ± 0.02
cli-next ▓ 1.01x ± 0.01 ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)

@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ce416ab. Configure here.

@sobanieca-redocly
sobanieca-redocly marked this pull request as ready for review July 22, 2026 14:22
@sobanieca-redocly
sobanieca-redocly requested review from a team as code owners July 22, 2026 14:22
Comment thread .changeset/drift-infra-header-ignores.md Outdated
Comment thread .changeset/drift-discriminator-validation.md Outdated
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
// Ajv rejects loosely defined discriminators at compile time (for example
// when the tag property has no `const`/`enum` or is not required); such
// schemas fall back to being validated against every oneOf branch.
this.fallbackAjv = new AjvConstructor({ ...ajvOptions, discriminator: false });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How does the lint handle this case? i cant find any fallback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Lint doesn't have a fallback — it just reports that Ajv couldn't compile the schema (the try/catch is in validateExample in packages/core/src/rules/utils.ts), which is fair there, since it points to an issue in the OAD itself. Drift is a different story. Lint only compiles a schema when it has an example attached, so even a spec with no lint issues can contain a loose discriminator. Without the fallback, drift would then report "Schema compilation failed" for every exchange hitting that schema — errors about perfectly valid traffic. The fallback is used in compileSchema() at the bottom of this file: if the strict instance throws, the schema is recompiled with discriminator: false, which validates against every oneOf branch — same as drift did before this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, the const/enum + required constraints come from Ajv, not the spec (docs), so the fallback is ok

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Reviewed the diff. Overall this looks good — no blockers, all 44 checks green, sound design. A few optional nits and one question below.

What it does: fixes false positives in drift / schema-consistency where a oneOf payload was validated against every branch, so a discriminated union produced errors from the non-matching branches. Now validation goes only through the branch selected by the discriminator (Ajv discriminator: true), with a fallback to the previous "validate all branches" behavior when Ajv can't compile the schema. Separately broadens the built-in ignored-header list (x-amzn-*, x-github-*, x-hub-signature).

Notes:

  • (question) schema-validator.ts:198 — the bare catch {} in compileSchema falls back on any compile error, not only discriminator-related ones. Functionally safe (the two Ajv instances differ only in the discriminator flag, so an unrelated compile error throws identically and still surfaces as "Schema compilation failed"), but worth confirming the broad catch is intentional vs. narrowing it to discriminator errors.
  • (nit) Ajv's discriminator: true keys off const/enum in each branch and ignores OpenAPI discriminator.mapping. A spec that encodes the value only in mapping won't compile → falls back to all-branches (exactly what the "loose discriminator" test covers). Acceptable for a consistency check, but a real behavioral nuance.
  • (nit) interaction with relaxRequiredForTarget (schema-validator.ts:186):`` if the discriminator property is readOnly/`writeOnly` it gets stripped from `required` for that target, the strict compile throws, and it silently falls back to all-branches validation for that target only. Graceful, but a subtle asymmetry — maybe worth a one-line comment.
  • (nit) the broadened IGNORED_UNDOCUMENTED_HEADER_PREFIXES is unconditional (no way to un-ignore those headers). Consistent with the existing built-in list, documented in the changeset, and drift is experimental — flagging only so it's a conscious call.

Tests: good — the new schema-validator.test.ts covers valid payload, errors from only the selected branch, and the loose-discriminator fallback, plus a test for the ignored headers. Minor gaps: no test for a discriminator value absent from every branch, none for the double-throw path (both Ajv instances fail → "Schema compilation failed"), and none for the readOnly + relaxRequiredForTarget interaction above.

Backward compatibility checks out — the fallback faithfully reproduces the old behavior (strict: false treated discriminator as an unknown keyword), so only the intended fix changes. Fine as a patch on an experimental command.


Generated by Claude Code

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 10645f2. Configure here.

// Ajv rejects loosely defined discriminators at compile time (for example
// when the tag property has no `const`/`enum` or is not required); such
// schemas fall back to being validated against every oneOf branch.
this.fallbackAjv = new AjvConstructor({ ...ajvOptions, discriminator: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing schema validation disable

Medium Severity

Enabling discriminator: true without validateSchema: false leaves Ajv's default schema checks on. Ajv's discriminator meta-schema disallows properties beyond propertyName and mapping, so valid OpenAPI discriminators with defaultMapping or extensions fail compilation and silently use the no-discriminator fallback, keeping the noisy oneOf false positives this change aims to remove. Elsewhere in the repo, discriminator: true is paired with validateSchema: false.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 10645f2. Configure here.

// Ajv rejects loosely defined discriminators at compile time (for example
// when the tag property has no `const`/`enum` or is not required); such
// schemas fall back to being validated against every oneOf branch.
this.fallbackAjv = new AjvConstructor({ ...ajvOptions, discriminator: false });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, the const/enum + required constraints come from Ajv, not the spec (docs), so the fallback is ok

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.

3 participants