范围外发现,来自 #5922(PR 见该单)的实现过程。#5922 收口的是声明值为标量的字段上的算子对象;id 不在其中,因为它的归属在另一条轴上,未在该 PR 内修改(PD #10)。
事实(worktree @ origin/main @ 70f132c15,记录型 driver 驱动真实引擎,packages/objectql)
PROBE data.id operator + multi: {"err":null,
"calls":[{"fn":"updateMany","args":[{"object":"probe_task"},{"id":{"$in":["a","b"]},"title":"x"}]}]}
update('task', { id: { $in: ['a','b'] }, title: 'x' }, { multi: true }) 今天:
record-validator.ts 的 SKIP_FIELDS 按设计跳过 id(引擎自有列),而更重要的是:这一格的裁定已经写在派发层,而不是校验层。ENGINE_UPDATE_DISPATCH_CASES(packages/metadata-core/src/engine-update-dispatch.ts)明确列了这条 case:
operator object in data.id WITH multi:true — the declared bulk intent is honoured (#5748) → expect: 'multi'
且 packages/objectql/src/engine-update-dispatch.test.ts 用真实引擎逐条驱动这个 case-set。在 record-validator 里拒收同一个调用,就是对同一个问题给出第二个答案 —— 正是 engine-update-dispatch.ts 这一族模块被抽出来防止的事(#4550 / #4434)。所以这条要么由派发层在判定 data.id 不是 id 之后顺手把它从 SET 载荷里剥掉,要么是一次明确的裁决说「带算子对象 data.id 的 multi 调用应当拒绝」—— 两者都在 #5748 / #5919 的轴上,不在字段校验的轴上。
建议方向(不预设结论)
注意 #5591(update 载荷剥离时序,同包待发)可能与 A 落在同一处代码,建议一并定价。
倾向 A。它与 #5748 已裁的语义一致(声明了 bulk intent 就照做),只是把「不是 id 的 data.id」从主键位置移走之后不再把它留在列位置。
关联:#5922(母体,id 之外的标量面已收口)、#5748 / PR #5919(裁 A,data.id 的标量判定)、#5591(同包 update 剥离时序)、#4550 / #4434(为什么共享谓词而不是第二个答案)。
范围外发现,来自 #5922(PR 见该单)的实现过程。#5922 收口的是声明值为标量的字段上的算子对象;
id不在其中,因为它的归属在另一条轴上,未在该 PR 内修改(PD #10)。事实(worktree @
origin/main@70f132c15,记录型 driver 驱动真实引擎,packages/objectql)update('task', { id: { $in: ['a','b'] }, title: 'x' }, { multi: true })今天:data.id不做标量测试 —— 载荷里的算子对象被当成主键绑定,且盖过显式options.multi: true#5748 裁 A / PR fix(metadata-core,objectql): update 的data.id同过标量测试,载荷里的算子对象不再被当成主键 (#5748) #5919 之后,非标量data.id不再被绑成主键,调用落到updateMany;{ "id": { "$in": ["a","b"] }, "title": "x" }原样作为 SET 交给驱动,即驱动被要求把主键列写成一个序列化的算子对象。命中的每一行都被改写主键。为什么 #5922 没顺手收口
record-validator.ts的SKIP_FIELDS按设计跳过id(引擎自有列),而更重要的是:这一格的裁定已经写在派发层,而不是校验层。ENGINE_UPDATE_DISPATCH_CASES(packages/metadata-core/src/engine-update-dispatch.ts)明确列了这条 case:且
packages/objectql/src/engine-update-dispatch.test.ts用真实引擎逐条驱动这个 case-set。在 record-validator 里拒收同一个调用,就是对同一个问题给出第二个答案 —— 正是engine-update-dispatch.ts这一族模块被抽出来防止的事(#4550 / #4434)。所以这条要么由派发层在判定data.id不是 id 之后顺手把它从 SET 载荷里剥掉,要么是一次明确的裁决说「带算子对象data.id的 multi 调用应当拒绝」—— 两者都在 #5748 / #5919 的轴上,不在字段校验的轴上。建议方向(不预设结论)
resolveEngineUpdateDispatch已经判出data.id不是 id,引擎在multi分支把id从 SET 载荷里去掉(它既不是主键也不是作者想写的列)。语义最小,不改任何 case 的 verdict。data.id的 multi 调用整体拒绝。诊断最好,但要改ENGINE_UPDATE_DISPATCH_CASES里那条刚落地的 case,属对 ObjectQL.update 的data.id不做标量测试 —— 载荷里的算子对象被当成主键绑定,且盖过显式options.multi: true#5748 裁 A 的部分回退,需要新裁决。{ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 / sharing: DELETE /sharing/rules/:idOrName answers 500 for both address forms — rules cannot be deleted over REST #4434 那一族。注意 #5591(update 载荷剥离时序,同包待发)可能与 A 落在同一处代码,建议一并定价。
倾向 A。它与 #5748 已裁的语义一致(声明了 bulk intent 就照做),只是把「不是 id 的
data.id」从主键位置移走之后不再把它留在列位置。关联:#5922(母体,
id之外的标量面已收口)、#5748 / PR #5919(裁 A,data.id的标量判定)、#5591(同包 update 剥离时序)、#4550 / #4434(为什么共享谓词而不是第二个答案)。