Skip to content

openapi: nodeview's expansion memo makes an answer depend on the depth a node was first expanded at #404

Description

@OmarAlJarrah

View.expand (compilers/openapi/internal/nodeview/nodeview.go) memoizes a mapping's merge expansion, and returns a cached entry as complete regardless of the depth the caller reached it at:

if cached, ok := v.pairs[n]; ok { return cached, true }
...
if depth > MergeDepthLimit { v.exhausted = true; return nil, false }

A node first expanded at a shallow depth is memoized; a later walk reaching that same node deeper than MergeDepthLimit allows is then served from the memo instead of truncating. So the answer depends on what was expanded before it, which makes a View unsafe to share across independent walks — the memo is not a pure cache of a pure function, because the function it caches is depth-sensitive and the key is not.

Why this is worth a root-cause issue

Two instances are already recorded, and both were found the hard way:

The reverted change is the cost side. declaresResourceIDAbove now rebuilds a View per call purely because sharing one is unsafe, which is the opposite of what the memo exists for. #338 asks for fewer rescans over the same nodes; that cannot be answered while a shared view can change an answer.

Reproducer

mergeBoundOrderSpec in compilers/openapi/internal/schema/schema_test.go writes the document: anchors c0..c50 form a 51-link chain, d0..d20 ride on c50 for 71 total, and two schemas walk it from different depths. TestDynamicRef_ResourceBoundaryVerdictIsOrderInvariant pins the current per-call behaviour; making the view outlive one walk reddens it.

Shape of a fix

The memo needs to record what it is valid for, not just what it produced — for example keying on (node, remaining depth budget), or storing the depth an entry was computed at and recomputing when a shallower budget would truncate. Alternatively expand could refuse to serve a cached entry to a caller whose remaining budget is smaller than the entry's own expansion consumed. Either makes a shared view safe and lets #338 proceed.

Found while probing #362.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions