Repository navigation
check:durability-log-level 结构性看不见「读接缝把故障答成空值」这一类 —— #4825 / #5108 全家都在闸门盲区里 #5186
Description
发现于 #5108(DatabaseLoader 读故障吞成空结果)实现期,不在该单范围内,故单独立卡。未认领 —— 只是记录。观察类,今天没有用户会撞到。
现象
scripts/check-durability-degradation-log-level.mjs 是 #4632 那条规则的机械执行者。它的判据是:
catch块里调了DURABILITY_CRITICAL_CALLEES词表里的操作,而日志级别低于error且没有 rethrow → 红。
这个模型对写/DDL 接缝是合身的,对读接缝则结构性地表达不了。两条都不成立:
- 词表是按 callee 名字匹配的,而读接缝的 callee 是
find/findOne/count这种彻底通用的名字 —— 加进词表会把全仓库每一处数据读取都拖进来,不可行。词表现有 9 项(syncSchema、initObjects、writeRecord、dropPromotedDraftRow…)清一色是写/DDL 语义的专名。 - 更根本的:读接缝的错误形态不是「日志太轻」,而是「编了一个返回值,且一行都不记」。
catch { return []; }在闸门眼里连一个可判级别的日志调用都没有 —— 它检查的那个维度在这里是空的。DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 修掉的那五处正是这个形状:catch {}→[]/false/null,零日志。[metadata] nextEventSeq() 把驱动读失败也当成「表还没建」,静默从 1 重新发号 —— #4632 同形,机械检查覆盖不到 #4825(nextEventSeq的catch { return 1 })是同一族,而且是最贵的那半(写进去的数是错的)。
也就是说:这个族已经在这一个包里出现过三次(#4728 → #4825 → #5108),每次都是人读出来的,闸门一次都没有帮上忙,而且照现在的模型也帮不上。
这一类的可判据形状
三次实例长得一模一样,可以描述:
- 一个
catch包着一次读(driver/engine 的find/findOne/count,或它们的薄封装); catch体里没有任何日志,也没有 rethrow;- 而是
return一个该方法的空值/零值([]/false/null/0/1)。
判据的关键不在「调了什么」,而在「没读到,却编了一个答案出来」。良性豁免也已经有现成词汇:isMissingTableError()(packages/metadata/src/errors.ts 专门为跨包导出它而存在)—— 一个 catch 只要按错误类型区分过、且只在良性分支返回空值,就应当放行。#4825 和 #5108 修完之后的代码正好就是这个形状,可以直接当 fixture。
为什么不该塞进别的单
- 不是
saveMetaItem是 #4632 词表覆盖不到的持久性接缝:8 处 catch 完全静默吞掉元数据写入失败 #4754 的子集。saveMetaItem是 #4632 词表覆盖不到的持久性接缝:8 处 catch 完全静默吞掉元数据写入失败 #4754 是「saveMetaItem这个写 callee 不在词表里,加进去会红 8 处」—— 一个具体的清账,做法就是加一个词表条目 + 逐处判。它完整落地也覆盖不到读接缝:词表法本身就是这里行不通的那个办法。 - 不是
DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 的子集。DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 修的是DatabaseLoader那五处的行为;闸门能不能防住这一族的下一次是另一件事,而且要动scripts/(DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 的派单文件面里没有这块,刻意没越界)。
建议方向(裁决留给维护者)
- 给现有脚本加一条独立规则(不走
DURABILITY_CRITICAL_CALLEES):catch无日志无 rethrow +return空值 + 块内含读调用 → 红,除非catch里出现按错误类型的区分(isMissingTableError之类)。必然需要一份 baseline —— 全仓库这个形状的catch大概率不少,而且相当一部分是合法的功能性降级; - 或者判定这一族不适合机械判(误报率过高),转而只在 AGENTS.md 里把「读接缝不得编造答案」写成明文规则,靠 review 兜 —— 但那样就要接受它会再犯,并且明说这个取舍;
- 或者收窄到只扫元数据/持久化层的读接缝(
packages/metadata、packages/metadata-protocol、packages/objectql的存储缝),把误报面压到可管的范围,再逐步放开。
两条不是本卡要定的;要定的是「这一族目前完全没有机械保护,而它已经复发三次」这一点。
复现
# 现状:五处 `catch {} → 空值` 的读接缝(#5108 修复前的 database-loader.ts)
# 对闸门完全透明 ——
node scripts/check-durability-degradation-log-level.mjs
# → OK,12 durability-critical catch seam(s), all loud or rethrowing关联
#4632(立规矩)、#4728 / #4825 / #5108(同一族的三次复发)、#4754(词表覆盖的另一半:写接缝)、AGENTS.md「Degradation log levels — warn vs error」「Absence must be loud」、packages/metadata/src/errors.ts(良性判据的现成词汇)。
Activity
os-zhuang commentedon Aug 5, 2026
发现分诊轮:转
needs-user-decision,并按落点改domain:engine→domain:devx(摘finding)。scripts/check-durability-degradation-log-level.mjs(门禁脚本),按域表scripts/(门禁类)归 devx;被扫描对象是 metadata/objectql 包不改变落点。DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108)全靠人读,闸门结构性帮不上;良性豁免词汇(isMissingTableError)与 fixture 均现成。个人建议方向 3(先收窄到 metadata/metadata-protocol/objectql 存储缝)成本最可控。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 6, 2026
处置(2026-08-06):退回派发队列,定向方向 3。
复核结论:三次真实复发(#4728/#4825/#5108),「Absence must be loud」是 AGENTS.md 已明文的不变量,机械化它不是新决策。
范围:读接缝规则收窄到 metadata / metadata-protocol / objectql 存储缝 + shrink-only baseline;防误报方法论照本脚本自己的先例(#4754/#5241:显式词表、非启发式)。不取方向 2(只写文档=明示接受第四次复发)。量级 M。
经办:PM 会话
session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:
裁 3——收窄先行:新规则(catch 无日志无 rethrow + return 空值 + 含读调用 → 红,isMissingTableError 豁免)先只扫元数据/持久化层,带 baseline 落地;误报面/成本可控验证后再评估放开全仓(届时由 devx 座位自提,不必回决策箱)。⛔ 不选 2(纯 AGENTS.md 靠 review = 接受 #4728→#4825→#5108 第四次复发)。
流转:摘
needs-user-decision→pm:queue,归 devx 车道(落点 scripts/)。评估与落地会话:
session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
hotlong commentedon Aug 6, 2026
认领:PM 循环第 1 轮(devx 车道)
会话:
session_01BDmDsu2575gDxeMCxXhDE3分支:
claude/issue-5186-durability-read-seam-gateWorktree:
objectstack-issue-5186域:
domain:devx文件面:
scripts/check-durability-degradation-log-level.mjs、scripts/下新增 baseline/fixture、.changeset/(越界即停,报告说明)按 10:44Z 已落地的裁 3 执行:新读接缝规则收窄到 metadata / metadata-protocol / objectql 存储缝 + shrink-only baseline,
isMissingTableError类型区分豁免;⛔ 不做方向 2。Generated by Claude Code
hotlong commentedon Aug 6, 2026
验收(devx 车道,会话
session_01BDmDsu2575gDxeMCxXhDE3):ACCEPT → PR #5986,已转 ready 并挂入合并队列。交付摘要(按维护者 10:44Z「裁 3 —— 收窄先行」):
scripts/check-durability-degradation-log-level.mjs同脚本新增判据独立的读接缝规则:try 含读(find/findOne/count 或薄封装,跟 2 跳)+ catch 零日志 + return 空值/零值 + 未按错误类型区分 → 红;豁免只认声明的isMissingTableError(两种极性 + 重抛式 guard),手抄错误码不算(与 loadMetaFromDb 用 /no such table/i 正则判「良性首启」,其余 sys_metadata 读失败吞成 console.warn + loaded:0 —— isMissingTableError 的手抄第二份 #5841 对齐)。扫描面收窄 metadata / metadata-protocol / objectql;新 shrink-only baseline(64 读接缝,3 入账)。零 CI 接线(lint.yml 一行未动)。DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 修复 → 6 处红 5 处;第 6 处load()信封形状如实写进脚本头「Honest limitations」(放宽会把三包每个 result envelope 误报掉)。baseline shrink-only 双向验证(删条目红 / stale 条目红)。自检 fixture 35→61,全部取自仓内真实代码。seedAutonumber把读故障答成return 0—— 对已有 N 行的表重新从 1 发号,自增号与既有行相撞且零日志(#4825 同族,活体) #5979(seedAutonumbercatch→0,[metadata] nextEventSeq() 把驱动读失败也当成「表还没建」,静默从 1 重新发号 —— #4632 同形,机械检查覆盖不到 #4825 同族最贵形状,连 [P2] A direct engine call silently dropssort/select/skip/populate— declared query contract, zero enforcement #4371 的注释都预言过)、MetadataProtocol.listCommits把 commit store 读不到答成[]—— ADR-0067 时间线上「无历史」与「读不到」不可分辨(零日志,JSDoc 里写着这是设计) #5980(listCommits吞故障答[],ADR-0110 D3 违反,同文件已有现成处方没走);referenceExists复核为合法(签名即契约)以reviewed-legitimate入账。AGENTS.md 指路牌缺口立 [finding] AGENTS.md 的「It has teeth」段只描述check:durability-log-level的日志级别那半 —— #5186 之后同一条命令还跑读接缝规则,散文没跟上 #5981(finding,⛔ 非被否决的方向 2)。skip-changeset。误报面可控的实证:全扫 64 处仅 3 入账 —— 放开全仓的评估待本规则跑一段后由本座位自提(裁决原文授权,不回决策箱)。Generated by Claude Code
feat(devx): read-seam invention rule gains an identity-pass-through c…
docs(scripts): record the measured decision NOT to extend the read-se…
docs(service-automation): re-anchor the dead tracker citations to the…