Skip to content

fix(runtime,tooling): saveMetaItem 进入持久性词表 + 包发布可见性翻转不再静默丢写 (#4754) - #5252

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4754-savemetaitem-durability-wordlist
Aug 4, 2026
Merged

fix(runtime,tooling): saveMetaItem 进入持久性词表 + 包发布可见性翻转不再静默丢写 (#4754)#5252
os-zhuang merged 1 commit into
mainfrom
claude/issue-4754-savemetaitem-durability-wordlist

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #4754

先说重测结果:issue 的清单要按当前 main 重读

issue 的「8 处 / 3 包 4 文件」表是 08-03 测的。在 origin/main(基线 718b229fa,已合并至 4addd9dd)重跑探针,总数与文件分布完全一致,只有行号漂移 —— E6(#5093)对 rest-server enrichment 管线的改动没有增删这一族接缝:

文件 issue 的数 重测的数 说明
packages/runtime/src/domains/packages.ts 3 3 三处指向同一个 saveMetaItem()(行 219),由三层嵌套 catch 各报一次
packages/rest/src/rest-server.ts 2 2 行号 3671 / 4025
packages/metadata-protocol/src/protocol.ts 2 2 行号 7631 / 8769
packages/runtime/src/domains/meta.ts 1 1 行号 161

逐处读完之后,结论和 issue 的预判差别很大:issue 把「catch 里连日志都没有」这个形态当成了「真丢数据」这个实质,而 8 处里只有 1 处真的丢。下面是逐处判定。

逐处判定

真丢失(1 处)—— 已按范本改成 error

packages/runtime/src/domains/packages.ts,ADR-0045 可见性翻转。 这是一次搭别人便车的元数据写入:草稿此时已经发布成功,所以 POST /packages/:id/publish-drafts 无论翻转成不成都答 200,写失败只在响应体里留下一个 unhideError —— 没有运维会读它。于是症状变成「我明明发布了,应用却没出现」,而且要过很久才有人把它和这里联系起来。这正是 #4669 的形状,AGENTS.md 的判据原问答案是(系统看起来完全正常,而它声称写进去的东西没落盘)。

现在按 service-automation start() 的范本在 error 级报告,一行里交清两件事:

  • 后果:点名是哪个包;它的 app 仍以 hidden: true 存着,因而在启动器里不可见;而发布报告的是成功;没有任何东西会重试这次翻转;
  • 修复动作:重跑 POST /packages/:id/publish-drafts(幂等),或直接对该 app 走 PUT /meta/app/:namehidden: false;并带上原始错因。

响应契约不变 —— 仍然 200,仍然带 unhideError

不是降级、故障已答给调用方(3 处)—— 进 shrink-only 基线

  • packages/runtime/src/domains/meta.tscatchdeps.errorFromThrown(e, 400)saveMetaItem 就是这个请求的主操作,失败原样回给调用方(保留协议自己的 .status 和结构化 issues)。没有任何路径在降级后继续,Studio 拿到的是字段级锚定的 4xx/422。
  • packages/metadata-protocol/src/protocol.ts ×2 — 两处都是契约就是逐项结果报告的批量操作:migrateStoredItemsreport.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 skim error」),也正是 #4420 那条 warn 当初没人读的成因。基线条目写清了理由与关闭条件,指向 #5241

基线键是 file::callee(不含行号 —— 行号会因无关改动天天漂),所以 3 处站点合成 2 个键。条目里新增了 sites 字段列出复核过的站点集合,并在表头说明这个粒度的代价。

闸门自身的误报(4 处)—— 修闸门,不写基线

剩下 4 处根本不是代码的问题,是闸门看错了。给正确代码写基线是有害的:基线表头自己写着每条都要有「WHAT closes it」,而正确代码永远关不掉;更糟的是它给下一个作者立了「这闸门会误报,遇红先加基线」的范例 —— 而脚本自己的注释恰恰说,误报到让人绕过的闸门 worth less than no gate, because it also reports success。所以这两个缺陷是修掉的:

① 同文件 concise-arrow 报告器看不见。 闸门文档明说 catch 一侧会追同文件 helper(不然「把失败藏到一层间接后面」就成了最省事的变哑办法),但遍历只访问子节点,而

const logError = (...args: unknown[]) => (globalThis as any).console?.error(...args);

的函数体就是那个调用表达式本身。于是 rest-server.ts 里最响的两处 /meta PUT(logError + sendError)被判成「完全静默」。修完后它们如实显示 loud (error@49 via logError())

② 一处接缝被按嵌套层数重复指认。 一个已被内层 catch 消化掉的调用,仍然算在每一层外层 catch 头上。packages.ts 那一个 saveMetaItem 因此被报了三次 —— 真接缝一次,加上路由级和函数级两个永远看不到它的通用错误处理器。现在只有当内层 catch 每条路径都向外传播时(复用已有的 catchRecovers),外层才算真正的守卫;内层 catch/finally 体里的调用仍然算外层的。覆盖面一点没丢 —— 那个消化掉故障的内层 catch 本身照样被判。

效果

词表加 saveMetaItem,闸门未修:  8 处命中
仅修闸门两个精度缺陷:          4 处命中   ← 4 处误报消失,一行产品代码没动
修掉真丢失那 1 处 + 3 处进基线: 绿
$ pnpm check:durability-log-level
✓ self-test: 19 case(s) passed
✓ durability-degradation log levels: 22 durability-critical catch seam(s), all loud or rethrowing (2 baselined).

测试

自测用例双向钉住(#4690:只绿过的闸门和什么都不匹配的闸门无法区分)。把两个修复分别单独回退,新用例确实失败:

=== 回退修复 ①(concise-arrow)===
  ✗ passes: catch delegating to a loud CONCISE-ARROW helper (expression body):
      expected violation=false, got violation=true count=1
=== 回退修复 ②(嵌套遮蔽)===
  ✗ passes: enclosing catch is not accused when an inner RECOVERING catch already consumed the call:
      expected violation=false count=0, got violation=true count=1
  ✗ flags: the inner catch itself is still judged (no coverage lost to shadowing):
      expected violation=true count=1, got violation=true count=2

最后一条说明了为什么给自测加了 expectCount:布尔断言区分不出「正确地报 1 次」和「按嵌套层数重复报 2 次」 —— 光看「还是红的」这个 bug 能完美蒙混过去。

产品侧新增用例同样做了反向验证(去掉日志即失败:expected "error" to be called 1 times, but got 0 times):

$ pnpm --filter @objectstack/runtime test
 Test Files  89 passed (89)
      Tests  1313 passed (1313)

$ pnpm --filter @objectstack/runtime typecheck
> tsc --noEmit          # 干净

以上均为合并 origin/main(4addd9dd)、pnpm install --frozen-lockfile + 重建依赖图之后重跑的结果;闸门在合并后仍是 22 seams,新入的 driver-sql schema-drift 改动没有新增这一族接缝。

顺带发现(未在本 PR 修,已另立)

未触碰:packages/metadata-protocol/src/seed-loader.ts(#5127 同批)、packages/spec/**content/docs/releases/**,以及派单列出的其余在飞面。rest-server.tsprotocol.ts 虽在声明文件面内,最终没有改动(①/基线已解决)。


🤖 Generated with Claude Code

https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7

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

vercel Bot commented Aug 4, 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 4, 2026 12:26pm

Request Review

@github-actions github-actions Bot added the size/m label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

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

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)
  • content/docs/releases/v17.mdx (via @objectstack/runtime)

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 documentation Improvements or additions to documentation tests tooling labels Aug 4, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 12:28
@os-zhuang
os-zhuang enabled auto-merge August 4, 2026 12:28
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 18b8eaa Aug 4, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4754-savemetaitem-durability-wordlist branch August 4, 2026 12:44
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

Development

Successfully merging this pull request may close these issues.

saveMetaItem 是 #4632 词表覆盖不到的持久性接缝:8 处 catch 完全静默吞掉元数据写入失败

2 participants