Repository navigation
update 的 **by-id** 路径同样把非标量 data.id 交给驱动写主键列(#6262 的孪生形状,where.id 胜出时) #6435
Description
Activity
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
认领(engine-core 席 #6019,会话
session_019Q7oc7ASjh8yxyS3Yz78We,第 18 轮):- 分支:
claude/issue-6435-by-id-payload-id-strip;工作树:../objectstack-6435 - 文件面:
packages/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 测试)。 - engine.ts 串行链前驱:fix(objectql): multi update 的 SET 载荷剥掉非 id 的
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」已满足。 - 执行口径:仅路线 A(剥「派发已判定不是 id」的那份载荷
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
- 分支:
- added a commit that references this issue
on Aug 8, 2026 - added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 23, 2026 - added a commit that references this issue
on Aug 23, 2026 - added a commit that references this issue
on Oct 7, 2026
范围外发现,来自 #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自己列着的那一行:派发是对的(#5748 裁 A / PR #5919:算子对象不是 id,
where.id胜出,绑定rec_1),但载荷同样没被清理。实测(PR #6433 新增测试里对现状的钉死断言,绿):驱动侧确认这确实落到 SET 而非被忽略 ——
packages/drivers/driver-sql/src/sql-driver.ts:3254的update():formatted由整个data得出,id不在任何跳过名单里。于是 SQL 形如UPDATE task SET id = '{"$in":["a","b"]}', title = 'x' WHERE id = 'rec_1'—— rec_1 的主键被改写成一个序列化的算子对象。与 #6262 的关系
同一族、不同分支,不是重复:
idwhere谓词(AST)driver.update的独立 id 参数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没有查),只记录形状,严重度请分诊裁。方向(不预设结论)
data.id是算子对象 +multi: true时,{"$in":[...]}作为普通列进入updateMany的 SET 载荷,写向主键列 #6262 同构):by-id 分支也剥 —— 但只剥「派发判定不是 id」的那一份;标量data.id保持现状(它等于绑定的主键,同值空写,且这是长期行为,动它是另一个决定)。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 的部分回退,需要新裁决。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(为什么共享谓词而不是第二个答案)。