做 #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 回:
400 {"error":"Metadata item object/acct not found"}
无 code,措辞是「这一项不存在」,真实情况是「元数据存储读不到」。和 #5462 是同一族的错误结论(可用性故障被讲成「你要的东西不存在」),但产出方不同、路由不同、状态码也不同:#5462 那条是启发式在 REST 层把它升格成 404,这条是产出方自己就已经把故障转写成了「不存在」,REST 层拿到的时候真相已经没了。
顺带的两个次生事实:
根因(file:line)
packages/metadata-protocol/src/protocol.ts:3149,getMetaItem 的 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 也无 code 的 Error。
复现(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 与 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
做 #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