Skip to content

fix(plugin-audit): exclude sys_upload_session from audit/activity writes (#5202) - #5215

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5202-audit-skip-upload-session
Aug 4, 2026
Merged

fix(plugin-audit): exclude sys_upload_session from audit/activity writes (#5202)#5215
os-zhuang merged 1 commit into
mainfrom
claude/issue-5202-audit-skip-upload-session

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5202

#5193 / PR #5201 完全同形:一行清单 + 注释,不做任何横切改造。

改了什么

packages/plugins/plugin-audit/src/audit-writers.tsSKIP_OBJECTS 第 (2) 组
(ADR-0057 决策 5 “stop the amplifier”)加入 sys_upload_session,注释写明理由:

为什么是放大器

审计钩子对全部对象注册,且没有「系统上下文写入不审计」的豁免,所以
StorageMetadataStore 自己的写入被当成用户编辑记了下来。一次 N 块的分块上传:

写入 次数
createSession()engine.insert 1
updateSession()engine.update(每块一次) N
终态 updateSession()(completed / failed / expired) 1
deleteSession() 或 ADR-0057 的 TTL / retention reaper 1

每次写入同时落一行 sys_audit_log 一行 sys_activity,即 2 × (1 + N) 行,
外加每次 beforeUpdatecaptureBefore 快照读。而且每行还特别肥:updateSession()
写的是 merged 全量记录,所以那个随块数增长的 parts JSON blob 会挤进每一次 diff 的
old_value / new_value

⛔ 没有动 sys_file

sys_file 也声明了 transient,但那只为收墓碑行与未完成上传;其行是 mostly permanent
business truth,审计有合规价值 —— #5201 的 dev 已查过并刻意不豁免,本 PR 沿用该判断,
加了一个测试把这个刻意的不豁免钉住,防止后来者“顺手补全”把 sys_file 也塞进去。

同样按 issue 要求未做SKIP_OBJECTSlifecycle.class 机械关联」的横切改造
(独立决策),也未碰 packages/spec / content/docs/releases/

测试

新增 describe 块 audit writers — chunked upload sessions are excluded (#5202, ADR-0057 D5),
5 个用例:

  1. 完整生命周期归零 —— create → 8 × chunk update → complete → delete,共 11 次写入,
    断言 created[](不是“少一点”,是零);fixture 里 parts真的按块增长的,
    因为那些 diff 载荷的体积正是本单的一半问题。
  2. 中途放弃 + reaper 收行 —— 3 块后 expired,再由 TTL reaper DELETE,同样归零。
  3. 不再为每块重读增长中的行 —— 4 次 beforeUpdate + 1 次 beforeDelete 零快照读,
    同一 harness 上的业务对象对照组证明该断言失败。
  4. sys_file 仍然被审计(刻意不豁免的钉子)。
  5. 普通业务写入仍然被审计(豁免面保持窄)。

假引擎沿用文件里既有的 makeEngine,未定义 delete(),所以不涉及 #5197
assertEngineDeleteDispatch 路由要求。

突变检查(删掉清单那一行 → 测试红)

× writes NO audit/activity row for a completed 8-chunk upload (create → 8 × update → complete → delete)
× writes NO audit/activity row when the session is aborted and reaped instead
× does not re-read the growing session row before every chunk update

AssertionError: expected [ …(22) ] to deeply equal []
AssertionError: expected [ …(12) ] to deeply equal []
AssertionError: expected [ 'sys_upload_session', …(4) ] to deeply equal []

Tests  3 failed | 105 passed (108)

22 = 2 × (1 + 8 + 1 + 1),12 = 2 × (1 + 3 + 1 + 1),5 = 4 次 chunk 快照读 + 1 次删除前快照读
—— 与 issue 正文推算的放大倍数逐位吻合。第 4、5 两个对照用例在突变下仍绿(它们本就该绿)。

恢复后

pnpm --filter @objectstack/plugin-audit typecheck   → tsc --noEmit, exit 0
pnpm --filter @objectstack/plugin-audit test        → Test Files 7 passed (7)
                                                       Tests 108 passed (108)

Changeset

.changeset/audit-skip-sys-upload-session.md,patch(审计台账内容对运维可见),
#5201 的写法,含运维迁移提示:原先靠 sys_activity 看上传活动的,应改读
sys_upload_session(进行中状态)与 sys_file(实际存下什么的持久记录)。

🤖 Generated with Claude Code

https://claude.ai/code/session_017MCKJaEomEqg4tvz4SzdNd


Generated by Claude Code

…tes (#5202)

SKIP_OBJECTS group (2) — ADR-0057 decision 5 "stop the amplifier" — gained
sys_job_queue in #5193/#5201; sys_upload_session is the same gap one table over.
It declares lifecycle.class: 'transient' and its own object comment settles what
the rows are worth: "an upload session is ephemeral state, never business truth"
(ADR-0057 / #2970 item 4). Nothing connected that declaration to the exemption
list, which is hand-written.

The audit writers register for all objects and there is no system-context
exemption, so StorageMetadataStore's own writes were mirrored into sys_audit_log
AND sys_activity. A chunked upload of N parts costs 1 + N writes — the
createSession() insert plus one updateSession() per chunk — then a terminal
status update and the row's removal (deleteSession, or the TTL/retention reaper),
so 2 × (1 + N) ledger rows for one file, each with its own beforeUpdate snapshot
read. Each row was unusually fat too: updateSession() writes the merged FULL
record, so the `parts` JSON blob that grows with every chunk rode along in every
diff's old_value/new_value.

sys_file stays audited on purpose: it declares transient as well, but only to
reap tombstones and unfinished uploads — its rows are mostly permanent business
truth with real compliance value.

Tests pin a completed 8-chunk lifecycle and an aborted-then-reaped one producing
zero rows, the skipped snapshot read (with a business-object control), the
deliberate non-exemption of sys_file, and that ordinary writes are still audited.
Removing the one list entry turns the first three red (22, 12 and 5 unwanted
writes respectively).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MCKJaEomEqg4tvz4SzdNd
@vercel

vercel Bot commented Aug 4, 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 Aug 4, 2026 10:21am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m and removed documentation Improvements or additions to documentation tests tooling labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

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/cli.mdx (via @objectstack/plugin-audit)
  • content/docs/deployment/production-readiness.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants