You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
engine-delete-dispatch.ts 的自我描述是「the one answer to what does ObjectQLEngine.delete do with this call」,engine-delete-dispatch.test.ts 的文件头把这条性质写成了它存在的理由:
A shared predicate that drifted from ObjectQL.delete would be worse than no predicate at all: every fake engine pinned to it would be confidently, uniformly wrong, and the gate over them would report success.
B. 引擎向判定看齐:engine.ts 的两个分支改成 dispatch.kind === 'by-id' 而不是 if (hookContext.input.id)。这会改变行为 —— where: { id: 0 } 从 reject 变成按 id 删除 —— 因此需要单独评估 hook 清空 id 的那条语义(engine.ts 里 delete 的 reject 分支注释明写「re-asked here because a beforeDelete hook may have cleared the id since」,清空用的正是真值语义)。
不论选哪条,ENGINE_DELETE_DISPATCH_CASES 都应补上 where: { id: 0 } / where: { id: '' } 两例,让逐例对照从此覆盖这一半。update 侧已在 #5480 补齐(falsy scalar where.id (0), no multi → reject + multi with a FALSY data.id),delete 侧还没有。
发现于 #5480(抽
engine-update-dispatch.ts时逐条对照 delete 侧的既有形状),PR 见该单。属 PD #10 的范围外发现,不在 #5480 内修(#5480 是 update 侧的行为保持重构,不动 delete 侧语义)。事实(origin/main @ 488b66c,已实测)
engine-delete-dispatch.ts的自我描述是「the one answer to what doesObjectQLEngine.deletedo with this call」,engine-delete-dispatch.test.ts的文件头把这条性质写成了它存在的理由:这条性质在假值标量 id 上不成立:
resolveEngineDeleteDispatch({ where: { id: 0 } })返回{ kind: 'by-id', id: 0 }——scalarDeleteId只排除null/ 数组 / 算子对象,0是合法的number;ObjectQL.delete('task', { where: { id: 0 } })抛Delete requires an ID or options.multi=true。原因在
engine.ts:判定的结果先落进const id = dispatch.kind === 'by-id' ? dispatch.id : undefined,分支却按if (hookContext.input.id)——真值测试。0/''为真值假,于是落到else if (options?.multi && driver.deleteMany),再落到 reject。实测(记录型 driver 驱动真实引擎,
packages/objectql):断言内容:
resolveEngineDeleteDispatch({where:{id:0}}).kind === 'by-id',而await engine.delete('task', {where:{id:0}})抛出的正是ENGINE_DELETE_REJECT_MESSAGE;id: ''同。为什么值得记
这正是该模块和
check:engine-double-contract门禁存在的那一类问题,只不过发生在防线内部:assertEngineDeleteDispatch(options)钉死的假引擎会接受delete(o, { where: { id: 0 } })(判定不抛),而真服务器拒绝它 —— 门禁认定为 pinned 的替身在这一输入上仍然比生产者宽松,这就是 sharing: DELETE /sharing/rules/:idOrName answers 500 for both address forms — rules cannot be deleted over REST #4434 的形状;ENGINE_DELETE_DISPATCH_CASES里没有任何假值标量 id 的用例,所以「真实引擎 == 判定」的逐例对照跑不到它 —— 检查在跑、是绿的、但结构上够不到这个输入(merge.os-regen.driver指向「上一个装过依赖的 worktree」的绝对路径 —— 该 worktree 一删,全容器的生成物合并驱动就坏了 #4868 家族)。可达性:
id: 0对自增主键少见;id: ''则是「路径段为空 / 表单字段未填」直传where.id的常见形状。两者都不是纯理论输入。建议动作
先定方向,再动代码 —— 两条路语义不同,不要猜:
resolveEngineDeleteDispatch末尾改真值测试(if (id)),假值标量落到multi/reject。这是如实描述生产者的一份判定,与 测试替身比真实实现宽松:四个缺陷因此带着绿灯发布——需要一条把替身钉在真实契约上的闸门 #4550 的立场一致(objectql 的 UPDATE dispatch 没有共享判定函数 —— delete 有resolveEngineDeleteDispatch,update 的同款三分支只是 engine.ts 里的一个内联 throw #5480 抽 update 侧时正是这么做的:ObjectQL.update的分支同样是真值测试,所以resolveEngineUpdateDispatch也写成真值测试,并在模块头写明第 3 点)。engine.ts的两个分支改成dispatch.kind === 'by-id'而不是if (hookContext.input.id)。这会改变行为 ——where: { id: 0 }从 reject 变成按 id 删除 —— 因此需要单独评估 hook 清空 id 的那条语义(engine.ts里 delete 的 reject 分支注释明写「re-asked here because a beforeDelete hook may have cleared the id since」,清空用的正是真值语义)。不论选哪条,
ENGINE_DELETE_DISPATCH_CASES都应补上where: { id: 0 }/where: { id: '' }两例,让逐例对照从此覆盖这一半。update 侧已在 #5480 补齐(falsy scalar where.id (0), no multi → reject+multi with a FALSY data.id),delete 侧还没有。关联:#4550(判定本体)、#4434(家族起源)、#5480(发现来源,update 侧的同款抽取)、#5629 / #5694(门禁的发现面)。