Authentication & multi-tenancy
Identity, roles, organization scoping, the operator vs GP contexts, plan gating, and the tenant lifecycle.
Authentication is built on better-auth (@repo/auth) with a Drizzle adapter. The same auth
layer provides organizations, memberships, and the operator capabilities the Command Center relies
on.
Identity & sessions
A signed-in user has a session row carrying a userId, an activeOrganizationId (the tenant the
session is currently acting within), and optionally an impersonatedBy value for operator support
sessions. On sign-in, activeOrganizationId is hydrated from the user's lastActiveOrganizationId
so a returning user lands back in their organization.
Supported methods are email + password, magic links, passkeys, and two-factor; no third-party identity providers are offered. Self-serve signup is disabled. The platform is invitation-only, so new users arrive through an organization invitation.
Two kinds of role
The platform separates a global role from an organization role:
| Role | Where | Meaning |
|---|---|---|
user.role = 'admin' | Global (on the user) | Platform operator. Unlocks the Command Center and all cross-tenant procedures. |
member.role | Per organization | owner, admin, or member within that organization. Gates fund-level actions like inviting members or managing policies. |
A helper treats a user as an organization admin if they are an owner/admin member or a
global platform admin.
Operator context vs GP workspace
| Operator (Command Center) | GP user (workspace) | |
|---|---|---|
| Identifying role | Global admin | Organization member |
| Active organization | Org-agnostic; acts across tenants | Always scoped to one organization |
| Routes | /operator/*, /admin/* | /{organizationSlug}/* |
| Data reach | All tenants, via audited procedures | Only their organization's data |
Operator routes are guarded in the layout: a non-admin visiting /operator is redirected away.
Admin/operator access additionally requires two-factor authentication to be enabled on the
account (enforced by default). A platform admin who hasn't set up 2FA is redirected to
/admin-2fa-required instead of reaching /admin or /operator.
Organization scoping
Tenant isolation is enforced in the API, not left to the UI. Org-scoped procedures resolve an
acting organization from the session (or, for operators, from an explicit context) and reject
the call if the caller is not a member. Knowledge queries additionally filter on tenantScope and
organizationId, so one tenant can never retrieve another tenant's documents.
Global knowledge (the operator-managed library) is readable by every tenant by design. It is the shared playbook. Organization-scoped knowledge is visible only within its own organization.
Plan-based feature gating
Each organization has a plan tier that gates features and sets usage limits:
| Plan | Advisor | Policy Hub | LP Reporting | Monthly run cap |
|---|---|---|---|---|
bot_only | ✔ (global playbook only) | — | — | 500 |
policy_hub | ✔ (org + global) | ✔ | — | 2,000 |
full | ✔ (org + global) | ✔ | ✔ (flag also required) | 10,000 |
LP Reporting is gated twice: the full plan and the per-organization lp_reporting feature
flag. Both the server (requireReportingPlan) and the navigation model check both, so a tenant with
only one of the two never sees reporting routes it cannot use.
| Flag | Controls |
|---|---|
lp_reporting | The LP Reporting route group and its nav items |
advisor_hub | Advisor hub surfaces, including operator Retrieval Preview |
advisor_guidance | Folder Registry and Advisor Guide |
Flags are resolved per organization and fail closed — an unset flag is off.
Gating is enforced in two places: API middleware (a plan backstop and a usage-cap check that runs
before any metered AI call) and the workspace navigation, which only shows features the plan
includes. A request that exceeds the monthly run cap is rejected before it spends; a request that
cannot resolve an organization is rejected as well. The effective cap is the explicit
usageCounter.runsCap if set, then the operator's per-organization allowance
(organization.aiRunsCapOverride, set on the organization's Usage tab), otherwise the plan default.
Tenant lifecycle
An organization is active, inactive (no members), or suspended:
- Suspend sets
organization.suspendedAt. The workspace layout reads this and blocks entry for that tenant's users. - Reactivate clears
suspendedAt.
Both actions are operator-only and recorded in the audit log. See the runbook on plans & lifecycle.
Support sessions (join as support)
Operators can open a support session to view the product as a specific user inside a tenant. The
flow records support.impersonation_started in the audit log (with the operator's identity and an
optional reason), sets session.impersonatedBy, and acts as the target user until the operator ends
the session, which records support.impersonation_ended. The original operator is always the
recorded actor, so impersonated actions remain attributable.