fix(runtime,tooling): saveMetaItem 进入持久性词表 + 包发布可见性翻转不再静默丢写 (#4754) - #5252
Merged
os-zhuang merged 1 commit intoAug 4, 2026
Merged
Conversation
…nd stop the publish visibility flip losing writes silently (#4754) #4632's gate only knows the callees in DURABILITY_CRITICAL_CALLEES, so it cannot discover a new persistence seam. saveMetaItem was one of the seams it could not see -- the #4669 shape exactly. Adding the entry surfaced 8 catches. Judged one by one, 4 were real reports of the gate's own imprecision and 1 was a genuine silent loss: * packages/runtime/src/domains/packages.ts -- ADR-0045's visibility flip is a metadata WRITE riding on a publish that already succeeded, so the route answers 200 either way and the failure only left an unhideError nobody reads. Now reported at `error` naming the consequence (apps stay stored hidden:true and invisible while the publish reports success) and the fix (re-run publish-drafts, idempotent; or PUT /meta/app/<name> hidden:false), per the service-automation start() exemplar. Response contract unchanged. Two precision defects in the checker, both fixed with bidirectional self-test cases (the pnpm script runs --self-test, so CI enforces them): * A same-file concise-arrow reporter was invisible. The catch side is documented to follow same-file helpers, but the walker only visited children and `const logError = (...a) => console.error(...a)` IS the call expression, so rest-server.ts's two loudest /meta PUT handlers read as silent-swallow. * One seam was accused once per level of nesting. A call already consumed by an inner recovering catch was still attributed to every enclosing catch -- which are generic route-level handlers that are correct as written. Only an inner catch that propagates on every path now leaves the outer catch a real guard. The 3 remaining sites propagate the failure to the caller (meta.ts returns a field-anchored 4xx/422; protocol.ts's two batch paths write it into their per-item outcome report and flip the aggregate). Those are not degradations, so they are baselined with reasons and a closing condition rather than being raised to `error` -- which on the meta.ts path would emit a durability error per off-spec body, the mirror-image failure AGENTS.md warns about. The gate cannot yet express "reported to the caller"; filed as #5241. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 4, 2026 12:28
os-zhuang
enabled auto-merge
August 4, 2026 12:28
os-zhuang
deleted the
claude/issue-4754-savemetaitem-durability-wordlist
branch
August 4, 2026 12:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4754
先说重测结果:issue 的清单要按当前 main 重读
issue 的「8 处 / 3 包 4 文件」表是 08-03 测的。在
origin/main(基线718b229fa,已合并至4addd9dd)重跑探针,总数与文件分布完全一致,只有行号漂移 —— E6(#5093)对 rest-server enrichment 管线的改动没有增删这一族接缝:packages/runtime/src/domains/packages.tssaveMetaItem()(行 219),由三层嵌套 catch 各报一次packages/rest/src/rest-server.tspackages/metadata-protocol/src/protocol.tspackages/runtime/src/domains/meta.ts但逐处读完之后,结论和 issue 的预判差别很大:issue 把「catch 里连日志都没有」这个形态当成了「真丢数据」这个实质,而 8 处里只有 1 处真的丢。下面是逐处判定。
逐处判定
真丢失(1 处)—— 已按范本改成
errorpackages/runtime/src/domains/packages.ts,ADR-0045 可见性翻转。 这是一次搭别人便车的元数据写入:草稿此时已经发布成功,所以POST /packages/:id/publish-drafts无论翻转成不成都答 200,写失败只在响应体里留下一个unhideError—— 没有运维会读它。于是症状变成「我明明发布了,应用却没出现」,而且要过很久才有人把它和这里联系起来。这正是 #4669 的形状,AGENTS.md 的判据原问答案是是(系统看起来完全正常,而它声称写进去的东西没落盘)。现在按
service-automationstart()的范本在error级报告,一行里交清两件事:hidden: true存着,因而在启动器里不可见;而发布报告的是成功;没有任何东西会重试这次翻转;POST /packages/:id/publish-drafts(幂等),或直接对该 app 走PUT /meta/app/:name置hidden: false;并带上原始错因。响应契约不变 —— 仍然 200,仍然带
unhideError。不是降级、故障已答给调用方(3 处)—— 进 shrink-only 基线
packages/runtime/src/domains/meta.ts—catch→deps.errorFromThrown(e, 400)。saveMetaItem就是这个请求的主操作,失败原样回给调用方(保留协议自己的.status和结构化issues)。没有任何路径在降级后继续,Studio 拿到的是字段级锚定的 4xx/422。packages/metadata-protocol/src/protocol.ts×2 — 两处都是契约就是逐项结果报告的批量操作:migrateStoredItems做report.failed++并把该行连原因一起 itemise;复制入包那处failed.push()并把聚合success翻成false、填failedCount/failed[]。这比一行日志更响,而且正是 permission-set backfill (ADR-0094 D4) 现在 100% 失败:行里的active存储列喂进了 #4001 之后严格化的 permission spec #4669 自己采用的「error+ 计数」形状,只不过以结构化数据交付。这三处不能升成
error:meta.ts那条路径最常见的情况是作者提交了不合 spec 的 body,升级后每一次校验拒绝都会打一条持久性error—— 正是 AGENTS.md 点名的镜像错误(「trains everyone to skimerror」),也正是 #4420 那条warn当初没人读的成因。基线条目写清了理由与关闭条件,指向 #5241。闸门自身的误报(4 处)—— 修闸门,不写基线
剩下 4 处根本不是代码的问题,是闸门看错了。给正确代码写基线是有害的:基线表头自己写着每条都要有「WHAT closes it」,而正确代码永远关不掉;更糟的是它给下一个作者立了「这闸门会误报,遇红先加基线」的范例 —— 而脚本自己的注释恰恰说,误报到让人绕过的闸门 worth less than no gate, because it also reports success。所以这两个缺陷是修掉的:
① 同文件 concise-arrow 报告器看不见。 闸门文档明说
catch一侧会追同文件 helper(不然「把失败藏到一层间接后面」就成了最省事的变哑办法),但遍历只访问子节点,而的函数体就是那个调用表达式本身。于是
rest-server.ts里最响的两处/metaPUT(logError+sendError)被判成「完全静默」。修完后它们如实显示loud (error@49 via logError())。② 一处接缝被按嵌套层数重复指认。 一个已被内层
catch消化掉的调用,仍然算在每一层外层catch头上。packages.ts那一个saveMetaItem因此被报了三次 —— 真接缝一次,加上路由级和函数级两个永远看不到它的通用错误处理器。现在只有当内层catch每条路径都向外传播时(复用已有的catchRecovers),外层才算真正的守卫;内层catch/finally体里的调用仍然算外层的。覆盖面一点没丢 —— 那个消化掉故障的内层 catch 本身照样被判。效果
测试
自测用例双向钉住(#4690:只绿过的闸门和什么都不匹配的闸门无法区分)。把两个修复分别单独回退,新用例确实失败:
最后一条说明了为什么给自测加了
expectCount:布尔断言区分不出「正确地报 1 次」和「按嵌套层数重复报 2 次」 —— 光看「还是红的」这个 bug 能完美蒙混过去。产品侧新增用例同样做了反向验证(去掉日志即失败:
expected "error" to be called 1 times, but got 0 times):以上均为合并
origin/main(4addd9dd)、pnpm install --frozen-lockfile+ 重建依赖图之后重跑的结果;闸门在合并后仍是 22 seams,新入的 driver-sql schema-drift 改动没有新增这一族接缝。顺带发现(未在本 PR 修,已另立)
check:durability-log-level分不清「故障已答给调用方」和「故障被吞掉」—— 词表加saveMetaItem后逼出 2 条基线,记的全是正确代码 #5241 — 闸门分不清「故障已答给调用方」和「故障被吞掉」,这正是本 PR 那 2 条基线的来由。根因是saveMetaItem和词表里其他条目不是一类:现有条目清一色是背景副作用(调用方不等结果),而saveMetaItem8 处里 7 处是调用方直面的主操作。观察类,finding。publish-drafts可见性翻转中途失败时,已翻转的 app 从响应里整批消失 ——unhiddenApps丢了,metadata:reloaded也漏播 #5242 — 同一段代码的另一个缺陷:可见性翻转中途失败时,已经翻转成功的 app 从响应里整批消失(unhiddenApps的赋值在循环之后、try 之内),连带metadata:reloaded对这几个 app 漏播。与日志级别无关,改它要动作用域和响应语义,故未夹带。观察类,finding。未触碰:
packages/metadata-protocol/src/seed-loader.ts(#5127 同批)、packages/spec/**、content/docs/releases/**,以及派单列出的其余在飞面。rest-server.ts与protocol.ts虽在声明文件面内,最终没有改动(①/基线已解决)。🤖 Generated with Claude Code
https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7