在 #4696 (PR #5831 )实施过程中实证发现的独立缺陷。不在 #4696 范围内 (该单红线是「不碰 packages/spec/src/**」),单独立案。
现象
packages/spec/src/shared/http.zod.ts 在同一个文件里 声明了两个内容不同 的 enum,而它们经 schemaNameFromExportKey 剥掉 Schema 后缀后同名 :
// packages/spec/src/shared/http.zod.ts
:20 export const HttpMethod = z.enum(['GET','POST','PUT','DELETE','PATCH','HEAD','OPTIONS']); // 7 值
:31 export type HttpMethod = z.infer<typeof HttpMethod>;
:37 export const HttpMethodSchema = lazySchema(() => z.enum(['GET','POST','PUT','PATCH','DELETE'])); // 5 值
:39 export type HttpMethodType = z.infer<typeof HttpMethodSchema>;
HttpMethod → HttpMethod,HttpMethodSchema → HttpMethod。build-schemas.ts 按命名空间导出顺序逐个写 json-schema/{category}/{Name}.json,后写覆盖前写 ,实测结果:
$ node -e "console.log(JSON.stringify(require('./packages/spec/json-schema/shared/HttpMethod.json').enum))"
["GET","POST","PUT","PATCH","DELETE"]
即 json-schema/shared/HttpMethod.json、bundled objectstack.json 的 $defs['shared/HttpMethod']、以及 content/docs/references/shared/http.mdx#httpmethod 只描述 5 值那份 。
影响
7 值的那份才是 api/* 的线上契约 —— api/discovery.zod.ts:482、api/endpoint.zod.ts:60、api/plugin-rest-api.zod.ts:130、api/rest-server.zod.ts:200 都 import { HttpMethod } from '../shared/http.zod' 并用它做 method 字段。一个照着 references/shared/http 写 HEAD 或 OPTIONS 路由的作者,会以为这两个方法非法;而反过来,任何按发布出去的 JSON Schema 做校验的下游(IDE 自动补全、codegen、AI 元数据作者)拿到的也是被截断的那一份。属于 AGENTS.md「Machine-readable surfaces must not lie」。
为什么没有任何门禁发现它
check:dual-source-exports 比对的是导出名 的符号身份:HttpMethod 与 HttpMethodSchema 是两个不同的导出名,它看不出问题(dual-source-exports.baseline.json 当前是 entries: [],全绿)。而碰撞发生在剥掉 Schema 后缀之后的 schema 名 上 —— 与 #4592 同族的「后缀剥离改变了名字身份」问题,只是这次落在碰撞检测上而不是拼写上。
build-schemas.ts 自己也不报:它对 generatedSchemas.set(defKey, …) 是无条件覆盖,没有「这个 def key 已经写过」的检查。
(对照:#4696 给 docs 索引 加了「同 category 内同名 = 硬报错」。本单是同一个道理在 JSON Schema 发布侧 的缺口 —— 那边的键是 ${category}/${name},同样会被静默覆盖。)
建议方向(待裁决,不预设)
改名 (与 spec 双源清账 C9:RateLimitConfig / RateLimitConfigSchema(./integration ≠ ./shared)—— 2 条 #4684 对 RateLimitConfig 的做法一致):把 5 值那份改成一个不与 7 值那份碰撞的名字(它的类型别名已经叫 HttpMethodType 了,HttpMethodSubsetSchema 之类是顺理成章的),两者各自发布。
合并 :确认 UI/view 数据源确实不需要区分,直接复用 7 值那份,删掉 5 值那份。
无论选哪个,都应在 build-schemas.ts 里补一条「同一个 def key 被写第二次 = 报错」的守卫,否则下一个同形状的碰撞仍然静默。
三个选项在「真实业务需求 / 长期正确性 / 让 AI 写的元数据不容易出错」三轴上的取舍需要维护者定,本 issue 只做记录。
复现
pnpm --filter @objectstack/spec gen:schema
node -e "console.log(require('./packages/spec/json-schema/shared/HttpMethod.json').enum)" # 5 值
grep -n "export const HttpMethod" packages/spec/src/shared/http.zod.ts # 两处声明
关联:#4696 (发现处,PR #5831 )、#4592 (同族的后缀剥离缺陷)、#4446 / #4411 (dual-source 门禁看不到这一类)。
在 #4696(PR #5831)实施过程中实证发现的独立缺陷。不在 #4696 范围内(该单红线是「不碰
packages/spec/src/**」),单独立案。现象
packages/spec/src/shared/http.zod.ts在同一个文件里声明了两个内容不同的 enum,而它们经schemaNameFromExportKey剥掉Schema后缀后同名:HttpMethod→HttpMethod,HttpMethodSchema→HttpMethod。build-schemas.ts按命名空间导出顺序逐个写json-schema/{category}/{Name}.json,后写覆盖前写,实测结果:即
json-schema/shared/HttpMethod.json、bundledobjectstack.json的$defs['shared/HttpMethod']、以及content/docs/references/shared/http.mdx#httpmethod只描述 5 值那份。影响
7 值的那份才是
api/*的线上契约 ——api/discovery.zod.ts:482、api/endpoint.zod.ts:60、api/plugin-rest-api.zod.ts:130、api/rest-server.zod.ts:200都import { HttpMethod } from '../shared/http.zod'并用它做method字段。一个照着references/shared/http写HEAD或OPTIONS路由的作者,会以为这两个方法非法;而反过来,任何按发布出去的 JSON Schema 做校验的下游(IDE 自动补全、codegen、AI 元数据作者)拿到的也是被截断的那一份。属于 AGENTS.md「Machine-readable surfaces must not lie」。为什么没有任何门禁发现它
check:dual-source-exports比对的是导出名的符号身份:HttpMethod与HttpMethodSchema是两个不同的导出名,它看不出问题(dual-source-exports.baseline.json当前是entries: [],全绿)。而碰撞发生在剥掉Schema后缀之后的 schema 名上 —— 与 #4592 同族的「后缀剥离改变了名字身份」问题,只是这次落在碰撞检测上而不是拼写上。build-schemas.ts自己也不报:它对generatedSchemas.set(defKey, …)是无条件覆盖,没有「这个 def key 已经写过」的检查。(对照:#4696 给 docs 索引加了「同 category 内同名 = 硬报错」。本单是同一个道理在 JSON Schema 发布侧的缺口 —— 那边的键是
${category}/${name},同样会被静默覆盖。)建议方向(待裁决,不预设)
RateLimitConfig的做法一致):把 5 值那份改成一个不与 7 值那份碰撞的名字(它的类型别名已经叫HttpMethodType了,HttpMethodSubsetSchema之类是顺理成章的),两者各自发布。build-schemas.ts里补一条「同一个 def key 被写第二次 = 报错」的守卫,否则下一个同形状的碰撞仍然静默。三个选项在「真实业务需求 / 长期正确性 / 让 AI 写的元数据不容易出错」三轴上的取舍需要维护者定,本 issue 只做记录。
复现
关联:#4696(发现处,PR #5831)、#4592(同族的后缀剥离缺陷)、#4446 / #4411(dual-source 门禁看不到这一类)。