Repository navigation
发布出去的 /api/v1/openapi.json 描述的 built-in 路由一条都不存在 —— 10 个 operation 真实 boot 全部 404(证伪 #5456 的「当前未漂移」) #5588
Description
Activity
进维护者决策收件箱(cli 车道 PM,
session_016FNvXhtSdnEGEfLEsMmvxh,已打needs-user-decision;domain 标签留分诊席)v17 相关性提请注意:这份
/api/v1/openapi.json是随包发布的对外契约文档,consumer 照它生成客户端会全线 404——若 v17 发布窗口临近,可能需要一个止血决定。待拍板三问(dev 的完整三轴论证在 #5456 的 needs_decision 回报,正文有逐条对照表):
- 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,但请明示。 - 若裁 A/B(仍需对账门):集合定义推荐「逐行处置台账」——复用
rest-route-ledger现有 disposition 惯例,每条内建路由 documented 或带 review 过的 undocumented 理由,新路由只有一处要填。 - 先修再落门 vs 门带背离基线落地:推荐先修——「已知背离」基线会把一份发布出去的错误对外契约登记为有意如此,与 PD chore: version packages #10 冲突;仓内基线 idiom 记录的都是内部债,无一是对外契约。
拍板后执行路径:裁 C → #5588 按 C 实施、#5456 关闭(门无对象);裁 A/B → #5588 先修、#5456 解锁按台账形状落门。cli 车道随时可派。
Generated by Claude Code
- built-in 路由段的形状:A 止血改模板(最便宜,一个 PR 可落,但把可配置的
维护者裁定(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 段):
- 第一棒(本单,cli 车道):
packages/rest在registerOpenApiEndpoints的 serve 期流水线里丢弃静态产物中过时的 built-in 段、改由自身路由事实(rest-route-ledger/getRoutes())产出正确的 built-in operations(路径含真实前缀、动词 PATCH、不再描述幽灵路由);rest-openapi-route.test.ts:125等钉住旧形状的 pin 同步翻正为断言新语义(承重翻转,不是删断言)。 - 第二棒(另立单转 spec 座位,Blocked-by 本单):
packages/spec/scripts/build-openapi.ts摘除generateCrudPaths/generateMetadataPaths/generateDiscoveryPaths,发布的静态产物不再携带任何路由描述;发布出去的 OpenAPI 文档components.schemas是空的,而 6 个$ref全部悬空 ——lazySchemaProxy 撞上typeof === 'object'判据 #5168 自洽门随之调整覆盖面。
连带处置:#5456(对账门)关闭,not planned —— 裁 C 后门无对象(rest 既是路由事实又是文档产出方,无两方可对账);若 C 实施受阻改走 A/B,重开它按台账形状落门。
标签:摘
needs-user-decision→pm:queue,归 cli 车道排程(v17 相关,rest 包空出后优先)。
Generated by Claude Code
- 第一棒(本单,cli 车道):
认领: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
验收:ACCEPT(第一棒)(cli 车道 PM,
session_01DWUR56YsttL5sTF72Q75TQ,2026-08-06 07:0xZ)→ PR #5821- 裁定 C 兑现于 rest 半边:built-in 段由
routeManager.getAll()(路由器匹配请求的同一张表)serve 期产出,静态旧段丢弃不合并——前缀跟getApiBasePath()配置、动词即注册动词、幽灵行结构上不可能;{object}展开与声明式端点合并原样保留;components.schemas/info/securitySchemes仍归 spec。 - 证据硬度为本会话之最:真实 boot 逐条探测文档描述的 1360 个 operation,0 条 NOT MOUNTED(issue 的 0/10 表逐行翻面,PR 正文有对照);静态 7 条旧 path 零泄漏由「直接读本进程加载产物比对」的钉子守住;反向验证摘掉丢弃限肢即 8 条红、报错点名幽灵。
- 覆盖面判定合规:不按 disposition 裁剪(server-only 与 public 同为被服务路由);9 条 direct-mount 排除逐条给理由且立 rest 的 9 条 direct-mount 路由对
RestServer不可枚举 —— 因此进不了/openapi.json,也进不了任何运行时自省 #5822 记账(它们对 RestServer 不可枚举,凭空补上正是本单要修的缺陷类);不编造 schema/状态码,匿名路由的 security 取「故意少说」的安全方向。 - CI 亲核:23 检查 0 failure,ESLint
success;changesetrest: minor档正确;文档体积 151→1078 paths 是修复的直接结果(真实路由终于全部被描述),不是膨胀。 - 界外:rest 的 9 条 direct-mount 路由对
RestServer不可枚举 —— 因此进不了/openapi.json,也进不了任何运行时自省 #5822 已立(finding);gen:schemarmSync 整个json-schema/会顺手抹掉gen:openapi的产物,rest 的 openapi 路由测试随后 503 假红——check:generated原地跑 build-schemas 也触发 #5371 未重复立单、改为追加实测佐证评论——查重纪律执行到位。
转 ready + 挂 auto-merge;MERGED 后:①在 #5744 留解锁评论通知 spec 座位(第二棒可动);②rest 线排 #5563(独占窗口,须待 #5673 落地、四包全空)。
Generated by Claude Code
- 裁定 C 兑现于 rest 半边:built-in 段由
- added a commit that references this issue
on Aug 6, 2026
发布出去的
GET /api/v1/openapi.json里,built-in 路由那一段描述的每一条路径都不存在。真实 boot 逐条探测,文档里的路径全部 404;真实路由在另一个前缀上。任何人拿这份文档生成客户端,生成出来的客户端每一个数据调用都会 404。基线:
origin/main@5acb93add。在 #5456 的前提复核中量到(#5456 的 body 断言「当前未漂移,7 条与今天的路由面一致」——该断言被本单证伪),按 Prime Directive #10 单独立单。真实 boot 实测
逐条探测文档描述的路径 vs 真实路由:
动词也错。文档写
PUT {object}/{id},真实是PATCH——服务器对PUT明确回 405:逐条对照
packages/spec/scripts/build-openapi.ts的generateCrudPaths/generateMetadataPaths/generateDiscoveryPaths用basePath = '/api'手写出 7 条 path / 10 个 operation:GET /api/{object}GET /api/v1/data/:object/v1、缺/data)POST /api/{object}POST /api/v1/data/:objectGET /api/{object}/{id}GET /api/v1/data/:object/:idPUT /api/{object}/{id}PATCH /api/v1/data/:object/:idDELETE /api/{object}/{id}DELETE /api/v1/data/:object/:idGET /api/metaGET /api/v1/meta/v1)GET /api/meta/typesGET /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/objectstackpackages/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(crud6 +metadata17 +discovery2 = 25 条),文档也只描述了 10 个 operation。为什么没被发现
serve 期的 enrichment(
rest-server.tsregisterOpenApiEndpoints)只做四件事:覆写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也只是对默认部署正确。/api/v1/data/{object}等 —— 最便宜,但把一个可配置值焊进发布产物,是新的漂移源。/data/{object}),由 rest 在 serve 期用自己的getApiBasePath()拼前缀 —— 和它已经在做的{object}展开同一条流水线。spec 保持与部署无关。openapi-endpoints.ts里产出声明式端点那一段了),spec 只保留它真正拥有的components.schemas/info/securitySchemes。ADR-0076 意义上的唯一属主本来就是 rest(GET /openapi.json有两个属主:rest-server真serve,http-dispatcher的generateOpenApi分支全仓无实现(ADR-0076 D1 影子重复) #5078 已用真实 boot 确权),这样结构上不可能漂移,也就不再需要gen:openapi的 base spec 用 7 条手写 path 描述路由面,与 rest 真实路由无任何对账 —— 漂移了不会红(#5168 的剩余那一半) #5456 那道对账门。关联
gen:openapi的 base spec 用 7 条手写 path 描述路由面,与 rest 真实路由无任何对账 —— 漂移了不会红(#5168 的剩余那一半) #5456(对账门):门要落地,先得决定 base spec 该描述什么;在此之前「模板集合 ↔ ledger 双向相等」在两个方向上都不成立。components.schemas是空的,而 6 个$ref全部悬空 ——lazySchemaProxy 撞上typeof === 'object'判据 #5168(自洽门,已修)、GET /openapi.json有两个属主:rest-server真serve,http-dispatcher的generateOpenApi分支全仓无实现(ADR-0076 D1 影子重复) #5078(属主确权)、gen:schemarmSync 整个json-schema/会顺手抹掉gen:openapi的产物,rest 的 openapi 路由测试随后 503 假红——check:generated原地跑 build-schemas 也触发 #5371(产物生命周期)、fix(rest): 两个端点契约面只宣告匹配器实际会服务的集合 (#5224) #5487 / 声明式端点的两个机器可读面会说谎:runtime-authoredapi行在 /meta/api 与 /openapi.json 里在场,匹配器却永远看不见(真实 boot 实测) #5224(faces announce only what the matcher serves —— 同一类「面在说谎」,那单管的是声明式端点,本单管的是 built-in 路由)。