You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#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.
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.
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.
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.
TestDerivedDiffDirectionalSymmetry (the forward/reverse symmetry guard) stays on hold until this lands, since the forward baseline is what is being corrected here.
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 warninginto0 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)
anyOfwrapper (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.oneOfwrapper, 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.oneOfwrapper, 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: treatoneOfas breaking unless mutual exclusivity + coverage is established.Messaging
request-property-removedfor moved properties. The property still exists inside the alternatives, so that message is wrong regardless of breaking-ness.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).request-body-one-of-addedseverity: adding aoneOfconstraint to a request can reject payloads, so INFO is too weak for the unprovable case.request-body-type-changed: object -> ''artifact is noise from dropping the top-level type during wrapping; fold it into the wrapping recognition.Scope
ListOfTypesDiffis unaffected (single scalar types are mutually exclusive, no exactly-one trap; verified).Decisions to settle before coding
anyOf-coverage only, or alsooneOfmutual-exclusivity detection? Simplest sound MVP: onlyanyOf(with coverage) is non-breaking; alloneOfwrapping is reported breaking until exclusivity detection is added.OneOfWrappingDiffvirtual diff: keep it but have it carry "provably safe?" and detectanyOftoo, or replace it.Test fixtures
One per case (anyOf-safe, oneOf-overlapping-breaking, oneOf-mutually-exclusive-safe, single-alternative), for request and response.
Downstream
Empty()content check, marking the derived diffs inSchemaDiff, 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.