# Project Status

Last updated: 2026-10-23

## Where the project stands
All 22 modules in the plan have a working implementation with services, screens, migrations and tests. "Built and tested here" is not the same as "ready for production": the right-hand column of the table below says what has **not** been proven, and `KNOWN-LIMITATIONS.md` lists the design limits. Nothing has been deployed to the real server.

## Verified results (run on this machine: PHP 8.4.27, MariaDB 11.8.2)
| Check | Result |
|---|---|
| `phpunit` (Unit + Integration + Isolation) | **493 tests, 5590 assertions, all passing; 1 skipped** (~105 s) |
| `phpstan` level 8 (app/, cron/, public/, tests/) | **No errors**, no suppressions |
| Backup → restore round trip | Real dump of the test database restored into a scratch database with **both** drivers (PHP-native and `mariadb-dump`); table counts, row counts and triggers compared |
| Database | 97 tables, 44 immutability triggers, 17 migrations |

Not verified: anything on the production server; a real payment gateway; a real SMTP provider; upload of a backup to a real S3 bucket (the request signature is verified against the AWS published example, not a live bucket); behaviour under concurrent web load; real browsers/phones (PWA install, service worker); `.github/workflows/ci.yml` (written, **never executed**).

## Modules
| Module | Built | Not proven / not built |
|---|---|---|
| Foundation: tenancy, auth, MFA, RBAC, audit chain, encryption, queue, mail A/B | Yes, isolation suite | Real SMTP provider |
| Platform admin, onboarding, presets, settings, branding | Yes | |
| CRM: clients, prospects, custom fields, consents, health, documents, agreements, data requests, retention | Yes | Malware scanning (D-15); free-text reasons in contract events and check-ins are not erasable |
| Public enquiry page `/join/{slug}` | Yes (off by default per organisation; honeypot, throttle, opt-in consent, no enumeration) | No CAPTCHA |
| Memberships: plans, versions, contracts, freezes, cancellation, renewals | Yes | |
| Attendance: QR check-in, access policy, overrides | Yes | Offline check-in (D-07, later) |
| Classes & bookings: schedules, waitlists, credits, no-shows | Yes | |
| Billing: invoices, payments, allocations, credit notes, refunds, recurring billing, dunning, provider webhooks | Yes, gap-free numbering, immutability triggers, four-eyes approvals | **No real gateway adapter**: only the generic signed-webhook interface (D-06) |
| Finance: expenses, reconciliation | Yes | |
| Inventory & POS: products, stock, suppliers, purchase orders, sales, refunds | Yes | No card terminal integration |
| Training: trainers, programmes, assessments | Yes | |
| Operations: shifts, commissions, equipment and maintenance | Yes | |
| Communications: templates, announcements, outbox, unsubscribe | Yes | Bounce tracking needs a bounce channel |
| Reports & exports | Yes | |
| Member portal + PWA shell | Yes (invite, link, own data only) | Not tested in real browsers; no offline writes by design |
| Backups & restore: native + mysqldump, encrypted archive, GFS retention, directory/S3 destinations, restore-test job | Yes | S3 not exercised live |
| Installer: web setup page `public/install.php`, plus CLI `install` / `upgrade` / maintenance mode / grants / demo seeder | Yes. The setup page was run end to end against an empty scratch MariaDB and produced a working install | Not run on a real cPanel host. cPanel database users usually cannot grant, so expect the "least privilege not applied" notice until accounts are split |

## Bugs found and fixed along the way (selection)
Migrator version types · SMTP recipient rejection misclassified as retryable · tenant-scope guard regex backtracking · least-privilege user could not see migrations · `Gate` accepted arbitrary scopes · stale table-level grants survived (now revoked per table) · visibility scope leak for branch-limited staff · status column too short in migration 0010 (dev migration reset before it was applied anywhere real) · FLUSH PRIVILEGES not permitted for the migrator (removed) · route param with dots.

## Decisions taken without waiting for answers (change on request)
Dev DB on port 3307 (portable zip) · password policy 12–128 chars, Argon2id 64 MiB/4 · sessions 30 min idle / 12 h absolute · login backoff after 5 failures, 30 per IP per 15 min · unknown mail outcomes never auto-resent · audit IP stored as keyed hash plus prefix · `billing.self_approve_limit` defaults to 0, so even a single owner cannot approve their own refund or expense until they raise it · public enquiry page off until an organisation enables it.

## Next
1. Answers to D-01 (server versions), D-03 (domains), D-04 (DB users), D-06 (payment provider): these decide what is built next (a real gateway adapter) and how the server is prepared.
2. Run the runbook in `docs/RUNBOOK-INSTALL-UPGRADE.md` on a staging copy of the real server, then run the backup runbook's restore test there.
3. Browser/phone testing of the portal and PWA.
4. Push to GitHub (or another host) and run the CI workflow for the first time.
