> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brainstormer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# RBAC System

> Two-tier Role-Based Access Control: platform roles, organization roles, group permissions, resource-level access, and middleware implementation.

## Overview

The RBAC system operates at two tiers -- **platform level** (superadmin) and **organization level** (org roles, groups, resource permissions) -- and is enforced through shared middleware across all services.

<Warning>
  **Non-Negotiable Principle:** Any new route, feature, or resource MUST declare and enforce its permission requirements. If a PR introduces a new endpoint without permission declaration and middleware enforcement, it is incomplete and must not ship.
</Warning>

## Two-Tier Hierarchy

```mermaid theme={null}
graph TB
    subgraph "Platform Tier"
        SA["superadmin -> all access"]
        U["user -> org only"]
    end

    subgraph "Organization Tier"
        OW["owner -> full org control"]
        AD["admin -> manage org (no billing)"]
        ME["member -> use permitted resources"]
        VI["viewer -> read-only"]
    end

    SA -->|overrides| OW
    U --> OW

    OW --> RP
    AD --> RP
    ME --> RP
    VI --> RP

    subgraph "Resource Permissions"
        RP["org_groups + resource_perms<br/>Per-agent, per-KB, per-user grants"]
    end
```

## Permission Resolution Order

When checking "Can user X perform action Y on resource Z?":

<Steps>
  <Step title="Superadmin Bypass">
    If `user.platform_role === 'superadmin'` -> **ALLOW** (all actions, all orgs)
  </Step>

  <Step title="Org Role Check">
    Load user's role in target organization:

    * `owner` -> ALLOW (all actions within org)
    * `admin` -> ALLOW (all except: delete org, manage billing, transfer ownership)
    * `member` / `viewer` -> proceed to resource check
  </Step>

  <Step title="Resource Permission Check (member/viewer)">
    a. Direct user permission on specific resource? -> use that
    b. Any group the user belongs to has permission? -> use highest grant
    c. Wildcard permission (resource\_id = NULL, meaning "all of type")? -> use that
    d. No match -> **DENY**
  </Step>

  <Step title="Default Viewer Behavior">
    Can read resources they have explicit access to, nothing else.
  </Step>
</Steps>

## Action Types Per Resource

| Resource Type    | Available Actions                                                |
| ---------------- | ---------------------------------------------------------------- |
| `agent`          | `use`, `edit`, `delete`, `view_analytics`, `manage_distribution` |
| `knowledge_base` | `read`, `add_sources`, `edit`, `delete`, `query`                 |
| `analytics`      | `view_own`, `view_org`, `export`                                 |
| `billing`        | `view`, `manage`, `purchase_credits`                             |
| `settings`       | `view`, `edit`                                                   |
| `members`        | `view`, `invite`, `remove`, `change_role`                        |
| `groups`         | `view`, `create`, `edit`, `delete`, `manage_members`             |
| `api_keys`       | `view`, `create`, `revoke`                                       |
| `conversations`  | `view_own`, `view_all`, `delete`                                 |

## Database Schema

### Tables

```sql theme={null}
-- Organization-scoped groups
CREATE TABLE org_groups (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  name VARCHAR(100) NOT NULL,
  description TEXT,
  is_default BOOLEAN DEFAULT FALSE,
  created_by UUID REFERENCES users(id),
  UNIQUE(organization_id, name)
);

-- Group membership
CREATE TABLE org_group_members (
  group_id UUID NOT NULL REFERENCES org_groups(id) ON DELETE CASCADE,
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  PRIMARY KEY (group_id, user_id)
);

-- Resource-level permissions (assigned to groups OR users)
CREATE TABLE resource_permissions (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  group_id UUID REFERENCES org_groups(id) ON DELETE CASCADE,  -- WHO (group)
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,        -- WHO (user)
  resource_type VARCHAR(50) NOT NULL,                         -- WHAT type
  resource_id UUID,                                            -- NULL = wildcard
  actions TEXT[] NOT NULL,                                     -- WHAT actions
  CHECK (group_id IS NOT NULL OR user_id IS NOT NULL),
  CHECK (NOT (group_id IS NOT NULL AND user_id IS NOT NULL))
);

-- Admin audit log
CREATE TABLE admin_audit_log (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  actor_id UUID NOT NULL REFERENCES users(id),
  actor_role VARCHAR(50) NOT NULL,
  organization_id UUID REFERENCES organizations(id),
  action VARCHAR(100) NOT NULL,
  target_type VARCHAR(50),
  target_id UUID,
  details JSONB DEFAULT '{}',
  ip_address INET,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
```

### Helper Function

```sql theme={null}
-- Resolve effective permissions for a user on a resource
CREATE OR REPLACE FUNCTION resolve_resource_access(
  p_user_id UUID,
  p_organization_id UUID,
  p_resource_type VARCHAR,
  p_resource_id UUID
) RETURNS TEXT[] AS $$
DECLARE
  v_platform_role VARCHAR;
  v_org_role VARCHAR;
BEGIN
  -- 1. Superadmin bypass
  SELECT platform_role INTO v_platform_role FROM users WHERE id = p_user_id;
  IF v_platform_role = 'superadmin' THEN RETURN ARRAY['*']; END IF;

  -- 2. Org role check
  SELECT role INTO v_org_role FROM organization_members
    WHERE user_id = p_user_id AND organization_id = p_organization_id;
  IF v_org_role IN ('owner', 'admin') THEN RETURN ARRAY['*']; END IF;

  -- 3. Direct user + group permissions (specific + wildcard)
  -- Returns merged action array or empty array
END;
$$ LANGUAGE plpgsql STABLE;
```

## Middleware Architecture

Every service implements these Fastify hooks:

### requirePlatformRole(role)

```typescript theme={null}
// Rejects with 403 if user.platformRole !== role
// Used on: /admin/* routes, platform-wide endpoints
```

### requireOrgRole(minRole)

```typescript theme={null}
// Checks organization_members.role for current org
// Role hierarchy: owner > admin > member > viewer
// Superadmin bypasses this check
// Used on: org settings, team management, billing
```

### checkResourceAccess(resourceType, resourceId, requiredAction)

```typescript theme={null}
// Calls resolve_resource_access() or in-memory equivalent
// Returns 403 if user lacks the required action
// Superadmin and org owner/admin bypass
// Used on: agent CRUD, KB operations, analytics views
```

### auditLog(action, targetType, targetId, details)

```typescript theme={null}
// Writes to admin_audit_log after successful state-changing operations
// Required for: all admin actions, permission changes, user management
```

## Route Middleware Mapping

| Route Pattern                     | Middleware                                            | Notes                 |
| --------------------------------- | ----------------------------------------------------- | --------------------- |
| `GET /admin/*`                    | `requirePlatformRole('superadmin')`                   | All superadmin routes |
| `PUT /organizations/:id/settings` | `requireOrgRole('admin')`                             | Org settings          |
| `POST /organizations/:id/members` | `requireOrgRole('admin')`                             | Invite members        |
| `DELETE /organizations/:id`       | `requireOrgRole('owner')`                             | Delete org            |
| `GET /bots/:id`                   | `checkResourceAccess('agent', botId, 'use')`          | Agent access          |
| `PUT /bots/:id`                   | `checkResourceAccess('agent', botId, 'edit')`         | Agent edit            |
| `DELETE /bots/:id`                | `checkResourceAccess('agent', botId, 'delete')`       | Agent delete          |
| `GET /knowledge-bases/:id`        | `checkResourceAccess('knowledge_base', kbId, 'read')` | KB read               |
| `GET /analytics/*`                | `checkResourceAccess('analytics', null, 'view_org')`  | Org analytics         |
| `GET /billing/*`                  | `checkResourceAccess('billing', null, 'view')`        | Billing view          |

## New Feature Checklist

When adding any new feature:

1. Does it introduce a new resource type? -> Add to resource/action table above
2. Does it have routes? -> Apply `checkResourceAccess` or `requireOrgRole` middleware
3. Does it modify state? -> Add `auditLog` call
4. Does it have a UI? -> Gate render on permission context
5. Does it incur cost? -> Also integrate with billing
6. Add tests for permission enforcement (403/200 per role)

## Relationship to Billing

RBAC and billing are complementary mandatory systems:

* **RBAC** answers: "Is this user allowed to perform this operation?"
* **Billing** answers: "Should this operation be charged, and to whom?"

RBAC check runs **before** billing. If a user lacks permission, the request is rejected before any cost is incurred. Both systems share the same `organizationId` + `userId` context from JWT auth middleware.
