# System-Wide Roles & Permissions Implementation Plan

## 1. Purpose

This document is the implementation rulebook for redesigning authorization across the entire Microfinance Management System.

The target is a consistent, auditable, secure and maintainable authorization architecture where:

1. **Authentication** answers: who is the user?
2. **Module configuration** answers: is this feature enabled for the business?
3. **Permission/RBAC** answers: what operation may this user perform?
4. **Branch isolation** answers: on which branch-owned records may the user perform it?
5. **Model/domain policy** answers: is this specific record/action valid in its current business state?

These layers must remain separate. A permission must never bypass branch isolation, and branch access must never imply permission to perform an operation.

---

## 2. Current State and Why It Must Be Standardized

The application already has a functional custom RBAC foundation:

- custom `Role` and `Permission` models;
- `roles`, `permissions`, `role_user`, and `permission_role` tables;
- `User::hasRole()` and `User::hasPermission()` helpers;
- custom `permission:` middleware;
- role/permission administration screens;
- a super-administrator bypass;
- permission checks on many routes;
- branch isolation implemented independently through `BranchAccessService`, `BranchContext`, model scopes, middleware, and controller validation.

However, authorization has grown module-by-module. Permissions are currently seeded by multiple seeders, and newer modules such as payroll, cash closing, tasks/calendar, and management alerts maintain separate permission definitions. This creates permission drift, makes omissions harder to detect, and makes it possible for a route, navigation item, background action, export, or newly added module to be introduced without a canonical authorization contract.

The redesign must therefore standardize authorization without weakening the branch-isolation work already completed.

---

## 3. Technology Decision

### 3.1 Adopt Spatie Laravel Permission

Use `spatie/laravel-permission` as the RBAC engine.

**Recommended target:** Spatie Laravel Permission `^8.0`.

The current production/CI baseline is PHP 8.3, so the implementation should formally raise the project PHP minimum from `^8.2` to `^8.3` when the package is introduced. If any real deployment must remain on PHP 8.2, stop at the dependency phase and use the supported v6 line instead of silently breaking that environment.

### 3.2 Why Spatie

It provides:

- Laravel Gate integration;
- `HasRoles` on users;
- role and permission synchronization APIs;
- permission middleware;
- Blade authorization support;
- cached permission resolution;
- standard role/permission relationships;
- mature testing and upgrade paths;
- clean support for custom Role/Permission models.

### 3.3 Deliberate features we will NOT enable initially

#### Teams

Do **not** use Spatie Teams to represent branches.

Branch access is already a mature domain-specific isolation layer with support for primary branch, multiple assigned branches, no-branch users, super-administrator access, parent-owned records, controller validation, model scopes and URL-level isolation.

Spatie will control **functional authorization** only. `BranchAccessService` remains the canonical branch-access source.

#### Wildcard permissions

Do **not** enable wildcard permissions initially.

A financial application benefits from explicit permission records because they are easier to audit, test, compare, display in the admin UI, and include in maker-checker reviews.

#### Direct permissions on users

The Spatie schema supports them, but the application UI must not allow routine direct user permission assignment in the first implementation.

Users should inherit abilities from roles. Direct permissions create hidden exceptions and make access reviews harder.

If a future business requirement needs temporary overrides, implement a separate time-bounded override workflow with audit history rather than casually using direct permissions.

---

## 4. Core Authorization Architecture

### Layer A — Authentication

Unauthenticated users may only access explicit public/authentication routes such as login, password reset, 2FA challenge, and intentionally public remote-payment submission when that module is enabled.

### Layer B — Module availability

`EnsureModuleEnabled` remains responsible for business feature toggles.

Examples:

- payroll enabled;
- fixed assets enabled;
- remote payments enabled;
- financial reports enabled;
- loan write-offs enabled;
- tasks/calendar enabled.

A user having `payroll.view` does not enable payroll if the payroll module is disabled.

### Layer C — Functional permission

Spatie/Laravel Gate becomes the canonical permission evaluator.

Application code should use:

- `$user->can('permission.name')`;
- `$request->user()->can(...)`;
- `@can(...)` / `@cannot(...)` in Blade;
- Laravel `can:` middleware for simple route abilities;
- model policies for record-specific actions.

Avoid authorization code that checks a role name directly. Role checks should normally exist only in super-admin handling, role administration rules, and tests.

### Layer D — Branch/data scope

Keep all existing branch-isolation mechanisms intact.

Permission checks happen **in addition to** branch scoping.

Example:

A cashier with `payments.receive` assigned only to Branch A:

- may receive payments for Branch A loans;
- may not access Branch B loans;
- may not receive a Branch B payment by manually posting its ID;
- may not see Branch B totals in search/reports/dashboard.

### Layer E — Model/domain policy

Permissions alone do not make every state transition legal.

Policies/services must continue to enforce rules such as:

- only approved loans may be disbursed;
- an approver may not approve where maker-checker rules prohibit self-approval;
- payroll must be approved before payment;
- write-off approval requires the correct state;
- closed periods/records cannot be mutated improperly;
- financial accounts must be valid for the target branch;
- collateral custody transitions must be legal;
- users cannot elevate themselves beyond their delegation authority.

---

## 5. Models and Package Integration

### 5.1 User model

Replace the custom role implementation with Spatie `HasRoles`.

Target shape:

```php
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasFactory, Notifiable, NormalizesPhoneNumbers, HasRoles;
}
```

Remove the custom `roles()` relationship after migration and compatibility validation.

The existing `hasRole()` call name is supplied by Spatie, reducing application churn.

The custom `hasPermission()` helper should be migrated to Laravel Gate calls. During the transition it may temporarily remain as a deprecated wrapper around `can()` so old call sites do not break in a single large change.

### 5.2 Custom Role model

Keep application metadata while extending Spatie's model:

```php
class Role extends \Spatie\Permission\Models\Role
{
    // display_name, description, is_system
}
```

Preserve:

- `display_name`;
- `description`;
- `is_system`.

### 5.3 Custom Permission model

Extend Spatie's Permission model while preserving:

- `display_name`;
- `group`;
- `description`.

Optional catalog-only metadata should include:

- module/domain;
- risk classification;
- whether it is assignable to custom roles;
- whether it is system-critical;
- whether it implies a financial posting/action.

Do not add columns merely for UI decoration if the metadata can safely live in the canonical catalog class.

### 5.4 Guard

Use a single `web` guard for application staff authorization.

Do not create duplicate role/permission sets for unnecessary guards.

---

## 6. Database Migration Strategy

The migration must be non-destructive until the new authorization stack has passed regression testing.

### Step 1 — Install/publish Spatie configuration and migrations

Configure the application custom models in `config/permission.php`.

### Step 2 — Reuse role and permission identity

Preserve existing `roles` and `permissions` rows and IDs where possible.

Add required Spatie fields such as `guard_name`, defaulting existing rows to `web`.

Adjust unique constraints to the package-compatible form where required.

### Step 3 — Create standard Spatie pivot tables

Create:

- `role_has_permissions`;
- `model_has_roles`;
- `model_has_permissions`.

Do not use Spatie Teams columns.

### Step 4 — Copy legacy assignments

Migrate:

- `permission_role` -> `role_has_permissions`;
- `role_user` -> `model_has_roles` with `model_type = App\\Models\\User`.

Migration must be idempotent or safely guarded against duplicate assignment.

### Step 5 — Verification command

Create a one-time/read-only authorization migration verifier that reports:

- legacy role count vs Spatie role count;
- legacy permission count vs Spatie permission count;
- legacy user-role assignments vs new assignments;
- legacy role-permission assignments vs new assignments;
- users that would lose roles;
- roles that would lose permissions;
- unknown permission names;
- orphaned assignments.

The release must fail if discrepancies exist.

### Step 6 — Cut application code over

Only after migrated data is verified:

- enable `HasRoles`;
- switch route/middleware checks;
- switch Blade checks;
- switch controller/service checks;
- run full tests.

### Step 7 — Legacy cleanup in a later migration

Do not drop `permission_role` and `role_user` in the first cutover migration.

After at least one successful full regression cycle and staging acceptance, a separate cleanup migration may remove legacy pivot tables and obsolete custom middleware/helper code.

This separation gives us a safer rollback boundary.

---

## 7. Canonical Permission Catalog

Create one source of truth instead of spreading permission definitions through feature seeders.

Recommended structure:

```text
app/Support/Authorization/PermissionName.php
app/Support/Authorization/PermissionCatalog.php
app/Support/Authorization/DefaultRoleMatrix.php
```

### `PermissionName`

Use a PHP backed enum or constants for permission strings. This prevents silent typos in application code.

### `PermissionCatalog`

For every permission define:

- name;
- display label;
- group/module;
- description;
- risk level.

Suggested risk levels:

- `read`;
- `operate`;
- `approve`;
- `financial`;
- `security`.

### `DefaultRoleMatrix`

Contains the default system roles and their permission sets.

Seeders become thin synchronization wrappers around the catalog instead of independent sources of authorization truth.

---

## 8. Permission Naming Rules

Permission names must be stable API identifiers. Display names can change without renaming permissions.

Preferred pattern:

```text
<domain>.<resource-or-subresource>.<action>
```

Existing stable names may be retained where already clear to reduce migration risk.

Examples:

```text
customers.view
customers.create
customers.update
customers.documents.manage
loans.approve
loans.disburse
expenses.approve
payroll.pay
roles.update
```

### Rules

1. Use lowercase kebab/dot-safe identifiers consistently.
2. Do not encode branch IDs or user IDs in permission names.
3. Do not encode workflow state in ad-hoc role names.
4. Prefer explicit action names over generic `.manage` when the action carries financial/security risk.
5. Avoid permissions that duplicate module toggles.
6. Avoid role names in route authorization logic.
7. Once a permission ships to production, rename it only through an explicit data migration and compatibility mapping.

---

## 9. Proposed Whole-System Permission Catalog

This list is the target audit baseline. During implementation every current route/controller/action must map to one of these capabilities or be explicitly classified as authenticated self-service/public.

### 9.1 Dashboard and global navigation

- `dashboard.view`
- `search.use`

All ordinary operational roles will normally receive these, but they must still respect branch-scoped query results.

### 9.2 Business configuration

- `business.settings.view`
- `business.settings.update`
- `business.contract-templates.view`
- `business.contract-templates.manage`
- `system.settings.manage`

### 9.3 Branch administration

- `branches.view`
- `branches.create`
- `branches.update`
- `branches.manage-status` if branch activation/deactivation becomes separate

### 9.4 Staff administration

- `users.view`
- `users.create`
- `users.update`
- `users.manage-status`
- `users.assign-roles`
- `users.assign-branches`
- `users.reset-security` if privileged reset/recovery administration is added

Staff assignment permissions must not by themselves allow privilege escalation. See the delegation rules section.

### 9.5 Roles and security administration

- `roles.view`
- `roles.create`
- `roles.update`
- `roles.delete`
- `roles.assign`
- `audit.view`
- `audit.export`

System roles can be protected from deletion/rename while custom roles may be managed according to policy.

### 9.6 Customers

- `customers.view`
- `customers.create`
- `customers.update`
- `customers.documents.view`
- `customers.documents.manage`
- `customers.guarantors.manage`
- `customers.watchlist.manage`
- `customers.risk.view`
- `customers.risk.update` if manual risk edits are supported

Deprecate broad legacy `customers.manage` after all call sites are mapped to granular permissions.

### 9.7 Loan products

- `loan-products.view`
- `loan-products.create`
- `loan-products.update`
- `loan-products.manage-status`

The existing broad `loan-products.manage` can be temporarily retained as a compatibility alias until route coverage is converted.

### 9.8 Collateral

- `collateral.view`
- `collateral.create`
- `collateral.update`
- `collateral.custody`
- `collateral.documents.view`
- `collateral.documents.manage`
- `collateral.categories.manage`
- `collateral.release`

### 9.9 Loan applications and documents

- `loans.view`
- `loans.create`
- `loans.update`
- `loans.transition`
- `loans.documents.view`
- `loans.documents.manage`
- `loans.contract.view`
- `loans.contract.generate`
- `loans.installments.excuse`

### 9.10 Loan approval

- `loans.approve`
- `loans.manager-approve`
- `loans.auto-approve`
- `loans.approval.additional-collateral`

Maker-checker/domain restrictions remain mandatory even when the permission is granted.

### 9.11 Loan disbursement

- `loans.disburse.view`
- `loans.disburse.execute`
- `loans.disburse.receipt`

The existing `loans.disburse` can be migrated to the granular pair during implementation.

### 9.12 Loan payments

- `payments.view`
- `payments.receive`
- `payments.receipt`
- `payments.statement`

### 9.13 Remote payments

- `payments.remote.submit`
- `payments.remote.view`
- `payments.remote.verify`

Public submission remains controlled by module availability, throttling and public-input validation rather than an authenticated staff permission.

### 9.14 Arrears and collections

- `arrears.view`
- `arrears.follow-up`
- `collections.view`
- `collections.manage`

### 9.15 Loan top-ups

- `loans.top-up.view`
- `loans.top-up.create`
- `loans.top-up.execute`
- `loans.top-up.override`

### 9.16 Early settlement and restructuring

- `loans.adjustments.view`
- `loans.settlement.execute`
- `loans.settlement.adjust`
- `loans.restructure.create`
- `loans.restructure.approve`

### 9.17 Write-offs and recovery

- `loans.writeoff.view`
- `loans.writeoff.request`
- `loans.writeoff.approve`
- `loans.writeoff.recover`

### 9.18 Financial accounts and accounting

- `accounts.view`
- `accounts.create`
- `accounts.update`
- `accounts.capital`
- `accounts.transfer`
- `accounting.view`
- `accounting.export` if ledger export is added

Deprecate broad `accounts.manage` after conversion.

### 9.19 Expenses

- `expenses.view`
- `expenses.create`
- `expenses.approve`
- `expenses.categories.manage`
- `expenses.recurring.manage`
- `expenses.voucher.view`

Deprecate legacy `expenses.manage` after granular conversion.

### 9.20 Payroll

Keep the current strong granular model:

- `payroll.view`
- `payroll.compensation.manage`
- `payroll.periods.create`
- `payroll.approve`
- `payroll.pay`

Remove/deprecate any obsolete broad `payroll.manage` grant after all old code is audited.

### 9.21 Cash reconciliation and daily closing

- `cash-closing.view`
- `cash-reconciliation.perform`
- `daily-closing.prepare`
- `daily-closing.confirm`

### 9.22 Fixed assets

- `fixed-assets.view`
- `fixed-assets.create`
- `fixed-assets.update`
- `fixed-assets.depreciate`
- `fixed-assets.maintenance`
- `fixed-assets.reports.view`
- `fixed-assets.reports.export`

The existing `fixed-assets.manage` can be migrated to create/update.

### 9.23 Reports

- `reports.view`
- `reports.export`

Optional later split if business access requirements demand it:

- `reports.financial.view`
- `reports.portfolio.view`
- `reports.branch-performance.view`
- `reports.payroll.view`
- `reports.financial.export`

Do not over-split until there is a real access distinction; start with view/export separation.

### 9.24 Notifications

- `notifications.view`
- `notifications.retry`
- `notifications.manage`

### 9.25 Tasks

- `tasks.view`
- `tasks.create`
- `tasks.update`
- `tasks.assign`
- `tasks.update-status`

Migrate current broad `tasks.manage` into the explicit actions.

### 9.26 Calendar and appointments

- `calendar.view`
- `appointments.create`
- `appointments.update`
- `appointments.manage-status`

Migrate current `appointments.manage` into explicit actions if the UI/API exposes those separately.

### 9.27 Management alerts

- `management-alerts.view`
- `management-alerts.acknowledge`
- `management-alerts.resolve`
- `management-alerts.settings`

This is preferable to one broad `management-alerts.manage` because changing alert thresholds/settings is more privileged than acknowledging an alert.

### 9.28 Documents and exports

Document authorization should normally derive from the business object permission plus branch/model policy rather than having one dangerous global `documents.view` permission.

Examples:

- loan agreement: loan view/contract permission + loan branch policy;
- payment receipt: payment/loan view + branch policy;
- collateral receipt: collateral view + branch policy;
- expense voucher: expense view + branch policy;
- payslip: payroll view + employee/branch policy.

### 9.29 Personal security/self-service

Authenticated users should normally be able to manage their own:

- password;
- two-factor authentication;
- recovery codes;
- personal profile/security settings.

These do not need business permissions because they are self-service actions restricted to the authenticated principal. Administrative reset of another user's security would require a separate privileged permission.

---

## 10. Default System Roles

The existing system-role concept is good and should be retained.

### 10.1 `super-administrator`

- system-wide branch access through the existing branch access layer;
- all functional permissions through `Gate::before`;
- cannot have its effective access reduced by editing its permission checkbox list;
- assignment/removal must be tightly controlled;
- system must never allow the last active super administrator to be removed/deactivated accidentally.

### 10.2 `branch-administrator`

Purpose: one-person branch operator with broad day-to-day branch operations, but no global security/system administration.

Should have most operational branch permissions, including branch-level user operations, loans, collections, cash, expenses and reporting, subject to maker-checker rules that must not be bypassed merely because this role is broad.

### 10.3 `branch-manager`

Purpose: branch oversight and approvals.

Strong on:

- branch visibility;
- customer/loan visibility;
- approval;
- financial/report oversight;
- daily closing confirmation;
- task/alert management.

Should not automatically receive global role/security/system-setting administration.

### 10.4 `loan-officer`

Strong on:

- customer onboarding;
- guarantors/documents;
- collateral registration;
- loan application creation/update;
- top-up creation;
- arrears follow-up;
- restructuring request creation.

Should not receive financial approval/payment posting by default.

### 10.5 `loan-inspector`

Strong on:

- customer/loan/collateral review;
- loan approval/rejection within threshold;
- document review;
- restructuring approval where business policy allows.

Should not originate financial postings merely due to approval access.

### 10.6 `cashier`

Strong on:

- receiving payments;
- payment receipts;
- remote payment verification where configured;
- cash reconciliation;
- approved disbursement execution if business policy grants it;
- settlement/recovery financial posting where explicitly granted.

### 10.7 `accountant`

Strong on:

- financial accounts;
- transfers/capital;
- ledger;
- expenses;
- payroll payment;
- fixed assets;
- reports;
- cash reconciliation/preparation.

Approval authority must still be explicit and separate from posting authority.

### 10.8 `debt-collector`

Strong on:

- arrears;
- collection follow-ups;
- promises to pay;
- customer watchlist where authorized;
- collateral visibility/custody actions specifically required for recovery;
- restructuring requests;
- remote evidence submission.

### 10.9 `hr-payroll-officer`

Strong on:

- staff visibility appropriate to branch scope;
- compensation setup;
- draft payroll preparation;
- payslip visibility.

Should not pay payroll by default.

### 10.10 `auditor`

Read-only by design wherever possible:

- audit logs;
- reports;
- loans/customers/collateral;
- accounting;
- expenses;
- payroll;
- fixed assets;
- management alerts;
- arrears.

Avoid mutation permissions.

### 10.11 Custom roles

Support custom roles after the base migration is stable.

Custom roles should:

- have a human display name and description;
- be assignable only by authorized users;
- never silently inherit permissions from another role;
- be explicit permission bundles;
- be protected from accidental deletion when assigned to users;
- be audited on create/update/delete.

---

## 11. Super Administrator Design

Use Laravel `Gate::before` as the single super-admin bypass.

Concept:

```php
Gate::before(function (User $user, string $ability): ?bool {
    return $user->hasRole('super-administrator') ? true : null;
});
```

Application code must use Gate-compatible checks (`can`, middleware `can:`, policies, Blade `@can`) so the super-admin path remains consistent.

Do not scatter `hasRole('super-administrator')` throughout controllers/views for ordinary feature access.

The existing branch-access system may still need its explicit system-wide determination because data scoping is independent from Laravel Gate.

---

## 12. Route Authorization Strategy

### 12.1 Replace custom permission middleware

Gradually replace:

```text
permission:customers.view
```

implemented by custom `EnsurePermission`, with either:

```text
can:customers.view
```

or Spatie's standard permission middleware where necessary.

Prefer Laravel's `can:` for single-permission route checks because it naturally uses Gate and therefore the super-admin rule.

### 12.2 Route audit requirement

Every authenticated business route must be classified as one of:

1. explicit permission route;
2. authenticated self-service route;
3. intentionally public route;
4. internal/technical route protected by an alternative control.

No route should remain unclassified.

Examples currently needing explicit review include:

- dashboard;
- global search;
- document branding asset route;
- self-security routes;
- public remote payment submission.

### 12.3 Automated route permission audit

Add a test/Artisan audit that inspects the route collection and fails when a new business route under `auth` has no accepted authorization classification.

Maintain a very small explicit allowlist for self-service routes rather than allowing broad exceptions.

---

## 13. Model Policies

Route middleware prevents obvious entry, but model policies should become the record-level authorization contract for sensitive models.

Priority policies:

- `CustomerPolicy`;
- `LoanPolicy`;
- `CollateralPolicy`;
- `LoanPaymentPolicy`;
- `LoanDisbursementPolicy`;
- `ExpensePolicy`;
- `FinancialAccountPolicy`;
- `PayrollPeriodPolicy`;
- `FixedAssetPolicy`;
- `LoanWriteOffPolicy`;
- `LoanRestructuringPolicy`;
- `UserPolicy`;
- `RolePolicy`.

Policy logic combines:

1. Gate permission;
2. branch accessibility;
3. model ownership/relationship where relevant;
4. domain state constraints where appropriate.

Do not duplicate the full branch-scoping system inside every policy. Policies should call the canonical branch access service/helper.

---

## 14. Privilege Escalation Protection

This is mandatory because role administration can otherwise defeat all other controls.

### 14.1 Role assignment ceiling

A user with `users.assign-roles` must not automatically be able to assign every role.

Rules:

- only a super administrator may assign/remove `super-administrator`;
- branch-level administrators may only assign approved non-system-wide roles;
- an actor may not grant a role containing permissions beyond the actor's delegation ceiling unless explicitly authorized by a stronger security permission;
- hidden form values must not bypass the server-side assignment ceiling.

### 14.2 Branch assignment ceiling

Keep current branch assignment enforcement:

- non-system-wide users can only assign branches they can administer;
- primary branch must be among assigned branches;
- a role assignment never expands branch visibility automatically.

### 14.3 Self-escalation restrictions

Prevent users from using staff-edit capability to:

- give themselves super-admin;
- expand their own branch set beyond delegated branches;
- grant themselves roles above their ceiling;
- deactivate/remove the last super administrator;
- bypass maker-checker restrictions by editing their own role during a transaction.

### 14.4 Critical-role change audit

Role assignment changes must record:

- actor;
- target user;
- roles before/after;
- branches before/after when changed in same operation;
- timestamp;
- request/IP context through the existing audit system;
- optional administrative reason for high-risk changes.

---

## 15. Maker-Checker and Separation of Duties

RBAC must support, not replace, maker-checker rules.

High-risk combinations to review explicitly:

- loan creation vs loan approval;
- loan approval vs disbursement;
- expense creation vs expense approval/payment;
- payroll preparation vs payroll approval vs payroll payment;
- cash reconciliation vs daily closing confirmation;
- write-off request vs write-off approval;
- restructuring request vs approval;
- remote-payment submission vs verification;
- role creation/editing vs role assignment.

Where business rules require different people, enforce it in the workflow/domain layer using actor IDs, not merely by assuming roles are mutually exclusive.

---

## 16. Admin UI Plan

### 16.1 Roles list

Show:

- role display name;
- system/custom badge;
- description;
- user count;
- permission count;
- risk summary;
- actions allowed to current operator.

### 16.2 Role create/edit screen

Permission grid grouped by domain:

- Staff & Security;
- Branches;
- Customers;
- Loans;
- Collateral;
- Payments;
- Collections;
- Accounting;
- Expenses;
- Payroll;
- Cash Closing;
- Fixed Assets;
- Reports;
- Notifications;
- Tasks/Calendar;
- Management;
- Settings.

Each row should show:

- permission label;
- description;
- risk level;
- checkbox;
- optional dependency/warning.

System role restrictions must be enforced server-side, not just disabled in UI.

### 16.3 Useful role tools

Add after core authorization is stable:

- create custom role;
- duplicate role;
- compare two roles;
- filter permissions by module/risk;
- show effective user access;
- list users assigned to a role.

### 16.4 Staff create/edit

Display:

- assigned branches;
- primary branch;
- assignable roles;
- effective permission summary;
- warning for high-risk roles;
- server-computed assignment restrictions.

Do not show roles the actor is forbidden to assign.

### 16.5 Navigation and buttons

Use `@can`/`@canany` so users do not see unavailable operations.

However, hidden navigation is only UX. Routes/controllers/policies remain the security boundary.

---

## 17. Audit and Events

Authorization changes are security events.

Audit at minimum:

- role created;
- role renamed/updated;
- role deleted;
- role permissions changed;
- user roles assigned/removed;
- critical branch assignment changes;
- attempted forbidden privilege escalation where useful;
- super-admin assignment/removal;
- permission catalog synchronization changes during deployment.

For role permission changes, record names as well as IDs so audit history remains understandable even if numeric IDs differ between environments.

---

## 18. Cache Strategy

Use Spatie's built-in permission cache.

Rules:

- use Spatie APIs such as `syncPermissions`, `syncRoles`, `assignRole`, etc.;
- avoid raw pivot writes in normal application code;
- if a migration/seeder performs direct DB writes, explicitly reset permission cache afterward;
- production deployment should run the package cache reset after permission/catalog migrations if required;
- tests must verify that permission changes take effect without stale authorization results.

Do not add a second custom permission cache.

---

## 19. Seeder Redesign

Replace permission-definition fragmentation with one canonical synchronizer.

Target:

```text
AuthorizationSeeder
  -> sync PermissionCatalog
  -> sync DefaultRoleMatrix
  -> preserve custom roles
  -> preserve custom-role assignments
```

Rules:

- idempotent on every run;
- never delete a custom role merely because it is not in default roles;
- never silently remove unknown production permissions until a deliberate deprecation migration says to do so;
- system default role permission sets may be synchronized deliberately;
- custom roles retain their explicit configured permissions;
- reset Spatie permission cache after synchronization.

The existing payroll/cash-closing/management permission seeders should be retired only after their definitions have been absorbed into the canonical catalog.

---

## 20. Legacy Permission Cleanup

Current broad/legacy permissions must not be deleted immediately.

Examples:

- `customers.manage`;
- `expenses.manage`;
- `accounts.manage`;
- `fixed-assets.manage`;
- `payroll.manage` if still present;
- `tasks.manage`;
- `appointments.manage`;
- `management-alerts.manage`;
- broad `loans.contract` / `loans.documents` where granular replacements are introduced.

For each legacy permission:

1. search all route/controller/service/view/test references;
2. map it to canonical granular abilities;
3. migrate default role grants;
4. migrate custom roles conservatively;
5. keep compatibility for one transition phase;
6. add a test that forbids new references;
7. remove the legacy permission only in a later cleanup migration.

---

## 21. Authorization Coverage Tests

### 21.1 Catalog integrity

Tests must assert:

- every permission name is unique;
- every default role permission exists in the catalog;
- every DB system permission matches catalog metadata after seeding;
- every system role exists;
- no default role references a deprecated/unknown permission.

### 21.2 Route coverage

Fail CI when:

- a protected business route has no permission/self-service/public classification;
- a route references a permission absent from the catalog;
- a deprecated permission is newly referenced.

### 21.3 Permission behavior

For every major module test:

- user without permission gets 403;
- user with permission can reach the action for an allowed branch;
- permission does not permit another branch;
- super-admin succeeds intentionally;
- no-branch user sees no branch-owned data even with a functional role;
- multi-branch user sees exactly assigned branch union.

### 21.4 UI behavior

Test representative navigation/actions:

- unauthorized action hidden;
- authorized action visible;
- direct URL remains forbidden even if hidden.

### 21.5 Role administration security

Test:

- non-super cannot assign super-admin;
- branch admin cannot assign inaccessible branches;
- self-escalation attempt rejected;
- last super-admin protection;
- system role delete protection;
- custom role CRUD authorization;
- role change audit records before/after state.

### 21.6 Seeder behavior

Test repeated seeding:

- produces no duplicates;
- preserves custom roles;
- preserves user assignments;
- refreshes default role grants predictably;
- refreshes permission cache.

---

## 22. Static Source Audits

Add permanent source audits similar to the existing branch-isolation audits.

CI should reject new patterns such as:

- business routes protected only by `auth` when not allowlisted;
- direct custom `hasPermission()` usage after migration cutoff;
- direct role-name authorization checks outside approved files;
- raw writes to Spatie pivot tables outside migrations/seeders;
- hard-coded permission strings not present in the canonical enum/catalog where practical;
- direct user permission assignment from application UI code if direct permissions are disabled by policy.

This prevents gradual authorization drift after the initial migration.

---

## 23. Implementation Phases

### Phase 0 — Freeze and inventory

Deliverables:

- route inventory;
- permission inventory;
- role inventory;
- role-permission matrix export;
- all permission call sites inventory;
- all role-name check call sites;
- self-service/public route allowlist;
- list of legacy/broad permissions.

No runtime behavior change.

### Phase 1 — Dependency and schema bridge

Deliverables:

- raise PHP floor to 8.3 if confirmed across environments;
- install Spatie v8;
- publish/configure package;
- custom Spatie Role/Permission models with existing metadata;
- add `guard_name`;
- create Spatie pivots;
- copy legacy assignments;
- migration verification command/test.

No old pivots removed.

### Phase 2 — Canonical permission catalog

Deliverables:

- `PermissionName` enum/constants;
- `PermissionCatalog`;
- `DefaultRoleMatrix`;
- `AuthorizationSeeder`;
- absorb payroll/cash-closing/management permission seeders;
- catalog integrity tests.

### Phase 3 — User/Gate integration

Deliverables:

- add `HasRoles` to User;
- configure custom models;
- add `Gate::before` super-admin handling;
- temporary legacy helper compatibility if required;
- permission cache tests.

### Phase 4 — Core administrative authorization

Convert first because it controls privilege:

- roles;
- staff users;
- branch assignments;
- business/system settings;
- audit log.

Add privilege escalation protections before converting operational modules.

### Phase 5 — Customer, collateral and loan origination

Convert:

- customers;
- customer documents;
- guarantors/watchlist;
- loan products;
- collateral;
- loan applications;
- loan documents/contracts.

Add policies and route coverage tests.

### Phase 6 — Loan decisions and financial movements

Convert:

- approvals;
- disbursement;
- payments;
- remote payments;
- top-ups;
- settlements;
- restructuring;
- write-offs/recoveries;
- arrears/collections.

This phase requires strong maker-checker regression.

### Phase 7 — Accounting and operational finance

Convert:

- financial accounts;
- capital;
- transfers;
- ledger;
- expenses;
- recurring expenses;
- payroll;
- cash reconciliation/daily closing;
- fixed assets.

### Phase 8 — Reports, management and cross-cutting UI

Convert:

- financial/loan reports;
- exports;
- notifications;
- tasks;
- calendar/appointments;
- management alerts;
- dashboard;
- global search;
- navigation/action buttons;
- document exports.

### Phase 9 — Full authorization audit

Run automated audit across:

- all registered routes;
- controllers;
- policies;
- Blade templates;
- jobs/commands that perform business mutations;
- exports/downloads;
- background scheduled operations;
- admin screens.

No business capability may remain unclassified.

### Phase 10 — Staging migration rehearsal

On a production-like database copy:

1. back up;
2. run migrations;
3. run authorization migration verifier;
4. compare before/after effective permissions for every user;
5. test representative users for every default role;
6. test branch isolation for single/multi/no-branch users;
7. test super-admin;
8. test maker-checker workflows;
9. run full application suite;
10. run authorization source/route audits.

### Phase 11 — Production cutover

During controlled deployment:

- verified pre-deployment backup;
- authorization migrations;
- permission synchronization;
- cache reset;
- release verification;
- targeted role smoke tests;
- audit role counts and assignments;
- preserve deployment evidence.

### Phase 12 — Legacy cleanup

Only after successful staging and production observation:

- remove custom `EnsurePermission`;
- remove deprecated custom `hasPermission()` wrapper;
- remove old `role_user` / `permission_role` pivots;
- remove fragmented permission seeders;
- remove deprecated broad permissions;
- tighten static source audits to zero legacy exceptions.

---

## 24. Background Jobs and Console Commands

Interactive user RBAC should not be blindly applied to system scheduler jobs because they run without a logged-in user.

Classify commands/jobs into:

1. **system automation** — runs under trusted application service context and must retain branch-safe iteration;
2. **user-triggered queued work** — persist initiating user/branch context and re-authorize where appropriate;
3. **administrative console command** — protected operationally at server access level, with explicit documentation.

Never fake a super-admin user simply to make scheduled jobs pass permission checks.

---

## 25. Exports and Downloads

Every export/download is data access and must be part of the permission audit.

Rules:

- permission required for the source business capability;
- branch/model scope required;
- export may require a stronger `.export` permission when bulk data is materially more sensitive than screen viewing;
- private files must still use secure file service/path checks;
- do not authorize a file solely by knowing its ID/path.

Reports should begin with separate `reports.view` and `reports.export` permissions.

---

## 26. Custom Role Governance

Custom roles are useful but must not make authorization unmanageable.

Rules:

- system roles have stable machine names;
- custom role machine name generated once and not casually changed;
- role names unique per `web` guard;
- system roles cannot be deleted;
- assigned custom roles cannot be deleted without explicit reassignment/removal workflow;
- high-risk permission changes show warnings and require authorized actor;
- every change audited;
- permission catalog remains system-controlled; administrators compose roles from permissions but do not invent arbitrary permission strings from the UI.

---

## 27. Role Assignment vs Branch Assignment

A role applies to the user's functional capability across the branches the user is assigned to.

Example:

```text
User roles: cashier
Assigned branches: Dodoma Main, Area C
```

The user may perform cashier-authorized operations in those two branches only.

Do not create duplicate roles such as:

```text
cashier-dodoma
cashier-area-c
```

Do not encode branch into permission names.

If the business later requires different functional roles per branch for the same user, that is a separate design change and must not be improvised through Spatie Teams without revisiting the existing branch architecture.

---

## 28. Definition of Done

The system-wide RBAC implementation is complete only when all of the following are true:

- Spatie is the canonical permission engine;
- one canonical permission catalog exists;
- one canonical default role matrix exists;
- every business route is authorization-classified;
- every permission used by code exists in the catalog;
- no new fragmented feature permission seeders exist;
- no business authorization depends on role-name checks except approved super/security cases;
- branch isolation remains fully green;
- maker-checker workflows remain fully green;
- role/staff administration prevents privilege escalation;
- all role and permission changes are audited;
- custom roles are supported safely;
- navigation/buttons use permission-aware rendering;
- direct URLs are still server-authorized;
- exports/downloads are authorized;
- permission cache behavior is tested;
- legacy permission/pivot code is removed after safe cutover;
- full SQLite test suite passes;
- full MySQL CI passes;
- production-config and release-verification gates pass;
- staging authorization rehearsal shows no unintended access gain/loss;
- production rollout includes a verified authorization assignment report.

---

## 29. Implementation Order Summary

Implement in this exact order:

1. Inventory and freeze current effective access.
2. Install Spatie and bridge schema without deleting legacy data.
3. Introduce canonical permission catalog/default role matrix.
4. Integrate User + Gate super-admin handling.
5. Secure role/staff administration and privilege-delegation ceilings.
6. Convert customer/collateral/loan origination.
7. Convert approvals and financial transaction workflows.
8. Convert accounting/payroll/cash/fixed assets.
9. Convert reports/management/navigation/search/downloads.
10. Add route/source authorization audits.
11. Rehearse migration on production-like staging data.
12. Cut over production with verified role/permission comparison.
13. Remove legacy RBAC only after successful observation.

This order intentionally secures the controls that assign privileges **before** migrating the rest of the business operations.
