# 27 — Final Verification (post-Phase-9)

A real verification pass over the repo as it stands after Phases 0 → 9. The earlier "Phase 6: Verification" was a quick sanity check at the time of foundation; this is the audited, cross-checked version.

## File inventory

| Area | Files |
|---|---|
| Root configs | 9 |
| Docs | 32 |
| API source | 49 |
| Mobile (`app/` + `src/`) | 34 |
| Dashboard | 21 |
| Website | 13 |
| Shared packages (`packages/*/src`) | 33 |
| Tests | 1 (`apps/api/test/common/request-context.spec.ts`) |
| **Total non-PDF files** | **251** |

## Boundary checks (all green)

- `@navi/*` references: types (30), validators (5), config (2), ui (2), api-client (2). No app or package imports another app's source.
- No file under `packages/*` imports anything under `apps/*`. ✅
- No mobile file imports dashboard/website code, and vice versa. ✅
- Workspace dependency graph is acyclic:
  - `types` ← `validators`, `ui`, `config` (config has no deps), `api-client`
  - `api-client` ← mobile, dashboard
  - All `@navi/*` ← consumed by the four apps as appropriate.

## Permission audit (all green)

- 15 unique permission codes are used by API controllers via `@RequirePermissions(...)`.
- 53 permission codes exist in the seed catalogue.
- **0 controller-required permissions are missing from the seed.**
- The `*` wildcard permission is created and assigned only to `ADMIN` and `SUPER_ADMIN`.
- Partner roles (`HOTEL_PARTNER`, `ACTIVITY_PARTNER`, …) all carry `audit.read.assigned` and `membership.read.assigned`.

## Public-route audit (all green)

`@Public()` is declared exactly where it should be:

- `auth/*` (signup, login, refresh)
- `health`
- `destinations` (read-only public catalogue)
- `listings` (read-only public catalogue)
- `emergency` (UAE numbers — read-only)
- `i18n/content` (read-only public translations)
- `webhooks/:provider` (HMAC-verified)

All other routes require an authenticated context.

## Cross-partner isolation (current posture)

- `applyScope(where, 'own'|'assigned'|'all')` helper in `apps/api/src/common/scope/prisma-scope.ts` is the documented enforcement path, with unit-test coverage in `apps/api/test/common/request-context.spec.ts`.
- Partner-scoped read endpoints (`booking.read.assigned`, `listing.read.assigned`, etc.) are declared in roles but their listing endpoints don't exist yet — they ship with the partner dashboard in Phase 2. This is honest and intentional: there is no half-built path that returns unscoped data to a partner.
- Existing `findMany` calls are either: filtered by `userId` (own), gated to a `*.read.all` permission, or public read paths.

## Frontend boundary checks

- **No hardcoded AED literals** in any mobile screen (formerly in trip-planner result; now rendered via `formatMoney`).
- **No price math** in mobile screens. Tax, subtotal, totals are computed in the API.
- **No business rules** in mobile/dashboard/website components. UI only renders.
- Dashboard does not yet consume `@navi/api-client`; it's wired as a workspace dep, ready for the login → session-cookie slice.

## Middleware order (verified)

1. `RequestContextMiddleware` — sets `traceId`, `locale`, empty actor.
2. `JwtAuthMiddleware` — decodes Bearer token, resolves `userId/roles/permissions/memberships`, wraps the rest of the request in `RequestContextStore.run(...)`.
3. `IdempotencyMiddleware` — replays cached responses keyed by `Idempotency-Key`.
4. `RbacGuard` — checks `@Public()` and `@RequirePermissions(...)`.
5. `AuditInterceptor` — writes `AuditLog` for `@Audited(...)` handlers, with proper `before` (prior-state) / `after` (result + payload) semantics.

## Apps that boot today (conceptually)

- **API** — `pnpm --filter @navi/api dev` after `prisma generate && prisma migrate && prisma seed`. Health, auth, listings, destinations, emergency, bookings (own), refunds, webhooks, audit-logs are all served.
- **Dashboard** — `pnpm --filter @navi/dashboard dev` on :3001. Login → middleware redirect when no cookie. Sidebar permission-gated.
- **Website** — `pnpm --filter @navi/website dev` on :3000. `/en` and `/ar` routes render with RTL.
- **Mobile** — `pnpm --filter @navi/mobile start`. Splash → onboarding (with persisted flag) → login → tabs (auth-gated) → Emergency screen with confirm-call modal.

## Known mocks (intentional — flagged for the next slice)

- Mobile auth screens still bypass `/v1/auth/*` (UI stubs).
- Dashboard login does not call the API yet.
- Refresh-token rotation throws "not implemented."
- Idempotency cache is in-memory, not Redis-backed.
- Webhook events are written to `AuditLog`, not the dedicated `WebhookEvent` table.
- Provider implementations are mocks (payment, sms, ai, ocr, translation, email, storage).

## Risks worth re-stating

- `UserRole` PK includes nullable `businessId`; Postgres treats NULL ≠ NULL, so two role-grants with `businessId=null` for the same `(userId, roleId)` could co-exist. Mitigation: defensive upsert in any code that grants roles. Long-term: introduce a `__global__` sentinel.
- Local `pnpm install` cannot run inside the Cowork sandbox because the workspace mount blocks pnpm's rename-into-place. CI on GitHub Actions runs install fine. The first PR after a real local install must commit `pnpm-lock.yaml`.
- `JwtAuthMiddleware` currently re-resolves the user on every request. For high-volume routes we'll want a small cache (Redis or in-memory LRU) — schedule for Phase 2.

## Verdict

The foundation is internally consistent, boundaries are clean, the permission catalogue covers every controller, and the cross-cutting pieces (auth context, RBAC, audit, idempotency, webhooks, scope helper) are all in place. The remaining work is **integration** (wire screens to API), not **architecture rework**.
