Under the 2020-12 dialect a side may declare both of its keywords (minimum and exclusiveMinimum, maximum and exclusiveMaximum). They are conjunctive, so the effective bound is the tighter of the two, and ir.Constraints has one bound plus one exclusivity flag per side to hold it. The reader keeps the tighter one and names the other, with its exact value, in an openapi/degraded-construct diagnostic.
What it does not do is keep the discarded keyword anywhere in the document.
Why it is not nothing
No value the source admits or excludes changes: the discarded keyword is implied by the kept one. What is lost is the record that the source spelled the bound twice, and that record only exists in a diagnostic — a channel a consumer may filter by severity, and one that is not part of the document a downstream stage reads. A consumer reconstructing the source, or diffing two revisions of it, cannot see the difference between
{minimum: 10, exclusiveMinimum: 0}
and
which the lossless-by-default invariant says it should.
Why it was not done with the fix
annotation.Constraints returns (*ir.Constraints, []ir.Diagnostic) and ir.Constraints has no Unmodeled channel of its own. Its callers route what it returns to different carriers — a property, a parameter, and a hoisted alias node — each with its own home for preserved keywords, so giving the constraint reader somewhere to put a verbatim keyword is a change to that seam rather than to the reconciliation.
Expected
The keyword that does not reach Constraints is preserved verbatim with an UnmodeledReason and provenance, on whichever carrier the reading position owns, and the diagnostic keeps naming it.
Under the 2020-12 dialect a side may declare both of its keywords (
minimumandexclusiveMinimum,maximumandexclusiveMaximum). They are conjunctive, so the effective bound is the tighter of the two, andir.Constraintshas one bound plus one exclusivity flag per side to hold it. The reader keeps the tighter one and names the other, with its exact value, in anopenapi/degraded-constructdiagnostic.What it does not do is keep the discarded keyword anywhere in the document.
Why it is not nothing
No value the source admits or excludes changes: the discarded keyword is implied by the kept one. What is lost is the record that the source spelled the bound twice, and that record only exists in a diagnostic — a channel a consumer may filter by severity, and one that is not part of the document a downstream stage reads. A consumer reconstructing the source, or diffing two revisions of it, cannot see the difference between
{minimum: 10, exclusiveMinimum: 0}and
{minimum: 10}which the lossless-by-default invariant says it should.
Why it was not done with the fix
annotation.Constraintsreturns(*ir.Constraints, []ir.Diagnostic)andir.Constraintshas noUnmodeledchannel of its own. Its callers route what it returns to different carriers — a property, a parameter, and a hoisted alias node — each with its own home for preserved keywords, so giving the constraint reader somewhere to put a verbatim keyword is a change to that seam rather than to the reconciliation.Expected
The keyword that does not reach
Constraintsis preserved verbatim with anUnmodeledReasonand provenance, on whichever carrier the reading position owns, and the diagnostic keeps naming it.