# ECOLE ECOIN Architecture Standards

Date: 2026-05-21

Status: baseline for all new work after the architecture audit.

## Purpose

These standards stop architecture drift while keeping Laravel and Livewire productive. They apply to new features first. Existing code is migrated through the refactoring roadmap, not through a rewrite.

## Canonical Module Structure

New domain work belongs under:

```text
app/Domain/{Module}/
  Actions/
  Services/
  DTOs/
  Events/
  Listeners/
  Jobs/
  Policies/
  Queries/
  Data/
```

Tests stay under:

```text
tests/Feature/{Module}/
tests/Unit/{Module}/
```

Use only folders that the module needs. Do not create empty folders for decoration.

## Module Ownership

| Module | Owns writes to | May read from | Must call instead of writing directly |
| --- | --- | --- | --- |
| Courses | course/catalog entities | cohorts, instructors for display | Cohort/Scheduling for session generation |
| Cohorts/Scheduling | cohort configuration, schedule generation, attendance session timing | course, room, instructor availability | Finance for earnings; Messaging for notifications |
| Enrollments | registration/enrollment lifecycle | course/cohort availability, billing policies | Payments for payment confirmation; Messaging for sending |
| Payments/Finance | payment lifecycle, receipts, allocations, transactions | enrollment/corporate references | Enrollment for seat lifecycle callbacks |
| Attendance | attendance marks, attendance summaries | enrolled learner/corporate trainee scope | Certificates for issuance rules |
| WhatsApp | contacts, conversations, webhook processing, journey state | course/cohort summaries, enrollment references | Enrollment/Payments Actions for actual registration/payment writes |
| Messaging | templates, outbox, provider dispatch, message logs | event payloads | Never mutate business state from a template renderer |
| Corporate | organization, B2B leads, proposals, agreements, corporate cohorts, reports, invoices | courses, sessions, finance summaries | Messaging/Finance/Scheduling public APIs |
| Portal/UI | request state and presentation | query DTOs | Domain Actions for mutations |
| SEO/GEO/Tracking | metadata, attribution capture, analytics events | public entity summaries | Never own business workflow state |

## Layer Rules

### Livewire rule

Livewire components may:

- Authorize pages and sensitive actions.
- Validate request/UI input.
- Hold UI state and pagination/filter state.
- Call an Action, Query, or dedicated Service.
- Dispatch UI events/toasts.

Livewire components must not become the only owner of:

- Workflow state transitions.
- Multi-model transactions.
- Financial calculations.
- Provider fallback behavior.
- Audit and notification orchestration.
- Reusable query scoping rules.

Preferred mutation shape:

```php
public function save(CreateEnrollmentAction $action): RedirectResponse
{
    $this->authorize('create', Enrollment::class);

    $command = EnrollmentCommand::fromValidated($this->validate($this->rules()));
    $enrollment = $action->handle($command, auth()->user());

    return redirect()->route('admin.enrollments.show', $enrollment);
}
```

### Controller rule

Controllers are transport adapters.

They may:

- Resolve route models.
- Authorize.
- Validate or accept Form Requests.
- Call an Action or Query.
- Return response/download/redirect.

They must not hide a full workflow inside a controller closure or download route.

### Model rule

Models may contain:

- Relationships.
- Casts.
- Scopes.
- Lightweight accessors.
- Constants describing persisted values.

Avoid placing large workflow state machines, provider calls, or financial orchestration in model hooks. If a hook is needed for reference generation or compatibility, document why the hook exists and keep it narrow.

### Action rule

Every important mutation gets an explicit Action or workflow service owned by the writing module.

Examples:

- `CreateEnrollmentAction`
- `ApproveDepositPaymentAction`
- `CreateCorporateInvoiceAction`
- `AllocateCorporatePaymentAction`
- `SendWhatsAppTemplateAction`
- `ConvertB2bLeadToOrganizationAction`
- `CreateAttendanceSessionAction`

Action requirements:

- Authorize at caller or at the Action boundary when caller types vary.
- Accept explicit DTO/command data instead of depending on Livewire properties.
- Own transaction boundaries.
- Write audit/activity records when the action is sensitive.
- Dispatch domain events after successful commit when downstream work exists.

### Query rule

Reusable data access belongs in Query objects when:

- The same filter/scope is used by UI, export, API, or automation.
- Tenant or role scoping is part of the query.
- The query builds a dashboard or report.

Use policies for permission decisions and Query objects for safe data selection.

## Event-Driven Readiness

### Domain event catalog

New work should prefer formal events for lifecycle milestones:

- `EnrollmentCreated`
- `DepositPaid`
- `AttendanceMarked`
- `ProposalAccepted`
- `AgreementActivated`
- `CorporateInvoiceIssued`
- `CorporatePaymentAllocated`
- `WhatsAppLeadCreated`

### Event rules

- Events describe business facts, not UI intentions.
- Dispatch after commit for transactional business writes.
- Payloads should carry stable IDs and minimal non-sensitive metadata.
- Listeners must be idempotent where providers, queues, reminders, or analytics are involved.
- A listener may trigger messaging, tracking, reminders, and projections.
- A listener must not silently bypass module ownership and mutate unrelated core state without a documented Action.

## Cross-Cutting Standards

### Security

- Route protection, policies, file privacy, tenant scoping, masking, and audits follow `docs/security_baseline.md`.

### Localization

- No new UI labels, toasts, aria labels, placeholders, PDF labels, or empty states should be hardcoded.
- Add AR and FR keys together.
- Use technical constants and brands directly only when intentionally not localized.

### Tracking

- Use centralized tracking/attribution services.
- Do not scatter pixel or event logic through Blade/Livewire.

### PDFs and files

- PDF generation belongs to domain/application services.
- Sensitive PDFs and receipts use private disks.
- Downloads use controller plus policy, not direct public links.

## Naming Standards

- `Organization` is the canonical corporate tenant.
- Do not use `Company` for new Corporate code.
- Pick one public vocabulary per module and keep aliases only at route compatibility boundaries.
- Treat `teacher` and `instructor` naming overlap as a migration topic; new operations should follow the route/module chosen by the roadmap.
- Document when `Session` means cohort/course session and when `AttendanceSession` means scheduled lesson occurrence.

## Review Checklist For New Features

- Which module owns the write?
- Which Action performs the mutation?
- Which policy and route middleware protect it?
- Which Query object scopes data for tenant/role?
- Which audit/activity entry is expected?
- Which event is emitted after success?
- Which jobs/listeners are idempotent?
- Which tests cover permission, IDOR, workflow state, and files?
- Which AR/FR keys and docs are added?

## Definition Of Done For New Modules

- Architecture owner declared.
- Routes protected.
- Actions and queries separated from UI.
- Policies and tests exist.
- Private files handled through policy downloads.
- Events documented when automation depends on lifecycle changes.
- Translation and documentation are complete.
