A schema that writes oneOf/anyOf beside a $ref loses the union outright. It reaches no
field of the document, it is not kept under Unmodeled, and nothing is reported.
Reproduction
openapi: 3.1.0
info: {title: T, version: "1"}
paths: {}
components:
schemas:
Base: {type: object, properties: {id: {type: string}}}
G: {$ref: '#/components/schemas/Base', oneOf: [{type: string}, {type: integer}]}
H: {$ref: '#/components/schemas/Base', anyOf: [{type: string}, {type: integer}]}
$ morphic compile refunion.yaml -skip-validate | jq -c '.types["t/openapi/components/schemas/G"] | {kind, base, u: .unmodeled}'
{"kind":"scalar","base":{"target":"t/openapi/components/schemas/Base","nullable":false},"u":null}
$ morphic compile refunion.yaml -skip-validate 2>&1 >/dev/null | wc -l
0
anyOf behaves identically. Under 2020-12 a $ref conjoins with its siblings, so the union
narrows the referenced schema; dropping it silently widens what the SDK accepts and leaves no
trace that it ever existed.
Why it happens
The union family has one keeper, preserveUnionSiblings, and it is reached only through
lowerBesideUnmodeledUnion — the structural-body path. A declaration that peels a leading
$ref never gets there. The census that catches the other sibling keywords cannot cover for
it either: censusKeywords lists additionalProperties, const, enum, format, items,
maxItems, minItems, patternProperties, prefixItems, properties, required, type
and uniqueItems, and oneOf/anyOf are in neither that list nor keywordHome's switch.
So the union falls between the two mechanisms: the path that would keep it is not taken, and
the path that is taken does not look for it.
#283 covered format/enum/const/required/additionalProperties beside a $ref and was
fixed by #348; the union family was not in its list and is still open.
Expected
oneOf/anyOf written beside a $ref is kept verbatim under Unmodeled on the node the
position owns, with the same degraded-construct report the structural-body path already emits,
rather than disappearing.
A schema that writes
oneOf/anyOfbeside a$refloses the union outright. It reaches nofield of the document, it is not kept under
Unmodeled, and nothing is reported.Reproduction
anyOfbehaves identically. Under 2020-12 a$refconjoins with its siblings, so the unionnarrows the referenced schema; dropping it silently widens what the SDK accepts and leaves no
trace that it ever existed.
Why it happens
The union family has one keeper,
preserveUnionSiblings, and it is reached only throughlowerBesideUnmodeledUnion— the structural-body path. A declaration that peels a leading$refnever gets there. The census that catches the other sibling keywords cannot cover forit either:
censusKeywordslistsadditionalProperties,const,enum,format,items,maxItems,minItems,patternProperties,prefixItems,properties,required,typeand
uniqueItems, andoneOf/anyOfare in neither that list norkeywordHome's switch.So the union falls between the two mechanisms: the path that would keep it is not taken, and
the path that is taken does not look for it.
#283 covered
format/enum/const/required/additionalPropertiesbeside a$refand wasfixed by #348; the union family was not in its list and is still open.
Expected
oneOf/anyOfwritten beside a$refis kept verbatim underUnmodeledon the node theposition owns, with the same degraded-construct report the structural-body path already emits,
rather than disappearing.