Skip to content

fix(plugin-audit,plugin-sharing): record pages, by_object and the Config list stop naming the target object by its API name - #24698

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24611-share-audit-record-labels
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24611-share-audit-record-labels

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 ADR-0079 title: 19 objects that declare titleFormat and NO pointer get nameField: id stamped by the registry designation pass, so a renderer honouring the declared pointer (objectui#9436) shows the raw record id as the title #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 fix(plugin-approvals,plugin-audit,plugin-sharing): list views drop the target object's API name column beside a record pointer #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 plugin-approvals: the approval request page's Object chip shows the target object's API name (object_name, such as showcase_announcement), while the get serves object_label, which sys_approval_request does not declare #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.
  • ADR-0079 title: 19 objects that declare titleFormat and NO pointer get nameField: id stamped 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, 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

…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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-audit, @objectstack/plugin-sharing, touching 14 documentable anchor(s).

33 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 9e2722a923d29d3367ab38d2dee6023b696af8ea.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 38 pages)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9e2722a923d29d3367ab38d2dee6023b696af8ea → packageMentionDocs.

Which tree this was computed on

This run read content/docs from dda177f22dc4f8083abd1ec7522734f877aab71b — the merge of head 5fd7d943f61e6744f75066e06f6eabcde8beed7a into base 9e2722a923d29d3367ab38d2dee6023b696af8ea, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 9e2722a923d29d3367ab38d2dee6023b696af8ea → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #24611 · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-20T00:31Z · head 5fd7d943f6, the review of record for claim 6279563553.

  1. Form: draft, base main. The first line is Fixes #24611, and no other closing keyword sits beside an issue number. Clause-②: no: title, highlight and list-view keys on shipped platform objects, with no export, accepted value, stored column or authorable key moving. One patch changeset for @objectstack/plugin-audit and @objectstack/plugin-sharing. 6 files, +256/−14. No contract face is touched (check-governed-merges: not governed).
  2. Premise, measured (the card's title reading corrected):
    • The dev built a real SchemaRegistry at 92ce3af40. It stamps nameField: 'object_name' on sys_record_share and 'label' on sys_share_link, and the console's heading (objectui at the pin 12dc7b47c3) ranks a declared pointer above titleFormat. So the share's heading was the bare API name, and titleFormat titled none of these rows. No titleFormat token is label-bearing: the console renders {object_name} / {record_id} as stored text.
    • The seat's read of the diff: sys_record_share declares nameField: 'recipient_id' and drops titleFormat. sys_share_link declares nameField: 'label', with titleFormat: '{permission} · {audience}' for an unlabelled link. sys_audit_log drops its unreachable titleFormat. All three strips drop both halves of the pointer pair, so record_id draws in the Details tab as the card headed by the object's label. by_object sorts instead of grouping, and config_changes drops its Object column; its rows carry no record_id.
    • Pins: sharing-pointer-pairs.test.ts 10 → 16 and sys-audit-log-pointer-pair.test.ts 6 → 12. Four ablations, each red on its own pin and restored to HEAD's blob.
  3. Gates:
    • Local on the head: plugin-sharing 1169 tests and plugin-audit 937 tests pass, typechecks exit 0 with the ledgers unchanged, and the 67 derived families exited 0 (--ran: 0 NOT-MEASURED, 0 UNRUN).
    • The seat's diff read: 0 expect( lines removed.
    • CI on the head at this read: 20 success, 6 skipped, 11 in progress, 0 failed. Landing waits for all of them.
  4. Accepted deviations:
  5. Out of scope (the seat files each):
  6. Landing: ordinary queue landing. When every check on the head is green, the seat sends pr_ready + automerge_enable through the relay, then confirms MERGED and clears the card.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 00:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 20, 2026 00:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 54159fd Oct 20, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24611-share-audit-record-labels branch October 20, 2026 01:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants