Skip to content

PRD: Resource, Action & Permission Declaration Hierarchy

ModulePermissionsPRD IDPRD-HIER-001
StatusReady to devFEATHIER
EpicPlaneBANA-1572
Date2026-06-04Versionv1.0
Packages@nx/identityURDHIER
SurfaceBackend
OwnerPhát Nguyễn

What the resource, action & declaration hierarchy is

The merchant-scoped RBAC engine supports three inheritance axes BANA has not fully exploited yet: an action axis (manage covers read), a resource axis (an order's refund operation sits under the order; an order's line items sit under the order), and a scope axis (an organizer covers its merchants).

Before this increment, permissions were flat: close to 950 per-operation permission codes, each granted one row at a time. An owner role carried close to 950 grant rows; "headquarters sees every merchant" was faked by writing one membership row per merchant. There was no broad manage/write action, and no convenient way to declare permissions - every module hand-rolled its own declaration. The result was roles that were huge and brittle, slow to grow when a new operation was added, and hard to read back to reason about who could do what.

Three axes nested together

This increment models permissions as a resource tree times an action lattice times a scope tree, so a role collapses to a handful of coarse grants while a role that needs fine-grained access can still grant each operation precisely, and turns declaring a new controller's permissions into a single call.

When an admin grants access to the owner role on the create/edit role screen, they now pick one management grant for the whole Sale module instead of ticking every operation one by one - every subject and operation under Sale is covered automatically. Granting access at the module or subject level unlocks every screen and sub-operation underneath right away, instead of granting each operation one at a time. A headquarters/management role reaches every merchant under its organizer through a single organizer-scoped grant, with no per-merchant membership row required.

An engineer declares a new controller's permissions - its resource, its operations, and their base action - with one call in the code, instead of hand-writing every permission row. When the migration runs at backend startup, permissions and hierarchy edges sync automatically and stay unchanged on repeated runs; a permission code removed from the code declaration gets cleaned up automatically along with its grant rows, so nothing accumulates.

The easiest place to get it wrong

"Manage Sale" is not a wildcard

A grant's coarseness comes from the resource tree plus the manage action, not from a wildcard pattern like "everything under sale". There is no arbitrary wildcard permission in the system - every breadth flows through an explicitly declared hierarchy.

Likewise, a headquarters role's reach through the scope axis (organizer covers merchant) is a separate edge that is still being built - today a headquarters role still needs a membership row per merchant, and cannot reach them through the organizer-scoped grant alone yet.

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.