Repository navigation
Description
范围外发现,来自 #6262 / PR #6433 的实施过程(PD #10)。#6262 与其分诊都把范围钉在 multi 分支(「Scope as queued = route A only」、「非 multi 路径不动」),所以本条未在该 PR 内修改,只记录。
事实
#6262 收口的是 multi 分支的 SET 载荷。同一个缺陷在 by-id 分支上还留着一份,触发条件是 data.id 非标量、而 options.where.id 是标量真值 —— 也就是 ENGINE_UPDATE_DISPATCH_CASES 自己列着的那一行:
{ what: 'operator object in data.id, scalar where.id — the WHERE id wins, the operator is not one',
data: { id: { $in: ['a', 'b'] }, title: 'x' },
options: { where: { id: 'rec_1' } },
expect: 'by-id', expectId: 'rec_1' }
派发是对的(#5748 裁 A / PR #5919:算子对象不是 id,where.id 胜出,绑定 rec_1),但载荷同样没被清理。实测(PR #6433 新增测试里对现状的钉死断言,绿):
driver.update('task', 'rec_1', { id: { $in: ['a','b'] }, title: 'x' })
^^^^^^^^^^^^^^^^^^^^^^ 进 SET 子句
驱动侧确认这确实落到 SET 而非被忽略 —— packages/drivers/driver-sql/src/sql-driver.ts:3254 的 update():
const builder = this.getBuilder(object, options).where('id', id);
const formatted = this.applyWriteColumnMap(object, this.formatInput(object, data));
await builder.update(formatted);formatted 由整个 data 得出,id 不在任何跳过名单里。于是 SQL 形如 UPDATE task SET id = '{"$in":["a","b"]}', title = 'x' WHERE id = 'rec_1' —— rec_1 的主键被改写成一个序列化的算子对象。
与 #6262 的关系
同一族、不同分支,不是重复:
| 行定位 | 载荷里的 id |
状态 | |
|---|---|---|---|
| #6262 | where 谓词(AST) |
算子对象等 ⇒ 已剥离(PR #6433) | 已修 |
| 本条 | driver.update 的独立 id 参数 |
算子对象等 ⇒ 仍原样进 SET | 未修 |
PR #6433 的注释与测试对 by-id 路径的说法是「主键走独立参数,载荷里的 id 是冗余而非破坏」—— 那句话对标量 data.id 成立(SET id = 'rec_1' WHERE id = 'rec_1',同值空写),对非标量不成立,这就是本条。该 PR 已按现状把 by-id 载荷钉死,所以本条一旦修,那两个 pin 会响亮翻红,不会被悄悄改掉。
另一个更常见的入口
同样的判定阶梯下,data: { id: null, … } + 标量 where.id 也走 by-id,SET 里就带上 id = NULL:客户端 GET 一条记录、改两个字段、整体 PUT 回来,而序列化把 id 写成 null 的形状,就够了。落到 SQL 是 NOT NULL 约束报错(好的情况),或在宽松存储上留下一条主键为空的行。未实测这条端到端(REST 层是否先行剥 id 没有查),只记录形状,严重度请分诊裁。
方向(不预设结论)
- A(与
data.id是算子对象 +multi: true时,{"$in":[...]}作为普通列进入updateMany的 SET 载荷,写向主键列 #6262 同构):by-id 分支也剥 —— 但只剥「派发判定不是 id」的那一份;标量data.id保持现状(它等于绑定的主键,同值空写,且这是长期行为,动它是另一个决定)。 - B:响亮拒绝非标量
data.id(无论哪条分支)—— 与data.id是算子对象 +multi: true时,{"$in":[...]}作为普通列进入updateMany的 SET 载荷,写向主键列 #6262 的 B 案是同一个裁决,要改ENGINE_UPDATE_DISPATCH_CASES里那条expect: 'by-id',属对 ObjectQL.update 的data.id不做标量测试 —— 载荷里的算子对象被当成主键绑定,且盖过显式options.multi: true#5748 裁 A 的部分回退,需要新裁决。 - C:驱动侧各自跳过
id—— 不建议,五个后端各给一个答案,正是{ 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 那一族。
关联:#6262 / PR #6433(multi 分支那一半)、#5748 / PR #5919(data.id 的标量判定)、#5922(id 之外的标量面)、#4550 / #4434(为什么共享谓词而不是第二个答案)。
Activity
os-zhuang commentedon Aug 7, 2026
Triage —
pm:queue·domain:engine-core·target:v17Landing site (read, not guessed). The fix belongs in
packages/objectql/src/engine.ts— the by-id arm atengine.ts:5964(result = await driver.update(object, hookContext.input.id, hookContext.input.data, …)), the sibling of themultiarm that PR #6433 is editing ~50 lines below the sameif.packages/drivers/driver-sql/src/sql-driver.ts:3254is cited in the body as evidence that the payload reaches the SET clause, not as a landing site — route C (per-driver skip lists) is the #5240 / #4434 shape the issue itself argues against.packages/objectql⇒domain:engine-core. Freeze check: the 2026-08-05 investment freeze (#5499) coversdriver-memory/driver-mongodbonly and does not touch this card.Not a duplicate of #6262 — verified against the PR diff. #6262 (
pm:dispatched,domain:engine-core,target:v17) is scoped to themultiarm: PR #6433's strip sits insideelse if (options?.multi && driver.updateMany), and its newengine-update-multi-payload-id.test.tscloses with a describe block titled "#6262 — the by-id path is untouched" that pins today's behaviour (expect(call.data).toEqual({ id: { $in: ['a','b'] }, title: 'x' })). The two cards are complementary halves, and fixing this one must flip those pins — loudly, by design.Sequencing (same lane, therefore no
pm:blocked). It stays dispatchable but must land after PR #6433: same file, same function, and the pins above. Both cards aredomain:engine-core, so the lane PM sees both in its own in-flight view, which is where the ordering belongs (same-domain batch independence is the lane's own step 3).Release board.
target:v17under criterion ① (data error on a shipped surface): on a backend that accepts the write,UPDATE task SET id = '{"$in":["a","b"]}' WHERE id = 'rec_1'destroys the row's identity irreversibly, and the twin #6262 is already boarded for the same failure on the other arm. Boarding is this seat's single-producer call, not a priority claim — the maintainer drops it at release time if the RC can ship without it.Scope note for whoever takes it. Route A (strip only the payload
idthat the dispatch has already ruled is not a primary key) needs no new ruling. Route B (loudly reject a non-scalardata.idon both arms) reverses a verdictENGINE_UPDATE_DISPATCH_CASESstates today and is a partial rollback of #5748's ruling A — that is a maintainer decision, not a dev's choice; split it out asneeds-user-decisionrather than deciding it inside the PR. Thedata: { id: null }round-trip-PUT entry named in the body is explicitly untested end-to-end (whether the REST layer stripsidfirst was not read) — treat it as an unverified shape, not an established repro.Stale-premise check against
origin/main(fetched,26b72e0):engine.ts:5964still handsdriver.updatethe untouched payload;ENGINE_UPDATE_DISPATCH_CASESstill lives atpackages/metadata-core/src/engine-update-dispatch.ts:267and is re-exported frompackages/objectql/src/engine-update-dispatch.ts; PR #6433 is open, not merged. Duplicate search run across all three repos (data.id,updateManySET payload, primary-key column) — hits are #6262 (twin arm) and #6437 (DroppedFieldsEvent.reason, afindingspawned by the same PR); neither is this card.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
baozhoutao commentedon Aug 8, 2026
认领(engine-core 席 #6019,会话
session_019Q7oc7ASjh8yxyS3Yz78We,第 18 轮):claude/issue-6435-by-id-payload-id-strip;工作树:../objectstack-6435packages/objectql/src/engine.ts(仅 by-id update 臂)+engine-update-multi-payload-id.test.ts(其中 fix(objectql): multi update 的 SET 载荷剥掉非 id 的data.id(#6262) #6433 钉现状的 by-id pin 按设计翻红并改写)+ 新增用例 + changeset。⛔ 不触packages/metadata-core(ENGINE_UPDATE_DISPATCH_CASES归 metadata 席;路线 A 不改派发判定,载荷断言按 fix(objectql): multi update 的 SET 载荷剥掉非 id 的data.id(#6262) #6433 先例落 objectql 测试)。data.id(#6262) #6433(multi 臂,已 MERGED)→ fix(objectql,lint): 服务端为requiredWhen绑定 parent 作用域,并把构建期硬闸扩到同一格 (#4977) #6440 → fix(objectql): sys_file hydrate 读故障与「无文件」可分辨 (#6116) #6456 → fix(objectql): seedAutonumber 播种扫描覆盖 scope 内每一行,不再取 5000 行窗口 (#6249) #6467(00:0xZ MERGED)。当前链空,分诊要求的「must land after PR fix(objectql): multi update 的 SET 载荷剥掉非 id 的data.id(#6262) #6433」已满足。id;标量data.id现状不动)。路线 B(响亮拒收)= 对 ObjectQL.update 的data.id不做标量测试 —— 载荷里的算子对象被当成主键绑定,且盖过显式options.multi: true#5748 裁 A 的部分回退,维护者专属 —— 若实施中出现非 B 不可的证据,停、回报,拆needs-user-decision,⛔ 不得在 PR 内裁。data:{id:null}回写入口按分诊口径视为未验证形状处理。pm:queue→pm:dispatched(target:v17保留,分诊已按判据 ① 上板)。Generated by Claude Code
fix(objectql): strip a ruled-not-an-id `data.id` from the by-id updat…
feat(spec,objectql): DroppedFieldsEvent.reason names the dispatch-rul…
fix(objectql,metadata-core): refuse a by-id update whose scalar where…
fix(objectql,metadata-core): refuse a by-id update whose scalar where…
test(spec): the first data/ file group's test titles state each cited…