Managing members — roles, remove, leave, transfer

Per-row role dropdown + Remove on /members for owners and admins. Members can leave on their own; owners can hand off the keys via Make-owner.

Updated 2026-05-26

Open /members. Each row carries a role dropdown (owner / admin / contributor / viewer / auditor), a Remove button (admins only), a Make-owner button (visible to current owners on non-owner rows), and a Leave button on your own row. Self-service Leave is also available on /settings/account.

**Last-seen tracking.** Each member row shows when they last signed in: "last seen today", "last seen 3d ago", "last seen 2mo ago", or an ISO date for older signs. "last seen never" surfaces for accounts that haven't signed in since the indicator was deployed. Useful for offboarding decisions ("this admin hasn't logged in for 6 months — deactivate?") and SOC 2 user-activity reviews. The timestamp is updated on every Auth.js session refresh, so it stays current within seconds of activity. The audit log is the canonical source for precise sign-in timestamps; the /members list is the triage surface.

**Inactive-user filter (admin only).** Filter chips at the top of the members section toggle between "All N members" and "Inactive 90+ days (M)". The threshold is adjustable (1-365 days, default 90) for workspaces with longer activity cycles. Inactive = no recorded sign-in OR last sign-in older than the threshold. Each inactive member row keeps the existing Remove button + role-change dropdown — triage and offboarding happen in one place. Useful for SOC 2 quarterly access reviews and post-engagement client cleanup.

Role changes:

- Pick the new role from the dropdown, click Save. Audit log records a permission.granted event with kind=role-changed. - Refuses last-owner demotion — every workspace must always have at least one owner. - Refuses self-demotion from owner unless another owner exists.

Removing a member:

- Click Remove on the target row. The user isn't soft-deleted (which would orphan their externalId and prevent re-sign-in). Instead, they're MOVED into a fresh personal tenant where they're the owner of their own solo workspace — they keep their account and audit history but no longer see this workspace's data. - Refuses last-owner removal. - The removed user's stale JWT continues to say "I'm in this workspace" until the next page load. The (app) layout's tenant-mismatch check redirects them to /sign-in?moved=1 with a friendly banner ("your workspace assignment changed") so the next sign-in carries the correct tenantId.

Leaving a workspace yourself:

- Click Leave on /members (your own row) or click "Leave workspace" on /settings/account. Same plumbing as remove — you're moved into a fresh personal tenant. - Refuses if you're the last owner. The path: transfer ownership to another member first → leave normally.

Transferring ownership:

- On /members, click "Make owner" next to a non-owner row (visible to current owners only). The target is promoted to owner; you stay an owner too. Two owners means either can leave or demote without orphaning the workspace. - After transfer, optionally demote yourself from the role dropdown, or leave directly.

All of these are recorded on the user/<id> stream of the audit log so the trail of "who became what when, by whom" is queryable end-to-end. The events emit on the workspace's audit chain, not the moved user's new tenant — the original workspace is the system of record for the change.