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(门禁的发现面)。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions