Part of #5846 (the retirement its (a) half makes possible). Filed by the engine-core seat per multi-repo rule 3 (linkage chores are issues, not memory) at dispatch time of the #5574+#5846 engine half.
Blocked-by: #5574
Blocked-by: #5846
Fact (origin/main at filing)
packages/plugins/plugin-audit/src/audit-writers.ts — captureBefore (registered on beforeUpdate / beforeDelete) performs its own ql.findOne for the target id and stores the row on ctx.__previous. Its own comment states the reason: "HookContext.previous 官方有类型但引擎不总是填" (the engine does not always fill it).
That justification is expiring:
Once that lands, captureBefore's fetch is a per-write redundant engine read (full read pipeline: middleware, RLS, field masking) that no consumer needs.
Scope
Make captureBefore consume ctx.previous when bound (or retire the fetch entirely if the engine guarantee covers every registered case — claimant judges by code, not by this card). Audit output must stay byte-identical. Pin with a test that a single-id update() on an audited kernel performs no additional engine read beyond the engine's own bound one — and measure the delete side too (bound since #5272).
Non-scope
Routing
Lands in packages/plugins/plugin-audit ⇒ expected domain:identity; routing label left to the triage seat (single-producer rule).
Refs: #5846 (source), #5272, #5574, #5860 (closed), #5929.
Part of #5846 (the retirement its (a) half makes possible). Filed by the engine-core seat per multi-repo rule 3 (linkage chores are issues, not memory) at dispatch time of the #5574+#5846 engine half.
Blocked-by: #5574
Blocked-by: #5846
Fact (origin/main at filing)
packages/plugins/plugin-audit/src/audit-writers.ts—captureBefore(registered onbeforeUpdate/beforeDelete) performs its ownql.findOnefor the target id and stores the row onctx.__previous. Its own comment states the reason: "HookContext.previous官方有类型但引擎不总是填" (the engine does not always fill it).That justification is expiring:
hookContext.previous—— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 the engine reads the prior row BEFORE dispatchingbeforeDeleteand bindsprevious;claude/issue-5574-bulk-before-per-row) moves the update-side prior read ahead ofbeforeUpdateand bindspreviouson both the single-id and predicate paths, retiringsys_fetch_previous_update.Once that lands,
captureBefore's fetch is a per-write redundant engine read (full read pipeline: middleware, RLS, field masking) that no consumer needs.Scope
Make
captureBeforeconsumectx.previouswhen bound (or retire the fetch entirely if the engine guarantee covers every registered case — claimant judges by code, not by this card). Audit output must stay byte-identical. Pin with a test that a single-idupdate()on an audited kernel performs no additional engine read beyond the engine's own bound one — and measure the delete side too (bound since #5272).Non-scope
object注册 ⇒ 引擎「按对象」需求门(#5284 单 id / #5038 批量)在 audit 启用时恒真 #5860 closed 2026-08-08 via the hook 注册契约只能表达「命中这些对象」,无法表达「全局但排除这些对象」—— #5860 因此在 plugin-audit 内无法落地 #5928excludeObjectscontract) — do not reopen it.sys_fetch_previous_delete) is delete() 的按对象前置行门在任何 kernel 托管引擎上恒真 —— ObjectQLPlugin 自带的 sys_fetch_previous_delete 以 object: '*' 注册在 beforeDelete #5929 (engine-core lane).Routing
Lands in
packages/plugins/plugin-audit⇒ expecteddomain:identity; routing label left to the triage seat (single-producer rule).Refs: #5846 (source), #5272, #5574, #5860 (closed), #5929.