Skip to content

fix(plugin-audit): exclude computed fields from update diffs + treat undefined/null as equal#3293

Merged
baozhoutao merged 1 commit into
mainfrom
fix/audit-writer-computed-field-diff
Jul 19, 2026
Merged

fix(plugin-audit): exclude computed fields from update diffs + treat undefined/null as equal#3293
baozhoutao merged 1 commit into
mainfrom
fix/audit-writer-computed-field-diff

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

问题

objectui 记录历史 Tab 实测(甘特 QA 项目)暴露出审计写入器 diff() 的两个数据层 bug:

  1. 计算字段幻影 diff:beforeUpdate 快照经查询路径读回,带 formula/summary/rollup/autonumber 的计算值;ctx.result 是裸写入结果,不带。于是每次更新都为每个计算字段写入一条 值 → null 的假变更(前端表现为 紧前依赖(渲染用): [id] → — 逢改必现)。
  2. undefined↔null 噪声行:safeStringify(undefined)JSON.stringify 契约返回 undefined(非字符串),与 'null' 比较必不等,导致"字段缺失"对"显式 null"被记成 old=new=null 的废行。

修复

  • diff() 接受字段定义,跳过 formula / summary / rollup / autonumber 类型(派生值的变化由源字段的 diff 隐含);仅计算字段变化时不再写审计/活动行
  • 比较前对两侧做 ?? null 归一化;真实的"值 → null"清空仍正常记录(守护用例覆盖)

测试

  • 新增 4 条回归(plugin-audit 42/42 全绿);红绿闭环:暂存回旧实现后 3 条如预期失败,第 4 条为防过修守护
  • 真栈实弹:本分支全量构建(71/71)起 showcase 后端,更新 showcase_project.health(该记录 task_count=5/total_estimate=100 均在 before 快照内)→ 审计行为干净的 {"health":"green"} → {"health":"yellow"},零幻影字段
  • 合栈 UI 验证:配合 objectui#2691 的 Playwright live 测试在本分支后端上通过

配套展示层修复:objectstack-ai/objectui#2691(老数据幻影行由前端过滤兜底)。

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 19, 2026 3:41pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-audit.

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

  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-audit)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-audit)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-audit)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-audit)

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.

@baozhoutao
baozhoutao merged commit 5c31684 into main Jul 19, 2026
16 checks passed
@baozhoutao
baozhoutao deleted the fix/audit-writer-computed-field-diff branch July 19, 2026 15:52
…undefined/null as equal

Two audit-writer diff bugs surfaced by the objectui record History tab
(gantt QA report showed "紧前依赖(渲染用): [id] → —" on every drag):

1. The beforeUpdate snapshot is read back through the query path, which
   computes formula/summary/rollup/autonumber fields; ctx.result is the raw
   write result, which does not. diff() therefore recorded a phantom
   "value → null" change for every computed field on every update. Computed
   fields are now excluded from the diff via the engine schema (their
   changes are implied by their source fields); an update touching only
   computed fields no longer writes an audit/activity row at all.

2. safeStringify(undefined) returns undefined (JSON.stringify contract),
   not a string, so a key absent on one side compared unequal to an
   explicit null on the other and wrote a noise row with old=new=null.
   Values are normalized with `?? null` before comparison; a real
   value → null transition is still recorded (covered by a guard test).

Verified against a live showcase stack: updating showcase_project.health
now writes old_value {"health":"green"} / new_value {"health":"yellow"}
with no task_count/total_estimate phantoms, and the three new regression
tests fail against the pre-fix writer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 22, 2026
The v16 sweep (#3358 §4 "Record History tab") couldn't exercise the tab because
no showcase object opted into it: the console gates the History tab on
object-level `enable.trackHistory` (RecordDetailView) — audit *capture* is
always on, but the *tab* is opt-in — and the showcase only set `trackHistory`
at the FIELD level (industry/status), which just selects which diffs are
summarized. So the tab never rendered anywhere and #2691/#3293 (display-value
diffs, computed-field exclusion) were undemonstrated.

Set `enable: { trackHistory: true }` on Account (which already declares
field-level `trackHistory` on `industry`/`status` for clean diffs).

Verified in the running app: the Account detail now shows a **历史 / History**
tab; after editing `industry`, it renders "Dev Admin · UPDATE · 行业: Retail →
Technology" — field label + select **display values** (not raw stored codes/ids),
no phantom value→null rows. `os validate` + `tsc --noEmit` pass.

Follow-up to #3364 / #3393, from the #3358 sweep §4.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant