Skip to content

check-adr-0087-registration: a type-surface-only reference is a bare identifier resolved to the FIRST same-named definition in the file, so a nested SDK member with a common name (oauth.applications.get) cannot be named — PR #15445 had to leave its fourth narrowed member out of its marker #15627

Description

@os-litant

Found during the contract re-review of PR #15445 (card #14312). Filed bare for triage: no label, no assignee, no type, no severity asserted. The changeset marker on that PR says the ambiguity "is filed as its own card"; no such card was found by three semantic searches, by title among the 40 most recent issues (created 2026-09-04T21:16Z to 2026-09-05T00:22Z), or by number in the commit message, the changeset, the PR body, or any #14312 comment — so this is that card. If one exists, close this as its duplicate.

The limitation, measured at 747b9e670c6bda153e0b12fb814f4d4eecd8b1fa

Two readers in scripts/check-adr-0087-registration.mjs compose into it:

  1. parseSymbolRef(ref) accepts only <path>#<IDENT> where IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/. A dotted member path is rejected outright:
    parseSymbolRef("packages/client/src/index.ts#get")                    -> { path, symbol: "get" }
    parseSymbolRef("packages/client/src/index.ts#oauth.applications.get") -> null
    parseSymbolRef("packages/client/src/index.ts#applications.get")       -> null
    
  2. memberReturnAnnotation(text, symbol) iterates every <symbol>(...) match in file order and returns on the first definition it finds (annotated or not). It has no notion of the object-literal nesting a member sits in.

Replayed with the gate's own regex over maskComments(text), packages/client/src/index.ts at HEAD declares 14 definitions named get, in file order:

1928:UNANNOTATED  2268:UNANNOTATED  2601:UNANNOTATED  2817:UNANNOTATED  3184:annotated
4072:annotated    4196:annotated    4897:annotated    4990:annotated    5243:annotated
5372:annotated    5585:annotated    6357:annotated    6517:annotated

and the gate's shipping reader answers for the first one:

memberReturnAnnotation(HEAD, "get")                              -> { annotation: null }
memberReturnAnnotation(text truncated just after line 1928, "get") -> { annotation: null }
memberReturnAnnotation(text truncated BEFORE line 1928, "get")     -> null

So a marker naming packages/client/src/index.ts#get would be refused by predicate 4 with "is false at HEAD: get … is still UNANNOTATED" — a verdict about a member (line 1928, unannotated at both revs) that the PR never touched — while the member actually narrowed (oauth.applications.get, line 3184, unannotated at base → Promise< OAuthApplication > at HEAD) is unaddressable by the reference grammar.

Consequence on PR #15445, and why it recurs

That PR's type-surface-only marker names register, getPublic and consent (each unique in the file, each verified by the gate) and discloses oauth.applications.get in prose only. ADR-0087 D8 says the gate judges the claim that was made and not its completeness — but here the incompleteness is forced by the grammar, not chosen, and the fourth member's predicate-4 reading exists only as a human assertion.

It will recur immediately: the two sibling cards on the same hot file — #14313 (auth.*, 14 methods) and #14314 (organizations.*, 19 methods) — bind members named get, list, delete, update, create, and the like, most of which are declared many times in packages/client/src/index.ts. Their markers will be able to name only the members whose name happens to be unique in the file.

Shape of a fix (not applied, not decided here)

Either accept a qualified reference (a dotted member path resolved through the object-literal nesting the member sits in), or an explicit disambiguator that is not a line number (line numbers rot within the day on this file). Whichever is chosen: ADR-0087's marker grammar and the gate's accepted grammar are pinned to each other (documentedCategories reads the ADR's own examples), so the ADR example line and the --self-test fixtures move with it. The decision belongs to the gate's owner.

Related, not the same defect: #15489 (readDeclaredTypeSurface .type misreads for interface/class/enum and multi-line aliases) and #14502.

Re-check

node --input-type=module -e 'import {parseSymbolRef} from "./scripts/check-adr-0087-registration.mjs"; console.log(parseSymbolRef("packages/client/src/index.ts#oauth.applications.get"))'   # null today
grep -n -E "^\s*get: (async )?(<[^>]*> ?)?\(" packages/client/src/index.ts | wc -l   # 14 at 747b9e6; re-locate by content, never by line

Generated by Claude Code

Activity

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions