# AccessMatrix Module — Architecture Map

> **Last updated:** 2026-06-10
> **Status:** Living document — incrementally updated as new information is discovered

---

## 1. Purpose & Responsibilities

The AccessMatrix module provides a **visual permission management interface** for the ERP. It manages:
- Full matrix view of all roles vs. all registered permissions (checkbox grid)
- Role comparison (diff two roles to see unique/shared permissions)
- Permission toggle (grant/revoke individual permissions per role)
- Role cloning (duplicate a role's permissions into a new role)
- Permission health audit (orphan permissions, unprotected routes, dependency violations)
- CSV export of the complete matrix

It does **NOT** directly manage:
- Role CRUD (roles table is managed elsewhere, matrix just reads/modifies permissions)
- User-to-role assignment (handled by HR/Auth modules)
- Permission registration (each module registers its own permissions in bootstrap.php)

---

## 2. Directory & File Structure

```
app/Modules/AccessMatrix/
├── bootstrap.php                              # Permission + menu registration
├── Routes.php                                 # 6 routes
├── Controllers/
│   └── AccessMatrixController.php            # All actions (index, compare, toggle, export, clone, health)
├── Services/
│   ├── MatrixService.php                     # Build matrix, compare roles, clone roles, export CSV
│   └── PermissionDiscoveryService.php        # Orphan detection, unprotected routes, dependency validation
└── Views/
    ├── index.php                             # Full matrix grid with toggle checkboxes + clone modal
    ├── compare.php                           # Side-by-side role comparison
    └── health.php                            # Permission health dashboard
```

---

## 3. Database Schema (Production — Source of Truth)

### 3.1 `roles` Table (29 active rows as of 2026-06-10)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| role_code | varchar(50) | NO | UNI | Unique role identifier |
| name_ar | varchar(200) | NO | | Arabic display name |
| name_en | varchar(200) | YES | | English display name |
| description_ar | varchar(500) | YES | | |
| description_en | varchar(500) | YES | | |
| is_system | tinyint(1) | NO | | Default: 0 |
| is_active | tinyint(1) | NO | | Default: 1 |
| parent_role_id | bigint unsigned | YES | MUL | FK to roles (self-ref hierarchy) |
| has_all_branches | tinyint(1) | NO | | Default: 0 |
| category | varchar(50) | YES | | Role grouping category |
| level | tinyint unsigned | NO | | Sort order / hierarchy level, default 0 |
| is_template | tinyint(1) | NO | | Default: 0 |
| created_at | timestamp | NO | | |
| updated_at | timestamp | NO | | On update cascade |
| created_by | bigint unsigned | YES | | FK to employees |
| updated_by | bigint unsigned | YES | | FK to employees |

### 3.2 `role_permissions` Table (656 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| role_id | bigint unsigned | NO | MUL | FK to roles |
| permission_key | varchar(100) | NO | MUL | Permission string (e.g. 'member.view') |
| granted_at | timestamp | NO | | When permission was granted |
| granted_by | bigint unsigned | YES | | FK to employees (who granted it) |

### 3.3 `permission_dependencies` Table (95 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| permission_key | varchar(100) | NO | MUL | The permission that depends on another |
| requires_key | varchar(100) | NO | MUL | The required permission |
| is_auto_grant | tinyint(1) | NO | | Default: 1; if true, granting parent auto-grants child |

---

## 4. Routes

| Method | Path | Action | Middleware | Permission |
|--------|------|--------|------------|------------|
| GET | /access-matrix | index | auth | access_matrix.view |
| GET | /access-matrix/compare | compare | auth | access_matrix.view |
| POST | /access-matrix/toggle | toggle | auth, csrf | access_matrix.manage |
| GET | /access-matrix/export | export | auth | access_matrix.view |
| POST | /access-matrix/clone | clone | auth, csrf | access_matrix.manage |
| GET | /access-matrix/health | health | auth | access_matrix.view |

---

## 5. Core Business Flows

### 5.1 Matrix View (index)
1. `MatrixService::buildMatrix()` queries all active roles ordered by `level`
2. Reads all registered permissions from `PermissionRegistry::getAllGrouped()`
3. For each role, queries `role_permissions` to get assigned keys
4. Wildcard check: if a role has `*` permission_key, all checkboxes show as granted
5. Returns grid data: groups > permissions > roles (granted: bool)

### 5.2 Permission Toggle (AJAX)
1. Receives `role_id` + `permission_key`
2. Validates role exists
3. Checks if `role_permissions` row exists for that combination
4. If exists: DELETE (revoke); if not: INSERT (grant)
5. Returns JSON `{granted: bool}`

### 5.3 Role Clone
1. Validates source role exists and new code is unique
2. Creates new role with same properties (sets `is_system=0`, `is_template=0`, `is_active=1`)
3. Copies all `role_permissions` from source to new role

### 5.4 Health Audit
- **Orphan Permissions**: Registered in `PermissionRegistry` but not used in any route's permission slot
- **Unprotected Routes**: Routes that have no permission key in position [4]
- **Dependency Violations**: Roles that have permission X which requires permission Y, but Y is missing
- **Stats**: Total permissions, group count, distribution by group

---

## 6. Events Dispatched

None. This module does not dispatch any events.

---

## 7. Events Consumed

None. This module does not listen to any events.

---

## 8. Cross-Module Dependencies

### 8.1 AccessMatrix IMPORTS FROM:

| Module/Component | What it uses |
|-----------------|-------------|
| App\Core\Registries\PermissionRegistry | `getAllGrouped()`, `getAll()` — reads all registered permissions from all modules |
| App\Core\Registries\MenuRegistry | Menu registration (bootstrap) |

### 8.2 Other modules that IMPORT FROM AccessMatrix:

None. No other modules reference AccessMatrix services or controllers.

### 8.3 Database Dependencies (reads from other modules' data)

| Table | Owner Module | How it's used |
|-------|-------------|---------------|
| roles | Auth/Settings | Read roles, insert cloned roles |
| role_permissions | Auth/Settings | Read, insert, delete permissions |
| permission_dependencies | Auth/Settings | Read dependency rules for health validation |
| employees | HR | Read current employee for `granted_by` |

---

## 9. Permissions

| Key | Description |
|-----|-------------|
| access_matrix.view | View the matrix, compare roles, export CSV, view health |
| access_matrix.manage | Toggle permissions, clone roles |

---

## 10. Background Processes (Cron)

None.

---

## 11. High-Risk Areas

### 11.1 Permission Toggle (CRITICAL)
- Direct manipulation of `role_permissions` table — no audit trail beyond `granted_at`/`granted_by`
- No confirmation step — single AJAX call grants/revokes
- Wildcard `*` roles can't be modified via toggle (they already have everything)
- If system roles (`is_system=1`) are modified, could break expected default behavior

### 11.2 Role Clone
- Creates a new role immediately — no approval workflow
- If source role has `*` permission, the clone also gets all individual permission rows (not wildcard)
- No way to undo a clone (must manually delete the role)

### 11.3 PermissionDiscoveryService File Scanning
- `findUnprotectedRoutes()` and `findOrphanPermissions()` scan ALL module Routes.php files via `glob()`
- Uses `require` to load route files — any side effects in route files will execute
- Performance: re-reads all route files on every health page load (no caching)

### 11.4 Dependency Validation
- `permission_dependencies` table data must be maintained manually
- If dependencies are stale or missing, health audit won't catch real violations
- No enforcement — violations are reported but permissions can still be granted

---

## 12. Known Patterns & Gotchas

1. **No EventBus usage**: This module is purely CRUD on permission data — no events dispatched or consumed
2. **PermissionRegistry is read-only**: The matrix doesn't modify what permissions exist, only which roles have them
3. **Wildcard permission**: A role with `permission_key = '*'` in `role_permissions` is treated as having all permissions
4. **CSV export includes BOM**: Output starts with `\xEF\xBB\xBF` for proper Excel Arabic support
5. **Health page performance**: Scans all Routes.php files + validates all roles' dependencies on every load
6. **No soft delete on roles/role_permissions**: Revoked permissions are hard-deleted
