# Phase 0: Foundation (approved 18 Oct 2026; delivered, see CHANGELOG.md)

Brief scope: repository, CI, module structure, tenancy and branch scope, roles and permissions,
audit log, design system, seeders.
Exit criteria: isolation tests pass; a branch user cannot read another branch; 2FA works.

## What already exists (audited 18 Oct 2026)

| Brief item | Current state | Gap |
|---|---|---|
| Tenancy | `tenants` table + `BelongsToTenant` global scope on 64 models. One church = one tenant. | None for church-level isolation. |
| Branch isolation | `branch_id` on 21 tables. Filtering is done by hand in 17 controllers via `visibleBranchIds()`. | No global branch scope. One forgotten `where` leaks data. No test sweeps every model. |
| Roles & permissions | 14 roles in `config/church.php`, one `role` column per user, `hasPermission()`. | Brief asks for spatie/laravel-permission, branch-scoped roles, fine-grained permissions, "admins grant only what they hold", the brief's 9 roles. |
| Audit log | Append-only (model guard + MySQL triggers). | Not hash-chained (not tamper-evident). No branch_id or device_id. No log of reads (auditor views) or exports. |
| 2FA | TOTP, enforced per role by `RequireTwoFactor`. Tested. | Role list must match the brief: HQ roles, auditors, treasurers, branch admins. |
| Module structure | Flat `app/Models`, `app/Services`. | Brief wants `app/Modules/{Core,Website,Accounting,Directs,Licensing,Sync}`. |
| CI & quality | GitHub Actions: SQLite + MariaDB suites, audits, Docker build. PHPUnit, Pint installed. | No Larastan, no Pest, Pint not enforced in CI. No git repo on this machine. |
| Design system | Tailwind 4, blue "brand" tokens, `.card/.btn/.table` component classes used by ~90 Vue pages. | Hymnal tokens not yet in the app. |
| Seeders | Demo church seeder. | Needs TOPRC: HQ, regions, sample branches, one user per brief role. |

## Proposed work

1. **Repository.** `git init`, `.gitignore` check, first commit of the current app as the baseline, then a
   `phase-0-foundation` branch (brief: one branch per phase). Git is not installed on this PC, so this
   needs Git for Windows installed first.
2. **Branch scope (the core of the phase).**
   - `BelongsToBranch` trait: global scope limiting rows to the user's visible branches, auto-stamps
     `branch_id` on create, refuses to create a row in a branch the user cannot see.
   - `BranchContext` (like `TenantContext`): current user's branch set; HQ/auditor get all or an
     assigned set; jobs and console set it explicitly; `withoutBranchScope()` only for HQ services.
   - Applied to all 21 branch-owned tables. The hand-written `visibleBranchIds()` filters stay as a
     second layer until tests prove the scope, then are removed controller by controller.
   - Route-model binding returns 404 (not 403) for another branch's record, so ids are not probed.
3. **Roles and permissions.**
   - Install spatie/laravel-permission with its "teams" feature keyed on branch, so one person can be
     Treasurer in one branch and nothing in another.
   - Seed the brief's 9 roles with fine-grained permissions (`cashbook.create`, `journal.approve`, ...),
     mapped from today's permission names so existing screens keep working.
   - Migrate each user's `role` column into a role assignment; keep the column read-only for one phase,
     then drop it.
   - Rule: a branch admin can only grant permissions they hold, and only in their branch (policy + test).
   - Separation of duties helper (`preparedBy !== approver`) in Core for later modules.
4. **Audit log.** Add `branch_id`, `device_id`, `prev_hash`, `hash` (SHA-256 over the previous hash and
   the row). `php artisan audit:verify` walks the chain and reports the first broken link. New events:
   login, logout, failed login, permission change, export, auditor view.
5. **Module structure.** Create `app/Modules/{Core,Website,Accounting,Directs,Licensing,Sync}` with a
   service provider each. New Phase 0 code (branch scope, permissions, audit chain) goes in `Core`.
   Existing accounting code is **not** moved now; it moves into `Accounting` in Phase 2 when that
   module is reworked, so working code is not churned twice.
6. **Design system in the app.** Port the approved Hymnal tokens into `resources/css/app.css`
   (light + dark), restyle the shared component classes and `AppLayout.vue` (oxblood rail for branch
   users, ink rail for HQ), self-host Fraunces/Inter/Plex Mono. Because most pages use the shared
   classes, the whole app takes the new look without editing each page.
7. **Quality gates.** Add Larastan (level 6, with a baseline file so existing code does not block CI),
   enforce `pint --test` in CI, add Pest alongside the current PHPUnit tests (both run together).
8. **Seeders.** `TobasSeeder`: Old Path Revival Church, HQ (Lekki), 2 regions, 4 branches, chart of
   accounts from the existing template, one test user per brief role (password and 2FA notes in the
   seeder file, not in chat).
9. **Tests for the exit criteria.**
   - Isolation sweep: for every model with `branch_id`, a Branch A user cannot list, view, update or
     delete Branch B rows, through Eloquent and through every GET route.
   - HQ and auditor see across branches; auditor cannot write anything.
   - Branch admin cannot grant a permission they lack, or grant in another branch.
   - 2FA enforced for the brief's roles; audit chain detects a tampered row.
10. **Docs.** Update DECISIONS.md and CHANGELOG.md; Phase 0 manual test script for a non-developer.

## Out of scope for Phase 0

Website/CMS (Phase 1), accounting changes (Phase 2), devices/offline (Phase 6), licensing (Phase 8).
`device_id` is added to the audit log now but stays null until Phase 6.
