Skip to content

shared/http.zod.ts 的两个不同 enum 发布成同一个 JSON Schema 名 HttpMethod,后写覆盖前写 —— 参考页只描述了 5 值那份,7 值的线上契约(HEAD/OPTIONS)不见了 #5832

Description

@baozhoutao

#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>;

HttpMethodHttpMethodHttpMethodSchemaHttpMethodbuild-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:482api/endpoint.zod.ts:60api/plugin-rest-api.zod.ts:130api/rest-server.zod.ts:200import { HttpMethod } from '../shared/http.zod' 并用它做 method 字段。一个照着 references/shared/httpHEADOPTIONS 路由的作者,会以为这两个方法非法;而反过来,任何按发布出去的 JSON Schema 做校验的下游(IDE 自动补全、codegen、AI 元数据作者)拿到的也是被截断的那一份。属于 AGENTS.md「Machine-readable surfaces must not lie」。

为什么没有任何门禁发现它

check:dual-source-exports 比对的是导出名的符号身份:HttpMethodHttpMethodSchema 是两个不同的导出名,它看不出问题(dual-source-exports.baseline.json 当前是 entries: [],全绿)。而碰撞发生在剥掉 Schema 后缀之后的 schema 名上 —— 与 #4592 同族的「后缀剥离改变了名字身份」问题,只是这次落在碰撞检测上而不是拼写上。

build-schemas.ts 自己也不报:它对 generatedSchemas.set(defKey, …) 是无条件覆盖,没有「这个 def key 已经写过」的检查。

(对照:#4696docs 索引加了「同 category 内同名 = 硬报错」。本单是同一个道理在 JSON Schema 发布侧的缺口 —— 那边的键是 ${category}/${name},同样会被静默覆盖。)

建议方向(待裁决,不预设)

  1. 改名(与 spec 双源清账 C9:RateLimitConfig / RateLimitConfigSchema(./integration ≠ ./shared)—— 2 条 #4684RateLimitConfig 的做法一致):把 5 值那份改成一个不与 7 值那份碰撞的名字(它的类型别名已经叫 HttpMethodType 了,HttpMethodSubsetSchema 之类是顺理成章的),两者各自发布。
  2. 合并:确认 UI/view 数据源确实不需要区分,直接复用 7 值那份,删掉 5 值那份。
  3. 无论选哪个,都应在 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 门禁看不到这一类)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions