发现于 #5591(update 面同类缺陷)的实测过程,insert 面的对称问题。#5591 的界限明确不含 insert,故独立立单,非其 sub-issue(修法不在 #5591 的完成范围内,也不依赖它先合)。
现象
stripRuntimeOwnedFields(packages/objectql/src/validation/rule-validator.ts)与 update 面的 stripReadonlyFields 用的是同一种判据:suppliedKeys 只是键名集合。而 engine.ts insert 路径上:
triggerHooks('beforeInsert', rowCtx) 在 engine.ts:5318
rows 由 rowHookContexts 的 input.data 映射而来,在 engine.ts:5341
suppliedPerRow 快照在 engine.ts:5374
- 剥离在
engine.ts:5397-5412,delete result[name] 删的是执行到剥离时的当前值
于是 hook 若覆写了一个调用方也提交过的 runtime-owned 键,被删掉的是 hook 的写入,而不是调用方提交的值。
实测(worktree @ origin/main cca11e9d3,真 ObjectQL + InMemory 驱动)
对象 probe_num2 { title: text, code: autonumber }
beforeInsert hook: ctx.input.data.code = 'HOOK-OVERWRITE'
A) 调用方不提交 code => 落库 code = "HOOK-1" (hook 写入存活,符合文档)
B) 调用方提交 code='CALLER-FORGED' => 落库 code = "1" (hook 写入被删,序列补发)
B 与该函数自己的文档直接矛盾 —— runtimeOwnedStripWarning() 的正文写着:
A beforeInsert/beforeUpdate hook does NOT need either — hook-written keys are not caller-supplied.
A 与 B 的差别只有「调用方 payload 里是否碰巧出现同名键」,与 #5591 在 update 面记录的是同一条判据错误。
影响
窄但真实:一个在 beforeInsert 里重新签发/归一化记录号的 hook,只要调用方同时提交了该键,其写入就被静默丢弃并回落到序列值。整记录 POST(读模板 → 改字段 → 整体提交)会触发。今日无已知线上用例,故未自评严重度,交分诊定级。
期望
与 #5591 同一判据:剥离作用于调用方提交的值,不作用于 hook 覆写后的当前值。#5591 在 update 面的修法(入口快照携带调用方的值,剥离时以 Object.is 比对身份;非导出函数,签名可自由收敛)可直接照搬到 stripRuntimeOwnedFields + engine.ts 的 suppliedPerRow。
注意 insert 面有一处 update 面没有的细节需先确认:suppliedPerRow 取自 opCtx.data,而 hook 是原地改 ctx.input.data;两者是否同一引用决定了快照是否必须显式浅拷贝(update 面 #5591 已因此显式 spread)。上面的 A 用例说明 hook 新增的键当前不在快照里,所以至少新增面是干净的。
边界
Blocked-by: #5591
Generated by Claude Code
发现于 #5591(update 面同类缺陷)的实测过程,insert 面的对称问题。#5591 的界限明确不含 insert,故独立立单,非其 sub-issue(修法不在 #5591 的完成范围内,也不依赖它先合)。
现象
stripRuntimeOwnedFields(packages/objectql/src/validation/rule-validator.ts)与 update 面的stripReadonlyFields用的是同一种判据:suppliedKeys只是键名集合。而engine.tsinsert 路径上:triggerHooks('beforeInsert', rowCtx)在engine.ts:5318rows由rowHookContexts的input.data映射而来,在engine.ts:5341suppliedPerRow快照在engine.ts:5374engine.ts:5397-5412,delete result[name]删的是执行到剥离时的当前值于是 hook 若覆写了一个调用方也提交过的 runtime-owned 键,被删掉的是 hook 的写入,而不是调用方提交的值。
实测(worktree @ origin/main
cca11e9d3,真 ObjectQL + InMemory 驱动)B 与该函数自己的文档直接矛盾 ——
runtimeOwnedStripWarning()的正文写着:A 与 B 的差别只有「调用方 payload 里是否碰巧出现同名键」,与 #5591 在 update 面记录的是同一条判据错误。
影响
窄但真实:一个在 beforeInsert 里重新签发/归一化记录号的 hook,只要调用方同时提交了该键,其写入就被静默丢弃并回落到序列值。整记录 POST(读模板 → 改字段 → 整体提交)会触发。今日无已知线上用例,故未自评严重度,交分诊定级。
期望
与 #5591 同一判据:剥离作用于调用方提交的值,不作用于 hook 覆写后的当前值。#5591 在 update 面的修法(入口快照携带调用方的值,剥离时以
Object.is比对身份;非导出函数,签名可自由收敛)可直接照搬到stripRuntimeOwnedFields+engine.ts的suppliedPerRow。注意 insert 面有一处 update 面没有的细节需先确认:
suppliedPerRow取自opCtx.data,而 hook 是原地改ctx.input.data;两者是否同一引用决定了快照是否必须显式浅拷贝(update 面 #5591 已因此显式 spread)。上面的 A 用例说明 hook 新增的键当前不在快照里,所以至少新增面是干净的。边界
readonly的豁免、以及 DataProtocol ingress 剥离),stripRuntimeOwnedFields只处理 runtime-owned 一类。Blocked-by: #5591
Generated by Claude Code