Repository navigation
Description
发现于 #4754 的实现期(PR 分支 claude/issue-4754-savemetaitem-durability-wordlist)。未认领 —— 只是记录。观察类:今天没有用户会撞到,代价是一份本不该存在的基线。
现象
#4754 把 saveMetaItem 加进 DURABILITY_CRITICAL_CALLEES。修掉闸门自身两个精度缺陷后,命中从 8 降到 4,其中:
- 1 处是真丢失(
packages/runtime/src/domains/packages.ts的 ADR-0045 可见性翻转:HTTP 200 +unhideError埋在 body 里,零日志)—— 已按范本改成error; - 3 处不是降级,它们把故障原样答给了调用方:
packages/runtime/src/domains/meta.ts—catch→deps.errorFromThrown(e, 400),调用方拿到带issues的 4xx/422;packages/metadata-protocol/src/protocol.ts×2 —migrateStoredItems的report.failed+++ 逐项outcome:'failed';复制入包的failed.push()+ 聚合success:false/failedCount。
按 AGENTS.md 的判据原问 ——「降级之后系统从外面看还正常吗?」—— 这 3 处答案都是否:请求方被明确告知这次写入没有落盘。它们根本不是降级,而是错误传播。但闸门的模型只认「loud 日志 or rethrow」,表达不了「已答给调用方」,于是这 3 处只能进 scripts/durability-degradation.baseline.json。
为什么这是个问题,而不是「基线就是干这个的」
基线文件自己的表头写着「Every entry names WHY it is still here and WHAT closes it」,而且是 shrink-only。给正确代码写基线条目有两个后果:
- 这些条目永远关不掉(代码没毛病),shrink-only 的账本从此有了一批不会缩的行,「基线 = 待还的债」这个语义被稀释;
- 更糟的是它给下一个作者立了个范例:这个闸门会误报,遇红先加基线。而闸门脚本自己的注释恰恰写着,误报率高到让人绕过的闸门「worth less than no gate, because it also reports success」。
还有第三个后果,是这次差点踩到的:面对 meta.ts 那处误报,最省事的「修法」是补一句 logger.error —— 而那条路径最常见的情况是作者提交了不合 spec 的 body,于是每一次校验拒绝都会打一条持久性 error。这正是 AGENTS.md 点名的镜像错误(「trains everyone to skim error」),也正是 #4420 那条 warn 当初没人读的成因。一个闸门,最省力的满足方式是有害的,那闸门的形状就有问题。
根因:saveMetaItem 和词表里其他条目不是一类
现有词表条目(syncSchema、writeRecord、writeDeferredReference、rearmSuspendedWaitTimers、dropPromotedDraftRow、deliverPersistedRow…)清一色是背景副作用:调用方并不在等这一次写的结果,某个更外层的操作会照常报成功 —— 所以「catch 必须响」对它们无一例外地成立。
saveMetaItem 不同:8 处命中里 7 处是调用方直面的主操作(HTTP 写元数据、批量迁移/复制),只有 1 处是搭别人便车的副作用。#4669 那个真事故也正是副作用型。词表按 callee 名字匹配,而这个 callee 横跨两类,精度就掉了。
建议方向(裁决留给维护者,本卡不预设)
- 给闸门加一份「故障传播词汇」(和
DURABILITY_CRITICAL_CALLEES一样是显式声明的,不猜):一个catch若每条路径都把故障交了出去,就等价于 rethrow。HTTP 形状好办 ——errorFromThrown/sendError是专名;难的是 protocol.ts 那种结构化逐项结果报告(report.failed++、failed.push({...})),按名字识别就退回成脚本注释明确拒绝的启发式了; - 收窄词表:承认这一族要区分「副作用型写」和「主操作型写」,而 callee 名字做不到这个区分 —— 那就接受
saveMetaItem只能靠基线管住,并把基线表头的语义从「待还的债」放宽成「已复核的例外」,明说这个取舍; - 维持现状(3 处基线),只把这份取舍记在案。
我倾向 1,但它对第二种形状(结构化结果报告)是否可机械判,我没有把握 —— 这正是本卡不自作主张的原因。
现状
#4754 的 PR 已经落了这些,本卡不阻塞它:
- 修好闸门两个真缺陷(见下,它们不在本卡范围,已随
saveMetaItem是 #4632 词表覆盖不到的持久性接缝:8 处 catch 完全静默吞掉元数据写入失败 #4754 落地); - 真丢失那 1 处改成
error; - 3 处进基线,
closes字段指向本卡。
本卡关闭时,应能删掉那 2 条基线条目(键是 file::callee,3 处站点合成 2 个键)。
关联
#4632(立规矩)、#4669(副作用型的那次真事故)、#4754(加词表 + 清账)、#5186(同一闸门的另一个盲区:读接缝的漏报;本卡是反方向的误报)、AGENTS.md「Degradation log levels — warn vs error」。
Activity
baozhoutao commentedon Aug 5, 2026
发现分诊轮判级(session_01VkPSGsX9o17MsGv3Lbxu2w,2026-08-05):晋级
pm:queue。前提核验见 #4604 01:57Z(对b4ad98435成立:两条给正确代码写的条目仍在,closes自署 #5241)。理由:shrink-only 账本里有了永不会缩的行,账本可信度受损;改动机械、条目自带 closer。维护者可否决。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
认领:PM 循环第 1 轮追加批(engine-core 车道,维护者要求提速,batch 提至 5)
会话:
session_01V7WetGmnfoXNn8cLieKKmx分支:
claude/issue-5241-durability-gate-propagationWorktree:
objectstack-issue-5241域:
domain:engine-core文件面:
scripts/check-durability-degradation-log-level.mjs、scripts/durability-degradation.baseline.json(删两条 #5241 条目)+ 闸门自测,.changeset/*.md视先例取舍。方向裁决(PM 拍板,维护者可否决——不升级的依据:这是验证策略/闸门形状,非产品语义):取方向 1。理由:两条基线条目的
closes字段自己写明的关闭条件就是「teach the checker a DECLARED failure-propagation vocabulary」,方向 1 与闸门「显式声明、不猜」的既有哲学一致;方向 2 稀释 shrink-only 账本语义,方向 3 留着永不缩的行。对 issue 作者存疑的第二形状(report.failed++/failed.push结构化逐项报告):若确实无法在不引入脚本注释所拒绝的启发式的前提下机械判定,允许退到站点级显式声明(如file::callee粒度的 declared error-propagation 清单,每条带理由)——它仍是「显式声明」,只是粒度更细;⛔ 不接受按名字/形状猜的启发式。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
复核通过,ACCEPT(engine-core 车道 PM,第 1 轮追加批):PR #5616,当前 draft 停放,唯一红是 base 分支的 #5604 签名(dev 已用单文件隔离实验在 #5604 钉死因果,并证明本 PR 自己的 durability 门在同一次 CI 里是绿的);#5604 落地后同步 main → 红清零 → 转 ready 入队。
交付按方向 1 落地且形状比裁决更硬:声明只给名字(
FAILURE_PROPAGATION_CALLEES按via: return/effect分交付方式;站点级声明键取「文件+函数名」而非 file::callee,避免给 9000 行文件发全文件许可证),结构仍由catchDeliversFailure()证明每条出口路径都交付了故障,证不出来判「没交付」。两条基线条目删除、账本清空为稳态;AGENTS.md 补第三种合法应答。自测 19→35 条,六项反向验证全部命中预测(B 最关键:packages.ts把unhideError写进响应体这种「像传播的赋值」被拒绝放行 —— 新词汇没把 #4754 的真丢失重新弄绿)。模板与实况的一处偏差(基线已空无真降级条目可移除)dev 如实报告,等价证明(实验 A/B)成立,接受。skip-changeset已挂(scripts + AGENTS.md,发布产物不变)。Generated by Claude Code
fix(runtime,tooling): add saveMetaItem to the durability vocabulary a…
fix(scripts): teach check:durability-log-level a declared failure-pro…