Repository navigation
getMetaItem 的 overlay 读用裸 catch 把「sys_metadata 不可达」吞成「该项不存在」—— GET /meta/:type/:name 在存储故障时回一个无 code 的 400「not found」 #5532
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 5, 2026 分诊:
pm:queue+domain:engine。- 落点锚定:正文已给 file:line ——
packages/metadata-protocol/src/protocol.ts:3149的裸 catch(getMetaItem 的 overlay 读)与:5413的无 code「not found」;metadata*家族按域表归domain:engine。 - 过时前提检查:origin/main 上该文件自立单以来无新合并,裸 catch 仍在,前提成立。
- 查重:与 sys_metadata 不可用被 mapDataError 的 unknown-object 启发式误报成 404 OBJECT_NOT_FOUND,且 404 属「预期状态」因而一行日志都不留 #5462(REST 层启发式,PR fix(rest): 缺表错误只在「缺的正是所请求对象」时才判为 unknown object (#5462) #5530 已收口)、
/meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489(handleRouteError 兜底,finding)、DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108(DatabaseLoader 复数读,已修)同族不同位,正文的区分论证核验成立,无在飞 PR 覆盖。 - 修法方向:方向 A(catch 内区分 miss 与 outage)有 ADR-0110 D3 与
DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 先例锚定,C(终末错误结构化)为顺手卫生,无需维护者另行拍板;draft 分支与 getMetaItems 的同型未验证点宜在实现时一并核。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 落点锚定:正文已给 file:line ——
认领:PM 循环第 2 轮主批(engine-core 车道)
会话:session_01V7WetGmnfoXNn8cLieKKmx
分支:claude/issue-5532-getmeta-outage-vs-miss
Worktree:objectstack-issue-5532
域:domain:engine-core
文件面:packages/metadata-protocol/src/protocol.ts(getMetaItem overlay 读 + getMetaItemCached 终末错误;同型的 draft 分支与 getMetaItems 复数读一并核验)、metadata-protocol 测试、.changeset/*.md。
修法按分诊确认:方向 A(catch 内区分 miss 与 outage,ADR-0110 D3 / #5108 先例锚定)+ 顺手 C(终末 not found 结构化)。前提已对合并后 main 复核:裸 catch 注释现于 :3365(issue 引用的 3149 已漂移),:3081 另有一处同型复数读。#5596/#5627 今天两次动过本文件,行号以内容定位。
Generated by Claude Code
复核通过,ACCEPT(engine-core 车道 PM,第 2 轮主批):PR #5705,CI 23 项全绿,转 ready 入队。
交付超出立单面且全部有据:同族裸 catch 实测 4 处(单数 / draft / preview / 复数)统一走新私有判别
rethrowUnlessMetadataStoreUnprovisioned(复用 #5108/#4867 的isMissingTableError,未建表良性、其余 503+SERVICE_UNAVAILABLE挂 cause);方向 C 落地(终末 miss → 404+RESOURCE_NOT_FOUND);零新增 error-code 词汇(标准目录自带),spec/rest/metadata 生产码零改动。公开认账一条派发词漂移:我预设的 wire 翻转「400→404」是 #5489 之前的旧形状,dev 实测纠正为 500→404,方向不变、值以实测为准 —— 后续读者以 PR 的表为准。双向反向验证红集分离(7/5 与 3/9,证明 404 是方向 C 自身贡献而非拆分副产品),objectql 侧两个钉死旧行为的 fixture 按「整条替换」处理并保留良性对照。顺带发现已核实:#5706(getEffectiveLock 故障时保护闸门 fail-open,被拒的写被放行且无 denied 审计)—— 可达缺陷,建议分诊优先定级,本车道将在 protocol.ts 下一个空位优先认领;#5707(三层视图展示失真,finding)。
Generated by Claude Code
- added a commit that references this issue
on Aug 6, 2026 - added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 23, 2026 - added a commit that references this issue
on Sep 28, 2026
做 #5462(
mapDataError的 unknown-object 启发式误报)时,在同一个真实 harness 上打出来的旁证。不在那单范围内(那单的文件面被明确限定为packages/rest/src/rest-server.ts+packages/rest测试,本条的根因在packages/metadata-protocol的产出方),按 Prime Directive #10 单独记在这里,unassigned。现象
sys_metadata整体不可用时,GET /api/v1/meta/object/acct回:无
code,措辞是「这一项不存在」,真实情况是「元数据存储读不到」。和 #5462 是同一族的错误结论(可用性故障被讲成「你要的东西不存在」),但产出方不同、路由不同、状态码也不同:#5462 那条是启发式在 REST 层把它升格成 404,这条是产出方自己就已经把故障转写成了「不存在」,REST 层拿到的时候真相已经没了。顺带的两个次生事实:
mapDataError的终末兜底{ status: 400, error: raw }(Metadata item object/acct not found不含object not found子串,也匹配不到别的分支),所以内部措辞是逐字上线的。[REST] Unhandled error日志(400 无 code 不属isExpectedRouteError),所以运维侧不像 sys_metadata 不可用被 mapDataError 的 unknown-object 启发式误报成 404 OBJECT_NOT_FOUND,且 404 属「预期状态」因而一行日志都不留 #5462 那样全黑 —— 但日志里记的也是「not found」,同样看不出是存储故障。根因(file:line)
packages/metadata-protocol/src/protocol.ts:3149,getMetaItem的 customization-overlay 读:裸
catch,无日志。注释自己就写着 "DB not available",然后照 miss 处理。驱动抛的SQLITE_ERROR: no such table: sys_metadata到此为止,item保持undefined,一路穿过 registry / MetadataService 兜底,最后由getMetaItemCached(同文件:5413)转成:—— 一个既无
status也无code的Error。复现(in-process,已实跑)
真实
ObjectQL+ 真实ObjectStackProtocolImplementation,驱动每个方法都抛SQLITE_ERROR: no such table: sys_metadata(即 #5464 / #5462 用的那套 harness)。在协议对象上挂一层 Proxy 记录 rejection:驱动的原始报文在这一行之前就已经被丢掉了。
为什么这是缺陷而不是设计
ADR-0110 D3 已经为这件事立过规矩,原话在
MetadataManager.loadDiagnosed的注释里:miss 与 outage 是两个不同的事实、安全含义相反,消费方不得把 outage 读成「作者没声明」。#5108 按这条把DatabaseLoader的复数读路径修掉了。本条是同一条规矩在单数读路径上、且在更上一层(metadata-protocol 的 overlay 读,不是 metadata 的 loader)的又一处未覆盖点 —— #5108 的标题限定语就是「在复数读路径上」,所以这里不是它的重复。对客户端的实际后果:Studio / Setup 在元数据库故障期间会把每一个对象都显示成「不存在」,而不是「后端故障」,处置方向完全相反。
可选方向(不代裁决)
catch区分「读不到」和「没有这一行」—— 只有真正的 miss 才 fall through,存储异常照实抛(或包成带status: 503/code的错误),让 REST 层的 fix(rest): a declared 5xx no longer ships its own message to the client (#5437) #5464 消毒 + 日志口接住。logger.error一行,并在最终not found上带degraded标记,让上层能分辨。getMetaItemCached的终末not found改成带code/status的结构化错误(无论如何都该做,否则它永远落mapDataError的 400 终末兜底并逐字上线内部措辞)—— 但只做 C 不解决根因,故障依然被讲成 miss。A 与 Prime Directive #12 / ADR-0110 D3 一致;C 是无论选哪条都该顺手补的卫生问题。
未验证的部分
只测了
GET /api/v1/meta/:type/:name一条路由。同一个裸catch之下还有 draft 读分支(readState === 'draft',:3150-3163)会在同样情况下抛NO_DRAFT/ 404 —— 即「存储故障」被讲成「没有草稿」,形状相同但没实测。getMetaItems(复数)在同样条件下的表现也没测。关联
#5462(同一个 harness 上发现;REST 层的启发式一侧,已由 PR #5530 收口)、#5108(
DatabaseLoader复数读,已修)、ADR-0110 D3、#5437 / #5464(REST 5xx 消毒与日志口,方向 A 的接住方)、#4754(saveMetaItem的静默 catch 家族,写侧对位)。Generated by Claude Code