Repository navigation
Commit e391f87
fix(plugin-detail): name the actor of an activity row that carries only actor_id (objectui#12067) (#12070)
Fixes #12067
Clause-②: no
Implemented by the os-dev dispatched from seat `domain:ui#3`, session
`https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8`.
## What changed
- **`record:history`** (`record-history.tsx`): its `sys_activity`
self-fetch now passes `$expand: ['actor_id']`, and the entry's user
comes from `activityActorName(row)` instead of `actor_name` alone.
- **`recordActivityFeed.ts`**: a new module-level reader,
`activityActorName`, applies the card's rule. It returns the
`actor_name` snapshot when that is non-blank. Otherwise it returns the
expanded `actor_id` record's `name` (`sys_user`'s declared name field).
Otherwise it returns `null`, so the caller keeps its existing fallback.
`activityRowToFeedItem` now uses it in place of `row.actor_name ??
systemActorLabel`.
- The two other `sys_activity` reads whose rows go through
`activityRowToFeedItem` pass the same expand: the `record:activity`
block's self-fetch (`record-activity.tsx`) and the console record page's
merged feed (`RecordDetailView.tsx`, app-shell).
- Docs: one paragraph in `content/docs/plugins/plugin-detail.mdx`
(AGENTS.md #2).
- Changeset `.changeset/12067-history-actor-id.md`: `patch` for
`@object-ui/plugin-detail` and `@object-ui/app-shell`.
No export, prop, type or pack key changes. `activityActorName` is not
re-exported from the package index. In the built
`packages/plugin-detail/dist/index.d.ts` it has 0 hits, against 3 hits
for the control `activityRowToFeedItem`. `SysActivityRow` is unchanged
too: the reader reads `actor_id` through its existing index signature.
## Measurements (the dispatch's three mechanism hypotheses)
1. **How History reads `sys_activity`, and whether the read can expand
`actor_id`: yes, in the same request.**
- The renderer calls `dataSource.find('sys_activity', { $filter: {
object_name, record_id }, $orderby: { timestamp: 'desc' }, $top })`.
`$expand` is a declared `QueryParams` member (`@object-ui/types`
`data.ts`).
- `ObjectStackAdapter.find` sends any `$expand` to
`rawFindWithPopulate`: one `GET
/api/v1/data/sys_activity?populate=actor_id&...`. Its filter conversion
is the same as the SDK route's: `translateFilterToAST` calls
`convertFiltersToAST`.
- On the server (objectstack `6befe19c`, spec 17.7.0),
`sys_activity.actor_id` is `Field.lookup('sys_user')`. It is the same at
`@objectstack/plugin-audit@17.0.0`, so every 17.x server declares the
field and the protocol's expand gate (`assertExpandTargetsExist`) admits
it.
- `expandRelatedRecords` loads the ids of the whole page with ONE
`find(sys_user, id in [...])` through the engine's own read path (CRUD
gate, RLS, FLS). When that read is refused it keeps the bare id, and the
parent read still succeeds.
- This is the route objectui#11701's Audit Log page took for the same
question: `$expand=user_id`, then read `name`.
2. **The `sys_user` read door: ordinary viewers are not refused, so the
stop clause does not fire.**
- `member_default` grants `sys_user` `allowRead: true`, row-scoped by
`sys_user_self` (`id == current_user.id`) and `sys_user_org_members`
(`id in current_user.org_user_ids`). The read-only set carries the same
two `select` policies.
- So a viewer can read the name of every member of the same
organization. An actor outside that scope, or a deleted user, keeps the
bare id, and the entry shows the existing fallback. A raw id is never
shown as a name.
3. **`recordActivityFeed.ts`: the same gap.**
- The same id-only rows reach `activityRowToFeedItem` through two reads:
the `record:activity` self-fetch, and `RecordDetailView`'s merged feed
on the default console record page.
- `actor: row.actor_name ?? systemActorLabel` showed those rows as made
by "System", which credits the change to no person. Per the claim, the
same rule now applies there.
- The rule takes effect only where the read expands `actor_id`, so both
of those reads now do.
## Surface beyond the claim's file list (stated, not silent)
The claim names `record-history.tsx`, `recordActivityFeed.ts`
(conditional), their tests and the changeset. This PR also touches:
- `record-activity.tsx` and `RecordDetailView.tsx`: one `$expand:
['actor_id']` line each. Without them, the rule the claim admits into
`recordActivityFeed.ts` would read an expanded record that never
arrives. Leaving `RecordDetailView` out would also make the console
record page and the `record:activity` block disagree about the same row,
a disagreement that file's own comment calls a bug.
- `RecordDetailView.activityActorId-12067.test.tsx` (that read's pin)
and the docs paragraph.
The seat may amend the claim's file surface.
## Tests (all at `6b1a3ff64`)
New pins:
- `record-history.actorId-12067.test.tsx`, the claim's four:
- a row with only `actor_id` shows the user's name;
- a row with both shows `actor_name`;
- a row with neither shows the existing fallback, and so does a bare id
the viewer may not read;
- a page of 6 id-only rows makes exactly one `find`, on `sys_activity`
with `$expand: ['actor_id']`, and no `sys_user` read.
- `recordActivityFeed.actorId-12067.test.tsx`: the same three rules on
the mapper, plus the `record:activity` self-fetch (one read, expanded,
both names shown, no "System").
- `RecordDetailView.activityActorId-12067.test.tsx`: the console page's
`sys_activity` read carries the expand, and the feed shows the user, not
"System".
Runs:
- `pnpm exec vitest run packages/plugin-detail/ --maxWorkers=2`: `Test
Files 250 passed | 1 skipped (251)`, `Tests 2433 passed | 8 skipped
(2441)`.
- app-shell, **narrowed and declared**: the 111 app-shell test files
that name `RecordDetailView` (`git grep -l RecordDetailView` over
app-shell tests), run in two halves: `56 passed (56)` / `495 passed`,
and `55 passed (55)` / `394 passed`. The full app-shell suite (1245
files) does not fit the foreground cap, so it is CI's.
- `apps/console/src/__tests__/record-block-record-reach.test.tsx`: `13
passed (13)`.
- `pnpm --filter @object-ui/plugin-detail type-check` and `pnpm --filter
@object-ui/app-shell type-check`: both exit 0, after building each
dependency closure. `tsc -p tsconfig.test.json --listFilesOnly` lists
each new test file.
- `pnpm exec eslint` on the 7 touched TS/TSX files: 0 errors and 140
warnings (`no-explicit-any` and the like; the repo has no warnings
ratchet). This is a narrowed run, and three facts back it:
- population: the repo's own `eslint.config.js`;
- count: 7 files, read from `--format json`;
- invariance: no `parserOptions.project` or `projectService`, and no
custom rule in `eslint-rules/` reads the filesystem, so this diff cannot
move a verdict on an untouched file.
- `check:control-bytes`, `check:test-path-roots`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:new-line-citations` (0 new), `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:doc-types` and `check:doc-fences`: all exit 0. So do
`check-changeset-presence.mjs` (7 source files of 2 released packages, 1
changeset), `check-changeset-no-major.mjs`, `check-changeset-fixed.mjs`
and `check-changeset-overwrite.mjs`. `check-governed-queue-guard.mjs
--test` reports NOT GOVERNED for all 9 paths.
- NOT MEASURED:
- `check:doc-snippets` and `check:doc-examples`. Reason: prerequisite.
The gate printed "THE GATE COULD NOT RUN" because the packages its
examples import are not built. The docs diff adds 0 fenced blocks.
- The console eager-closure budget. Reason: it needs a console build,
which is CI's Bundle Analysis job.
## Ablations (predictions written before each run)
Each run used `ablation-replace.mjs` in wrap mode. Every anchor hit 1
time and went to 0 on disk. Every restore matched the HEAD blob, and
`git diff HEAD` was empty afterwards.
| Mutation | Predicted | Observed |
|---|---|---|
| A1: drop `$expand: ['actor_id']` from `record-history.tsx` | history:
2 red (id-only, N rows), 2 green | `Tests 2 failed \| 2 passed (4)` |
| A2: `user_name: activityActorName(r)` back to `r.actor_name ?? null` |
the same 2 red / 2 green | `Tests 2 failed \| 2 passed (4)` |
| A3: mapper back to `row.actor_name ?? systemActorLabel` | feed: 3 red,
"both" green; app-shell pin red | `Tests 4 failed \| 1 passed (5)`
across both files |
| A4: drop the expand from `record-activity.tsx` | feed: self-fetch red,
3 mapper pins green | `Tests 1 failed \| 3 passed (4)` |
| A5: drop the expand from `RecordDetailView.tsx` | app-shell pin red |
`Tests 1 failed (1)` |
## Acceptance notes
- **The `$expand` route skips the adapter's missing-resource memo.** On
a deployment with no `sys_activity` (no audit plugin), each mount now
repeats a 404 read instead of remembering the first one. What the user
sees is unchanged: a 404 is not a refusal, so the history and the feed
stay empty. Observation only, nothing filed; carrier: none.
- **Avatar.** `actor_avatar_url` is still the only avatar source. An
id-only row shows initials from the resolved name. This is outside the
card's acceptance.
- **Writer half.** The writer half is objectstack-ai/objectstack#22510,
and this PR does not touch it. Once it lands, new rows carry
`actor_name`, which still wins. The expand then names only the older
rows, at the cost of one batched server-side `sys_user` read per page.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent acc4328 commit e391f87
9 files changed
Lines changed: 454 additions & 2 deletions
File tree
- .changeset
- content/docs/plugins
- packages
- app-shell/src/views
- plugin-detail/src/renderers
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
263 | 271 | | |
264 | 272 | | |
265 | 273 | | |
| |||
Lines changed: 176 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1970 | 1970 | | |
1971 | 1971 | | |
1972 | 1972 | | |
| 1973 | + | |
| 1974 | + | |
| 1975 | + | |
| 1976 | + | |
| 1977 | + | |
1973 | 1978 | | |
1974 | 1979 | | |
1975 | 1980 | | |
| |||
Lines changed: 127 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
0 commit comments