refScan (compilers/openapi/internal/scan/scan.go:304) builds one nodeview.View and shares it across the whole pre-scan. nodeview memoizes a mapping's merge expansion, so a node first expanded shallowly is later served from that memo to a walk that reaches it deeper than MergeDepthLimit would allow. Whether the bound is hit therefore depends on which schema the scan reached first.
The bound being hit sets View.Exhausted(), which scan.go:174 reports as openapi/cycle-scan-failed. So the same document emits that warning in one declaration order and not the other.
Reproducer
One document, two orders. Anchors c0..c50 form a 51-link chain with d0..d20 riding on c50 (71 total, past the 64 bound); schema P merges *c50, schema Q merges *d20.
Declared P-then-Q, the c-chain is memoized shallowly and Q's deeper reach is answered from cache, so the bound is never hit. Declared Q-then-P, Q expands first and truncates, and the compile gains:
warning openapi/cycle-scan-failed: cycle pre-scan stopped at its 64-level merge-key expansion bound;
reference-cycle protection is incomplete for this source
The exact generator is mergeBoundOrderSpec in compilers/openapi/internal/schema/schema_test.go, added by #362.
Why the sweep does not catch it
The construct needs YAML anchors, and internal/harness's reverseMappings declines to permute a document whose aliases the reversal would lift above their anchors — so orderInvariant returns ok without ever comparing. Filed separately as the oracle blind spot.
Impact is a diagnostic rather than the IR: the warning says cycle protection is incomplete, and it appears or not depending on source order. Found while probing #362, which had the same mechanism in the resource-boundary walk and reverted it.
refScan(compilers/openapi/internal/scan/scan.go:304) builds onenodeview.Viewand shares it across the whole pre-scan.nodeviewmemoizes a mapping's merge expansion, so a node first expanded shallowly is later served from that memo to a walk that reaches it deeper thanMergeDepthLimitwould allow. Whether the bound is hit therefore depends on which schema the scan reached first.The bound being hit sets
View.Exhausted(), whichscan.go:174reports asopenapi/cycle-scan-failed. So the same document emits that warning in one declaration order and not the other.Reproducer
One document, two orders. Anchors
c0..c50form a 51-link chain withd0..d20riding onc50(71 total, past the 64 bound); schemaPmerges*c50, schemaQmerges*d20.Declared P-then-Q, the c-chain is memoized shallowly and Q's deeper reach is answered from cache, so the bound is never hit. Declared Q-then-P, Q expands first and truncates, and the compile gains:
The exact generator is
mergeBoundOrderSpecincompilers/openapi/internal/schema/schema_test.go, added by #362.Why the sweep does not catch it
The construct needs YAML anchors, and
internal/harness'sreverseMappingsdeclines to permute a document whose aliases the reversal would lift above their anchors — soorderInvariantreturnsokwithout ever comparing. Filed separately as the oracle blind spot.Impact is a diagnostic rather than the IR: the warning says cycle protection is incomplete, and it appears or not depending on source order. Found while probing #362, which had the same mechanism in the resource-boundary walk and reverted it.