# CYDM — Master Development Plan

> **Status:** Living document. Updated as decisions change.
> **Owner:** Dismas Ndadilla · **Started:** 16 August 2026
> **Companion docs:** [ENVIRONMENT.md](ENVIRONMENT.md) · [ROADMAP.md](ROADMAP.md) · [PROGRESS.md](PROGRESS.md) · [TESTING.md](TESTING.md) · [DESIGN.md](DESIGN.md)

---

## 1. What we are building

**CYDM** (Community Youth Development Microfinance) is a **multi-tenant SaaS microfinance core banking platform** for Tanzania. Each institution — an MFI, a SACCOS, or a Tier-4 community group — gets its own subdomain (`akiba.cydm.co.tz`), its own branding inside a fixed blue/orange design language, and complete data isolation, while sharing one codebase, one database and one deployment.

The system must be **regulator-ready on day one**: the Bank of Tanzania (BoT) and TCDC prescribe specific forms, ratios, classifications and audit trails. Those are not "phase 4 polish" — they shape the data model, so they are designed in from the first migration.

### The three planes

| Plane | Host | Who uses it | What it does |
|---|---|---|---|
| **Central / Marketing** | `cydm.co.tz` | Prospects | Landing, pricing, self-service signup |
| **Platform Admin** | `admin.cydm.co.tz` | CYDM staff | Tenant CRUD, billing, impersonation, health |
| **Tenant App** | `{slug}.cydm.co.tz` | MFI staff + members | The actual microfinance system |

---

## 2. Confirmed technical decisions

These supersede anything in the older spec documents where they conflict. Each carries its reasoning so it can be revisited intelligently.

| Area | Decision | Reasoning |
|---|---|---|
| **Framework** | Laravel **12.x** | Laravel 13 requires **PHP ^8.4**; Laradock runs PHP 8.3.33 and is shared with ~10 other projects. See §2.0. |
| **Frontend** | Inertia + **React 19** + TypeScript (Laravel React starter kit) | SPA feel without a separate API surface to secure. Type safety matters for money code. |
| **UI** | **shadcn/ui** + Tailwind CSS v4, CSS-variable mode | Per-tenant theming requires CSS variables, not compiled classes. |
| **Database** | **MySQL 8.4** — database `cydm` | Already running in Laradock with drivers installed. See §2.1 for why not Postgres, and the migration path. |
| **Multi-tenancy** | **stancl/tenancy v3.10.x**, single database, `tenant_id` + `BelongsToTenant` | v3.10.1 supports Laravel 10–13. **v4 is not released — it is work-in-progress**, so the earlier spec's "v4" was aspirational. |
| **Cache / Queue** | Redis (Laradock `redis` service) + Horizon | Tenant-prefixed keys, tenant-aware jobs. |
| **Auth** | Laravel session auth + Sanctum (API) + TOTP 2FA | 2FA is mandatory per BoT guidelines. |
| **Permissions** | spatie/laravel-permission, team-scoped by tenant | Roles must not leak across tenants. |
| **Money** | Integer **cents**, `bigint`, never float | Non-negotiable. Rounding drift is a regulatory finding. |
| **Testing** | **Pest 4** (unit, feature, browser) | Pest 4 ships real browser testing, so UI is verified for real, not assumed. |
| **AI tooling** | Laravel **Boost** (dev) + Laravel **AI SDK** | Boost gives agents accurate context and `.ai/rules`. AI SDK is scoped narrowly — see §2.2. |
| **Timezone / Currency** | `Africa/Dar_es_Salaam`, TZS | Regulatory requirement. |
| **Languages** | Swahili + English, both first-class | Consumer-protection requirement, not a nice-to-have. |

### 2.0 Why Laravel 12, not 13

The original plan targeted Laravel 13. The framework's own release-notes table lists Laravel 13 as supporting "PHP 8.3 – 8.5", but **that table is wrong**: `laravel/framework` v13 declares `"php": "^8.4"` in its actual Composer constraint. The upgrade was attempted and Composer rejected it outright:

```
laravel/framework[dev-master, 13.0.x-dev] require php ^8.4
  -> your php version (8.3.33) does not satisfy that requirement
```

Getting to PHP 8.4 would mean either bumping `PHP_VERSION` in Laradock's shared `.env` and rebuilding `workspace`, `php-fpm` and `php-worker` — which affects **every other project on this machine** (paisha, tanpesa, eventyetu, africraft, fincacrm and others) — or standing up parallel 8.4 workspace and php-fpm services. Neither is something to do unattended on a shared environment.

The deciding factor was checking what we would actually lose. Nothing:

| Package we need | Requirement | Laravel 12? |
|---|---|---|
| `laravel/ai` (the AI SDK) | `php ^8.3`, `illuminate/* ^12.0\|^13.0` | ✅ |
| `laravel/boost` | Laravel 10, 11, 12, 13 | ✅ |
| `stancl/tenancy` v3.10 | `illuminate/support ^10\|^11\|^12\|^13` | ✅ |
| `spatie/laravel-permission` | Laravel 10+ | ✅ |

Laravel 12 receives **security fixes until February 2027**. Its bug-fix window closed on 13 August 2026, which is the one real cost — we carry that until we upgrade.

**Upgrade path**, once PHP 8.4 is available and other projects have been checked for compatibility:

```bash
# In laradock/.env
PHP_VERSION=8.4

docker compose build workspace php-fpm php-worker
docker compose up -d workspace php-fpm php-worker

# Then, in /var/www/cydm
composer require "laravel/framework:^13.0" --update-with-all-dependencies
```

Laravel 13 is explicitly a minimal-breaking-change release, so the application code should need little or no modification. Nothing in this build depends on Laravel 13-only APIs — that constraint is recorded as a project rule.

### 2.1 Why MySQL, and the Postgres path

The architecture spec called for PostgreSQL 16 (JSONB, row-level security, `pgvector`). The running Laradock environment tells a different story:

- The `postgres` container is **not running**; only `mysql` (8.4) is.
- `PHP_FPM_INSTALL_PGSQL=false`, `PHP_WORKER_INSTALL_PGSQL=false`, `WORKSPACE_INSTALL_PG_CLIENT=false` — the PHP-FPM and worker containers have **no Postgres driver**. Enabling it means editing `.env` and rebuilding three images.
- The user pointed explicitly at `laradock/mysql` for database creation.

MySQL 8.4 covers everything the first several phases need: JSON columns for `branding`/`settings`, generated columns, CTEs, window functions for ageing reports, and `utf8mb4` for Swahili text.

**What we give up, and when it will matter:**

| Postgres feature | Needed for | Mitigation now |
|---|---|---|
| Row-Level Security | Defence-in-depth tenant isolation | Global scopes + a CI grep banning `withoutGlobalScope`, plus isolation tests |
| `pgvector` | Semantic search over loan documents | Deferred; Meilisearch handles lexical search |
| Native `JSONB` indexing | Fast queries into `settings` | MySQL generated columns + indexes where needed |

**Migration trigger:** when RLS or vector search becomes a real requirement. Because every table carries `tenant_id` and all access goes through Eloquent, the switch is a driver change plus migration rewrites — not an application rewrite. Recorded as a project rule so agents keep it portable (no MySQL-only SQL in application code).

### 2.2 Where AI is and is not used

The Laravel AI SDK is installed, but deliberately kept **off the money path**. No AI decides a loan outcome, computes a balance, or classifies a loan — those are deterministic, auditable, and regulator-inspected.

**Sanctioned uses:**
- Drafting Swahili/English notification and letter copy for staff to review before sending.
- Extracting structured fields from uploaded documents (payslips, business records) as a *suggestion* a human confirms.
- Natural-language querying of reports for internal staff.

**Prohibited uses (recorded as a project rule):** credit decisions, interest/schedule computation, loan classification, provisioning, GL postings, anything appearing in a BoT return.

---

## 3. Build strategy

### 3.1 Principles

1. **Vertical slices, not layers.** Each slice ships migration → model → service → controller → page → test → browser verification. No half-built modules.
2. **Regulatory shape first.** Field lists come from the regulations, not from guesses. A missing `nida_number` column later is a data migration across every tenant.
3. **Money is tested before it is trusted.** Every calculator gets property-based tests and worked examples checked against hand calculations.
4. **Tenant isolation is proven, not assumed.** Every module adds a test that tenant B cannot see tenant A's rows.
5. **The browser is the acceptance test.** A feature is done when the page renders and the flow completes in a real browser.
6. **Document as we go.** `PROGRESS.md` is updated per slice; feature docs are written before the feature is built.

### 3.2 Definition of done (per slice)

- [ ] Migration + model with `BelongsToTenant` where applicable
- [ ] Service/action class holding the business logic (controllers stay thin)
- [ ] Form request validation, in both languages
- [ ] Inertia page using the design system — no ad-hoc colours
- [ ] Pest feature test covering happy path + at least two failure modes
- [ ] Tenant-isolation test
- [ ] Browser check: page renders, flow completes, screenshot captured
- [ ] `php artisan test` green
- [ ] Audit-log entry emitted for any state change
- [ ] Feature doc updated, `PROGRESS.md` appended, git commit

---

## 4. Phased plan

### Phase 0 — Environment & scaffolding *(in progress)*

| # | Step | Detail |
|---|---|---|
| 0.1 | Documentation plan | This document, plus the restructured `docs/` tree |
| 0.2 | Git | `git init`, `.gitignore`, baseline commit of specs |
| 0.3 | Database | `cydm` database + grants in Laradock MySQL |
| 0.4 | Nginx | `laradock/nginx/sites/cydm.conf` → `cydm.test` + `*.cydm.test` |
| 0.5 | Hosts | `cydm.test`, `admin.cydm.test`, `demo.cydm.test` → 127.0.0.1 |
| 0.6 | Laravel 13 | React starter kit, merged into the existing folder |
| 0.7 | `.env` | `DB_HOST=mysql`, `REDIS_HOST=redis`, `APP_URL=http://cydm.test`, TZ |
| 0.8 | Boost + AI SDK | `boost:install` with guidelines, skills, MCP |
| 0.9 | Core packages | tenancy, permission, sanctum, horizon, pest |
| 0.10 | Vite | Bind `0.0.0.0:5173` for HMR through Docker |

**Exit criteria:** `http://cydm.test` serves the Laravel welcome page; `php artisan about` shows MySQL and Redis connected; `php artisan test` passes.

### Phase 1 — Foundation *(the first shippable product)*

| # | Slice | Contents |
|---|---|---|
| 1.1 | Design system | OKLCH blue/orange tokens, light + dark, shadcn components, `BrandLogo` |
| 1.2 | Tenancy core | `tenants`, `domains`, `BelongsToTenant`, subdomain middleware, isolation tests |
| 1.3 | Auth | Login, register, password reset, email verification, **TOTP 2FA**, password policy (complexity, 90-day expiry, history of 5, lockout after 5) |
| 1.4 | RBAC | 8 seeded roles, permission matrix, policies, `usePermissions` hook |
| 1.5 | App shell | Sidebar, breadcrumbs, user menu, dark mode, SW/EN switcher, notification bell |
| 1.6 | Dashboards | Role-aware KPI tiles + charts from seeded demo data |
| 1.7 | Audit log | Immutable append-only log, before/after values, actor/IP/device |
| 1.8 | Settings | Institution profile, numbering series, holiday calendar, branding editor |

**Exit criteria:** a user registers, verifies email, enrols 2FA, logs in, lands on a role-appropriate dashboard, switches to Swahili and dark mode, and every action appears in the audit log — verified in a browser.

### Phase 2 — Members & savings

Member KYC (all Reg-2019 fields, NIDA/TIN, next of kin, documents, signature), member lifecycle, share capital, savings products, deposits/withdrawals/transfers, interest accrual and posting, statements, and the double-entry engine underneath all of it.

**Critical dependency:** the **posting engine** and chart of accounts must land before the first savings transaction. Retrofitting double-entry onto existing transactions is the single most expensive mistake available here.

### Phase 3 — Loans

Loan products, the 4-stage maker-checker workflow (submit → appraise → approve → disburse), reducing-balance schedule generation, collateral and guarantors with locking, repayment allocation (fees → penalties → interest → principal), arrears and PAR ageing, classification and provisioning, restructuring and write-off.

### Phase 4 — Money movement & compliance

Mobile money (M-Pesa, Airtel, Mixx, HaloPesa, Tigo), a unified gateway abstraction with per-provider drivers, webhook reconciliation, SMS/WhatsApp notifications, the ten BoT MSP forms, CRB submission, and AML transaction monitoring.

### Phase 5 — Platform & scale

Self-service tenant onboarding, billing and plan limits, member self-service portal, group lending, HR/payroll, fixed assets, dividends, custom report builder, public API, USSD.

*Detailed sequencing and estimates live in [ROADMAP.md](ROADMAP.md); per-feature specifications live in [`docs/features/`](features/).*

---

## 5. Cross-cutting rules

These apply to every slice and are recorded as Boost project rules under `.ai/rules/` so both humans and agents inherit them.

### Money
- Stored as **integer cents** in `bigint` columns. Never float, never decimal-as-string.
- All arithmetic goes through `App\Support\Money`.
- Rounding is explicit and documented per operation; banker's rounding for interest.

### Tenancy
- Every business table has `tenant_id` with a composite index `(tenant_id, …)`.
- Every business model uses `BelongsToTenant`.
- `withoutGlobalScope` is **banned** in application code; CI greps for it.
- Queued jobs re-resolve the tenant from their payload — never from request state.
- Uniqueness is always scoped: `UNIQUE (tenant_id, member_number)`, never `UNIQUE (member_number)`.

### Accounting
- Every financial transaction produces balanced journal entries in the same database transaction.
- Ledgers are append-only. Corrections are reversing entries, never updates or deletes.
- The trial balance must always balance; a scheduled job asserts this and alerts on drift.

### Security & audit
- Every state change writes an audit record with actor, timestamp, IP, device, before/after.
- PII (NIDA, phone, address) is encrypted at rest and masked in logs.
- Authorisation is enforced by policies, never by hiding UI elements alone.

### Data & i18n
- All user-facing strings live in translation files — Swahili and English, both complete.
- Dates render `DD/MM/YYYY`; times in `Africa/Dar_es_Salaam`.
- Financial records retained 7 years minimum.

---

## 6. Risk register

| Risk | Impact | Mitigation |
|---|---|---|
| Tenant data leak | Existential — regulatory and reputational | Global scopes, isolation tests per module, CI grep, code review on any scope bypass |
| Rounding drift in interest | Regulatory finding, member disputes | Integer cents, worked-example tests, daily reconciliation job |
| Mobile-money sandbox access delays | Blocks Phase 4 | Driver abstraction + fake driver, so the flow is built and tested before credentials arrive |
| NIDA / CRB API access requires licensing | Blocks e-KYC | Interface + manual-verification fallback; the workflow works without the API |
| MySQL instead of Postgres | Limits RLS / vector search | Keep SQL portable, no MySQL-only syntax in app code; documented migration trigger |
| stancl/tenancy v4 never lands | Architecture drift | v3.10 is stable and Laravel 13 compatible; nothing depends on v4 |
| Scope is very large | Never shipping | Vertical slices; Phase 1 alone is a usable product |

---

## 7. Environment quick reference

Full detail in [ENVIRONMENT.md](ENVIRONMENT.md).

```bash
# From Windows PowerShell
cd "C:\Users\dismas\Documents\DEVELOPMENT ENV\laradock"
docker compose exec workspace bash

# Inside the container
cd /var/www/cydm
```

| Service | Host inside Docker | Port from Windows |
|---|---|---|
| App | `http://cydm.test` | 80 |
| MySQL | `mysql` | 3306 |
| Redis | `redis` | 6379 |
| Vite HMR | `0.0.0.0:5173` | 5173 |

---

## 8. How progress is tracked

- **[PROGRESS.md](PROGRESS.md)** — append-only log, one entry per completed slice: what was built, what was tested, what judgement calls were made.
- **[ROADMAP.md](ROADMAP.md)** — the forward plan with sequencing.
- **Git** — one commit per slice, message referencing the slice number.
- **Task list** — live in-session tracking.

---

*Supersedes the stack claims in `TECHNICAL_ARCHITECTURE.md` where they conflict (Postgres → MySQL, tenancy v4 → v3.10). Those documents are folded into `docs/ARCHITECTURE.md` and `docs/COMPLIANCE.md` during Phase 0.*
