Repository navigation
Commit 8e4a230
fix(plugin-audit): a user's own sys_user_preference write no longer reaches their bell as a sys_activity row (#22996)
Fixes #22961
Clause-②: no
A user's own write to their own `sys_user_preference` row (the console's
`ui.recent`, `ui.favorites` and palette recents) no longer adds a
`sys_activity` row, so it no longer reaches the user's bell as `Updated
User Preference "ui.recent"`. The `sys_audit_log` row for the same write
is unchanged.
## What the bell entries are, measured
The card marked its mechanism "to confirm". Measured on `origin/main`
`a2240c4dc`:
- **They are `sys_activity` rows, not inbox messages.** A throwaway
probe (not committed) used the real `ObjectKernel`, `ObjectQLPlugin`,
`AuditPlugin`, SQLite and the shipped `SysUserPreference` object. It
created and then updated a user's own `ui.recent` row, acting as that
user. Each write produced one `sys_audit_log` row and one `sys_activity`
row. The update's summary was `Updated User Preference "ui.recent"` (the
card's text), with `actor_id` set to the user. The objectui bell's
Activity tab lists the newest `sys_activity` rows the caller may read.
Its second line is `timeAgo · object_name`, which matches the card's `·
sys_user_preference` subtitle; notification rows show no object name.
- **Nothing turns a record write into a `sys_inbox_message`.**
`service-messaging` registers no data hook. The only callers of `emit()`
are approvals, comment @mentions, the automation `notify` node and the
HTTP nodes. Subscription expansion (`sys_notification_subscription`) is
not wired, and its object docblock says so. So the inbox half of the
card's acceptance item 3 already holds on `main`, and the activity
mirror is the channel to change.
- **Other writers of a preference row.** Members are limited to their
own rows by RLS (`sys_user_preference_self`, `user_id ==
current_user.id`). An organization admin's wildcard `modifyAllRecords`
passes that RLS, and Setup lists the object (`nav_user_preferences`), so
an admin can edit another user's row. No server-side code writes the
object. These writers keep today's rows.
- **Which objects are in the class.** The console keeps all of its
per-user UI state on `sys_user_preference`: objectui's `ConsoleShell`
binds its `ui.favorites`, `ui.recent` and `ui.flow.palette.recents`
adapters to it. No other per-user UI-state object exists. No spec key
means "per-user UI state". The existing `enable.activities: false` mutes
every writer, which is wider than this rule.
## The change
In `packages/plugins/plugin-audit/src/audit-writers.ts`:
- Beside `SKIP_OBJECTS`, a closed, module-private map,
`PER_USER_UI_STATE_OWNER_FIELD` (`sys_user_preference` maps to
`user_id`). Its comment writes the rule down: what reaches a user's bell
is another actor's act or something the user needs, never the user's own
UI state.
- In `writeAudit`, the writer keeps the ledger row and skips the
activity row when both of these hold:
- the object is in the map;
- the user the activity row would name (`userId`, the `actor_id` value)
is the owner on every side of the row the write carries.
It also skips the actor-name read, which only feeds the activity row.
This is the same seam as the existing `enable.activities` opt-out.
- Not touched: `SKIP_OBJECTS`; `AUDIT_EXCLUDED_OBJECTS`, which the hook
registration and `read-audit.ts` read; the package entry.
`dist/index.d.ts` names neither new symbol (0 hits; the control
`installAuditWriters` has 3). The `sys_audit_log` row a preference write
produces is unchanged.
## Pins (`audit-writers.test.ts`)
- The owner's own create, update and delete of a `sys_user_preference`
row each produce exactly one `sys_audit_log` row (`action`, `user_id`
and `record_id` asserted) and zero `sys_activity` rows.
- CONTROL: the same user's update of an ordinary audited object keeps
its activity row.
- CONTROL: on a user's preference row, each of these keeps its activity
row, with the expected `actor_id`:
- a write by another user (an admin);
- a system write with no user;
- the owner moving the row to another user.
Ablations ran through `scripts/ablation-replace.mjs` in wrap mode. For
each one the tool showed the anchor count going 1 to 0 on disk and the
blob changing. It then proved the restore: blob equal to `HEAD` and an
empty `git diff HEAD`. They ran on `2632f1ee5`, whose plugin-audit
sources are byte-identical to the head:
1. **Skip removed** (`mirrorsActivity = activitiesEnabled`): `Tests 3
failed | 4 passed`. The three own-write pins fail with `AssertionError:
expected [ { object: 'sys_activity', …(1) } ] to have a length of +0 but
got 1`.
2. **Blanket mute** (owner check removed): `Tests 3 failed | 4 passed`.
The three other-writer controls fail with `AssertionError: expected []
to have a length of 1 but got +0`.
3. **Ledger row dropped** (`sys_user_preference` added to
`SKIP_OBJECTS`): `Tests 6 failed | 1 passed`. The own-write pins fail at
`audit-writers.test.ts:727`, the `sys_audit_log` length assertion.
The first attempts at ablations 2 and 3 were no-ops. Their replacement
text contained the anchor, so `ablation-replace` saw the anchor count
stay at 1, refused before running the tests, and restored the file. Both
were re-run with anchors the replacement does not contain. The readings
above are from the re-runs.
The card's acceptance item 1 (a browser walk) was not done. The pins
carry it at the writer seam that produces the rows.
## Verification
- `@objectstack/plugin-audit` unit suite, at `2632f1ee5` (later commits
change only the changeset): `Test Files 47 passed (47)`, `Tests 750
passed (750)`.
- `pnpm --filter @objectstack/plugin-audit typecheck` (`tsc`, the
scripts project and `check:test-typecheck` over `tsconfig.test.json`):
exit 0.
- Dependency closure (`turbo run build
--filter=@objectstack/plugin-audit^...`, 15 tasks) and the package's own
build (`check-dts-emitted`: 1/1 declaration file present): exit 0.
- Derived gates: `node scripts/pm/dispatch-gates.mjs --commands` over
this branch (3 paths, 67 families), every command run and reconciled
with `--ran`.
- At `03373429f`: `Run reconciliation — 67 derived, 67 run, 0
NOT-MEASURED, 0 UNRUN`, with every recorded exit code 0.
- A first pass at `2632f1ee5` recorded `check:dual-build-cjs-loads` and
`check:i18n` at exit 3 (PREREQUISITE NOT MET), because other packages
had no `dist/`. Later in that pass, `check:type-check-debt` built the
whole workspace through `turbo run typecheck`.
- On the head pass both measured green: 107 CommonJS entries across 66
packages load, and 9 packages' translation bundles are in sync.
- Lint, scoped to the touched files, with three pieces of evidence:
- eslint's own config claims both files (`--print-config`: 6 and 5
rules); it claims nothing for the changeset `.md`.
- `eslint --no-inline-config --format json` over the two files: 2 files,
0 errors, 0 warnings, at `03373429f`.
- `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules, stated at its line 327), so
this diff cannot change the result for any file it does not touch.
- `origin/main` was merged before opening the PR. It was still
`a2240c4dc` (remote tip read with `ls-remote`), so the merge was a
no-op.
## Acceptance notes
- Activity rows written before this change are not deleted. They leave
the feed when the 14-day `sys_activity` retention
(`lifecycle.retention.maxAge: '14d'`) removes them. The changeset says
so.
- For a skipped activity row, the writer still composes the summary and
resolves the locale, exactly as it does today for `enable.activities:
false`. That work is wasted but has no observable effect. Not filed.
- The bell's Activity tab shows `sys_activity.object_name` verbatim
(objectui `InboxPopover.tsx`, filled by `activityItemType.ts`). Every
other activity row therefore still shows a machine name such as
`crm_lead` rather than the object's label. This is reported to the seat
as a candidate in objectui. It is not in this PR.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>1 parent 929c8b1 commit 8e4a230
3 files changed
Lines changed: 184 additions & 2 deletions
File tree
- .changeset
- packages/plugins/plugin-audit/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
686 | 686 | | |
687 | 687 | | |
688 | 688 | | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
689 | 789 | | |
690 | 790 | | |
691 | 791 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
| 158 | + | |
| 159 | + | |
158 | 160 | | |
159 | 161 | | |
160 | 162 | | |
| |||
260 | 262 | | |
261 | 263 | | |
262 | 264 | | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
263 | 326 | | |
264 | 327 | | |
265 | 328 | | |
| |||
2012 | 2075 | | |
2013 | 2076 | | |
2014 | 2077 | | |
| 2078 | + | |
| 2079 | + | |
| 2080 | + | |
| 2081 | + | |
| 2082 | + | |
2015 | 2083 | | |
2016 | 2084 | | |
2017 | 2085 | | |
2018 | 2086 | | |
2019 | 2087 | | |
2020 | 2088 | | |
2021 | 2089 | | |
2022 | | - | |
| 2090 | + | |
2023 | 2091 | | |
2024 | 2092 | | |
2025 | 2093 | | |
| |||
2055 | 2123 | | |
2056 | 2124 | | |
2057 | 2125 | | |
2058 | | - | |
| 2126 | + | |
2059 | 2127 | | |
2060 | 2128 | | |
2061 | 2129 | | |
| |||
0 commit comments