Skip to content

fix(scripts): a same-named member declaration is a homonym in the ADR-0087 reference scan - #25349

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lxpaib
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lxpaib

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #25293

Clause-②: no
Metadata-protocol: none

The card asked for a judgment first, between (1) a same-named member declaration is a homonym and (2) it is not, because the contract member is the capability being retired. This PR builds (1). #25190 is not addressed here; this PR only unblocks the renderTemplate half the ruling on it holds back.

The judgment: option 1

IEmailService.renderTemplate (packages/spec/src/contracts/email-service.ts:341 on 97967a9) resolves a NAMED, stored email_template from the metadata registry and renders it. The export being retired, @objectstack/plugin-email#renderTemplate (template-engine.ts:109), takes a template BODY inline and renders it with no save moment. They are two capabilities that share a word.

The retirement's own changeset settles it. On #25190's head 85058b6e its FROM / TO line reads: FROM renderTemplate imported from @objectstack/plugin-email TO a template saved or registered as an email_template and rendered by name through the mail service: renderTemplate({ template, data, locale }) on the email service. The contract member is the TO side, the door consumers are sent to. Option 2 would have to retire the replacement together with the thing it replaces, so the gate refused something it should have admitted. That is a blind spot in a gate that already exists, not a product question, so there is no needs_decision.

What changed

scripts/check-adr-0087-registration.mjs, the reference scan shared by runtime-interface-only (step 4) and runtime-export-retired (predicate 2):

  • New memberDeclarationOffsets(text, symbol) finds where the symbol is the NAME of a member declaration: a method or property signature in an interface body or a type-literal type alias (any depth, since every brace there is a type literal), or a method or field at the top depth of a class body. Regions are located on the comment- and literal-masked projection. The innermost open bracket must be one of the region's member braces, so a parameter name or a type argument never counts.
  • metadataSurfaceReference blanks only those name bytes, then reads the file as before. This is narrower than the top-level homonym rule, which clears the whole file: a member shadows nothing, so a member signature next to a live call still refuses on the call, and the reported line is the call's line.
  • Unchanged: a bare call, a value, a member access (svc.renderTemplate(...)), a parameter name, a type argument and every string literal still refuse. Object-literal keys and generic constraints on functions are not read, which is the conservative direction (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690).
  • A header paragraph records the case and the narrower scope.

Self-test: new battery MH1-MH12 (5 clears, 7 floors that must still refuse) is added to SELF_TEST_BATTERIES with floor 12. REX-G4 / REX-R10b run the same predicate end to end through scan() on a spec contract.

Verification (on d0b41c5)

  • node scripts/check-adr-0087-registration.mjs --self-test: exit 0, 564 assertions.
  • Ablation, one-off and not committed: with scripts/ablation-replace.mjs, the blanking loop was mutated to iterate nothing (the anchor hit once; blob a8c26e1f became 088e842d). The self-test went to exit 1 with 8 failures: MH1-MH5, MH9, REX-G4 and REX-R10b. All 7 floors stayed green, which is the expected direction. Restore: the blob matches HEAD and git diff HEAD is empty.
  • Re-run on plugin-email, service-messaging: retire the render exports that take a template body with no save moment (renderTemplate, renderNotification) — #25082 ruling F2 B3 #25190's shape. A measurement head is origin/main 97967a9, plus 85058b6e's plugin-email half (src/index.ts and its pin test), plus a changeset that names only runtime-export-retired @objectstack/plugin-email#renderTemplate with the same FROM spelling. Running node scripts/check-adr-0087-registration.mjs --base origin/main on it:
    • before (script at 97967a9): exit 1, [predicate 2: no-metadata-surface] is false: packages/spec/src/contracts/email-service.ts (a spec contracts/** entry) mentions renderTemplate IN CODE, quoting the method signature line.
    • after (script at d0b41c5): exit 0, verified: @objectstack/plugin-email#renderTemplate (export, packages/plugins/plugin-email/src/template-engine.ts).
  • Derived gate list (dispatch-gates --commands, 1 path against merge base 97967a9): 36 families, all exit 0. Reconciled with --ran: 36 derived, 36 run, 0 NOT-MEASURED (a derived zero: every line recorded its exit code).

No changeset: scripts/ is repository tooling and ships in no package's files[], so this PR carries skip-changeset.

Acceptance notes

🤖 Generated with Claude Code

https://claude.ai/code/session_013CjPYTdJTD78hcb8LJ9ErK


Generated by Claude Code

…-0087 reference scan

`metadataSurfaceReference` cleared a metadata-surface hit only when the file
declared the name at top level, so a contract METHOD SIGNATURE of the same name
(`IEmailService.renderTemplate`) read as an unresolvable mention and refused the
retirement of `@objectstack/plugin-email#renderTemplate`.

`memberDeclarationOffsets` finds the member-name occurrences (interface bodies,
type-literal aliases, a class body's top depth) and blanks only those bytes, so a
bare call, value, member access, parameter, type argument or string literal in
the same file still refuses. Self-test: MH1-MH12 plus REX-G4 / REX-R10b end to end.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013CjPYTdJTD78hcb8LJ9ErK
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:spec seat 1 (seat post #6017) · os-tesla · session session_013odhA26dvzy7pQmevdTTvy · 2026-10-23T02:12Z

Head d0b41c549f · card #25293 · claim 6355136578 · report 6355429958.


Generated by Claude Code

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

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants