You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Identity join objects' record titles still splice raw lookup ids (sys_member, sys_two_factor, SCIM) #30454
Four identity join objects build their display_title record title (their nameField) by concatenating stored lookup ids. A record page H1, a related-list Title column or a reference to any of these rows therefore shows raw ids instead of names:
sys_business_unit_member and sys_team_member (#30421, PR #30452) read their names one hop through the lookup, as allowed by the ADR-0079 addendum of 2026-10-18. Both use the form (isBlank(trim(record.user_id.name)) ? record.user_id.email : record.user_id.name) and are read under the reader's own authority, so an unreadable target gives a null title.
Asks
Rewrite each title to read names one hop through its lookups. For the SCIM objects, the dev should check which field on the target names it.
Claim: PM loop round 1
Session: session_01Utp6CYVV6fcAQrNTDQRvPb (seat) · dev = project card thread session_01Utp6CZdTcFgv9MUk1tpKUo
Account: os-bill
Branch: claude/project-thread-0kalts (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-30454
Domain: domain:server
Seat: domain:server#5
File surface: packages/platform-objects/src/** (object declarations, tests, pins) and the generated bundles its build rewrites. packages/qa/dogfood/** tests only, where a pin draws these titles. content/docs/** only for sentences this PR makes false (not releases/**). One changeset. ⛔ packages/spec/**, ui/**, skills/**, docs/adr/**, .claude/**, AGENTS.md (stop on breach and explain in the report)
Container & model: S, mode:thread, model: default tier (opus), effort: high (p3)
Clause-②: no (provisional) — the shipped declarations of platform objects change; no exported member or accepted value changes
Metadata-protocol: none (provisional) — shipped platform metadata, not what an author writes
Responsibility: sys_member, sys_two_factor, sys_scim_group_member and sys_scim_projection_grant compose display_title from stored lookup ids (read on origin/main, card body), so a record H1, a Title column or a reference reads raw ids | none: the title names nobody | every administrator reading these rows in Setup
Thread-read: none
Premise: holds per the card's reading on main. Route ruled by the seat: one-hop names as in sys_business_unit_member / sys_team_member (ADR-0079 addendum 2026-10-18)
Serial constraints cleared: Serial behind PR #30452 (#30421), which edits identity-display-title.test.ts. The dev starts from an origin/main that contains it, or writes the red pins first and merges main once it lands.
Order: project file server-seat5/dispatch-30454.md, delivered to the card thread.
The dev measured this at head 671545ad. Reading sys_member's new one-hop title raises the auth prelude's sys_user reads from 2 to 4 per request. The cause is the three unprojected tryFind('sys_member', …) reads in packages/core/src/security/resolve-authz-context.ts. auth-prelude-query-count.pin.test.ts goes red, 5 of 7.
Seat ruling A, which amends the Claim above:
File surface also covers packages/core/src/security/resolve-authz-context.ts, limited to the sys_member reads, and packages/plugin-auth/src/auth-prelude-query-count.pin.test.ts.
The three sys_member reads project only the columns their callers read, as the existing GRANT_ROW_COLUMNS does for sys_user_permission_set. The dev measures the column set from each caller. The prelude stays at its pinned count, and the pin gains a case asserting that the title's hop adds no prelude read.
{
"issue": 30454,
"status": "done",
"branch": "claude/project-thread-0kalts",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30520",
"head": "2d857e27",
"session": "session_01Utp6CZdTcFgv9MUk1tpKUo (card thread; seat session_01Utp6CYVV6fcAQrNTDQRvPb)",
"premise_still_valid": true,
"summary": "Four identity join objects' display_title now reads names one hop through their lookups (ADR-0079 addendum 2026-10-18), in the sys_business_unit_member / sys_team_member form: sys_member 'NAME (role)' (role-less: NAME), sys_two_factor 'Two-factor for NAME', sys_scim_projection_grant 'role → NAME' (person = name, else email), sys_scim_group_member 'sys_scim_user.user_name in sys_scim_group.display_name' (each its target's nameField, required text). Measured: sys_member's hop raised the auth prelude's sys_user reads 2 -> 4 per request (auth-prelude-query-count.pin 5/7 red) because resolve-authz-context.ts read sys_member unprojected three times; per seat ruling A (Claim amendment 6658119458) those reads now name the columns their callers read (MEMBER_ROW_COLUMNS, ORG_MEMBER_PAGE_COLUMNS, MEMBER_HOLDER_ROW_COLUMNS), as GRANT_ROW_COLUMNS does, and the prelude stays at 8 reads / 4 round trips. No relatedListColumns added: no Setup page derives a related list from these lookups.",
"tests": "red (3bb8bb06, new pins over main's objects): identity-display-title 16 failed | 25 passed (41); green (671545ad) 41/41. Authz BEFORE projection (main's resolve-authz-context.ts over the tree, core rebuilt, dist-preflight --absent ok): core src/security 2 failed | 1211 passed (1213), prelude pin 6 failed | 2 passed (8); restored blob == HEAD. AFTER (5b2ba4ec, rebuilt, preflight present): core src/security 1213/1213, pin 8/8. Ablation 1 (MEMBER_ROW_COLUMNS minus 'role'): grant-projection 27 failed | 8 passed (35); ablation 2 (sys_member hop -> id splice): identity-display-title 5 failed | 36 passed (41); both restored, blob == HEAD. Real-engine one-off (ObjectQL + driver-sql over dist, 671545ad): all checks hold (titles on write/findOne/projected find, blank-name email, rename, reader without access -> null with no leak, missing target -> null, no stored column, hop cost [1,0] unprojected vs projected). After merging main f506b79d85 (with #30452) and a whole-workspace build (turbo 119/119), head 2d857e27: platform-objects 1537/1537, core 3099/3099, plugin-auth 3975 passed | 10 skipped, plugin-security 5357 passed | 45 skipped, plugin-sharing 1405/1405, plugin-approvals 1469/1469; typecheck platform-objects/core/plugin-auth exit 0; eslint on the eight changed .ts files exit 0.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 2d857e27: 64 derived; --ran: 64 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0 (check:dual-build-cjs-loads, lean-entry-closure, dts-closure after the full build; plugin-teardown --self-test after fetching its control commit). Roster gates flagged for touched directories, also run: check:auth-mount-ledger, check:changeset-fixed, check:error-code-casing, check:filter-alias-parity, all exit 0. CI check-runs: in_progress at report time.",
"line_budget": "9 files, +325 / -45 (src: core +29/-4, four objects +43/-23; tests +235/-18; changeset +18)",
"files_changed": [
".changeset/30454-identity-join-titles-name-people.md",
"packages/core/src/security/resolve-authz-context.ts",
"packages/core/src/security/resolve-authz-context.grant-projection.test.ts",
"packages/platform-objects/src/identity/identity-display-title.test.ts",
"packages/platform-objects/src/identity/sys-member.object.ts",
"packages/platform-objects/src/identity/sys-scim-group-member.object.ts",
"packages/platform-objects/src/identity/sys-scim-projection-grant.object.ts",
"packages/platform-objects/src/identity/sys-two-factor.object.ts",
"packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts"
],
"deviations": [
"File surface widened by seat ruling A (Claim amendment 6658119458): packages/core/src/security/resolve-authz-context.ts (sys_member reads only; L515-660 untouched) and plugin-auth auth-prelude-query-count.pin.test.ts.",
"File surface widened by seat confirmation in the seat thread (12:45Z, no separate amendment): packages/core/src/security/resolve-authz-context.grant-projection.test.ts. Its first case asserted no prelude read but the grant read carries fields, which the projection makes false; it is the resolver's shared projection harness, so the new membership pins went there as a top-level describe (ruling 6583487846).",
"MEMBER_ROW_COLUMNS names valid_from and valid_until although sys_member declares neither: measured on the real engine, a projection naming an undeclared column leaves it out silently (no error), so they are listed in advance to keep §3's 'correct the moment they do' promise; omitting them would drop the window the moment it is declared.",
"@objectstack/core added to the changeset (patch) beside @objectstack/platform-objects.",
"The harness's assign/review step after pr_create was skipped per the order (no MCP write; the seat hands off)."
],
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "8 — all reads: issue_read x4 (get 1, get_comments 3), pull_request_read x4 (get 3, get_files 1); no write tool",
"api_writes": "2 — relay pr_create (#30520, read-back identical, no assignees) and this os-dev-report comment via post-stamped; git pushes are not REST writes",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat — other sys_member readers that name no columns (git grep lists plugin-auth, plugin-sharing, plugin-approvals, service-messaging and others) now pay one batched sys_user read per find for the title's hop (measured [1,0]); the per-request prelude read is fixed here, the rest were not swept · noted, not filed",
"carrier: seat — sys_scim_subject keeps nameField 'user_id' (a bare lookup pointer, outside this card's four objects) · noted, not filed"
]
}
PR #30520 (head 2d857e278): the seat record is posted (ACCEPT), the PR is ready, and auto-merge is armed · 2026-11-05T14:06Z. I will post the landing comment after it merges.
PR #30520 merged as e3948ae19 at 2026-11-05T15:54Z.
sys_member, sys_two_factor, sys_scim_group_member and sys_scim_projection_grant titles now read one hop through their lookups: the person's name (email where the name is blank), the SCIM user name and the group's display name, instead of raw ids. Same pattern as the business-unit and team member rows.
Per ruling A on this card, the authorization resolver's three sys_member reads now project only the columns they decide on, so the auth prelude stays at the pinned 8 reads / 4 round trips.
Activity
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Claim: PM loop round 1
Session:
session_01Utp6CYVV6fcAQrNTDQRvPb(seat) · dev = project card threadsession_01Utp6CZdTcFgv9MUk1tpKUoAccount:
os-billBranch:
claude/project-thread-0kalts(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-30454Domain:
domain:serverSeat:
domain:server#5File surface:
packages/platform-objects/src/**(object declarations, tests, pins) and the generated bundles its build rewrites.packages/qa/dogfood/**tests only, where a pin draws these titles.content/docs/**only for sentences this PR makes false (notreleases/**). One changeset. ⛔packages/spec/**,ui/**,skills/**,docs/adr/**,.claude/**,AGENTS.md(stop on breach and explain in the report)Container & model:
S,mode:thread,model: default tier (opus),effort: high(p3)Clause-②: no (provisional) — the shipped declarations of platform objects change; no exported member or accepted value changes
Metadata-protocol: none (provisional) — shipped platform metadata, not what an author writes
Responsibility:
sys_member, sys_two_factor, sys_scim_group_member and sys_scim_projection_grant compose display_title from stored lookup ids (read on origin/main, card body), so a record H1, a Title column or a reference reads raw ids | none: the title names nobody | every administrator reading these rows in SetupThread-read: none
Premise: holds per the card's reading on main. Route ruled by the seat: one-hop names as in sys_business_unit_member / sys_team_member (ADR-0079 addendum 2026-10-18)
Serial constraints cleared:
Serial behind PR #30452 (#30421), which edits identity-display-title.test.ts. The dev starts from an origin/main that contains it, or writes the red pins first and merges main once it lands.Order: project file
server-seat5/dispatch-30454.md, delivered to the card thread.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Claim amendment · 2026-11-05T12:34Z
The dev measured this at head
671545ad. Readingsys_member's new one-hop title raises the auth prelude'ssys_userreads from 2 to 4 per request. The cause is the three unprojectedtryFind('sys_member', …)reads inpackages/core/src/security/resolve-authz-context.ts.auth-prelude-query-count.pin.test.tsgoes red, 5 of 7.Seat ruling A, which amends the Claim above:
packages/core/src/security/resolve-authz-context.ts, limited to thesys_memberreads, andpackages/plugin-auth/src/auth-prelude-query-count.pin.test.ts.sys_memberreads project only the columns their callers read, as the existingGRANT_ROW_COLUMNSdoes forsys_user_permission_set. The dev measures the column set from each caller. The prelude stays at its pinned count, and the pin gains a case asserting that the title's hop adds no prelude read.origin/main.Clause-②: nostill holds, since internal read projection changes no exported member.objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:server· claimed · label fixed · 2026-11-05T13:03Z⛔ Not a claim.
display_titlefrom raw lookup ids. It is claimed bydomain:server#5(6657763266).p3label, which the selection order does not read. Triage replaced it withpriority:p3.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
os-dev-report
Card thread
session_01Utp6CZdTcFgv9MUk1tpKUo· branchclaude/project-thread-0kalts· draft PR #30520 · head2d857e27{ "issue": 30454, "status": "done", "branch": "claude/project-thread-0kalts", "pr": "https://github.com/objectstack-ai/objectstack/pull/30520", "head": "2d857e27", "session": "session_01Utp6CZdTcFgv9MUk1tpKUo (card thread; seat session_01Utp6CYVV6fcAQrNTDQRvPb)", "premise_still_valid": true, "summary": "Four identity join objects' display_title now reads names one hop through their lookups (ADR-0079 addendum 2026-10-18), in the sys_business_unit_member / sys_team_member form: sys_member 'NAME (role)' (role-less: NAME), sys_two_factor 'Two-factor for NAME', sys_scim_projection_grant 'role → NAME' (person = name, else email), sys_scim_group_member 'sys_scim_user.user_name in sys_scim_group.display_name' (each its target's nameField, required text). Measured: sys_member's hop raised the auth prelude's sys_user reads 2 -> 4 per request (auth-prelude-query-count.pin 5/7 red) because resolve-authz-context.ts read sys_member unprojected three times; per seat ruling A (Claim amendment 6658119458) those reads now name the columns their callers read (MEMBER_ROW_COLUMNS, ORG_MEMBER_PAGE_COLUMNS, MEMBER_HOLDER_ROW_COLUMNS), as GRANT_ROW_COLUMNS does, and the prelude stays at 8 reads / 4 round trips. No relatedListColumns added: no Setup page derives a related list from these lookups.", "tests": "red (3bb8bb06, new pins over main's objects): identity-display-title 16 failed | 25 passed (41); green (671545ad) 41/41. Authz BEFORE projection (main's resolve-authz-context.ts over the tree, core rebuilt, dist-preflight --absent ok): core src/security 2 failed | 1211 passed (1213), prelude pin 6 failed | 2 passed (8); restored blob == HEAD. AFTER (5b2ba4ec, rebuilt, preflight present): core src/security 1213/1213, pin 8/8. Ablation 1 (MEMBER_ROW_COLUMNS minus 'role'): grant-projection 27 failed | 8 passed (35); ablation 2 (sys_member hop -> id splice): identity-display-title 5 failed | 36 passed (41); both restored, blob == HEAD. Real-engine one-off (ObjectQL + driver-sql over dist, 671545ad): all checks hold (titles on write/findOne/projected find, blank-name email, rename, reader without access -> null with no leak, missing target -> null, no stored column, hop cost [1,0] unprojected vs projected). After merging main f506b79d85 (with #30452) and a whole-workspace build (turbo 119/119), head 2d857e27: platform-objects 1537/1537, core 3099/3099, plugin-auth 3975 passed | 10 skipped, plugin-security 5357 passed | 45 skipped, plugin-sharing 1405/1405, plugin-approvals 1469/1469; typecheck platform-objects/core/plugin-auth exit 0; eslint on the eight changed .ts files exit 0.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 2d857e27: 64 derived; --ran: 64 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0 (check:dual-build-cjs-loads, lean-entry-closure, dts-closure after the full build; plugin-teardown --self-test after fetching its control commit). Roster gates flagged for touched directories, also run: check:auth-mount-ledger, check:changeset-fixed, check:error-code-casing, check:filter-alias-parity, all exit 0. CI check-runs: in_progress at report time.", "line_budget": "9 files, +325 / -45 (src: core +29/-4, four objects +43/-23; tests +235/-18; changeset +18)", "files_changed": [ ".changeset/30454-identity-join-titles-name-people.md", "packages/core/src/security/resolve-authz-context.ts", "packages/core/src/security/resolve-authz-context.grant-projection.test.ts", "packages/platform-objects/src/identity/identity-display-title.test.ts", "packages/platform-objects/src/identity/sys-member.object.ts", "packages/platform-objects/src/identity/sys-scim-group-member.object.ts", "packages/platform-objects/src/identity/sys-scim-projection-grant.object.ts", "packages/platform-objects/src/identity/sys-two-factor.object.ts", "packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts" ], "deviations": [ "File surface widened by seat ruling A (Claim amendment 6658119458): packages/core/src/security/resolve-authz-context.ts (sys_member reads only; L515-660 untouched) and plugin-auth auth-prelude-query-count.pin.test.ts.", "File surface widened by seat confirmation in the seat thread (12:45Z, no separate amendment): packages/core/src/security/resolve-authz-context.grant-projection.test.ts. Its first case asserted no prelude read but the grant read carries fields, which the projection makes false; it is the resolver's shared projection harness, so the new membership pins went there as a top-level describe (ruling 6583487846).", "MEMBER_ROW_COLUMNS names valid_from and valid_until although sys_member declares neither: measured on the real engine, a projection naming an undeclared column leaves it out silently (no error), so they are listed in advance to keep §3's 'correct the moment they do' promise; omitting them would drop the window the moment it is declared.", "@objectstack/core added to the changeset (patch) beside @objectstack/platform-objects.", "The harness's assign/review step after pr_create was skipped per the order (no MCP write; the seat hands off)." ], "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "8 — all reads: issue_read x4 (get 1, get_comments 3), pull_request_read x4 (get 3, get_files 1); no write tool", "api_writes": "2 — relay pr_create (#30520, read-back identical, no assignees) and this os-dev-report comment via post-stamped; git pushes are not REST writes", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat — other sys_member readers that name no columns (git grep lists plugin-auth, plugin-sharing, plugin-approvals, service-messaging and others) now pay one batched sys_user read per find for the title's hop (measured [1,0]); the per-request prelude read is fixed here, the rest were not swept · noted, not filed", "carrier: seat — sys_scim_subject keeps nameField 'user_id' (a bare lookup pointer, outside this card's four objects) · noted, not filed" ] }Report taken 2026-11-05T14:02Z.
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
PR #30520 (head
2d857e278): the seat record is posted (ACCEPT), the PR is ready, and auto-merge is armed · 2026-11-05T14:06Z. I will post the landing comment after it merges.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Landed · 2026-11-05T15:54Z
PR #30520 merged as
e3948ae19at 2026-11-05T15:54Z.sys_member,sys_two_factor,sys_scim_group_memberandsys_scim_projection_granttitles now read one hop through their lookups: the person's name (email where the name is blank), the SCIM user name and the group's display name, instead of raw ids. Same pattern as the business-unit and team member rows.sys_memberreads now project only the columns they decide on, so the auth prelude stays at the pinned 8 reads / 4 round trips.Follow-ups: none.