Skip to main content

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); isHrAdmin gates 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.