Skip to content

openapi: derive a node's name from its coordinate in one function, and diff it against the names the lowering gives #980

Closed

Description

@OmarAlJarrah

Problem

A node's name is derived twice. Lowerers thread a hint down and extend it with compile.SubHint (40 signatures in schema and operation declare hint string). For a $ref that reaches a coordinate before its declaration, positionHint in schema/resolve.go replays the same names from the pointer. The replay is exact beneath /components/schemas and stops elsewhere, because the owner's name is not in the pointer ("Elsewhere the enclosing hint is not in the pointer").

Whether one function of the document and the coordinate can name every node is not known. The change that removes the second derivation (#981) depends on the answer. This issue answers it with a comparison and changes no behaviour.

How far the replay reaches today

For every t/anon/… node with a source pointer, compare Naming.Hint with compile.NamingHint(positionHint(pointer)). Under /components/{requestBodies,responses,headers,parameters}/<name>/…, start the replay at the entry name and continue with positionStep from the schema token. Over 1,952 compiled specs: 115 under testdata/ and 1,837 from a local corpus of single-file OpenAPI 3 specs that is not committed (morphic @3a706a7b).

Region Anonymous nodes Stored hint equals replay
components/schemas 20,713 99.76%
components/requestBodies 265 100%
components/responses 44 100%
components/headers 2 100%
components/parameters 73 58.90%
components/pathItems 2 0%
paths 13,117 22.98%
webhooks 10 30.00%

The same comparison on cloudflare/api-schemas @843eae2f: components/schemas 6,844 nodes at 98.58%, components/requestBodies 60 at 100%, components/responses 183 at 92.90%, components/parameters 66 at 6.06%, paths 27,025 at 66.16%.

Nothing under paths is named by the pointer alone, but the owners are few. Grouping the paths and webhooks nodes by the owner path that precedes the schema token gives 13,127 nodes (same corpus):

Owner shape Nodes Cumulative
paths/{key}/{method}/responses/{code}/content/{mediaType}/schema 5,202 39.63%
paths/{key}/{method}/requestBody/content/{mediaType}/schema 4,253 72.03%
paths/{key}/{method}/parameters/{i}/schema 3,535 98.96%
paths/{key}/{method}/responses/{code}/headers/{header}/schema 75 99.53%
paths/{key}/{method}/parameters/{i}/content/{mediaType}/schema 37 99.81%

On Cloudflare three shapes cover 99.99% of its 27,025 paths nodes: response schema (18,591), parameter schema (5,012) and request body schema (3,418).

What the replay lacks is what each lowerer already reads today:

  • a response: ids.DeclarationHint(responsePointer, "response"), which is the component's entry name when the response is a $ref;
  • a request body: requestBodyHint (the operation ID plus _request) or the component's entry name;
  • a parameter: its name member, not its component key;
  • a composition branch: its ordinal, or the name of the node its $ref resolves to (branchHint). The sampled misses under components/schemas are of this kind: an allOf branch stored as base where the replay gives variant_0.

Direction

A draft.

  1. Add nameAt(doc, pointer), a pure function of the loaded document and a coordinate. Its rules are the owner shapes above, each taking the owner's name from the document the way the lowerers do, followed by the positionStep table for the structure beneath it.
  2. Run it as an oracle beside the lowering: for each node of every compiled testdata/ spec, and of a corpus named by an environment variable so the uncommitted one can be used, require nameAt to equal Naming.Hint, and report mismatches grouped by owner shape.
  3. Post the resulting table here.

The decision gate for #981 is that at least 99% of nodes are equal on testdata/ and the corpus, and that every remaining mismatch is assigned to a rule or named as needing state only the lowering holds. If more than 1% of nodes need such state, close this issue and #981 with the reason.

Out of scope

Changing any lowerer; #981 does that. Callbacks, itemSchema and composed variants (named from their branch target) may be left to #981 if together they stay under 0.1% of anonymous nodes, or are named by a rule already in the function.

Related: #747, #729, #917, #981

Priority: P3, rule 1 (a test that changes no behaviour), raised to the tier of the work it unblocks (the naming change, P3). The comparison above over 1,952 specs → 99.76% of 20,713 components/schemas nodes equal, and five owner shapes cover 99.81% of 13,127 paths nodes (spec @843eae2f, morphic @3a706a7b).

Activity

  1. added
    type:choreRefactor or cleanup, no behavior change
    priority:P3Later emitters, M4 and up, 3.2-only, streaming, webhooks, rare constructs
    on Oct 19, 2026
  2. OmarAlJarrah commented on Oct 23, 2026

    @OmarAlJarrah
    MemberAuthor

    Measured with naming.Namer, the function this proposes as nameAt, run beside the lowering of main at 2683dee, before the lowering adopted it: for every anonymous node and union variant a compile builds, the namer's name against the Naming.Hint the lowering gave. The corpus is the 338 specs under testdata/, 1,881 real-world OpenAPI 3 documents and the Cloudflare schema at e3ada35b; 1,263 of them build anonymous nodes or union variants.

    The first run matched 80,594 of 80,826 nodes (99.71%) and 8,862 of 8,862 variants. The 232 mismatches fell to two rules the function lacked, neither needing state only the lowering holds: the literal node an enum member lowers to when no enumeration holds the enum (…/enum/<i>, named as a prefixItems slot is), and a schema at the root of a document that a $ref reads as a schema, which no owner names. With both rules:

    Region Nodes Equal
    paths 41,277 100%
    components/schemas 38,692 100%
    components/requestBodies 325 100%
    components/responses 265 100%
    components/parameters 169 100%
    elsewhere (webhooks, extensions, other component sections, other documents) 98 100%
    union variants 8,862 100%

    On the Cloudflare schema alone: paths 27,054, components/schemas 7,079, components/responses 185, components/parameters 66 and components/requestBodies 60 nodes, all equal.

    The gate holds, so #1106 makes the function the only source of names. Its corpus sweep against main changes no name in any of these documents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:P3Later emitters, M4 and up, 3.2-only, streaming, webhooks, rare constructstype:choreRefactor or cleanup, no behavior change

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions