fix(metadata-protocol): listCommits 把 commit store outage 上抛 503,不再答成「无历史」 (#5980) - #6126
Conversation
…「无历史」
`listCommits` 读 `sys_metadata_commit` 的 catch 对任何失败都返回 `[]`,零日志、
不按错误类型区分 —— 它的 JSDoc 还把这写成了设计("Returns [] if the commit
store is unavailable"),于是后来者照着读成契约。
这是 ADR-0110 D3 在 ADR-0067 提交时间线上的违反:miss 与 outage 是意义相反的
两个事实。后果面朝向 revert,因为这条时间线正是 revertCommit 的选择面:
GET /packages/:id/commits → { commits: [] },故障期间 UI 显示「无可回滚项」
rollbackToPackageCommit → 过滤同一个 [],一次都没回滚却返回 success: true
第二条是本 seam 比同族其它读更尖的地方:别的读答错的是一个问题,这一处把一次
没做的写报成了完成(实测复现:{"success":true,"revertedCommits":[],"failed":[]})。
改为按错误类型区分,走本文件既有的 rethrowUnlessMetadataStoreUnprovisioned
(#5532 为 getMetaItems 引入,#5707 / #5840 沿用):表未 provision 的首启仍返回
`[]`;其余失败包成 503 SERVICE_UNAVAILABLE 上抛,驱动原始错误挂 cause。无新
依赖、无新 import。
JSDoc 改为如实措辞;scripts/durability-read-invention.baseline.json 摘除
`protocol.ts::listCommits` 条目(shrink-only)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
ACCEPT(执行席 PM 验收) 核过:① 改动面 = 翻 ready + auto-merge,进队列。 Generated by Claude Code |
Fixes #5980
前提复核
单据在
9e3709a分诊,该文件今天已被 #5998、#6051 两次大改并 MERGED,行号全部漂移。以下逐条按内容在origin/main(合并基a65ff1c63)重定位实读:listCommits()结尾} catch { return []; },零日志零区分protocol.ts:9857(单据写 :9706,漂 +151 行),catch原样protocol.ts:9853-9855,整段读取确认rethrowUnlessMetadataStoreUnprovisioned()同文件已有,修法无新依赖:3161;实际调用点 6 处(:3383 / :3439 / :3614 / :3679 / :4051 / :7211),另 6 处 JSDoc 交叉引用,合计 13 处提及。单据与分诊评论写的「12+ 处调用点」把文档引用一并计入了。不影响结论:处方在同文件同类读上已成建制,本次修改无新 import、无新依赖。protocol.ts::listCommits条目,shrink-onlyscripts/durability-read-invention.baseline.json,verdict: unfixed-degradation,tracked_by: #5980前提全部成立(第三条的计数按实测修正),按单据处方实施。
处置
packages/metadata-protocol/src/protocol.ts,只动listCommits的catch与其 JSDoc:JSDoc 删掉 "Returns [] if the commit store is unavailable"。这句的问题不只是描述了缺陷,是把缺陷写成了契约,后来者照着读就不会再怀疑它。换成如实措辞:
[]只表示「确实没有提交历史」(首启未 provision / 无人 apply 过),outage 一律 503 上抛、驱动原始错误挂cause。为什么是缺陷而非设计 —— ADR-0110 D3:miss 与 outage 是意义相反的两个事实。后果面朝向 revert,因为这条时间线正是
revertCommit的选择面:GET /packages/:id/commits→{ commits: [] },故障期间 UI 显示「无可回滚项」,运维判断做反;rollbackToPackageCommit过滤的正是这个[]→ 一次都没回滚,却返回success: true。第二条是本 seam 比同族其它读更尖的地方:别的读答错的是一个问题,这一处把一次没做的写报成了完成。已在测试里实测复现(见下)。
scripts/durability-read-invention.baseline.json摘除该条目(shrink-only,修好即摘)。测试与反向验证(预测先写死,再运行)
新增 7 例,放在既有的
protocol.metadata-store-outage.test.ts—— #5532 / #5707 / #5840 同一份规矩的 covers 文件,共用expectStoreUnavailable,使四处 seam 的信封不会各自漂移。Lap A —— 把
catch临时改回裸return []方向预测(运行前写死):ordinary red,只翻 outage 半边。预测 4 红 / 3 绿(新增例),既有 18 例全绿。
实测:
Tests 4 failed | 498 passed (502)—— 完全命中。翻红的正是 4 个 outage 例,其中
rollbackToPackageCommit那例的实测输出就是缺陷本身:3 个 benign / healthy 例全绿 —— 这个分离是「第四处读加入同一条规矩」与「listCommits 现在一律抛」的区别所在。
Lap B —— 限肢还原、baseline 条目已摘(账本与代码互相钉死)
预测:闸门红。实测红,并且直接指出落点:
即:修复缺席而条目已摘 → 红;修复在场而条目未摘 → 按该文件头声明的 shrink-only 语义同样红。两个方向都钉住。
命令与真实输出
typecheck:@objectstack/metadata-protocol没有typecheck脚本(package.json的 scripts 只有build / dev / clean / test / test:watch),如实记录,不伪造 —— #4311 台账那一类。类型正确性由pnpm --filter '@objectstack/metadata-protocol^...' build(含 DTS,exit 0)与全量 vitest 覆盖。合并纪律:
git fetch origin main→git merge-tree预检(exit 0,无冲突路径)→git merge origin/main(非 rebase)。合入带来packages/metadata与packages/spec改动,已重装依赖、重建依赖包并完整重跑,仍 502/502。必答项
#5841(同文件另一处退休手抄
/no such table/i)——不触其面,不改其定价。#5841 的落点是
loadMetaFromDb的 hydration catch(protocol.ts:10659一带),已经在用isMissingTableError,并带 #5897 的storeUnavailable返回值。本 PR 只动listCommits的 catch,两处相距数百行、入口不同、调用方不同。方向上是互相加强而非改价:两处最终都问同一个isMissingTableError谓词,本 PR 又少了一处手抄的机会。#4636(
loadMetaFromDbpackageId,决策箱)——无交叠,与预期一致。#4636 的面是
loadMetaFromDb的 packageId 语义,本 PR 一行未动该函数,也未动它读的任何字段。#6051 刚落的 degraded 读法 —— 同一措辞体系,
rethrowUnlessMetadataStoreUnprovisioned语义无变化。实读确认该 guard 在 #6051 后仍是
if (isMissingTableError(error)) return; throw metadataStoreUnavailableError(error);,未被改写。#6051 加的是另一条通路:readItemFromMetadataService/getDiagnosed,针对 MetadataService(loader 链)那半边——它故意不抛,因为两个调用方要同一事实却处置不同。两者措辞体系一致(同为 503SERVICE_UNAVAILABLE,ErrorCode词表内,cause带原始错误,消息含 "unknown" 不含 "not found",由同一个expectStoreUnavailable/expectLoaderOutage断言),差别只在错误来源能否以 throw 抵达本层。listCommits是 engine 直读,失败以 throw 抵达,因此走 guard 这条,与 #5532 / #5707 同形。revert 面 —— 只测只答,未顺手修;结论:后端无需跟进,UI 侧亦无正确性缺口。
rollbackToPackageCommit:已在本 PR 覆盖(见 Lap A 实测),503 正确穿透,不再产出success: true假回执。GET /packages/:id/commits(packages/runtime/src/domains/packages.ts:347):catch (e) => deps.errorFromThrown(e, 500),而HttpDispatcher.errorFromThrown(http-dispatcher.ts:718-724)优先读e.status,故 503 原样上线、code: SERVICE_UNAVAILABLE进details。无需改动。packages/app-shell/src/preview/commitHistory.ts:36:if (!res.ok) throw new Error(...),故 503 在 UI 侧以异常呈现,不会被渲染成「无历史」。无需跟进。 唯一可议之处是那句异常文案是通用的commits HTTP 503,操作者看不到「commit store 不可达、可重试」这层意思 —— 属观察类文案打磨,不是缺陷,未立案亦未顺手改,在此点名供 PM 裁量。Generated by Claude Code