From c5c14bc62693271dedea3ff9c78eff86ae0f0ada Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 11:11:41 +0000 Subject: [PATCH 1/5] fix(platform-objects): sys_session.active_team_id derives no Sessions list on a team Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NftZtMY2EJSyAjctCSfkis --- .../src/identity/sys-session.object.ts | 2 + .../sys-team-derived-related-lists.test.ts | 92 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts diff --git a/packages/platform-objects/src/identity/sys-session.object.ts b/packages/platform-objects/src/identity/sys-session.object.ts index 804084ba101..adf2c552a66 100644 --- a/packages/platform-objects/src/identity/sys-session.object.ts +++ b/packages/platform-objects/src/identity/sys-session.object.ts @@ -234,6 +234,8 @@ export const SysSession = ObjectSchema.create({ label: 'Active Team', required: false, group: 'Context', + // A team record derives no Sessions list: its only header action signs out the caller's own devices. + relatedList: false, }), // ── Client fingerprint ─────────────────────────────────────── diff --git a/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts b/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts new file mode 100644 index 00000000000..3699e132910 --- /dev/null +++ b/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts @@ -0,0 +1,92 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * A team record's page lists no Sessions; a user's page still does. + * + * `sys_team` declares no record page, so its related lists are DERIVED: every + * `lookup` / `master_detail` field whose `reference` is `sys_team` becomes a + * related list on the team's detail page unless the field sets + * `relatedList: false` (`FieldSchema.relatedList`; the reader is objectui's + * `deriveRelatedLists`, cited in `packages/spec/liveness/field.json`). + * + * `sys_session.active_team_id` used to derive one. That list carried the + * session object's only `list_toolbar` action, "Sign out other devices", + * which revokes the CALLER's own other sessions — on a team record it read as + * a team action and was not one. The field, its lookup and its label stay + * (better-auth writes `activeTeamId` into it); only the derived list goes. + * + * The user's own Sessions list is a curated tab on `SysUserDetailPage`, keyed + * on `sys_session.user_id`, and is where that action belongs: pinned unchanged. + */ + +import { describe, expect, it } from 'vitest'; +import * as PlatformObjects from '../index.js'; +import { SysSession } from './sys-session.object.js'; +import { SysUserDetailPage } from '../pages/sys-user.page.js'; + +type FieldDef = { type?: string; reference?: string; relatedList?: boolean | 'primary'; label?: string }; +type ActionDef = { name?: string; locations?: string[] }; +type ObjectDef = { name?: string; fields?: Record; actions?: ActionDef[] }; + +const CATALOGUE: ObjectDef[] = [ + ...new Set( + Object.values(PlatformObjects).filter( + (v): v is ObjectDef => + !!v && typeof v === 'object' && typeof (v as ObjectDef).name === 'string' && !!(v as ObjectDef).fields, + ), + ), +]; + +/** `.` for every relationship field that derives a related list on `parent`. */ +function derivedRelatedLists(parent: string): string[] { + const out: string[] = []; + for (const obj of CATALOGUE) { + for (const [name, field] of Object.entries(obj.fields ?? {})) { + if (field.type !== 'lookup' && field.type !== 'master_detail') continue; + if (field.reference !== parent || field.relatedList === false) continue; + out.push(`${obj.name}.${name}`); + } + } + return out.sort(); +} + +describe('sys_team derived related lists (Setup → Teams → a team → Related)', () => { + it('exclude sys_session', () => { + const lists = derivedRelatedLists('sys_team'); + expect(lists.filter((l) => l.startsWith('sys_session.'))).toEqual([]); + // Guard against over-applying: the team's members are still listed. + expect(lists).toContain('sys_team_member.team_id'); + }); + + it('keep sys_session.active_team_id as a lookup to sys_team, suppressed only as a related list', () => { + const field = SysSession.fields.active_team_id as FieldDef; + expect(field.type).toBe('lookup'); + expect(field.reference).toBe('sys_team'); + expect(field.label).toBe('Active Team'); + expect(field.relatedList).toBe(false); + }); +}); + +describe("the user's own Sessions related list is unchanged", () => { + it('SysUserDetailPage lists sys_session by user_id', () => { + const lists: Array<{ objectName?: string; relationshipField?: string }> = []; + const walk = (node: unknown): void => { + if (Array.isArray(node)) return node.forEach(walk); + if (!node || typeof node !== 'object') return; + const n = node as { type?: string; properties?: { objectName?: string; relationshipField?: string } }; + if (n.type === 'record:related_list' && n.properties) lists.push(n.properties); + Object.values(node).forEach(walk); + }; + walk(SysUserDetailPage); + expect(lists).toContainEqual(expect.objectContaining({ objectName: 'sys_session', relationshipField: 'user_id' })); + }); + + it('sys_session.user_id still derives a related list on sys_user', () => { + expect(derivedRelatedLists('sys_user')).toContain('sys_session.user_id'); + }); + + it('"Sign out other devices" stays the session list toolbar action', () => { + const action = (SysSession.actions as ActionDef[]).find((a) => a.name === 'revoke_my_other_sessions'); + expect(action?.locations).toEqual(['list_toolbar']); + }); +}); From 79262d748dd1d9af9ab54b3d5f6251ec39f46886 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 11:15:06 +0000 Subject: [PATCH 2/5] chore(changeset): platform-objects patch for the team Sessions related list Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NftZtMY2EJSyAjctCSfkis --- .changeset/24765-team-sessions-related-list.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/24765-team-sessions-related-list.md diff --git a/.changeset/24765-team-sessions-related-list.md b/.changeset/24765-team-sessions-related-list.md new file mode 100644 index 00000000000..9473bcc30a9 --- /dev/null +++ b/.changeset/24765-team-sessions-related-list.md @@ -0,0 +1,15 @@ +--- +'@objectstack/platform-objects': patch +--- + +fix(platform-objects): a team record no longer lists Sessions with "Sign out other devices" (#24765) + +Clause-②: no (narrowing) + +**BREAKING** for a reader of a team's derived related lists only: Setup → Teams → a team → Related no longer lists *Sessions*. Shipped as `patch` under the launch-window convention. + +- **Before.** `sys_session.active_team_id` is a lookup to `sys_team` that did not set `relatedList: false`, so every team record derived a Sessions related list. That list carried the session list's only toolbar action, "Sign out other devices", whose confirmation reads "Sign out of every other device where you're currently logged in?". The action signs out the administrator's own other sessions, not the team's. +- **Now.** `active_team_id` sets `relatedList: false`, so a team record derives no Sessions list. A team's Members and Invitations lists are unchanged. +- **Unchanged.** The `active_team_id` field, its lookup to `sys_team`, its label and its column: better-auth still writes `activeTeamId` there. A user record's Sessions tab (keyed on `user_id`), where "Sign out other devices" belongs, and the action itself. + +**What to do.** Nothing. No team-scoped session action existed to lose. From b2902cd623c832faa65f25f1df756716edb0957b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 11:15:17 +0000 Subject: [PATCH 3/5] chore(changeset): state the ADR-0087 disposition Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NftZtMY2EJSyAjctCSfkis --- .changeset/24765-team-sessions-related-list.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/24765-team-sessions-related-list.md b/.changeset/24765-team-sessions-related-list.md index 9473bcc30a9..7c827719557 100644 --- a/.changeset/24765-team-sessions-related-list.md +++ b/.changeset/24765-team-sessions-related-list.md @@ -6,6 +6,8 @@ fix(platform-objects): a team record no longer lists Sessions with "Sign out oth Clause-②: no (narrowing) + + **BREAKING** for a reader of a team's derived related lists only: Setup → Teams → a team → Related no longer lists *Sessions*. Shipped as `patch` under the launch-window convention. - **Before.** `sys_session.active_team_id` is a lookup to `sys_team` that did not set `relatedList: false`, so every team record derived a Sessions related list. That list carried the session list's only toolbar action, "Sign out other devices", whose confirmation reads "Sign out of every other device where you're currently logged in?". The action signs out the administrator's own other sessions, not the team's. From 1e3f35f9a52cd532e8da73d0b0df8c4cab3549dc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 11:17:13 +0000 Subject: [PATCH 4/5] test(platform-objects): type the derived-related-list catalogue through unknown Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NftZtMY2EJSyAjctCSfkis --- .../src/identity/sys-team-derived-related-lists.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts b/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts index 3699e132910..92daaa4c980 100644 --- a/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts +++ b/packages/platform-objects/src/identity/sys-team-derived-related-lists.test.ts @@ -30,7 +30,7 @@ type ObjectDef = { name?: string; fields?: Record; actions?: A const CATALOGUE: ObjectDef[] = [ ...new Set( - Object.values(PlatformObjects).filter( + (Object.values(PlatformObjects) as unknown[]).filter( (v): v is ObjectDef => !!v && typeof v === 'object' && typeof (v as ObjectDef).name === 'string' && !!(v as ObjectDef).fields, ), From ae89b5720b605d3f79c107f6c1d689c081e6ce3d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 12:43:19 +0000 Subject: [PATCH 5/5] chore(changeset): grade the platform-objects narrowing minor Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NftZtMY2EJSyAjctCSfkis --- .changeset/24765-team-sessions-related-list.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/24765-team-sessions-related-list.md b/.changeset/24765-team-sessions-related-list.md index 7c827719557..75b1a2bbcd6 100644 --- a/.changeset/24765-team-sessions-related-list.md +++ b/.changeset/24765-team-sessions-related-list.md @@ -1,5 +1,5 @@ --- -'@objectstack/platform-objects': patch +'@objectstack/platform-objects': minor --- fix(platform-objects): a team record no longer lists Sessions with "Sign out other devices" (#24765) @@ -8,7 +8,7 @@ Clause-②: no (narrowing) -**BREAKING** for a reader of a team's derived related lists only: Setup → Teams → a team → Related no longer lists *Sessions*. Shipped as `patch` under the launch-window convention. +**BREAKING** for a reader of a team's derived related lists only: Setup → Teams → a team → Related no longer lists *Sessions*. Shipped as `minor` under the launch-window convention for accept-set narrowings. - **Before.** `sys_session.active_team_id` is a lookup to `sys_team` that did not set `relatedList: false`, so every team record derived a Sessions related list. That list carried the session list's only toolbar action, "Sign out other devices", whose confirmation reads "Sign out of every other device where you're currently logged in?". The action signs out the administrator's own other sessions, not the team's. - **Now.** `active_team_id` sets `relatedList: false`, so a team record derives no Sessions list. A team's Members and Invitations lists are unchanged.