在 #5202(把 sys_upload_session 加进 plugin-audit 的 SKIP_OBJECTS)里核对写入面时发现。不在 #5202 的 PR 里修(越界,一事一议;那单只动 packages/plugins/plugin-audit/src/)。
事实(对 origin/main @ b8503183c 核过)
packages/services/service-storage/src/metadata-store.ts 里,StorageMetadataStore 的全部 8 处引擎调用都套在同一个形状里 —— try { await this.engine.X(...) } catch { /* ignore */ },catch 块里没有任何 logger 调用,异常不外抛:
| 方法 |
行 |
调用 |
catch 注释 |
createFile() |
:83 |
engine.insert('sys_file', full) |
engine not available or schema not migrated — keep in-memory only |
getFile() |
:94 |
engine.findOne('sys_file', …) |
fall through to memory |
updateFile() |
:110 |
engine.update('sys_file', …) |
|
deleteFile() |
:122 |
engine.delete('sys_file', …) |
|
createSession() |
:146 |
engine.insert('sys_upload_session', full) |
|
getSession() |
:157 |
engine.findOne('sys_upload_session', …) |
|
updateSession() |
:178 |
engine.update('sys_upload_session', …) |
|
deleteSession() |
:190 |
engine.delete('sys_upload_session', …) |
|
每个写方法在调引擎之前都先写了进程内的 private readonly files = new Map(...) / sessions = new Map(...)(:68-69),读方法则「先引擎、失败或未命中就退回 Map」。类注释(:60-66)把这个 fallback 说成:
Backed by IDataEngine (objectql) when available — otherwise falls back to a process-local Map (suitable for tests and dev environments where the data engine isn't wired up).
问题:声明的意图与实际覆盖面不一致
注释和 catch 里的措辞说的是引擎不存在 / schema 未迁移这两种启动期状态。但 if (this.engine) 已经把「引擎不存在」挡在外面了,所以这些 catch 实际捕获的是引擎已接好之后的运行期失败 —— 约束冲突、连接抖动、RLS 拒绝、驱动错误。这类失败被:
- 不上报 —— 调用方拿到的是正常返回值(
createFile 返回 full,updateSession 返回 merged),HTTP 层照样 200;
- 不记录 —— catch 块里没有 logger,
this.engine 也没有被标记降级;
- 被内存 Map 掩盖 —— 紧随其后的
getFile() / getSession() 会从 Map 里读到那条「以为写成功了」的记录,所以同进程内自检也看不出异常。
后果按对象分两档:
为什么单独立单
我个人倾向 A:if (this.engine) 已经区分开了「没接引擎」与「接了引擎但写失败」,后者静默吞掉是把一个可诊断的错误变成一份丢失的业务真相,而 fallback 声称服务的对象(tests / dev)恰好是 this.engine 为 null 的那条分支 —— 也就是说这些 catch 对它们声称的用途并不必要。
未验证
我只做了静态阅读,没有构造一次真实的引擎写失败来观察端到端表现,也没有查是否有更上层的重试/补偿逻辑兜底。严重度请 PM 判。
Found-during: #5202 / PR #5215
在 #5202(把
sys_upload_session加进 plugin-audit 的 SKIP_OBJECTS)里核对写入面时发现。不在 #5202 的 PR 里修(越界,一事一议;那单只动packages/plugins/plugin-audit/src/)。事实(对
origin/main@b8503183c核过)packages/services/service-storage/src/metadata-store.ts里,StorageMetadataStore的全部 8 处引擎调用都套在同一个形状里 ——try { await this.engine.X(...) } catch { /* ignore */ },catch 块里没有任何logger调用,异常不外抛:createFile():83engine.insert('sys_file', full)engine not available or schema not migrated — keep in-memory onlygetFile():94engine.findOne('sys_file', …)fall through to memoryupdateFile():110engine.update('sys_file', …)deleteFile():122engine.delete('sys_file', …)createSession():146engine.insert('sys_upload_session', full)getSession():157engine.findOne('sys_upload_session', …)updateSession():178engine.update('sys_upload_session', …)deleteSession():190engine.delete('sys_upload_session', …)每个写方法在调引擎之前都先写了进程内的
private readonly files = new Map(...)/sessions = new Map(...)(:68-69),读方法则「先引擎、失败或未命中就退回 Map」。类注释(:60-66)把这个 fallback 说成:问题:声明的意图与实际覆盖面不一致
注释和 catch 里的措辞说的是引擎不存在 / schema 未迁移这两种启动期状态。但
if (this.engine)已经把「引擎不存在」挡在外面了,所以这些 catch 实际捕获的是引擎已接好之后的运行期失败 —— 约束冲突、连接抖动、RLS 拒绝、驱动错误。这类失败被:createFile返回full,updateSession返回merged),HTTP 层照样 200;this.engine也没有被标记降级;getFile()/getSession()会从 Map 里读到那条「以为写成功了」的记录,所以同进程内自检也看不出异常。后果按对象分两档:
sys_file—— plugin-audit:sys_upload_session同样声明 lifecycle.class: 'transient' 却不在 SKIP_OBJECTS —— 分块上传每传一块写一组 audit_log + activity 行 #5202 的调查刚刚确认过这张表是 mostly permanent business truth、有合规价值(这正是它被刻意排除在审计豁免之外的理由)。一次被吞掉的createFileinsert = 文件字节进了 backend,持久元数据行从来没存在过,而 API 报告成功。重启或换 worker 之后那条 Map 记录也没了,附件从此无从寻址,且没有任何一行日志指向发生过什么。sys_upload_session—— 多 worker 部署下,被吞掉的createSession让后续分块请求落到别的 worker 时查不到 session;失败表现为莫名其妙的上传中断,而不是一个可诊断的错误。为什么单独立单
sys_upload_session同样声明 lifecycle.class: 'transient' 却不在 SKIP_OBJECTS —— 分块上传每传一块写一组 audit_log + activity 行 #5202 的范围(那单只碰packages/plugins/plugin-audit/src/,且是纯豁免清单改动);logger.error+ 打降级标记;C. 按方法分档,sys_file写入抛、session 写入降级),各自对现有调用方的影响不同。请维护者/PM 定夺走哪条。我个人倾向 A:
if (this.engine)已经区分开了「没接引擎」与「接了引擎但写失败」,后者静默吞掉是把一个可诊断的错误变成一份丢失的业务真相,而 fallback 声称服务的对象(tests / dev)恰好是this.engine为null的那条分支 —— 也就是说这些 catch 对它们声称的用途并不必要。未验证
我只做了静态阅读,没有构造一次真实的引擎写失败来观察端到端表现,也没有查是否有更上层的重试/补偿逻辑兜底。严重度请 PM 判。
Found-during: #5202 / PR #5215