Role model
Roles are the app_role enum, stored on company_memberships.role (company-wide) and
case_team_members.role_in_case (per-case, multi-valued).
The enum (accreted, never pruned)
Live/intended roles reflect the Portuguese disciplinary procedure:
super_admin, hr_admin, case_manager, instructor_internal, instructor_external,
legal_reviewer, decider, employee, witness, external_counsel.
Legacy roles still in the enum (from the whistleblower pivot),
"kept but unused" yet still granted access via aliasing:
company_admin (→hr_admin), case_handler (→case_manager), viewer.
useUserRole.ROLE_PRIORITY ranks them; is_company_admin() (DB) treats
super_admin/company_admin/hr_admin as admin.
The gotchas (their own notes)
- The app's effective role is the global max across all memberships, not per active tenant.
- The UI is effectively binary (
isSuperAdmin/ not);isHrAdmingates several unrelated privileges at once. - Two screens write different role strings for the same role (legacy vs new set) → orphaned users.
- RLS enforces membership + admin, not the fine-grained roles — instructor / legal reviewer / decider distinctions live only in app code.
Rebuild implication
Define the canonical role matrix the domain needs, normalise legacy strings, evaluate
per-tenant, and enforce role-gated writes server-side (RLS/policies), especially for the
decider gate and instructor/legal-reviewer separation of duties.