Skip to content

Commit c8ff269

Browse files
os-zhuangos-zhuangclaude
authored
fix(plugin-audit): consume the engine's bound ctx.previous and record one normalised view on both sides of the diff (#6656) (#6977)
* fix(plugin-audit): consume the engine's bound ctx.previous and normalise both sides of the diff (#6656) Retires captureBefore's own pre-image read (the engine binds `previous` before every before* dispatch, under the same predicate that dispatches the hook), and masks the credential field classes on both sides of the audit diff so the two sides share one view as well as one source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ * fix(plugin-audit): drop only VIRTUAL computed fields from audit snapshots (#6656) Reverse verification of the snapshot-normalisation limb showed it could only be made red on the create face. Chasing why exposed an over-reach: the limb keyed off `COMPUTED_FIELD_TYPES`, but of that set only `formula` is virtual. Both `autonumber` (seeded at insert) and `summary` (written by `recomputeSummaries`) are stored columns, present and equal on BOTH sides — dropping them deleted the record's human-facing number and its roll-up values from the ledger to fix an asymmetry they never had. Narrowed to a new `VIRTUAL_FIELD_TYPES` set, documented against the wider one so the two are not unified later, and pinned in both directions: the fixture now carries a stored `autonumber` alongside the `formula`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ --------- Co-authored-by: os-zhuang <steve@objectstack.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 17116a0 commit c8ff269

7 files changed

Lines changed: 903 additions & 85 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
"@objectstack/plugin-audit": patch
3+
---
4+
5+
fix(plugin-audit): consume the engine's bound `ctx.previous` and record one normalised view on both sides of the diff (#6656)
6+
7+
`plugin-audit` used to fetch its own pre-image. `captureBefore`, registered on
8+
`beforeUpdate` / `beforeDelete`, issued a `ql.findOne` for the target row and
9+
stashed it on `ctx.__previous`, because `HookContext.previous` was "officially
10+
typed but not always populated by the engine itself". That is no longer true on
11+
any path this plugin registers for, so the read is retired and the writer reads
12+
the contract value.
13+
14+
**The read that goes away** (measured with a counting driver on the audited
15+
object, `driver.findOne` per write):
16+
17+
| write | before | after |
18+
|:--|--:|--:|
19+
| single-id `update()` | 2 | 1 |
20+
| single-id `delete()` | 2 | 1 |
21+
| predicate `update()`, 3 matched rows | 3 | 0 |
22+
| predicate `delete()`, 3 matched rows | 3 | 0 |
23+
24+
The predicate column is the larger half and was pure waste. #5574 binds
25+
`input.id` on every per-row *before* context, which defeated the handler's own
26+
`if (!id) return` bulk guard — so it read every matched row, and every result
27+
was discarded, because `__previous` landed on the per-row *before* context while
28+
the per-row *after* contexts (the ones the writer actually runs on) never saw
29+
it. The engine's own matched-row read is untouched and still serves both phases,
30+
so the ledger is unchanged.
31+
32+
**What the ledger records changes, and deliberately.** The two sides of an audit
33+
diff came from two different pipelines: `before` through the engine's read path
34+
(credentials masked, formulas hydrated, file references resolved) and `after`
35+
from the raw write result. That asymmetry — not the redundant read — is why a
36+
write that touched one field recorded phantom "changes" for every secret, file
37+
and formula field on the record. Retiring the read makes both sides
38+
same-source; the writer now also gives them one view, so the surface levels
39+
upward rather than down to raw store contents:
40+
41+
- **Credential fields are masked on both sides.** Single-id delete `old_value`
42+
still reads `••••••••` for a `secret` field — that face is byte-identical.
43+
Change detection still runs on the raw values, so rotating a secret is still
44+
recorded as a change; only the recorded values are masked.
45+
- **A pre-existing leak is closed.** The stored `secret:` ref was already
46+
reaching `sys_audit_log.new_value` on every create and update, and a
47+
`password` field — which ADR-0100 stores in cleartext at rest — was landing
48+
there **in plaintext**, in the audit ledger and in the `sys_activity` summary
49+
rendered in the record feed. Both now record the mask.
50+
- **Virtual (`formula`) fields leave the full snapshots.** `ctx.result` carries
51+
hydrated formulas (#5504) and the raw pre-image structurally cannot, so
52+
create `new_value` would have described a field delete `old_value` could
53+
never carry. Only genuinely virtual fields are dropped: `autonumber` and
54+
`summary` are stored columns present and equal on both sides, and they stay
55+
in the snapshot.
56+
57+
Two consequences worth naming, both narrowing single-id delete to what bulk
58+
delete already did: its `old_value` now records a file field's stored id rather
59+
than the resolved `{id, name, size, url}` object, and drops formula values. An
60+
object whose label field is a formula falls back to the record id in the
61+
`sys_activity` label on delete for the same reason.
62+
63+
No audit coverage is removed: the plugin keeps its `afterInsert` / `afterUpdate`
64+
/ `afterDelete` registrations, which is what holds the engine's pre-image demand
65+
gates open, and every one of them keeps the `excludeObjects` face from #5860.

packages/plugins/plugin-audit/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@objectstack/plugin-audit",
33
"version": "17.0.0-rc.5",
44
"license": "Apache-2.0",
5-
"description": "Audit Plugin for ObjectStack System audit log object and audit trail",
5+
"description": "Audit Plugin for ObjectStack \u2014 System audit log object and audit trail",
66
"main": "dist/index.js",
77
"types": "dist/index.d.ts",
88
"exports": {
@@ -19,11 +19,11 @@
1919
},
2020
"dependencies": {
2121
"@objectstack/core": "workspace:*",
22+
"@objectstack/objectql": "workspace:*",
2223
"@objectstack/platform-objects": "workspace:*",
2324
"@objectstack/spec": "workspace:*"
2425
},
2526
"devDependencies": {
26-
"@objectstack/objectql": "workspace:*",
2727
"@types/node": "^26.1.2",
2828
"typescript": "^6.0.3",
2929
"vitest": "^4.1.10"

0 commit comments

Comments
 (0)