# 02 — Module Dependency Map

An arrow `A → B` means A depends on B (B must exist first). Modules may only call a dependency's **service interface**, never its tables.

## Layers
```
L0 Core kernel   : router, container, config, DB/migrator, logger, errors, view/CSRF/validation, queue, crypto, clock, ids
L1 Identity/tenant: M20 Audit(core) · M03 IAM · tenancy (TenantContext) · M17a Mail transport+queue+template engine (design-review change: later phases need invoice, renewal and booking emails before P8)
L2 Tenant setup  : M01 Platform admin · M02 Config/branding
L3 People        : M04 CRM/clients · M07 Trainers
L4 Products      : M05 Memberships · M09 Classes/bookings · M08 Programmes
L5 Money         : M10 Billing → M11 Recurring/provider → M12 Refunds/credit/expenses/recon
L6 Commerce      : M14 Inventory → M13 POS
L7 Operations    : M06 Attendance (needs M05, M10 status) · M15 Staff · M16 Facilities
L8 Experience    : M17b Templates/history · M18 Portal/PWA · M19 Reports/exports
L9 Resilience    : M21 Backups · M22 Diagnostics/deploy/installer
```

## Dependency table
| Module | Depends on | Notes |
|---|---|---|
| Core | — | No business logic |
| M20 Audit | Core | Writer is available to every module |
| M03 IAM | Core, M20, M17a | Invitations, verification and reset need mail |
| M17a Mail/queue | Core, M20 | SMTP A→B failover, retries |
| M01 Platform | M03, M20 | Separate platform realm |
| M02 Config/branding | M03, M20 | Presets, theme publish/rollback |
| M04 CRM | M02, M03, M20, crypto | Custom fields, consents, documents |
| M07 Trainers | M03, M04 | Assigned-client scope feeds M03 authz |
| M05 Memberships | M04, M02 | Contract snapshots, freezes |
| M09 Bookings | M04, M05, M07, M16(optional rooms) | Entitlement check via M05 |
| M08 Programmes | M04, M07 | Health-class data uses encryption |
| M10 Billing | M04, M05, M02 (tax/currency) | Immutable issued snapshots |
| M11 Recurring | M10, M17a | Provider webhooks idempotent |
| M12 Fin ops | M10, M11 | Refund approvals via M03 limits |
| M14 Inventory | M02 | Stock movements append-only |
| M13 POS | M14, M10, M12 | Sales produce invoices/payments |
| M06 Attendance | M04, M05, M10 (status read) | Access decision = membership + payment policy |
| M15 Staff | M03, M13 (commission source) | |
| M16 Facilities | M02, M15 | |
| M17b Templates | M17a, M02 | Branded, validated placeholders |
| M18 Portal/PWA | M03, M04, M05, M09, M10 | Read/write via same services, member-scoped |
| M19 Reports | all data modules | Read-only; per-permission exports |
| M21 Backups | Core, M22 | Independent of mail failover |
| M22 Diagnostics | Core, M20 | Installer/upgrader last |

## Build order (matches [10-backlog.md](10-backlog.md))
P1 Core, M20, M03, M17a, tenancy, isolation tests → P2 M01, M02 → P3 M04 → P4 M05, M06 → P5 M07, M09, M08 → P6 M10, M11, M12 → P7 M14, M13 → P8 M17b → P9 M18 → P10 M15, M16, M19 → P11 M21, M22.

Note: M06 (P4) reads invoice/payment status from M10 (P6). Until P6, the access policy supports only membership status; the payment-status condition is stubbed behind an interface with a "no financial hold" implementation, and wired in P6. This is a deliberate, documented seam, not a fake.

## Forbidden dependencies
- No module reads another module's tables directly.
- Lower layers never import higher layers (L1 cannot call L5).
- Reports (M19) are the only consumers allowed multi-module read queries, via dedicated read-model repositories, still tenant-scoped.
