Repository navigation
Commit b238856
fix(plugin-audit): the activity record label is the record's ADR-0079 title, with the id as the floor (#21896)
Fixes #21878
Clause-②: no
## What changes
`plugin-audit`'s record-change mirror wrote each activity row's record
label (`record_label`, and the label inside the created / deleted /
generic updated summary) from a fixed list of field names (`name`,
`subject`, `title`, `full_name`, `label`, `first_name`, `company`,
`email`), falling back to the record id. That list was a second
display-name dialect beside ADR-0079's `resolveDisplayField`, which the
same file already used for referenced-record titles. An object titled by
`company_name` therefore showed its record id on every activity row.
Now, in `packages/plugins/plugin-audit/src/audit-writers.ts`:
- `titleFieldOf(objectName)` is the ONE answer for "which field titles a
record of this object in an activity row": `resolveDisplayField` over
the registered definition (the cached `getObjectDef` lookup the file
already had, no second lookup), flooring to the id when nothing
resolves, when the resolver lands on `id`, when the title field is a
credential (`collectMaskedReadFields`) or `internal`
(`collectInternalReadFields`). Those three skips were inline in
`resolveLookupTitles`; they moved here, and `resolveLookupTitles` now
asks `titleFieldOf` too.
- `recordLabel(record, titleField, id)` reads that field's value from
the same masked ledger view as before. A null, structured or blank value
floors to the id; it never borrows another populated field. The fixed
key list is deleted, not extended.
- The `field` answer the activity row declares as the label's source
(the declaration the read-side redaction keys on) is the resolved field.
No `packages/spec` edit, no change to the read-side gating, no change to
the comment hooks, no backfill: rows already written keep their stored
label.
## Measured, per the dispatch's hypotheses
- **H1, held.** `recordLabel(record, id)` at `audit-writers.ts:299`
(base `e085a8c3`) picked from the 8-key list and took no object
definition. One caller, the activity mirror. Red pin on a
`company_name`-titled object: with the key list put back (ablation
below) the new pin reads `expected '3JO4ITH0my2YkCtR' to be
'ARDCOMPANYONE41'` — the card's symptom.
- **H2, held.** The one caller already had `getObjectDef` (the cached
`engine.getSchema` lookup the referenced-title path and the milestone
path use). It is reused; no second lookup.
- **H3, measured.** Over the bundled example objects, each registered
through a real `ObjectQL` registry so the answer is the materialized
`nameField` (registered answer equals authored answer for all 32): 27
keep their label field; 5 change. The "before" column models the list as
"first list key the object declares".
| object | before | after |
|---|---|---|
| `crm_contact` | `full_name` (a formula with no `returnType: 'text'`,
so ADR-0079 does not derive it) | `first_name`, its registered
`nameField` |
| `crm_opportunity_line_item` | id | `product` |
| `showcase_expense_line` | id | `merchant` |
| `showcase_ext_order` | id | `customer_id` |
| `showcase_invoice_line` | id | `description` |
The same measurement over 50 platform objects
(`@objectstack/platform-objects`): 24 move from the id to their declared
`nameField`, and 3 change field to it — `sys_email_template` `name` to
`label`, `sys_invitation` `email` to `display_title`,
`sys_scim_connection_credential` `label` to `connection_id`. In every
case the new field is the one the record page titles the record by.
Objects titled by `name` / `title` / `subject` are unchanged.
- **H4, held.** `activity-field-redaction.ts` `redactActivityRows`
serves `record_label` (and the summary that interpolates it) whole only
when every declared source field is served to the reader. The
declaration now names the resolved field, so a reader not served
`company_name` is served neither; pinned in the unit file and at the
REST door.
- **H5, held.** Nothing rewrites stored rows; only rows written from now
on change.
## Tests
All at head `e0da4f8a5d`.
- `pnpm --filter @objectstack/plugin-audit exec vitest run
--maxWorkers=2`: 40 files, 641 tests passed.
- `pnpm --filter @objectstack/plugin-audit typecheck`: exit 0, test
layer included (`check:test-typecheck` OK, 0 debt). Run at `1088327afd`;
`git diff 1088327 e0da4f8 -- packages/plugins/plugin-audit` is
empty.
- Dogfood, `--project isolated`: the new
`activity-label-display-field.dogfood.test.ts` plus the sibling
`activity-field-values.dogfood.test.ts`: 2 files, 14 tests passed,
against a rebuilt `plugin-audit` `dist/`. `pnpm --filter
@objectstack/dogfood typecheck`: exit 0.
- New unit file `audit-record-label-display-field.test.ts` (real kernel,
`ObjectQL`, SQLite driver, `AuditPlugin`; the security service is the
one stand-in): `company_name` labels on create / update / delete with
`company_name` declared as the source; `name`, `title`, `subject`
labelled as before; an explicit `nameField` wins over `name`; a blank
title and an empty `name` beside a populated `title` both floor to the
id; a credential title floors to the id; a reader not served
`company_name` gets neither label nor summary, and the control keeps
both.
- Fixture triage in `audit-writers.test.ts`: four localized-summary
cases wrote a record of `person_qualification`, an object the fake
engine never declared, and passed only because the key list guessed
`name` from the record. Disposition: declare the object (titled by
`name`), since a real engine writes only registered objects. Assertions
unchanged.
- Narrowed lint: `eslint --no-inline-config --format json` over the four
changed TypeScript files: 4 files, 0 errors, 0 warnings. All four sit
inside the config's linted population (`--print-config` resolves each).
`eslint.config.mjs` enables no type-aware linting, so this diff cannot
move a verdict on an untouched file. `pnpm lint` itself is CI's.
## Ablation: the fixed key list put back
Through `scripts/ablation-replace.mjs` (anchor hit 1, blob
`47497d1d8eed` to `61d82281885f`), committed fix first.
- **Unit, source-resolved:** 7 red, 4 green, as predicted. Red: both
`company_name` label cases, the explicit `nameField` case, the
empty-`name` floor, the credential floor, and both read-side cases (the
label at rest was the id). Green, as predicted: `name` / `title` /
`subject`, the blank-`company_name` floor, the registry control.
Restore: blob after restore `47497d1d8eed` equals HEAD, `git diff HEAD`
empty.
- **Dogfood, dist-resolved:** mutate, rebuild `plugin-audit`,
`ablation-dist-preflight` found the marker in both JS bundles, then 2
red (the `company_name` label case; the withheld reader was served a
`record_label`) and 2 green (the `name` case, the empty-title floor).
Restore: source blob equals HEAD, rebuilt, `ablation-dist-preflight
--absent` exit 0 (marker absent from all 6 built files, tree clean),
then the green dogfood run above.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `e0da4f8a5d`: 70 commands, all run, and
the `--ran` reconciliation reads 70 derived, 70 run, 0 NOT-MEASURED, 0
UNRUN. Two first readings were build-state, not this diff:
`check:dts-closure` exited 1 on `@objectstack/hono`'s declarations while
my own closure build was rewriting that `dist/` (re-run: 0, 169/169
present), and `check:dual-build-cjs-loads` exited 3, PREREQUISITE NOT
MET, on packages this worktree had not built yet (re-run after the full
build: 0).
## Acceptance notes
- `crm_contact`'s activity label moves from the full name to the first
name, because the example's `full_name` formula declares no `returnType:
'text'` and its registered title is `first_name`. The activity label now
agrees with the record page. Whether the example should title contacts
by `full_name` is the example's call. Carrier: none.
- A formula title (`display_title` on several platform objects) is
declared as the label's only source, though its value is computed from
other fields. A reader served the formula field is served the same value
by the data plane, so the label serves nothing the data plane does not.
- The writer's per-installation object-definition cache (`getObjectDef`,
`getFieldDefs`) is never invalidated, so a definition changed at runtime
may be read stale by the label, milestones and `enable.activities`
alike. This was read from the source, not measured, and it predates this
change. Carrier: none.
- `origin/main` advanced 4 commits past the base `e085a8c3`; none
touches a file of this diff or what the gate derivation reads. Not
merged; the queue rebuilds on current `main`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011K3zqE8Pv1Evw5hc8tZCnN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 25eb7de commit b238856
5 files changed
Lines changed: 587 additions & 25 deletions
File tree
- .changeset
- packages
- plugins/plugin-audit/src
- qa/dogfood/test
| 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 | + | |
Lines changed: 275 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 | + | |
| 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 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
897 | 897 | | |
898 | 898 | | |
899 | 899 | | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
900 | 905 | | |
901 | 906 | | |
902 | 907 | | |
903 | 908 | | |
904 | | - | |
| 909 | + | |
905 | 910 | | |
906 | 911 | | |
907 | 912 | | |
| |||
965 | 970 | | |
966 | 971 | | |
967 | 972 | | |
968 | | - | |
| 973 | + | |
969 | 974 | | |
970 | 975 | | |
971 | 976 | | |
| |||
0 commit comments