fix(metadata-core): delete 判定末尾改真值测试,假值标量 where.id 不再答 by-id (#5747) - #5999
fix(metadata-core): delete 判定末尾改真值测试,假值标量 where.id 不再答 by-id (#5747)#5999baozhoutao wants to merge 2 commits into
Conversation
…patch (#5747) `resolveEngineDeleteDispatch` answered `by-id` for a falsy scalar `where.id` (`0`, `''`) while `ObjectQL.delete` — which branches on `if (hookContext.input.id)` — routes the same call to multi/reject. Measured against the real engine with a recording driver: {where:{id:0}} engine=reject predicate=by-id {where:{id:''}} engine=reject predicate=by-id {where:{id:0},multi:true} engine=multi predicate=by-id {where:{id:''},multi:true} engine=multi predicate=by-id So every fake engine pinned to `assertEngineDeleteDispatch` ACCEPTED `delete(o, { where: { id: '' } })` while a running server throws — the #4434 shape this module exists to remove, on the one input the shared case-set could not reach (#4868 family). This changes the PREDICATE, never the engine: the predicate is a description of `ObjectQL.delete` and the description was wrong. `engine.ts` is untouched and `delete(o, {where:{id:0}})` throws before and after. Option B (make `{id:0}` a real by-id delete) would have changed producer behaviour and is deliberately not taken. `ENGINE_DELETE_DISPATCH_CASES` gains the four falsy shapes, so the real-engine parity run now reaches them. `scalarDeleteId` stays value-faithful — it answers "is that VALUE a scalar", the truthiness test belongs to the dispatch — matching how `scalarUpdateId` and the update twin already split it (#5748). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
…ete-falsy-scalar-id
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31111166282 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
队列管家拦截:⛔ 不原样重投 —— 台账命中 objectstack 表第 2 行「已修签名」(作用域外的新落点)本 PR 于 14:30Z 被踢出合并队列。红与本 PR 的 diff 无关(你改的是 完整签名(取完整日志归档,非 tail —— SKILL note 7):
note 7 的两个陷阱都现身、都已绕过:同 job 内 判读:这是 #4796 家族那条 5000ms 超时签名的作用域外新落点,不是「修好的东西又坏了」,也不是本 PR 的回归。#4856 的 这是同一签名在 40 分钟内踢掉的第二个不相干 PR:13:52Z 踢 #5973(diff 只碰 让行判据:处置前读本 PR 最近 30 分钟评论,仅有 ⛔ 本条为止:未重投、未入队/撤队、未重跑、未改代码、未动认领。 Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31117284898 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31120011988 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #5747
前提复核(实测,不是照抄 issue)
issue 正文的落点是旧世界(
packages/objectql),判定与 CASES 已随 PR #5871 迁到packages/metadata-core/src/engine-delete-dispatch.ts。在新落点上用记录型 driver 驱动真实引擎逐条实测,前提成立:ObjectQL.deletescalarDeleteId{ where: { id: 0 } }rejectby-id0{ where: { id: '' } }rejectby-id''{ where: { id: 0 }, multi: true }multiby-id0{ where: { id: '' }, multi: true }multiby-id''{ where: { id: 'rec_1' } }(对照)by-idby-id'rec_1'两侧问的不是同一个问题:判定读
scalarDeleteId(...) !== undefined,而engine.ts把判定结果落进id后按if (hookContext.input.id)分支 —— 真值测试,0/''落到 multi/reject 阶梯。后果正是本模块存在的理由(#4434 形状):按
assertEngineDeleteDispatch(options)钉死的替身接受delete(o, { where: { id: '' } }),而真服务器抛Delete requires an ID or options.multi=true。pinned 替身在这一个输入上仍比生产者宽松。id: ''(路径段为空 / 表单字段未填直传where.id)是可达形状。改了什么
按分诊裁定实施 A,一行行为改动:
改的是判定,不是引擎。
resolveEngineDeleteDispatch是对ObjectQL.delete的描述,错的是描述:delete(o, { where: { id: 0 } })改动前抛错,改动后照样抛错,生产者行为零变化,engine.ts一字未动。B 方案(让{ id: 0 }变成真的按 id 删)是改生产者行为,明确不取,并在模块头写明为什么。其余为文档与用例:
!== undefined),与 update 侧孪生模块的第 3 点同形,便于对读;ENGINE_DELETE_DISPATCH_CASES补四例({id:0}/{id:''}× 有/无multi)—— 此前这套「真实引擎 == 判定」的逐例对照结构上够不到这个输入(merge.os-regen.driver指向「上一个装过依赖的 worktree」的绝对路径 —— 该 worktree 一删,全容器的生成物合并驱动就坏了 #4868 家族:一次逐例跑不可能反驳一个没人列出来的输入),这才是判定能安静漂移的原因;scalarDeleteId保持值忠实({ where: { id: 0 } }仍返回0),真值测试只加在判定这一层。与 update 侧scalarUpdateId的分法一致:提取器回答「那个值是不是标量」,判定回答「这个调用是什么」。把提取器收窄成「真值标量」会让它和自己的名字打架。反向验证(方向先预测,后运行)
把
if (id)改回if (id !== undefined)并重建 —— 四行预测,四行全中:predicate断言上AssertionError: predicate: expected 'by-id' to be 'reject'/to be 'multi'branches on TRUTHINESSexpected 'by-id' to be 'reject'a fake pinned to assertEngineDeleteDispatch…expected [Function] to throw an errorfalsy-id multi 仍被 where 限定第四行是关键的一行:它证明那条测试钉的是引擎而不是判定 ——
{ id: 0 }走 multi 是engine.ts自己的if (input.id)决定的,与判定返回什么无关。所以它在回退下不该变红,也确实没有。第三行是本单的靶心:回退后
assertEngineDeleteDispatch({ where: { id: '' } })不抛,这就是替身比生产者宽松的那一格。一个如实交代的次生差异
engine.ts的const id = dispatch.kind === 'by-id' ? dispatch.id : undefined意味着hookContext.input.id由判定派生。收紧判定后,{ where: { id: 0 } }的input.id从0变为undefined。逐处核对了它的下游,没有一处改变行为:if (!id)(AST 播种)、if (id && wantsPreImage)、if (hookContext.input.id)三处对0与undefined同答;唯一的??(hookContext.input.id ?? resultId)只在isPredicateWrite === false时可达,而那蕴含input.id为真,故对假值 id 结构上不可达。剩下的是beforeDelete钩子看到的input.id:引擎本就拒绝把它当 id,现在钩子看到的和引擎的判断一致了 —— 这是消除不自洽,不是引入行为。测试
packages/metadata-core:103/103;packages/objectql:2138/2138(新增 4 条 CASES + 3 条命名测试)metadata-protocol的 13 个接线 fake(49 文件 / 486 测试全绿)—— PM 要求的自证:全仓 grep 假值标量 id 字面量,除 update 侧自己的用例外无任何 fixture 驱动这个形状,故新语义对它们无命中pnpm typecheck(metadata-core + objectql):Donenode scripts/check-engine-double-contract.mjs:OK — 72 pinned, 133 in the DEBT ledger, 2 exempt.node scripts/check-nul-bytes.mjs:OKgit merge origin/main(⛔ 未 rebase)并全量重建 + 重跑,全绿changeset
@objectstack/metadata-corepatch —— 写明「修的是判定对生产者的忠实度,生产者行为零变化」。Generated by Claude Code