You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
结果:一次历史 upsert 把既有行的 closed_at 改成历史值,撤销后这行的 closed_at仍是导入值(未回滚)。这正是 #3493 在 undo 路径上的镜像。undo 的代码注释本身写着「an undo reinstates an established fact, it does not walk the FSM」——「reinstate established fact」正是 preserveAudit 服务的语义,却没带上。
现象
#3493 / #3497 让
treatAsHistorical导入通过preserveAudit保留原始时间线:client 提供的updated_at/updated_by被保留,审计/业务readonly字段(closed_at、resolved_by…)进白名单、不再被 strip。但导入 undo(逻辑回滚)没有对称地带上
preserveAudit,所以撤销一次历史upsert刷新时,恰恰是这个功能保留下来的那些字段无法被如实还原——被撤销的行并没有回到导入前的状态。机制(确切落点)
packages/rest/src/rest-server.ts:4052(POST /data/import/jobs/:jobId/undo):undo 通过
updateData({ data: u.before, context: writeCtx })把每行「被导入触碰的字段」还原成导入前快照u.before。但writeCtx没有preserveAudit,于是这条 restore-update 走的是普通写路径:u.before里的业务readonly字段(如closed_at)被stripReadonlyFieldsdrop(非 system + 无 preserveAudit)→ 该字段的还原被静默丢弃,行保持导入值;updated_at/updated_by重新 stamp 成撤销当下 / 撤销者,driver 再强制 stampupdated_at = now(feat(rest): preserve the original audit timeline for a historical import (#3493) #3497 的 hook/driver 只在preserveAudit下才尊重 client 值)。结果:一次历史 upsert 把既有行的
closed_at改成历史值,撤销后这行的closed_at仍是导入值(未回滚)。这正是 #3493 在 undo 路径上的镜像。undo 的代码注释本身写着「an undo reinstates an established fact, it does not walk the FSM」——「reinstate established fact」正是preserveAudit服务的语义,却没带上。影响面
窄但真实:仅影响「
treatAsHistorical+upsert刷新既有行 + 之后 undo」。created-only 行的 undo 是 delete,不受影响;非历史导入不触碰 readonly 字段,也不受影响。候选修法(需拍板)
对称补齐(最小改动):undo 的
writeCtx也带preserveAudit: true(与skipStateMachine并列),让 restore-update 如实还原u.before里的审计/readonly 字段。一行改动 + 一条测试。一个需拍板的语义点(
updated_at):「undo 本身算不算一次修改」存在分歧。updated_at一起还原(方案 1 直接达成);updated_at该记撤销时刻」→updated_at不还原可接受,但业务readonly字段(closed_at)仍应还原(否则 undo 不完整)——这部分正是preserveAudit白名单覆盖的、争议最小的部分。倾向方案 1(最小、对称、符合 undo 的「逻辑回滚」语义);
updated_at的哲学取舍留给维护者定。复现
treatAsHistorical:true+writeMode:'upsert'刷新一行既有记录,payload 带一个声明为readonly的业务字段(如closed_at:'2021-03-01T…')→ 被保留写入(feat(rest): preserve the original audit timeline for a historical import (#3493) #3497)。POST /data/import/jobs/:jobId/undo。closed_at仍是导入值,未还原成导入前的值。参考
preserveAudit— 正向导入那半)packages/rest/src/rest-server.ts:4052(undo 的 writeCtx)packages/objectql/src/plugin.ts(审计 hook 的preserveAudit分支)、packages/objectql/src/validation/rule-validator.ts(stripReadonlyFields白名单)、packages/plugins/driver-sql/src/sql-driver.ts(update stamp)