Problem
The OpenAPI compiler's allOf reconciliation (compilers/openapi/schema.go,
mergeProperty) is first-declaration-wins for a property's shape: when two inline
branches declare the same field, it OR's Required but keeps only the first
branch's type, constraints, default, docs, visibility, and extensions. Anything the
later branch adds or narrows is dropped.
Under allOf intersection semantics the merged field should satisfy every
branch. So a second branch that adds a constraint the first lacks (e.g. pattern,
maxLength, enum, a default) is currently lost, and the resulting model is a
lossy view of the source. This also weakens the ir-design §4.3 promise that
composition "keeps every branch reconstructible": the later branch's contribution to
the field leaves no trace.
Impact
- Emitted SDKs/validation may under-constrain fields that the spec constrained.
- A default declared only in a later branch disappears.
Proposed direction
Intersect the reconciled property across branches rather than first-wins:
- union the constraint sets (tightest wins where they overlap),
- carry a default/docs/examples from a later branch when the first has none,
- preserve provenance of every contributing branch.
This is plausibly better as a dedicated IR pass/ (operating on the composed model)
than inline in the compiler, so the compiler stays a straightforward lowering and the
intersection logic is testable in isolation. Coordinate with the conflict-detection
work (they touch the same merge point).
Acceptance
- A field declared in two branches, where the second adds
maxLength/pattern/a
default, ends up with those on the reconciled property.
- Provenance lets a consumer see every branch that contributed to the field.
Context
Follow-up to #8. That PR fixed the duplicate-wire-name false positives with a
minimal, correct reconciliation and explicitly deferred full constraint intersection.
Problem
The OpenAPI compiler's
allOfreconciliation (compilers/openapi/schema.go,mergeProperty) is first-declaration-wins for a property's shape: when two inlinebranches declare the same field, it OR's
Requiredbut keeps only the firstbranch's type, constraints, default, docs, visibility, and extensions. Anything the
later branch adds or narrows is dropped.
Under
allOfintersection semantics the merged field should satisfy everybranch. So a second branch that adds a constraint the first lacks (e.g.
pattern,maxLength,enum, adefault) is currently lost, and the resulting model is alossy view of the source. This also weakens the ir-design §4.3 promise that
composition "keeps every branch reconstructible": the later branch's contribution to
the field leaves no trace.
Impact
Proposed direction
Intersect the reconciled property across branches rather than first-wins:
This is plausibly better as a dedicated IR
pass/(operating on the composed model)than inline in the compiler, so the compiler stays a straightforward lowering and the
intersection logic is testable in isolation. Coordinate with the conflict-detection
work (they touch the same merge point).
Acceptance
maxLength/pattern/adefault, ends up with those on the reconciled property.Context
Follow-up to #8. That PR fixed the duplicate-wire-name false positives with a
minimal, correct reconciliation and explicitly deferred full constraint intersection.