观察类(finding):该分支在装了 MetadataPlugin(@objectstack/metadata-protocol,它注册 protocol 槽)的部署上不会跑到 —— callData 每个操作都优先走 protocol。所以今天没有用户撞得到,记录在案交分诊定级,不自行判轻重。
事实(packages/runtime/src/action-execution.ts,#5092 阅读期间发现)
callData 每个操作都是「protocol 优先,ObjectQL 兜底」。兜底分支对同一个事实(id 指向的记录不存在)给出三种互不一致的答案:
get(action-execution.ts get 分支):find 后 match ? {...} : null → 返回 null,/data 包成 200 {data: null};
update:if (!existing) throw new Error('[ObjectStack] Not Found') —— 这个 Error 不带 .status / .statusCode,两个 dispatcher 错误出口因此都兜底到 500。一个「记录不存在」被报成内部错误;
delete:没有存在性检查,直接 ql.delete(...) 然后返回 { deleted: true } → 200,对一条从未存在过的记录也回「删掉了」。
三者本应是同一个答案(大概率 404),而 protocol 路径给的是什么、与兜底是否一致,也没有任何测试钉住 —— 两条路径对同一请求可能给不同状态码,而调用方(/data、MCP 桥、以及即将接线的声明式端点执行器)无从分辨自己走的是哪条。
为什么值得记
不在本单处置的原因
#5092 的范围是端点目标委派,action-execution.ts 不在其文件面内;且此处要决定的是「记录不存在的规范答案是什么」,应有一次明确取舍(建议:三者统一成带 .status = 404 的抛出,protocol 路径同题对齐并补测试),不适合顺手改。
观察类(
finding):该分支在装了 MetadataPlugin(@objectstack/metadata-protocol,它注册protocol槽)的部署上不会跑到 ——callData每个操作都优先走 protocol。所以今天没有用户撞得到,记录在案交分诊定级,不自行判轻重。事实(
packages/runtime/src/action-execution.ts,#5092 阅读期间发现)callData每个操作都是「protocol 优先,ObjectQL 兜底」。兜底分支对同一个事实(id 指向的记录不存在)给出三种互不一致的答案:get(action-execution.tsget 分支):find后match ? {...} : null→ 返回null,/data包成 200{data: null};update:if (!existing) throw new Error('[ObjectStack] Not Found')—— 这个 Error 不带.status/.statusCode,两个 dispatcher 错误出口因此都兜底到 500。一个「记录不存在」被报成内部错误;delete:没有存在性检查,直接ql.delete(...)然后返回{ deleted: true }→ 200,对一条从未存在过的记录也回「删掉了」。三者本应是同一个答案(大概率 404),而 protocol 路径给的是什么、与兜底是否一致,也没有任何测试钉住 —— 两条路径对同一请求可能给不同状态码,而调用方(
/data、MCP 桥、以及即将接线的声明式端点执行器)无从分辨自己走的是哪条。为什么值得记
update的 500 会把一个 4xx 事实报成 5xx,并进入错误上报/告警;delete的「假成功」是最难发现的一类:集成方按 200 认定清理完成;/data的委派形状,因此会原样继承这三种答案 —— 修在callData一处,两个面同时受益;在消费者侧各打补丁则正是 contract-first 禁止的形状。不在本单处置的原因
#5092 的范围是端点目标委派,
action-execution.ts不在其文件面内;且此处要决定的是「记录不存在的规范答案是什么」,应有一次明确取舍(建议:三者统一成带.status = 404的抛出,protocol 路径同题对齐并补测试),不适合顺手改。