Skip to content

fix(rest): undo of a historical import preserves the audit timeline (#3549) - #3556

Merged
os-zhuang merged 1 commit into
mainfrom
claude/import-undo-preserveaudit
Jul 27, 2026
Merged

fix(rest): undo of a historical import preserves the audit timeline (#3549)#3556
os-zhuang merged 1 commit into
mainfrom
claude/import-undo-preserveaudit

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

背景 / 问题

关闭 #3549

treatAsHistorical(历史数据导入)在 #3493/#3497 中获得了「另一半」能力:导入的写上下文携带 preserveAudit,因此会保留原始 updated_at/updated_by 以及业务 readonly 字段(closed_atresolved_by 等),而不是「盖上当前时间 / 剥离」。

撤销(undo)路径被遗漏了。撤销一个已完成的导入作业时(POST /data/import/jobs/:jobId/undo),它会用捕获的导入前快照 u.before 还原被更新的行——却使用了一个普通写上下文:

const writeCtx = { ...(context ?? {}), skipAutomations: true, skipStateMachine: true };

于是审计自动戳(audit auto-stamp)会把 updated_at/updated_by 重写为「现在」,悄悄破坏了历史导入刻意保留的那条审计时间线。换句话说:导入保留了原始时间戳,撤销却把它抹掉了——撤销本应是「还原」,结果反而改写了事实。

改动

撤销的写上下文现在镜像导入自身的写上下文:当作业行标记了 treat_as_historical 时,携带 preserveAudit,从而把快照里的审计/时间戳值(以及任何业务 readonly 字段)原样写回。

const writeCtx = {
    ...(context ?? {}),
    skipAutomations: true,
    skipStateMachine: true,
    ...(row.treat_as_historical ? { preserveAudit: true } : {}),
};
  • row.treat_as_historical 已经持久化在导入作业行上(rest-server.ts 创建作业时写入 treat_as_historical: prepared.treatAsHistorical),因此撤销时无需额外查询即可判定。
  • 普通导入的撤销行为完全不变(默认 stamp/strip)——preserveAudit 仅在历史导入时按条件加入。
  • 条件式合并对齐了 import-runner.ts 里导入自身写上下文的写法(treatAsHistorical ? { skipStateMachine: true, preserveAudit: true } : {})。

为什么这是「保留」而非「特权」

  • captureBefore 只快照导入负载里出现过的键,所以撤销写回的就是导入前的确切值;带上 preserveAudit 只是让这些值被原样写回,而不是被审计钩子覆盖。
  • 审计溯源不受影响:撤销本身仍会记录(sys_audit_log + 作业行上的 reverted_at),所以「谁在何时撤销」这条事实依然可查——被保留的只是被还原行的业务时间线。

测试

packages/rest/src/import-job-integration.test.ts 新增两条回归测试(真实 ObjectQL 引擎 + 内存驱动 + RestServer,全链路走真实的 create-job / worker / undo 路由):

  1. 历史导入的撤销:断言还原写(updateData)的上下文携带 preserveAudit: true
  2. 普通导入的撤销:断言还原写的上下文不含 preserveAudit(默认 stamp/strip)。

为让 treat_as_historical 标志在作业行上往返,测试的 sys_import_job 镜像补充了该字段。

Test Files  3 passed (3)   # import-integration + import-runner-historical + import-job-integration
     Tests  42 passed (42)

import-job-integration.test.ts 单独运行:13 passed,含 2 条新增。)

影响面

  • 仅改动撤销路由的写上下文构造 + 测试;无接口/schema 变更。
  • Changeset:@objectstack/rest patch(行为修复)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01B5rdfBKjkbcoEif4KUV6xE


Generated by Claude Code

…3549)

A `treatAsHistorical` import writes with `preserveAudit` (#3493), keeping the
original `updated_at`/`updated_by` and business `readonly` fields instead of
stamping-now / stripping them. Its undo route restored the captured pre-import
snapshot with a plain write context, so the audit auto-stamp re-wrote
`updated_at`/`updated_by` to "now" — silently corrupting the very timeline the
historical import had preserved.

The undo write context now mirrors the import's own: it carries `preserveAudit`
iff the job row is flagged `treat_as_historical`, so restoring `u.before`
re-writes the snapshotted audit/timestamp values verbatim. A normal import's
undo is unchanged (default stamp/strip).

Adds two regression tests in import-job-integration.test.ts asserting the undo
write context carries preserveAudit iff the import was historical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5rdfBKjkbcoEif4KUV6xE
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 4:30am

Request Review

@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)
  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 04:41
@os-zhuang
os-zhuang merged commit ef5e72d into main Jul 27, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/import-undo-preserveaudit branch July 27, 2026 04:41
os-zhuang added a commit that referenced this pull request Jul 27, 2026
…am (#3493 follow-ups) (#3560)

Follow-ups to #3493 / #3549 / #3556 — no behavior change.

Real-SQLite end-to-end test (@objectstack/runtime): wires the real ObjectQL
engine to the real SqlDriver (better-sqlite3) and proves the preserveAudit seam
that mock / in-memory drivers structurally cannot — that context.preserveAudit
threads through buildDriverOptions and defeats the SQL driver's updated_at
force-stamp on both the forward historical write and the undo restore, and that
a business readonly field (closed_at) survives the engine strip all the way to
disk. Includes the #3549 undo capstone: restore-under-preserveAudit rolls the
timeline back, while a plain restore re-stamps now (the bug #3556 fixed).

Docs: the treatAsHistorical schema describe (@objectstack/spec, regenerated
references/api/export.mdx) now documents all three effects — FSM skip (#3479),
audit-timeline preservation (#3493) and undo mirroring (#3556) — instead of only
the state-machine half; protocol/objectql/state-machine.mdx gains a bullet on
the symmetric undo behavior.

Also confirms (via a codebase audit) that the import-undo route was the only
business-record snapshot write-back path, so #3556 needs no sibling fix.


Claude-Session: https://claude.ai/code/session_01B5rdfBKjkbcoEif4KUV6xE

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants