Skip to content

fix(metadata-protocol): listCommits 把 commit store outage 上抛 503,不再答成「无历史」 (#5980) - #6126

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5980-listcommits-outage
Aug 7, 2026
Merged

fix(metadata-protocol): listCommits 把 commit store outage 上抛 503,不再答成「无历史」 (#5980)#6126
baozhoutao merged 2 commits into
mainfrom
claude/issue-5980-listcommits-outage

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5980

前提复核

单据在 9e3709a 分诊,该文件今天已被 #5998#6051 两次大改并 MERGED,行号全部漂移。以下逐条按内容origin/main(合并基 a65ff1c63)重定位实读:

前提 结论 实读
listCommits() 结尾 } catch { return []; },零日志零区分 成立 函数在 protocol.ts:9857(单据写 :9706,漂 +151 行),catch 原样
JSDoc 含 "Returns [] if the commit store is unavailable" 成立(确为跨行折行,单行 grep 零命中) protocol.ts:9853-9855,整段读取确认
处方 rethrowUnlessMetadataStoreUnprovisioned() 同文件已有,修法无新依赖 成立;计数与单据不符,如实报告 定义 :3161;实际调用点 6 处(:3383 / :3439 / :3614 / :3679 / :4051 / :7211),另 6 处 JSDoc 交叉引用,合计 13 处提及。单据与分诊评论写的「12+ 处调用点」把文档引用一并计入了。不影响结论:处方在同文件同类读上已成建制,本次修改无新 import、无新依赖。
baseline 有 protocol.ts::listCommits 条目,shrink-only 成立 scripts/durability-read-invention.baseline.json,verdict: unfixed-degradation,tracked_by: #5980

前提全部成立(第三条的计数按实测修正),按单据处方实施。

处置

packages/metadata-protocol/src/protocol.ts,只动 listCommitscatch 与其 JSDoc:

} catch (error) {
    // [#5980] Benign (the table has not been provisioned) falls through;
    // everything else is a read that did not happen and leaves as a 503.
    this.rethrowUnlessMetadataStoreUnprovisioned(error);
    return [];
}

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 那例的实测输出就是缺陷本身:

FAIL … > rollbackToPackageCommit stops reporting `success: true` for a rollback it never performed
AssertionError: expected a rejection, but the call resolved with
  {"success":true,"revertedCommits":[],"failed":[]}

3 个 benign / healthy 例全绿 —— 这个分离是「第四处读加入同一条规矩」与「listCommits 现在一律抛」的区别所在。

Lap B —— 限肢还原、baseline 条目已摘(账本与代码互相钉死)

预测:闸门红。实测红,并且直接指出落点:

✗ 1 read seam(s) invent an empty answer for a read that failed, and tell nobody
  packages/metadata-protocol/src/protocol.ts:9919  (in listCommits())
    guards  : find() at line 9900 — a multi-row read (IDataDriver.find)
    found   : catch logs nothing at all and returns `[]` at line 9920
EXIT=1

即:修复缺席而条目已摘 → 红;修复在场而条目未摘 → 按该文件头声明的 shrink-only 语义同样红。两个方向都钉住。

命令与真实输出

$ pnpm --workspace-concurrency=2 --filter @objectstack/metadata-protocol test -- --maxWorkers=2
 Test Files  49 passed (49)
      Tests  502 passed (502)      # 合并 origin/main 后完整重跑,同样 502/502

$ node scripts/check-durability-degradation-log-level.mjs
✓ durability-degradation log levels: 24 durability-critical catch seam(s), all loud, …
✓ read-seam invention (#5186, 3 package roots): 64 read seam(s), none invents an
  unreported empty answer (6 return an empty value on a type-discriminated benign
  branch) (2 baselined).          # 摘条目后 3 → 2

$ node scripts/check-engine-double-contract.mjs
check-engine-double-contract: OK — 73 pinned, 133 in the DEBT ledger, 2 exempt.

$ node scripts/check-nul-bytes.mjs
check-nul-bytes: OK (scanned 5863 tracked text file(s); … no raw ASCII control bytes).

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 maingit merge-tree 预检(exit 0,无冲突路径)→ git merge origin/main(非 rebase)。合入带来 packages/metadatapackages/spec 改动,已重装依赖、重建依赖包并完整重跑,仍 502/502。

必答项

#5841(同文件另一处退休手抄 /no such table/i)——不触其面,不改其定价。
#5841 的落点是 loadMetaFromDb 的 hydration catch(protocol.ts:10659 一带),已经在用 isMissingTableError,并带 #5897storeUnavailable 返回值。本 PR 只动 listCommits 的 catch,两处相距数百行、入口不同、调用方不同。方向上是互相加强而非改价:两处最终都问同一个 isMissingTableError 谓词,本 PR 又少了一处手抄的机会。

#4636(loadMetaFromDb packageId,决策箱)——无交叠,与预期一致。
#4636 的面是 loadMetaFromDb 的 packageId 语义,本 PR 一行未动该函数,也未动它读的任何字段。

#6051 刚落的 degraded 读法 —— 同一措辞体系,rethrowUnlessMetadataStoreUnprovisioned 语义无变化。
实读确认该 guard 在 #6051 后仍是 if (isMissingTableError(error)) return; throw metadataStoreUnavailableError(error);,未被改写。#6051 加的是另一条通路:readItemFromMetadataService / getDiagnosed,针对 MetadataService(loader 链)那半边——它故意不抛,因为两个调用方要同一事实却处置不同。两者措辞体系一致(同为 503 SERVICE_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_UNAVAILABLEdetails无需改动。
  • objectui 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

claude added 2 commits August 7, 2026 02:34
…「无历史」

`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
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 2:48am

Request Review

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT(执行席 PM 验收)

核过:① 改动面 = listCommits 一个 catch + JSDoc 改写(旧 JSDoc 那句「Returns [] if unavailable」正是把缺陷写成契约的证据,新 TSDoc 把因果与 @throws 写全),与 #5532/#5707/#5840 同处方同信封(expectStoreUnavailable 四缝共用,一处再放宽即一文件内 diff 可见);② 7 条新用例里 rollbackToPackageCommit 那条钉住了本 seam 最尖的伤害 —— 其余同族错答一个「问题」,这里把一次没做的「写」报成完成(success:true 假回执),Lap A 的失败输出即缺陷本体;③ 双肢反向验证(限肢还原 + 账本互钉)预测/实测全中;④ revert 三条消费通路(runtime 路由读 e.status、objectui !res.ok throw、rollback 本 PR 覆盖)逐一实读,无需跟进立案,文案观察已按 PD #10 立 objectui#3529;⑤ 前提第三条的调用点计数按实测公开修正(6 调用 + 6 文档引用),不影响修法 —— 这类如实纠偏正是要的;⑥ changeset 带行为变化说明(GET /packages/:id/commits 故障时 503),不触 releases/。

⚠️ 本文件今日第三次落地:进队列由全量跑兜底,若队列前方再有同文件 PR 落地导致冲突,按 #5919 语义合并流程处理。

翻 ready + auto-merge,进队列。


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review August 7, 2026 02:59
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 877545c Aug 7, 2026
24 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5980-listcommits-outage branch August 7, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants