Repository navigation
Description
Filing gate ②: a maintainer ruling, carried out as child 9 of the nine that #26059's ruling (Q3) names. ⛔ Not a claim.
The Title column in Setup → Business Unit Members, and the record page heading, read " in ": two opaque ids, with no person or department name. This is not a rendering bug. The object's title is declared as a formula over the two stored lookup ids, so the title is the ids by definition. It needs to be built from the two lookups' display names instead.
Part of #26059
Ruling-ref: 6416233738 (#26059 (comment))
Ruling: batch #396 item 1, letter Q1 是 / Q2 不做 / Q3 分拆 / Q4 框架. This card is Q3 child 9 (server, p3): item 8. Brief 6415795834, item-by-item reading 6415971852 (item 8).
Filed by domain:ui seat 2 (domain:ui#2) · os-steve · session session_01SMps7wq7rBTk53e8KTSEup, first claimant of #26059. Domain, grade and route are triage's on first touch.
Business reason. An admin checking who belongs to which department reads a column of ids and has to open each row to find out. Any other surface that names a membership record by its title shows the same ids.
Reader who acts. The domain:server execution seat, dispatch step: one os-dev, one PR in packages/platform-objects/src/identity/. The PR touches the formula runtime or a hook only if the mechanism chosen below needs it.
Spec contract. None is expected to change. If the chosen mechanism needs a formula capability that does not exist, say so on this card before building. Do not widen @objectstack/formula silently.
Duplicate check. A local scan of the 1,962 newest issues (all states, #22168–#26181) for sys_business_unit_member.{0,200}(title|display)|display_title.{0,100}business_unit found 0 hits.
Ruled scope (ruling Q3 item 9)
sys_business_unit_member's title is built over the two lookups' display names instead ofrecord.user_id + ' in ' + record.business_unit_id(sys-business-unit-member.object.ts:38, :52–55).- The dev names the mechanism first. Either a formula over related display names, or a stamped title kept up to date by a hook on both ids. The reading did not find a lookup-display helper in
packages/formula/src/stdlib.ts.
Anchors (re-read on origin/main 83a26c38)
packages/platform-objects/src/identity/sys-business-unit-member.object.ts::38titleFormat: '{user_id} in {business_unit_id}'holds.:52–58is thedisplay_titleformula field; the expressionF`record.user_id + ' in ' + record.business_unit_id`is at:55. The ruling's:52–55holds.- The file's own comment at
:33–35already states the limit: "a formula is evaluated on the stored row, before$expand, and cannot reach a related record's own title". That confirms the mechanism has to be named before any code.
packages/platform-objects/src/identity/identity-display-title.test.tsholdstitleFormatanddisplay_titleto the same text, so both move together.packages/formula/src/stdlib.ts: no lookup-display helper found (re-checked).
Expected / acceptance
- The mechanism is named on this card before code, with its cost on writes and reads.
- The list Title column and the record heading show "<user's name> in <business unit's name>".
- The title follows a change of either
user_idorbusiness_unit_id. A renamed user or unit shows the new name; if the mechanism is a stamp, the PR says how far that goes. titleFormatanddisplay_titlestill agree (identity-display-title.test.ts). A test covers the named title.
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade — as filed ·
pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T16:04Z⛔ Not a claim, ⛔ not a dispatch.
Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 3 · card thread
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01KCZSeNY7z5jAXn3EyHdZwwAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-6rh8kd(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: the maintainer's ruling on #26059 (
6416233738, Q3 child 9) and triage audit6420804912, dispatched under 「p1 p2 处理完了,就可以处理 p3」 (2026-10-26T10:29Z) —sys_business_unit_member's title is built over the two lookups' display names; the mechanism is named on the card with its cost before any code; ⛔ no silent widening of@objectstack/formula(a missing capability goes toneeds_decision)File surface:
packages/platform-objects/src/identity/sys-business-unit-member.object.ts,identity-display-title.test.tsand the identity tests, a hook in the identity package only if the named mechanism is a stamp, one changeset. ⛔ Nopackages/spec/**, ⛔ nopackages/formula/**without a ruling (stop on breach; explain in the report)Container & model:
M,bug,mode:thread, default model and effort (set at thread open)Clause-②: no (expected; the dev measures on the real diff)
Metadata-protocol: none
Responsibility: graded as filed by triage
6420804912; a child of #26059 under ruling6416233738Thread-read: 6420804912
Premise: holds (read 2026-10-26T18:53Z at
origin/mainc49688601d: card open, unassigned,pm:queue, no claim on the thread)Resume-from: none — fresh start from
origin/mainSerial constraints cleared: no open PR known to the seat touches
sys-business-unit-member.object.tsat this act; the dev re-measures every open PR onpackages/platform-objects/src/identity/**before the first edit and beforepr_createGenerated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Mechanism, named before code (card thread
session_01KCZSeNY7z5jAXn3EyHdZww, branchclaude/project-thread-6rh8kd)Choice: a formula over the related display names, read ONE hop through each lookup. No stamp, no hook, no new formula capability.
It already exists; nothing in
@objectstack/formulais widened. The one-hop formula read is onmainsince #24197 (ADR-0079 addendum 2026-10-18, items 1 to 4):record.FIELD.RELATEDthrough alookupis preloaded by the engine read path (find,findOne, the insert and update responses;packages/objectql/src/engine.tsformulaHopScope/resolveOneHopRelated). The comment atsys-business-unit-member.object.ts:33–35("a formula ... cannot reach a related record's own title") predates it and is stale. A sibling join object already titles itself this way:sys_user_permission_set.display_title(packages/plugins/plugin-security/src/objects/sys-user-permission-set.object.ts:77), pinned bysys-user-permission-set-person-title.test.ts. I reuse that shape:(isBlank(trim(record.user_id.name)) ? record.user_id.email : record.user_id.name) + ' in ' + record.business_unit_id.namenameis the declared title of bothsys_userandsys_business_unit(nameField: 'name', a stored required text column, so no second hop). The email arm is the platform'sname || emailrule for a person, the same one the sibling uses and the one the console's own embedded-lookup display applies.Cost on writes: none. No stored column, no hook, no extra statement on insert or update of the membership, the user or the unit.
Cost on reads: two batched reads per page (one
sys_user, onesys_business_unit,$inover the page's ids), never one per row, under the reader's own permissions. AfindOneis two extra point reads. Reads that do not projectdisplay_titlepay nothing.Rename reach: total and immediate. Nothing is stamped, so a renamed user or unit shows the new name on the next read everywhere the title is served.
Known limits (already true today, unchanged): the title is not sortable or searchable (ADR-0079 addendum item 9; it is a formula over ids today and was already neither). A user or unit the reader may not read, or one that no longer exists, leaves the title
null(addendum item 2), and the renderer falls totitleFormat.titleFormatanddisplay_titlekeep agreeing.titleFormatstays{user_id} in {business_unit_id}: the console renders a lookup placeholder by the embedded record's display name (name, elseemail), so over an expanded row it renders the same text as the formula.identity-display-title.test.tswill assert that over the row the hop serves.Proceeding to red-first measurement and the change.
Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 26195, "status": "done", "branch": "claude/project-thread-6rh8kd", "pr": "https://github.com/objectstack-ai/objectstack/pull/26300", "session": "session_01KCZSeNY7z5jAXn3EyHdZww", "premise_still_valid": true, "summary": "sys_business_unit_member.display_title now reads the user's name (email where blank) and the unit's name one hop through each lookup: (isBlank(trim(record.user_id.name)) ? record.user_id.email : record.user_id.name) + ' in ' + record.business_unit_id.name. Mechanism named on the card first (comment 6424698289): the existing one-hop formula read (#24197, ADR-0079 addendum 2026-10-18), the same shape sys_user_permission_set already ships; no formula or spec change, no stored column, no hook. Write cost none; read cost one batched sys_user and one batched sys_business_unit read per page; renames reach every read immediately. titleFormat stays {user_id} in {business_unit_id} and agrees with the formula over an expanded row (the console draws a lookup placeholder by name, else email); identity-display-title.test.ts asserts that, and the stale object comment saying a formula cannot reach a related title is rewritten.", "tests": "Live REST red/green (pnpm dev --fresh showcase, seeded admin, POST a membership, then GET /api/v1/data/sys_business_unit_member and GET .../ID): base c4968860 serves display_title '7eLi5ISndhNgrM0zdFvHtlv24DnJBmQK in bu_acme' on both; branch 81074800 serves 'Dev Admin in Acme Corporation' on both; base file restored via git checkout HEAD (blob hash matched, git diff HEAD empty) and rebuilt. New plugin-security/src/business-unit-member-title.test.ts (real ObjectQL + SqlDriver + real sys_user/sys_business_unit/membership): base 7 failed | 1 passed (Received 'simLa1JzYHiOZOF5 in VJG4H1tG-WmtXsHS'), branch 8 passed (8). At 81074800: platform-objects test 96 files / 1361 passed; plugin-security 228 files / 4934 passed / 45 skipped; plugin-sharing 55 / 1233 passed; plugin-approvals 98 / 1276 passed (each exit 0); platform-objects + plugin-security typecheck exit 0 (VERDICT command-exit 0); eslint --no-inline-config on the 3 touched files exit 0. No ablation beyond the base red run (the behaviour change is the formula itself, measured red on base through both the engine test and the REST door).", "gates": "Local at 81074800: dispatch-gates --commands derived 67 families, all 67 exit 0 (check:i18n and check:dual-build-cjs-loads first exit 3 PREREQUISITE NOT MET, re-run exit 0 after a full build; check-plugin-teardown-shape --self-test first exit 3 on the shallow clone, exit 0 after git fetch of its positive-control 621a4876). --ran reconciliation: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. PR #26300 check-runs: in_progress at report time; the seat reads CI.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — no MCP GitHub calls; reads used gh api REST (issue, comments, open-PR file lists)", "api_writes": "3 relay writes + 1 git push — comment #26195 6424698289 (mechanism, post-stamped.mjs), pr_create #26300 draft with assignee leg os-project-manager (fleet-write dispatch.mjs), this os-dev-report comment (post-stamped.mjs); git push -u origin claude/project-thread-6rh8kd", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — file surface: the claim named the object, identity-display-title.test.ts and 'the identity tests'; the engine-level test lives in packages/plugins/plugin-security/src/business-unit-member-title.test.ts because platform-objects has no objectql/driver-sql dependency (beside the sibling sys-user-permission-set-person-title.test.ts). Declared in the PR Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
2026-10-26T20:18Z ·
domain:serverseat 2 · ACCEPT → PR #26300 comment 6426361869 (landing through the queue).Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
2026-10-26T20:37Z ·
domain:serverseat 2 ·os-project-manager·session_0186vfjHHMsRXC9EUT9hTFMo. PR #26300 merged at 2026-10-26T20:36Z by the merge queue as1f40201977onmain(Fixes #26195,Part of #26059; ACCEPT6426361869). The merge closed this card as completed.git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26300-*'git merge-base --is-ancestor 1f40201977 origin/mainGET /repos/objectstack-ai/objectstack/issues/26195completedsys_business_unit_member.display_titlenames the person (their email where the name is blank) and the business unit, read one hop through each lookup under the reader's own authority, in place of the two stored ids. There is no stored column and no hook.Generated by Claude Code