# Cross-Module Dependency Graph

> **Generated:** 2026-06-11
> **Source:** 75 architecture maps in `/docs/architecture-maps/`
> **Purpose:** Single source of truth for cross-module relationships

---

## 1. Module Dependency Matrix

### 1.1 Core Infrastructure Modules (depended upon by 10+ modules)

| Module | Depended Upon By | Classification |
|--------|-----------------|----------------|
| **Members** | Children, Spouses, Temporary, Death, Divorce, Waiver, Transfers, Subscriptions, Installments, Payments, Cashier, Carnets, Foreign, Honorary, Seasonal, Fines, Interviews, Archive, Forms, Dashboard, Reports, Documents, Accounting, Notifications, PlayerAffairs | Hub (25+) |
| **Payments** | Cashier, Members, Installments, Fines, ActivitySubscriptions, Foreign, Death, Divorce, Waiver, Transfers, Accounting, Treasury, Subscriptions, Sales, Inventory | Hub (15+) |
| **Cashier** | Members, Children, Spouses, Temporary, Death, Divorce, Waiver, Transfers, Carnets, Seasonal, SportsActivity, Treasury, Audit | Hub (13+) |
| **Branches** | Members, Accounting, Cashier, HR, Pricing, Rules, Inventory, Carnets, Death, Divorce, Foreign, Children, Dashboard, Payments | Hub (14+) |
| **Rules** | Children, Spouses, Temporary, Fines, Installments, Carnets, Foreign, Honorary, Seasonal, Death, Divorce, Waiver, Transfers, Pricing | Hub (14+) |
| **Pricing** | Children, Spouses, Temporary, Carnets, Members, Transfers, Seasonal | Hub (7+) |
| **Disciplines** | Academies, Facilities, Coaches, ActivitySubscriptions, SportsDashboard | Hub (5+) |
| **Coaches** | Academies, AcademyContracts, ActivitySubscriptions, FacilityDashboards, FacilityGrids, PlayerAffairs, PlaygroundAdmin, PoolManagement, Reservations, Scheduling, Sessions, SportsActivity, SportsDashboard, TrainerPortal, Training, TrainingGroups | Hub (16+) |
| **Accounting** | Sales, Inventory | Hub (indirect via events: 10+ modules) |

### 1.2 Module Import Matrix (who imports from whom)

| Module | Imports From |
|--------|-------------|
| **Academies** | Disciplines, Facilities |
| **AcademyContracts** | Academies |
| **AccessMatrix** | Core (PermissionRegistry, MenuRegistry) |
| **Accounting** | Payments, Inventory |
| **Achievements** | Core (EventBus, Logger) |
| **ActivitySubscriptions** | Payments, Sales, Disciplines, Academies, PlayerAffairs |
| **Alerts** | (SQL only: PlayerAffairs, Academies, SportsActivity, ActivitySubscriptions) |
| **Archive** | (SQL only: Members, Spouses, Children, Temporary, Payments, Installments, Subscriptions, Fines, Documents, Seasonal, SportsActivity, Honorary, Foreign, HR, Settings) |
| **Audit** | Core (Database hooks), Cashier (PaymentRequestService label) |
| **Auth** | Users (Employee model), Core (MenuRegistry) |
| **Carnets** | Pricing, Rules, Cashier, Fines, Members |
| **Cashier** | Payments, Treasury |
| **Children** | Members (NationalIdParser, FormFeeService), Rules, Pricing, Cashier, Forms, Shared (PhotoUploadService) |
| **Coaches** | Members (NationalIdParser) |
| **Dashboard** | (SQL only: Members, Payments, Interviews, Installments, Branches, Audit, Children, Forms, Honorary) |
| **Death** | Members, Archive, Payments, Cashier, Rules, ServiceCatalog |
| **Disciplines** | (none - self-contained) |
| **Divorce** | Members, Archive, Payments, Cashier, Rules, ServiceCatalog |
| **Documents** | Members (validation) |
| **Facilities** | Disciplines |
| **FacilityDashboards** | Facilities, Reservations, Coaches, SportsActivity, PlayerAffairs |
| **FacilityGrids** | Facilities, Coaches, SportsActivity, PlayerAffairs, Reservations, PoolManagement |
| **Fines** | Payments, Rules, Workflow |
| **Foreign** | Members (MembershipRulesService — type validation), Rules, Payments |
| **Forms** | Core (Validator), DB lookups (governorates, countries, qualifications, branches) |
| **Honorary** | Members (MemberNumberGenerator — activation), Rules |
| **HR** | Users (Employee model) |
| **Installments** | Payments, Rules, Members (MemberNumberGenerator, BoardOfferService) |
| **Interviews** | Members, Workflow, Forms |
| **Inventory** | Accounting (JournalServiceAdapter) |
| **Members** | (self-contained core) |
| **Payments** | (self-contained processor) |
| **Sales** | Accounting (JournalServiceAdapter), Inventory |
| **Seasonal** | Rules, Cashier, Pricing |
| **Spouses** | Members (NationalIdParser, FormFeeService), Rules, Pricing, Cashier, Forms |
| **Subscriptions** | Payments, Rules, ServiceCatalog |
| **Temporary** | Members, Rules, Pricing, Cashier, Forms |
| **Transfers** | Members (MemberNumberGenerator), Archive, Payments, Cashier, Rules, ServiceCatalog, Pricing |
| **Waiver** | Members (MemberNumberGenerator), Archive, Payments, Cashier, Rules, ServiceCatalog |

---

## 2. Event Bus Map

### 2.1 Payment & Financial Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `payment.completed` | Payments/PaymentService | Accounting (journal entry), Treasury (session recording) |
| `payment.voided` | Payments/PaymentService | Accounting (reverse journal), Cashier (PaymentLifecycleService) |
| `payment_request.created` | Cashier/PaymentRequestService | (no external listeners) |
| `payment_request.completed` | Cashier/PaymentRequestService | Cashier/bootstrap (post-payment orchestration) |
| `payment_request.cancelled` | Cashier/PaymentRequestService | Cashier/bootstrap (PaymentLifecycleService) |
| `installment_plan.created` | Installments/Controller | Accounting (create AR) -- **NAME MISMATCH: Accounting listens for `installment.plan_created`** |
| `installment.paid` | Installments/Controller | Accounting (update AR) |
| `subscription.paid` | Subscriptions | Accounting (update AR) |
| `activity_sub.generated` | ActivitySubscriptions | (no listeners) |
| `activity_sub.paid` | ActivitySubscriptions | PlayerAffairs (activate player card) |
| `activity_sub.revoked` | ActivitySubscriptions | (no listeners) |
| `activity_sub.exempted` | ActivitySubscriptions | (no listeners) |

### 2.2 Member Lifecycle Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `member.activated` | Cashier/bootstrap (all membership types), Installments/ChequeService, Honorary/Controller | Notifications |
| `member.pending_cheques` | Cashier/bootstrap | (no listeners) |
| `member.dropped` | Installments/DefaultChecker | Accounting (write off AR) |
| `child.added` | Children/Controller | (no listeners) |
| `child.removed` | Children/Controller | (no listeners) |
| `child.frozen` | Children/Controller | (no listeners) |
| `child.fee_paid` | Cashier/bootstrap | Children (activates child) |
| `spouse.fee_paid` | Cashier/bootstrap | Spouses (activates spouse) |
| `temporary.fee_paid` | Cashier/bootstrap | Temporary (activates temp member) |
| `seasonal.fee_paid` | Cashier/bootstrap | Seasonal (activates seasonal) |

### 2.3 Transfer/Death/Divorce Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `death.recorded` | Death/Controller | (no listeners) |
| `death.fee_paid` | Death/Controller | Cashier (status update) |
| `death.completed` | Death/Controller | (no listeners) |
| `divorce.submitted` | Divorce/Controller | (no listeners) |
| `divorce.board_approved` | Divorce/Controller | (no listeners) |
| `divorce.fee_paid` | Divorce/Controller | Cashier (status update) |
| `divorce.completed` | Divorce/Controller | (no listeners) |
| `transfer.fee_paid` | Cashier/bootstrap | Transfers (status update) |
| `transfer.completed` | Transfers | Accounting (Dr. AR, Cr. Transfer Revenue) |
| `waiver.fee_paid` | Cashier/bootstrap | Waiver (status update) |
| `waiver.completed` | Waiver | Accounting (Dr. AR, Cr. Waiver Revenue) |

### 2.4 Sales & Inventory Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `sale.completed` | Sales | Accounting (COGS entry), DailyCashMovementService, StatementIntegrationService |
| `sale.voided` | Sales | Accounting (reverse COGS) |
| `sale.refunded` | Sales | Accounting (Dr. Sales Revenue, Cr. Cash) |

### 2.5 HR Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `hr.payroll.paid` | HR/PayrollController | Accounting (journal: Dr. Salary+Insurance, Cr. Bank+Tax), HR/bootstrap (SMS) |
| `hr.leave.submitted` | HR/LeaveService | (no external listeners) |
| `hr.leave.approved` | HR/LeaveService | HR/bootstrap (SMS) |
| `hr.leave.rejected` | HR/LeaveService | HR/bootstrap (SMS) |
| `hr.loan.approved` | HR/LoanService | HR/bootstrap (SMS) |
| `hr.contract.renewed` | HR/ContractService | (no external listeners) |
| `hr.contract.terminated` | HR/ContractService | (no external listeners) |
| `hr.attendance.recorded` | HR | HR/bootstrap (auto-detect violations) |
| `attendance.violations_detected` | HR/AttendanceViolationService | (no external listeners) |

### 2.6 Fines & Violations Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `violation.reported` | Fines/ViolationController | (no listeners) |
| `fine.imposed` | Fines/FineController | Accounting (create AR + journal), StatementIntegrationService |
| `fine.paid` | Fines/FineController | Accounting (settle AR, recognize revenue) |
| `fine.appealed` | Fines/FineController | (no listeners) |

### 2.7 Sports & Academy Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `academy.enrollment_created` | Academies/EnrollmentService | (no listeners) |
| `academy.enrollment_promoted` | Academies/EnrollmentService | (no listeners) |
| `academy.enrollment_suspended` | Academies/EnrollmentService | (no listeners) |
| `academy.enrollment_dropped` | Academies/EnrollmentService | (no listeners) |
| `academy.contract.activated` | AcademyContracts | (no listeners) |
| `academy.settlement.approved` | AcademyContracts | (no listeners) |
| `coach.rated` | Coaches/CoachRatingService | (no listeners) |
| `coach.payment.approved` | Coaches/CoachPaymentService | (no listeners) |
| `player.achievement_earned` | Achievements/AchievementEngine | ParentPortal (notification), Notifications (trigger) |
| `waitlist.spot_offered` | Disciplines/WaitlistService | (no listeners) |
| `waitlist.spot_accepted` | Disciplines/WaitlistService | (no listeners) |

### 2.8 Facility Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `facility.entry_recorded` | Facilities/AccessControlService | Accounting (Dr. Cash, Cr. Facility Revenue) |
| `facility.attendance_absent` | FacilityGrids/AttendanceService | (no listeners) |
| `facility.payment_reminder` | FacilityGrids/AttendanceService | (no listeners) |

### 2.9 Carnet Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `carnet.issued` | Carnets/Controller | (no listeners) |
| `carnet.printed` | Carnets/Controller | (no listeners) |
| `carnet.deactivated` | Carnets/Controller | (no listeners) |
| `carnet_guest.entry_recorded` | Carnets/GuestEntryService | Notifications -- **NAME MISMATCH: Accounting listens for `carnet.guest_entry_recorded`** |
| `carnet.low_balance` | Carnets/GuestEntryService | Notifications |

### 2.10 Procurement Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `procurement.invoice_approved` | Procurement | Accounting (Dr. Inventory+Tax, Cr. AP) |
| `procurement.payment_completed` | Procurement | Accounting (Dr. AP, Cr. Cash/Bank), DailyCashMovementService |
| `procurement.payment_voided` | Procurement | Accounting (reverse journal + revert AP) |
| `procurement.rtv_completed` | Procurement | Accounting (Dr. AP, Cr. Inventory) |

### 2.11 Treasury Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `treasury.deposit.created` | Cashier/DepositService | (no listeners) |
| `treasury.deposit.confirmed` | Cashier/DepositService | Accounting (Dr. Bank, Cr. Main Cash) |
| `treasury.deposit.rejected` | Cashier/DepositService | (no listeners) |
| `treasury.settlement.received` | Cashier/SettlementReceiveController | Accounting (Dr. Main Cash, Cr. Sub Cash) |

### 2.12 Auth Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `auth.login` | Auth/AuthController | Audit (log login) |
| `auth.logout` | Auth/AuthController | Audit (log logout) |
| `auth.password_changed` | Auth/AuthController | Audit (log change) |

### 2.13 Other Events

| Event | Dispatcher | Listeners |
|-------|-----------|-----------|
| `branch.created` | Branches/Model | (no listeners) |
| `branch.updated` | Branches/Model | (no listeners) |
| `document.uploaded` | Documents/Controller | (no listeners) |
| `document.archived` | Documents/Controller | (no listeners) |
| `form.submitted` | Forms/Controller, FormBridge | (no listeners) |
| `foreign.registered` | Foreign/Controller | (no listeners) |
| `honorary.created` | Honorary/Controller | (no listeners) |
| `interview.scheduled` | Interviews/Controller | (no listeners) |
| `interview.decided` | Interviews/Controller | (no listeners) |
| `rental.deposit_collected` | Rentals | Accounting (reclassify to deposit liability) |
| `rental.deposit_refunded` | Rentals | Accounting (reverse reclassification) |
| `tournament.fee_collected` | Tournaments | Accounting (Dr. Cash, Cr. Tournament Revenue) |
| `member.profile_data` | Members | Documents (inject document list) |

---

## 3. Shared Database Tables

### 3.1 Tables Written By Multiple Modules

| Table | Owner Module | Also Written By | Operation |
|-------|-------------|-----------------|-----------|
| `members` | Members | Interviews (status update), Death (archive + create), Divorce (create), Waiver (archive + create), Transfers (create), Installments/ChequeService (activate), Cashier/bootstrap (activate), Foreign (membership_type), Honorary (membership_type), Fines (status: suspend/terminate) |
| `activity_subscriptions` | ActivitySubscriptions | PlayerAffairs/EventListeners (INSERT/UPDATE directly) |
| `payments` | Payments | (all via PaymentService - single writer) |
| `payment_requests` | Cashier | (all via PaymentRequestService - single writer) |
| `children` | Children | Death/Controller (reassignment), Members/AutoFreezeService (classification change) |
| `spouses` | Spouses | Death/Controller (archive), Divorce/Controller (archive) |
| `role_permissions` | Auth/Settings | AccessMatrix (INSERT/DELETE for toggle) |

### 3.2 Tables Read By Multiple Modules

| Table | Owner Module | Read By |
|-------|-------------|---------|
| `members` | Members | Almost every module (20+) |
| `payments` | Payments | Dashboard, Accounting, Cashier, Installments, Reports, Members, Archive, Treasury |
| `branches` | Branches | Members, Accounting, Cashier, HR, Pricing, Rules, Inventory, Dashboard, Reports |
| `coaches` | Coaches | 16+ modules (Academies, FacilityGrids, SportsActivity, TrainerPortal, etc.) |
| `players` | PlayerAffairs | Achievements, FacilityGrids, Disciplines, ActivitySubscriptions, SportsActivity |
| `sport_disciplines` | Disciplines | Academies, Facilities, Coaches, ActivitySubscriptions, Achievements |
| `facilities` | Facilities | Reservations, Rentals, FacilityDashboards, FacilityGrids, SportsActivity, PlayerApi |
| `training_groups` | SportsActivity | Disciplines (waitlist), ActivitySubscriptions (enrollment) |
| `academy_enrollments` | Academies | AcademyContracts, ActivitySubscriptions, PlayerAffairs, Disciplines |
| `fines` | Fines | Carnets, Payments, Members, Reports, Accounting, Archive |
| `installment_plans` + `installment_schedule` | Installments | Payments, Members, Accounting, Dashboard, Carnets, Archive, Reports |
| `subscriptions` | Subscriptions | Members, Carnets, Dashboard, Accounting, Archive, Reports |
| `audit_trail` | Audit | Dashboard, Members, Reports |
| `session_attendance` | SportsActivity | Achievements, Alerts, Coaches |
| `reservations` | Reservations | FacilityDashboards, FacilityGrids/PoolFinancialService |

---

## 4. Payment Flow Chain

```
[Module Creates Obligation]
    Members, Children, Spouses, Temporary, Death, Divorce, Waiver, 
    Transfers, Carnets, Seasonal, Fines, SportsActivity
          │
          ▼
[Cashier: PaymentRequestService::createRequest()]
    Creates payment_request (status: pending)
    Dispatches: payment_request.created
          │
          ▼
[Cashier Queue: /cashier]
    Cashier employee selects request
    Sets status: processing (atomic lock)
          │
          ▼
[Treasury Path Decision]
    ├── Treasury configured → TreasuryService::collectPayment()
    │       Records in treasury session, tracks custody
    └── No treasury → PaymentRequestService::processRequest()
          │
          ▼
[Payments: PaymentService::processPayment()]
    Creates `payments` record
    Creates `receipts` record (REC-YYYY-NNNNNN)
    Dispatches: payment.completed
          │
          ▼
[Post-Payment Event Chain]
    ├── Accounting/bootstrap: auto-post journal entry (Dr. Cash/Bank, Cr. Revenue/AR)
    ├── Accounting/StatementIntegrationService: update customer statement
    ├── Accounting/DailyCashMovementService: record daily cash movement
    ├── Cashier/bootstrap: payment_request.completed handler
    │       ├── membership_fee/down_payment → MembershipPaymentGuard (activate member)
    │       ├── addition_fee → activate dependent (child/spouse/temporary)
    │       ├── seasonal_fee → activate seasonal membership
    │       ├── divorce/death/waiver/transfer fee → update case status
    │       └── dispatches: member.activated, spouse.fee_paid, child.fee_paid, etc.
    └── Treasury: record in session totals
          │
          ▼
[Void Path (if needed)]
    PaymentService::voidPayment()
    Dispatches: payment.voided
    ├── Accounting: reverse journal entry
    ├── Cashier/PaymentLifecycleService: deactivate member if applicable
    └── Updates original payment_request status
```

---

## 5. Member Lifecycle Dependencies

### 5.1 Full Member Lifecycle

```
[potential] ─── Members/MemberController::store()
     │
     ▼
[under_review] ─── Members (form fee paid via Cashier)
     │
     ▼
[interview_scheduled] ─── Interviews/InterviewController::storeSchedule()
     │
     ▼
[payment_pending] ─── Interviews/InterviewController::decide(accepted)
     │
     ├── Cash path → Cashier → Payments → MembershipPaymentGuard → [active]
     └── Installment path → Cashier → Payments → [pending_cheques]
                                                        │
                                                        ▼
                                     Installments/ChequeService → [active]
```

### 5.2 Modules Involved at Each Stage

| Stage | Modules Involved |
|-------|-----------------|
| **Registration** | Members (create), Forms (NEW_MEMBERSHIP), Cashier (form_fee) |
| **Review** | Members, Interviews, Workflow |
| **Payment** | Cashier, Payments, Pricing, Members/BillingService |
| **Activation** | Cashier/bootstrap, Members/MembershipPaymentGuard, Members/MemberNumberGenerator |
| **Dependents** | Children, Spouses, Temporary (each has own fee → Cashier → activate) |
| **Ongoing** | Subscriptions (annual fees), Fines (violations), Carnets (ID cards), Documents |
| **Separation** | Transfers, Death, Divorce, Waiver (each creates new member + archive) |
| **Termination** | Fines (termination penalty), Installments (3+ overdue → drop), Subscriptions (5yr unpaid → drop) |

### 5.3 Member Status Writers (Danger Zone)

| Module | Status Written | Bypasses Members Service? |
|--------|---------------|--------------------------|
| Cashier/bootstrap | active | No (uses MembershipPaymentGuard) |
| Installments/ChequeService | active | YES (direct update) |
| Installments/DefaultChecker | dropped | YES (direct update) |
| Interviews | payment_pending, rejected | YES (direct DB update) |
| Fines | suspended, terminated | YES (direct update) |
| Foreign | (membership_type only) | N/A |
| Honorary | (membership_type only) | N/A |
| Death | deceased, archived | YES (direct update in transaction) |
| Divorce | (spouse archived only) | N/A |
| Subscriptions/OverdueFineApplicator | dropped | YES (direct update) |

---

## 6. Critical Integration Points

### 6.1 Tightest Coupling Pairs

| Module A | Module B | Integration Surface | Risk |
|----------|----------|--------------------|----|
| **Cashier** | **Payments** | PaymentService is the ONLY payment processor; Cashier calls it for every payment | CRITICAL - if PaymentService breaks, all revenue collection stops |
| **Cashier/bootstrap** | **Members** | Post-payment orchestration: activates members, activates dependents, sets payment_pending → active | CRITICAL - if bootstrap listener fails, members never activate |
| **Accounting** | **Payments** | Every `payment.completed` creates a journal entry; every `payment.voided` reverses it | HIGH - GL integrity depends on listener not failing |
| **Members** | **Children/Spouses/Temporary** | BillingService aggregates all dependent fees; MembershipPaymentGuard activates all dependents on lump-sum payment | HIGH - fee calculation spans 4 modules |
| **ActivitySubscriptions** | **PlayerAffairs** | PlayerAffairs directly INSERTs/UPDATEs `activity_subscriptions` table, creating dual code paths | HIGH - business logic divergence risk |
| **Installments** | **Members** | ChequeService directly activates members; DefaultChecker directly drops members | HIGH - bypasses Members module's own lifecycle management |
| **Cashier** | **Treasury** | Cashier uses TreasuryService for session management, custody, settlements | MEDIUM - if Treasury is misconfigured, payments may fail |
| **Coaches** | **SportsActivity** | Coach data used by 16+ modules for scheduling, attendance, performance | MEDIUM - coach schema change would cascade widely |

### 6.2 Event-Based Critical Paths

| Event Chain | Consequence of Failure |
|------------|----------------------|
| payment.completed → Accounting listener | Journal entries not posted; GL out of sync (fail-open, logged) |
| payment_request.completed → Cashier/bootstrap | Member never activates despite payment recorded |
| hr.payroll.paid → Accounting listener | Salary expenses not recorded in GL |
| fine.imposed → Accounting listener | AR not created; fine revenue never recognized |
| member.dropped → Accounting listener | AR balance never written off |

---

## 7. Leaf Modules (No Dependents)

These modules are not imported by any other module. They can be modified in isolation with minimal risk:

| Module | Notes |
|--------|-------|
| **AccessMatrix** | Pure UI for permission management; no other module calls it |
| **Alerts** | Standalone polling system; no other module imports from it |
| **Dashboard** | Read-only aggregation; pure consumer |
| **FacilityDashboards** | Read-only analytics (legacy mirror code migrated to FacilityGrids) |
| **News** | Content management, standalone |
| **Support** | Support ticket system, standalone |
| **Tutorials** | Static tutorial content |
| **MatchCenter** | Match management, standalone |
| **MedicalBoard** | Medical board decisions, standalone |
| **ParentPortal** | Player parent interface (reads from other modules but nothing imports from it) |

---

## 8. Hub Modules (Many Dependents)

These modules have the highest change risk due to many downstream consumers:

| Module | Dependent Count | Critical Services |
|--------|----------------|-------------------|
| **Members** | 25+ | NationalIdParser, MemberNumberGenerator, BillingService, FormFeeService, MembershipPaymentGuard, AutoFreezeService |
| **Payments** | 15+ | PaymentService::processPayment(), PaymentLifecycleService, BalanceCalculator |
| **Cashier** | 13+ | PaymentRequestService::createRequest(), Post-payment orchestration in bootstrap.php |
| **Coaches** | 16+ | Coach model/table (coach_id FK in 10+ tables) |
| **Branches** | 14+ | Branch model (branch_id FK in 15+ tables) |
| **Rules** | 14+ | RuleEngine::getValue() (business rules for fees, limits, windows) |
| **Accounting** | Indirect: 10+ via events | AccountingIntegrationService (20+ event listeners), JournalService |
| **Disciplines** | 5+ | SportDiscipline model (discipline_id FK in 5+ tables) |
| **Pricing** | 7+ | PricingEngine::calculateChildFee(), getServiceFee() |
| **ServiceCatalog** | 5+ | ServicePrice model (SVC_* codes for fee lookup) |

---

## 9. Known Issues & Broken Links

### 9.1 Event Name Mismatches (CRITICAL)

| Dispatched Event | Dispatched By | Expected Listener | Actual Listener Event Name | Impact |
|-----------------|---------------|-------------------|---------------------------|--------|
| `carnet_guest.entry_recorded` | Carnets/GuestEntryService | Accounting | `carnet.guest_entry_recorded` | **Accounting never posts guest entry revenue journal** |
| `installment_plan.created` | Installments/Controller | Accounting | `installment.plan_created` | **AR may never be created for new installment plans** |

### 9.2 Orphaned Events (Dispatched But Never Consumed)

| Event | Dispatcher | Status |
|-------|-----------|--------|
| `academy.enrollment_created/promoted/suspended/dropped` | Academies | No listeners registered |
| `academy.contract.activated/terminated` | AcademyContracts | No listeners registered |
| `academy.settlement.created/approved` | AcademyContracts | No listeners registered |
| `violation.reported` | Fines | No listeners registered |
| `fine.appealed` | Fines | No listeners registered |
| `child.added/removed/frozen` | Children | No listeners registered |
| `document.uploaded/archived` | Documents | No listeners registered |
| `form.submitted` | Forms | No listeners registered |
| `interview.scheduled/rescheduled/decided` | Interviews | No listeners registered |
| `branch.created/updated` | Branches | No listeners registered |
| `foreign.registered` | Foreign | No listeners registered |
| `honorary.created` | Honorary | No listeners registered |
| `death.recorded/completed` | Death | No listeners registered |
| `divorce.submitted/board_approved/rejected/completed` | Divorce | No listeners registered |
| `coach.rated` | Coaches | No listeners registered |
| `coach.payment.approved` | Coaches | No listeners registered |
| `waitlist.spot_offered/accepted` | Disciplines | No listeners registered |
| `facility.attendance_absent` | FacilityGrids | No listeners registered |
| `facility.payment_reminder` | FacilityGrids | No listeners registered |
| `payment_request.created` | Cashier | No listeners registered |
| `member.pending_cheques` | Cashier | No listeners registered |
| `treasury.deposit.created/rejected` | Cashier | No listeners registered |
| `activity_sub.generated/revoked/exempted` | ActivitySubscriptions | No listeners registered |

### 9.3 Direct Table Access Violations

| Module | Table Accessed | Owner Module | Concern |
|--------|---------------|--------------|---------|
| PlayerAffairs | `activity_subscriptions` | ActivitySubscriptions | Direct INSERT/UPDATE bypasses service logic |
| Interviews | `members` (status) | Members | Direct UPDATE bypasses Members lifecycle |
| Fines | `members` (status) | Members | Direct UPDATE bypasses Members lifecycle |
| Installments/DefaultChecker | `members` (status) | Members | Direct UPDATE bypasses Members lifecycle |
| Subscriptions/OverdueFineApplicator | `members` (status) | Members | Direct UPDATE bypasses Members lifecycle |
| Archive | 12+ tables | Various | Direct SELECT but uses raw table names (SQL injection risk on manual snapshot) |

### 9.4 Dual Implementation Conflicts

| Feature | Implementation 1 | Implementation 2 | Risk |
|---------|------------------|------------------|------|
| Academy contracts | `AcademyContracts` module (full settlements) | `SportsActivity/AcademyContractController` at `/sa/academies/{aid}/contracts` | Two paths to same data |
| Facility management | `Facilities` module at `/facilities` | `SportsActivity/FacilityController` at `/sa/facilities` | Two management UIs for same table |
| Alert processing | `AlertProcessorService` (web) | `AlertProcessorJob` (cron) | Different SQL queries, different dedup logic |
| Activity subscriptions | `ActivitySubscriptions` module | `PlayerAffairs/EventListeners` | Two code paths writing same table |

### 9.5 Missing Cron-Based Enforcement

| Expected Behavior | Status | Module |
|-------------------|--------|--------|
| Reinstate members when suspension_to passes | NOT IMPLEMENTED | Fines |
| Expire honorary memberships on end_date | Implemented in Members/AutoFreezeService | Honorary (external) |
| Revoke overdue activity subscriptions | Service method exists, cron integration unclear | ActivitySubscriptions |
| Process waitlist offer expiry (48h) | Service method exists, cron integration unclear | Disciplines |
| Auto-generate monthly activity subscriptions | Manual trigger only | ActivitySubscriptions |

### 9.6 Permission Mismatches

| Module | Registered Permissions | Actual Route Permissions | Issue |
|--------|----------------------|--------------------------|-------|
| Archive | archive.view, archive.take_snapshot, archive.compare, archive.number_chain | report.view_audit, settings.edit | Routes use OTHER modules' permissions; own permissions are unused |
| Death | (none) | transfer.view, transfer.initiate, transfer.approve | Reuses Transfers permissions |
| Divorce | (none) | transfer.view, transfer.initiate, transfer.approve | Reuses Transfers permissions |

---

## 10. Module Communication Patterns Summary

### 10.1 Communication Types Used

| Pattern | Modules Using It | Coupling Level |
|---------|-----------------|----------------|
| **Direct Service Import** | Cashier→Payments, Children→Pricing, Death→Archive | Tight |
| **EventBus (fire-and-forget)** | Payments→Accounting, Fines→Accounting, HR→Accounting | Loose |
| **Direct SQL (cross-table query)** | Dashboard (10 tables), Alerts (8 tables), Disciplines/SportsDashboard (12 tables) | Medium (schema coupling) |
| **PaymentRequestService pattern** | 12+ modules → Cashier → Payments | Standard |
| **FormBridge pattern** | Members, Children, Spouses, Transfers → Forms | Standard |
| **ArchiveService pattern** | Death, Divorce, Waiver, Transfers → Archive | Standard |

### 10.2 Integration Service Registry

| Service | Module | Consumers | Purpose |
|---------|--------|-----------|---------|
| `PaymentRequestService` | Cashier | Members, Children, Spouses, Temporary, Death, Divorce, Waiver, Transfers, Carnets, Seasonal, SportsActivity | Create payment queue items |
| `PaymentService` | Payments | Cashier, Fines, Foreign, Subscriptions, ActivitySubscriptions, Death, Divorce | Process payments |
| `ArchiveService` | Archive | Death, Divorce, Waiver, Transfers | Snapshot + number chain |
| `FormBridge` | Forms | Members, Children, Spouses, Transfers | Dynamic form rendering/submission |
| `RuleEngine` | Rules | 14+ modules | Business rule lookups |
| `PricingEngine` | Pricing | Children, Spouses, Transfers, Seasonal | Fee calculations |
| `NationalIdParser` | Members | Children, Spouses, Coaches, Death, Divorce | Parse Egyptian 14-digit NID |
| `MemberNumberGenerator` | Members | Death, Divorce, Waiver, Transfers, Installments | Sequential membership numbers |
| `JournalService` | Accounting | Sales (via adapter), Inventory (via adapter) | Direct journal posting |
| `AccountingIntegrationService` | Accounting | (via EventBus only) | Auto-post from events |
| `QRCodeGenerator` | Carnets | SportsActivity | QR code SVG generation |
| `GuestEntryService` | Carnets | SportsActivity (service desk) | Guest entry recording |

---

---

## 11. Detailed Integration Maps (Critical Hub Modules)

### 11.1 Members Module — Complete Integration Surface

**Services exposed (consumed by 25+ modules):**

| Service | Method | Consumers |
|---------|--------|-----------|
| NationalIdParser | ::parse($nid) | Children, Spouses, Temporary, Coaches, Death, Divorce, SportsActivity/RegistrationWizard |
| MemberNumberGenerator | ::assign($memberId) | Cashier/bootstrap, Installments/ChequeService, Death, Divorce, Waiver, Transfers |
| MemberNumberGenerator | ::next() | Members/MemberController (form number) |
| MemberNumberGenerator | ::getFormFee() | Members (views), BillingService |
| MembershipPaymentGuard | ::activateMember($id, $paymentId) | Cashier/bootstrap, PaymentLifecycleService |
| MembershipPaymentGuard | ::deactivateMember($id, $voidedPaymentId) | Cashier/bootstrap (on void) |
| MembershipPaymentGuard | ::activateDependent($table, $entityId, $memberId, $paymentId) | Cashier/bootstrap |
| MembershipPaymentGuard | ::deactivateDependent($table, $entityId, $voidedPaymentId) | Cashier/bootstrap (on void) |
| MembershipPaymentGuard | ::activateIncludedDependents($memberId, $paymentId) | Cashier/bootstrap |
| MembershipPaymentGuard | ::reconcile($memberId) | Members/MemberController::show (every profile load) |
| BillingService | ::getMemberBill($memberId) | Members/MemberController::show, ::payMembership |
| FormFeeService | ::getFormFee($memberId, $member) | Children, Spouses, Temporary |
| FormFeeService | ::isOnInitialForm($member) | Children, Spouses, Temporary |
| FormFeeService | ::isSpouseFreeSlot($memberId, $order) | Spouses |
| FormFeeService | ::isChildFreeSlot($memberId, $order, $age) | Children |
| MembershipRulesService | ::getChildAdditionFee($age, $value) | Children/ChildFeeCalculator |
| MembershipRulesService | ::getSpouseAdditionFee($order, $value, $date) | Spouses/SpouseFeeCalculator |
| MembershipRulesService | ::getTemporaryMemberFee($value) | Temporary/TemporaryFeeCalculator |
| MembershipRulesService | ::getSeparationFee($years, $value) | Transfers/TransferProcessor |
| MembershipRulesService | ::getDivorceTransferFee($scenario, $value) | Divorce/DivorceController |
| MembershipRulesService | ::getWaiverFee($value) | Waiver/WaiverService |
| MembershipRulesService | ::canChildSeparate($gender, $age, $employed, $married) | Transfers |
| MembershipValidationService | ::checkByMembershipNumber($num) | SportsActivity/BookingWizard, PlayerAffairs |
| MembershipValidationService | ::checkByNationalId($nid) | SportsActivity/ServiceDesk |
| MemberSearchService | ::search($query, $limit) | Members/MemberController |
| AutoFreezeService | ::processAutoFreeze() | Cron (children → separated at 25) |
| AutoFreezeService | ::freezeTemporaryAtAgeLimit() | Cron (temp members → expired at 25) |
| AutoFreezeService | ::flagHonoraryExpiry() | Cron (honorary → expired) |

**Fields on `members` that other modules depend on:**

| Field | Read By | Written By (external) |
|-------|---------|----------------------|
| status | Cashier, Payments, Subscriptions, Installments, Fines, Carnets, Death, Divorce, Reports, Dashboard, Accounting | Interviews, Fines, Installments/DefaultChecker, Subscriptions/OverdueFineApplicator, Cashier/bootstrap (via Guard) |
| membership_number | Carnets, Death, Divorce, Waiver, Transfers, Reports | MemberNumberGenerator (via Guard) |
| membership_value | Children, Spouses, Temporary, Transfers, Divorce, Waiver, BillingService | Members/saveFillForm (on qualification select) |
| qualification_id | Pricing (fee lookup), BillingService | Members/saveFillForm |
| branch_id | Pricing (branch-based fees), Dashboard, Reports | Members/store, Members/update |
| activated_by_payment_id | MembershipPaymentGuard (reconcile check) | Cashier/bootstrap (on activation) |
| activated_at | Reports | Cashier/bootstrap (on activation) |
| special_discount_id | BillingService, Members/show | Members/applyDiscount, Members/saveFillForm |
| transferred_from_divorce_id | Members/show (origin display) | Divorce/DivorceController |

**Events dispatched and full listener chains:**

| Event | Full Handler Chain |
|-------|-------------------|
| `member.created` | → (no external listeners currently) |
| `member.status_changed` | → (no external listeners currently) |
| `member.activated` | → Sales/bootstrap (unknown action), Notifications (SMS) |
| `member.dropped` | → Accounting/bootstrap (write off AR: Dr. Bad Debt Expense, Cr. AR) |

---

### 11.2 SportsActivity Module — Complete Integration Surface

**Services consumed FROM other modules:**

| External Service | Method Called | SA Service That Calls It |
|-----------------|-------------|--------------------------|
| Cashier/PaymentRequestService | ::createRequest($data) | RegistrationWizardService, EnrollmentService, CardRenewalService, PoolTicketService |
| Payments/PaymentService | ::processPayment($data) | BookingService (immediate bookings), SaPaymentService |
| Members/MembershipValidationService | ::checkByMembershipNumber($num) | BookingWizardController |
| Members/MembershipValidationService | ::checkByNationalId($nid) | ServiceDeskController |
| Members/NationalIdParser | ::parse($nid) | RegistrationWizardService |
| Carnets/QRCodeGenerator | ::generate($data) | RegistrationWizardService (card generation) |
| Shared/PhotoUploadService | ::upload($file, $dir, $id) | RegistrationWizardService |
| Notifications/SmsNotificationService | ::send($phone, $message) | bootstrap.php (6 listeners) |

**payment_request types created by SA:**

| payment_type | related_entity_type | Created By |
|-------------|--------------------|-----------| 
| sports_registration | sa_registrations | RegistrationWizardService |
| sa_form_fee | sa_registration_form | RegistrationWizardService |
| sa_card_renewal | sa_player_cards | CardRenewalService |
| sports_subscription | sa_subscriptions | (manual via SubscriptionController) |
| hourly_booking | sa_bookings | BookingService (when queued) |

**SaEventListenerService routing table (payment_request.completed):**

| related_entity_type | Action Taken |
|--------------------|--------------|
| sa_registrations | Complete registration → activate card + enrollment |
| sa_registration_form | Mark form fee paid on registration |
| sa_subscriptions | Mark subscription paid |
| sa_bookings | Mark booking paid |
| sa_group_players | Activate enrollment (status → active) |
| sa_player_cards | Complete card renewal (extend valid_until) |

**SaEventListenerService routing table (payment.voided):**

| related_entity_type | Reversal Action |
|--------------------|----------------|
| sa_subscriptions | Revert to unpaid |
| sa_bookings | Revert to unpaid |
| sa_group_players | Revert to pending_payment |

---

### 11.3 Payments Module — Complete Integration Surface

**PaymentService::processPayment() — Full Input Contract:**

```php
[
  'member_id'          => ?int,       // FK to members (null for guests)
  'player_id'          => ?int,       // FK to sa_players (sports payments)
  'payment_type'       => string,     // REQUIRED: one of 17+ types
  'amount'             => string,     // REQUIRED: bcmath string > 0
  'currency'           => string,     // Default: 'EGP'
  'payment_method'     => string,     // REQUIRED: cash/check/visa/bank_transfer
  'check_number'       => ?string,
  'check_bank'         => ?string,
  'check_date'         => ?string,
  'visa_reference'     => ?string,
  'transfer_reference' => ?string,
  'transfer_bank'      => ?string,
  'related_entity_type'=> ?string,    // spouses/children/temporary_members/sa_bookings/etc.
  'related_entity_id'  => ?int,
  'notes'              => ?string,
  'payment_date'       => ?string,    // Default: today
  'treasury_id'        => ?int,       // If treasury system active
  'session_id'         => ?int,       // If treasury system active
]
```

**PaymentService::processPayment() — Return Contract:**

```php
[
  'success'        => bool,
  'payment_id'     => int,
  'receipt_id'     => int,
  'receipt_number' => string,  // REC-YYYY-NNNNNN
  'error'          => ?string, // Only if success=false
]
```

**PaymentService::voidPayment() — Side Effects Chain:**

```
voidPayment($paymentId, $reason, $employeeId)
  ├── UPDATE payments SET is_voided=1
  ├── UPDATE receipts SET is_voided=1
  ├── UPDATE payment_requests SET is_voided=1 (linked requests)
  ├── DISPATCH payment.voided
  │     ├── Accounting/bootstrap → JournalService::reverseEntry()
  │     ├── Accounting/StatementIntegrationService → void statement line
  │     ├── Accounting/DailyCashMovementService → record outflow
  │     └── Cashier/bootstrap → PaymentLifecycleService::onPaymentVoided()
  │           ├── membership_fee/down_payment → MembershipPaymentGuard::deactivateMember()
  │           │     └── deactivateAllDependents()
  │           ├── addition_fee → MembershipPaymentGuard::deactivateDependent()
  │           ├── separation_fee → revert transfer_request status
  │           ├── divorce_fee → revert divorce_case status
  │           ├── death_fee → revert death_case status
  │           └── waiver_fee → revert waiver status
  └── RETURN ['success' => true]
```

**PaymentLifecycleService — Decision Tree (on payment_request.completed):**

| payment_type | Action |
|-------------|--------|
| membership_fee | MembershipPaymentGuard::activateMember() + activateIncludedDependents() + dispatch member.activated |
| down_payment | transitionToPendingCheques() + createInstallmentPlan() + dispatch member.pending_cheques |
| addition_fee | MembershipPaymentGuard::activateDependent(related_entity_type, related_entity_id) |
| form_fee | (no lifecycle action — just recorded) |
| annual_subscription | (no lifecycle action — Subscriptions module handles) |
| separation_fee | Update transfer_request.status → 'fee_paid' |
| divorce_fee | Update divorce_case.status → 'fee_paid' |
| death_fee | Update death_case.status → 'fee_paid' |
| waiver_fee | Update waiver status → 'fee_paid' |
| seasonal_fee | Activate seasonal_members record |
| fine | (no lifecycle action — Fines module handles) |
| sports_* | (delegates to SaEventListenerService) |

---

### 11.4 Cashier Module — Complete Integration Surface

**PaymentRequestService::createRequest() — Full Input Contract:**

```php
[
  'member_id'           => ?int,      // FK to members
  'payment_type'        => string,    // REQUIRED
  'amount'              => string,    // REQUIRED: bcmath > 0
  'description_ar'      => string,    // Arabic label for queue display
  'related_entity_type' => ?string,   // For post-payment routing
  'related_entity_id'   => ?int,      // For post-payment routing
  'notes'               => ?string,   // JSON for metadata (installment_months, fee_breakdown)
  'branch_id'           => ?int,      // Auto-set from current employee if null
]
```

**PaymentRequestService::createRequest() — Return Contract:**

```php
[
  'success'        => bool,
  'request_number' => string,  // PRQ-YYYY-NNNNNN
  'request_id'     => int,
  'error'          => ?string,
]
```

**Cashier/bootstrap.php — Master Routing Table (payment_request.completed):**

```
payment_request.completed event received
  ├── Extract: payment_type, related_entity_type, related_entity_id, member_id, payment_id
  │
  ├── IF payment_type IN ('membership_fee', 'down_payment'):
  │     └── PaymentLifecycleService handles (activate/cheques)
  │
  ├── IF payment_type = 'addition_fee':
  │     ├── related_entity_type = 'spouses' → MembershipPaymentGuard::activateDependent('spouses', id, member_id, payment_id)
  │     ├── related_entity_type = 'children' → MembershipPaymentGuard::activateDependent('children', id, member_id, payment_id)
  │     └── related_entity_type = 'temporary_members' → MembershipPaymentGuard::activateDependent('temporary_members', id, member_id, payment_id)
  │
  ├── IF payment_type = 'seasonal_fee':
  │     └── UPDATE seasonal_members SET status='active' WHERE id = related_entity_id
  │
  ├── IF payment_type = 'separation_fee':
  │     └── UPDATE transfer_requests SET status='fee_paid' WHERE id = related_entity_id
  │
  ├── IF payment_type = 'divorce_fee':
  │     └── UPDATE divorce_cases SET status='fee_paid' WHERE id = related_entity_id
  │
  ├── IF payment_type = 'death_fee':
  │     └── UPDATE death_cases SET status='fee_paid' WHERE id = related_entity_id
  │
  ├── IF payment_type = 'waiver_fee':
  │     └── UPDATE waiver_requests SET status='fee_paid' WHERE id = related_entity_id
  │
  ├── IF payment_type IN ('sports_registration', 'sa_form_fee', 'sa_card_renewal', 'sports_subscription', 'hourly_booking'):
  │     └── SaEventListenerService::handlePaymentCompleted($data)
  │
  └── IF payment_type = 'carnet_replacement':
        └── UPDATE carnets SET status='active' WHERE member_id = member_id
```

**Duplex prevention query (before createRequest):**

```sql
SELECT id FROM payment_requests 
WHERE member_id = ? AND payment_type = ? 
  AND related_entity_type = ? AND related_entity_id = ?
  AND status IN ('pending', 'processing', 'completed')
  AND is_voided = 0 LIMIT 1
```

---

### 11.5 Accounting Module — Complete Integration Surface

**AccountingIntegrationService — Full Event→Journal Mapping:**

| Event | Journal Entry | Debit Account | Credit Account |
|-------|--------------|---------------|----------------|
| `payment.completed` (membership_fee) | Dr. Cash/Bank, Cr. Membership Revenue | 11200xxx (Cash) | 41100xxx (Revenue) |
| `payment.completed` (form_fee) | Dr. Cash, Cr. Form Fee Revenue | 11200xxx | 41200xxx |
| `payment.completed` (annual_subscription) | Dr. Cash, Cr. Subscription Revenue | 11200xxx | 41300xxx |
| `payment.completed` (fine) | Dr. Cash, Cr. Fine Revenue | 11200xxx | 41400xxx |
| `payment.completed` (installment) | Dr. Cash, Cr. AR-Installments | 11200xxx | 12100xxx |
| `payment.completed` (addition_fee) | Dr. Cash, Cr. Addition Fee Revenue | 11200xxx | 41500xxx |
| `payment.completed` (down_payment) | Dr. Cash, Cr. AR-Installments | 11200xxx | 12100xxx |
| `payment.voided` | REVERSE of original entry | (swap Dr/Cr) | |
| `fine.imposed` | Dr. AR-Fines, Cr. Fine Revenue | 12200xxx | 41400xxx |
| `fine.paid` | Dr. Cash, Cr. AR-Fines | 11200xxx | 12200xxx |
| `member.dropped` | Dr. Bad Debt, Cr. AR | 52xxx | 12xxx |
| `sale.completed` | Dr. Cash, Cr. Sales Revenue + Dr. COGS, Cr. Inventory | 11200/51xxx | 41xxx/13xxx |
| `sale.voided` | REVERSE of sale entry | | |
| `sale.refunded` | Dr. Sales Revenue, Cr. Cash | 41xxx | 11200xxx |
| `hr.payroll.paid` | Dr. Salary+Insurance, Cr. Bank+Tax+Insurance | 61xxx/62xxx | 11300/21xxx |
| `transfer.completed` | Dr. AR, Cr. Transfer Revenue | 12xxx | 41600xxx |
| `waiver.completed` | Dr. AR, Cr. Waiver Revenue | 12xxx | 41700xxx |
| `treasury.deposit.confirmed` | Dr. Bank, Cr. Main Cash | 11300xxx | 11200001 |
| `treasury.settlement.received` | Dr. Main Cash, Cr. Sub Cash | 11200001 | 11200xxx |
| `procurement.invoice_approved` | Dr. Inventory+Tax, Cr. AP | 13xxx/15xxx | 21100xxx |
| `procurement.payment_completed` | Dr. AP, Cr. Cash/Bank | 21100xxx | 11200/11300xxx |
| `rental.deposit_collected` | Dr. Cash, Cr. Deposit Liability | 11200xxx | 22xxx |
| `facility.entry_recorded` | Dr. Cash, Cr. Facility Revenue | 11200xxx | 41800xxx |

**Accounting fail-open pattern:** All event listeners are wrapped in try/catch. Failure is logged but NEVER blocks the originating operation. This means:
- A payment will succeed even if the journal entry fails
- GL may become out of sync if listeners throw
- `GLSyncService::rebuild()` exists as a reconciliation tool

**Critical account codes (from AccountCodes.php):**

| Constant | Code | Purpose |
|----------|------|---------|
| CASH_MAIN | 11200001 | Main treasury cash |
| CASH_SUB_SA | 11200002 | Sports activity sub-treasury |
| CASH_SUB_MEM | 11200003 | Membership sub-treasury |
| AR_INSTALLMENTS | 12100001 | Installment receivables |
| AR_FINES | 12200001 | Fine receivables |
| AR_SUBSCRIPTIONS | 12300001 | Subscription receivables |
| REVENUE_MEMBERSHIP | 41100001 | Membership fee revenue |
| REVENUE_FORMS | 41200001 | Form fee revenue |
| REVENUE_SUBSCRIPTIONS | 41300001 | Annual subscription revenue |

---

## 12. Dependency Statistics

| Metric | Value |
|--------|-------|
| Total modules | 75 |
| Events dispatched (unique) | 85+ |
| Events with active listeners | ~30 |
| Orphaned events (no listeners) | 55+ |
| Event name mismatches | 2 (critical) |
| Hub modules (5+ dependents) | 10 |
| Leaf modules (0 dependents) | 10 |
| Tables shared across modules | 15+ |
| Direct member-status writers (bypassing Members) | 6 |
| Missing cron enforcement | 5 |
