Roles & permissions
There are seven roles. Two are for client contacts, five are for staff users. Permissions are encoded in code — see packages/auth/src/rbac.ts — and the RBAC matrix page is generated from that file at build time, so it cannot drift.
Client-contact roles
These are the magic-link recipients. They never see the staff dashboard.
- Client submitter — usually the client's HR or finance person. Submits the monthly intake (payroll), responds to unreconciled items (bookkeeping). Cannot approve.
- Client approver — usually a director / FC. Approves the cycle (payroll). Cannot submit.
Staff roles
| Role | Primary surface | Can do |
|---|---|---|
| Payroll Executive | Payroll cycles | Manage cycles, resolve issues, generate exports, upload outputs, request approvals |
| Payroll Lead | Payroll cycles + admin | Everything Payroll Executive does, plus override blocking issues, manage clients, manage templates |
| Bookkeeper | Bookkeeping batches & reconciliation | Manage batches, approve journal entries, generate upload files, run reconciliation |
| Senior Accountant | Bookkeeping + admin | Everything Bookkeeper does, plus override journal flags, manage clients, manage templates |
| Platform Admin | Both products + staff | Manage staff users, manage clients, manage templates, view all cycles and batches; can override issues across both products |
How escalation works
- A blocking validation issue on a payroll submission can be overridden by a Payroll Lead or Platform Admin via the POL approval flow. A Payroll Executive cannot override on their own.
- A flagged journal entry (low confidence, foreign-currency, keyword-only match) can be overridden by a Senior Accountant or Platform Admin. A Bookkeeper can approve normal entries but not override flags.
- Anything tenant-bleed-suspected escalates to Platform Admin and follows the runbook.
Where this is enforced
Three layers, all reading the same matrix:
- API — every Fastify route guards with
requirePermission(Action.X). - Web sidebar —
apps/web/src/lib/dashboard-nav.tsmirrors the matrix; nav entries the role can't access don't appear. - Worker jobs — staff-triggered jobs check the actor's role before running.
If you find a discrepancy — e.g. the sidebar shows a link your role can't access — that's a bug, not your error. Open an issue.