Skip to content

engine-delete-dispatch 的共享判定与 ObjectQL.delete 在「假值标量 id」上不一致 —— where: { id: 0 } 判定答 by-id,引擎却 reject #5747

Description

@os-zhuang

发现于 #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 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.

这条性质在假值标量 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):

✓ FINDING A: delete predicate says by-id for where.id=0 but the engine REJECTS it
✓ FINDING A2: same for where.id=""
Tests  2 passed

断言内容:resolveEngineDeleteDispatch({where:{id:0}}).kind === 'by-id',而 await engine.delete('task', {where:{id:0}}) 抛出的正是 ENGINE_DELETE_REJECT_MESSAGE;id: '' 同。

为什么值得记

这正是该模块和 check:engine-double-contract 门禁存在的那一类问题,只不过发生在防线内部:

  1. 一个按 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 的形状;
  2. ENGINE_DELETE_DISPATCH_CASES 里没有任何假值标量 id 的用例,所以「真实引擎 == 判定」的逐例对照跑不到它 —— 检查在跑、是绿的、但结构上够不到这个输入(merge.os-regen.driver 指向「上一个装过依赖的 worktree」的绝对路径 —— 该 worktree 一删,全容器的生成物合并驱动就坏了 #4868 家族)。

可达性:id: 0 对自增主键少见;id: '' 则是「路径段为空 / 表单字段未填」直传 where.id 的常见形状。两者都不是纯理论输入。

建议动作

先定方向,再动代码 —— 两条路语义不同,不要猜:

不论选哪条,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(门禁的发现面)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions