PRD: Fixed & custom roles
| Module | Permissions (CORE-02) | PRD ID | PRD-ROLE-001 |
| Status | Shipped | Owner | Permissions squad |
| Date | 2026-05-27 | Version | v1.0 |
| Packages | @nx/identity · @nx/core · apps/client · apps/bo | URD | ROLE · CROLE |
TL;DR
Gives every KICKO tenant a working role layer on top of the Casbin RBAC engine: eight fixed system roles ship pre-seeded with a numeric priority hierarchy, and administrators and owners can define their own custom roles scoped to an organization or merchant. The result: access levels are predictable out of the box, the top roles bypass data filtering while Owner/Cashier/Employee stay scoped, and no one can ever create or manage a role at or above their own priority.
1. Context & Problem
KICKO controls feature and data access through a Casbin priority-based RBAC model with per-merchant domains, but the engine alone has no opinion about which roles exist or who may create them. Without a role layer, every tenant would start empty - no default access levels, no guard against an admin minting a role more powerful than themselves, and no way to limit a role to one organization or merchant.
A merchant needs sensible defaults the moment it is created, a fixed set of system roles that cannot be tampered with, and the freedom for administrators and owners to define narrower roles for their own staff. Roles must also be wired to the per-merchant domain model so that a role's domain travels in the sign-in token and role listings filter by merchantId/organizer. This is the foundation the rest of the Permissions module (grants, effective permissions, hierarchy) builds on.
2. Goals & Non-Goals
Goals
- Seed eight fixed system roles at startup with a numeric priority hierarchy - Super Admin / Admin / Operator bypass data filtering, while Owner / Cashier / Employee are scoped to the org or merchants they belong to; seed a default
GUESTrole for sign-up. - Protect SYSTEM roles: block modify, delete, and priority change; exclude them from role-merchant mapping counts.
- Let administrators and owners create custom roles with an i18n name, a priority strictly below their own, and an optional organization/merchant scope; auto-generate the role identifier from priority + name.
- Support role update (name, description, priority) and safe deletion (blocked while users remain assigned; cascade-removes grants and scope links).
- Scope role CRUD by organizer/merchant; make
merchantIdan optional query param; HQ owners skip the merchant/identifier filter. - Return a role's domain in the JWT token, so authorization resolves within the active merchant domain.
- Provide a role-management UI: role list with filters, a create/edit form with priority input and merchant sync, 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/writegrants - owned by the Resource, Action & Domain Hierarchy increment. - Permission catalog and grant/revoke - owned by Policy-Definition Grants.
- Time- or shift-based permissions, permission audit logging, per-merchant active-role switching.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Out-of-box readiness | 100% of new merchants have the eight fixed roles available at first sign-in |
| System-role integrity | Zero successful modify/delete/priority-change on a SYSTEM role; system roles never counted in role-merchant mapping |
| Privilege-escalation guard | Zero custom roles created at or above the creator's own priority |
| Scope correctness | Role list/count always filtered to the requesting user's reach; foreign IDs never returned |
| Adoption | Custom roles created per organizer trends up as merchants tailor staff access |
4. 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 organization and merchants |
| Administrator (BO) | 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 merchant is created with eight fixed roles already seeded → an admin or owner creates a custom role with a priority below their own and an optional org/merchant scope → they edit the role's name, priority, and permission tree → and delete it safely once no users remain assigned, with system roles always protected.
5. User Stories
- As an owner, I want a fixed set of roles available the moment my merchant exists, so I can assign staff without configuring access levels from scratch.
- As an administrator, I want to create a custom role with a priority strictly below my own, so I can delegate without granting more power than I hold.
- As an administrator, I want to scope a custom role to one organization or merchant, so it only applies where it should.
- As an administrator, I want to update a role's name, description, and priority, so role definitions stay accurate over time.
- As an administrator, I want deletion blocked while users are still assigned, so I never strand a user without a role.
- As an owner, I want system roles to be untouchable, so the platform's baseline access model can't be broken.
- As an administrator, I want a role-management screen with a collapsible permission tree, so I can see and shape what a role can do at a glance.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Seed eight fixed system roles at startup (Super Admin, Admin, Operator, Owner, Cashier, Employee, Customer, Guest); seed a default GUEST for sign-up | URD-ROLE-001 |
| FR-2 | Each role carries a numeric priority that sets its place in the hierarchy | URD-ROLE-003 |
| FR-3 | Super Admin / Admin / Operator bypass all data filtering and hold every permission | URD-ROLE-004 |
| FR-4 | Owner sees only their own organization and its merchants; Employee/Cashier see only assigned merchants | URD-ROLE-005..006 |
| FR-5 | Every list and count operation is filtered by the requesting user's scope and cannot be overridden | URD-ROLE-007..008 |
| FR-6 | An HQ owner reaches every sibling merchant of the organizer; HQ skips the merchant/identifier filter | URD-ROLE-009 |
| FR-7 | SYSTEM roles cannot be modified, deleted, or have their priority changed; excluded from role-merchant mapping counts | URD-ROLE-002 |
| FR-8 | Authorized users create custom roles with an i18n name, priority, and optional org/merchant scope | URD-CROLE-001 · URD-CROLE-004 |
| FR-9 | Role identifier is auto-generated from priority + name and is unique within its scope | URD-CROLE-002 |
| FR-10 | A new role's priority must be strictly lower than the creator's own highest priority | URD-CROLE-003 |
| FR-11 | Custom roles can be updated (name, description, priority) | URD-CROLE-005 |
| FR-12 | A role cannot be deleted while users remain assigned; deletion cascade-removes grants and scope links | URD-CROLE-006..007 |
| FR-13 | An Owner can create roles scoped only to their own organization or merchants | URD-CROLE-008 |
| FR-14 | A role's domain is returned in the sign-in JWT token | URD-ROLE-004 |
Full requirement text and acceptance criteria live in the Permissions URD. This PRD references them rather than restating them.
7. 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 atomically |
| Tenancy & authz | All role CRUD scoped per merchant (x-merchant-id) and organizer; gated by permissions; privilege-escalation guard on every create/manage |
| Statelessness | The session token is stateless - role changes take effect on next sign-in; a role's domain is carried in the JWT |
| Performance / scale | Role list/count filtering by scope avoids loading roles outside the user's reach |
| i18n | Role name and description are bilingual ({ en, vi }) |
| Consistency | Identifier auto-generation and scope-link creation happen in one operation; partial failures don't leave orphaned roles |
8. UX & Flows
Key screens: in apps/client, a role-management screen with a collapsible permission tree, a create/edit form with priority input, multi-language description, and merchant sync; in apps/bo, role screens with filters and an admin update form.
9. Data & Domain
| Entity | Role |
|---|---|
Role | Named access level - i18n name, numeric priority, SYSTEM or CUSTOM type, auto-generated identifier |
| Role-merchant / role-organizer scope link | Membership binding a custom role to a specific organization or merchant |
| User-role membership | Links a user to a role; deletion is blocked while any remain |
| Permission grant | Policy record removed in cascade when a role is deleted |
| Sign-in token (JWT) | Carries the user's roles and the active merchant domain |
Conceptual only - full schema and the policy data model live in the identity RBAC docs.
10. Dependencies & Assumptions
Depends on
- User Management (CORE-01) - users are the subjects that receive roles.
- Commerce (orgs & merchants) - organizations and merchants are the scopes roles attach to.
- Casbin RBAC engine (
@nx/core) - the priority-based model with per-merchant domains the role layer sits on.
Assumptions
- Each merchant is created with access to the eight seeded fixed roles.
- The active merchant is chosen per request via the active-merchant header; the role's domain resolves within it.
- An organizer/merchant the custom role is scoped to already exists.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| An admin mints a role more powerful than themselves | Privilege-escalation guard - new priority must be strictly below the creator's own |
| System roles tampered with or miscounted | SYSTEM roles immutable and excluded from role-merchant mapping counts |
| Deleting a role strands assigned users | Deletion blocked while users remain; must unassign first |
| Stateless token means stale permissions | Accepted - role/permission changes take effect on next sign-in |
| HQ-owner reach across sibling merchants | HQ owners skip the merchant/identifier filter; behaviour documented as expansion |
12. 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 + default GUEST seeded at startup; no data migration |
| Launch criteria | Eight roles seeded and protected; custom create/update/delete verified with priority guard and cascade; role list/count scoped correctly; role domain present in JWT |
| Monitoring | Custom-role creation volume, privilege-escalation rejections, role-scope filter consistency |
13. FAQ
Can a system role be edited or deleted? No - the eight SYSTEM roles are immutable: modify, delete, and priority change are all rejected, and they are excluded from role-merchant mapping counts.
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.
What scope does a custom role apply to? It can be scoped to a specific organization or merchant; an Owner can only scope to their own organization or merchants.
Why can't I delete a role? Deletion is blocked while users are still assigned - unassign them first. Once empty, deletion cascade-removes the role's grants and scope links.
When do role changes take effect? On next sign-in - the session token is stateless and carries the user's roles and domain.
References
- URD: Permissions - Fixed Roles · Custom Roles
- Related PRD: Policy-Definition Grants · Per-merchant Casbin Authorization · Resource, Action & Domain Hierarchy
- Module: Permissions - overview + traceability
- Developer: @nx/identity · RBAC & Policy Definitions