# Menu & User Access Control — Implementation Plan & Status

**Branch:** `feature/settlement-recon-core`
**Plan date:** 2026-03-08
**Last updated:** 2026-03-09
**Project:** `tmsuat_apps` (umbrella)
**Apps in scope:** `platform_web`, `da_product_app`, `settlement_core`, `platform_core`
**Out of scope (this plan):** business logic changes in settlement/reconciliation engines

---

## Implementation Status Summary

| Phase | Title | Status |
|---|---|---|
| 0 | Discovery, Catalog & Freeze | ✅ Complete |
| 1 | Authorization Foundation | ✅ Complete |
| 2 | Permission Data Model Hardening | ✅ Complete |
| 3 | Menu Engine in `platform_web` | ✅ Complete |
| 4 | App-Owned Menu Providers | ✅ Complete |
| 5 | Route Guards & LiveView Action Guards | ✅ Complete |
| 6 | Menu Sections (Settlement + Users) | ✅ Complete |
| 7 | Admin UX — User Creation, Role & Permission Management | ✅ Complete (extended beyond original plan) |
| 8 | Tests, Rollout & Backward Compatibility Cutover | 🔲 Pending |

---

## Objective

Implement a centralized, permission-driven navigation and authorization system with these capabilities:

1. Dynamic left menu based on logged-in user permissions.
2. Role-based and permission-based access control for route visibility and actions.
3. Action-level controls (`view`, `edit`, `delete`, plus domain actions like `approve`, `release`, `transmit`).
4. App-owned menu definitions (for example: settlement menu defined in `settlement_core`) and composed centrally in `platform_web`.
5. Backward-compatible rollout from current hardcoded menu and mixed authorization checks.
6. Custom named roles that group permissions and can be assigned to users.
7. Admin-initiated user creation with role assignment at creation time.

---

## Current-State Summary (Observed at Plan Start)

1. Authentication was centralized in `PlatformWeb.UserAuth`.
2. User/role/permission tables existed in `da_product_app` (`modules`, `permissions`, `user_permissions`).
3. `platform_web` rendered a hardcoded menu in `SidebarComponent`.
4. Authorization checks were inconsistent — some role-based, some ad-hoc.
5. No user creation UI existed in user management.
6. No named custom roles (only static `:user`, `:admin`, `:superuser` enum on the `users` table).

---

## Target Architecture

1. **Authorization Core (web-facing):** `PlatformWeb.Authorization`
2. **Menu Registry (web-facing):** `PlatformWeb.Menu.Registry`
3. **Menu Provider Behavior (extension point):** `PlatformCore.Menu.Provider`
4. **App-owned provider modules:**
   - `SettlementCore.MenuProvider`
   - `DaProductApp.MenuProvider`
5. **Permission catalog and role defaults:** maintained in `da_product_app` seeds + migrations.
6. **Custom Role RBAC layer:** `roles`, `role_permissions`, `user_roles` tables + `Role`/`UserRole` schemas.
7. **Route/action guards:** standardized `on_mount` and controller plugs.

---

## Permission Naming Standard

Canonical format: `<domain>.<resource>.<action>`

| # | Permission Name | Description |
|---|---|---|
| 1 | `users.account.view` | View user accounts |
| 2 | `users.account.edit` | Create and edit user accounts |
| 3 | `users.account.delete` | Delete user accounts |
| 4 | `users.role.assign` | Assign roles to users |
| 5 | `users.permission.view` | View user permissions |
| 6 | `users.permission.manage` | Grant, revoke, and manage custom roles/permissions |
| 7 | `terminal.device.view` | View terminal devices |
| 8 | `terminal.device.edit` | Create and edit terminal devices |
| 9 | `transaction.record.view` | View transaction records |
| 10 | `settlement.dashboard.view` | View settlement dashboard |
| 11 | `settlement.files.view` | View settlement files |
| 12 | `settlement.mis.view` | View MIS reports |
| 13 | `settlement.mis.generate` | Generate MIS reports |
| 14 | `settlement.mis.approve_l1` | Level-1 approve MIS |
| 15 | `settlement.mis.approve_l2` | Level-2 approve MIS |
| 16 | `settlement.mis.reject` | Reject MIS reports |
| 17 | `settlement.adjustments.view` | View merchant adjustments |
| 18 | `settlement.adjustments.edit` | Create and edit adjustments |
| 19 | `settlement.payouts.view` | View payout batches |
| 20 | `settlement.payouts.transmit` | Transmit payout batches to bank |
| 21 | `settlement.payouts.reinitiate` | Re-initiate failed payouts |
| 22 | `settlement.risk_holds.view` | View risk-held transactions |
| 23 | `settlement.risk_holds.release` | Release risk-held transactions |
| 24 | `settlement.tid_master.view` | View TID master records |
| 25 | `settlement.merchant_portal.view` | View merchant settlement portal |
| 26 | `reconciliation.exceptions.view` | View reconciliation exceptions |
| 27 | `reconciliation.exceptions.release` | Release reconciliation exceptions |
| 28 | `reconciliation.exceptions.bulk_release` | Bulk-release reconciliation exceptions |

**Legacy alias map** (for backward compatibility — old names accepted at runtime):

| Legacy name | Canonical name |
|---|---|
| `user:read` | `users.account.view` |
| `user:write` | `users.account.edit` |
| `terminal:read` | `terminal.device.view` |
| `terminal:write` | `terminal.device.edit` |
| `transaction:read` | `transaction.record.view` |
| `settlement:read` | `settlement.dashboard.view` |
| `manage_users` | `users.account.edit` |

---

## Phase 0 — Discovery, Catalog, and Freeze — ✅ Complete

**Outcome:** Permission matrix finalized, naming standard agreed, route inventory complete.

**All canonical permission names seeded** idempotently in `apps/da_product_app/priv/repo/seeds.exs`.

---

## Phase 1 — Authorization Foundation — ✅ Complete

**File:** `apps/platform_web/lib/platform_web/authorization.ex`

### API

| Function | Purpose |
|---|---|
| `can?(user, permission)` | Check single permission; superusers always pass |
| `can_any?(user, permissions)` | Any-of check |
| `can_all?(user, permissions)` | All-of check |
| `load_permissions(socket)` | Load + cache permissions into process dictionary |
| `require_permission_live(socket, permission)` | Redirect to `/dashboard` if not authorized |
| `require_permission_conn(conn, permission)` | Plug-level guard with redirect |

### Caching

Permissions are fetched from the database once per socket-mount and cached in the process dictionary as a `MapSet`. Subsequent `can?/2` calls use the cached set with no additional DB queries.

### Permission query (post Role RBAC update)

The cache query unions **both** sources:
1. Direct `user_permissions` grants
2. Permissions inherited via `user_roles` → `role_permissions`

```
user_permissions
  JOIN permissions ON permission_id
WHERE user_id = ?

UNION

user_roles
  JOIN role_permissions ON role_id
  JOIN permissions ON permission_id
WHERE user_id = ?
```

### `UserAuth` on_mount callbacks added

| Callback | Behavior |
|---|---|
| `:mount_current_user` | Assigns current user; no redirect |
| `:ensure_authenticated` | Redirects to `/users/log_in` if unauthenticated |
| `:load_permissions` | Assigns user + loads permission cache |
| `{:require_permission, perm}` | Loads permissions + redirects if not authorized |
| `:redirect_if_user_is_authenticated` | Redirects to `/dashboard` if already logged in |

---

## Phase 2 — Permission Data Model Hardening — ✅ Complete

### Existing tables (pre-plan)

| Table | Purpose |
|---|---|
| `users` | User accounts; `role` enum field (`:user`, `:admin`, `:superuser`) |
| `modules` | Groups permissions by domain (User Mgmt, Settlement, etc.) |
| `permissions` | Named permission keys, belongs to a module |
| `user_permissions` | Direct user↔permission grant join table |

### Seeds — `apps/da_product_app/priv/repo/seeds.exs`

- All 28 canonical permissions seeded across 5 modules
- Idempotent (`get_or_create` pattern); safe to re-run
- Default admin user seeded: `admin@example.com` / `admin123` (`:superuser`)

### Role default bundles — `apps/da_product_app/lib/da_product_app/users.ex`

| System role | Default permissions |
|---|---|
| `:user` | View-only across all domains |
| `:admin` | All permissions except `users.permission.manage` |
| `:superuser` | All permissions (bypasses checks entirely in `Authorization`) |

Default permissions are assigned automatically on `register_user/1` via `Ecto.Multi`.

---

## Phase 3 — Menu Engine — ✅ Complete

### Files

| File | Description |
|---|---|
| `apps/platform_core/lib/platform_core/menu/item.ex` | Menu item struct |
| `apps/platform_core/lib/platform_core/menu/provider.ex` | `@callback menu_items(context) :: [Item.t()]` behavior |
| `apps/platform_web/lib/platform_web/menu/registry.ex` | Collects providers, filters by permission, sorts, builds menu |
| `apps/platform_web/lib/platform_web/components/sidebar_component.ex` | Delegates entirely to `Registry.build/1` |
| `apps/platform_web/lib/platform_web/components/layouts/app.html.heex` | Passes `current_user` + `current_page` to sidebar |

### Menu item struct fields

`id`, `label`, `path`, `icon`, `group`, `order`, `required_permissions`, `children`, `source_app`

### Feature flag

```elixir
config :platform_web, :dynamic_menu, true
```

When `false`, Registry falls back to `legacy_menu/0` (hardcoded list).

### Group display order

| Order | Group |
|---|---|
| 0 | Dashboard |
| 1 | Terminal Management |
| 2 | Parameter Management |
| 3 | OTA Updates |
| 4 | Settlement |
| 5 | Reconciliation |
| 6 | Security & Compliance |
| 7 | System |

---

## Phase 4 — App-Owned Menu Providers — ✅ Complete

### Config registration — `config/config.exs`

```elixir
config :platform_web, :menu_providers, [
  DaProductApp.MenuProvider,
  SettlementCore.MenuProvider
]
```

### `DaProductApp.MenuProvider`

**File:** `apps/da_product_app/lib/da_product_app/menu_provider.ex`

| Item | Path | Required permission |
|---|---|---|
| Overview | `/dashboard` | _(none)_ |
| Analytics | `/analytics` | _(none)_ |
| Alerts | `/alerts` | _(none)_ |
| All Terminals | `/terminals` | `terminal.device.view` |
| Locations | `/terminals_all/locations` | `terminal.device.view` |
| Groups | `/terminals/groups` | `terminal.device.view` |
| Parameter Dashboard | `/admin/parameters` | _(none)_ |
| Templates | `/admin/parameters/templates` | _(none)_ |
| Push Logs | `/admin/parameters/logs` | _(none)_ |
| Device Config | `/admin/device-configs` | _(none)_ |
| App Packages | `/ota/packages` | _(none)_ |
| Update Jobs | `/ota/jobs` | _(none)_ |
| Update Status | `/ota/status` | _(none)_ |
| Audit & Compliance | `/audit-compliance` | _(none)_ |
| User Management | `/admin/users` | `users.account.view` |
| Role Management | `/admin/roles` | `users.permission.manage` |
| My Profile | `/users/settings` | _(none)_ |

### `SettlementCore.MenuProvider`

**File:** `apps/settlement_core/lib/settlement_core/menu_provider.ex`

| Item | Path | Required permission |
|---|---|---|
| Settlement Dashboard | `/admin/settlements` | `settlement.dashboard.view` |
| File Tracker | `/admin/settlements/files` | `settlement.files.view` |
| MIS Approval | `/admin/settlements/mis` | `settlement.mis.view` |
| Adjustments | `/admin/settlements/adjustments` | `settlement.adjustments.view` |
| Payouts | `/admin/settlements/payouts` | `settlement.payouts.view` |
| Risk Holds | `/admin/settlements/risk-holds` | `settlement.risk_holds.view` |
| TID Master | `/admin/settlements/tid-master` | `settlement.tid_master.view` |
| Recon Exceptions | `/admin/reconciliation/exceptions` | `reconciliation.exceptions.view` |
| Merchant Portal | `/merchant/settlements` | `settlement.merchant_portal.view` |

---

## Phase 5 — Route Guards & Action Guards — ✅ Complete

### Router live_sessions — `apps/platform_web/lib/platform_web/router.ex`

| Session name | Gate | Routes covered |
|---|---|---|
| `:default` | authenticated + permissions loaded | Dashboard, terminals, OTA, parameters, risk management |
| `:settlement_admin` | `settlement.dashboard.view` | All `/admin/settlements/*` and `/admin/reconciliation/*` |
| `:merchant_portal` | `settlement.merchant_portal.view` | `/merchant/settlements/*` |
| `:require_authenticated_user` | authenticated + permissions loaded | `/admin/users`, `/admin/roles`, user settings |

### Action-level guards

All `handle_event` handlers in `UserManagementLive` and `RoleManagementLive` call `authorized?/1` before performing mutations. Settlement LiveViews gate write actions (approve, reject, release, transmit) with `Authorization.can?/2`.

---

## Phase 6 — Menu Sections — ✅ Complete

All settlement, reconciliation, and users menu sections are live and permission-filtered. See Phase 4 tables above for full coverage.

---

## Phase 7 — Admin UX: User Creation, Role & Permission Management — ✅ Complete

This phase exceeded the original plan scope. In addition to the permission assignment panel, a full **custom Role RBAC layer** was implemented.

---

### 7.1 — Database: New Role RBAC Tables

Three new migrations added under `apps/da_product_app/priv/repo/migrations/`:

| Migration file | Table created | Purpose |
|---|---|---|
| `20260310000001_create_roles.exs` | `roles` | Named role entities |
| `20260310000002_create_role_permissions.exs` | `role_permissions` | Role ↔ permission join |
| `20260310000003_create_user_roles.exs` | `user_roles` | User ↔ role join (with audit fields) |

#### `roles` table

| Column | Type | Notes |
|---|---|---|
| `id` | serial PK | |
| `name` | varchar | unique |
| `description` | varchar | |
| `active` | boolean | default `true` |
| `inserted_at` / `updated_at` | utc_datetime | |

#### `role_permissions` table

| Column | Type | Notes |
|---|---|---|
| `id` | serial PK | |
| `role_id` | FK → `roles` | cascade delete |
| `permission_id` | FK → `permissions` | cascade delete |

Unique index on `(role_id, permission_id)`.

#### `user_roles` table

| Column | Type | Notes |
|---|---|---|
| `id` | serial PK | |
| `user_id` | FK → `users` | cascade delete |
| `role_id` | FK → `roles` | cascade delete |
| `assigned_by` | FK → `users` | nilify on delete |
| `assigned_at` | utc_datetime | |
| `inserted_at` / `updated_at` | utc_datetime | |

Unique index on `(user_id, role_id)`.

---

### 7.2 — New Ecto Schemas

| Schema | File | Purpose |
|---|---|---|
| `DaProductApp.Users.Role` | `apps/da_product_app/lib/da_product_app/users/role.ex` | Role entity with `many_to_many :permissions` |
| `DaProductApp.Users.UserRole` | `apps/da_product_app/lib/da_product_app/users/user_role.ex` | User↔role join with audit fields |

#### Schema relationship updates

| Schema | New association |
|---|---|
| `DaProductApp.Users.User` | `many_to_many :roles, Role, join_through: "user_roles"` |
| `DaProductApp.Users.Permission` | `many_to_many :roles, Role, join_through: "role_permissions"` |

---

### 7.3 — Context Functions Added: `DaProductApp.Users`

**File:** `apps/da_product_app/lib/da_product_app/users.ex`

#### Role CRUD

| Function | Description |
|---|---|
| `list_roles/0` | All roles ordered by name, permissions preloaded |
| `get_role!(id)` | Single role with permissions preloaded |
| `change_role/2` | Changeset for form binding (Role) |
| `create_role(attrs, permission_ids)` | Inserts role + populates `role_permissions` in a transaction |
| `update_role(role, attrs, permission_ids)` | Updates role + replaces full permission set atomically |
| `delete_role(role)` | Deletes role; cascades to `role_permissions` and `user_roles` |

#### User-Role assignment

| Function | Description |
|---|---|
| `list_user_roles(user)` | Returns `[Role]` assigned to a user (with permissions preloaded) |
| `assign_role_to_user(user, role, assigned_by)` | Inserts `user_roles` row if not already present |
| `revoke_role_from_user(user, role)` | Deletes `user_roles` row |
| `list_all_user_permissions(user)` | Union of direct grants + role-inherited (for display) |

#### User management helpers

| Function | Description |
|---|---|
| `change_user/2` | `registration_changeset` with `hash_password: false` — safe for live form validation |
| `confirm_user/1` | Sets `confirmed_at` directly — used for admin-created users |

---

### 7.4 — Authorization Update: Role-Inherited Permissions

**File:** `apps/platform_web/lib/platform_web/authorization.ex`

Both `load_permission_names/1` (cache build) and `db_has_permission?/2` (fallback) now union:

1. Direct grants from `user_permissions`
2. Permissions inherited via `user_roles` → `role_permissions`

All existing `can?/2` calls in LiveViews and menu filtering automatically benefit — no changes needed in call sites.

---

### 7.5 — New LiveView: `RoleManagementLive`

**File:** `apps/platform_web/lib/platform_web/live/role_management_live.ex`
**Route:** `GET /admin/roles`
**Access:** `users.permission.manage` OR `:admin`/`:superuser` system role

#### Features

| Feature | Detail |
|---|---|
| Role list | Table showing name, description, permissions (with "+N more" overflow), active status |
| Create role | Modal with name, description, active toggle, permission checkboxes grouped by module |
| Edit role | Same modal pre-populated; replaces entire permission set on save |
| Delete role | `data-confirm` prompt; cascades cleanly from DB |
| Live counter | Selected permission count updates as checkboxes are toggled |
| Per-module grouping | Each module shown as a collapsible section with own selected/total counter |

#### State assigns

| Assign | Type | Purpose |
|---|---|---|
| `@roles` | `[Role]` | Full role list |
| `@permissions_by_module` | `[{Module, [Permission]}]` | For modal checkboxes |
| `@show_modal` | boolean | Create/edit modal visibility |
| `@editing_role` | `Role \| nil` | `nil` = create, struct = edit |
| `@form` | `Form` | Role changeset form |
| `@selected_permission_ids` | `MapSet` | Currently selected permission IDs in modal |

---

### 7.6 — Updated LiveView: `UserManagementLive`

**File:** `apps/platform_web/lib/platform_web/live/user_management_live.ex`
**Route:** `GET /admin/users`

#### New: Create User

**"Add User" button** in page header opens a modal.

| Form field | Required | Notes |
|---|---|---|
| Full Name | Yes | Letters + spaces, min 2 chars |
| System Role | Yes | `user` / `admin` / `superuser` dropdown |
| First Name | No | |
| Last Name | No | |
| Email | Yes | Unique, validated |
| Password | Yes | Min 12 chars |
| Confirm Password | Yes | Must match |
| Custom Roles | No | Checkbox grid of all active roles |

**Behaviour on submit:**
1. `Users.register_user/1` — inserts user + assigns default permissions by system role
2. `Users.confirm_user/1` — auto-confirms (admin set the password; no email flow needed)
3. `Users.assign_role_to_user/3` — assigns each checked custom role

Live validation on `phx-change` shows field errors inline before submit.

#### Updated: User table

| Column | Notes |
|---|---|
| Name | |
| Email | |
| System Role | In-row dropdown; change fires `toggle_user_role` event |
| Custom Roles | Purple badges showing assigned role names; "None" italic if empty |
| Status | Green "Confirmed" / Yellow "Pending" based on `confirmed_at` |
| Actions | "Assign Roles" (purple) + "Delete" (red with confirm) |

#### New: Assign Roles modal (existing users)

Opened via "Assign Roles" per-user button. Shows all roles with Assign/Remove toggle per role. Role rows highlighted purple when assigned. Changes take effect immediately (no save step).

#### New event handlers

| Event | Action |
|---|---|
| `open_create_modal` | Opens create user modal with fresh form |
| `close_create_modal` | Closes create modal |
| `validate_user` | Live-validates form on change |
| `toggle_new_user_role` | Toggles role selection in create modal |
| `create_user` | Registers user, confirms, assigns roles |
| `open_roles_modal` | Opens assign-roles modal for existing user |
| `close_roles_modal` | Closes assign-roles modal |
| `assign_user_role` | Assigns role to user; refreshes modal state |
| `revoke_user_role` | Revokes role from user; refreshes modal state |

---

### 7.7 — Menu Update: Role Management item

**File:** `apps/da_product_app/lib/da_product_app/menu_provider.ex`

Added to the **System** group:

```elixir
%Item{
  id: :role_management,
  label: "Role Management",
  path: "/admin/roles",
  icon: "hero-shield-check",
  group: "System",
  order: 1,
  required_permissions: ["users.permission.manage"],
  source_app: :da_product_app
}
```

Visible only to users who hold `users.permission.manage` (superusers always see it).

---

## Phase 8 — Tests, Rollout, and Backward Compatibility Cutover — 🔲 Pending

**Tasks remaining:**

1. Authorization unit tests (`authorization_test.exs`)
2. Menu registry tests
3. LiveView authorization tests — `UserManagementLive` and `RoleManagementLive`
4. Rollout procedure:
   - Stage 1: render-only menu filtering (done)
   - Stage 2: route guards (done)
   - Stage 3: action guards (done)
   - Stage 4: retire legacy permission checks / aliases
5. Monitoring logs for unauthorized access attempts
6. Document rollback procedure (feature flag path)

---

## Detailed Permission Matrix

### User Management Module

| Permission | `:user` default | `:admin` default | `:superuser` |
|---|---|---|---|
| `users.account.view` | ✅ | ✅ | ✅ |
| `users.account.edit` | — | ✅ | ✅ |
| `users.account.delete` | — | ✅ | ✅ |
| `users.role.assign` | — | ✅ | ✅ |
| `users.permission.view` | ✅ | ✅ | ✅ |
| `users.permission.manage` | — | — | ✅ |

### Terminal Management Module

| Permission | `:user` default | `:admin` default | `:superuser` |
|---|---|---|---|
| `terminal.device.view` | ✅ | ✅ | ✅ |
| `terminal.device.edit` | — | ✅ | ✅ |

### Settlement Module

| Permission | `:user` default | `:admin` default | `:superuser` |
|---|---|---|---|
| `settlement.dashboard.view` | ✅ | ✅ | ✅ |
| `settlement.files.view` | ✅ | ✅ | ✅ |
| `settlement.mis.view` | ✅ | ✅ | ✅ |
| `settlement.mis.generate` | — | ✅ | ✅ |
| `settlement.mis.approve_l1` | — | ✅ | ✅ |
| `settlement.mis.approve_l2` | — | ✅ | ✅ |
| `settlement.mis.reject` | — | ✅ | ✅ |
| `settlement.adjustments.view` | ✅ | ✅ | ✅ |
| `settlement.adjustments.edit` | — | ✅ | ✅ |
| `settlement.payouts.view` | ✅ | ✅ | ✅ |
| `settlement.payouts.transmit` | — | ✅ | ✅ |
| `settlement.payouts.reinitiate` | — | ✅ | ✅ |
| `settlement.risk_holds.view` | ✅ | ✅ | ✅ |
| `settlement.risk_holds.release` | — | ✅ | ✅ |
| `settlement.tid_master.view` | ✅ | ✅ | ✅ |
| `settlement.merchant_portal.view` | ✅ | ✅ | ✅ |

### Reconciliation Module

| Permission | `:user` default | `:admin` default | `:superuser` |
|---|---|---|---|
| `reconciliation.exceptions.view` | ✅ | ✅ | ✅ |
| `reconciliation.exceptions.release` | — | ✅ | ✅ |
| `reconciliation.exceptions.bulk_release` | — | ✅ | ✅ |

---

## Role RBAC — Architecture Summary

```
users
  └─ user_roles (many-to-many join, tracked: assigned_by, assigned_at)
       └─ roles (id, name, description, active)
            └─ role_permissions (many-to-many join)
                 └─ permissions (id, name, description, module_id)
                      └─ modules (id, name, description)

users
  └─ user_permissions (direct grants, many-to-many join)
       └─ permissions
```

**Effective permissions = union(direct grants, role-inherited grants)**

The union is computed once at socket mount and cached in the process dictionary. Superusers bypass all checks.

---

## Key Files Reference

| File | Role |
|---|---|
| `apps/da_product_app/priv/repo/migrations/20260310000001_create_roles.exs` | DB: roles table |
| `apps/da_product_app/priv/repo/migrations/20260310000002_create_role_permissions.exs` | DB: role_permissions join |
| `apps/da_product_app/priv/repo/migrations/20260310000003_create_user_roles.exs` | DB: user_roles join |
| `apps/da_product_app/lib/da_product_app/users/role.ex` | Role schema |
| `apps/da_product_app/lib/da_product_app/users/user_role.ex` | UserRole schema |
| `apps/da_product_app/lib/da_product_app/users/user.ex` | Updated: `many_to_many :roles` |
| `apps/da_product_app/lib/da_product_app/users/permission.ex` | Updated: `many_to_many :roles` |
| `apps/da_product_app/lib/da_product_app/users.ex` | Updated context + new role/user functions |
| `apps/da_product_app/priv/repo/seeds.exs` | Canonical permissions + default admin |
| `apps/platform_web/lib/platform_web/authorization.ex` | Updated: role-inherited permission union |
| `apps/platform_web/lib/platform_web/user_auth.ex` | on_mount callbacks |
| `apps/platform_web/lib/platform_web/router.ex` | Routes with permission gates |
| `apps/platform_web/lib/platform_web/live/role_management_live.ex` | NEW: Role CRUD LiveView |
| `apps/platform_web/lib/platform_web/live/user_management_live.ex` | Updated: create user + role assignment |
| `apps/platform_web/lib/platform_web/menu/registry.ex` | Menu composition + permission filtering |
| `apps/platform_web/lib/platform_web/components/sidebar_component.ex` | Delegates to Registry |
| `apps/da_product_app/lib/da_product_app/menu_provider.ex` | Updated: Role Management item |
| `apps/settlement_core/lib/settlement_core/menu_provider.ex` | Settlement menu definitions |
| `apps/platform_core/lib/platform_core/menu/item.ex` | Menu item struct |
| `apps/platform_core/lib/platform_core/menu/provider.ex` | Menu provider behavior |

---

## Risk Register

| Risk | Status | Mitigation |
|---|---|---|
| Permission naming drift between seeds and runtime checks | ✅ Resolved | Canonical registry + compatibility alias map |
| Missing server-side guards while UI appears restricted | ✅ Resolved | Route guards + action-level `authorized?` checks |
| Breaking existing user flows during menu migration | ✅ Resolved | Feature flag fallback to static menu |
| Inconsistent user permission data in existing DBs | 🔲 Pending | Migration/reconciliation script not yet built |
| Cross-app coupling complexity with provider design | ✅ Resolved | Behavior contract in `platform_core` |
| Role assignments not reflected in live session | ✅ Resolved | Permission cache rebuilt on each socket mount |

---

## Deliverables Checklist

| Deliverable | Status |
|---|---|
| Detailed permission matrix | ✅ |
| Authorization module (`PlatformWeb.Authorization`) | ✅ |
| Dynamic menu registry | ✅ |
| App-owned menu providers (settlement + users) | ✅ |
| Route and action guards | ✅ |
| Role RBAC — DB tables + schemas | ✅ |
| Role RBAC — context functions | ✅ |
| Role RBAC — `RoleManagementLive` UI | ✅ |
| User creation UI in `UserManagementLive` | ✅ |
| Role assignment UI (create + existing users) | ✅ |
| Authorization union of direct + role permissions | ✅ |
| Feature flag rollout docs + rollback procedure | 🔲 |
| Authorization and LiveView test coverage | 🔲 |
| Legacy permission alias retirement plan | 🔲 |

---

## Execution Notes

1. All migrations for the Role RBAC layer (`20260310000001–3`) have been applied to the database.
2. The `seeds.exs` is idempotent and safe to re-run in any environment.
3. The `dynamic_menu: true` config is the default; set to `false` to fall back to the hardcoded menu.
4. `users.permission.manage` is the gate for Role Management — only superusers hold it by default.
5. Admin-created users are auto-confirmed (no email confirmation token required).
6. Phase 8 (testing + cutover) is the only remaining phase.
