PRD: Fixed & custom roles
| Module | Permissions | PRD ID | PRD-ROLE-001 |
| Status | Ready to dev | FEAT | ROLE |
| Epic | — | Plane | BANA-1512 |
| Date | 2026-05-27 | Version | v1.0 |
| Packages | @nx/identity | URD | ROLE |
| Surface | Client · Owner/MgrBO · Ops | ||
| Owner | Phát Nguyễn | ||
What fixed & custom roles are
BANA controls feature and data access through a priority-based RBAC model with per-merchant scope. The mechanism itself has no opinion about which roles exist or who may create them - that's the role layer's job. Without it, every merchant would start empty: no default access levels, nothing stopping an admin from minting a role more powerful than themselves, and no way to limit a role to one organizer or merchant.
This PRD builds that role layer: eight fixed roles (Role) that ship with every merchant from day one, plus a mechanism for Admin/Owner to create their own custom roles without ever breaking the platform's baseline access model. It's the foundation the rest of the Permissions module (grants, effective permissions, hierarchy) builds on.
Why a role layer is needed
A merchant needs sensible defaults the moment it is created, a fixed set of roles no one can tamper with, and the freedom for Admin and Owner to define narrower roles for their own staff. Roles also have to line up with the per-merchant scope model, so a role's scope travels with the user when they sign in, and role listings filter by merchant and organizer.
From a new organizer to a custom role
Every merchant has eight fixed roles ready from the moment it is created (Super Admin, Admin, Operator, Owner, Cashier, Employee, Customer, Guest), ranked by a numeric priority; the top three - Super Admin, Admin, Operator - bypass all data filtering, while Owner sees only their own organizer and merchants, and Employee/Cashier see only assigned merchants.
The moment a user creates an organizer, they get the Owner role assigned automatically, no manual grant needed.
On the Client app, the Team section has a Role screen where Admin/Owner create custom roles: set a bilingual name, pick a priority from 3 tiers - High/Medium/Low (no typing raw numbers), choose the scope, and pick the permission tree to grant. A new role's priority must always be strictly below the creator's own highest priority - no one can ever create a role at or above their own level.
Deleting a custom role is blocked while users remain assigned to it; a successful delete cascades away every grant and scope link that belonged to it. On the Back Office, operations staff manage roles the same way, filtered by organizer or merchant.
The easiest place to get it wrong
The Edit/Delete buttons show for everyone, the server does the blocking
Fixed roles can never be deleted; editing a fixed role's name, description, or priority is reserved for callers who hold Operator or above. On the form, the priority field is locked for every fixed role regardless of who's viewing it, but the Edit button still shows for everyone - the server is what actually rejects the request when someone without enough privilege submits.
The UI never hides an action based on the viewer's permission. Clicking a button you don't have permission for gets you rejected with a clear message, not a button that disappeared first.
1. Goals & Non-Goals
Goals
- Seed eight fixed roles at startup with a numeric priority hierarchy - Super Admin / Admin / Operator bypass data filtering, while Owner / Cashier / Employee are scoped to the organizer or merchants they belong to; seed a default Guest role for sign-up.
- Protect fixed roles: block deletion outright; restrict editing and priority changes to callers who hold Operator or above.
- Let Admin and Owner create custom roles with a bilingual name, a priority strictly below their own, and an optional organizer/merchant scope; auto-generate the role identifier from priority + name.
- Support role update (name, description, priority) and safe deletion (blocked while users remain assigned; deletion cascades every grant and scope link).
- Auto-assign the Owner role to a user the moment they finish creating their organizer.
- Scope role management by organizer/merchant; make the merchant an optional filter; an Owner at the headquarters (HQ) merchant reaches every merchant in the same organizer.
- Provide a role-management screen: role list with filters, a create/edit form with a 3-tier priority picker, and a collapsible permission tree.
Non-Goals
- Wildcard / glob permissions and permission categories - see Non-Goals in the URD.
- The resource/action/domain hierarchy and coarse manage/write grants - owned by the Resource, Action & Domain Hierarchy increment.
- Permission catalog and grant/revoke - owned by Policy Definition, Permission Catalog & Grants.
- Time- or shift-based permissions, permission audit logging, per-merchant active-role switching.
2. Success Metrics
| Metric | Target / signal |
|---|---|
| Out-of-box readiness | 100% of new merchants have the eight fixed roles available at first sign-in |
| Fixed-role integrity | No fixed-role delete ever succeeds; edit/priority-change restricted to callers who hold Operator or above |
| Privilege-escalation guard | Zero custom roles created at or above the creator's own priority |
| Scope correctness | Role list/count narrows to a merchant when a merchant filter is supplied; users can never widen their reach with a self-declared filter |
| Adoption | Custom roles created per organizer trend up as merchants tailor staff access |
3. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Super Admin / Admin / Operator | Manage roles across the platform with full data reach |
| Owner | Create and manage roles scoped to their own organizer and merchants |
| Operations staff (Back Office) | Maintain roles and their permission trees from the Back Office |
| Employee / Cashier | Receive a fixed, scoped role limiting them to assigned merchants |
Core scenarios: a user creates a new organizer → gets the Owner role assigned right away, and a freshly created merchant already has the eight fixed roles → the Owner creates a custom role with a priority below their own and an optional organizer/merchant scope → they edit the role's name, priority, and permission tree → and delete it safely once no users remain assigned, with fixed roles always protected.
4. User Stories
| # | As a | I want | So that |
|---|---|---|---|
| 01 | owner | to be assigned the Owner role the moment I create my organizer | I can manage staff without asking anyone to grant it |
| 02 | owner | a fixed set of roles available the moment my merchant exists | I can assign staff without configuring access levels from scratch |
| 03 | administrator | to create a custom role with a priority strictly below my own | I can delegate without granting more power than I hold |
| 04 | administrator | to scope a custom role to one organizer or merchant | it only applies where it should |
| 05 | administrator | to update a role's name, description, and priority | role definitions stay accurate over time |
| 06 | administrator | deletion blocked while users are still assigned | I never strand a user without a role |
| 07 | owner | fixed roles to be untouchable | the platform's baseline access model can't be broken |
| 08 | administrator | a role-management screen with a collapsible permission tree | I can see and shape what a role can do at a glance |
5. Functional Requirements
| # | Requirement | Status | URD ref |
|---|---|---|---|
FR-001 | Eight fixed roles - Super Admin, Admin, Operator, Owner, Cashier, Employee, Customer, Guest - are created at system startup. Each carries a bilingual name (English/Vietnamese) and a fixed numeric priority. | ✅ | URD-ROLE-001 |
FR-002 | A role's priority sets its place in the hierarchy relative to other roles. On the create/edit form for custom roles (Client and Back Office alike), users don't type a raw number - they pick 1 of 3 tiers: High / Medium / Low. | ✅ | URD-ROLE-003 |
FR-003 | Super Admin, Admin, and Operator bypass all scope-based data filtering and hold every permission, regardless of organizer or merchant. | ✅ | URD-ROLE-004 |
FR-004 | Owner sees only data within their own organizer and its merchants. Employee and Cashier see only the merchants they're assigned to. | ✅ | URD-ROLE-005 · URD-ROLE-006 |
FR-005 | Every role list or count operation is automatically filtered to the caller's scope. Users can't widen that scope with a self-declared filter, nor reach a role outside their scope by calling its ID directly. | ✅ | URD-ROLE-007 · URD-ROLE-008 |
FR-006 | An Owner at the organizer's headquarters (HQ) merchant sees and manages roles across every merchant in the same organizer, not just the current one. On the Client Role screen, an HQ Owner gets an extra merchant picker when creating/editing a custom role, while a non-HQ Owner doesn't see it. | ✅ | URD-ROLE-009 |
FR-007 | The eight fixed roles can never be deleted. • Editing a fixed role's name, description, or priority is allowed only for callers who hold Operator or above; lower-priority callers are rejected. • On the edit form, the priority field always shows locked with a warning for any fixed role, regardless of who's viewing it - the actual block happens server-side on submit. | ✅ | URD-ROLE-002 |
FR-008 | The moment a user creates a new organizer, they are automatically assigned the Owner role, with no separate role-assignment step. | ✅ | URD-ROLE-010 |
FR-009 | An Admin or Owner creates a custom role. • A bilingual name (English/Vietnamese). • An optional description. • One of 3 priority tiers (High/Medium/Low). • A scope limited to a specific organizer or a specific merchant. | 🔶 | URD-CROLE-001 · URD-CROLE-004 |
FR-010 | The role's identifier is auto-generated by the system from priority + name, never typed by the creator. It's unique within the exact scope the role belongs to. | ✅ | URD-CROLE-002 |
FR-011 | A newly created role's priority must always be strictly below the creator's own highest priority. Trying to create a role at or above that level is rejected. | ✅ | URD-CROLE-003 |
FR-012 | Custom roles can be updated for name, description, and priority. The new priority still has to stay below the editor's own highest priority - the same escalation guard applies as on create. | ✅ | URD-CROLE-005 |
FR-013 | A role can't be deleted while users remain assigned to it. • They must be unassigned first. • Once deleted, every grant and scope link that belonged to it is cleaned up in the same operation. | ✅ | URD-CROLE-006 · URD-CROLE-007 |
FR-014 | An Owner can only create custom roles scoped to their own organizer or merchants. Trying to create one for a different organizer or merchant is rejected. | ✅ | URD-CROLE-008 |
5.1 Acceptance Criteria
- Open the Role screen for the first time on a freshly created merchant → see all eight fixed roles (Super Admin, Admin, Operator, Owner, Cashier, Employee, Customer, Guest), each showing a bilingual name.
- Open the create or edit form for a custom role → the priority field is a picker with 3 tiers - High / Medium / Low - not a free-text number input.
- Sign in as Super Admin, Admin, or Operator and call any scope-filtered listing → see all data, not limited by organizer or merchant.
- Sign in as Owner and open the merchant/role list → see only merchants in your own organizer; sign in as Employee or Cashier → see only your assigned merchants.
- An Employee calls the role list with a filter wider than their scope, or calls a role outside their scope directly by ID → gets no results beyond their scope / is rejected.
- Sign in as an Owner at the HQ merchant, open the Role screen → see an extra merchant picker and can create/edit a role for another merchant in the same organizer; sign in as an Owner at a regular merchant → no merchant picker shown.
- Try to delete one of the eight fixed roles → rejected. A caller below Operator opens the edit form for a fixed role → the priority field shows locked with a warning; submitting a name/description change → rejected by the server; a caller at Operator or above can edit it.
- Create a new organizer → the creator is assigned the Owner role right away, with no need to visit a permissions screen to grant it manually.
- Create a custom role scoped to a merchant → the role applies only in that merchant; create one scoped to an organizer → the role applies across every merchant in that organizer.
- Create a custom role, then open it again → the identifier was auto-generated from priority + name, never typed; create another role with the same identifier in the same scope → rejected.
- Sign in with a highest-held priority at the Medium tier, try to create a role at High or Medium → rejected; create one at Low → succeeds.
- Edit the name, description, or priority of a custom role you created → saves fine; try to raise the priority to at or above your own highest → rejected.
- Delete a custom role that still has users assigned → rejected with a message to unassign them first; unassign everyone and delete → the role, its grants, and its scope links all disappear in one delete.
- Sign in as Owner, try to create a custom role for an organizer or merchant that isn't yours → rejected.
6. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Data integrity | All records are soft-deleted, never physically removed; deleting a role cascades to its grants and scope links as one atomic operation |
| Authorization & UI | The Edit/Delete actions for a role still show in the UI regardless of whether the viewer has the right permission; the server is always what decides whether a request is allowed, returning a clear error on rejection - the UI never hides actions based on permission |
| Tenancy & authz | All role management is scoped per merchant and organizer; gated by permissions; privilege-escalation guard on every create/edit |
| Statelessness | The sign-in session is stateless - role changes take effect on next sign-in; a role's scope is carried in the session |
| Performance / scale | Role list/count filtering by scope avoids loading roles outside the user's reach |
| i18n | Role name and description are bilingual (English & Vietnamese) |
| Consistency | Identifier auto-generation and scope-link creation happen in one operation; partial failures don't leave orphaned roles |
7. UX & Flows
Key screens: on the Client app, the Team section has a Role screen with a list filtered to the current merchant, a create/edit form with a 3-tier priority picker (locked when editing a fixed role), a bilingual description, and a collapsible permission tree grouped by module; an HQ Owner gets an extra merchant picker for another merchant in the same organizer. On the Back Office, operations staff have an equivalent screen for managing roles across merchants/organizers, filtered by organizer or merchant.
8. Data & Domain
| Concept | Role in this increment |
|---|---|
| Role | A named access level - bilingual name, numeric priority, fixed or custom type, auto-generated identifier |
| Role-merchant / role-organizer scope link | Binds a custom role to a specific organizer or merchant |
| User-role assignment | Links a user to a role; deletion is blocked while any assignment remains |
| Granted permission | A record cleaned up in cascade when a role is deleted |
| Sign-in session | Carries the user's roles and the active merchant scope |
Conceptual only - full schema and the permission data model live in the identity RBAC docs.
9. Dependencies & Assumptions
Depends on
| # | Feature | What it depends on |
|---|---|---|
| 01 | User Management (CORE-01) | users are the subjects that receive roles. |
| 02 | Commerce (organizers & merchants) | organizers and merchants are the scopes roles attach to. |
| 03 | Platform authorization mechanism | the priority-based RBAC model with per-merchant scope the role layer sits on. |
Assumptions
| # | Assumption | What breaks if wrong |
|---|---|---|
| 01 | Each merchant is created with access to the eight seeded fixed roles. | A freshly created merchant has no role to assign at all - not even Owner for its own creator - so every permission-gated action is blocked from the start. |
| 02 | The active merchant is chosen per session; the role's scope resolves within it. | The role's scope can't be resolved for that request, so every merchant-scoped permission check has nothing to apply against. |
| 03 | The organizer/merchant a custom role is scoped to already exists. | The scope link points at an organizer/merchant that doesn't exist, so the custom role never applies anywhere. |
10. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | ROLE is P1, CROLE is P2 - see URD feature catalog |
| Rollout | All merchants; no feature flag |
| Migration | Eight fixed roles + a default Guest are seeded at startup; no data migration |
| Launch criteria | Eight roles seeded and protected; Owner auto-assigned on organizer creation; custom create/update/delete verified with the priority guard and cascading cleanup; role list/count scoped correctly |
| Monitoring | Custom-role creation volume, privilege-escalation rejections, role-scope filter consistency |
References
Risks & Open Questions
| # | Risk / question | Mitigation / status |
|---|---|---|
| 01 | An admin mints a role more powerful than themselves | Privilege-escalation guard - new priority must be strictly below the creator's own |
| 02 | A fixed role gets tampered with | Fixed roles can never be deleted; edits are limited to callers who hold Operator or above |
| 03 | Deleting a role strands assigned users | Deletion blocked while users remain; must unassign first |
| 04 | Stateless sessions mean old permissions can still apply for a while | Accepted - role/permission changes take effect on next sign-in |
| 05 | HQ-Owner reach across sibling merchants | An HQ Owner reaches every merchant in the same organizer; documented as a deliberate expansion |
Frequently Asked Questions
| # | Question | Answer |
|---|---|---|
| 01 | Can a fixed role be edited or deleted? | Deletion is always rejected for the eight fixed roles. Editing and priority changes are rejected for callers below Operator - only callers at Operator or above can edit a fixed role. |
| 02 | Do I need to assign myself the Owner role when I create an organizer? | No - the Owner role is assigned automatically the moment your organizer is created, no extra step needed. |
| 03 | Why can't I create a role with the same priority as mine? | The privilege-escalation guard requires a new role's priority to be strictly below your own highest priority, so you can never delegate more power than you hold. |
| 04 | What scope does a custom role apply to? | It's scoped to a specific organizer or merchant; an Owner can only scope to their own organizer or merchants. |
| 05 | Why can't I delete a role? | Deletion is blocked while users are still assigned - unassign them first. Once empty, deletion cascades away the role's grants and scope links. |
| 06 | Why do I still see the Edit/Delete buttons even though I don't have permission? | The UI doesn't hide actions based on the viewer's permission; click it and the server checks and rejects with a clear message if you're not allowed. |
| 07 | When do role changes take effect? | On next sign-in - the session is stateless and carries the user's roles and scope. |