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:
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
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
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
747b9e670c6bda153e0b12fb814f4d4eecd8b1faTwo readers in
scripts/check-adr-0087-registration.mjscompose into it:parseSymbolRef(ref)accepts only<path>#<IDENT>whereIDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/. A dotted member path is rejected outright: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.tsat HEAD declares 14 definitions namedget, in file order:and the gate's shipping reader answers for the first one:
So a marker naming
packages/client/src/index.ts#getwould 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-onlymarker namesregister,getPublicandconsent(each unique in the file, each verified by the gate) and disclosesoauth.applications.getin 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 namedget,list,delete,update,create, and the like, most of which are declared many times inpackages/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 (
documentedCategoriesreads the ADR's own examples), so the ADR example line and the--self-testfixtures move with it. The decision belongs to the gate's owner.Related, not the same defect: #15489 (
readDeclaredTypeSurface.typemisreads for interface/class/enum and multi-line aliases) and #14502.Re-check
Generated by Claude Code