# MVNexus — Ultimate System Audit Report

**Audit date:** 2026-05-20  
**Auditor posture:** Senior Staff Engineer / Security Auditor / QA Lead / Product Reviewer  
**Method:** Full code-path review of backend (11 Laravel modules), frontend (Vite + React SPA), database (53 migrations), routes, policies, realtime, queues, i18n, and tests. **No prior audit doc was trusted without re-verification.**  
**Test run:** `php artisan test` — **166 passed / 166** (12.8s). **Search, Filter, and Ticket statistics have zero test coverage.**

---

## 1. Executive Summary

MVNexus is a **modular Laravel monolith + Vite/React SPA** with Sanctum cookie auth, department/company multi-tenancy, Reverb realtime, TanStack Query, and EN/AR i18n. Core ticketing, auth, department portals, notifications (in-app), SLA tracking, and most admin CRUD surfaces are **real implementations backed by APIs** — not mock UI.

However, the application has **multiple production-blocking security and tenancy defects** that tests do not catch:

| Severity | Count | Examples |
|----------|-------|----------|
| **Critical (P0)** | 8 | Cross-department search leak, saved-filter IDOR, `TicketPolicy` fatal typo, missing `User::department_id`, unauthenticated statistics endpoint, `AdminInviteController` missing import |
| **High (P1)** | 12 | Settings read exposes encrypted values to any dept member, company admin can assign dept heads (business rule violation), search category filter uses wrong column, visibility key mismatch |
| **Medium (P2)** | 18 | Frontend permission UX leaks, cache invalidation gaps, E2E tests are mocked not integration |
| **Low / cleanup** | 25+ | Dead SSO/routing code, unused API endpoints, stale agent terminology |

**Bottom line:** Suitable for **controlled UAT with super-admin and single-department pilots**. **Not production-ready** for multi-department enterprise rollout until P0/P1 items are remediated.

---

## 2. Readiness Score: **58 / 100**

| Dimension | Score | Rationale |
|-----------|-------|-----------|
| Security | 45 | Tenancy leaks in Search/Filters/Statistics; settings read overexposure |
| Multi-tenant isolation | 50 | Policies exist but optional `department_id` + no membership validation in middleware |
| Auth / session | 78 | Sanctum + OTP/magic link solid; invite endpoint unthrottled |
| Core ticketing | 72 | CRUD + policies mostly correct; visibility settings broken |
| Admin / settings | 55 | Real CRUD; permission/business-rule mismatches |
| Dashboard / reports | 68 | Real API; partial frontend error handling |
| Search | 35 | Broken scoping, wrong column, no tests |
| Notifications | 65 | In-app + email work; Slack/SMS fake; runtime email errors in logs |
| Realtime | 75 | Channel auth correct; department channel broader than ticket visibility |
| SLA | 70 | Job + events work; `SlaMetEvent` dead |
| Onboarding | 74 | Real wizard + API; not blocking |
| i18n / RTL | 70 | Namespaces complete; API error toasts English-only |
| Test coverage | 55 | 166 backend tests; **zero** search/filter/statistics tests; E2E mocked |
| Dead code / cleanup | 60 | SSO, routing rules, unused endpoints |

---

## 3. UAT Readiness: **Conditional YES**

**Safe for UAT if:**
- Single company, 1–3 departments
- Super admin (`said.abdulaziem@mountainview-eg.com`) performs cross-dept operations
- Department heads use **only their department context** (always pass `department_id`)
- Search module **disabled or super-admin-only** until fixed
- Saved filters **not used** in UAT
- Email notifications verified in target environment (Mailgun configured)

**Not safe for UAT:**
- Multi-department users exploring Search quick filters
- Company admins assigning department heads (violates stated business rule)
- Penetration testing on `/api/v1/search/*` or `/api/v1/filters/*`

---

## 4. Production Readiness: **NO**

Blockers prevent enterprise multi-tenant production until remediated. Estimated **2–3 sprints** for P0+P1 fixes + integration tests.

---

## 5. Critical Blockers (P0)

### CB-01 — `TicketPolicy` calls nonexistent method (runtime fatal for agent visibility path)

```59:59:app/Modules/Ticketing/Policies/TicketPolicy.php
                $visibilitySettings = $user->getDepartmentVisibilitySettings($ticket->department_id);
```

User model only defines `getVisibilitySettings()`:

```265:271:app/Modules/Identity/Models/User.php
    public function getVisibilitySettings(string $departmentId): array
    {
        $department = $this->departments()
            ->where('departments.id', $departmentId)
            ->first();

        return $department?->pivot->visibility_settings ?? [];
```

**Impact:** Any user with `DepartmentRole::AGENT` hitting ticket view policy path gets **500 error**.

---

### CB-02 — Visibility settings key mismatch (policy always denies agent broad view)

Policy reads `can_see_all_tickets`; defaults use `see_all_tickets`:

```61:64:app/Modules/Ticketing/Policies/TicketPolicy.php
                return match ($visibilitySettings['can_see_all_tickets'] ?? false) {
                    true => true,
                    false => $ticket->current_assignee_id === $user->id || $ticket->requester_id === $user->id,
                };
```

```64:68:app/Modules/Identity/Enums/DepartmentRole.php
            self::AGENT => [
                'see_all_tickets' => false,
                'see_unassigned_tickets' => true,
```

**Impact:** Even after CB-01 fix, visibility settings **never apply correctly**.

---

### CB-03 — `User::department_id` does not exist (Search/Filter broken)

```52:55:app/Modules/Search/Controllers/SearchController.php
        $tickets = $this->searchService->myTickets(
            $request->user()->id,
            $request->user()->department_id,
```

User has `primaryDepartmentId()` method, not `department_id` property:

```216:223:app/Modules/Identity/Models/User.php
    public function primaryDepartmentId(): ?string
    {
        $primary = $this->departments()
            ->where('department_user.is_primary', true)
            ->first();

        return $primary?->id;
    }
```

Same bug in `FilterController.php` lines 29, 33, 84, 138.

**Impact:** Quick filters and saved filters operate with `null` department → combined with CB-04 yields cross-department data.

---

### CB-04 — Search returns cross-department tickets when `department_id` omitted

```41:43:app/Modules/Search/Services/SearchService.php
        if ($searchQuery->departmentId) {
            $this->queryBuilder->applyDepartmentIsolation($query, $searchQuery->departmentId);
        }
```

No `TicketPolicy::viewAny` scoping. No membership validation on client-supplied `department_id`.

**Impact:** Authenticated user with `department.tickets.view` can search **all departments' tickets** by omitting or spoofing department context.

---

### CB-05 — Saved filter IDOR (any user can update/delete/apply any filter)

```98:109:app/Modules/Search/Controllers/FilterController.php
    public function update(Request $request, string $id): JsonResponse
    {
        $validated = $request->validate([...]);
        $filter = $this->filterService->updateFilter($id, $validated);
```

No ownership check. Repository updates by ID only.

**Impact:** Cross-user filter tampering; applying another user's filter may expose tickets outside intended scope.

---

### CB-06 — Ticket statistics endpoint has no authorization

```226:232:app/Modules/Ticketing/Controllers/TicketController.php
    public function statistics(Request $request, string $departmentId): JsonResponse
    {
        $statistics = $this->ticketService->getDepartmentStatistics($departmentId);

        return response()->json([
            'data' => $statistics,
        ]);
```

**Impact:** Any authenticated user can read department ticket statistics for **any** `{departmentId}` UUID.

---

### CB-07 — `AdminInviteController` missing `Permission` import (runtime error for company admins)

```33:35:app/Modules/Identity/Controllers/AdminInviteController.php
        if (! $user->is_super_admin) {
            if ($user->hasPermissionTo(Permission::COMPANY_USERS_MANAGE->value)) {
```

No `use App\Modules\Identity\Enums\Permission;` in file.

**Impact:** Company admin listing invites → **500 Class not found**.

---

### CB-08 — `DepartmentIsolation` middleware sets context but never validates membership

```39:51:app/Shared/Http/Middleware/DepartmentIsolation.php
        $departmentId = $request->route('departmentId')
            ?? $request->route('department')
            ?? $request->header('X-Department-Id')
            ?? $request->input('department_id')
            ?? $user->departments()->first()?->id;

        if ($departmentId) {
            $request->attributes->set('department_id', $departmentId);
            app()->instance('current_department_id', $departmentId);
        }
```

**Impact:** Client can set `X-Department-Id` to any UUID; downstream optional scoping uses spoofed context.

---

## 6. High-Priority Issues (P1)

| ID | Issue | Location | Impact |
|----|-------|----------|--------|
| HP-01 | Settings `index`/`show` readable by any department **member** (no `DEPARTMENT_SETTINGS_MANAGE` check on read) | `SettingsController.php` L48–106, `SettingsContextResolver.php` L20–36 | Encrypted setting **values** returned via `getCastedValue()` |
| HP-02 | Company admin role includes `department.heads.manage` | `RolePermissionSeeder.php` L118 | Violates requirement: only super admin assigns heads |
| HP-03 | Company admin can move users across departments via `manageDepartments` | `UserPolicy.php` L115–117, `UserDepartmentController.php` | Violates stated super-admin-only cross-dept move rule |
| HP-04 | Search category filter uses wrong column `ticket_category_id` | `QueryBuilderService.php` L58 | Category filter in search **never matches** (model uses `category_id`) |
| HP-05 | Ticket list optional department filter | `TicketRepository.php` L42–44 | Same cross-dept leak pattern as search |
| HP-06 | Reply delete: any department member can delete any reply | `ReplyPolicy.php` L71 | Over-permissive delete |
| HP-07 | Attachment delete bypasses ticket policy | `AttachmentController.php` L34–37 | Uploader OR super admin only; managers cannot moderate |
| HP-08 | `accept-invite` has no rate limit | `Identity/Routes/api.php` L38 | Invite token brute-force |
| HP-09 | Health endpoint exposes `APP_DEBUG` | `HealthController.php` L21 | Info disclosure |
| HP-10 | `trustProxies(at: '*')` | `bootstrap/app.php` L29 | IP spoofing for OTP rate limits if misconfigured |
| HP-11 | Email notifications failing at runtime | `storage/logs/laravel.log` | `Array to string conversion`, `Mailer [mailgun] is not defined` |
| HP-12 | Frontend ticket actions shown without permission checks | `TicketDetailSidebar.tsx` L186–300 | UX leak → 403 on submit |

---

## 7. Medium Issues (P2)

- **PermissionRoute** silent redirect to dashboard (no forbidden message) — `PermissionRoute.tsx`
- **Settings page** uses role name heuristics (`super`/`admin`) not permissions — `SettingsPage.tsx` L143–147
- **Header Settings link** visible to all authenticated users — `Header.tsx` L262–265
- **Create ticket button** shown without `department.tickets.create` check — `TicketsPage.tsx`
- **Nav vs route permission mismatch** for Invites/Users — `navConfig.ts` vs `routePermissions.ts`
- **No query invalidation** after ticket create (app + portal) — `useCreateTicket`, portal create hook
- **Search results stale** after ticket mutations — no `['search']` invalidation
- **Dashboard ticket trends** hides on error with no message — `DashboardPage.tsx` L244–260
- **Notification realtime** uses `refetchType: 'none'` — list may stay stale
- **Audit logs page** uses raw fetch not TanStack Query — `AdminAuditLogsPage.tsx`
- **No optimistic updates** anywhere (acceptable but UX latency)
- **API interceptor toasts** hardcoded English — `api.ts` L126–186
- **DepartmentRole::AGENT** still used throughout despite "users only, no agents" product rule
- **MonitorSlaComplianceJob** runs every minute with no distributed lock — duplicate breach events risk under multi-worker
- **Audit retention** configured (90 days) but no purge job in `routes/console.php`
- **`HasCompanyScope`** global scope stub always returns null — `HasCompanyScope.php` L47–51
- **Broadcast department channel** allows any member, not ticket-level visibility — `routes/channels.php` L40–46
- **Attachment public URL** via `getUrl()` if disk is public — `Attachment` model
- **E2E tests use mocked API** — not true integration — `critical-flows.spec.ts` L6–8

---

## 8. Low-Priority Cleanup

- Dead: `SsoIdentity` model + migration, `RoutingService`, `SlaMetEvent`, `RateLimiter::for('auth')` unused
- Deprecated aliases: `agentPerformance`, `agents` endpoints
- Unused frontend: `Can.tsx`, `AdminRoute.tsx`, `useChecklistProgress.ts`, `useDepartmentAgents`, `SETTINGS_NAV_ITEM`
- Unused API constants: `DASHBOARD.CONSOLIDATED`, `SLA_METRICS`, `RECENT_ACTIVITY`, `AUDIT_LOGS.SHOW`, authentication-logs routes
- `@laravel/echo-react` in devDependencies but manual `echoClient.ts` used
- `config/audit.php` `track_failed_requests` never read
- `platform_manager` global role exists with broad read — document or remove
- Hardcoded super admin email in seeder — `SuperAdminSeeder.php` L29
- Hardcoded OTP domain default — `config/otp.php` L7

---

## 9. Security Review

### Strengths
- Sanctum stateful SPA + CSRF (`bootstrap/app.php` L27, `api.ts` XSRF handling)
- OTP rate limiting (route + `OtpService` email/IP keys)
- Generic auth responses (no email enumeration)
- Session regeneration on login (`AuthLoginService.php` L27)
- Security headers middleware appended
- Sensitive audit attribute filtering
- Notification dedupe service
- Attachment MIME/extension blocklist
- Broadcast auth requires Sanctum + ticket policy for ticket channels

### Weaknesses
- **Tenancy enforcement is opt-in per query**, not defense-in-depth
- **No Search/Filter policies**
- **Settings read authorization weaker than write**
- **Health endpoints public** with environment/debug info
- **Virus scan placeholder** only logs (`AttachmentService.php` L187–191)
- **Slack/SMS channels return null** — preferences may imply support (`NotificationService.php` L159–160)
- **Reply create** allows any ticket viewer (`ReplyPolicy.php` L37) — requesters can reply on closed tickets? Only checks `!$ticket->isClosed()` ✓

### CSRF / Session / Sanctum
- Correct SPA pattern: `/sanctum/csrf-cookie` → session cookie → API with credentials
- `AuthenticateSession` configured in sanctum.php
- **Risk:** `SANCTUM_STATEFUL_DOMAINS` must be explicitly set in production

---

## 10. Multi-Tenant Isolation Review

| Layer | Company isolation | Department isolation | Verdict |
|-------|-------------------|----------------------|---------|
| User/Company policies | ✅ `company_id` checks | N/A | Good |
| Department policies | ✅ | ✅ head scoping | Good |
| Ticket policy | ✅ super admin bypass | ⚠️ visibility broken | Partial |
| Ticket repository list | ✅ if filter set | ❌ optional dept filter | **Leak** |
| Search service | ❌ | ❌ optional + no membership | **Dangerous** |
| Filter service | ❌ | ❌ null dept from bug | **Dangerous** |
| Dashboard | ✅ `DashboardAudienceScope` | ✅ | Good |
| Audit logs | ✅ `AuditAudienceScope` | ✅ manager-scoped | Good |
| Settings read | ✅ company match on write | ❌ member can read all | **Leak** |
| Middleware | Sets context only | ❌ no validation | **Gap** |

**Company isolation** is generally enforced at policy/service level. **Department isolation** fails in Search, Filters, Statistics, and optional ticket listing.

---

## 11. Auth / Session Review

| Flow | Status | Notes |
|------|--------|-------|
| OTP request/verify | ✅ Production-viable | Throttled; domain restriction via `OTP_ALLOWED_EMAIL_DOMAIN` |
| Magic link | ✅ | Same throttle group |
| Trusted devices | ✅ | Fingerprint in OTP verify |
| Session management | ✅ | `/auth/sessions`, revoke endpoints |
| Admin invite | ⚠️ | Works but list broken for company admin (CB-07); accept unthrottled |
| Microsoft/SSO | ✅ Removed from flows | Schema remnant only |
| Super admin | ✅ | `is_super_admin` flag + seeder |

---

## 12. Realtime Review

| Component | Status |
|-----------|--------|
| Echo client + Reverb | ✅ Disabled gracefully without `VITE_REVERB_APP_KEY` |
| Broadcast auth route | ✅ `auth:sanctum` + CSRF |
| `user.{id}` channel | ✅ ID match |
| `ticket.{id}` channel | ✅ `Gate::allows('view', $ticket)` |
| `ticket.{id}.notes` | ✅ Note policy |
| `department.{id}.tickets` | ⚠️ Any dept member (broader than ticket visibility) |
| Presence channel | ✅ view policy + avatar |
| SafeBroadcaster | ✅ Failures don't block HTTP |
| Frontend invalidation | ✅ Dashboard + notifications |

**Risk:** Department dashboard realtime may trigger refreshes for tickets user cannot view individually.

---

## 13. Onboarding Review

| Item | Status |
|------|--------|
| API (`/onboarding/*`) | ✅ Real persistence |
| SetupWizard | ✅ Multi-persona, real admin APIs |
| GuidedTour | ✅ DOM `data-tour` targets |
| Checklist | ✅ `ChecklistProgressService` |
| Gate wrapper | ✅ `OnboardingGate.tsx` |
| Department head invite step | ✅ Respects `DEPARTMENT_HEADS_MANAGE` in UI |

**Issue:** Onboarding can complete while Search module remains broken — checklist doesn't validate tenancy security.

---

## 14. Admin / Settings Review

### Admin pages (frontend → API)

| Page | Backend | Verdict |
|------|---------|---------|
| Companies | ✅ CRUD | Production-safe (super admin) |
| Roles | ✅ | Production-safe (super admin) |
| Users | ✅ | UAT-only (cross-dept rules mismatch) |
| Invites | ⚠️ | **Broken for company admin** (CB-07) |
| Departments | ✅ | UAT-only |
| Categories | ✅ | UAT-only |
| Ticket meta | ✅ | UAT-only |
| SLA policies | ✅ | UAT-only |
| Notification templates | ✅ | UAT-only |
| Assignee performance | ✅ | UAT-only |
| Audit logs | ✅ list only | UAT-only |

### Settings
- **Real CRUD** with inheritance (department → company → global → default)
- **Write authorization** via `SettingsContextResolver` ✅
- **Read authorization** missing for department scope ❌
- Frontend scope toggles use role heuristics not permissions ❌

---

## 15. Dashboard Review

- **Not fake** — calls `/dashboard/overview`, `/ticket-trends`, `/assignee-performance`
- Backend uses `DashboardPolicy` + `DashboardAudienceScope` ✅
- Unused backend endpoints: `/dashboard/consolidated`, `/dashboard/sla-metrics`, `/dashboard/recent-activity` — **no frontend consumer**
- Frontend hides assignee chart on error without message
- Realtime invalidation on department ticket events ✅

---

## 16. Notifications Review

| Channel | Status |
|---------|--------|
| In-app | ✅ Real + broadcast |
| Email | ⚠️ Works when Mailgun configured; runtime errors in logs |
| Slack | ❌ Fake (`null` channel) |
| SMS | ❌ Fake (`null` channel) |
| Preferences | ✅ User-scoped |
| Templates | ✅ Admin CRUD + dept override |
| Dedupe | ✅ `NotificationDedupeService` |
| Listeners | ✅ Queued |

**Log evidence:** `Array to string conversion` on ticket_assigned/reply templates; `Mailer [mailgun] is not defined` when misconfigured.

---

## 17. SLA Review

| Component | Status |
|-----------|--------|
| Policy CRUD | ✅ |
| Tracking on ticket create | ✅ `StartSlaTracking` listener |
| First reply marks response met | ✅ |
| Status change handling | ✅ |
| Monitor job (every minute) | ✅ Warning + breach events |
| Breach/warning notifications | ✅ |
| `SlaMetEvent` | ❌ Dead — no listeners |
| Frontend SLA summary | ✅ On ticket detail |
| Dashboard SLA metrics API | ❌ No UI |

---

## 18. Ticketing Review

| Feature | Status |
|---------|--------|
| Create/read/update/delete | ✅ Policy-gated |
| Assign/unassign | ✅ |
| Replies + notes + attachments | ✅ |
| Department portal create | ✅ Ignores payload `department_id` (test proves) |
| Profile my-tickets | ✅ Cross-department for requester |
| Statistics endpoint | ❌ **Unauthenticated scope** |
| Visibility settings | ❌ **Broken** (CB-01, CB-02) |
| Agent terminology | ⚠️ Still in `DepartmentRole::AGENT` |

---

## 19. Search Review

| Feature | Status |
|---------|--------|
| Full-text search | ⚠️ Works but **cross-dept leak** |
| Filters (status, priority, etc.) | ⚠️ Category filter **broken column** |
| Quick filters | ❌ **Broken** (`department_id` bug) |
| Saved filters | ❌ **IDOR** |
| Suggestions | ✅ User-scoped history |
| Rate limit | ✅ `throttle:search` 60/min |
| Frontend SearchPage | ✅ Wired to API |
| Tests | ❌ **None** |

---

## 20. Audit Logs Review

| Feature | Status |
|---------|--------|
| Model observers | ✅ Ticket, User, Department, etc. |
| Auth event logging | ✅ Login/logout/failed |
| Audience scoping | ✅ Company/dept/global permissions |
| Sensitive filtering | ✅ |
| Authentication logs API | ✅ Backend exists |
| Authentication logs UI | ❌ **No frontend** |
| Audit detail API | ✅ Exists |
| Audit detail UI | ❌ **Not wired** |
| Retention purge | ❌ **Not scheduled** |
| Reply/Note/Attachment audit | ❌ Not audited |

---

## 21. Translation / i18n Review

| Area | EN | AR | RTL |
|------|----|----|-----|
| UI namespaces (12) | ✅ | ✅ | ✅ `LocaleProvider` sets `dir` |
| Ticket status/priority slugs | ✅ | ✅ | via `translateMetricLabel` |
| API error toasts | ❌ English only | ❌ | N/A |
| Backend notification payloads | ⚠️ Locale-aware templates | ✅ | N/A |
| Dynamic settings group tabs | ⚠️ Raw key fallback | ⚠️ | N/A |
| DepartmentRole labels | Still says "Agent" | "وكيل" | Product mismatch |

---

## 22. Performance Review

| Issue | Location | Severity |
|-------|----------|----------|
| N+1 in `TicketPolicy::view()` | Multiple `$user->departments()` queries | Medium |
| N+1 in `belongsToDepartment()` | Hot path `exists()` each call | Medium |
| Search `orWhereHas` on replies/notes | `QueryBuilderService.php` L32–37 | High load on large datasets |
| SLA monitor every minute, all active trackings | `MonitorSlaComplianceJob` | Medium — needs index on `sla_status` |
| Ticket list eager loading | ✅ Good | — |
| Search eager loading | ✅ Good | — |
| Duplicate dashboard refetch on realtime | Debounced invalidation | Low |
| No React Query Devtools in prod | — | OK |

**Missing indexes to verify:** `tickets.department_id`, `tickets.current_assignee_id`, `ticket_sla_tracking.sla_status` — present in migrations but worth EXPLAIN on production volume.

---

## 23. Dead Code Inventory

| Item | Path | Evidence |
|------|------|----------|
| SSO identity | `app/Modules/Identity/Models/SsoIdentity.php` | No routes/controllers |
| Routing rules | `app/Modules/Categories/Services/RoutingService.php` | No API |
| SlaMetEvent | `app/Modules/Sla/Events/SlaMetEvent.php` | No listeners |
| HasCompanyScope | `app/Shared/Traits/HasCompanyScope.php` | Returns null |
| Auth rate limiter | `AppServiceProvider.php` L34–37 | Never applied |
| Can component | `resources/js/src/features/auth/components/Can.tsx` | Zero imports |
| AdminRoute | `resources/js/src/features/auth/components/AdminRoute.tsx` | Zero imports |
| useChecklistProgress | `features/onboarding/hooks/useChecklistProgress.ts` | Zero imports |
| useDepartmentAgents | `useDepartmentAssignableUsers.ts` | Only assignable used |
| fetchAgentPerformance | `adminService.ts` | Deprecated alias |
| Dashboard unused endpoints | `apiEndpoints.ts` L48–54 | No consumers |
| Auth logs frontend | — | No pages |

---

## 24. Fake Implementation Inventory

| Feature | Claimed | Actual | Path |
|---------|---------|--------|------|
| Virus scanning | Config flag | Log placeholder only | `AttachmentService.php` L187 |
| Slack notifications | Enum channel | Returns null | `NotificationService.php` L159 |
| SMS notifications | Enum channel | Returns null | `NotificationService.php` L160 |
| SSO / Microsoft | — | DB schema only | `sso_identities` migration |
| Category routing rules | Service exists | No API/UI | `RoutingService.php` |
| Company global scope | Trait on User | Stub | `HasCompanyScope.php` |
| Failed request audit | Config | Never read | `config/audit.php` L108 |
| Dashboard consolidated view | API exists | No UI | `DashboardController::consolidated` |
| Dashboard SLA metrics | API exists | No UI | `DashboardController::slaMetrics` |
| Dashboard recent activity | API exists | No UI | `DashboardController::recentActivity` |
| Authentication logs admin | API exists | No UI | Audit routes |
| Audit log detail | API exists | No UI | `AUDIT_LOGS.SHOW` unused |
| E2E critical flows | "Production flows" | **Mocked API** | `critical-flows.spec.ts` |

**Not fake (verified real API):** Dashboard overview/trends/assignee, admin CRUD, onboarding wizard, department portals, notification bell, ticket lifecycle.

---

## 25. API Contract Mismatches

| Frontend | Backend | Issue |
|----------|---------|-------|
| Search filters `category_id` | QueryBuilder uses `ticket_category_id` | Filter silently fails |
| User primary department | Controllers use `$user->department_id` | Always null |
| Settings `canEditCompany` | Requires `COMPANY_SETTINGS_MANAGE` permission | UI uses role name heuristic |
| `adminService.fetchAssigneePerformance` | Returns `assignees` + legacy `agents` | ✅ Defensive parsing (good) |
| Ticket statistics | No frontend consumer found | Orphan endpoint |
| Visibility settings keys | `see_all_*` in DB/defaults vs `can_see_all_tickets` in policy | Logic mismatch |

---

## 26. Missing E2E Coverage

| Flow | Unit tests | Feature tests | E2E |
|------|------------|---------------|-----|
| OTP login | ✅ | ✅ | Mocked smoke |
| Magic link | ✅ | ✅ | Mocked |
| Invite accept | Partial | ✅ | Mocked |
| Ticket CRUD | ✅ | ✅ | Mocked |
| Ticket assignment | ✅ | ✅ | Mocked |
| Department portal | ✅ | ✅ | Smoke spec |
| Onboarding wizard | ✅ | ✅ | Smoke spec |
| Search | ❌ | ❌ | ❌ |
| Saved filters | ❌ | ❌ | ❌ |
| Cross-dept isolation | ❌ | ❌ | ❌ |
| Settings encrypted read | ❌ | Partial | ❌ |
| Realtime ticket updates | ✅ unit | Partial | ❌ |
| SLA breach flow | ✅ unit | Partial | ❌ |
| Admin company admin invite list | ❌ | ❌ | ❌ |
| Email notification delivery | Partial | ✅ | ❌ |
| RTL / AR UI | ❌ | ❌ | ❌ |
| Permission forbidden UX | Partial | ❌ | ❌ |

---

## 27. Recommended Cleanup Plan

### Phase 1 — Security hotfix (1 week)
1. Fix CB-01 through CB-08
2. Add Search/Filter feature tests for tenancy
3. Restrict settings read to manage permissions or filter non-public keys
4. Add `Permission` import to `AdminInviteController`
5. Throttle `accept-invite`

### Phase 2 — Business rule alignment (1 week)
1. Remove `DEPARTMENT_HEADS_MANAGE` from company admin role OR restrict `manageHeads` policy to super admin only
2. Restrict cross-department membership changes to super admin
3. Rename/remove `DepartmentRole::AGENT` → align with "users only" model
4. Fix visibility settings key naming (single canonical schema)

### Phase 3 — UX + i18n (1 week)
1. Wire `Can` component to ticket actions, settings links
2. Translate API error toasts
3. Forbidden page instead of silent redirect
4. Cache invalidation on ticket create

### Phase 4 — Cleanup (ongoing)
1. Remove SSO/routing dead code or implement
2. Remove unused API endpoints or build UI
3. Implement audit retention job
4. Replace E2E mocks with real API integration tests in CI

---

## 28. Recommended Architecture Simplifications

1. **Single department context service** — Replace `$user->department_id` fantasy with `DepartmentContext::resolve($user, $request)` that validates membership; use everywhere (Search, Filter, Ticket list, Settings, Middleware).

2. **Mandatory department scope on all ticket queries** — Default to user's primary department; super admin must explicitly pass `department_id` or `all=true` with audit log.

3. **Collapse agent/manager/viewer into permissions** — Product says "users only"; `DepartmentRole` adds confusion. Use permissions + `is_department_head` flag only.

4. **One authorization pattern for reads** — Apply policy checks in controllers OR global scope, not optional `if ($departmentId)`.

5. **Delete or implement notification channels** — Remove Slack/SMS from enum/preferences until implemented to avoid false expectations.

6. **Consolidate dashboard endpoints** — Either expose consolidated/SLA/activity in UI or remove endpoints.

---

## 29. Production Rollout Checklist

- [ ] All P0 blockers resolved and verified
- [ ] Search/Filter tenancy tests passing
- [ ] `SANCTUM_STATEFUL_DOMAINS` set for production domain
- [ ] `APP_DEBUG=false` verified; health endpoint stripped or authenticated
- [ ] Mailgun (or mail driver) configured and email templates tested
- [ ] Reverb configured (`BROADCAST_CONNECTION=reverb`, `VITE_REVERB_*`)
- [ ] Queue worker running (`MonitorSlaComplianceJob` requires scheduler)
- [ ] Redis/cache for sessions if multi-node
- [ ] `trustProxies` restricted to load balancer IPs
- [ ] OTP domain restriction reviewed (`OTP_ALLOWED_EMAIL_DOMAIN`)
- [ ] Super admin seeder run once; remove hardcoded email from docs/runbooks if needed
- [ ] Attachment storage disk private (not public URLs)
- [ ] Rate limits verified on auth endpoints
- [ ] Pen test on Search, Filters, Statistics, Settings read
- [ ] AR/RTL UAT on ticket detail, dashboard, admin tables
- [ ] Real E2E suite against staging API (not mocks)

---

## 30. Final Verdict

MVNexus is a **substantially implemented enterprise helpdesk** with real auth, ticketing, SLA, notifications, admin CRUD, department portals, onboarding, and audit infrastructure. It is **not a hollow prototype**.

However, **critical multi-tenant isolation failures in Search, Filters, and Statistics**, combined with **broken ticket visibility policy code**, **missing Permission import breaking admin invites**, and **business-rule violations around department head assignment**, make it **unsafe for production multi-department deployment today**.

**Recommendation:** Halt production launch. Fix P0 in immediate sprint. Run targeted UAT on ticketing + portals + dashboard only (disable Search). Re-audit Search/Filters module before enabling.

---

*Report generated from direct code inspection. Line references reflect repository state at audit time.*
