#4411 的第二条待裁决。那条 issue 由一次同名双源引发(MetadataWatchEvent 在 ./kernel 与 ./system 各一份、形状不同、kernel 版零消费方),已在 #4411 的 PR 里连同同文件另外 10 个同类一并退役。但产生这类问题的机制没有被堵上 —— api-surface.json 里同名跨入口是可见但不报警 的,下一个同名双源仍会静默落地。
为什么这类缺陷特别值得机器来抓
它是只在 import 路径上分叉 的缺陷:两份声明共用一个名字,靠 from '@objectstack/spec/<entry>' 区分。
一次 code review 很难看见它 —— 两个文件各自都是合理的。
当前实测基线(#4411 PR 合入后)
从 packages/spec/api-surface.json 直接算:
指标
#4411 前
#4411 后
跨子路径 同名导出(name (kind) 出现在 >1 个非 . 入口)
103
83
其中在 >1 个源文件里各自声明的(真·多源,非 re-export)
70
待重算
复算脚本:
const s = require ( './packages/spec/api-surface.json' ) ;
const map = new Map ( ) ;
for ( const [ ep , list ] of Object . entries ( s ) . filter ( ( [ , v ] ) => Array . isArray ( v ) ) )
for ( const n of list ) ( map . get ( n ) ?? map . set ( n , [ ] ) . get ( n ) ) . push ( ep ) ;
console . log ( [ ...map ] . filter ( ( [ , eps ] ) => eps . filter ( e => e !== '.' ) . length > 1 ) . length ) ;
.(根入口)+ 子路径的组合要排除 —— 那是有意的 re-export,同一个符号。
设计要点(裁决时值得先看的几条)
按符号 identity 判定,不能只按名字。 83 条里绝大多数是同一个声明从两个入口可达(合法),真正的缺陷是两个不同声明共用一个名字 。scripts/build-api-surface.ts 已经用 TS checker 解析 module symbol,拿到 declaration 位置来做同一性判断是可行的 —— 这也是这条检查唯一有价值的形态,只比名字会淹没在误报里。
需要 allowlist 基线。 剩余的真·多源不可能在一个 PR 里清完,得按 ratchet 模式落地(新增即失败,存量记账),形态参照 authorable-surface.json / check-type-check-coverage.mjs。
挂载点。 跟着 check:api-surface 走(同一个 TypeScript Type Check job,读 built dist/*.d.ts);注意 AGENTS.md 记的 stale-dist 陷阱同样适用。
已知的存量条目(建档,便于起基线)
MetadataFormat —— system/metadata-persistence.zod.ts(7 个成员,含 yml/ts/js 别名)与 shared/metadata-types.zod.ts(4 个成员)枚举分歧 ,两者都在导出面上。spec 同名双源:两个 MetadataWatchEvent 形状不同、分挂两个子路径入口,其中 kernel 版零消费方(ADR-0049 enforce-or-remove) #4411 只把 kernel 的第三份删掉了,这一对仍在。
contracts/metadata-service.ts 自有的 MetadataExportOptions / MetadataImportOptions interface —— 与 system/metadata-persistence.zod.ts 的同名 schema 第三形状 ,且有消费方,不是删得掉的死副本,需要单独判定哪边是真源。
system ↔ integration(ConsumerConfig、DatabaseProvider、MessageQueueProvider、MultipartUploadConfig)、system ↔ cloud(EnvironmentArtifact、TenantPlan)、kernel ↔ api(*PackageRequest/Response 一族)、ui ↔ shared(HttpRequest、HttpMethodSchema)等成组条目 —— 需逐组判定 re-export vs 真分歧。
待裁决
这条检查是否值得做、按什么优先级做,由维护者定(#4411 里已明确记为「值不值当由维护者定」)。开这条 issue 是为了不让那次裁决连同实测基线一起丢失。
关联:#4411 (触发路径 + 已退役的 11 个同名双源)、#4404 、#4251 、#4393 、ADR-0049
#4411 的第二条待裁决。那条 issue 由一次同名双源引发(
MetadataWatchEvent在./kernel与./system各一份、形状不同、kernel 版零消费方),已在 #4411 的 PR 里连同同文件另外 10 个同类一并退役。但产生这类问题的机制没有被堵上 ——api-surface.json里同名跨入口是可见但不报警的,下一个同名双源仍会静默落地。为什么这类缺陷特别值得机器来抓
它是只在 import 路径上分叉的缺陷:两份声明共用一个名字,靠
from '@objectstack/spec/<entry>'区分。MetadataWatchEvent形状不同、分挂两个子路径入口,其中 kernel 版零消费方(ADR-0049 enforce-or-remove) #4411 的例子:type: 'add'vs'added')。MetadataWatchEvent形状不同、分挂两个子路径入口,其中 kernel 版零消费方(ADR-0049 enforce-or-remove) #4411 里看起来更规范的那份(规范化枚举、字段必填、每个属性都有.describe())是死的,真正在用的是自称 legacy 的宽松超集。按"哪个看起来更正统"来选,选反。一次 code review 很难看见它 —— 两个文件各自都是合理的。
当前实测基线(#4411 PR 合入后)
从
packages/spec/api-surface.json直接算:name (kind)出现在 >1 个非.入口)复算脚本:
.(根入口)+ 子路径的组合要排除 —— 那是有意的 re-export,同一个符号。设计要点(裁决时值得先看的几条)
scripts/build-api-surface.ts已经用 TS checker 解析 module symbol,拿到 declaration 位置来做同一性判断是可行的 —— 这也是这条检查唯一有价值的形态,只比名字会淹没在误报里。authorable-surface.json/check-type-check-coverage.mjs。check:api-surface走(同一个TypeScript Type Checkjob,读 builtdist/*.d.ts);注意 AGENTS.md 记的 stale-dist 陷阱同样适用。已知的存量条目(建档,便于起基线)
MetadataFormat——system/metadata-persistence.zod.ts(7 个成员,含yml/ts/js别名)与shared/metadata-types.zod.ts(4 个成员)枚举分歧,两者都在导出面上。spec 同名双源:两个MetadataWatchEvent形状不同、分挂两个子路径入口,其中 kernel 版零消费方(ADR-0049 enforce-or-remove) #4411 只把 kernel 的第三份删掉了,这一对仍在。contracts/metadata-service.ts自有的MetadataExportOptions/MetadataImportOptionsinterface —— 与system/metadata-persistence.zod.ts的同名 schema 第三形状,且有消费方,不是删得掉的死副本,需要单独判定哪边是真源。system↔integration(ConsumerConfig、DatabaseProvider、MessageQueueProvider、MultipartUploadConfig)、system↔cloud(EnvironmentArtifact、TenantPlan)、kernel↔api(*PackageRequest/Response一族)、ui↔shared(HttpRequest、HttpMethodSchema)等成组条目 —— 需逐组判定 re-export vs 真分歧。待裁决
这条检查是否值得做、按什么优先级做,由维护者定(#4411 里已明确记为「值不值当由维护者定」)。开这条 issue 是为了不让那次裁决连同实测基线一起丢失。
关联:#4411(触发路径 + 已退役的 11 个同名双源)、#4404、#4251、#4393、ADR-0049