Repository navigation
Description
做 #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 层拿到的时候真相已经没了。
顺带的两个次生事实:
- 状态码是 400 而不是 404 —— 它落在
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 — 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:这层
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 收口)、#5108(DatabaseLoader 复数读,已修)、ADR-0110 D3、#5437 / #5464(REST 5xx 消毒与日志口,方向 A 的接住方)、#4754(saveMetaItem 的静默 catch 家族,写侧对位)。
Generated by Claude Code
Activity
os-zhuang commentedon Aug 5, 2026
分诊:
pm:queue+domain:engine。packages/metadata-protocol/src/protocol.ts:3149的裸 catch(getMetaItem 的 overlay 读)与:5413的无 code「not found」;metadata*家族按域表归domain:engine。/meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489(handleRouteError 兜底,finding)、DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108(DatabaseLoader 复数读,已修)同族不同位,正文的区分论证核验成立,无在飞 PR 覆盖。DatabaseLoader把存储读故障吞成空结果 —— ADR-0110 D3 的 miss/outage 之分在复数读路径上不成立 #5108 先例锚定,C(终末错误结构化)为顺手卫生,无需维护者另行拍板;draft 分支与 getMetaItems 的同型未验证点宜在实现时一并核。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
认领:PM 循环第 2 轮主批(engine-core 车道)
会话:
session_01V7WetGmnfoXNn8cLieKKmx分支:
claude/issue-5532-getmeta-outage-vs-missWorktree:
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
os-zhuang commentedon Aug 6, 2026
复核通过,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
fix(metadata-protocol): an unreadable sys_metadata is an outage, not …
fix(metadata-protocol): propagate the metadata-store 503 out of getMe…
fix(rest): stop absorbing a failed registry read into a 200 on GET /a…
fix(rest): GET /meta/:type/:name answers absence in one envelope, whi…