---
stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8]
lastStep: 8
status: 'complete'
completedAt: '2026-03-11'
inputDocuments:
- '_bmad-output/planning-artifacts/product-brief-l-ami-fiduciaire-2026-03-10.md'
- '_bmad-output/planning-artifacts/prd.md'
- '_bmad-output/planning-artifacts/prd-validation-report.md'
- '_bmad-output/planning-artifacts/ux-design-specification.md'
- '_bmad-output/planning-artifacts/research/market-fiduciary-saas-morocco-research-2026-03-10.md'
- '_bmad-output/planning-artifacts/research/domain-moroccan-fiduciary-operations-research-2026-03-10.md'
- '_bmad-output/planning-artifacts/research/ecosystem-partners-morocco-fiduciary-research-2026-03-10.md'
- '_bmad-output/planning-artifacts/research/cloud-adoption-saas-trends-future-outlook-research-2026-03-11.md'
- '_bmad-output/planning-artifacts/research/domain-moroccan-tax-regulation-digital-compliance-research-2026-03-10.md'
- '_bmad-output/project-context.md'
- 'docs/index.md'
- 'docs/project-overview.md'
- 'docs/architecture.md'
- 'docs/development-guide.md'
- 'docs/source-tree-analysis.md'
workflowType: 'architecture'
project_name: "l'ami fiduciaire"
user_name: 'Saad'
date: '2026-03-11'
---
# Architecture Decision Document
_This document builds collaboratively through step-by-step discovery. Sections are appended as we work through each architectural decision together._
## Project Context Analysis
### Requirements Overview
**Functional Requirements (58 FRs across 10 groups):**
The 58 FRs decompose into three architectural tiers:
| Tier | FR Groups | Architectural Impact |
|---|---|---|
| **Foundation** | Workspace & Onboarding (FR1-FR6), Team & Role Management (FR7-FR11), Client Management (FR12-FR15) | Multi-tenant data model, RBAC engine, session-based workspace resolution |
| **Core Domain** | Declaration Lifecycle (FR16-FR23), Dashboard & Visibility (FR24-FR28), Collaboration & Notifications (FR29-FR33), Client Portal & Document Exchange (FR34-FR40) | Declaration state machine, role-scoped queries, notification service, token-based portal |
| **Power Features** | Search/Filtering/Navigation (FR41-FR44), Archive System (FR45-FR51), Activity & Audit (FR52-FR55), Platform Administration (FR56-FR58) | Full-text search, archive storage strategy, audit trail, admin dashboard |
The Foundation tier is partially built (multi-tenant workspaces, client CRUD, basic folder system). Core Domain and Power Features are the MVP build scope.
**Non-Functional Requirements (28 NFRs across 4 categories):**
| Category | Key NFRs | Architectural Driver |
|---|---|---|
| **Performance** (NFR1-6) | 2s page loads, 10s bulk operations (50 items), 1s search, 3s dashboard render for 200 clients | Server-side rendering optimization, query performance, pagination strategy |
| **Security** (NFR7-15) | TLS 1.2+, AES-256 at rest, tenant isolation at every query, token security, 2FA, CNDP compliance, EU hosting | Encryption layer, middleware architecture, hosting infrastructure |
| **Scalability** (NFR16-20) | 200 concurrent workspaces, 1,000 users, 500 clients/workspace, 5,000 declarations/workspace, 1TB storage, 2-3x peak handling | Database indexing, query optimization, storage architecture, caching |
| **Reliability** (NFR21-28) | 99.5% uptime, 1hr RPO, 1hr RTO, >99% email delivery, zero data loss tolerance | Backup strategy, email service selection, monitoring, queue reliability |
**UX-Driven Architectural Requirements:**
- Role-driven sidebar and dashboard content (4 internal roles + client portal = 5 distinct view contexts)
- Inline table actions without page navigation (requires component-level interaction patterns)
- Filter persistence across views (session/URL state management)
- Bulk operations with real-time feedback (progress indication for 50+ item operations)
- Summary cards that filter tables below (client-side or server-side filter linkage)
- Server-side pagination as Inertia.js constraint (no infinite scroll)
- Mobile-first client portal alongside desktop-first internal app
### Scale & Complexity
- **Primary domain:** Full-stack web (Laravel 12 + Vue 3 monolith via Inertia.js 2)
- **Complexity level:** High
- **Estimated architectural components:** ~15-20 major components (auth, RBAC, tenant isolation, declaration engine, notification service, document storage, client portal, dashboard aggregation, archive system, search/filter, audit logging, admin panel, email delivery, file preview, bulk operations)
### Technical Constraints & Dependencies
**Brownfield Constraints (non-negotiable):**
| Constraint | Impact |
|---|---|
| Laravel 12 + PHP 8.2+ | Backend framework, ORM, routing, middleware patterns locked in |
| Vue 3 + TypeScript strict | Frontend framework, component patterns, type system |
| Inertia.js 2.0 | No REST API layer -- controllers render pages directly. Server-driven SPA. All URLs from PHP controllers. |
| shadcn-vue (reka-ui) + Tailwind CSS 4 | UI component library, styling approach. Cannot modify `components/ui/*` |
| MySQL 8.4 | Database engine. Session-based tenant resolution via `current_workspace_id` |
| Spatie Media Library | File upload/download infrastructure |
| Spatie Activity Log | Audit trail infrastructure |
| Laravel Fortify | Authentication + 2FA |
| bensampo/laravel-enum | Enum pattern for business domain values |
| Laravel Wayfinder | Type-safe frontend route generation |
| Pest 4 | Testing framework (PHP-only, no frontend JS tests) |
| Docker/Sail + GitHub Actions | Dev environment + CI/CD |
**Regulatory Constraints:**
| Regulation | Requirement |
|---|---|
| CNDP (Law 09-08) | EU-hosted infrastructure, data retention policies, user consent mechanisms |
| AML (Law 43-05) | Audit trails supporting firms' compliance obligations |
| Tax compliance context | Declaration types aligned with Moroccan fiscal calendar |
| 10-year retention | Archived data must be preserved for legal minimum |
**Infrastructure Constraints:**
| Constraint | Implication |
|---|---|
| Moroccan internet (ADSL/4G) | Page loads must work on slower connections -- server-side rendering, optimized asset delivery |
| Fiscal deadline peaks (15th-20th monthly, Jan-Mar annually) | System must handle 2-3x normal load without degradation |
| Email delivery during peaks | Mission-critical -- failed email = missed filing = financial penalty for client |
### Cross-Cutting Concerns Identified
1. **Tenant Isolation** -- Every database query, every file access, every API response must be scoped to the current workspace. This is not just a middleware concern -- it's a per-model, per-controller, per-query concern. Existing pattern: session-based `current_workspace_id` with `abort(404)` for violations.
2. **Role-Based Authorization** -- Three-tier access (Owner sees all, Manager sees all with configurable actions, Worker sees only assigned items). Affects every controller method, every Eloquent query scope, every Vue component's visible content. Current pattern: custom `authorizeXxx()` methods (no Gates/Policies).
3. **Audit Trail** -- Every data modification logged with actor, timestamp, and change details (FR52-NFR12). Already implemented via Spatie Activity Log trait. Must extend to all new models.
4. **Email Notification Reliability** -- 5 existing email types, more to come (nudge notifications, bulk scheduling). >99% delivery rate required. Queue-based sending with retry logic needed.
5. **File Security** -- Client financial documents (CIN numbers, bank statements, tax documents) require encryption at rest. Access limited to authorized workspace members and specific client via token link.
6. **Search & Filtering** -- Consistent filter bar across Clients, Declarations, Archive views with session persistence. Quick search across views. Affects frontend component architecture and backend query building.
7. **Bulk Operations** -- Creating/notifying/updating 50+ items at once. Must be performant (10s target), provide feedback, and handle partial failures gracefully. Queue-based processing likely needed.
## Starter Template Evaluation
### Primary Technology Domain
Full-stack web monolith (Laravel 12 + Vue 3 via Inertia.js 2.0) -- **brownfield project with established stack**.
### Starter Assessment: Brownfield Context
This project does not require a new starter template. The foundation is an active codebase with:
- **Backend:** Laravel 12, PHP 8.2+ (8.4 runtime), Eloquent ORM, 7 models, 16 migrations
- **Frontend:** Vue 3.5, TypeScript 5.2 strict, 31 page components, shadcn-vue design system (20+ component groups)
- **Bridge:** Inertia.js 2.0 (server-driven SPA -- no REST API layer)
- **Styling:** Tailwind CSS 4.1 with CVA, `cn()` helper
- **Auth:** Laravel Fortify (login, registration, password reset, email verification, 2FA/TOTP)
- **Files:** Spatie Media Library 11 (upload/download)
- **Logging:** Spatie Activity Log 4 (audit trail)
- **Enums:** bensampo/laravel-enum 6 (9 business enums)
- **Routes:** Laravel Wayfinder 0.1.9 (type-safe frontend routes)
- **Icons:** Lucide (lucide-vue-next)
- **Testing:** Pest 4 (PHP-only), 13 feature test files
- **Code Quality:** Laravel Pint, ESLint 9, Prettier 3 (with Tailwind plugin)
- **Build:** Vite 7, SSR enabled
- **Dev Environment:** Docker/Sail (MySQL 8.4, Mailpit, Soketi)
- **CI/CD:** GitHub Actions (lint + test workflows)
### Architectural Decisions Already Established
**Language & Runtime:**
- PHP 8.2+ with strict typing conventions, explicit `$fillable`, Form Request classes, PHPDoc generics on relationships
- TypeScript 5.2 strict mode, `isolatedModules: true`, `import type` for type-only imports, `@/` path alias
**Styling Solution:**
- Tailwind CSS 4.1 via `@tailwindcss/vite` plugin, shadcn-vue components (headless reka-ui primitives), CVA for variants, `cn()` utility
**Build Tooling:**
- Vite 7 with `laravel-vite-plugin` 2.0, SSR support enabled, concurrent dev server (PHP + Queue + Logs + Vite)
**Testing Framework:**
- Pest 4 with `RefreshDatabase` auto-applied, `test()` closures, `expect()` chaining, `route()` helper in tests
**Code Organization:**
- Pages: `resources/js/pages/{domain}/{Action}.vue`
- Components: `resources/js/components/` (app) + `components/ui/` (shadcn-vue, never modify)
- Types: `resources/js/types/` with barrel `index.ts`
- Composables: `resources/js/composables/`
- Controllers: `app/Http/Controllers/`
- Requests: `app/Http/Requests/`
- Models: `app/Models/`
- Enums: `app/Enums/`
- Mail: `app/Mail/`
**Development Experience:**
- `composer dev` runs all services concurrently (server + queue + logs + Vite HMR)
- `composer test` runs Pint lint check then Pest tests
- `npm run lint` + `npm run format` for JS/Vue code quality
- Docker/Sail for consistent local environment
### Stack Gaps for MVP (Requiring Architectural Decisions)
| Gap | Options to Evaluate | Decision Step |
|---|---|---|
| Permission toggle system | JSON column on pivot vs. permissions table | Step 4 |
| Search implementation | MySQL FULLTEXT vs. Laravel Scout + Meilisearch | Step 4 |
| In-app notifications | `DatabaseNotification` vs. custom notifications table | Step 4 |
| Bulk operation processing | Synchronous vs. queued jobs with progress broadcasting | Step 4 |
| Archive strategy | Soft state on declarations vs. separate archive table | Step 4 |
| Filter persistence | URL query params vs. session storage | Step 4 |
| Document preview | Client-side PDF.js vs. server-generated previews | Step 4 |
| Real-time features | Soketi (configured, not wired) -- scope of use for MVP | Step 4 |
**Note:** No new starter initialization needed. First implementation story should be the "Folders to Declarations" terminology migration (Pre-Phase from PRD).
## Core Architectural Decisions
### Decision Priority Analysis
**Critical Decisions (Block Implementation):**
- Permission toggle storage (JSON on pivot) -- blocks Phase 1 RBAC
- Archive strategy (status-based `archived_at`) -- blocks Phase 5 archive system
- Queue driver (Redis) -- blocks reliable email delivery and bulk operations
- Email service provider (Amazon SES EU) -- blocks notification reliability
**Important Decisions (Shape Architecture):**
- Search implementation (MySQL FULLTEXT) -- shapes Phase 4 search/filter
- In-app notifications (Laravel DatabaseNotification) -- shapes Phase 3 nudge system
- Filter persistence (URL query params) -- shapes Phase 4 filtering UX
- Caching strategy (Redis) -- shapes dashboard performance
- File storage (S3-compatible) -- shapes document management scalability
- Hosting provider (EU VPS + Laravel Forge) -- shapes deployment pipeline
**Deferred Decisions (Post-MVP):**
- Real-time WebSocket features (Soketi) -- not needed for MVP launch
- Full observability stack (Grafana/Prometheus) -- Laravel Pulse + Sentry sufficient for MVP
- Meilisearch integration -- MySQL FULLTEXT sufficient at initial scale
- Progress broadcasting for bulk operations -- synchronous sufficient for MVP batch sizes
### Data Architecture
**D1: Permission Toggle Storage**
- **Decision:** JSON column (`permissions`) on the `workspace_user` pivot table
- **Rationale:** 3 fixed roles with ~12 toggle-able permissions is a small, well-bounded set. JSON avoids extra JOINs on every permission check. Permissions are read frequently, written rarely (only when Owner configures Manager permissions).
- **Implementation:** Add `permissions` JSON column to `workspace_user` migration. Default permissions per role defined in a config file or enum. `WorkspaceUser` model casts `permissions` to array.
- **Affects:** FR3, FR4, FR7-FR11 (all role/permission features)
**D2: Archive Strategy**
- **Decision:** Status-based archiving with `archived_at` timestamp column on declarations table
- **Rationale:** Declarations stay in the same table, filtered out of active views via `whereNull('archived_at')` scope. Re-open is trivial (`archived_at = null`). At 5,000 declarations per workspace, a single indexed table performs well. 10-year retention is simply not deleting rows.
- **Implementation:** Add `archived_at` nullable timestamp to declarations migration. Add `scopeActive()` and `scopeArchived()` Eloquent scopes. Auto-set `archived_at` when status becomes "Closed."
- **Affects:** FR21-FR23, FR45-FR51 (entire archive system)
**D3: Search Implementation**
- **Decision:** MySQL FULLTEXT indexes for quick search across clients and declarations
- **Rationale:** No additional service dependency. At 200 workspaces with 500 clients / 5,000 declarations each, MySQL FULLTEXT delivers sub-second results. Workspace-scoped queries limit the search space further. Can migrate to Meilisearch post-MVP if search demands grow.
- **Implementation:** Add FULLTEXT indexes on searchable columns (client name, declaration type/notes, etc.). Use `MATCH...AGAINST` in natural language mode. Laravel Scout can be added later as an abstraction layer without changing the search API.
- **Affects:** FR43, NFR6 (quick search, 1-second response)
**D4: Caching Strategy**
- **Decision:** Redis for application caching
- **Rationale:** Triple-duty Redis (cache + queue + sessions) is the standard Laravel production pattern. Dashboard aggregation queries (counts by status, overdue items) benefit from short-lived cache. Redis is already supported by Laravel Sail.
- **Implementation:** Add Redis service to Docker Compose. Set `CACHE_DRIVER=redis`, `QUEUE_CONNECTION=redis`, `SESSION_DRIVER=redis` in environment. Use `Cache::remember()` for dashboard aggregations with 5-minute TTL. Invalidate on status changes.
- **Affects:** NFR1, NFR5, NFR16-20 (performance, scalability)
### Authentication & Security
**D5: Queue Driver**
- **Decision:** Redis queue driver
- **Rationale:** Already adding Redis for cache/sessions. Redis queues are fast, support delayed jobs, retries (up to 3 within 5 minutes per NFR26), and job batching. Native Laravel support.
- **Implementation:** Set `QUEUE_CONNECTION=redis`. Email mailables dispatched via `dispatch()` or `Mail::queue()`. Failed jobs table for monitoring. `queue:work` process managed by Supervisor in production.
- **Affects:** NFR26 (email delivery reliability), FR32-FR33 (notification scheduling)
**D6: File Encryption at Rest**
- **Decision:** Storage-level encryption (provider-managed AES-256 SSE)
- **Rationale:** Meets NFR7 (AES-256 at rest) transparently without breaking Spatie Media Library or adding application-level encryption complexity. Encryption/decryption is handled by the storage provider with zero performance overhead for the application.
- **Implementation:** Enable SSE (Server-Side Encryption) on the S3-compatible bucket. All objects encrypted automatically. No application code changes needed.
- **Affects:** NFR7 (encryption at rest), NFR14 (document access control)
**D7: Session Storage**
- **Decision:** Redis sessions
- **Rationale:** Consistent with Redis for cache and queue (triple-duty). Fast session reads, supports horizontal scaling if needed later. Session-based workspace resolution (`current_workspace_id`) benefits from Redis speed.
- **Implementation:** Set `SESSION_DRIVER=redis`. No application code changes -- Laravel handles session management transparently.
- **Affects:** Multi-tenant workspace resolution (session-based `current_workspace_id`)
### Communication Patterns
**D8: In-App Notification System**
- **Decision:** Laravel's built-in `DatabaseNotification` system
- **Rationale:** Battle-tested, supports database + mail channels, read/unread tracking built-in, `Notifiable` trait already on User model via Fortify. Workspace scoping added via query constraints when fetching notifications.
- **Implementation:** Run `php artisan notifications:table` migration. Create notification classes (e.g., `NudgeNotification`, `DeclarationOverdueNotification`). Query notifications with workspace scope via the related declaration/client. Mark as read via `markAsRead()`.
- **Affects:** FR29-FR31 (nudge system, notification center)
**D9: Bulk Operation Processing**
- **Decision:** Synchronous processing for bulk creation/updates; individual email sends queued via Redis
- **Rationale:** 50 DB inserts complete in <1 second synchronously. Email notifications for those 50 items are dispatched to Redis queue individually, processing asynchronously. This meets the 10-second NFR target without the complexity of progress broadcasting. Controller returns immediately after DB operations; emails process in background.
- **Implementation:** Bulk creation: `Declaration::insert($records)` or loop with `create()` for model events. Each notification dispatched to queue: `Notification::send($users, new BulkNotification())`. Failed individual sends retry automatically via Redis queue.
- **Affects:** FR17, FR32 (bulk declaration creation, bulk notification scheduling), NFR2-3 (10-second target)
**D10: Real-Time Features (Soketi)**
- **Decision:** Deferred for MVP -- no WebSocket wiring
- **Rationale:** Inertia.js pages refresh data on every navigation, which is sufficient for MVP. The nudge notification center loads on page visit. Soketi remains configured in Docker for post-MVP activation. First post-MVP use case: real-time notification badge updates.
- **Implementation:** No implementation needed for MVP. Soketi stays in Docker Compose for future use.
- **Affects:** Post-MVP enhancement path
### Frontend Architecture
**D11: Filter Persistence**
- **Decision:** URL query parameters
- **Rationale:** Native Inertia.js pattern -- `router.get(url, { status: 'en_cours', assignee: 'me' })`. Filters survive page refresh, browser back/forward, and are shareable/bookmarkable. No server-side session management needed. Consistent with how Inertia handles pagination.
- **Implementation:** Filter components emit changes as URL params via `router.get()`. Controllers read filters from `Request` and apply Eloquent scopes. Default filters (e.g., Worker's "assignee = me") applied when no params present.
- **Affects:** FR41-FR44 (filtering, persistence, search)
**D12: Document Preview**
- **Decision:** Client-side viewers (PDF.js for PDFs, native `
` for images)
- **Rationale:** No server processing overhead. PDF.js is mature and handles most PDF files. Images render natively. A shadcn-vue Dialog component wraps the viewer for a modal experience. File is fetched via existing Spatie Media download route.
- **Implementation:** Vue component with `