PRD: Policy definition, permission catalog & grants
| Module | Permissions | PRD ID | PRD-PERM-001 |
| Status | Ready to dev | FEAT | PERM · BANA-1435 |
| Epic | — | Plane | BANA-1549 |
| Date | 2026-05-28 | Version | v1.0 |
| Packages | @nx/identity | URD | PERM |
| Surface | BO · OpsClient · Owner/Mgr | ||
| Owner | Phát Nguyễn | ||
What policy definition, permission catalog & grants is
The operations team (in the back office) and merchant owners (in the team module) get a management surface for BANA's authorization model: maintain the permission catalog and manage the policy definition. BANA's authorization model - priority-based RBAC with per-merchant authorization (now enabled) - already stores a permission catalog and policy grants, and the underlying grant/revoke/effective-permission logic already existed before this. What is missing is a usable surface to drive them: the operations team cannot browse the permission catalog, attach or detach permissions, or inspect which roles, users, and merchants a policy applies to without touching the data directly.
Structured by target
This PRD builds that management surface on top of the existing authorization logic: a permission catalog screen (list / create / edit) and a policy-definition screen organised into target sections - each section is a two-panel layout (subject list on the left, the selected subject's grant tabs on the right) with its own grant dialog. The same surface is cloned into the merchant-facing team module so an owner can manage policy within their own merchant scope, backed by a bilingual English/Vietnamese labelling pass.
The permission catalog is managed through three screens - list, create, edit - each permission carries a system-wide unique code, subject, action, and scope. Policy definition is split into per-target sections: pick a user, a role, or a merchant, then grant/revoke permissions through tabbed tables and dialogs. Assigning a role to a person works from three entry points - the Permissions tab in the employee form on the Client app, the by-person tab and the by-role tab in policy definition - all three write into the same grant set.
The easiest place to get it wrong
Idempotent means skipped and counted, not an error
Re-granting an already-granted item is skipped and counted, never duplicated - and never raises an error either. Assigning a role to a user can carry an optional domain that limits where the role takes effect - leave it empty and it applies globally.
Owners manage policy within their own merchant scope through a clone of this surface inside the team module - the same per-target logic, not a separate fork.
1. Goals & Non-Goals
Goals
- Maintain the permission catalog through list / create / edit screens - system-wide unique code, subject, action, scope, bilingual name/description.
- Manage the policy definition per target: sections by user, role, and merchant are live.
- Make grant/revoke idempotent: re-granting an already-granted item is skipped and counted, never duplicated.
- Explicit scope semantics: assigning a role to a user can carry an optional domain - empty means the role applies globally, filled means it only applies within that domain; grant/revoke authority is enforced by per-target ownership checks; the active merchant scopes policy lookups.
- Clone the policy-definition surface into the team module so owners - and the operations team when working inside a specific merchant - manage policy within that merchant's scope.
- Ship the English/Vietnamese labels the screens depend on.
Non-Goals
- The resource/action/domain hierarchy and the declaration toolkit - coarse manage/write grants and module roll-up are a separate increment (PRD-HIER-001).
- Wildcard/glob permissions, permission categories / UI grouping, role templates / bundles (see Non-Goals in the URD).
- Time- or shift-based permissions and permission audit logging.
- The underlying authorization engine - this increment is the management surface, not the enforcement engine or the grant/revoke logic it consumes.
2. Success Metrics
| Metric | Target / signal |
|---|---|
| Self-service management | Permission catalog and grants are managed in-product; zero direct data edits to grant/revoke |
| Catalog integrity | Every permission has a system-wide unique code; permissions with active grants cannot be deleted |
| No duplicate grants | Re-granting an already-granted item is a no-op: the item is skipped and counted, the record count is unchanged |
| Grant scope | Grant/revoke only succeeds when the operator holds ownership over the corresponding target; policy lookups are scoped to the active merchant |
| Owner reach | Merchant owners can manage policy within their own scope via the team module |
3. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Operations team - back office | Maintain the permission catalog, manage roles, and grant/revoke permissions on users, roles, and merchants |
| Owner - team module | Manage policy within their own merchant scope |
Core scenario: maintain the permission catalog → open policy definition → pick a target tab (user / role / merchant) → select a subject in the left panel → review the subject's grant tabs → grant or revoke via the dialog → already-granted items are skipped and counted, new items are created.
4. User Stories
| # | As a | I want | So that |
|---|---|---|---|
| 01 | operations team | to create, edit, and list permissions with a unique code, subject, action, and scope | the catalog is maintained in-product |
| 02 | operations team | to select a user and see and manage the roles, permissions, merchants, and organizers assigned to that user | one person's policy sits in one place |
| 03 | operations team | to select a role, grant/revoke its permissions, and see which users hold it | I control what a role can do and who uses it |
| 04 | operations team | role-to-user assignment to require picking a merchant first, and let me fill in a domain to limit where the role takes effect | assignments never spill outside the intended scope |
| 05 | operations team | to assign a role to a person from whichever entry point is convenient - the employee form, the by-person tab, or the by-role tab - and know all three write to the same place | I don't have to remember a different rule for each one |
| 06 | operations team | re-granting an existing item to be skipped and counted instead of duplicated | repeated grant operations never corrupt the data |
| 07 | owner | the same policy-definition surface inside the team module | I can manage policy within my own merchant scope |
5. Functional Requirements
| # | Requirement | Status | URD ref |
|---|---|---|---|
FR-001 | Permission list screen - table with per-column filters | ✅ | URD-PERM-001..004 |
FR-002 | A permission create/edit form paired with catalog integrity rules. • Permission create/edit form. • Catalog integrity rules. | ✅ | URD-PERM-002 · URD-PERM-005 |
FR-003 | Role management with a direct-assignment permission tree | ✅ | URD-ROLE · URD-CROLE |
FR-004 | Policy-definition screen - per-target section layout | ✅ | URD-GRANT-001..002 |
FR-005 | The user section has 4 tabs plus a permission view-mode switcher. • 4 tabs. • Permission view-mode switcher. | ✅ | URD-GRANT-004 · URD-GRANT-006..007 |
FR-006 | Role section - 2 tabs | ✅ | URD-GRANT-001..002 |
FR-007 | The merchant section covers grant/revoke on Users and Roles scoped to the merchant. • Grant/revoke Users. • Roles scoped to the merchant. | 🔶 | URD-GRANT-001..002 |
FR-008 | Per-target grant dialogs and a role-to-user flow available through three entry points. • Per-target grant dialogs. • Role-to-user flow: pick a merchant first, then an optional domain. • Available via three entry points. | ✅ | URD-GRANT-001..002 |
FR-009 | Idempotent grant/revoke returning change counts | ✅ | URD-GRANT-003 |
FR-010 | Scope and operation authority are enforced through multiple layers. • Per-merchant scoping. • Ownership checks. • Server as the sole enforcement point. | ✅ | URD-GRANT-005 · URD-EFF-004 |
FR-011 | Raw policy-record inspection screen | 🚧 | URD-GRANT-006 |
FR-012 | Team-module clone | ✅ | URD-GRANT-001..007 · URD-EFF-004 |
FR-013 | English/Vietnamese labels across all screens | ✅ | URD-PERM-003 |
FR-001 - Permission list screen. Table columns: Code (rendered as a technical code), Name (in the current language), Subject, Action (rendered as a badge), Scope - all 5 columns filterable. Each row has two actions: Edit and Delete (delete asks for confirmation; the confirmation message shows the permission code).
FR-002 - Permission create/edit form. Fields:
| Field | Required | Notes |
|---|---|---|
| Code | ✔ | System-wide unique; locked (read-only) when editing |
| Name | ✔ | Bilingual English/Vietnamese |
| Subject | ✔ | One of 2 values: User, Role |
| Action | ✔ | One of 5 values: create, read, update, delete, execute |
| Scope | ✔ | One of 3 values: SYSTEM, ORGANIZER, MERCHANT |
| Description | ✘ | Bilingual English/Vietnamese |
Integrity rules: a permission with active grants cannot be deleted (the system refuses with an error); records are soft-deleted, never physically removed.
FR-003 - Role management with a permission tree. The role screen has a list table (columns: Name, Description, Status, Type - system roles carry a badge distinguishing them from custom roles; all filterable; the header has a merchant filter) and a create/edit form with: bilingual name + description; status (3 values: Active / Deactivated / Archived, default Active); priority (defaults to the low tier); organizer (required on the back office); merchant (optional); and a permission tree for ticking the permissions directly assigned to the role at create/edit time. This means there are two parallel mechanisms for assigning permissions to a role: (1) the permission tree in the role form - used when defining a role as a package; (2) the grant dialog in the policy definition's role section (FR-006) - used for one-off adjustments on an existing role. Both write into the same grant set.
FR-004 - Policy-definition screen layout. The screen has top-level target tabs: By user, By role, and By merchant (live). Each tab is a two-panel section: the left panel lists subjects (searchable, paginated 20 rows/page, with a guiding empty state when nothing is selected); the right panel shows the selected subject's grant tabs.
FR-005 - User section. Select a user (search by name/email/username) and work across 4 tabs: Roles, Permissions, Merchants, Organizers - each tab a table of existing grants + revoke actions + its own grant dialog (the Organizers tab lists the organizers the user belongs to; it is not the by-organizer target management screen). The Permissions tab additionally has a view-mode switcher with 3 values: All / Direct / Inherited - separating permissions assigned straight to the user from permissions inherited through roles.
FR-006 - Role section. Select a role (search by name/identifier; system roles carry a badge) and work across 2 tabs: Permissions (grant/revoke permissions on the role) and Users (the users holding the role - answering "who does this role apply to").
FR-007 - Merchant section. Select a merchant (search by name/identifier; the headquarters merchant carries a badge) and view/grant/revoke the Users belonging to it. The Roles scoped to the merchant exist in the Team area (view and revoke - FR-012); the shared back-office policy-definition screen does not have this tab.
FR-008 - Grant dialogs & assignment flow. The grant tabs in the user and role sections each have their own dialog: a multi-select table (filterable/searchable, paginated 25 rows/page), a confirm button showing the number of selected items and disabled while nothing is selected; the merchant section on the shared back-office screen also has a dialog for assigning users to a merchant. The merchant section in the Team area is view-and-revoke only - new grants are made from the by-user or by-role tabs. The role-to-user flow is special: a merchant must be picked first - the merchant list only contains merchants the user already belongs to - before the role list appears; the dialog also has an optional domain field: leave it empty and the role applies globally, fill it and the role only applies within that domain. Assigning a role to a person works from three entry points, all writing into the same grant set: (1) the Permissions tab in the employee form on the Client app (see PRD-EMP-001); (2) the grant dialog on the Roles tab in the user section (FR-005); (3) the grant dialog on the Users tab in the role section (FR-006). Assigning or revoking through any one of them is reflected immediately in the other two.
FR-009 - Idempotency. Grant is an "add if absent" operation: already-granted items are skipped, new items are created; the result reports the granted / skipped counts for the operator to verify. The coarse permission-grant path instead reports granted / updated / revoked counts. Repeating the same grant operation yields the same final state with an unchanged record count.
FR-010 - Scope & operation authority. Per-merchant authorization is enabled: the active merchant scopes policy lookups (e.g. the list of users holding a role). Grant/revoke authority is enforced by per-target ownership checks, plus a privilege-escalation guard (you cannot grant beyond your own authority). Grant/revoke buttons on screen do not hide themselves based on the signed-in user's permissions - every button always shows, and the server refuses the action the moment it is pressed; the server is the sole enforcement point, with no button-hiding logic on the client.
FR-011 - Raw policy-record inspection screen. A table listing raw policy records with columns: Variant, Subject type, Subject id, Target type, Target id, Action, Effect, Domain - all filterable - plus a create/edit form for individual records. This screen serves the operations team when tracing deep diagnostics; the target sections (FR-004..008) are the primary operating surface for day-to-day work.
FR-012 - Team-module clone. The same target-section surface lives inside the team module - used by owners on the Client app, and by the operations team when working inside a specific merchant on the back office; scoped to the merchant being operated. It contains the user, role, and merchant sections; the merchant section on this surface has the Users and Roles tabs for viewing and revoking - new grants are made from the user or role sections.
FR-013 - Bilingual labels. Every label, title, message, and description across the screens above exists in both English and Vietnamese.
5.1 Acceptance Criteria
- Create a permission with a new code → succeeds; create another with the same code → rejected.
- Open a permission's edit form → the code field is visible but read-only; every other field is editable.
- Delete a permission that has active grants → the system refuses with an error message; delete a grant-free permission → succeeds (soft-delete).
- Create a role on the back office with organizer left empty → the form raises a required-field error; fill in the organizer → the role is created along with the permissions ticked in the tree.
- In the user section, Permissions tab: Direct mode shows only permissions assigned straight to the user; Inherited shows only permissions coming through roles; All is the union of the two.
- Grant N permissions to a role where K were already granted → the result reports N−K granted, K skipped; the role's total grants grow by exactly N−K.
- Repeat the exact grant operation from the previous step → reports 0 granted, N skipped; the data is unchanged.
- Role-to-user assignment: with no merchant picked → the role list is empty with a pick-a-merchant hint; the merchant list only contains merchants the user already belongs to.
- Assign a role to a user with domain left empty → the role applies globally; fill in a domain → the role's permissions only apply within that domain.
- Revoke a grant and re-check the list → the item is gone; the corresponding user/role loses the permission on the next check.
- An owner opens the team module → sees only data inside their own merchant.
- Switch language English ↔ Vietnamese → every label/message on these screens follows, no untranslated strings.
- The merchant section in the Team area has a Roles tab: it lists roles scoped to the merchant; revoke a role there → the role disappears from the list; revoke a user on the Users tab → the user disappears likewise.
- Assign a role to a person through the employee form, then open the Roles tab in that person's user section → the just-assigned role shows up; assign another role through the Users tab in that role's section → reopening the employee form shows both roles.
- A user without ownership over a target still sees the corresponding grant/revoke button on screen; pressing it is refused by the server with a clear error message.
- Open the raw policy-record inspection screen → the table shows all eight columns (Variant, Subject type, Subject id, Target type, Target id, Action, Effect, Domain), each filterable; edit a record through the form → the new value shows in the table right away.
6. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Data integrity | Permission codes are system-wide unique; permissions with active grants cannot be deleted; records are soft-deleted, never physically removed |
| Idempotency | Grant is "add if absent": existing items are skipped and counted; repeating an operation yields the same final state (the membership path reports granted/skipped; the coarse grant path reports granted/updated/revoked) |
| Tenancy & authz | Grant/revoke authority enforced by per-target ownership checks + a privilege-escalation guard; the active merchant scopes lookups; role-to-user assignment can carry a domain limiting where the role takes effect; the UI never hides buttons by permission - the server is the sole enforcement point |
| Performance / scale | List screens read the catalog and per-target grants without per-row fan-out; grant lookups must read the subject's full grant set (never truncated by a default query limit) |
| i18n | Every user-facing label and description is bilingual English/Vietnamese |
7. UX & Flows
Key screens: in the back office - (1) the permission catalog (list / create / edit); (2) the role management screen with the permission tree; (3) the policy-definition screen with target tabs by user / role / merchant, each tab a two-panel layout + grant dialog. The raw policy-record inspection screen serves deep diagnostics (FR-011). In the owner-facing app, and for the operations team when working inside a specific merchant, the same target surface sits under the team module, scoped to the merchant being operated.
8. Data & Domain
| Entity | Role | Key attributes |
|---|---|---|
| Permission | A catalog entry | System-wide unique code; subject (User / Role); action (create / read / update / delete / execute); scope (SYSTEM / ORGANIZER / MERCHANT); bilingual name/description |
| Policy record | A grant attaching a subject to a target | Variant (policy / group); subject type & target type (5 principal kinds: User / Role / Permission / Merchant / Organizer); action; effect (allow / deny); domain (optional - empty means global) |
| Role | A permission recipient | Bilingual name/description; status (Active / Deactivated / Archived); priority; organizer (required on the back office); merchant (optional); the set of permissions assigned directly through the tree |
Conceptual level only - the full schema and policy data model live in the developer RBAC documentation.
9. Dependencies & Assumptions
Depends on
| # | Feature | What it depends on |
|---|---|---|
| 01 | Policy/grant logic (identity) | the policy / permission / grant-revoke logic this surface drives. |
| 02 | Per-merchant authorization (platform core) | enabled; the model + per-merchant policy resolution + scoped grant resolution. |
| 03 | Fixed & custom roles (URD-ROLE · URD-CROLE) | grants attach to existing roles; the role screen is detailed in the Fixed & custom roles PRD. |
| 04 | Bilingual labels | the English/Vietnamese labels the screens render. |
Assumptions
| # | Assumption | What breaks if wrong |
|---|---|---|
| 01 | The catalog/grant/revoke and effective-permission logic pre-exists (built before this increment). | If that logic didn't already exist, this management surface would have nothing to drive - it's a UI, not an engine. |
| 02 | An active merchant is selected per request. | Without an active merchant, policy lookups (for example the list of users holding a role) have no merchant to scope against. |
| 03 | Roles, users, and merchants exist as grant targets. | If a role, user, or merchant doesn't exist yet, the grant dialog has nothing to pick, and a grant has no valid target to attach to. |
10. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P2 - see the feature catalog in the URD (PERM / GRANT / EFF marked Built) |
| Rollout | All merchants; operations surface in the back office + the team-module clone; no feature flag |
| Migration | None - drives existing entities/logic; adds bilingual labels |
| Launch criteria | Acceptance criteria in §5.1 pass on staging |
| Monitoring | Grant/revoke error rate; items skipped by idempotency; periodic reconciliation of grant-lookup results against the actual grant data |
References
- URD: Permissions - Permission Catalog · Grant / Revoke · Effective Permissions & Scope
- Related PRD: Resource, Action & Domain Hierarchy · Fixed & custom roles · Employee management
- Module: Permissions - URD
- Developer: @nx/identity · RBAC · Casbin Authorization
Risks & Open Questions
| # | Risk / question | Mitigation / status |
|---|---|---|
| 01 | Grant lookups truncated by a default query limit | Read the subject's full grant set so users with many grants are never silently dropped |
| 02 | Duplicate grants on repeated operations | Idempotent per the FR-009 rule - existing items are skipped and counted |
| 03 | The two role-permission assignment mechanisms (tree in the role form vs dialog in policy definition) drifting apart | Both write into the same grant set; each mechanism's role is delineated in FR-003 |
| 04 | Merchant-scoped role operations drifting between the two surfaces | Roles scoped to a merchant only exist in the Team area, not on the shared back-office screen - FR-007 |
| 05 | Multiple surfaces sharing the same per-target logic (the shared back office, plus team-module copies for owners and for the operations team inside a merchant) drifting apart | Shared per-target logic; the team-module copies are scope-limited clones, not forks |
| 06 | Per-target raw grants not scaling to coarse access | Superseded by the resource/action/domain hierarchy (PRD-HIER-001) |
Frequently Asked Questions
| # | Question | Answer |
|---|---|---|
| 01 | Does this increment build the authorization engine? | No - the enforcement engine and the grant/revoke/effective-permission logic already exist. This increment is the management surface driving them. |
| 02 | What are the "target sections"? | The policy-definition screen splits into per-target tabs: by user (4 sub-tabs: Roles / Permissions / Merchants / Organizers, with an All / Direct / Inherited mode switcher for permissions), by role (2 sub-tabs: Permissions / Users), and by merchant (Users tab; the Roles tab exists in the Team area, and the shared back-office screen does not have it). Each sub-tab is a grant table + revoke actions; the sub-tabs in the user and role sections also have a grant dialog. |
| 03 | Does a grant apply everywhere? | It depends: when assigning a role to a user, an empty domain means the role applies globally; a filled domain means the role's permissions only apply within that domain. Policy lookups are always scoped to the active merchant, and grant authority is controlled by per-target ownership checks. |
| 04 | Why is grant/revoke idempotent? | So repeated operations never create duplicates: already-granted items are skipped and counted in the "skipped" part of the result, so repeating the same operation always yields the same final state. |
| 05 | How many ways are there to assign permissions to a role? | Two: ticking the permission tree while creating/editing the role (defining the role as a package), or the grant dialog in the role section (one-off adjustments). Both write into the same grant set. |
| 06 | How many ways are there to assign a role to a person? | Three: the Permissions tab in the employee form on the Client app, the Roles tab in the by-person section, or the Users tab in the by-role section. All three write into the same grant set - assigning or revoking through one is reflected immediately in the other two. |
| 07 | Can owners manage policy themselves? | Yes - the same policy-definition surface is cloned into the team module, scoped to the owner's merchant; the operations team uses the same clone when working inside a specific merchant. |
| 08 | What about coarse manage/write grants? | Out of scope here - module roll-up and the resource/action/domain hierarchy belong to PRD-HIER-001. |