Skip to content

checker: report single->oneOf wrapping accurately (keep the breaking verdict, fix the message), supersedes #1022 #1037

Description

@reuvenharrison

Background

#1022 was held after review. It read the single->oneOf transition in #702 as a false positive and suppressed the findings, but that example is a genuine breaking change, so #1022 turned the original 1 error, 2 warning into 0 error, 4 info (a false negative). Analysis is in the #1022 and #702 comments. This issue specs the correct fix.

Principle

A breaking-change gate must never hide a breaking change. Only label a transition non-breaking when we can prove the base's accepted payloads survive. When we cannot prove it, report breaking. A false positive is acceptable here; a false negative is not.

Cases (base = concrete object with required props; revision = wrapper)

  1. anyOf wrapper (validate against at least one): non-breaking widening if every base-valid payload still matches at least one alternative. Suppress the misleading "property removed"; the moved properties still exist.
  2. oneOf wrapper, overlapping/open alternatives (the [BUG] Changing from single value to oneOf gives funky changelog. #702 case): breaking. A base-valid payload can match more than one alternative and fail exactly-one, so a previously-valid payload is rejected. Report the narrowing; do not say the properties were removed.
  3. oneOf wrapper, mutually-exclusive alternatives (e.g. additionalProperties: false + disjoint required, or a discriminator) that cover the base payloads: non-breaking. Detecting this reliably is the hard part. Conservative default: treat oneOf as breaking unless mutual exclusivity + coverage is established.

Messaging

  • Stop emitting request-property-removed for moved properties. The property still exists inside the alternatives, so that message is wrong regardless of breaking-ness.
  • Emit an accurate finding for the wrapping (a dedicated id such as request-body-wrapped-in-one-of, or reuse the structure/oneOf ids) at a severity that reflects whether it is provably safe. Decide id + severity + localization (en/es/pt-br/ru).
  • Revisit request-body-one-of-added severity: adding a oneOf constraint to a request can reject payloads, so INFO is too weak for the unprovable case.
  • The request-body-type-changed: object -> '' artifact is noise from dropping the top-level type during wrapping; fold it into the wrapping recognition.

Scope

  • Object alternatives only. ListOfTypesDiff is unaffected (single scalar types are mutually exclusive, no exactly-one trap; verified).
  • Spec both directions. Request and response break in opposite directions (contravariance), and the reverse transition (wrapper -> concrete) has the mirror behavior.

Decisions to settle before coding

  • How far to prove safety: anyOf-coverage only, or also oneOf mutual-exclusivity detection? Simplest sound MVP: only anyOf (with coverage) is non-breaking; all oneOf wrapping is reported breaking until exclusivity detection is added.
  • New change id(s), severity, messages.
  • Fate of the OneOfWrappingDiff virtual diff: keep it but have it carry "provably safe?" and detect anyOf too, or replace it.

Test fixtures

One per case (anyOf-safe, oneOf-overlapping-breaking, oneOf-mutually-exclusive-safe, single-alternative), for request and response.

Downstream

  • Supersedes Don't report oneOf-wrapped request properties as removed (#702) #1022 (held). The incidental cleanups on that branch (the Empty() content check, marking the derived diffs in SchemaDiff, dropping the unresolved-ref guard) are independently good and can be salvaged.
  • TestDerivedDiffDirectionalSymmetry (the forward/reverse symmetry guard) stays on hold until this lands, since the forward baseline is what is being corrected here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions