Skip to content

发布出去的 /api/v1/openapi.json 描述的 built-in 路由一条都不存在 —— 10 个 operation 真实 boot 全部 404(证伪 #5456 的「当前未漂移」) #5588

Closed

Description

@baozhoutao

发布出去的 GET /api/v1/openapi.json 里,built-in 路由那一段描述的每一条路径都不存在。真实 boot 逐条探测,文档里的路径全部 404;真实路由在另一个前缀上。任何人拿这份文档生成客户端,生成出来的客户端每一个数据调用都会 404。

基线:origin/main @ 5acb93add。在 #5456 的前提复核中量到(#5456 的 body 断言「当前未漂移,7 条与今天的路由面一致」——该断言被本单证伪),按 Prime Directive #10 单独立单。

真实 boot 实测

pnpm dev:crm -- --fresh -p 39177
GET /api/v1/openapi.json  →  200, 151 paths({object} 已按 CRM 对象展开)

逐条探测文档描述的路径 vs 真实路由:

文档里的(展开后)路径                     真实结果
/api/crm_contact                       404   {"error":"Not found"}
/api/meta                              404   {"error":"Not found"}
/api/meta/types                        404   {"error":"Not found"}
/api/.well-known/objectstack           404   {"error":"Not found"}

真实路由                                  结果(401 = 路由在,鉴权挡住)
/api/v1/data/crm_contact               401   {"error":"UNAUTHENTICATED",...}
/api/v1/meta                           401
/api/v1/discovery                      200
/.well-known/objectstack               200   ← dispatcher 在根上服务,不在 /api 下

动词也错。文档写 PUT {object}/{id},真实是 PATCH——服务器对 PUT 明确回 405:

PUT   /api/v1/data/crm_contact/xyz   →  405
PATCH /api/v1/data/crm_contact/xyz   →  401

逐条对照

packages/spec/scripts/build-openapi.ts 的 generateCrudPaths / generateMetadataPaths / generateDiscoveryPaths 用 basePath = '/api' 手写出 7 条 path / 10 个 operation:

base spec operation 真实 rest 路由 判定
GET /api/{object} GET /api/v1/data/:object 路径错(缺 /v1、缺 /data)
POST /api/{object} POST /api/v1/data/:object 路径错
GET /api/{object}/{id} GET /api/v1/data/:object/:id 路径错
PUT /api/{object}/{id} PATCH /api/v1/data/:object/:id 路径错 且动词错(PUT 回 405)
DELETE /api/{object}/{id} DELETE /api/v1/data/:object/:id 路径错
GET /api/meta GET /api/v1/meta 路径错(缺 /v1)
GET /api/meta/types 全仓没有这条路由 描述了一条谁都不服务的路由(最接近的是 GET /api/v1/meta,它返回 types)
GET /api/meta/{type} GET /api/v1/meta/:type 路径错(缺 /v1)
GET /api/meta/{type}/{name} GET /api/v1/meta/:type/:name 路径错(缺 /v1)
GET /api/.well-known/objectstack rest 没有这条路由 dispatcher(packages/runtime/src/dispatcher-plugin.ts:658)在根路径 /.well-known/objectstack 上服务;rest 的 discovery 是 GET /api/v1 + GET /api/v1/discovery

字面比对:0/10 命中。补 /v1 和 /data 之后仍有 3/10 对不上。

反方向同样不等:RestServer.getRoutes() + 两个 direct-mount registrar 枚举出 91 条真实路由;即使只看 base spec 自称覆盖的三个 ledger family(crud 6 + metadata 17 + discovery 2 = 25 条),文档也只描述了 10 个 operation。

为什么没被发现

serve 期的 enrichment(rest-server.ts registerOpenApiEndpoints)只做四件事:覆写 servers[0](只写 origin,不含 basePath)、展开 {object}、合并声明式端点、覆写 info.version。没有任何一步重写 path 前缀。而 check:generated 的收尾台账把 gen:openapi 记为无门禁({ gen: 'gen:openapi', why: 'the OpenAPI document is generated but no check gate compares it to the routes' }),#5168 补的是产物自洽门($ref 能解析、schema 不降级),不看路由。所以两边各自「正确」,合起来全错,全绿。

现有测试还把错误形状钉住了:packages/rest/src/rest-openapi-route.test.ts:125 断言 body.paths['/api/{object}']['x-template'] === true —— 它证明了服务出去的文档字面上就带着 /api/{object}。

处置需要一次契约裁决(本单不预设)

注意 apiPath 是可配置的(api.apiPath ?? api.basePath + '/' + api.version,rest-server.ts:2830),所以 packages/spec 里的静态 JSON 原则上无法对所有部署写对前缀——今天写 /api 只是错得更彻底,写死 /api/v1 也只是对默认部署正确。

关联

Activity

  1. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 5, 2026
  2. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    ContributorAuthor

    进维护者决策收件箱(cli 车道 PM,session_016FNvXhtSdnEGEfLEsMmvxh,已打 needs-user-decision;domain 标签留分诊席)

    v17 相关性提请注意:这份 /api/v1/openapi.json 是随包发布的对外契约文档,consumer 照它生成客户端会全线 404——若 v17 发布窗口临近,可能需要一个止血决定。

    待拍板三问(dev 的完整三轴论证在 #5456 的 needs_decision 回报,正文有逐条对照表):

    1. built-in 路由段的形状:A 止血改模板(最便宜,一个 PR 可落,但把可配置的 apiPath 焊进静态发布产物——修掉今天这一份,修不掉这一类)/ B 模板相对化 + rest serve 期拼前缀(spec 保持部署无关,仍需 gen:openapi 的 base spec 用 7 条手写 path 描述路由面,与 rest 真实路由无任何对账 —— 漂移了不会红(#5168 的剩余那一半) #5456 的门)/ C built-in 段整体移交 rest 产出(rest 已在产出声明式端点段;ADR-0076 属主 GET /openapi.json 有两个属主:rest-server 真serve,http-dispatcher 的 generateOpenApi 分支全仓无实现(ADR-0076 D1 影子重复) #5078 已确权;结构上消灭第二真相源,gen:openapi 的 base spec 用 7 条手写 path 描述路由面,与 rest 真实路由无任何对账 —— 漂移了不会红(#5168 的剩余那一半) #5456 的门随之不需要存在)。dev 推荐 C 其次 B 反对 A,本席同意——与 fix(rest): 两个端点契约面只宣告匹配器实际会服务的集合 (#5224) #5487 刚立的「faces announce only what the matcher serves」同一原则;若需止血热修可先 A 后 C,但请明示。
    2. 若裁 A/B(仍需对账门):集合定义推荐「逐行处置台账」——复用 rest-route-ledger 现有 disposition 惯例,每条内建路由 documented 或带 review 过的 undocumented 理由,新路由只有一处要填。
    3. 先修再落门 vs 门带背离基线落地:推荐先修——「已知背离」基线会把一份发布出去的错误对外契约登记为有意如此,与 PD chore: version packages #10 冲突;仓内基线 idiom 记录的都是内部债,无一是对外契约。

    拍板后执行路径:裁 C → #5588 按 C 实施、#5456 关闭(门无对象);裁 A/B → #5588 先修、#5456 解锁按台账形状落门。cli 车道随时可派。


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 6, 2026
  4. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    ContributorAuthor

    维护者裁定(2026-08-06,经 cli 车道 PM session_01DWUR56YsttL5sTF72Q75TQ 誊写):C —— built-in 路由段整体移交 rest 产出。spec 只保留它真正拥有的 components.schemas / info / securitySchemes;ADR-0076 属主 rest(#5078 确权),结构上消灭第二真相源。v17 窗口若临近可先 A 止血,但默认直接实施 C。

    执行拆分(两棒,先 rest 后 spec,保证服务出的文档任何时刻不缺 built-in 段):

    1. 第一棒(本单,cli 车道):packages/rest 在 registerOpenApiEndpoints 的 serve 期流水线里丢弃静态产物中过时的 built-in 段、改由自身路由事实(rest-route-ledger / getRoutes())产出正确的 built-in operations(路径含真实前缀、动词 PATCH、不再描述幽灵路由);rest-openapi-route.test.ts:125 等钉住旧形状的 pin 同步翻正为断言新语义(承重翻转,不是删断言)。
    2. 第二棒(另立单转 spec 座位,Blocked-by 本单):packages/spec/scripts/build-openapi.ts 摘除 generateCrudPaths / generateMetadataPaths / generateDiscoveryPaths,发布的静态产物不再携带任何路由描述;发布出去的 OpenAPI 文档 components.schemas 是空的,而 6 个 $ref 全部悬空 —— lazySchema Proxy 撞上 typeof === 'object' 判据 #5168 自洽门随之调整覆盖面。

    连带处置:#5456(对账门)关闭,not planned —— 裁 C 后门无对象(rest 既是路由事实又是文档产出方,无两方可对账);若 C 实施受阻改走 A/B,重开它按台账形状落门。

    标签:摘 needs-user-decision → pm:queue,归 cli 车道排程(v17 相关,rest 包空出后优先)。


    Generated by Claude Code

  5. self-assigned this
    on Aug 6, 2026
  6. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    ContributorAuthor

    认领:PM 循环第 3 轮(cli 车道,v17 优先)
    会话:session_01DWUR56YsttL5sTF72Q75TQ
    分支:claude/issue-5588-openapi-builtin-rest-owned
    Worktree:objectstack-issue-5588
    域:domain:cli(裁决 C 的第一棒,rest 半边)
    文件面:packages/rest(openapi-endpoints.ts / rest-server.ts 的 registerOpenApiEndpoints 流水线 + rest-openapi-route.test.ts 等 pin)+ changeset;⛔ 不触 packages/spec(第二棒 #5744 归 spec 座位,Blocked-by 本单)

    执行注记:①serve 期丢弃静态产物中过时的 built-in 段、由 rest 自身路由事实产出正确段(真实前缀经 getApiBasePath()、PATCH 非 PUT、无幽灵路由);②rest-openapi-route.test.ts:125 等钉旧形状的 pin 承重翻转;③在飞注意:PR #5788(纯 rest 测试替身,已在合并队列)与本单文件面不相交,落地后 merge origin/main 照常;④验收对照 issue 正文的探测表,PR 正文给真实 boot 的前后对比。


    Generated by Claude Code

  7. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    ContributorAuthor

    验收:ACCEPT(第一棒)(cli 车道 PM,session_01DWUR56YsttL5sTF72Q75TQ,2026-08-06 07:0xZ)→ PR #5821

    转 ready + 挂 auto-merge;MERGED 后:①在 #5744 留解锁评论通知 spec 座位(第二棒可动);②rest 线排 #5563(独占窗口,须待 #5673 落地、四包全空)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions