# Sprint 0 — Execution Plan
**Epic 0: Project Initialization**

> Source: MIB v1.0 (Epic 0) · IMPLEMENTATION_ROADMAP.md · DBP v1.0 Ch.2, Ch.4, Ch.5, Ch.6, Ch.7

---

## Overview

Sprint 0 is the **foundation sprint**. No feature module may be built until every task in this sprint is complete and verified. The output is a stable, working framework kernel: folder structure, environment, front controller, routing, base classes, middleware, session management, view rendering, database connection, and error handling.

**Definition of Done for Sprint 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 operational. Error handling is in place. No business module code exists.

---

## Execution Order — All 12 Tasks

```
INIT-001 → INIT-002 → INIT-005 → INIT-003 → INIT-004
                                       ↓
                                   INIT-006
                                       ↓
                                   INIT-007
                                       ↓
                                   INIT-008 ←── INIT-012
                                       ↓
                                   INIT-009
                                       ↓
                                   INIT-010
                                       ↓
                                   INIT-011
```

---

## Task-by-Task Breakdown

---

### TASK 1 — INIT-001
**Create Project Folder Structure**
- **Priority**: CRITICAL
- **Size**: S (~30 min)
- **Dependencies**: None — this is the absolute first task
- **Ref Docs**: DBP v1.0 Ch.2, SAD v1.1 Ch.1

**What this creates:**

| 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

---

### TASK 2 — INIT-002
**Create Environment Configuration (.env)**
- **Priority**: CRITICAL
- **Size**: XS (~15 min)
- **Dependencies**: INIT-001 (folders must exist before placing files)
- **Ref Docs**: DBP v1.0 Ch.4, SAD v1.1 Ch.9

**What this creates:**

| Type | Path | Notes |
|------|------|-------|
| **File** | `.env.example` | Committed to git — all keys with comment docs, no values |
| **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 |
| **File** | `config/session.php` | Cookie name, timeout, flags |
| **File** | `config/storage.php` | Upload/export/log path constants |
| **File** | `config/modules.php` | Module registry — list of active modules |

**Environment Variables Required in `.env`:**
```
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
STORAGE_UPLOADS, STORAGE_EXPORTS, STORAGE_LOGS, STORAGE_CACHE
```

**Dependency note**: `INIT-005` (config loader) will be written AFTER INIT-002 because it *reads* these files. INIT-002 only creates the files — the `env()` function does not yet exist.

---

### TASK 3 — INIT-005
**Create Config Loader and `env()` Helper**
- **Priority**: CRITICAL
- **Size**: S (~30 min)
- **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 `require` on config files. Those config files use `env()`. If `env()` doesn't exist, the bootstrap fails. INIT-005 must come before INIT-003.

**What this creates:**

| Type | Path | Purpose |
|------|------|---------|
| **File** | `app/helpers/env.php` | `env($key, $default)` — reads `$_ENV` with fallback |
| **File** | `app/helpers/config.php` | `config($key)` — dot-notation loader (e.g. `config('database.host')`) |

**How it works:**
- `env()` reads from `$_ENV` (populated by `.env` parser) with a typed fallback
- `config()` loads the corresponding config file once per request (memory-cached via static variable)
- Both are global functions — no class, no instantiation

---

### TASK 4 — INIT-003
**Create Front Controller (`public/index.php`)**
- **Priority**: CRITICAL
- **Size**: M (~1 hour)
- **Dependencies**: INIT-001 (folders), INIT-002 (`.env`), INIT-005 (`env()` and `config()`)
- **Ref Docs**: DBP v1.0 Ch.5, SAD v1.1 Ch.4

**What this creates:**

| Type | Path | Purpose |
|------|------|---------|
| **File** | `public/index.php` | Single entry point — ALL web requests arrive here |
| **File** | `app/core/Bootstrap.php` | Loads `.env`, autoloader, config, starts session, registers error handler |
| **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. Register class autoloader (`Autoloader::register()`)
4. Load configuration files
5. Set PHP error reporting based on `APP_DEBUG`
6. Register global error/exception handlers
7. Start session
8. Dispatch request to router

**Acceptance Criteria:**
- `curl http://localhost/inveetaire/` returns a response (even if 404 or a "hello world")
- `curl http://localhost/inveetaire/app/core/Bootstrap.php` returns `403 Forbidden`

---

### TASK 5 — INIT-004
**Create Apache `.htaccess` Rewrite Rules**
- **Priority**: CRITICAL
- **Size**: XS (~15 min)
- **Dependencies**: INIT-003 (`public/index.php` must exist to rewrite to)
- **Ref Docs**: DBP v1.0 Ch.5, SAD v1.1 Ch.4

**What this creates:**

| 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/` |

**Rules defined:**
- 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

---

### TASK 6 — INIT-006
**Create Global Error Handler and Logger**
- **Priority**: CRITICAL
- **Size**: M (~1 hour)
- **Dependencies**: INIT-003 (Bootstrap must exist to register handlers), INIT-005 (`config()` needed for `APP_DEBUG` and log path)
- **Ref Docs**: DBP v1.0 Ch.7, SAD v1.1 Ch.13

**What this creates:**

| 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 levels**: `ERROR`, `WARNING`, `INFO`

**Error handling behavior:**
- `APP_DEBUG=true` → shows stack trace in browser (development only)
- `APP_DEBUG=false` → shows `errors/500.php`; writes to log file
- Log entry format: `[UTC timestamp] [LEVEL] [URL] [User/Role] Message`

---

### TASK 7 — INIT-007
**Create Core Router**
- **Priority**: CRITICAL
- **Size**: L (~2 hours)
- **Dependencies**: INIT-003 (Bootstrap dispatches to router; Request object needed)
- **Ref Docs**: DBP v1.0 Ch.5, SAD v1.1 Ch.4

**What this creates:**

| 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 |
| **File** | `routes/couple.php` | Couple portal routes |
| **File** | `routes/crew.php` | Crew portal routes |
| **File** | `routes/admin.php` | Super Admin portal routes |
| **File** | `routes/support.php` | Support Mode routes |

**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

**Route file structure (each file):**
```php
// Each file uses the router to register routes
// No logic — only route → controller mappings
```

> **Note**: Route files are empty skeletons at this stage. They are populated with real routes when their respective modules are built (Auth routes in Sprint 1, Guest routes in Sprint 4, etc.). However, the file structure and loading mechanism must be in place now.

---

### TASK 8 — INIT-008
**Create Base Controller, Model, and Service Classes**
- **Priority**: CRITICAL
- **Size**: M (~1 hour)
- **Dependencies**: INIT-007 (Router must exist; Request object used in BaseController)
- **Ref Docs**: DBP v1.0 Ch.4, SAD v1.1 Ch.6

**What this creates:**

| Type | Path | Purpose |
|------|------|---------|
| **File** | `app/core/BaseController.php` | `render()`, `redirect()`, `json()`, `flash()` — all module controllers extend this |
| **File** | `app/core/BaseModel.php` | PDO wrapper, `query()`, `find()`, `findAll()`, `insert()`, `update()` |
| **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

**BaseModel responsibilities:**
- Hold PDO connection instance (from Database singleton)
- `query(string $sql, array $params)` — execute parameterized statement
- Return arrays only — never objects

**Dependency note**: `INIT-012` (Database Connection) is listed as a sibling dependency of INIT-008 in the MIB. However, `Database.php` is listed as a file output of INIT-008. This means INIT-008 creates the `Database.php` class AND the connection logic together. INIT-012 then tests and validates the connection with actual DB credentials. In practice, `Database.php` must be written as part of INIT-008 so that `BaseModel` can reference it.

---

### TASK 9 — INIT-012
**Create Database Connection and Query Builder**
- **Priority**: CRITICAL
- **Size**: S (~30 min)
- **Dependencies**: INIT-005 (`config('database.*')` must work), INIT-008 (`Database.php` skeleton created there — this task completes and validates it)
- **Ref Docs**: DBP v1.0 Ch.4, PDD v1.0 Ch.1

**What this creates/completes:**

| Type | Path | Purpose |
|------|------|---------|
| **File** | `app/core/Database.php` | Completed PDO singleton with EXCEPTION error mode, charset, DSN |
| **File** | `config/database.php` | Already created in INIT-002; this task validates it connects |

**PDO configuration:**
- `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:**
- Connection established from `.env` credentials
- `SELECT 1` query executes and returns result
- Bad credentials → exception caught → logged → friendly error shown (not PHP error dump)

> **Also includes**: Creating the `inveetaire` database and running the migration order defined in PDD v1.0 Ch.13 is NOT part of INIT-012. That is a separate pre-requisite operation. INIT-012 only establishes the connection to an existing database. The database and schema must be created before this task can pass its acceptance criteria.

---

### TASK 10 — INIT-009
**Create Middleware Pipeline**
- **Priority**: CRITICAL
- **Size**: M (~1 hour)
- **Dependencies**: INIT-007 (Router dispatches via middleware stack), INIT-008 (BaseController used by middleware to send responses)
- **Ref Docs**: DBP v1.0 Ch.4, SAD v1.1 Ch.5

**What this creates:**

| Type | Path | Purpose |
|------|------|---------|
| **File** | `app/core/MiddlewarePipeline.php` | Executes ordered stack of middleware before controller |
| **File** | `app/middleware/AuthMiddleware.php` | Verifies valid session exists; redirects to login if not |
| **File** | `app/middleware/RoleMiddleware.php` | Verifies `$_SESSION['role']` matches required role; 403 if not |
| **File** | `app/middleware/CsrfMiddleware.php` | Validates CSRF token on all POST requests; 403 if missing/invalid |
| **File** | `app/middleware/WorkspaceMiddleware.php` | Reads `workspace_id` from session; validates resource ownership |
| **File** | `app/middleware/GuestMiddleware.php` | Resolves `guest_code` from URL; validates against workspace slug |
| **File** | `app/middleware/SupportMiddleware.php` | Detects support overlay in session; injects target `workspace_id` |

**Pipeline mechanism:**
- Each middleware implements `handle(Request $request, callable $next): Response`
- Router builds the middleware stack from route configuration
- Pipeline calls each handler in order; any middleware can short-circuit with a redirect or 403

> **Note**: At Sprint 0, most middleware will redirect to a placeholder or return a stub response, since Auth module (sessions/user records) does not exist yet. The middleware *files* and *structure* are created; their full logic is completed in Sprint 1 (Authentication).

---

### TASK 11 — INIT-010
**Create Session Manager, CSRF, and Flash Message**
- **Priority**: CRITICAL
- **Size**: M (~1 hour)
- **Dependencies**: INIT-009 (CsrfMiddleware uses Csrf class; SessionManager used in Bootstrap)
- **Ref Docs**: DBP v1.0 Ch.6, SAD v1.1 Ch.5

**What this creates:**

| 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 |
| **File** | `app/core/FlashMessage.php` | Stores success/error in session; reads and clears on next request |
| **File** | `app/helpers/csrf_field.php` | `csrf_field()` — outputs `<input type="hidden" name="_csrf" value="...">` |

**Session configuration (from `config/session.php`):**
```
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 and stored in `$_SESSION['csrf_token']`
- Regenerated after each successful form submission
- Validated by `CsrfMiddleware` on every POST

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

---

### TASK 12 — INIT-011
**Create View Renderer and Layout System**
- **Priority**: CRITICAL
- **Size**: M (~1 hour, MIB says 2 sessions)
- **Dependencies**: INIT-008 (BaseController calls `render()` which this task implements), INIT-010 (Flash messages displayed in layouts)
- **Ref Docs**: DBP v1.0 Ch.4, Ch.8

**What this creates:**

| 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 (compiled or CDN at this stage) |
| **File** | `public/assets/js/app.js` | Alpine.js initialization + global JS utilities |

**View rendering mechanism:**
```php
// BaseController::render($view, $data)
// 1. Extract $data array into local variables
// 2. Include the module view file (e.g. app/modules/Guest/views/index.php)
// 3. The view file outputs its HTML into an output buffer
// 4. The buffer is injected into the layout as $content
// 5. Layout file outputs final HTML
```

**Output escaping rule**: Every `echo` in a view must use `escape_html()`. No raw `echo $_var` ever.

---

## Dependency Graph (Visual)

```
INIT-001  (Folders)
  │
  ├─► INIT-002  (env files + config files)
  │     │
  │     └─► INIT-005  (env() + config() helpers)
  │               │
  │               └─► INIT-003  (Front Controller + Bootstrap + Autoloader)
  │                       │
  │                       ├─► INIT-004  (.htaccess rewrite rules)
  │                       │
  │                       └─► INIT-006  (Error Handler + Logger)
  │                               │
  │                               └─► INIT-007  (Router + Request + Route files)
  │                                       │
  │                                       ├─► INIT-008  (Base Classes + Database.php skeleton)
  │                                       │       │
  │                                       │       └─► INIT-012  (DB Connection validation)
  │                                       │
  │                                       └─► INIT-009  (Middleware Pipeline + all Middleware)
  │                                               │
  │                                               └─► INIT-010  (Session + CSRF + Flash)
  │                                                       │
  │                                                       └─► INIT-011  (View Renderer + Layouts)
```

---

## File Inventory by Category

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

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

### Core Framework Classes (INIT-003, INIT-007, INIT-008, INIT-012)
`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)
`app/core/MiddlewarePipeline.php`, `app/middleware/AuthMiddleware.php`, `app/middleware/RoleMiddleware.php`, `app/middleware/CsrfMiddleware.php`, `app/middleware/WorkspaceMiddleware.php`, `app/middleware/GuestMiddleware.php`, `app/middleware/SupportMiddleware.php`

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

### Helpers (INIT-005)
`app/helpers/env.php`, `app/helpers/config.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`

---

## Pre-Sprint 0 Pre-Requisite (Not a Task — Manual Action)

> [!IMPORTANT]
> **Database creation must happen before INIT-012 can be verified.**
>
> The `inveetaire` database must be created in MariaDB and the `inveetaire_app` database user must be provisioned with correct grants (per PDD v1.0 Ch.10.4) before INIT-012's acceptance criteria can be tested.
>
> The schema migration (all 35 tables per PDD v1.0 Ch.13) should also be run at this point so that the database connection can be tested against a real schema.

---

## What Sprint 0 Does NOT Build

| What | Why |
|------|-----|
| Login form | That is AUTH-001 in Sprint 1 |
| Any module (Guest, Vendor, etc.) | No module is built before the framework is stable |
| Database schema migration scripts | These are a pre-requisite operation, not an INIT task |
| TailwindCSS compilation pipeline | TailwindCSS CDN or a pre-compiled build is used at this stage |
| Real route logic in route files | Route files are skeletons — populated when each module is built |
| Super Admin seed data | Database seeding happens as a deployment step |

---

## Sprint 0 Completion Checklist

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

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

---

## Notes and Decisions for Implementation

1. **Autoloader**: A simple PSR-4 style `spl_autoload_register` — no Composer at this stage. Namespace `App\` maps to `app/`.
2. **`.env` Parser**: PHP has no built-in `.env` parser. A minimal custom parser (read file, split `KEY=VALUE`, populate `$_ENV`) is needed inside `Bootstrap.php`. No library.
3. **TailwindCSS at Sprint 0**: Use the TailwindCSS CDN `<script>` tag in layouts for now. Compilation pipeline is deferred.
4. **Alpine.js at Sprint 0**: Use CDN `<script>` tag. No bundler.
5. **Route files are loaded by the Router**: The Router's `loadRoutes()` method includes all 5 route files. Each file uses a global `$router` instance.
6. **INIT-004 and INIT-003 separation**: INIT-003 creates the front controller PHP file. INIT-004 creates the `.htaccess` files. They are separate because the `.htaccess` configuration is distinct from PHP logic and requires different testing.

---

**Awaiting your approval to begin implementation, starting with INIT-001.**
