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, 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.