Skip to content

getMetaItem 的 overlay 读用裸 catch 把「sys_metadata 不可达」吞成「该项不存在」—— GET /meta/:type/:name 在存储故障时回一个无 code 的 400「not found」 #5532

Description

@baozhoutao

#5462mapDataError 的 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 回:

400 {"error":"Metadata item object/acct not found"}

code,措辞是「这一项不存在」,真实情况是「元数据存储读不到」。和 #5462 是同一族的错误结论(可用性故障被讲成「你要的东西不存在」),但产出方不同、路由不同、状态码也不同#5462 那条是启发式在 REST 层把它升格成 404,这条是产出方自己就已经把故障转写成了「不存在」,REST 层拿到的时候真相已经没了。

顺带的两个次生事实:

根因(file:line)

packages/metadata-protocol/src/protocol.ts:3149getMetaItem 的 customization-overlay 读:

} catch {
    // DB not available — fall through to registry / MetadataService
}

catch,无日志。注释自己就写着 "DB not available",然后照 miss 处理。驱动抛的 SQLITE_ERROR: no such table: sys_metadata 到此为止,item 保持 undefined,一路穿过 registry / MetadataService 兜底,最后由 getMetaItemCached(同文件 :5413)转成:

throw new Error(`Metadata item ${request.type}/${request.name} not found`);

—— 一个既无 status 也无 codeError

复现(in-process,已实跑)

真实 ObjectQL + 真实 ObjectStackProtocolImplementation,驱动每个方法都抛 SQLITE_ERROR: no such table: sys_metadata(即 #5464 / #5462 用的那套 harness)。在协议对象上挂一层 Proxy 记录 rejection:

ASYNC-THROW getMetaItemCached status=undefined code=undefined name=Error
            msg=Metadata item object/acct not found
GET item 400 {"error":"Metadata item object/acct not found"} | logs 1

驱动的原始报文在这一行之前就已经被丢掉了。

为什么这是缺陷而不是设计

ADR-0110 D3 已经为这件事立过规矩,原话在 MetadataManager.loadDiagnosed 的注释里:miss 与 outage 是两个不同的事实、安全含义相反,消费方不得把 outage 读成「作者没声明」。#5108 按这条把 DatabaseLoader复数读路径修掉了。本条是同一条规矩在单数读路径上、且在更上一层(metadata-protocol 的 overlay 读,不是 metadata 的 loader)的又一处未覆盖点 —— #5108 的标题限定语就是「在复数读路径上」,所以这里不是它的重复。

对客户端的实际后果:Studio / Setup 在元数据库故障期间会把每一个对象都显示成「不存在」,而不是「后端故障」,处置方向完全相反。

可选方向(不代裁决)

  • A:这层 catch 区分「读不到」和「没有这一行」—— 只有真正的 miss 才 fall through,存储异常照实抛(或包成带 status: 503 / code 的错误),让 REST 层的 fix(rest): a declared 5xx no longer ships its own message to the client (#5437) #5464 消毒 + 日志口接住。
  • B:保留 fall-through 但至少 logger.error 一行,并在最终 not found 上带 degraded 标记,让上层能分辨。
  • C:把 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 收口)、#5108DatabaseLoader 复数读,已修)、ADR-0110 D3、#5437 / #5464(REST 5xx 消毒与日志口,方向 A 的接住方)、#4754saveMetaItem 的静默 catch 家族,写侧对位)。


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions