Repository navigation
Commit 54159fd
fix(plugin-audit,plugin-sharing): record pages, by_object and the Config list stop naming the target object by its API name (#24698)
Fixes #24611
Clause-②: no
The record pages of `sys_record_share`, `sys_share_link` and
`sys_audit_log`, the `by_object` grouping of record shares and the
`config_changes` audit list stop naming the target object by its API
name (`object_name`, such as `showcase_announcement`). On the record
page the target is now named by the Details tab's pointer card, headed
by the target object's label (the route PR #24500 took for approval
requests). This is the record-page and grouping half of the family whose
list-column half was PR #24600.
Responsibility: `plugin-sharing` and `plugin-audit`'s own declarations
(`nameField`, `titleFormat`, `highlightFields`, two list views) | the
platform path that names the target by its label is the console's
pointer card for the declared `referenceVia` pair, which the default
record page draws in its Details tab | reached by every admin who opens
a record share, a share link or an audit row in Setup, live today.
Readings are against objectstack `92ce3af40` (this branch's base;
`origin/main` `486745ec7` is merged in at `5fd7d943f` and touches
neither package) and objectui at the console pin `.objectui-sha` =
`12dc7b47c3`, by `git show` and `git grep`. objectui `fe11eb12e5` (the
type-half fetch PR #24600 waited on) is an ancestor of that pin: `git
merge-base --is-ancestor` exit 0. The pages were not driven in a
browser; the last section says why.
## What the measurement changed about the card
The card read the titles off `titleFormat`. Measured, `titleFormat`
titles none of these rows on the record page:
- **The served title pointer.** A real `SchemaRegistry` (built at
`92ce3af40`, `registerObject` then `getObject`, with `multiTenant` true
and false) serves `nameField: 'object_name'` for `sys_record_share`,
`nameField: 'label'` for `sys_share_link`, and the declared `nameField:
'action'` for `sys_audit_log`. The registry's designate-only pass
(`provisionPrimary(…, { synthesize: false })`) derives a pointer when
none is declared, and `object_name` is the first `*_name` text field on
a record share. This matches the six non-id designations #20044 listed.
- **The console's H1 ranks that pointer first.** `page:header`
(`containers.tsx`) draws the declared pointer's value, then the
`titleFormat` render (with select values as their option labels), then
the derivation. So a record share's H1 was the bare `object_name` value,
and its `titleFormat` was never reached. A share link's H1 was its
`label`, or `{object_name}/{record_id} ({permission})` when the label is
empty. An audit row's H1 is its `action`.
- **No `titleFormat` token renders a pointer pair as a label.**
`formatTitleTemplate` renders `{record_id}` and `{object_name}` as their
stored text.
- **The strip and the Details tab split the fields.** The default record
page (`RecordDetailView` → `buildDefaultPageSchema`) draws
`highlightFields` minus the title field in the strip. `HeaderHighlight`
draws each as its cell type, so `record_id` (a `text` field) reads as a
raw id: the strip has no pointer face. `record:details` draws every
visible field minus the strip fields and the H1 field. Only there does
`record_id` draw as the pointer card (`DetailSection` →
`resolveRecordPointer` → `RecordPreviewCard`, headed by
`useObjectLabel`). Before this PR, `record_id` was a strip field on all
three objects, so the card never drew.
## What changes
| Object | H1 | Strip | Details tab |
|---|---|---|---|
| `sys_record_share`, before | `object_name` (stamped) | Record (raw
id), Recipient, Access Level, Source | no target row |
| `sys_record_share`, after | `recipient_id` (declared) | Access Level,
Source | Record card (target object's label, record title); Object row |
| `sys_share_link`, before | `label`, else `{object_name}/{record_id}
({permission})` | Object (API name), Record (raw id), Permission,
Audience, Expires At, Revoked At | no target row |
| `sys_share_link`, after | `label`, else `{permission} · {audience}` |
Permission, Audience, Expires At, Revoked At | Record card; Object row |
| `sys_audit_log`, before | `action` | Timestamp, Object (API name),
Record ID (raw id), User | no target row |
| `sys_audit_log`, after | `action` | Timestamp, User | Record ID card;
Object row |
- `sys_record_share` declares `nameField: 'recipient_id'`, the principal
the grant is for, and drops its `titleFormat`, which the declared
required pointer always outranks.
- `sys_share_link` declares `nameField: 'label'`, the field the registry
already derived, and its `titleFormat` becomes `'{permission} ·
{audience}'`, for example "View · Anyone with the link".
- `sys_audit_log` drops `titleFormat: '{action} · {object_name}'`. The
required `action` pointer outranks it on every title reader, so it never
titled a row.
- All three `highlightFields` lose `object_name` and `record_id`.
- `sys_record_share.by_object` keeps its `object_name` sort and loses
its `grouping`.
- `sys_audit_log.config_changes` loses its `object_name` column.
- `object_name` stays a declared field, and the pair, its indexes and
every query on it are untouched.
**Why `recipient_id` for a record share's title.** Only a declared
pointer moves the H1, because the registry's derivation would otherwise
land on `object_name` again. The candidates on the object are its text
fields. `record_id` as the title would hide the record card: the Details
tab drops the H1 field. `id` is the raw-id title #20044 removed
elsewhere. A computed `display_title` formula, the shape #20059 used,
adds a field, which is outside this card's surface, and would show the
same stored id plus the access level, which the strip already shows.
`recipient_id` is required, so the title is never blank. It is the
principal's stored id, the value the strip's Recipient chip showed
before this PR.
## `by_object` and `config_changes`: the four axes
**`by_object`.** (i) Group on a column the pair serves with a label:
none exists. The object has no field holding a label, and a group header
draws a `text` value as stored (PR #24600's reading). (ii) Drop the
grouping and keep the sort: chosen. (iii) Keep the grouping.
- *Real business need.* The view answers "grants per object". The sort
keeps one object's grants together, and each Record cell draws its
target with that object's icon (`RecordPointerCell`, readable state).
What the change gives up is the collapsible header and its count. No nav
entry names the view (`nav_record_shares` opens `all_shares`).
- *Long-term soundness.* No column and no mapping. The view stops
modelling "the type half as visible text", which is the rule PR #24600
set for list columns. Header grouping can return if a label-bearing
grouping key is ever served.
- *Making AI mistakes harder.* The pin now refuses a grouping on a
`referenceVia` type half in either package.
- *Startup focus.* One key removed. Keeping it leaves the family's
defect on the view the card names.
**`config_changes`.** (i) A label-served column: none. These rows carry
`record_id: null`, so no pointer names their target. (ii) Drop the
Object column: chosen. (iii) Keep it.
- *Real business need.* The column drew one of three stored constants.
Its writers are `service-settings`' `config-change-audit.ts`
(`sys_setting`, or `sys_platform_setting` for a global-scope change) and
`plugin-auth`'s admin import (`sys_user`). It never said which setting
changed: that is in `metadata` and `new_value` (namespace, key, scope).
What it gives up is the global-versus-organization hint in the list. The
row's page still shows it in `object_name` and in the empty `tenant_id`
a global change leaves.
- *Long-term soundness.* Naming the object by label needs a stored label
column or a console-side mapping, and the card rules out both. Dropping
it follows #24375's rule: name the object by its label, or do not name
it at all.
- *Making AI mistakes harder.* The audit pin now refuses a type-half
column with or without its id half, so this view is no longer an
exception to the list rule.
- *Startup focus.* One column removed. No dogfood or unit test reads
this view's columns: `settings-config-change-audit.dogfood.test.ts`
reads its filter only.
## Pins
Both pointer-pair test files gain a block enumerated over every object
their package ships (`objects/index.ts`). Each block first asserts which
objects carry a pair, so it cannot pass on an empty set.
- **The served title pointer is never a type half.** It is read through
`provisionPrimary`, the pass the registry runs. Sharing also asserts it
is declared: `recipient_id` and `label`.
- **No `titleFormat` interpolates a type half.**
- **`highlightFields` lists neither half of a pair.**
- **No list view groups on a type half.** In audit, no list view shows a
type half either, with or without its id half.
- **Controls:** `by_object` still sorts on `object_name`,
`config_changes` keeps its filter, and both `record_id` fields still
declare `referenceVia: 'object_name'`.
**Ablation.** Each run was done once at `acb5b7eb9`, through `node
scripts/ablation-replace.mjs` in wrap mode, under
`scripts/pm/os-verify-lock.sh`. The tests import the objects by relative
source path, and `@objectstack/spec` was built, so no rebuild sits
between a mutation and its run. Each mutation landed: the anchor went
from 1 hit to 0 and the file's blob changed. Each restore put the blob
back equal to `HEAD`, left `git diff HEAD` empty, and the tree ended
with an empty `git status`.
| Mutation | File | Red |
|---|---|---|
| delete `nameField: 'recipient_id',` | `sys-record-share.object.ts` |
`the served title pointer is declared and is never a type half`:
received `served: "object_name", typeHalf: true` · `Tests 1 failed / 15
passed` |
| put a `grouping` on `object_name` back into `by_object` |
`sys-record-share.object.ts` | `no list view groups on a type half` · `1
failed / 15 passed` |
| put `record_id` back into `highlightFields` |
`sys-audit-log.object.ts` | `highlightFields lists neither half of a
pair`: `[ 'sys_audit_log: record_id' ]` · `1 failed / 11 passed` |
| put `object_name` back into `config_changes` columns |
`sys-audit-log.object.ts` | `no list view shows or groups on a type
half…` and the control · `2 failed / 10 passed` |
The first attempt at the grouping mutation was refused by the tool and
ran nothing: its replacement contained its anchor, so the anchor count
did not move. It was re-run with a disjoint anchor (the second row).
## Verification
Under `scripts/pm/os-verify-lock.sh`, each with `VERDICT command-exit
0`:
- The build of both packages and their dependency closure, at
`acb5b7eb9` and again at `5fd7d943f`, exited 0.
- At `5fd7d943f`: `pnpm --filter @objectstack/plugin-sharing --filter
@objectstack/plugin-audit test` gave `plugin-sharing` 49 files and 1169
tests passed, and `plugin-audit` 57 files and 937 tests passed.
- At `5fd7d943f`: the `typecheck` of both packages exited 0, with
`check:test-typecheck: OK` for both. No debt-ledger entry moved:
sharing's ledger still holds 2 files and 3 errors, audit's holds none.
- The two edited test files alone: sharing `Tests 16 passed (16)` (10
before), audit `Tests 12 passed (12)` (6 before).
**Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `5fd7d943f` derived 67 commands for this
diff. All 67 ran locally at `5fd7d943f`, each exit code captured before
any pipe: **67 exited 0.** On the first pass two refused with exit 3,
PREREQUISITE NOT MET, because there was no full build yet: `pnpm
check:i18n` and `pnpm check:dual-build-cjs-loads`. They re-ran with exit
0 after `pnpm turbo run build --filter='!@objectstack/docs'
--concurrency=2` (`76 successful, 76 total`, under the lock). Some
verdict lines:
- `pnpm check:i18n`: `check-i18n-bundles: OK (10 package(s) — all
bundles in sync, no undeclared authoring keys)`.
- `pnpm check:nul-bytes`: `OK (scanned 12431 text file(s) … no raw ASCII
control bytes)`.
- `node scripts/check-platform-object-tenancy-census.mjs`: `✓
platform-object tenancy census matches the tree: 85 platform-namespace
objects`.
- `pnpm check:test-source-alias`: `OK — 76 packages with tests scanned`.
- `pnpm check:dual-build-cjs-loads`: `✓ … 111 published require entry
point(s) across 69 package(s) load`.
`node scripts/pm/dispatch-gates.mjs --ran` reported `67 derived, 67 run,
0 NOT-MEASURED, 0 UNRUN`.
Declared to CI: the repo-wide scans (`pnpm lint` and the rest of the
battery), the vitest `repo` project, the type-check lanes and Build
Core. Their PR check-runs are the reading. `check:dev-prereqs` and
`check:skill-frame-freshness` were not run: the diff touches neither
`packages/spec` nor the four-axis frame files.
`scripts/check-platform-checklist.mjs` was not run: no exported symbol
is renamed or removed.
## Not measured: a browser reading
There is no console build in this container (`packages/console/dist` is
absent), so a screen reading needs objectui's console built at the pin
plus a booted backend. The static chain above stands in for it.
## Acceptance notes
- **The Details tab still draws `object_name` as a row of its stored
text**, beside the pointer card that names the same object by its label.
The default record page draws every visible field minus the strip and
the H1 field. So within this card's surface, `object_name` is drawn
somewhere on all three pages: as the H1, as a strip chip, or as a
Details row. This PR picks the Details row. The positions that would
remove it are outside this card's surface: `hidden: true` on the type
half (a field-level declaration, `Field.hidden`, "Hidden from default
UI"), or a console rule that folds a `referenceVia` type half into its
card. Reported to the seat.
- **An audit row's H1 reads its `action` as stored** (`config_change`,
not "Config Change"). The console's declared-pointer rung reads the
record as served, while its `titleFormat` rung maps select values to
option labels. This is unchanged by this PR. A record share's H1 is
likewise the recipient's stored id. Reported to the seat.
- **Other `by_object` views group on an object API name** outside any
pointer pair: `sys_sharing_rule.by_object` and `sys_webhook.by_object`
(`object_name` is the rule's or hook's own target-object field, with no
`record_id` beside it). They are outside this card and its pin.
- #20044, the ADR-0079 designation card, listed `sys_record_share →
object_name` and `sys_share_link → label` among the non-id designations
it noted as possibly intended. This PR declares a pointer on both, so
neither title is derived any more.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 758c77d commit 54159fd
6 files changed
Lines changed: 256 additions & 14 deletions
File tree
- .changeset
- packages/plugins
- plugin-audit/src
- objects
- plugin-sharing/src
- objects
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
45 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
46 | 56 | | |
47 | 57 | | |
48 | 58 | | |
49 | 59 | | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
| 60 | + | |
| 61 | + | |
54 | 62 | | |
55 | 63 | | |
56 | 64 | | |
| |||
128 | 136 | | |
129 | 137 | | |
130 | 138 | | |
131 | | - | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
132 | 146 | | |
133 | 147 | | |
134 | 148 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
102 | 103 | | |
103 | 104 | | |
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 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
46 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
47 | 59 | | |
48 | 60 | | |
49 | 61 | | |
50 | 62 | | |
51 | | - | |
52 | | - | |
| 63 | + | |
| 64 | + | |
53 | 65 | | |
54 | 66 | | |
55 | 67 | | |
| |||
95 | 107 | | |
96 | 108 | | |
97 | 109 | | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
98 | 114 | | |
99 | | - | |
100 | 115 | | |
101 | 116 | | |
102 | 117 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
53 | 64 | | |
54 | 65 | | |
55 | 66 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
128 | 129 | | |
129 | 130 | | |
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 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
0 commit comments