Skip to content

delete() by-id 仍允许 beforeDelete 重定向 input.id —— 与 update() 侧的 ADR-0058 Amendment II.1 不对称,需定夺是否一并退休 #6752

Description

@os-zhuang

发现于 #5574 的引擎半边(PR #6697)。属范围外发现,已在该 PR 内刻意保持原状,仅把不对称写进 ADR 与 pin,交由本单定夺。

事实(PR #6697 落地后的 origin/main 形状)

before* 处理器改写 ctx.input.id 时,两个动词的答复不一致

清空 id (CLEARED) 改指到另一个 id (REBOUND)
update() by-id 拒绝 拒绝
delete() by-id 拒绝 仍然照办#5272 的重读,未改动)
二者,per-row 拒绝 (D4) 拒绝 (D4)

清空这一列是齐的,而且不是选择题:它原本靠"落到 predicate 分支"生效,而 ADR-0058 Addendum II 要求派发阶梯在 before 阶段之前解析(per-row 上下文是从匹配行集构造出来的),所以那条落法已经不存在。这就是裁决点名要退休的能力,HookTargetRebindError / ERR_HOOK_TARGET_REBIND 是它的拒绝面。

改指这一列不齐,且是有理由的不齐。 反对照办的论据是"写会落到一条从未被读过前像、从未评估过 readonlyWhen 锁、验证规则是对着别的记录跑的行上"——而 delete() 上这句话根本不成立:#5272 已经重新解析新目标,重读它的前像并重绑 previousafterDelete 与 roll-up 汇总看到的都是真正被删的那行。update() 没有这套机制,要退休就得新造一套,而那正是 #5574 裁决明令禁止的"silently pick re-resolution instead"。

所以 PR #6697 的选择是:update() 拒绝,delete() 维持照办,把 repoint 本身留作独立问题——不作为一次时序改动的搭车项。

需要定夺的问题

delete() by-id 的 repoint 能力,是保留还是退休

三条候选路线,代价都已量过:

  1. 保留(现状)。 代价是两个动词对同一个槽位答复不同,作者要记两条规则。收益是 单记录 delete 从不绑定 hookContext.previous —— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 的既有能力与其 pin 全部不动,且它是正确的——重读让语义自洽,没有任何陈旧绑定泄漏给消费者。
  2. 退休 delete 侧的 repoint。update() 对齐成一条规则。代价是删掉一个已交付、有 pin 的能力(单记录 delete 从不绑定 hookContext.previous —— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 明确写了"A beforeDelete hook may repoint the target id"),这是行为移除,需要裁决而非重构。
  3. update() 也造重解析。delete() 对齐成另一条规则。已被 beforeUpdate hook 在 multi:true 批量更新上拿不到 ctx.previous —— sys_fetch_previous_update 依赖 input.id;引擎已为校验取 priorRows 却不喂 hook(17.0.0-rc.2) #5574 裁决排除("⛔ do not silently pick re-resolution instead"),除非裁决被显式修订。

前提核查(对着 origin/main 实测)

仓内没有任何消费者依赖 delete 侧的 repoint:全仓 grep -rn "input\.id\s*=" --include=*.ts --include=*.tsx --include=*.md --include=*.mdx(排除 node_modules 与 input.id ===)只有一处命中,是 packages/objectql/src/engine.test.ts 里为触发 #2982 fail-closed 断言而清空 id 的用例,与 repoint 无关。所以路线 2 的兼容性代价为零,它纯粹是一个"要不要少一条规则"的取舍,不是"会不会弄坏谁"。

这也意味着本单不阻塞任何东西——现状是自洽且正确的,只是不够齐整。

已落地的记录点(改动时需一并移动)

  • docs/adr/0058-expression-and-predicate-surface.md — Amendment II.1 的 scope 表格与其后的两段理由;
  • packages/objectql/src/hook-target-rebind-errors.ts — "What this error does NOT cover: delete()'s by-id REPOINT" 一节;
  • packages/objectql/src/engine.tsdelete() by-id 分支里重读块上方的注释;
  • packages/objectql/src/bulk-write-per-row-hooks.test.ts §7 D4 — still HONOURS a by-id beforeDelete REPOINT — deliberately not retired here,该用例断言重定向后被删的是新目标afterDeleteprevious 是新目标的前像。

关联

#5574(引擎半边,PR #6697)/ #5272(delete 侧前像时序与 repoint 重读)/ #5846 / ADR-0058 Addendum II + Amendment II.1

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions