Repository navigation
delete() by-id 仍允许 beforeDelete 重定向 input.id —— 与 update() 侧的 ADR-0058 Amendment II.1 不对称,需定夺是否一并退休 #6752
Description
Activity
- added a commit that references this issue
on Aug 8, 2026 Findings round: graded to the decision box —
finding→needs-user-decision, routeddomain:engine-core.- The card is a deliberate carve-out for a ruling: keep vs retire the delete()-by-id repoint capability — delivered and pinned (bulk-write-per-row-hooks.test.ts:730, still HONOURS a by-id beforeDelete REPOINT, deliberately not retired here — live @
271cee1). Route 3 (build re-resolution for update()) is already excluded by the beforeUpdate hook 在 multi:true 批量更新上拿不到 ctx.previous —— sys_fetch_previous_update 依赖 input.id;引擎已为校验取 priorRows 却不喂 hook(17.0.0-rc.2) #5574 ruling, so the open fork is 1 (keep the documented asymmetry) vs 2 (retire a shipped, pinned capability). Behavior removal of a delivered capability is a maintainer call by definition; the executed-ruling exception does not apply because PR feat(objectql): dispatch before* hooks per matched row on a predicate bulk write (#5574, #5846) #6697 explicitly deferred this exact question. - Routing: whichever way the ruling goes, the landing is
packages/objectqlbulk-write hook dispatch ⇒domain:engine-core. - Dedup: carved out of PR feat(objectql): dispatch before* hooks per matched row on a predicate bulk write (#5574, #5846) #6697 with the asymmetry recorded in the ADR and pin; no shadow found.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- The card is a deliberate carve-out for a ruling: keep vs retire the delete()-by-id repoint capability — delivered and pinned (bulk-write-per-row-hooks.test.ts:730, still HONOURS a by-id beforeDelete REPOINT, deliberately not retired here — live @
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsMaintainer ruling (2026-08-09): route 2 — retire
delete()'s by-id REPOINT, aligning withupdate().pm:queue.A
beforeDeletehook may no longer rebindctx.input.idto another record; the rebind answers withHookTargetRebindError/ERR_HOOK_TARGET_REBIND, exactly asupdate()and both per-row paths already do. Route 3 stays excluded per #5574's recorded ruling.Rationale (three-axis): measured compatibility cost is zero — the card's own repo-wide grep found no consumer of the repoint. One rule ("a by-id target is immutable in before hooks") across both verbs beats two individually-correct rules an author must memorize. And "a hook silently redirects which row gets deleted" is a top-grade footgun for AI-written hooks even though #5272's implementation is internally correct — correctness of the mechanism does not justify the surface.
Implementation notes: the four recorded landing points in the card body (ADR-0058 Amendment II.1 scope table,
hook-target-rebind-errors.ts's NOT-covered section, theengine.tsre-read block comment, and the D4 §7 pin asserting the repoint is honoured) all move together; #5272's pin is re-judged deliberately, with a changeset since this removes a shipped capability.⚠️ The ADR-0058 amendment makes this PR maintainer-merged only per standing discipline — no auto-merge, no agent queue entry.Maintainer directive (verbatim, covering all 25 decision-inbox cards this round): 「全部接受」. Recorded by PM session session_01LGRN2cSRfggfX9B2L83bQc. Veto window open — comment to overturn.
Generated by Claude Code
Claim: PM loop wave 9 (seat #6019)
Session:session_01MwoubC3jL271FYt9rGXwxb
Branch:claude/issue-6752-retire-delete-byid-repoint
Worktree:objectstack-6752
Domain:domain:engine-coreRace re-read: all comments re-read — the 2026-08-09 maintainer ruling (route 2) and the triage grading; no competing session ID.
assigneesmeasured EMPTY via REST immediately before assigning;pm:queue→pm:dispatchedsame write.A correction for the record, mine: this seat's patrol carried "#6752 awaiting ruling" all shift — the ruling has been on this card since 2026-08-09 05:40Z. The stale entry came from a shift-start prompt snapshot that was never re-verified against the card itself (lesson ㉒ cuts both ways: the do-not-touch list expires too). Dispatching now, one day late by this seat's own bookkeeping and zero days late by anyone else's.
packages/objectql/src/engine.tsis free (last owner PR #7476 merged8e177598d).
Generated by Claude Code
ACCEPT — PR #7482 verified and flipped ready. ⛔ NOT queued, ⛔ NOT auto-merged: per Prime Directive #14 this amends ADR-0058, so the merge itself is the maintainer's act.
Verified by reading the body and diff (25/26 green; the single red is
ADR maintainer approval— the gate that is the maintainer's pending signature, by design):- ✅ All four landing points moved together: the ADR-0058 scope table (the PR promotes the change to its own Amendment II.2), the
hook-target-rebind-errorsNOT-covered section, theengine.tsre-read block, and the D4 §7 pin — flipped to assertERR_HOOK_TARGET_REBINDwithpath: 'by-id', ⛔ not deleted. - ✅ The refusal is the
update()twin verbatim:input.id !== id— same-id rewrite stays legal, pinned as a negative control. CLEARED column,update()twin and per-row D4 all untouched as controls. - ✅ The compatibility grep was re-run AND corrected: the card's regex missed the
(ctx.input as any).id =cast form every pin uses — six hits where the card saw one, all six are tests of this very family, so the measured consumer count is still zero, now resting on a grep that can actually see the pins. This is the second time today a card's own enumeration was falsified by the dev re-running it (lesson ⑫ working as designed). - ✅ The honest framing is in the PR body: this removes a capability that worked — 单记录 delete 从不绑定
hookContext.previous—— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272's re-resolution was internally correct — and the body says so plainly, with the ruling's three axes as the reason it retires anyway. A reviewer will not mistake this for a bug fix. - ✅ ADR-0087 trio complete (own semantic entry file +
spec-changes.json+ upgrade guide, both--checkgates green); [PM seat] domain:spec — 🟢 os-warren · session_019G5ZnRrTMiwcHG6iZM19zf #6017 declaration filed by the seat.
@maintainer — this one is yours to merge by hand whenever you've reviewed the Amendment II.2 text. Everything else is green and will stay ready-but-unqueued until then; I keep patrolling until it lands on
origin/main.
Generated by Claude Code
- ✅ All four landing points moved together: the ADR-0058 scope table (the PR promotes the change to its own Amendment II.2), the
发现于 #5574 的引擎半边(PR #6697)。属范围外发现,已在该 PR 内刻意保持原状,仅把不对称写进 ADR 与 pin,交由本单定夺。
事实(PR #6697 落地后的 origin/main 形状)
before*处理器改写ctx.input.id时,两个动词的答复不一致:update()by-iddelete()by-id清空这一列是齐的,而且不是选择题:它原本靠"落到 predicate 分支"生效,而 ADR-0058 Addendum II 要求派发阶梯在 before 阶段之前解析(per-row 上下文是从匹配行集构造出来的),所以那条落法已经不存在。这就是裁决点名要退休的能力,
HookTargetRebindError/ERR_HOOK_TARGET_REBIND是它的拒绝面。改指这一列不齐,且是有理由的不齐。 反对照办的论据是"写会落到一条从未被读过前像、从未评估过
readonlyWhen锁、验证规则是对着别的记录跑的行上"——而delete()上这句话根本不成立:#5272 已经重新解析新目标,重读它的前像并重绑previous,afterDelete与 roll-up 汇总看到的都是真正被删的那行。update()没有这套机制,要退休就得新造一套,而那正是 #5574 裁决明令禁止的"silently pick re-resolution instead"。所以 PR #6697 的选择是:
update()拒绝,delete()维持照办,把 repoint 本身留作独立问题——不作为一次时序改动的搭车项。需要定夺的问题
delete()by-id 的 repoint 能力,是保留还是退休?三条候选路线,代价都已量过:
hookContext.previous—— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 的既有能力与其 pin 全部不动,且它是正确的——重读让语义自洽,没有任何陈旧绑定泄漏给消费者。update()对齐成一条规则。代价是删掉一个已交付、有 pin 的能力(单记录 delete 从不绑定hookContext.previous—— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 明确写了"AbeforeDeletehook may repoint the target id"),这是行为移除,需要裁决而非重构。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.ts—delete()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,该用例断言重定向后被删的是新目标且afterDelete的previous是新目标的前像。关联
#5574(引擎半边,PR #6697)/ #5272(delete 侧前像时序与 repoint 重读)/ #5846 / ADR-0058 Addendum II + Amendment II.1