Skip to main content

Agency RBAC Persistence Model

Purpose

This document describes the org RBAC persistence shape that is actually in the repo today.

Use it to understand where role, capability, and client-scope state currently lives.

Current Ownership Boundary

  • organizations and orgUsers own base org membership
  • org/access owns capability resolution and role-policy loading
  • clientAccessAssignments owns non-owner client visibility in the client-work slice

Implemented Persistence Layers

1. Base membership layer

Current table:

  • orgUsers

Current fields used by the org slice:

  • organizationId
  • userId
  • email
  • role as owner, admin, account_manager, or staff
  • status
  • capabilityMode
  • capabilityOverrides

This is already the source of truth for the base org role.

2. Client access scope layer

Current table:

  • clientAccessAssignments

Current fields:

  • organizationId
  • memberUserId
  • clientAccountId
  • active
  • assignedByUserId
  • assignedAt
  • updatedAt

Current behavior:

  • owners bypass this table and get global client visibility
  • non-owners derive client visibility from active assignment rows
  • there is no separately persisted non-owner all_clients access mode in the current slice

3. Role policy layer

Current table:

  • orgRolePolicies

Current fields:

  • organizationId
  • adminPermissions
  • accountManagerPermissions
  • staffPermissions
  • updatedByUserId
  • updatedAt

This is already used by the current org capability system.

4. Per-membership capability overrides

Current storage location:

  • orgUsers.capabilityMode
  • orgUsers.capabilityOverrides

Current behavior:

  • role_default uses the organization policy defaults
  • custom uses the explicit override list on the membership

The repo does not currently use a separate orgPermissionOverrides table.

Current Resolution Order

The current org resolution flow is:

  1. resolve active orgUsers membership
  2. if role is owner, grant global client visibility
  3. load org role policy from orgRolePolicies
  4. apply membership capabilityMode and capabilityOverrides
  5. for non-owners, resolve visible client accounts through active clientAccessAssignments
  6. apply route-level checks such as billing capability requirements

Important Current Constraint

The client-work slice now uses capability-first enforcement for operational authorization paths.

  • capability checks gate org settings, billing, and client-work mutation/read authorization paths
  • role checks remain for ownership invariants and role-transition rules (for example owner transfer and owner-protection rules)

The app still does not expose a separate client-work permission grammar beyond the existing capability set and client-scope model.

Backend Placement Rule

Keep the current ownership split intact while finishing the phase:

  • membership resolution in organizations/*
  • capability resolution in org/access/*
  • client visibility in org/clientScope.ts

Do not introduce a second client-scope persistence model while the current one is already working.