Skip to content

fix(plugin-audit): 把 sys_job_queue 加进 SKIP_OBJECTS 的运维管道豁免组 (#5193) - #5201

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5193-audit-skip-job-queue
Aug 4, 2026
Merged

fix(plugin-audit): 把 sys_job_queue 加进 SKIP_OBJECTS 的运维管道豁免组 (#5193)#5201
os-zhuang merged 1 commit into
mainfrom
claude/issue-5193-audit-skip-job-queue

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #5193

问题

audit-writers.tsSKIP_OBJECTS 第 (2) 组是 ADR-0057 决策 5「stop the amplifier」在写入器这一层的落点,组里已经有 sys_job / sys_job_run / sys_automation_run,唯独漏了同族里量最大的 sys_job_queue

审计钩子(afterInsert/afterUpdate/afterDelete)是全对象注册的,并且没有任何「系统上下文写入不记审计」的豁免 —— 把关的只有 SKIP_OBJECTS。所以 DbQueueAdapter 自己用 SYSTEM_CTX 写的每一行都照记不误:

sys_job_queue 是纯平台内部管道(managedBy = engine-owned、enable.apiMethods = get/list、#5179 起 lifecycle.class = transient),用户根本写不了它,这些行没有任何合规价值,只是噪声与写放大。

修法

与兄弟表完全同形:把 sys_job_queue 加进第 (2) 组(紧挨 sys_job_run),注释里写清理由并引 #5193 与 ADR-0057 决策 5、上下游的 #5160 / #5179 / #5192。一个名字、一处清单,没有新分支、没有新配置。

测试

packages/plugins/plugin-audit/src/audit-writers.test.ts 新增一组(照该文件既有的 fake-engine 形状,不引入新设施;fake engine 没有 delete 方法,因此不触及 assertEngineDeleteDispatch 门禁):

  1. 端到端形态 —— 把 service-queue 为一条消息做的全部写按序打一遍(publish insert → lease update → complete update → reaper delete),断言 created空数组(不是「少了几行」,是零);
  2. 豁免组钉死 —— sys_job / sys_job_run / sys_job_queue / sys_automation_run 四张表同组同待遇;
  3. 省下的那次快照读 —— beforeUpdate/beforeDelete 不再对被跳过的对象做 findOne,并用一个业务对象(crm_lead 确实被快照)作对照,证明该断言失败;
  4. 豁免不外溢 —— 普通业务写照常产出 sys_audit_log + sys_activity

变异验证:临时删掉那一行后,上述 1/2/3 三个用例全部转红(4 是对照,两种情况下都绿),确认测试钉的是修复本身。

pnpm --filter @objectstack/plugin-audit typecheck   # tsc --noEmit,0 error
pnpm --filter @objectstack/plugin-audit test        # Test Files 7 passed (7) / Tests 103 passed (103)

越界发现(未在本 PR 修)

sys_upload_session 同样声明了 lifecycle.class = transient(其自身注释即写「ephemeral state, never business truth」)却不在 SKIP_OBJECTS 里,分块上传每传一块就是一次 updateSession 全量写。已按一事一议单独立单:#5202(未指派,交 PM 分诊)。


🤖 Generated with Claude Code

https://claude.ai/code/session_017MCKJaEomEqg4tvz4SzdNd

…5193)

SKIP_OBJECTS group (2) — ADR-0057 decision 5 "stop the amplifier" — already
listed sys_job / sys_job_run / sys_automation_run, but not their highest-volume
sibling sys_job_queue. The audit writers register for all objects and there is
no system-context exemption, so DbQueueAdapter's own writes were mirrored into
sys_audit_log AND sys_activity: at least three per message (publish insert,
lease pending->running, terminal ->completed), plus a retry update per failure
and the #5192 reaper's periodic DELETE — on every email since #5160 routed
delivery through the queue. Each beforeUpdate also paid an extra findOne
snapshot of the row it was about to change.

sys_job_queue is engine-owned plumbing (managedBy: 'engine-owned',
enable.apiMethods: ['get','list'], lifecycle.class: 'transient' since #5179)
that no user can write, so the rows carried no compliance value.

Tests pin the whole message lifecycle (insert/lease/complete/reaper delete)
producing zero rows, the four siblings sharing one exemption group, the skipped
snapshot read (with a business-object control), and that ordinary writes are
still audited.

Co-Authored-By: Claude Opus 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 9:00am

Request Review

@github-actions github-actions Bot added size/m 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.

@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 09:14
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 0162c81 Aug 4, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5193-audit-skip-job-queue branch August 4, 2026 09:18
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 4, 2026
…tes (objectstack-ai#5202) (objectstack-ai#5215)

SKIP_OBJECTS group (2) — ADR-0057 decision 5 "stop the amplifier" — gained
sys_job_queue in objectstack-ai#5193/objectstack-ai#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 / objectstack-ai#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).


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

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/m tests tooling

Projects

None yet

2 participants