# INVEETAIRE
## MASTER IMPLEMENTATION BACKLOG
### Version 1.1 — Epic 0 Synchronization

| Field | Value |
|-------|-------|
| Document Type | Master Implementation Backlog (MIB) |
| Version | 1.1 |
| Status | Active — Epic 0 Synchronized |
| Based On | All Approved Documents (PRD v1.1 through ADR v1.0) |
| Total Epics | 16 (Epic 0 through Epic 15) |
| Total Tasks | 120+ sequenced implementation tasks |
| Change History | v1.0 — Initial Backlog |
| | v1.1 — Epic 0 synchronized with DESIGN_REVISION_PACK_v2.0 (REV-010, REV-011, REV-013, REV-015–020, REV-022, REV-023) |

> **Scope of this revision**: Only Epic 0 (INIT-001 through INIT-012) has been updated. Epics 1–15 remain at v1.0 and are unchanged. All changes are documented in DESIGN_REVISION_PACK_v2.0.

---

## PURPOSE

This document is the single source of truth for every coding task in Inveetaire. Every task is small enough to complete in one AI coding session. Every task references the approved architecture documents. No task may bypass the Architecture Decision Records.

---

## IMPLEMENTATION AUTHORITY HIERARCHY

When any lower-level document conflicts with a higher-level document, implementation must STOP, the conflict must be reported, and approval must be obtained before proceeding.

```
AI.md                  ← Highest authority
    ↓
ADR
    ↓
MIB
    ↓
Sprint Execution Plan
    ↓
Implementation Prompt
    ↓
Implementation         ← Lowest authority
```

*Ref: DESIGN_REVISION_PACK_v2.0 — REV-023*

---

## SIZE LEGEND

| Size | Duration |
|------|----------|
| XS | ~15 min |
| S | ~30 min |
| M | ~1 hour |
| L | ~2 hours |
| XL | ~3+ hours |

---

## EPIC 0 — PROJECT INITIALIZATION

**Scope:** Repository setup, folder structure, environment configuration, bootstrap, routing foundation, error handling, and logger. Nothing is built until this Epic is complete and verified.

**Definition of Done for Epic 0:**
> The Core Framework is stable. All requests route through `public/index.php`. The router dispatches correctly. Middleware executes in order. Sessions are secure. The database connection is validated. Error handling is in place. No business module code exists.

---

### EXECUTION ORDER

INIT task numbers are identifiers, not execution sequence. The following dependency-safe order is authoritative.

```
INIT-001 → INIT-002 → INIT-005 → INIT-003 → INIT-004
                                      ↓
                                  INIT-006
                                      ↓
                                  INIT-007
                                      ↓
                                  INIT-008
                                      ↓
                                  INIT-012   ← validates Database.php (created in INIT-008)
                                      ↓
                                  INIT-009
                                      ↓
                                  INIT-010
                                      ↓
                                  INIT-011
```

> **REV-010, REV-011**: Task IDs are identifiers, not sequence. The order above is authoritative. INIT-012 executes after INIT-008 and before INIT-009.

> Dependency order always takes precedence over numeric task identifiers.

---

### FEATURE: Repository & Folder Structure

---

#### INIT-001 — Create Project Folder Structure

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | S |
| Dependencies | None — first task |
| Ref Docs | DBP v1.0 Ch.2, SAD v1.1 Ch.1 |

**Description**

Create the complete approved folder structure as defined in DBP v1.0 Chapter 2. Every directory must exist and have a purpose-describing `.gitkeep`.

**Files Expected**

| Type | Path |
|------|------|
| Directory | `app/` |
| Directory | `app/modules/` |
| Directory | `app/core/` |
| Directory | `app/middleware/` |
| Directory | `app/helpers/` |
| Directory | `app/services/` |
| Directory | `app/views/layouts/` |
| Directory | `app/views/components/` |
| Directory | `app/views/errors/` |
| Directory | `config/` |
| Directory | `routes/` |
| Directory | `public/` |
| Directory | `public/assets/css/` |
| Directory | `public/assets/js/` |
| Directory | `public/assets/fonts/` |
| Directory | `storage/uploads/` |
| Directory | `storage/exports/` |
| Directory | `storage/reports/` |
| Directory | `storage/cache/` |
| Directory | `storage/logs/` |
| Directory | `storage/sessions/` |
| Directory | `themes/` |
| File | `.gitignore` |
| File | `.env.example` (empty placeholder) |
| File | `README.md` |
| File | `.gitkeep` in every empty directory |

**Acceptance Criteria**

- All directories verified to exist
- `.gitignore` excludes: `vendor/`, `.env`, `storage/logs/`, `storage/uploads/`, `storage/sessions/`
- `README.md` at project root documents the folder structure purpose

**Definition of Done**

Folder structure matches DBP v1.0 exactly. Every directory exists. `.gitignore` excludes vendor/, `.env`, `storage/logs/`, `storage/uploads/`.

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Environment Configuration

---

#### INIT-002 — Create Environment Configuration (.env)

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | XS |
| Dependencies | INIT-001 (folders must exist) |
| Ref Docs | DBP v1.0 Ch.4, SAD v1.1 Ch.9 |

**Description**

Create all environment and configuration files. All config files return PHP arrays using `$_ENV` direct access. The `env()` helper does not exist yet (that is INIT-005).

**Files Expected**

| Type | Path | Notes |
|------|------|-------|
| File | `.env.example` | Committed to git — all keys with comment docs |
| File | `.env` | NOT committed — local development values |
| File | `config/app.php` | Returns PHP array from env vars |
| File | `config/database.php` | DB host, name, user, pass, charset, PDO options |
| File | `config/session.php` | Cookie name, timeouts, cookie flags |
| File | `config/storage.php` | Upload/export/log path constants |
| File | `config/modules.php` | Module registry — list of active modules |

**Environment Variables Required**

```
APP_NAME, APP_ENV, APP_DEBUG, APP_URL
DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASS, DB_CHARSET
SESSION_NAME, SESSION_TIMEOUT_COUPLE, SESSION_TIMEOUT_ADMIN, SESSION_TIMEOUT_CREW
SESSION_INACTIVITY_TIMEOUT                    ← REV-018: formalized per MIB AUTH-004 (7200 = 2h)
STORAGE_UPLOADS, STORAGE_EXPORTS, STORAGE_LOGS, STORAGE_CACHE
```

**Acceptance Criteria**

- `.env.example` committed with all keys and comments
- `.env` excluded from git
- All 5 config PHP files return valid arrays
- No env() helper used (does not exist yet)
- Session timeouts match MIB AUTH-004 (8h admin / 24h couple / 12h crew / 2h inactivity)
- PDO options correct (ERRMODE_EXCEPTION, FETCH_ASSOC, no emulation)
- All 17 modules registered in `modules.php`

**Definition of Done**

All 5 config files return valid PHP arrays. Zero PHP warnings. `.env` excluded from git. `.env.example` fully documented.

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Config & Helper Infrastructure

---

#### INIT-005 — Create Config Loader and env() Helper

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | S |
| Dependencies | INIT-002 (`.env` and config files must exist to test against) |
| Ref Docs | DBP v1.0 Ch.4, Ch.10 |

> **Why INIT-005 before INIT-003?** The front controller (INIT-003) boots the application by calling config files that use `env()`. If `env()` does not exist, the bootstrap fails. INIT-005 must precede INIT-003.

**Description**

Implement two global helper functions. No class, no instantiation, no external dependencies. PHP 7.4 native only.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/helpers/env.php` | `env($key, $default)` — reads `$_ENV` with typed coercion and fallback |
| File | `app/helpers/config.php` | `config($key, $default)` — dot-notation loader with per-request static cache |

**Implementation Notes**

- `env()` reads from `$_ENV` (populated by `.env` parser) with full typed coercion: `"true"` → `true`, `"null"` → `null`, numeric strings → int/float
- `config()` uses `dirname(dirname(dirname(__FILE__)))` to resolve the project root — `ROOT_PATH` is NOT available when helpers are loaded. This is the correct and intentional pattern. *REV-019*
- Both functions are guarded by `function_exists()` to prevent redeclaration errors
- Both functions are global — no class, no namespace

**Acceptance Criteria**

- `env('KEY', 'default')` returns default when key not set
- `env()` casts `"true"/"false"/"null"` correctly
- `env()` casts numeric strings to int/float; preserves leading-zero strings
- `config('app.name')` returns string from `config/app.php`
- `config('session.timeout.admin')` navigates nested keys
- `config('nonexistent_file.key', 'safe')` returns default without crash
- Both functions guarded by `function_exists()`

**Definition of Done**

25/25 test cases pass. Zero PHP warnings. No external dependencies.

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Front Controller & Bootstrap

---

#### INIT-003 — Create Front Controller and Bootstrap

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | M |
| Dependencies | INIT-001 (folders), INIT-002 (`.env`), INIT-005 (`env()` and `config()`) |
| Ref Docs | DBP v1.0 Ch.5, SAD v1.1 Ch.4 |

**Description**

Create the single entry point and application bootstrap sequence. All web requests arrive at `public/index.php`.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `public/index.php` | Single entry point — ALL web requests arrive here |
| File | `app/core/Bootstrap.php` | Loads `.env`, autoloader, config, registers error handler, starts session |
| File | `app/core/Application.php` | `run()` — boots bootstrap, then dispatches request to router |
| File | `app/core/Autoloader.php` | PSR-4 style autoloader for `App\` namespace |

**Bootstrap Sequence (exact order)**

1. Define project root constant (`ROOT_PATH`)
2. Load `.env` file into `$_ENV`
3. Load helper functions (`env`, `config`, `csrf_field`, `escape_html`)
4. Register class autoloader
5. Configure PHP error reporting based on `APP_DEBUG`
6. Register global error/exception handlers
7. Start session with configured security settings (wired in INIT-010)

Request dispatch → `Router::dispatch()` is called by `Application::handleRequest()` (INIT-007)

**Acceptance Criteria**

- `curl http://localhost/inveetaire/` returns a response
- `curl http://localhost/inveetaire/app/core/Bootstrap.php` returns `403 Forbidden`

**Definition of Done**

Front controller operational. Bootstrap sequence complete. Autoloader maps `App\` namespace to `app/`.

**Estimated AI Coding Sessions:** 1

---

#### INIT-004 — Create Apache .htaccess Rewrite Rules

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | XS |
| Dependencies | INIT-003 (`public/index.php` must exist to rewrite to) |
| Ref Docs | DBP v1.0 Ch.5, SAD v1.1 Ch.4 |

**Description**

Configure Apache mod_rewrite to route all requests through the front controller, while serving static assets directly. Deny direct access to sensitive directories.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `public/.htaccess` | Rewrites all requests to `index.php`; allows direct static asset serving |
| File | `.htaccess` (root) | Redirects all requests from root into `public/` |

**Rewrite Rules**

- Enable `mod_rewrite`
- Pass requests for existing files/dirs directly (CSS, JS, images in `public/assets/`)
- All other requests → `public/index.php`
- DENY all direct access to `app/`, `storage/`, `config/`, `routes/`

**Acceptance Criteria**

- `curl http://localhost/inveetaire/app/modules/Guest/GuestController.php` → 403
- `curl http://localhost/inveetaire/storage/logs/app.log` → 403
- `curl http://localhost/inveetaire/` → dispatched to front controller
- `curl http://localhost/inveetaire/public/assets/css/app.css` → served directly

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Error Handling & Logging

---

#### INIT-006 — Create Global Error Handler and Logger

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | M |
| Dependencies | INIT-003 (Bootstrap must exist), INIT-005 (`config()` needed for log path) |
| Ref Docs | DBP v1.0 Ch.7, SAD v1.1 Ch.13 |

**Description**

Implement production-complete error handling and logging. Logger.php is implemented to full production specification in this task — it is not a skeleton requiring later finalization. *REV-015 (DRP v2.0)*

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/core/ErrorHandler.php` | Registers PHP error/exception handlers; routes to Logger or debug output |
| File | `app/core/Logger.php` | Writes dated log files to `storage/logs/app-YYYY-MM-DD.log` |
| File | `app/views/errors/500.php` | Friendly "Something went wrong" page |
| File | `app/views/errors/404.php` | Friendly "Page not found" page |
| File | `app/views/errors/403.php` | Friendly "Access denied" page |
| File | `app/views/errors/maintenance.php` | Maintenance mode page |

**Logger Specification**

- Log levels: `ERROR`, `WARNING`, `INFO`
- Log entry format: `[UTC timestamp] [LEVEL] [URL] [User/Role] Message`
- `APP_DEBUG=true` → shows stack trace in browser (development only)
- `APP_DEBUG=false` → shows `errors/500.php`; writes to log file
- Log path resolved from `config('storage.logs')` with `ROOT_PATH` fallback
- Thread-safe writes via `LOCK_EX`
- Never throws — falls back to `error_log()` on failure

**Integration Points Wired in This Task**

- `ErrorHandler` calls `Logger::error()` on unhandled exceptions
- `Logger::formatUser()` reads `$_SESSION` (Sprint 1 Auth context placeholder)

**Acceptance Criteria**

- Error caught → logged to daily file → 500 page shown (no PHP error dump in production)
- Logger writes `ERROR`, `WARNING`, `INFO` levels
- Log file created at `storage/logs/app-YYYY-MM-DD.log`

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Routing

---

#### INIT-007 — Create Core Router

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | L |
| Dependencies | INIT-003 (Bootstrap dispatches to router; Request object needed) |
| Ref Docs | DBP v1.0 Ch.5, SAD v1.1 Ch.4 |

**Description**

Implement URL matching, named parameter extraction, middleware resolution, and controller dispatch. Route files are skeletons at this stage — populated when each module is built.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/core/Router.php` | URL matching, middleware resolution, controller dispatch |
| File | `app/core/Request.php` | Wraps `$_GET`, `$_POST`, `$_SERVER`, named params |
| File | `routes/public.php` | Public + guest invitation routes (skeleton) |
| File | `routes/couple.php` | Couple portal routes (skeleton) |
| File | `routes/crew.php` | Crew portal routes (skeleton) |
| File | `routes/admin.php` | Super Admin portal routes (skeleton) |
| File | `routes/support.php` | Support Mode routes (skeleton) |

**Router Capabilities**

- Register routes: `GET`, `POST` only (no PUT/DELETE at MVP per DBP Ch.5)
- Route groups with shared middleware prefix
- Named URL parameters: `{id}`, `{slug}`, `{guest_code}`
- Dispatch to `ControllerClass::method()` pairs
- Return 404 response for unmatched routes
- Dispatch all matched routes through `MiddlewarePipeline` (wired in INIT-009)

**Acceptance Criteria**

- Routes register correctly for GET and POST
- Named parameters extracted from URL
- 404 returned for unmatched route
- Route files loaded by Router's `loadRoutes()` method

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Base Classes & Database Layer

---

#### INIT-008 — Create Base Controller, Model, and Service Classes

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | M |
| Dependencies | INIT-007 (Router must exist; Request object used in BaseController) |
| Ref Docs | DBP v1.0 Ch.4, SAD v1.1 Ch.6 |

**Description**

Create the base classes that all module classes extend. `Database.php` is implemented to production specification here. INIT-012 validates the live connection — it does not re-implement this file.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/core/BaseController.php` | `render()`, `redirect()`, `json()`, `flash()` — all module controllers extend this |
| File | `app/core/BaseModel.php` | PDO wrapper with core data access primitives |
| File | `app/core/BaseService.php` | Config access, helper access — all module services extend this |
| File | `app/core/Database.php` | PDO singleton — one connection per request lifecycle |

**BaseController Responsibilities**

- `render(string $view, array $data = [])` — load view file + wrap in layout
- `redirect(string $path)` — send 302 header + exit
- `json(array $data, int $code = 200)` — JSON response envelope
- `flash(string $type, string $message)` — set flash session message
- Default `$layout = 'app'` — intentional no-layout fallback. Portal controllers must declare their layout explicitly: `couple`, `admin`, `crew`, or `public`. *REV-017*

**BaseModel Core Data Access Primitives** *(REV-020)*

- `query(string $sql, array $params)` — execute parameterized statement; return arrays only
- `find(string $table, array $conditions)` — return single row or null
- `findAll(string $table, array $conditions)` — return multiple rows
- `insert(string $table, array $data)` — insert row; return last insert ID
- `update(string $table, array $data, array $conditions)` — update rows
- `count(string $table, array $conditions)` — return integer row count

**Acceptance Criteria**

- `BaseController::render()` wraps view in layout
- `BaseController::redirect()` sends 302 and exits
- `BaseController::json()` returns valid JSON envelope
- `BaseModel` holds PDO connection from `Database` singleton
- `BaseModel::count()` returns integer
- `Database::getInstance()` returns same object on repeated calls
- No business logic in any base class

**Definition of Done**

14/14 tests pass. Zero PHP warnings. No DI container, no Reflection.

**Estimated AI Coding Sessions:** 1

---

#### INIT-012 — Validate Database Connection

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | S |
| Dependencies | INIT-008 (`Database.php` implemented there; this task validates it), INIT-005 (`config('database.*')` must work) |
| Ref Docs | DBP v1.0 Ch.4, PDD v1.0 Ch.1 |

> **Execution position**: INIT-012 must execute after INIT-008 and before INIT-009. INIT-009 (middleware pipeline) must not be wired before the database connection is validated. *REV-011*

**Description**

Validate the database connection layer established in INIT-008 against a live MariaDB instance. This is a **validation task only** — `Database.php` was implemented to production specification in INIT-008. No new files are created by this task. *REV-011, REV-012 (merged)*

**Files Expected**

*None.* `Database.php` was fully implemented in INIT-008. `config/database.php` was created in INIT-002. This task validates both against a live connection.

**PDO Configuration to Verify**

- `PDO::ATTR_ERRMODE` → `PDO::ERRMODE_EXCEPTION`
- `PDO::ATTR_DEFAULT_FETCH_MODE` → `PDO::FETCH_ASSOC`
- `PDO::ATTR_EMULATE_PREPARES` → `false` (real prepared statements)
- Charset: `utf8mb4`

**Acceptance Criteria**

- `SELECT 1` query executes and returns result 1
- charset confirmed as `utf8mb4`
- Singleton identity confirmed: same PDO instance on repeated calls
- Bad credentials → exception caught → logged → friendly error shown (no PHP error dump)
- Real prepared statements execute correctly

> **Pre-requisite (manual, not a task):** The `inveetaire` database must exist in MariaDB and the `inveetaire_app` user must be provisioned before these criteria can be verified. Schema migration per PDD v1.0 Ch.13 should also be complete.

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Middleware Pipeline

---

#### INIT-009 — Create Middleware Pipeline

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | M |
| Dependencies | INIT-007 (Router dispatches via middleware stack), INIT-008 (BaseController used in dispatch), INIT-012 (database connection validated) |
| Ref Docs | DBP v1.0 Ch.4, SAD v1.1 Ch.5 |

**Description**

Implement the middleware pipeline engine. Only the pipeline infrastructure is in scope for Sprint 0. Concrete middleware classes are implemented in the sprints that own their logic. *REV-013*

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/core/MiddlewarePipeline.php` | Executes ordered stack of middleware before controller action |

**Pipeline Mechanism**

- Onion/decorator pattern: middleware wraps are built inside-out; first class in the array executes first
- Each middleware must expose `handle(Request $request, callable $next)` method
- Any middleware may short-circuit by not calling `$next`
- Empty middleware stack calls the destination (controller action) directly with zero overhead
- Missing middleware class → error logged via `Logger::error()`, no PHP fatal

**Concrete Middleware — Deferred to Implementing Sprints** *(REV-013)*

| File | Implementing Sprint |
|------|-------------------|
| `app/middleware/CsrfMiddleware.php` | Sprint 1 (Core Utilities) |
| `app/middleware/AuthMiddleware.php` | Sprint 2 (Authentication) |
| `app/middleware/RoleMiddleware.php` | Sprint 2 (Authentication) |
| `app/middleware/WorkspaceMiddleware.php` | Sprint 3 (Workspace) |
| `app/middleware/GuestMiddleware.php` | Sprint 4/5 (Guest) |
| `app/middleware/SupportMiddleware.php` | Sprint 13 (Support Mode) |

> `app/middleware/` contains only `.gitkeep` after Sprint 0. These files must not be created as stubs before their implementing sprint. Creating stubs that reference undefined Auth structures produces dead code that will be entirely rewritten.

**Acceptance Criteria**

- Middleware executes in registration order
- Empty middleware stack calls destination directly
- Early termination: not calling `$next` stops the chain
- Missing middleware class → error logged, no PHP fatal
- Missing `handle()` method → error logged, no PHP fatal
- Router wired to dispatch through `MiddlewarePipeline`
- No session dependencies in `MiddlewarePipeline.php`
- No database dependencies in `MiddlewarePipeline.php`
- No auth/authz/CSRF logic in `MiddlewarePipeline.php`

**Estimated AI Coding Sessions:** 1

---

### FEATURE: Session Management & CSRF

---

#### INIT-010 — Create Session Manager, CSRF, and Flash Message

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | M |
| Dependencies | INIT-009 (pipeline in place), INIT-003 (Bootstrap calls Session::start()) |
| Ref Docs | DBP v1.0 Ch.6, SAD v1.1 Ch.5 |

**Description**

Implement session initialization, CSRF token lifecycle, and flash message pull-semantics. `Bootstrap::init()` calls `Session::start()` as the final step before request dispatch.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/core/Session.php` | Starts session with correct cookie flags; provides typed get/set/destroy methods |
| File | `app/core/Csrf.php` | Generates CSRF token per session; validates on POST using `hash_equals()` |
| File | `app/core/FlashMessage.php` | Stores success/error in session; reads and clears on next request (pull-semantics) |
| File | `app/helpers/csrf_field.php` | `csrf_field()` — outputs `<input type="hidden" name="_csrf" value="...">` |

**Session Configuration**

```
name:             inveetaire_session
cookie_httponly:  true
cookie_secure:    true (HTTPS only in production)
cookie_samesite:  Strict
save_path:        storage/sessions/
```

**CSRF Token Lifecycle**

- Generated once per session; stored in `$_SESSION['csrf_token']`
- Regenerated after each successful form submission
- Validated by `CsrfMiddleware` on every POST (Sprint 1)
- Validation uses `hash_equals()` for timing-safe comparison

**Flash Message Lifecycle**

- `FlashMessage::set('success', 'Guest added.')` → stores in `$_SESSION['flash']`
- On next request, layout reads flash → displays banner → clears from session atomically

**Acceptance Criteria**

- Session starts with `inveetaire_session` name
- Cookie `httponly=true`, `samesite=Strict`, `path=/`
- Session files stored in `storage/sessions/`
- CSRF token is 64 hex chars (32 bytes)
- `Csrf::getToken()` is idempotent
- `Csrf::validate()` passes non-POST requests
- `Csrf::validate()` passes POST with correct token
- `Csrf::validate()` fails POST with wrong/empty token
- `csrf_field()` renders correct hidden input
- `FlashMessage::pull()` clears atomically on first read
- No auth/login/logout logic in any INIT-010 file
- No database references in any INIT-010 file

**Estimated AI Coding Sessions:** 1

---

### FEATURE: View Renderer & Layout System

---

#### INIT-011 — Create View Renderer and Layout System

| Field | Value |
|-------|-------|
| Layer | Core |
| Priority | Critical |
| Size | M |
| Dependencies | INIT-008 (BaseController::render() calls layout files), INIT-010 (flash messages displayed in layouts) |
| Ref Docs | DBP v1.0 Ch.4, Ch.8 |

> **Task name clarification**: This task is "Create View Renderer and Layout System" as defined in the Sprint 0 Execution Plan. The earlier task prompt name "Logging Finalization" was a documentation error and is superseded. Logger.php is production-complete after INIT-006 — no finalization task is required. *REV-015*

**Description**

Implement four portal layout shells, three reusable components, a CSS design system, and the Alpine.js initialization file. All view output must use `escape_html()` — no raw `echo` of any variable is permitted.

**Files Expected**

| Type | Path | Purpose |
|------|------|---------|
| File | `app/views/layouts/couple.php` | HTML shell for Couple Portal — nav, flash, content slot, assets |
| File | `app/views/layouts/admin.php` | HTML shell for Super Admin Portal |
| File | `app/views/layouts/crew.php` | HTML shell for Crew Portal (minimal — check-in only) |
| File | `app/views/layouts/public.php` | HTML shell for public invitation pages (no nav) |
| File | `app/views/components/nav_couple.php` | Couple portal navigation bar |
| File | `app/views/components/flash.php` | Flash message banner component |
| File | `app/views/components/pagination.php` | Pagination links component |
| File | `public/assets/css/app.css` | TailwindCSS base stylesheet |
| File | `public/assets/js/app.js` | Alpine.js initialization + global JS utilities |
| File | `app/helpers/escape_html.php` | **Approved prerequisite** — `escape_html(string $value): string` wrapper for `htmlspecialchars(ENT_QUOTES, UTF-8)`. Required by the output escaping rule. Registered in `Bootstrap::loadHelpers()`. *REV-016* |

> **No generic `app.php` layout**: There is no `app/views/layouts/app.php`. The default `$layout = 'app'` in `BaseController` is an intentional no-layout fallback. Each portal controller must explicitly declare its layout. *REV-017*

**Output Escaping Rule**

Every `echo` in a view, layout, or component must call `escape_html()`. No raw `echo $variable` is ever permitted.

```php
// Correct
echo escape_html($user['name']);

// Forbidden
echo $user['name'];
```

**Portal Layout Override Requirement** *(REV-017)*

| Portal | Required Declaration |
|--------|---------------------|
| Couple Portal | `$this->layout = 'couple'` |
| Super Admin | `$this->layout = 'admin'` |
| Crew Portal | `$this->layout = 'crew'` |
| Public / Guest | `$this->layout = 'public'` |

**View Rendering Mechanism**

```
BaseController::render($view, $data)
  1. Extract $data into local variables
  2. Include module view file → output buffer
  3. Buffer captured as $content
  4. Layout file wraps $content → final HTML output
```

**Acceptance Criteria**

- All 4 layout files render valid HTML
- All layouts include escape_html() for all variable output
- Flash component reads from `FlashMessage::pull()` and renders correctly
- Pagination component renders page links
- `public/assets/css/app.css` loaded by all layouts
- `public/assets/js/app.js` loaded by all layouts
- `escape_html()` available globally via Bootstrap::loadHelpers()
- No database access in any view or layout file
- No session access in any view or layout file (layouts pull flash via BaseController)

**Estimated AI Coding Sessions:** 2

---

## EPIC 0 — COMPLETION CHECKLIST

Before Epic 0 is declared complete and Sprint 1 (Authentication) begins:

- [x] All 22 directories exist and are committed (with `.gitkeep` where empty)
- [x] `.gitignore` excludes all sensitive/generated paths
- [x] `env()` and `config()` helpers work correctly
- [x] All requests route through `public/index.php` — direct PHP file access returns 403
- [x] 404 page renders for unknown routes
- [x] Error handler catches unhandled exceptions → logs to file → shows 500 page
- [x] Router dispatches correctly to a test controller
- [x] Named URL parameters (`{id}`, `{slug}`, `{guest_code}`) are extracted
- [x] `MiddlewarePipeline.php` executes middleware in registration order
- [x] `app/middleware/` directory is empty (`.gitkeep` only) — concrete middleware deferred
- [x] Session starts with `HttpOnly`, `SameSite=Strict` cookie flags
- [x] CSRF token is generated per session (64 hex chars)
- [x] Flash message appears after redirect, disappears on next request
- [x] All 4 layout files render valid HTML
- [x] `escape_html()` available globally and used in all view output
- [x] PDO connection to MariaDB validated (INIT-012)
- [x] Parameterized query executes and returns correct result
- [x] Bad DB credentials → logged → 500 page shown (no PHP error dump)

**Epic 0 Status: ✅ COMPLETE**

---

### Sprint Gate

Sprint 0 may only be declared complete after every checklist item above has been verified.

---

## EPIC 0 — FILE INVENTORY (Final, Synchronized)

### Directories Created (INIT-001)
22 directories across `app/`, `config/`, `routes/`, `public/`, `storage/`, `themes/`

### Configuration Files (INIT-002)
`.env`, `.env.example`, `config/app.php`, `config/database.php`, `config/session.php`, `config/storage.php`, `config/modules.php`

### Helper Functions (INIT-005, INIT-010, INIT-011)
`app/helpers/env.php`, `app/helpers/config.php`, `app/helpers/csrf_field.php`, `app/helpers/escape_html.php` *(REV-016)*

### Core Framework Classes (INIT-003, INIT-007, INIT-008)
`app/core/Bootstrap.php`, `app/core/Application.php`, `app/core/Autoloader.php`, `app/core/Router.php`, `app/core/Request.php`, `app/core/Database.php`, `app/core/BaseController.php`, `app/core/BaseModel.php`, `app/core/BaseService.php`

### Error & Logging (INIT-006)
`app/core/ErrorHandler.php`, `app/core/Logger.php`

### Middleware (INIT-009) *(REV-013)*
`app/core/MiddlewarePipeline.php`

> Concrete middleware classes are deferred: `CsrfMiddleware` → Sprint 1, `AuthMiddleware`/`RoleMiddleware` → Sprint 2, `WorkspaceMiddleware` → Sprint 3, `GuestMiddleware` → Sprint 4/5, `SupportMiddleware` → Sprint 13.

### Session, CSRF, Flash (INIT-010)
`app/core/Session.php`, `app/core/Csrf.php`, `app/core/FlashMessage.php`

### View System (INIT-011)
`app/views/layouts/couple.php`, `app/views/layouts/admin.php`, `app/views/layouts/crew.php`, `app/views/layouts/public.php`, `app/views/components/nav_couple.php`, `app/views/components/flash.php`, `app/views/components/pagination.php`

### Route Files — skeletons (INIT-007)
`routes/public.php`, `routes/couple.php`, `routes/crew.php`, `routes/admin.php`, `routes/support.php`

### Apache Configuration (INIT-004)
`public/.htaccess`, `.htaccess` (root-level redirect)

### Public Assets (INIT-011)
`public/index.php`, `public/assets/css/app.css`, `public/assets/js/app.js`

### Error Pages (INIT-006)
`app/views/errors/500.php`, `app/views/errors/404.php`, `app/views/errors/403.php`, `app/views/errors/maintenance.php`

### INIT-012 — No new files
`app/core/Database.php` and `config/database.php` were created in INIT-008 and INIT-002 respectively. INIT-012 validates them against a live connection only.

---

## SYNCHRONIZATION SUMMARY

### Sections Modified

| INIT Task | Change Type | DRP Revision |
|-----------|-------------|-------------|
| Header / Execution Order | Added execution sequence section; clarified IDs ≠ order | REV-010 |
| INIT-002 | Added `SESSION_INACTIVITY_TIMEOUT` to env key list | REV-018 |
| INIT-005 | Documented `__FILE__`-based root resolution pattern | REV-019 |
| INIT-008 | Added `count()` to BaseModel primitives; added portal layout override note | REV-017, REV-020 |
| INIT-009 | Removed six concrete middleware stubs from Files Expected; added deferral table | REV-013 |
| INIT-010 | No structural changes; INIT-010 was correctly specified |  |
| INIT-011 | Corrected task name; added `escape_html.php` to Files Expected; added portal layout override requirement; added no-`app.php` clarification | REV-015, REV-016, REV-017 |
| INIT-012 | Changed "creates/completes" to "validates"; added execution position note; removed Files Expected | REV-011 |
| Authority Hierarchy | Added implementation authority hierarchy section | REV-023 |

### Revisions Applied

| Revision | Description |
|----------|-------------|
| REV-010 | Execution sequence section added; IDs are identifiers, not sequence |
| REV-011 | INIT-012 positioned after INIT-008 and before INIT-009; described as validation-only |
| REV-013 | INIT-009 Files Expected reduced to `MiddlewarePipeline.php` only; six concrete middleware deferred |
| REV-015 | INIT-011 task name corrected; "Logging Finalization" error noted; INIT-006 clarified as production-complete |
| REV-016 | `app/helpers/escape_html.php` added to INIT-011 Files Expected as approved prerequisite |
| REV-017 | Portal layout override requirement documented; no generic `app.php` layout clarified |
| REV-018 | `SESSION_INACTIVITY_TIMEOUT` added to INIT-002 env key list |
| REV-019 | `__FILE__`-based root resolution documented in INIT-005 |
| REV-020 | `count()` added to BaseModel core data access primitives in INIT-008 |
| REV-022 | CLI test scaffold rule referenced in implementation notes |
| REV-023 | Implementation authority hierarchy added to document header |

### Files Added to Files Expected

| Task | File Added | Reason |
|------|-----------|--------|
| INIT-011 | `app/helpers/escape_html.php` | Approved prerequisite of output escaping rule (REV-016) |

### Files Removed from Files Expected

| Task | File Removed | Reason |
|------|-------------|--------|
| INIT-009 | `app/middleware/AuthMiddleware.php` | Deferred to Sprint 2 (REV-013) |
| INIT-009 | `app/middleware/RoleMiddleware.php` | Deferred to Sprint 2 (REV-013) |
| INIT-009 | `app/middleware/CsrfMiddleware.php` | Deferred to Sprint 1 (REV-013) |
| INIT-009 | `app/middleware/WorkspaceMiddleware.php` | Deferred to Sprint 3 (REV-013) |
| INIT-009 | `app/middleware/GuestMiddleware.php` | Deferred to Sprint 4/5 (REV-013) |
| INIT-009 | `app/middleware/SupportMiddleware.php` | Deferred to Sprint 13 (REV-013) |
| INIT-012 | `app/core/Database.php` (as a new file) | Already implemented in INIT-008; INIT-012 is validation-only (REV-011) |

### Execution Order Corrections

| Before | After | Revision |
|--------|-------|---------|
| INIT task numbers implied sequence | Explicit Execution Sequence section added | REV-010 |
| INIT-012 positioned after INIT-010 in dependency graph | INIT-012 positioned between INIT-008 and INIT-009 | REV-011 |
| INIT-008 → INIT-009 (direct) | INIT-008 → INIT-012 → INIT-009 | REV-011 |

---

*Document produced: 2026-07-01*
*Based on: DESIGN_REVISION_PACK_v2.0 (REV-010, REV-011, REV-013, REV-015–020, REV-022, REV-023)*
*Epic 0 status: Complete*
*Epics 1–15: Unchanged at v1.0*
