Skip to content

[finding] 裁定 C 落地后,服务出的 openapi 文档里 components.schemas 无人引用 —— CRUD body 从具体契约退成泛 {type:'object'} #5969

Description

@qq9340100

观察类发现,在 #5744(#5588 裁定 C 第二棒,PR #5967)实施过程中量到。不在该单范围内(落点是 packages/rest,第一棒 #5821 已收官),也不建议顺手改 —— 记下来交分诊。

事实

裁定 C 之后,GET {apiPath}/openapi.json 的 built-in 段由 packages/rest/src/openapi-builtin-paths.tsrouteManager.getAll() 产出。实测该产出不引用任何 #/components/schemas/…:

git grep -n 'components/schemas' -- packages/rest/src   →  0 hits

每个 operation 的形状是(openapi-builtin-paths.ts,operation 构造段):

  • 请求体:content: { 'application/json': { schema: { type: 'object' } } },required: false;
  • 响应:responses = { default: { description: RESPONSE_NOTE } }

与此同时,静态产物贡献的 components.schemas 九条(CreateRequest / UpdateRequest / SingleRecordResponse / ListRecordResponse / DeleteResponse / ApiError / BulkRequest / BulkResponse / BaseResponse)原样进入服务出的文档 —— 但没有任何 operation 指向它们#5744 摘除静态 paths 后,这九条在两份产物里都不再有指向者(静态产物的 $ref 数从 9 归 0)。

为什么是观察类,不是缺陷

旧的 $ref 并不是「好的、被 #5821 弄丢了」:它们挂在 0/10 命中的幽灵路径上,而且形状本身也是错的 —— { data } 信封 vs 裸记录体,packages/spec/src/api/plugin-rest-api.zod.ts 里早有记录。#5821 的取舍(「不编造:请求/响应 schema、状态码、query 参数一律不生成」)在当时是对的:路由表不携带 body 契约这一事实,凭空补上正是该单要修的那类缺陷。

所以今天没有用户会撞上错误信息 —— 只是 Scalar viewer(GET {apiPath}/docs)上 CRUD 的请求/响应从具体 schema 退成了泛 object,以及照文档生成的客户端拿不到类型。是文档质量的一次真实退化,不是正确性缺陷。

如果要修,方向长什么样

要让 body 契约回到文档里,得让路由注册携带这个事实(注册时声明 request/response schema 名,再由 buildBuiltinPaths 转成 $ref),而不是在文档产出侧按路径猜。这是新能力面,按 ADR-0049 的 enforce 路线走,需要先有真实业务拉力(谁在用生成的客户端?)。不建议在没有拉力时开工。

同族的一条小尾巴

packages/rest/src/rest-openapi-route.test.ts 里那条钉子的标题与注释在 #5744 合入后措辞过期:

  • 标题:discards the static artifact section even though spec still emits it —— spec 已不再 emit;
  • 注释:Leg 2 (#5744) removes the spec-side generation. Until it lands, the bundled artifact really does carry /api/{object} & co.

断言本身仍然绿且未整体空洞化:for (const path of stale) 的输入集合变成空集(实测 #5821 pin input set (stale paths): [] -> size 0),但循环之后的三条断言(components.schemas 键集逐字相等、securitySchemes 深等、info.title 相等)仍然咬合 —— 它现在钉的是「spec 拥有的那半边原样穿过 serve 期」。只是措辞需要跟上,并且如果要保住「不许泄漏」这一层语义,得换成一条幸存段仍能触及的输入(例如构造一份带 paths 的 artifact 喂给 loadOpenApiSpec 的缓存),而不是靠静态产物继续发错段来供货。


关联:#5588(裁决锚点)、#5821(第一棒)、#5744 / PR #5967(第二棒)、#5078、ADR-0076、ADR-0049。


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions