Repository navigation
fix(plugin-audit,plugin-sharing): record pages, by_object and the Config list stop naming the target object by its API name - #24698
Conversation
…fig list stop naming the target object by its API name sys_record_share declares nameField recipient_id (the registry derived object_name, so the page heading read the target's API name); sys_share_link declares nameField label and a titleFormat naming what the link grants; sys_audit_log drops a titleFormat its required action pointer always outranked. highlightFields lists neither half of the record_id / object_name pointer pair, so record_id draws as the labelled pointer card in the Details tab. by_object sorts instead of grouping on object_name; config_changes drops its Object column. Claude-Session: https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s Co-authored-by: Claude <noreply@anthropic.com>
…are-audit-record-labels
📓 Docs Drift CheckThis PR changes 2 package(s): 33 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin dda177f22dc4f8083abd1ec7522734f877aab71b && git checkout dda177f22dc4f8083abd1ec7522734f877aab71b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9e2722a923d29d3367ab38d2dee6023b696af8ea 5fd7d943f61e6744f75066e06f6eabcde8beed7a && git checkout -B drift-repro 9e2722a923d29d3367ab38d2dee6023b696af8ea && git merge --no-ff 5fd7d943f61e6744f75066e06f6eabcde8beed7a
node scripts/docs-audit/affected-docs.mjs --json 9e2722a923d29d3367ab38d2dee6023b696af8ea
|
席位核对ACCEPT · #24611 ·
Generated by Claude Code |
Fixes #24611
Clause-②: no
The record pages of
sys_record_share,sys_share_linkandsys_audit_log, theby_objectgrouping of record shares and theconfig_changesaudit list stop naming the target object by its API name (object_name, such asshowcase_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-sharingandplugin-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 declaredreferenceViapair, 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/main486745ec7is merged in at5fd7d943fand touches neither package) and objectui at the console pin.objectui-sha=12dc7b47c3, bygit showandgit grep. objectuife11eb12e5(the type-half fetch PR #24600 waited on) is an ancestor of that pin:git merge-base --is-ancestorexit 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,titleFormattitles none of these rows on the record page:SchemaRegistry(built at92ce3af40,registerObjectthengetObject, withmultiTenanttrue and false) servesnameField: 'object_name'forsys_record_share,nameField: 'label'forsys_share_link, and the declarednameField: 'action'forsys_audit_log. The registry's designate-only pass (provisionPrimary(…, { synthesize: false })) derives a pointer when none is declared, andobject_nameis the first*_nametext field on a record share. This matches the six non-id designations ADR-0079 title: 19 objects that declaretitleFormatand NO pointer getnameField: idstamped by the registry designation pass, so a renderer honouring the declared pointer (objectui#9436) shows the raw record id as the title #20044 listed.page:header(containers.tsx) draws the declared pointer's value, then thetitleFormatrender (with select values as their option labels), then the derivation. So a record share's H1 was the bareobject_namevalue, and itstitleFormatwas never reached. A share link's H1 was itslabel, or{object_name}/{record_id} ({permission})when the label is empty. An audit row's H1 is itsaction.titleFormattoken renders a pointer pair as a label.formatTitleTemplaterenders{record_id}and{object_name}as their stored text.RecordDetailView→buildDefaultPageSchema) drawshighlightFieldsminus the title field in the strip.HeaderHighlightdraws each as its cell type, sorecord_id(atextfield) reads as a raw id: the strip has no pointer face.record:detailsdraws every visible field minus the strip fields and the H1 field. Only there doesrecord_iddraw as the pointer card (DetailSection→resolveRecordPointer→RecordPreviewCard, headed byuseObjectLabel). Before this PR,record_idwas a strip field on all three objects, so the card never drew.What changes
sys_record_share, beforeobject_name(stamped)sys_record_share, afterrecipient_id(declared)sys_share_link, beforelabel, else{object_name}/{record_id} ({permission})sys_share_link, afterlabel, else{permission} · {audience}sys_audit_log, beforeactionsys_audit_log, afteractionsys_record_sharedeclaresnameField: 'recipient_id', the principal the grant is for, and drops itstitleFormat, which the declared required pointer always outranks.sys_share_linkdeclaresnameField: 'label', the field the registry already derived, and itstitleFormatbecomes'{permission} · {audience}', for example "View · Anyone with the link".sys_audit_logdropstitleFormat: '{action} · {object_name}'. The requiredactionpointer outranks it on every title reader, so it never titled a row.highlightFieldsloseobject_nameandrecord_id.sys_record_share.by_objectkeeps itsobject_namesort and loses itsgrouping.sys_audit_log.config_changesloses itsobject_namecolumn.object_namestays a declared field, and the pair, its indexes and every query on it are untouched.Why
recipient_idfor a record share's title. Only a declared pointer moves the H1, because the registry's derivation would otherwise land onobject_nameagain. The candidates on the object are its text fields.record_idas the title would hide the record card: the Details tab drops the H1 field.idis the raw-id title #20044 removed elsewhere. A computeddisplay_titleformula, 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_idis 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_objectandconfig_changes: the four axesby_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 atextvalue as stored (PR #24600's reading). (ii) Drop the grouping and keep the sort: chosen. (iii) Keep the grouping.RecordPointerCell, readable state). What the change gives up is the collapsible header and its count. No nav entry names the view (nav_record_sharesopensall_shares).referenceViatype half in either package.config_changes. (i) A label-served column: none. These rows carryrecord_id: null, so no pointer names their target. (ii) Drop the Object column: chosen. (iii) Keep it.service-settings'config-change-audit.ts(sys_setting, orsys_platform_settingfor a global-scope change) andplugin-auth's admin import (sys_user). It never said which setting changed: that is inmetadataandnew_value(namespace, key, scope). What it gives up is the global-versus-organization hint in the list. The row's page still shows it inobject_nameand in the emptytenant_ida global change leaves.object_name, such asshowcase_announcement), while the get servesobject_label, whichsys_approval_requestdoes not declare #24375's rule: name the object by its label, or do not name it at all.settings-config-change-audit.dogfood.test.tsreads 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.provisionPrimary, the pass the registry runs. Sharing also asserts it is declared:recipient_idandlabel.titleFormatinterpolates a type half.highlightFieldslists neither half of a pair.by_objectstill sorts onobject_name,config_changeskeeps its filter, and bothrecord_idfields still declarereferenceVia: 'object_name'.Ablation. Each run was done once at
acb5b7eb9, throughnode scripts/ablation-replace.mjsin wrap mode, underscripts/pm/os-verify-lock.sh. The tests import the objects by relative source path, and@objectstack/specwas 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 toHEAD, leftgit diff HEADempty, and the tree ended with an emptygit status.nameField: 'recipient_id',sys-record-share.object.tsthe served title pointer is declared and is never a type half: receivedserved: "object_name", typeHalf: true·Tests 1 failed / 15 passedgroupingonobject_nameback intoby_objectsys-record-share.object.tsno list view groups on a type half·1 failed / 15 passedrecord_idback intohighlightFieldssys-audit-log.object.tshighlightFields lists neither half of a pair:[ 'sys_audit_log: record_id' ]·1 failed / 11 passedobject_nameback intoconfig_changescolumnssys-audit-log.object.tsno list view shows or groups on a type half…and the control ·2 failed / 10 passedThe 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 withVERDICT command-exit 0:acb5b7eb9and again at5fd7d943f, exited 0.5fd7d943f:pnpm --filter @objectstack/plugin-sharing --filter @objectstack/plugin-audit testgaveplugin-sharing49 files and 1169 tests passed, andplugin-audit57 files and 937 tests passed.5fd7d943f: thetypecheckof both packages exited 0, withcheck:test-typecheck: OKfor both. No debt-ledger entry moved: sharing's ledger still holds 2 files and 3 errors, audit's holds none.Tests 16 passed (16)(10 before), auditTests 12 passed (12)(6 before).Derived gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat5fd7d943fderived 67 commands for this diff. All 67 ran locally at5fd7d943f, 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:i18nandpnpm check:dual-build-cjs-loads. They re-ran with exit 0 afterpnpm 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 --ranreported67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.Declared to CI: the repo-wide scans (
pnpm lintand the rest of the battery), the vitestrepoproject, the type-check lanes and Build Core. Their PR check-runs are the reading.check:dev-prereqsandcheck:skill-frame-freshnesswere not run: the diff touches neitherpackages/specnor the four-axis frame files.scripts/check-platform-checklist.mjswas not run: no exported symbol is renamed or removed.Not measured: a browser reading
There is no console build in this container (
packages/console/distis 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
object_nameas 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_nameis 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: trueon the type half (a field-level declaration,Field.hidden, "Hidden from default UI"), or a console rule that folds areferenceViatype half into its card. Reported to the seat.actionas stored (config_change, not "Config Change"). The console's declared-pointer rung reads the record as served, while itstitleFormatrung 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.by_objectviews group on an object API name outside any pointer pair:sys_sharing_rule.by_objectandsys_webhook.by_object(object_nameis the rule's or hook's own target-object field, with norecord_idbeside it). They are outside this card and its pin.titleFormatand NO pointer getnameField: idstamped by the registry designation pass, so a renderer honouring the declared pointer (objectui#9436) shows the raw record id as the title #20044, the ADR-0079 designation card, listedsys_record_share → object_nameandsys_share_link → labelamong 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