URD: Permissions
| Module | Version | Updated |
|---|---|---|
CORE-02 | v0.8 | 2026-07-15 |
What the Permissions module does
Defines how access to features and data is controlled. Permissions lets administrators assign roles to users, grant permissions to those roles, scope a role to a specific organizer or merchant, and have every query automatically filtered to exactly what the requesting user is allowed to see.
All of it runs on a Casbin priority-based RBAC model, with a per-merchant domain scope.
Six points to understand first
This is the most important part of the document. Understanding the six points below is understanding 80% of the module.
Throughout this section we use one running example. Hoa is the Owner of the organizer Sau Beo Coffee, which has 3 merchants: Sau Beo District 1 (headquarters), Sau Beo Thu Duc, and Sau Beo Go Vap. Tuan is a cashier at District 1. Mai is floor staff at Thu Duc.
1. Permissions are granted in clusters, not button by button
The natural instinct is to grant one permission per button. The system does not work that way. The whole system has around 755 grantable operations, grouped into 14 business domains: Sale, Inventory, Commerce, Payment, Finance, Pricing, Invoice, Tax, Ledger, Licensing, Account, Customer Care, Alerts, Support. Granting them one by one would mean the Owner role alone would need to carry all 755 permission rows.
Instead the system grants one row for a whole cluster, then lets it expand automatically. It expands in two directions.
The first direction is the resource tree: a business domain contains subjects, and subjects contain operations. A grant on the Sale domain automatically covers Sale Order, Sale Order Item, Shift, and everything beneath them.
The second direction is the action lattice: a higher tier satisfies every tier below it.
| Level granted | What it includes |
|---|---|
| Full access (manage) | Read + Write + Execute. The highest tier, covers everything. |
| Write | Create, Update, Delete |
| Read | View only, nothing can be changed |
| Execute | Run a business operation (close a shift, issue an invoice) that isn't an ordinary write |
Put both directions together: Hoa's Owner role carries only 14 grant rows - one per business domain, at Full access. Tuan's Cashier role carries far fewer:
| Business domain | Cashier's level | Meaning |
|---|---|---|
| Sale | Full access | Open an order, edit an order, close an order, close a shift |
| Payment | Write | Take payment, record a transaction |
| Inventory | Read | See how much stock is left, cannot receive or issue |
| Commerce | Read | View the item catalog, cannot change prices |
| Invoice | Read | View the invoice status of the order just sold |
Tuan has no row at all for Pricing, Tax, Ledger, or Account. No row means no permission - there's no need to spell out a "deny".
2. Effective permissions merge from multiple sources, and a deny always beats an allow
A person's permissions come from two sources, combined:
- Role-based permissions - Tuan is a Cashier, so he has everything the Cashier role has.
- Permissions granted directly to the individual - Hoa grants Tuan Read on Pricing directly, even though the Cashier role doesn't carry it.
The two sources merge and then deduplicate: a permission present in both sources only counts once. When a screen asks "what can Tuan actually do", the system returns this merged set. It can also be queried separately - "direct grants only" or "role-inherited only" - for auditing who granted what to whom.
But merging isn't a plain union. A grant row doesn't just carry a level - it also carries an effect: allow or deny. And the rule is: a deny beats an allow, no matter how much broader the allow is.
This is the most counter-intuitive part of the whole module. It exists so a merchant owner can punch a small hole in a large grant, instead of dropping the large grant and re-enumerating every small operation by hand.
Three examples running live in the system today:
| Who | Allow row (broad) | Deny row (narrow) | Real result |
|---|---|---|---|
| Mai (Employee) | Write on the whole Sale domain | Deny Write on Shift | Mai can open and edit orders, but cannot open or close a shift - view only |
| Tuan (Cashier) | Full access on the Sale domain | Deny Full access on Shift Adjustment | Tuan closes shifts normally, but cannot edit the numbers on an already-closed shift - only the Owner can |
| Hoa (Owner) | Full access on the Account domain | Deny Write on Root Permission Catalog | Hoa can view the permission list to grant to employees, but cannot invent new permissions herself - that's the system's job |
Notice the last example: even the Owner - the highest role within an organizer - is still denied in a few places. "Owner has full access" is a false statement.
The one exception: the three internal roles Super Admin, Admin, and Operator of the operations team skip this machinery entirely. They pass straight through - no permission check, no deny check, no data filtering. So they don't need a single grant row either.
3. Priority tier: no one can touch a role at or above their own level
Every role carries a priority number. That number is the staircase for the whole system.
| Role | Priority |
|---|---|
| Super Admin | 999 |
| Admin | 900 |
| Operator | 600 |
| Owner | 500 |
| Cashier | 110 |
| Employee | 100 |
| Customer | 10 |
| Guest | 1 |
A self-created role can only sit in the range 100 to 499 - always lower than Owner, always higher than or equal to Employee. No one can create a custom role that stands level with Owner.
The blocking rule fits in one sentence: to create, edit, delete, or grant a role, that role's priority must be STRICTLY LOWER than the actor's own highest priority. Equal doesn't pass either.
Hoa (Owner, 500) wants to create a role "Merchant Manager" at priority 300 for the manager at Thu Duc: allowed. She tries to set that role's priority to 500: blocked. She wants to edit the Operator role (600) of the operations team: blocked. She wants to assign the Owner role to someone else in her own organizer: also blocked, because Owner sits level with her.
One easy-to-miss detail: the system takes the highest priority among the acting user's own roles. If someone is both Employee (100) and Merchant Manager (300), their ceiling is 300, not 100.
The priority tier also decides what's visible: when Hoa opens the role picker to assign a role to an employee, the list only shows roles below 500. Operator, Admin, and Super Admin don't even appear in the list.
4. Grant ceiling: you can't hand out what you don't have
The priority tier only answers "which role can I touch". It doesn't answer "what permission can I put into that role". These are two separate locks, and both have to open.
The second rule: the level you grant someone else can never be higher than the level you yourself hold on that same resource.
| Level you hold | Level you're allowed to grant others |
|---|---|
| Full access | Read, Write, Execute, Full access (all of them) |
| Write | Write only |
| Read | Read only |
| Execute | Execute only |
Back to Sau Beo. Hoa has Full access on the Inventory domain, so she can grant the "Merchant Manager" role any level on Inventory.
But suppose Hoa grants "Merchant Manager" only Read on Inventory. The Thu Duc manager holds that role, and wants to create a further role "Stock Assistant" (priority 200 - lower than his own 300, so it clears the priority lock) and grant it Write on Inventory. Blocked. He only holds Read, so he can only hand out Read. He can't upgrade himself by creating a stronger role and assigning himself into it.
This lock only reads the permissions he holds through a role. Permissions granted directly to the individual don't raise his ceiling - that's a deliberate choice, better to block wrongly than open wrongly. And the three internal operations roles skip this lock too, like everything else.
5. Standing at one merchant means you only see that merchant
This is the most commonly misunderstood point, and getting it wrong leaks data.
Hoa owns all 3 merchants. Intuition says: when she opens the Orders screen she should see orders from all 3. She doesn't.
Every request to the system carries the active merchant - exactly one merchant, chosen by whatever the screen currently has selected. The data filter only filters by that exact merchant. When Hoa is standing at District 1, the Orders screen only shows District 1's orders. To see Thu Duc she has to switch the active merchant in the merchant picker, and the screen reloads.
Her permissions and the merchant she's standing at are two separate things:
| Question | Answered by |
|---|---|
| What can Hoa do? | The Owner role's grant rows (point 1) |
| Where can Hoa do it? | The list of merchants she's a member of |
| Which merchant is this click happening at? | The active merchant, carried on every request |
The grant rows for Owner, Cashier, and Employee are not pinned to any single merchant. They take effect at any merchant the person is a member of. Which means the real boundary doesn't sit in the grant row - it sits in the membership list.
So: Tuan is a member of District 1, not a member of Go Vap. His Cashier role lets him open orders - but only at District 1. If he switches the active merchant to Go Vap, his Cashier permission doesn't follow him there, because he isn't a member there.
One small but important point for testing: when someone calls a record directly by its id, outside their scope, the system returns "not found", not "no permission". This is deliberate - answering "no permission" would inadvertently confirm the record exists.
There's one door outside this rule: permissions meant for a guest (someone not signed in yet, with no merchant at all) are tagged system-wide. That's what lets the new-organizer signup screen, plan lookup, and tax-code lookup keep working at a moment when no merchant exists yet to stand at.
6. Owner reach extends to a new merchant by copying membership in, not by inferring it
Hoa opens a 4th merchant: Sau Beo Binh Thanh. She can manage it right away, with no one granting anything extra. The question is: how does the system pull that off?
Intuition says: the system "understands" that Binh Thanh sits under the Sau Beo Coffee organizer, and that Hoa is the organizer's Owner, so it infers she has permission at Binh Thanh. That's not what happens. The system doesn't infer anything.
What actually happens: the moment Binh Thanh is created, the system scans backward, finds everyone who both holds the Owner role and is a member of the Sau Beo Coffee organizer - which is Hoa - and writes a new membership row: "Hoa is a member of Binh Thanh." That row is real, sits in the data, and can be counted.
The consequence worth remembering, because it's the source of most of the confusing permission bugs support sees:
- The copy only runs once, at merchant-creation time. Anyone promoted to Owner after the merchants already exist doesn't automatically show up at the old merchants - membership has to be added per merchant.
- To revoke an Owner's access at one merchant, you have to remove exactly that merchant's membership row. Removing it at the organizer level doesn't make the merchant rows disappear.
- The number of membership rows grows with the number of merchants. An organizer with 30 merchants means each Owner carries 30 rows.
The three rules in points 3, 4, 5, plus this copying mechanism, explain nearly every "why can't this person click that button" question the support team fields daily.
What the module has
Seven feature groups. Click the AREA code to jump to the Details tab and read that group's requirements.
AREA | Name | Status |
|---|---|---|
ROLE | Fixed Roles | Partial |
CROLE | Custom Roles | Partial |
PERM | Permission Catalog | Built |
GRANT | Grant / Revoke | Partial |
EFF | Effective Permissions & Scope | Partial |
HIER | Resource, Action & Domain Hierarchy | Partial |
DECL | Permission Declaration | Partial |
1. Definitions
| Term | Definition |
|---|---|
| Role | A named access level with a numeric priority. SYSTEM (fixed) or CUSTOM (user-created). |
| Permission | A named action on a resource, identified by a globally unique code. |
| Grant (policy) | A record granting a permission to a role or directly to a user. |
| Membership (group) | A record linking a user to a role, a user to an org/merchant, or a role to an org/merchant. |
| Effective permissions | The union of all permissions a user holds: direct grants + permissions inherited through roles. |
| Scoped role | A custom role bound to a specific organizer or merchant, limiting where it applies. |
| Domain | The merchant a grant applies within, selected per request by the active-merchant header. |
| System-role bypass | Super Admin, Admin, and Operator skip all data filtering and hold every permission. |
| HQ-owner expansion | An Owner at a head-quarter merchant automatically reaches every sibling merchant of that organizer. |
| Privilege escalation | Managing a role at or above the actor's own priority - always blocked. |
2. Conceptual Model
Conceptual only - the full policy data model lives in the developer RBAC docs.
3. Features
ROLE - Fixed Roles Partial
Spec: PRD-ROLE-001 (shared with
ROLEandCROLE) · 10 requirements · 2 acceptance scenarios
Eight system roles ship pre-seeded at startup, ordered by a fixed numeric priority. The top three roles - Super Admin, Admin, Operator - see all data regardless of organizer or merchant. Owner sees only their own organizer and its merchants; Cashier and Employee see only the merchants they're assigned to. This filtering runs automatically on every list and count operation, and nothing overrides it - not a user-supplied filter, not calling a record directly by ID outside scope.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-ROLE-001 | Must | Provide eight fixed roles seeded at startup: Super Admin, Admin, Operator, Owner, Cashier, Employee, Customer, Guest. | Partial |
URD-ROLE-002 | Must | Fixed (SYSTEM) roles cannot be modified or deleted. | Built |
URD-ROLE-003 | Must | Each role has a numeric priority that sets its place in the hierarchy. | Built |
URD-ROLE-004 | Must | Super Admin, Admin, and Operator bypass all data filtering and hold every permission. | Built |
URD-ROLE-005 | Must | Owner sees only their own organizer and its merchants. | Built |
URD-ROLE-006 | Must | Employee (and Cashier) see only merchants they are assigned to. | Built |
URD-ROLE-007 | Must | Every list and count operation is filtered by the requesting user's scope. | Built |
URD-ROLE-008 | Must | Automatic filtering cannot be overridden by a user-supplied filter or direct ID access. | Built |
URD-ROLE-009 | Should | An Owner at a head-quarter merchant reaches every sibling merchant of that organizer. | Built |
URD-ROLE-010 | Must | The Owner role is automatically granted to a user when they create their own organizer. | Built |
AC-ROLE-01: Data filtering by role
| Given | When | Then |
|---|---|---|
| Users with different roles | They hit the same endpoint | Super Admin/Admin/Operator see all; Owner sees own org; Employee/Cashier see assigned merchants |
| Any list/count query | Run by a scoped user | Filtering is automatic and cannot be overridden |
| A scoped user supplies a wider filter or a foreign ID | Request is made | The filter is ignored / access denied |
AC-ROLE-02: Fixed-role protection
| Given | When | Then |
|---|---|---|
| Any of the eight system roles | User tries to modify or delete it | Operation rejected |
| A fixed role's priority | User tries to change it | Rejected - priorities are immutable |
CROLE - Custom Roles Partial
Spec: PRD-ROLE-001 (shared with
ROLEandCROLE) · 8 requirements · 2 acceptance scenarios
Administrators and Owners create their own roles with a custom name, priority, and an optional organizer/merchant scope - the priority always has to sit below the creator's own current priority. The role identifier is auto-generated by the system from priority + name, never typed by hand. Deleting a custom role is blocked while users remain assigned to it; a successful delete cascades away its permission grants and scope links in the same operation.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-CROLE-001 | Must | Authorized users can create custom roles with an i18n name, priority, and optional scope. | Partial |
URD-CROLE-002 | Must | Role identifier is auto-generated from priority + name and is unique within its scope. | Built |
URD-CROLE-003 | Must | A new role's priority must be strictly lower than the creator's own highest priority. | Partial |
URD-CROLE-004 | Must | Custom roles can be scoped to a specific organizer or merchant. | Partial |
URD-CROLE-005 | Must | Custom roles can be updated (name, description, priority). | Built |
URD-CROLE-006 | Must | A role cannot be deleted while users are still assigned to it. | Built |
URD-CROLE-007 | Must | Deleting a role cascade-removes its permission grants and scope links. | Built |
URD-CROLE-008 | Should | An Owner can create roles scoped only to their own organizer or merchants. | Built |
AC-CROLE-01: Custom role creation
| Given | When | Then |
|---|---|---|
| Admin or Owner | Creates a role with priority + name | Role created with type CUSTOM, identifier auto-generated |
| Priority ≥ creator's own | Creation attempted | Rejected (privilege escalation) |
| Org/merchant scope provided | Role created | Scope link created |
| Same priority + name in same scope | Creation attempted | Rejected (identifier collision) |
AC-CROLE-02: Role deletion
| Given | When | Then |
|---|---|---|
| Custom role with no users | Admin deletes | Role + grants + scope links soft-deleted |
| Custom role with assigned users | Admin deletes | Blocked - must unassign users first |
| System role | Admin deletes | Rejected - fixed roles are immutable |
PERM - Permission Catalog Built
Spec: PRD-PERM-001 (shared with
PERMandGRANT) · 5 requirements · 1 acceptance scenario
Administrators maintain the catalog of permissions: each is a uniquely coded action on a resource, with a bilingual name and description. A permission with active grants cannot be removed - the grants have to be revoked first.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-PERM-001 | Must | Admin can create permissions with a unique code, action, scope, and subject. | Built |
URD-PERM-002 | Must | Permission code must be globally unique. | Built |
URD-PERM-003 | Must | Permission name and description support i18n. | Built |
URD-PERM-004 | Must | Admin can update and delete permissions. | Built |
URD-PERM-005 | Must | A permission with active grants cannot be deleted. | Built |
AC-PERM-01: Permission catalog integrity
| Given | When | Then |
|---|---|---|
| Admin defines a permission | A unique code, action, scope, and subject are supplied | The permission is created |
| A duplicate code | Creation attempted | Rejected - codes are globally unique |
| A permission with active grants | Admin deletes | Blocked - must revoke grants first |
GRANT - Grant / Revoke Partial
Spec: PRD-PERM-001 (shared with
PERMandGRANT) · 7 requirements · 1 acceptance scenario
Administrators attach or remove permissions on a role, and assign or unassign roles to users. Every operation is idempotent: granting an already-granted permission is skipped, never duplicated. The privilege-escalation guard runs on every grant/revoke operation, blocking the instant it would touch a role at or above the actor's own priority.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-GRANT-001 | Must | Admin can grant one or more permissions to a role. | Partial |
URD-GRANT-002 | Must | Admin can revoke one or more permissions from a role. | Partial |
URD-GRANT-003 | Must | Granting an already-granted permission is idempotent (skipped, not duplicated). | Partial |
URD-GRANT-004 | Must | Admin can grant and revoke roles to/from users. | Partial |
URD-GRANT-005 | Must | Privilege-escalation guard is enforced on every grant/revoke operation. | Partial |
URD-GRANT-006 | Must | Admin can view all permissions granted to a role. | Partial |
URD-GRANT-007 | Must | Admin can view all users assigned to a role and all roles assigned to a user. | Partial |
AC-GRANT-01: Permission grant
| Given | When | Then |
|---|---|---|
| Role + permission IDs | Admin grants | Grant records created, count returned |
| Permission already granted | Admin grants again | Skipped (idempotent), skip count returned |
| Actor priority ≤ role priority | Admin grants | Rejected (privilege escalation) |
EFF - Effective Permissions & Scope Partial
Spec: PRD-EFF-001 · 4 requirements · 1 acceptance scenario
Anyone can ask what a user is actually allowed to do: the deduplicated union of direct grants and role-inherited grants, and which organizers and merchants they belong to. A grant resolves only within the active merchant domain, chosen per request.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-EFF-001 | Must | Compute effective permissions as the union of direct + role-inherited grants (deduplicated). | Built |
URD-EFF-002 | Should | The query supports a mode filter: direct, inherit, or both. | Built |
URD-EFF-003 | Must | Retrieve all organizers and all merchants a user belongs to. | Built |
URD-EFF-004 | Must | A grant resolves only within the active merchant domain chosen per request. | Partial |
AC-EFF-01: Effective permissions
| Given | When | Then |
|---|---|---|
| User with roles + direct grants | Effective query | Union of direct + inherited, deduplicated |
| Mode = direct | Query | Only direct grants returned |
| Mode = inherit | Query | Only role-inherited grants returned |
HIER - Resource, Action & Domain Hierarchy Partial
Spec: PRD-HIER-001 (shared with
HIERandDECL) · 9 requirements · 2 acceptance scenarios
Permissions form a resource tree × action lattice × domain tree, so one coarse grant (e.g. Sale:manage) replaces many flat grants - covering every subject, operation, and merchant beneath it. A head-quarter / management role reaches every merchant of its own organizer via an organizer-scoped grant, instead of needing a per-merchant membership row - this organizer-scoped reach is still being built.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-HIER-001 | Must | A grant MAY target a resource at any level - module, subject, or operation - and automatically covers all descendants. | Built |
URD-HIER-002 | Must | Operation resources nest under their subject by dotted code (SaleOrder.refund ⊂ SaleOrder) with no extra configuration. | Built |
URD-HIER-003 | Must | A subject MAY roll up to a module so a module-level grant covers every subject in it (grant on Sale ⇒ SaleOrder, SaleOrderItem, …). | Built |
URD-HIER-004 | Should | A child entity MAY nest under a parent across naming (SaleOrderItem ⊂ SaleOrder) so the parent grant covers the child. | Not built |
URD-HIER-005 | Must | Actions form a lattice - manage ⊃ {write, read, execute}, write ⊃ {create, update, delete} - a broader granted action satisfies any narrower request. | Built |
URD-HIER-006 | Must | A route requests a base action (read/create/update/delete/execute); a grant MAY use any tier including manage/write. | Built |
URD-HIER-007 | Must | A grant scoped to an Organizer applies to every merchant under it; a grant scoped to a Merchant applies only there. | Not built |
URD-HIER-008 | Must | A head-quarter / management role reaches all merchants of its organizer via an organizer-scoped grant - without a per-merchant membership row. | Not built |
URD-HIER-009 | Should | A grant MAY pin to SYSTEM_WIDE (everywhere) or ANY_MEMBER (every joined merchant). | Built |
AC-HIER-01: Coarse grant covers descendants (manage example)
| Given | When | Then |
|---|---|---|
A role granted (SaleOrder, manage) | A request to read SaleOrder.refund | Allowed - manage ⊃ read (lattice) and SaleOrder.refund ⊂ SaleOrder (dotted) |
A role granted (Sale, manage) (module) | Any operation on SaleOrderItem | Allowed - SaleOrderItem ⊂ Sale (module roll-up) |
A role granted (SaleOrder, read) only | A request to create SaleOrder | Denied - read does not cover create |
AC-HIER-02: Domain scope (organizer vs merchant / HQ)
| Given | When | Then |
|---|---|---|
A role granted on Organizer_9 | A request in any Merchant under organizer 9 | Allowed via g3 - no per-merchant membership needed |
A role granted on Merchant_7 only | A request in Merchant_8 | Denied - scope is a single merchant |
| An HQ/management user (organizer-scoped grant) | Acting on any sibling merchant | Allowed - reaches all merchants of the organizer |
DECL - Permission Declaration Partial
Spec: PRD-HIER-001 (shared with
HIERandDECL) · 5 requirements · 1 acceptance scenario
Permissions, the action lattice, and the resource/domain hierarchy are declared in code with minimal boilerplate and seeded idempotently, so a new controller needs one helper call and removed codes are reconciled away automatically.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-DECL-001 | Must | Permissions, the action lattice, and resource/domain hierarchy edges are declared in code and seeded idempotently. | Partial |
URD-DECL-002 | Must | Declaring a controller's operations + resource node + base action takes minimal boilerplate (one helper call). | Built |
URD-DECL-003 | Must | Role→grant assignment is declared as a small matrix (e.g. OWNER → Sale:manage), not a per-operation enumeration. | Built |
URD-DECL-004 | Must | Seeding reconciles removed codes idempotently - no stale permissions/grants accumulate. | Built |
URD-DECL-005 | Could | The toolkit is built in nx-seller first, with a clear path to port into IGNIS later. | Built |
AC-DECL-01: Minimal-boilerplate declaration
| Given | When | Then |
|---|---|---|
| A new controller | Author declares its resource + operations in one helper call | Permissions + route authorize specs are produced without enumerating CRUD |
| The action lattice + hierarchy edges | Migrations run | g5/g4/g3 edges seeded idempotently; re-run changes nothing |
| A permission code removed from the declaration | Migrations run | The stale permission + its grants are reconciled away |
4. Constraints
| ID | Constraint |
|---|---|
C-01 | Eight fixed roles are seeded at startup and are immutable. |
C-02 | No one can create or manage a role at or above their own priority. |
C-03 | Permission codes are globally unique. |
C-04 | Grant operations are idempotent. |
C-05 | All records are soft-deleted, never physically removed. |
C-06 | The session token is stateless - role/permission changes take effect on next sign-in. |
C-07 | All operations require authentication. |
C-08 | A grant resolves within one active merchant domain per request. |
5. Non-Goals
Permissions deliberately does not do the following.
| ID | Not done |
|---|---|
N-01 | Arbitrary glob/regex permissions (e.g. sales.*) - coarse access comes from the resource tree + manage action (HIER), not globs. |
N-02 | Time- or shift-based permissions. |
N-03 | Permission audit log. |
N-04 | Per-merchant active-role switching. |
Superseded by
HIER: the former non-goals "wildcard permissions", "permission categories / UI grouping" (now module roll-up), and "role templates / bundles" (now a coarse role→grant matrix) are replaced by the resource/action/domain hierarchy.
Version History
| Date | Author | Description | Version |
|---|---|---|---|
| 2026-02-26 | P. Do - Product Owner | Initial user stories | v0.1 |
| 2026-04-16 | Product | Restructured to URD format | v0.3 |
| 2026-05-29 | Product | Migrated to module-docs convention; reconciled with Casbin per-merchant RBAC (8 roles, priorities, domains); re-keyed areas (ROLE/CROLE/PERM/GRANT/EFF) | v0.4 |
| 2026-06-04 | Product | Added Resource/Action/Domain Hierarchy (HIER) + Permission Declaration (DECL) areas for the resource-action-hierarchy increment (PRD-HIER-001); superseded wildcard/categories/bundles non-goals | v0.5 |
| 2026-06-04 | Claude (AI pair) | Reorganize by feature (Feature Spine); each feature carries its own requirements + acceptance | v0.6 |
| 2026-07-06 | P. Nguyen | Absorbed role definitions merged from the User Management URD (its area became MORG); added URD-ROLE-010 (Owner auto-granted on organizer creation); priority column switched to full words, requirement ids backtick-wrapped; filled PRD links for ROLE/CROLE/PERM/GRANT/EFF | v0.7 |
| 2026-07-15 | Claude (AI pair) | Migrated to the standard Inventory presentation format: three-tab DocTabs (Overview / Details / History), a 4-column requirement table with a per-row status badge, ::: details AC-<CODE>-NN blocks for acceptance criteria; folded the old §3 Scope table into Non-Goals; no requirement content or ratified decision changed | v0.8 |