Roles and Tenancy
Definitions
Tenant
An isolated organization within the platform. A tenant represents a single customer, project, or organizational unit.
- Scope: All data belongs to exactly one tenant
- Isolation: Tenants cannot access each other's data
- Identity: Each tenant has a unique ID and display name
Pack
A named collection of requirements bound to a tenant. Packs define what compliance criteria apply to a specific use case.
- Binding: Packs are linked to tenants via pack bindings
- Configuration: Each pack can have different criteria, corpus, and engine settings
- Versioning: Pack configurations are versioned and can be activated per environment
Environment
The deployment stage where configurations are active.
| Environment | Purpose |
|---|---|
| DEV | Development and testing |
| STAGING | Pre-production validation |
| PROD | Production use |
User roles
| Role | Code | Description | Access level |
|---|---|---|---|
| Portal User | CM_USER | Submits evidence and reviews assessments | Portal only (/cm) |
| Admin Viewer | ADMIN_VIEWER | Monitors runs and reviews audit logs | Admin console (read-only) |
| C_AI Engineer | C_AI_ENGINEER | Full platform configuration access | Admin console (full access) |
Legacy terminology mapping
The codebase uses some legacy naming conventions. Here's how they map to current terminology:
| Legacy term | Current term | Code reference |
|---|---|---|
| CM | Portal User | Route /cm, type CM_USER |
| ComplianceManager | Portal User | Component ComplianceManager.tsx |
| CM_USER | Portal User | Session role constant |
| AdminRole | User role | Schema AdminRole constant |
When you see "CM" in routes, API responses, or component names, it refers to Portal Users—the users who submit evidence and review assessments.
Invariants
These conditions must always be true:
-
Every resource belongs to exactly one tenant: Documents, runs, assessments, and all other data are tenant-scoped.
-
Tenant ID is required for all data operations: API requests that modify or retrieve data must include tenant context.
-
Cross-tenant access is forbidden: A user authenticated in Tenant A cannot access Tenant B's data, even via direct API calls.
-
Session establishes tenant context: The admin login flow sets the tenant ID in the session, which persists until logout.
-
Portal Users are restricted to portal routes: Users with
CM_USERrole cannot access/adminroutes. -
Admin Viewers cannot modify data: The
ADMIN_VIEWERrole provides read-only access to monitoring and audit logs.
How it shows up in the UI
Portal interface (/cm)
- Portal Users see only their tenant's data
- The requirements panel shows the pack bound to their tenant
- Uploaded documents are stored in the tenant's document collection
- Assessment runs are scoped to the tenant
Admin login (/admin/login)
- User selects a role (C_AI Engineer or Admin Viewer)
- User selects a tenant from the available list
- Upon login, session is established with role + tenant
- All subsequent admin operations use this context
Admin console (/admin)
- The current tenant is shown in the header
- Switching tenants requires returning to login
- Run History shows only the current tenant's runs
- Audit logs are filtered to the current tenant
Tenant indicator
A badge in the admin interface shows:
- Current tenant name
- Current environment (DEV/STAGING/PROD)
- Current pack (if applicable)
How it shows up in the API
Session endpoint
GET /api/admin/session
Response:
{
"authenticated": true,
"role": "C_AI_ENGINEER",
"tenantId": "tenant-abc-123",
"packId": "project-pack-v1"
}
Tenant-scoped endpoints
All data endpoints require tenant context. The platform resolves tenant from:
- Session (primary): The logged-in user's session contains
tenantId - Query parameter (admin override):
?tenantId=xxx(requires super-admin) - Default fallback: Returns error if no tenant can be resolved
Example request with tenant context:
GET /api/runs
Cookie: session=...
The server extracts tenantId from the session and filters results accordingly.
Cross-tenant protection
Attempting to access another tenant's data returns:
{
"error": "CROSS_TENANT_ACCESS_DENIED",
"message": "Access to requested resource is forbidden"
}
Tenant list (admin only)
GET /api/admin/tenants
Response:
{
"tenants": [
{ "id": "tenant-abc", "name": "Project Alpha" },
{ "id": "tenant-xyz", "name": "Project Beta" }
]
}
Flow: tenant resolution
Request arrives
↓
┌─────────────────────────────┐
│ Check session for tenantId │
└─────────────────────────────┘
↓
Has tenant?
/ \
Yes No
↓ ↓
Use Check query param
session (requires super-admin)
↓ ↓
└──────┬───────┘
↓
Tenant resolved?
/ \
Yes No
↓ ↓
Execute Return 400
query TENANT_REQUIRED
Common misconceptions
1. "I can switch tenants without re-authenticating"
Reality: Tenant context is set at login time. To switch tenants, you must log out and log back in, selecting the new tenant.
2. "Admin Viewer can make changes if they know the API"
Reality: Role enforcement happens at the API layer, not just the UI. ADMIN_VIEWER requests to write endpoints are rejected.
3. "Portal Users can access the admin console if they know the URL"
Reality: The AdminAuthGuard component checks both authentication and role. CM_USER role is explicitly denied access to /admin routes.
4. "Tenants share the same requirements"
Reality: While the same pack definition might be used by multiple tenants, each tenant has its own binding. Criteria can be customized per tenant.
5. "Environment affects tenant isolation"
Reality: Environments (DEV/STAGING/PROD) affect configuration activation, not tenant isolation. A tenant's data remains isolated across all environments.
Related topics
- Runs, Snapshots, and Replay - How runs are scoped to tenants
- Evidence, Requirements, and Assessments - Tenant-scoped data model
- Admin Workflow - Managing tenant configuration
- Portal User Workflow - Working within a tenant
- API Reference - Authentication and tenant endpoints