Repository navigation
fix(adr-0087): predicate 2 reads a default export through its import, and a package name inside a path or URL - #27207
Conversation
…t, and a package name inside a path or URL An export named `default` is read through the import arm only: `default` is an ordinary key in metadata surfaces, so the bare-word arm refused every default retirement. A whole package's mention regex now admits a `/`-preceded occurrence (a path or URL in a string) while still refusing a scope segment, so an unscoped `plugin-x` is not `@scope/plugin-x`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziGaNnGcwiYSC9Qzck
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziGaNnGcwiYSC9Qzck
Contract reviewServed-tier: Inputs read (GET only): card #26324 body and all 4 comments (triage 6428250876, serial-wait 6430725274, claim 6465577081, os-dev-report 6466700067); card #27022 body and both comments (triage 6459401704, claim 6465589776); the director's contract review 6458009682 on PR #26766; PR #27207 body, file list (1 file, +126/−4), commits ( Gate verdicts on the head: ① Derived judgments1. #26324, the 2. #26324 — the shapes the reader does NOT see, graded. The brief asked for one case per shape where the gate now passes while a metadata surface really reaches the default export. Found, by probe:
3. The performance claim. 4. #27022, the mention regex — right, and the brief's checks all hold. Head:
5. Battery — the new cases pin what they claim; nothing existing was edited or removed. The diff has no
6. The driver-memory probe — the code supports the dev's three readings. On main 7. Declarations. ② Semver levelNo release: ③ Boundary flags
Should-fix, none blocking: a card for ③-(a); Implemented-by: VERDICT: PASS |
…ic load of its module A namespace import, a namespace re-export, or a dynamic import()/require() of the declaring package from a metadata surface now refuses a `default` operand, as the bare-word arm refuses them for every other export name. The battery roster floor moves to 59 for the DX battery. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziGaNnGcwiYSC9Qzck
Contract reviewServed-tier: Round 2 on the head that answers the PASS record on Gate verdicts on the head: ① Derived judgments1. Should-fix ③-(a) — fixed, and it closes the admit gap round 1 named. Two readers are added beside the two of round 1 and feed the same 2. Over-refusal — the questions asked, answered.
3. The readers are linear. Neither carries a lazy 4. The driver-memory probe stays green — the code supports it. On main, no metadata surface imports 5. Should-fix ①-5 — fixed, and the bump matches the roster. 6. Declarations and body. ② Semver levelUnchanged from round 1: no release, ③ Boundary flags
Should-fix: none. Noted only: ③-(b), ③-(c). Implemented-by: VERDICT: PASS |
维护者速读 (席位终稿)
席位意见:接受。契约复核两轮 PASS(6466959531、6467569746)。第一轮的两处应修(命名空间、动态导入的放行缺口,以及自测套数下限)已在第二轮修好。 席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26324
Fixes #27022
Clause-②: no
Metadata-protocol: none
Skills-docs: none — no file under
skills/namesruntime-export-retired,no-metadata-surfaceor the ADR-0087 gate (git grepoverskills/: 0 hits), so neither predicate is taught there维护者速读
default的退役只看"有没有元数据面 import 了这个默认导出,或以命名空间导入、命名空间再导出、动态import()/require()加载了整个模块",不再按单词扫default;整包退役(PACKAGE#*)把路径或 URL 字符串里出现的包名也算作引用,但@scope/plugin-x仍不算无作用域的plugin-x。default是元数据里的普通键({ value: 'technology', default: true }、Zod.default()),单词扫描在每棵树上都命中,所以任何default导出的退役都无法如实申报,卡住了 packages: 4 ESM-only subpaths answer require() with ESM, and driver-memory's default export is lost under CJS (attw CJSResolvesToESM / FalseExportDefault) #26206 的后半(移除 driver-memory 的旧默认导出)。路径/URL 那一处是 D9 原文"包名的任何其他出现,含字符串值"与门禁实际放行之间唯一已知的缺口。scripts/下一个不发布的门禁脚本。default一侧放宽的范围只限导出名恰为default的操作数,其他导出名读法不变;路径一侧是收紧(多拒)。回滚即还原本 PR。scripts/check-adr-0087-registration.mjs的defaultImportReference(四条线性 import 正则,及metadataSurfaceReference开头的分流)、packageReference的mention正则(新增一个排除@scope/段的负向后顾)、自测里的 DX 电池与 WP-U6–U9。node scripts/check-adr-0087-registration.mjs --self-test,预期打印705 assertions、退出码 0。What changes
runtime-export-retiredcan never certify an operand nameddefault(predicate 2 word-scansdefaultacross every metadata surface) #26324, predicate 2 for an export nameddefault.metadataSurfaceReferencehands adefaultsymbol to a newdefaultImportReference, which reads the import arm only. A metadata surface reaches a default export through an import that binds it: a default binding (import x from 'M', with named bindings or a namespace beside it,typeor not) ordefaultinside the braces of animport/export … fromclause ({ default as x },export { default } from 'M'). The specifier is attributed through the existingspecifierNamesModule, and comment spans are blanked through the sharedmaskComments, as before. The bare-word arm is not read fordefault, becausedefaultis an ordinary key in exactly the files predicate 2 reads. Every other export name keeps today's reading, both arms. The shared primitive servesruntime-interface-onlytoo, so aPATH#defaultthere gets the same reading.importSpecifiersFor's[\s\S]*?clause scan. That scan runs today only on files that already contain the symbol's word, and over every metadata surface it is super-linear in the whitespace runsmaskCommentsleaves: the first version of this fix reused it and the real-tree run had not finished after 12 minutes of CPU (one file alone,packages/spec/src/api/discovery.zod.ts, took 41 s). The linear version runs the whole real-tree gate in 7 s.defaultoperand is also refused by a metadata surface that binds the whole declaring module: a namespace import (import * as ns from 'M',ns.defaultone read away), a namespace re-export (export * as ns from 'M'), or a dynamicimport('M')/require('M'). The refusal rests on the binding alone, never on a.defaultread after it, so no member tracking is needed; every other export name already refuses these shapes through its bare-word arm, so this closes the admit gap round 1 opened. Both new readers are linear (statement-anchored, no[\s\S]*?). (2)SELF_TEST_BATTERY_FLOORmoves from 58 to 59 for the new DX battery.#*operand passes when a metadata surface names the package only inside a path or URL string #27022, thePACKAGE#*mention regex. Its negative lookbehind drops/from the excluded set, and a second negative lookbehind refuses an@SCOPE/segment just before the name (@[\w.~-]+/). A/may now precede the name, so a path or URL inside a string counts as amention, but not a/that closes an@scopesegment, so an unscopedplugin-xstill does not match inside@scope/plugin-x. The name-boundary rules on both sides are unchanged (plugin-xyandmy-plugin-xare other packages).metadataSurfaceReferenceandpackageReferencesay the same.Tests
DX (#26324), floor 19, and the roster floorSELF_TEST_BATTERY_FLOOR58 to 59: DX-G1 (the packages: 4 ESM-only subpaths answer require() with ESM, and driver-memory's default export is lost under CJS (attw CJSResolvesToESM / FalseExportDefault) #26206 shape: a retireddefaultexport beside adefault: truefield option and a Zod.default('')) green, with DX-G1b naming the operand and its declaring entry; DX-G2 (the import quoted only in a comment) green; DX-G3 (another module's default imported) green; DX-R1 (a metadata surface that imports the package's default) red; DX-R2 ({ default as x }) red; DX-R3 (import * as ns, thenns.default) red; DX-R4 (a dynamicimport()) red; DX-R5 (arequire()) red; DX-R6 (export * as ns from) red; DX-U1 to U3 units (default beside named bindings, a type-only subpath default, a multi-lineexport { default } fromafter a;; named bindings, adefault:key and a.default()call alone are not imports; a non-defaultexport name keeps the bare-word arm).mention, with its line), WP-U7 (inside a URL), WP-U8 (@scope/plugin-xis notplugin-x, bare, in a path and in a URL), WP-U9 (an unscopedplugin-xin a path and a URL is still a mention). WP-G3 (comment only) and WP-U4 (plugin-xyis notplugin-x) stay green unchanged.Self-test under the verification lock, at `f402e245`
Acceptance notes
The driver-memory measurement (the real shape packages: 4 ESM-only subpaths answer require() with ESM, and driver-memory's default export is lost under CJS (attw CJSResolvesToESM / FalseExportDefault) #26206 blocked on). A local probe commit on
origin/main72629f38deletes theexport default { … }block frompackages/drivers/driver-memory/src/index.tsand adds a changeset carryingruntime-export-retired @objectstack/driver-memory#defaultwith the line- FROM `default` TO `InMemoryDriver`: …. The probe was never pushed.origin/maincopy72629f38node scripts/check-adr-0087-registration.mjs --base origin/mainexamples/app-crm/src/objects/account.object.tsmentionsdefault({ label: 'Technology', value: 'technology', default: true }), the card's reading exactly2e1e09d5)72629f38verified: @objectstack/driver-memory#default (export, packages/drivers/driver-memory/src/index.ts)2e1e09d5)72629f38plus one object fileimport memoryPlugin from '@objectstack/driver-memory'--base PROBE_BASEprobe-host.object.ts(an object definition) IMPORTSdefaultfrom the module that declares itf402e245)9dacccf9(the same probe on the then-currentorigin/main)node scripts/check-adr-0087-registration.mjs --base origin/mainverified: @objectstack/driver-memory#default (export, packages/drivers/driver-memory/src/index.ts): the namespace and dynamic readers find nothing on main's metadata surfacesRead for
defaulton the binding, not on.default: a namespace import, a namespace re-export and a dynamicimport('M')/require('M')of the declaring module refuse on the module alone (round 2). The member spelling.defaultis never read, since it cannot be told from object keys and.default()calls; refusing on the binding is the conservative direction.importSpecifiersFor's super-linear clause scan is left as it is: its callers gate it on the word being present, so no current run is slow. Noted, not filed.The order expected
Clause-②: n/a; both claims carryClause-②: no, and the body copies the claim. No published accept-set moves (scripts/**is not shipped), sonois accurate either way.No changeset: the root package is private and
scripts/**is not published. Labelledskip-changeset.The ADR text needs no change: D9's second 2026-10-27 note already says "any other occurrence of the package name, a string value included", and the
defaultreading is the D7 import primitive the note points to.Verification
Derived gates (dispatch-gates --commands, 33 families) at `f402e245` (merge base `b322b5da`)
Generated by Claude Code