发现于 #4817(把 content/docs/protocol/kernel/http-protocol.mdx 的 discovery 一节改成两段式时逐字段核对实现),不在该 docs-only PR 范围内修,未认领。
现象
/discovery 属于「机器可读表面」——SDK、codegen、AI 客户端直接读它(AGENTS.md「Route & surface ownership」第 4 条:machine-readable surfaces must not lie)。但两个生产者返回的顶层键,有三个在 packages/spec/src/api/discovery.zod.ts 里根本没有声明:
| 线上字段 |
谁发出 |
spec 里的声明 |
scoping({ enabled, resolution, scoped, environmentId }) |
registerDiscoveryEndpoints,packages/rest/src/rest-server.ts |
无。git grep scoping -- packages/spec/src/api/ 只命中 events.zod.ts 的注释 |
features(顶层 { search, websockets, files, analytics, ai, notifications, i18n }) |
HttpDispatcher.getDiscoveryInfo(),packages/runtime/src/http-dispatcher.ts |
无 —— 而且 DiscoverySchema 的设计说明里明写「capabilities/features was removed because it was fully derivable from services[x].enabled」(discovery.zod.ts:211) |
endpoints(routes 的重复副本,注释写 "Alias for backward compatibility with some clients") |
同上 |
无 |
同时:
- REST 形状永远无法通过
DiscoverySchema.parse()。该 schema 把 name / environment / locale 声明为必填,而 getDiscovery()(packages/metadata-protocol/src/protocol.ts)三个都不产出,rest-server 也不补。
- dispatcher 形状不产出
capabilities(schema 里是可选,所以不报错),于是同一个协议概念被两个生产者用两套互不相交的字段表达:REST 说 capabilities,dispatcher 说 features。
environment 在 schema 里是 z.enum(['production','sandbox','development']),dispatcher 直接塞 getEnv('NODE_ENV', 'development') 的原始值,NODE_ENV=test / staging 都会落在枚举外。
之所以没人发现:唯一在协议层实际引用的是 GetDiscoveryResponseSchema(packages/spec/src/api/protocol.zod.ts:122),它是 DiscoverySchema.partial().required({version:true}).extend({apiName}),而 zod object 默认 strip 未知键 —— 于是「未声明的字段」和「缺失的必填字段」两类问题都被这层宽松包装吃掉了,没有任何 gate 在两端比对。这正是 Prime Directive #10 的 declared ≠ enforced 形状,只是方向反过来:enforced 的比 declared 的多。
为什么值得单开一条
这不是文档问题(#4817 已按实现实际返回的形状把两份示例写对了,包括 scoping / features / endpoints),而是契约问题:文档现在忠实描述了一个 spec 没有声明的线上形状。要么把这三个键补进 schema(并说清 features 与 capabilities 谁是正,endpoints 的退役时间表),要么从生产者删掉。
建议的决策点(需要维护者定,不要猜)
features vs capabilities:统一到一个,还是承认两个端点各有一套?若统一,哪个是正、另一个按 ADR-0087 走退役?
endpoints 这个 backward-compat 别名还有真实消费者吗?没有就删;有就声明并给退役时间表。
scoping 是 REST 层的真实能力协商信息,应当补进 schema而非删除 —— 但补在 DiscoverySchema 还是一个 REST 专属的扩展 schema 里,取决于第 1 点怎么定。
DiscoverySchema 的必填 name / environment / locale:REST 端点补上它们,还是承认 DiscoverySchema 描述的只是 dispatcher 形状、REST 形状另有其名?
修改落点在 packages/spec / packages/rest / packages/runtime,与 #4817 的 docs 车道不同,故未并入。
发现于 #4817,未认领 —— 谁开工谁按 AGENTS.md 认领。
发现于 #4817(把
content/docs/protocol/kernel/http-protocol.mdx的 discovery 一节改成两段式时逐字段核对实现),不在该 docs-only PR 范围内修,未认领。现象
/discovery属于「机器可读表面」——SDK、codegen、AI 客户端直接读它(AGENTS.md「Route & surface ownership」第 4 条:machine-readable surfaces must not lie)。但两个生产者返回的顶层键,有三个在packages/spec/src/api/discovery.zod.ts里根本没有声明:scoping({ enabled, resolution, scoped, environmentId })registerDiscoveryEndpoints,packages/rest/src/rest-server.tsgit grep scoping -- packages/spec/src/api/只命中events.zod.ts的注释features(顶层{ search, websockets, files, analytics, ai, notifications, i18n })HttpDispatcher.getDiscoveryInfo(),packages/runtime/src/http-dispatcher.tsDiscoverySchema的设计说明里明写「capabilities/featureswas removed because it was fully derivable fromservices[x].enabled」(discovery.zod.ts:211)endpoints(routes的重复副本,注释写 "Alias for backward compatibility with some clients")同时:
DiscoverySchema.parse()。该 schema 把name/environment/locale声明为必填,而getDiscovery()(packages/metadata-protocol/src/protocol.ts)三个都不产出,rest-server 也不补。capabilities(schema 里是可选,所以不报错),于是同一个协议概念被两个生产者用两套互不相交的字段表达:REST 说capabilities,dispatcher 说features。environment在 schema 里是z.enum(['production','sandbox','development']),dispatcher 直接塞getEnv('NODE_ENV', 'development')的原始值,NODE_ENV=test/staging都会落在枚举外。之所以没人发现:唯一在协议层实际引用的是
GetDiscoveryResponseSchema(packages/spec/src/api/protocol.zod.ts:122),它是DiscoverySchema.partial().required({version:true}).extend({apiName}),而 zod object 默认 strip 未知键 —— 于是「未声明的字段」和「缺失的必填字段」两类问题都被这层宽松包装吃掉了,没有任何 gate 在两端比对。这正是 Prime Directive #10 的 declared ≠ enforced 形状,只是方向反过来:enforced 的比 declared 的多。为什么值得单开一条
这不是文档问题(#4817 已按实现实际返回的形状把两份示例写对了,包括
scoping/features/endpoints),而是契约问题:文档现在忠实描述了一个 spec 没有声明的线上形状。要么把这三个键补进 schema(并说清features与capabilities谁是正,endpoints的退役时间表),要么从生产者删掉。建议的决策点(需要维护者定,不要猜)
featuresvscapabilities:统一到一个,还是承认两个端点各有一套?若统一,哪个是正、另一个按 ADR-0087 走退役?endpoints这个 backward-compat 别名还有真实消费者吗?没有就删;有就声明并给退役时间表。scoping是 REST 层的真实能力协商信息,应当补进 schema而非删除 —— 但补在DiscoverySchema还是一个 REST 专属的扩展 schema 里,取决于第 1 点怎么定。DiscoverySchema的必填name/environment/locale:REST 端点补上它们,还是承认DiscoverySchema描述的只是 dispatcher 形状、REST 形状另有其名?修改落点在
packages/spec/packages/rest/packages/runtime,与 #4817 的 docs 车道不同,故未并入。发现于 #4817,未认领 —— 谁开工谁按 AGENTS.md 认领。