# RC001 — Production Readiness Audit Report
## INVEETAIRE Platform — Pre-Client Onboarding Assessment

**Audit Date:** 2026-07-21  
**Auditor Persona:** Product Owner · Senior QA Lead · Solution Architect · UX Reviewer · Security Reviewer  
**Scope:** Sprint 0 → DX003 (all completed sprints)  
**Directive:** Strict. Objective. No flattery. Production-ready or not?

---

## AUDIT SCOPE

| Portal | Status Audited |
|---|---|
| Business Administration (BA) — Super Admin | ✓ |
| Couple Portal (Workspace) | ✓ |
| Crew Portal (CX002) | ✓ |
| Public Invitation + Interaction | ✓ |
| Authentication + Session | ✓ |
| Middleware Stack | ✓ |
| Developer Console (DX001–DX003) | ✓ |
| Core Framework | ✓ |
| Infrastructure & Config | ✓ |

---

## RATING SCALE

| Rating | Label | Meaning |
|---|---|---|
| ⭐⭐⭐⭐⭐ | Excellent | Production-ready. No action needed. |
| ⭐⭐⭐⭐ | Good | Minor polish only. Shippable. |
| ⭐⭐⭐ | Acceptable | Known gaps. Manageable for first client. |
| ⭐⭐ | Weak | Functional but has real risk. Needs sprint. |
| ⭐ | Failing | Blocking issue. Do not ship. |

---

## SEVERITY LEGEND

| Tag | Severity | SLA |
|---|---|---|
| 🔴 CRITICAL | Security breach or data loss risk | Block release |
| 🟠 HIGH | Broken user flow or functional failure | Fix before client |
| 🟡 MEDIUM | Degraded experience or operational gap | Fix in RC sprint |
| 🟢 LOW | Polish, UX, documentation | Backlog acceptable |

---

## MODULE AUDIT RESULTS

---

### MODULE 1 — Authentication & Session Management

**Rating: ⭐⭐⭐⭐⭐ Excellent**

**What was audited:**
- `AuthService.php` (1,134 lines) — login, logout, password reset, IP lockout
- `AuthMiddleware.php` — inactivity + absolute timeout enforcement
- `Session.php` — session lifecycle and security settings
- `Csrf.php` — token generation and validation
- `CsrfMiddleware.php` — per-route CSRF enforcement

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| AUTH-01 | 🟢 LOW | Session cookie `secure` flag is `false` on local dev — correct by config, but `.env.example` should warn operators to set `APP_ENV=production` and verify `HTTPS` before first client deployment. |
| AUTH-02 | 🟢 LOW | `AuthMiddleware` expired session redirect always sends Crew users to `/couple` login portal (line 104, 125: `$portal === 'admin' ? '/admin' : '/couple'`). Crew users get the Couple login screen after session expiry. Confusing but not breaking. |

**Strengths:**
- bcrypt password hashing with `password_verify` — correct
- IP-based lockout (5 attempts → 15 min) — implemented and logged
- Session regeneration on login (anti-fixation) — implemented
- CSRF on all POST routes — consistent
- `hash_equals()` used for token comparison — timing-safe
- Inactivity (2h) + absolute timeout (role-based 8h/12h/24h) — implemented
- GET /logout not registered — correct
- Stack traces never reach browser when `APP_DEBUG=false` — correct

**Verdict:** This is the strongest module in the entire platform. Ship as-is.

---

### MODULE 2 — Business Administration (Super Admin Portal)

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `AdminDashboardController.php` — dashboard, search, workspace adoption
- `AdminCrewController.php` — couple/crew management, assignments
- `WorkspaceController.php` — 4-step provisioning wizard
- `RoleMiddleware.php` — path-to-role enforcement
- Admin routes — `/app/admin/*`

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| BA-01 | 🟡 MEDIUM | **Flash message inconsistency.** `AdminCrewController` uses raw `Session::set('flash_success', ...)` and `Session::set('flash_danger', ...)` directly, while the established platform pattern is `$this->flash('success', ...)` via `BaseController`. This works, but it is a deviation. The admin dashboard view reads `flash_success` but the BaseController flash method may store under a different key. Needs verification to prevent silent flash message loss. |
| BA-02 | 🟡 MEDIUM | **`openWorkspace()` has no audit log.** When a Super Admin adopts a workspace (viewing mode), there is no log entry. This is an operational visibility gap — no way to know which admin viewed which client workspace, or when. |
| BA-03 | 🟡 MEDIUM | **Commercial Plans and Themes pages are placeholders.** `/app/admin/plans` and `/app/admin/themes` render placeholder views. This is expected but should be documented as "Future Version" in the admin UI itself, not just treated as stubs. |
| BA-04 | 🟢 LOW | **No workspace edit capability.** Once a workspace is provisioned, there is no route or controller to edit workspace details (couple names, wedding date, slug, plan). Admin must use a developer tool or database directly. This limits operational agility. |
| BA-05 | 🟢 LOW | **Admin search executes N+7 queries.** The status count loop in `AdminDashboardController::index()` runs 7 separate `SELECT COUNT(*)` queries for workspace status. Should be a single `GROUP BY` query. Not a blocking issue at low scale. |

**Strengths:**
- Role guard (`RoleMiddleware`) correctly enforces `super_admin` on all `/app/admin/*` paths
- Workspace provisioning is a proper 4-step wizard with session-persisted state
- Crew assignment uses transaction (`beginTransaction/commit/rollBack`) — correct
- `WorkspaceMiddleware` validates Super Admin workspace adoption via DB check (support sessions table)
- CSRF on all state-changing admin POST routes — consistent

**Verdict:** Functional. BA-01 (flash inconsistency) needs verification before client. BA-02 (audit log for workspace adoption) is a meaningful operational gap.

---

### MODULE 3 — Couple Portal (Wedding Workspace)

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `CoupleDashboardController.php` (1,184 lines, 59 KB) — largest single file in the platform
- `InvitationController.php` — invitation builder, preview, design
- `InvitationModel.php` — parameterized queries, data retrieval
- `GuestController.php` — guest CRUD, import, download
- All couple routes (`/app/couple/*`)

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| CP-01 | 🟠 HIGH | **`CoupleDashboardController` is a god object.** At 1,184 lines and 59 KB it handles dashboard summary, insights, report generation, and report completion — all in one controller. This violates the thin controller principle documented in the project's own architecture. Dashboard queries bypass the service layer and hit the DB directly inline. Not a blocker for first client, but creates real maintenance risk. |
| CP-02 | 🟡 MEDIUM | **`InvitationModel.php` uses Windows CRLF line endings (`\r\n`)** while all other files use LF. This inconsistency may cause issues in diff tools and future CI pipelines, and suggests the file was edited on Windows without a `.gitattributes` normalization rule. |
| CP-03 | 🟡 MEDIUM | **Invitation `preview` action switches layout to `public` mid-controller.** This is a legitimate technique but architecturally brittle — the layout override happens without resetting on errors, creating inconsistent rendering if an exception is caught. The catch block does switch back (`$this->layout = 'couple'`), which is good, but this pattern should be documented as a known design deviation. |
| CP-04 | 🟡 MEDIUM | **`GuestController` does not paginate the import preview.** The import preview shows all validated rows at once. For large guest lists (500+ rows), this will produce a very slow page load and potentially timeout. |
| CP-05 | 🟢 LOW | **Default budget fallback is hardcoded in Rupiah.** `CoupleDashboardController` line 80 sets `$totalBudget = 150000000.00` (Rp 150 million) as the fallback. If a workspace doesn't have budget settings, the dashboard shows IDR-specific amounts regardless of the `budget_settings.currency` field. |

**Strengths:**
- All guest CRUD uses `CsrfMiddleware` on POST routes — consistent
- `GuestController` dynamically sets layout based on `role_snapshot` (couples vs crew sharing the module) — elegant
- `WorkspaceMiddleware` enforces tenant isolation for all couple routes
- Import workflow has a 2-phase pattern (preview → confirm) — correct for data safety
- Guest template download provided — good operational support
- Invitation validator exists as a separate class — correct pattern

**Verdict:** Functional and shippable. CP-01 is the most significant long-term concern. Not a release blocker for the first client, but flag it for immediate post-RC001 refactoring.

---

### MODULE 4 — Guest Engagement (RSVP Center, Wishes, WhatsApp Queue)

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `EngagementController.php` — RSVP center, wishes, WhatsApp queue management
- `engagement/routes.php` — route structure
- `views/engagement/` — 4 view files

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| ENG-01 | 🟡 MEDIUM | **WhatsApp delivery is link-only (no real API integration).** The `MessageDeliveryService` generates `wa.me` deep links. This means "sending" an invitation is manually clicking a WhatsApp link per guest — there is no batch sending or confirmed delivery. This is a workflow and expectations management issue for the client: the platform does not actually send messages. The UI should make this explicitly clear (it appears to — "WhatsApp Queue" implies manual send — but should be verified during user testing). |
| ENG-02 | 🟢 LOW | **`markSent` and `resetQueue` are destructive with no undo.** Marking the entire queue as "reset" clears all sent statuses permanently. No confirmation dialog requirement is visible in the routes — verify the views have one. |

**Strengths:**
- CSRF on all state-changing POST routes
- Queue exists to let couples track which guests have been "contacted"
- Wishes collection from guest portal is functional end-to-end

**Verdict:** Solid. ENG-01 is a client expectations concern, not a technical bug. Verify the UI copy is clear.

---

### MODULE 5 — Wedding Operations (Scanner, Check-in, Crew Operations)

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `OperationController.php` — scanner, guest lookup, check-in, reject
- `CrewHomeController.php` — crew dashboard, wedding list, workspace selector
- `AttendanceService.php`, `DashboardService.php`, `OperationService.php`
- `operation/routes.php` — event-specific + legacy routes

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| OPS-01 | 🟠 HIGH | **Cookie set in `selectWorkspace()` is not `HttpOnly` or `Secure`.** Line 200: `setcookie($cookieName, ...)` uses only `path='/'`. For a persistent cookie that stores workspace context, this should be `httponly=true` and `secure=true` in production. A client-side script could read `last_selected_workspace_id_{userId}`. Not catastrophic but violates the platform's own security principles. |
| OPS-02 | 🟡 MEDIUM | **Legacy scanner routes still exist alongside event-specific routes.** `/app/crew/operation/scanner` (no event ID) and `/app/couple/operation/scanner` remain registered alongside the canonical `/operation/event/{event_id}/checkin`. The legacy routes redirect to the first available event, but having two route patterns creates confusion and testing surface. |
| OPS-03 | 🟡 MEDIUM | **Crew `selectWorkspace()` redirects to `/app/crew/operation/scanner`** (the legacy route), not the canonical event-specific scanner URL. This works due to redirect logic, but it is fragile — if the redirect logic changes, the crew lands on a broken URL. |
| OPS-04 | 🟢 LOW | **`CrewHomeController::weddingsList()` reads `$_GET['page']` directly** (line 107) instead of using `$request->get('page')` — inconsistent with the Request abstraction used everywhere else in the platform. |

**Strengths:**
- Workspace assignment verification in `selectWorkspace()` — crew user must be in `crew_accounts` for the target workspace before adoption. Correct.
- Check-in and reject both use CSRF protection
- Event-specific routes correctly scope operations to a specific wedding event
- `ensurePilotEventsExist()` auto-provisions events — prevents broken state for new workspaces

**Verdict:** Functional. OPS-01 (insecure cookie) is a security finding that should be fixed before production deployment.

---

### MODULE 6 — Crew Portal (CX002)

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `routes/crew.php` — dual route group architecture
- Layout reuse (`crew` layout dynamic selection in `GuestController` and `OperationController`)
- Crew home views (`crew_home.php`, `dashboard.php`, `weddings_list.php`)

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| CX-01 | 🟡 MEDIUM | **No sidebar access control for shared modules.** When a crew user accesses `/app/crew/guests`, the `GuestController` dynamically sets the `crew` layout — good. But the crew layout sidebar should only show modules permitted for crew. If the crew layout renders couple-only navigation items (Budget, Vendor, Timeline) via the same template, those links would be visible even if the routes behind them return 403. Need to verify `app/views/layouts/crew.php` filters the sidebar for crew role. |
| CX-02 | 🟢 LOW | **Crew settings uses Couple's `SettingsController`** — this is intentional reuse and architecturally sound, but the settings page title may say "Couple Settings" or render a couple-branded layout context. Verify the view renders correctly for the crew layout. |

**Strengths:**
- Correct dual route group pattern: first group (no WorkspaceMiddleware) for hub pages, second group (with WorkspaceMiddleware) for workspace-scoped operations
- Module reuse (Guest, Operation, Engagement) avoids code duplication — correct architectural decision
- Layout switching based on `role_snapshot` — clean

**Verdict:** Architecture is correct. CX-01 needs verification of crew sidebar content.

---

### MODULE 7 — Public Invitation + Guest Interaction (RSVP/Wish)

**Rating: ⭐⭐⭐⭐⭐ Excellent**

**What was audited:**
- `PublicInvitationController.php` — token resolution and rendering
- `PublicInvitationService.php`, `PublicInvitationModel.php`
- `InteractionController.php` — RSVP and wish submission
- `InteractionValidator.php`
- Theme engine: `ThemeService`, `ThemeLoader`, `ThemeRenderer`

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| PI-01 | 🟢 LOW | **Invitation title is hardcoded.** `PublicInvitationController::show()` sets `'title' => 'Wedding Invitation'` for all guests. Should be personalized: `"{$workspace['groom_name']} & {$workspace['bride_name']} Wedding Invitation"` for better browser tab UX and SEO. |
| PI-02 | 🟢 LOW | **No rate limiting on RSVP/Wish POST endpoints.** A malicious bot could flood `/i/{slug}/{token}/rsvp` with repeated submissions. The token is guest-specific, which limits blast radius, but there is no submission count guard per token. |

**Strengths:**
- Controller is intentionally thin — no SQL, delegates entirely to service layer
- 404 returned for invalid slug/token — no information leakage about valid workspaces
- CSRF on RSVP and Wish POST routes (even though the guest is unauthenticated) — correct
- Theme engine fully decoupled from controller — excellent separation
- Token is resolved from DB, not session — stateless, cache-friendly

**Verdict:** Best-designed module in the Couple-facing stack. Ship as-is.

---

### MODULE 8 — Messaging (WhatsApp Template + Preview)

**Rating: ⭐⭐⭐ Acceptable**

**What was audited:**
- `MessageController.php`, `MessageModel.php`
- `MessageTemplateService.php`, `MessageDeliveryService.php`
- Routes: template, save, preview

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| MSG-01 | 🟡 MEDIUM | **No message delivery confirmation.** The system generates WhatsApp deep links and marks guests as "sent" manually. There is no actual delivery confirmation. If a couple marks a guest as "sent" by mistake, there is no undo from the WhatsApp queue. (See also ENG-02.) |
| MSG-02 | 🟢 LOW | **Template preview route (`/messaging/preview`) is GET only** — no pagination shown for large guest lists in preview mode. At 500+ guests this will be a slow page. |

**Strengths:**
- `MessageDeliveryService` is correctly isolated — no HTML, no DB, pure logic
- Phone number normalization (Indonesian `08` → `62`) is implemented
- Template engine uses simple variable substitution — appropriate for V1
- CSRF on template save

**Verdict:** Functional for V1. Clients need to understand the WhatsApp flow is manual. Document it in onboarding materials.

---

### MODULE 9 — Budget Planner

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `BudgetController.php` (21 KB) — all budget CRUD
- `BudgetModel.php` (33 KB) — largest model in the platform
- Routes: 15 endpoints covering categories, expenses, payments, settings

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| BDG-01 | 🟡 MEDIUM | **`BudgetModel.php` at 33 KB is the largest model.** While this is acceptable given the domain complexity (categories, expenses, payments, settings), it should be monitored. No split is needed now, but any additional budget features risk making it unmanageable. |
| BDG-02 | 🟢 LOW | **No export of budget data to Excel/PDF.** Couples commonly want to export their budget summary. This is missing. Mark as "Future Version." |

**Strengths:**
- Complete CRUD with categories, expenses, and payment tracking
- Category reordering implemented
- CSRF on all POST routes
- Budget settings (total budget, currency) are workspace-scoped

**Verdict:** Feature-complete for V1. No blockers.

---

### MODULE 10 — Timeline Planner

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `TimelineController.php` (15 KB)
- `TimelineModel.php` (29 KB)
- Routes: tasks, milestones, calendar, toggle status

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| TL-01 | 🟢 LOW | **No bulk task completion.** Individual toggle-status per task requires many clicks for couples managing large task lists. Not a blocker, but a UX gap. |
| TL-02 | 🟢 LOW | **Calendar view has no week or day view** — only monthly. Adequate for V1. |

**Strengths:**
- Milestones and tasks are separate — good domain modeling
- Toggle status uses POST + CSRF — correct
- Calendar view exists and is workspace-scoped

**Verdict:** Solid V1 feature. No blockers.

---

### MODULE 11 — Vendor Manager

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `VendorController.php` (21 KB)
- `VendorModel.php` (25 KB)
- Routes: 17 endpoints covering vendors, categories, quotations, communications, expense linking

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| VND-01 | 🟢 LOW | **No vendor document upload.** Couples commonly attach contracts or photos to vendors. Missing in V1. Mark as "Future Version." |
| VND-02 | 🟢 LOW | **Category reorder uses POST without sorting feedback.** After reorder, the view should flash a confirmation. Verify the view handles this. |

**Strengths:**
- Detailed vendor workspace (detail view) with quotations and communication logs
- Budget integration via `link-expense` endpoint
- CSRF on all state-changing operations
- Category-based organization

**Verdict:** Comprehensive for V1. No blockers.

---

### MODULE 12 — Core Framework (Router, ErrorHandler, Logger, BaseModel, BaseController)

**Rating: ⭐⭐⭐⭐⭐ Excellent**

**What was audited:**
- `ErrorHandler.php` — three-handler registration (error, exception, shutdown)
- `Session.php` — lifecycle, security, timeout helpers
- `BaseModel.php` — PDO abstraction, parameterized queries
- `Router.php`, `MiddlewarePipeline.php`
- `Logger.php`

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| CORE-01 | 🟢 LOW | **`escape_html()` is available but discipline of its use in views is not enforced at the framework level.** There is no automated check to catch a developer forgetting to call `escape_html()` on an echoed variable. This is a human discipline issue, not a framework bug. A future CI lint rule would help. |

**Strengths:**
- All three PHP error handlers registered — fatal errors caught
- Environment-aware error display (debug vs. production)
- `BaseModel` enforces parameterized queries as architectural convention
- `MiddlewarePipeline` is correctly frozen (zero-arg middleware) and documented
- `Logger` is the sole log writer — centralized, consistent

**Verdict:** The framework foundation is solid and well-architected. Ship with confidence.

---

### MODULE 13 — Infrastructure & Security Configuration

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `.htaccess` — directory access control, rewrite rules
- `.env.example` — all configuration keys documented
- `storage/` directory structure
- Error views (403, 404, 500, expired, maintenance)
- `app/middleware/WorkspaceMiddleware.php`

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| INF-01 | 🟠 HIGH | **`.htaccess` pattern `RewriteCond %{REQUEST_URI} !^.*/app/(admin|couple|crew|support)(/.*)?$` may allow direct access to PHP files under `app/` if the virtual route prefix matches.** For example, a request to `/inveetaire/app/couple/../core/Database.php` could potentially bypass the rewrite block depending on how Apache handles path normalization. This needs testing on the actual XAMPP/cPanel deployment. The comment says `public/` Document Root deployment resolves this — verify before production. |
| INF-02 | 🟡 MEDIUM | **`.env` is not validated to exist at application startup.** If `.env` is missing (accidentally deleted or not copied on a new deployment), the `env()` helper will silently return `null` for all keys. The application will try to connect to the database with `null` credentials — this will produce PDO connection errors rather than a clear deployment error message. A bootstrap check for required env keys would improve operational safety. |
| INF-03 | 🟡 MEDIUM | **`storage/` subdirectories exist but no `.htaccess` or deny rules exist within them.** If Apache's Document Root is at the project root instead of `public/`, `storage/logs/`, `storage/sessions/`, and `storage/cache/` could be accessible via HTTP. The root `.htaccess` blocks these via rewrite, but defense in depth would add an `Options -Indexes` `.htaccess` in the `storage/` directory itself. |
| INF-04 | 🟢 LOW | **No `storage/` `.gitkeep` presence check.** In a fresh deployment, if `storage/sessions/` doesn't exist, `Session::start()` logs a warning and falls back to the system temp dir. This is handled, but a deployment checklist should explicitly list required storage directories. |

**Strengths:**
- `Options -Indexes` disables directory listing at project root
- `.env` excluded from git (`.gitignore` confirmed)
- `.env.example` is comprehensive and fully documented
- All 7 error view types exist (403, 404, 500, expired, maintenance, workspace_not_found, invitation_not_found)
- `WorkspaceMiddleware` enforces DB-level tenant isolation — not just session-based

**Verdict:** Good foundation. INF-01 needs verification in the actual hosting environment before first client goes live.

---

### MODULE 14 — Developer Console (DX001–DX003)

**Rating: ⭐⭐⭐⭐ Good**

**What was audited:**
- `DeveloperMiddleware.php` — `DEVELOPER_MODE` env guard
- `RoleMiddleware` mapping `/app/developer` → `super_admin`
- Developer routes, controller, and views

**Findings:**

| ID | Severity | Finding |
|---|---|---|
| DX-01 | 🟠 HIGH | **`DEVELOPER_MODE=true` is set in `.env.example`** — this means any operator who copies the example file verbatim will have the Developer Console enabled in production. The default should be `DEVELOPER_MODE=false` in `.env.example` so that production deployments are safe by default. |
| DX-02 | 🟡 MEDIUM | **Supabase Service Role Key is stored in `.env`** — this is correct, but the key is highly privileged. The `.env.example` documentation should include a warning that the Service Role Key bypasses all RLS policies and must be treated as a master database credential. |

**Strengths:**
- `DEVELOPER_MODE=false` blocks all console access when disabled — correct
- Double guard: `DeveloperMiddleware` + `RoleMiddleware` → must be super_admin AND developer mode enabled
- Health, Logs, Info, and Console pages provide actionable operational monitoring
- Credential masking for Supabase keys in views — confirmed in prior sprints

**Verdict:** Well-implemented. DX-01 is a clear operational risk — fix in `.env.example` before first deployment.

---

## CONSOLIDATED ISSUE REGISTER

### 🔴 CRITICAL — Release Blockers
*None identified.*

### 🟠 HIGH — Fix Before Client Onboarding

| ID | Module | Issue |
|---|---|---|
| OPS-01 | Operations/CX | `setcookie()` in `selectWorkspace()` missing `HttpOnly` and `Secure` flags |
| INF-01 | Infrastructure | `.htaccess` path traversal risk — verify in actual hosting environment |
| DX-01 | Developer Console | `.env.example` has `DEVELOPER_MODE=true` as default — should be `false` |

### 🟡 MEDIUM — Fix in RC Sprint

| ID | Module | Issue |
|---|---|---|
| AUTH-02 | Auth | Crew session expiry redirects to `/couple` login instead of `/admin` or dedicated crew login |
| BA-01 | Admin | Flash message key inconsistency (`flash_success` vs BaseController flash pattern) |
| BA-02 | Admin | No audit log when Super Admin adopts a workspace (viewing mode) |
| CP-01 | Couple Portal | `CoupleDashboardController` is a 1,184-line god object — needs decomposition post-RC |
| CP-02 | Couple Portal | `InvitationModel.php` uses CRLF line endings — normalize to LF |
| CP-04 | Couple Portal | Import preview has no pagination — timeout risk on large guest lists |
| CX-01 | Crew Portal | Verify crew sidebar does not render couple-only navigation items |
| INF-02 | Infrastructure | No startup validation that `.env` exists and required keys are present |
| INF-03 | Infrastructure | No defense-in-depth `.htaccess` in `storage/` directories |
| OPS-02 | Operations | Legacy scanner routes coexist with canonical event-specific routes |
| OPS-04 | Operations | `$_GET['page']` read directly in `weddingsList()` — should use `$request->get()` |

### 🟢 LOW — Backlog / Future Version

| ID | Module | Issue |
|---|---|---|
| AUTH-01 | Auth | Document production `APP_ENV + HTTPS + cookie.secure` checklist |
| BA-03 | Admin | Plans/Themes placeholders should say "Coming Soon" in the UI |
| BA-04 | Admin | No workspace edit capability post-provisioning |
| BA-05 | Admin | Status counts use N+7 queries — consolidate to GROUP BY |
| BDG-02 | Budget | No budget export to Excel/PDF |
| CP-03 | Couple Portal | Invitation preview layout-switch pattern is architecturally fragile |
| CP-05 | Couple Portal | Default budget hardcoded in IDR |
| CORE-01 | Framework | `escape_html()` discipline not enforced by lint |
| ENG-02 | Engagement | Queue reset has no undo |
| INF-04 | Infrastructure | No deployment checklist for storage directory creation |
| MSG-02 | Messaging | Preview has no pagination for large guest lists |
| OPS-03 | Operations | `selectWorkspace()` redirects to legacy scanner URL |
| PI-01 | Public Invitation | Invitation browser tab title is generic — not personalized |
| PI-02 | Public Invitation | No rate limiting on RSVP/Wish endpoints |
| TL-01 | Timeline | No bulk task completion |
| VND-01 | Vendor | No vendor document upload |

---

## PLATFORM-LEVEL OBSERVATIONS

### What Works Exceptionally Well

1. **Security consistency** — CSRF is applied on all state-changing POST routes across all modules without exception. This is rare and impressive for a custom framework.
2. **Tenant isolation** — `WorkspaceMiddleware` enforces DB-level verification, not just session trust. Correct approach.
3. **Middleware architecture** — The frozen zero-arg pipeline is a pragmatic, documented decision. It works.
4. **Module structure** — Each module has Controller / Model / Service / Validator split. Consistent.
5. **Error handling** — All three PHP error surfaces are covered. Production mode shows no stack traces.
6. **Authentication** — IP lockout, session regeneration, role-based timeouts, CSRF, secure cookies — all present.

### What Needs Attention (Non-Module)

1. **No automated tests exist.** Zero unit tests, zero integration tests, zero end-to-end tests. For a first client, this means the only QA layer is manual testing by the developer. This is a significant operational risk. The platform works today — but refactoring anything without a test suite is dangerous.

2. **No deployment runbook exists.** There is `database/README.md` and `.env.example` but no formal "How to deploy this to production on cPanel" document. Before onboarding a client, a step-by-step deployment checklist is mandatory.

3. **Tailwind CSS is used in several views** (the `insights.php` view uses Tailwind utility classes directly: `max-w-6xl`, `text-rose-600`, `bg-rose-50`, etc.) while the layout system appears to use a custom CSS design system. This is a CSS architecture inconsistency — some pages use Tailwind, others use CSS custom properties. Not breaking, but inconsistent.

4. **No `favicon.ico` or social preview meta tags.** For client-facing pages (the public invitation), there are no OG tags for WhatsApp/social previews. When a couple shares their invitation URL on WhatsApp, it will show a blank preview.

5. **Supabase synchronization is manual-only.** If the project's MySQL database is the source of truth and Supabase is the mirror — there is no automatic sync. In a disaster recovery scenario, the Supabase mirror may be stale. Document the sync frequency expectation clearly.

---

## PRODUCTION READINESS VERDICT

| Dimension | Score | Notes |
|---|---|---|
| Security | 4.5/5 | Strong. One cookie flag fix needed. |
| Authentication | 5/5 | No issues. |
| Authorization | 4.5/5 | Role enforcement solid. One crew redirect bug. |
| Data Integrity | 4/5 | CSRF everywhere. Transactions in critical paths. |
| Tenant Isolation | 5/5 | WorkspaceMiddleware is DB-verified. |
| Error Handling | 4.5/5 | All cases covered. Dev mode flag risk. |
| Code Architecture | 3.5/5 | God controller exists. Line ending inconsistency. |
| Infrastructure | 3.5/5 | `.htaccess` needs verification. No env validation. |
| Operational Tooling | 4/5 | Developer Console is solid. No deployment runbook. |
| Test Coverage | 1/5 | Zero automated tests. Critical gap. |

### **Overall: ⭐⭐⭐½ — CONDITIONALLY SHIPPABLE**

The platform is functionally complete and architecturally sound for a first client. The security fundamentals are strong. The critical blockers are minor in number and severity — none are data-loss or breach-class issues.

**Three things MUST happen before onboarding the first real client:**

1. ✅ Fix `DEVELOPER_MODE=true` in `.env.example` → change to `false`
2. ✅ Fix `setcookie()` cookie flags in `CrewHomeController::selectWorkspace()`
3. ✅ Verify `.htaccess` path blocking in the actual production hosting environment (cPanel)

**Recommended RC Sprint scope:** Focus on the 🟠 HIGH items first, then as many 🟡 MEDIUM items as possible before client onboarding. The 🟢 LOW items are post-client backlog.

---

## RC001 IMPLEMENTATION CHECKLIST — FOR EXECUTION AGENT

The following is the exact ordered checklist for the implementation phase.

### PRIORITY 1 — Security Fixes (Must ship)

- [ ] **RC001-S01:** In `CrewHomeController::selectWorkspace()`, add `httponly: true, secure: env('APP_ENV') === 'production', samesite: 'Strict'` to the `setcookie()` call.
- [ ] **RC001-S02:** In `.env.example`, change `DEVELOPER_MODE=true` to `DEVELOPER_MODE=false`.
- [ ] **RC001-S03:** Add a `storage/.htaccess` file with `Options -Indexes` + `Deny from all` to prevent directory browsing of log/session/cache files if Apache misconfiguration occurs.

### PRIORITY 2 — High-Impact Bugs (Must fix before client)

- [ ] **RC001-B01:** In `AuthMiddleware`, fix the session expiry redirect for Crew users. When `$role === 'crew'`, redirect to `/couple` is wrong — redirect to `/couple` is the crew login portal too (they use the couple login), so verify this is actually a UX issue and not a code bug. If crew logins go through `/couple`, document this as expected behavior and mark closed.
- [ ] **RC001-B02:** Audit and unify flash message keys. `AdminCrewController` uses `Session::set('flash_success', ...)` — verify the admin layout reads this exact key. If not, standardize to `$this->flash('success', ...)` pattern.
- [ ] **RC001-B03:** In `InvitationModel.php`, normalize CRLF to LF line endings.
- [ ] **RC001-B04:** In `PublicInvitationController::show()`, personalize the `title` field: `"{$payload['workspace']['groom_name']} & {$payload['workspace']['bride_name']} — Wedding Invitation"`.
- [ ] **RC001-B05:** In `CrewHomeController::weddingsList()`, replace `$_GET['page']` with `$request->get('page')`.

### PRIORITY 3 — Operational Improvements

- [ ] **RC001-O01:** Add an `audit_logs` entry in `AdminDashboardController::openWorkspace()` recording which admin opened which workspace.
- [ ] **RC001-O02:** Add an `.env` presence check in `Bootstrap.php` or `Application.php` — if `.env` file is not found, render a clear deployment error page instead of cryptic PDO failures.
- [ ] **RC001-O03:** Update the admin Plans and Themes placeholder pages to display "This feature is coming in a future version" rather than empty stub pages.

### PRIORITY 4 — Documentation

- [ ] **RC001-D01:** Create `docs/DEPLOYMENT.md` — step-by-step production deployment guide covering: cPanel Document Root configuration, `.env` setup, storage directory creation, database import, and `DEVELOPER_MODE=false` verification.
- [ ] **RC001-D02:** Add WhatsApp delivery expectation copy to the WhatsApp Queue UI — clarify that the platform generates links, not automated messages.
- [ ] **RC001-D03:** Add OG meta tags to `PublicInvitationController` / the theme renderer output so WhatsApp/social link previews show couple names and wedding date.

### OUT OF SCOPE (Future Version)

- Automated test suite
- Budget export (Excel/PDF)
- Bulk task completion in Timeline
- Vendor document upload
- Real WhatsApp API integration
- Workspace edit post-provisioning
- `CoupleDashboardController` god-object decomposition (schedule for Sprint after first client)

---

*Audit completed: 2026-07-21. Prepared for implementation agent.*
