update api and add more features

This commit is contained in:
root
2026-06-04 02:24:41 -04:00
parent fa6c9519a0
commit 4e33882ac7
131 changed files with 34596 additions and 340 deletions
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
# Islamic Sunday School Modular Plan Update Index
This bundle updates the global modular plan and Phases 1-6 so the extension domain is explicitly **Islamic Sunday School**.
Core rule: `SchoolCore` remains neutral. Islamic-specific behavior belongs in `App\Domain\IslamicSundaySchool` through policies, resolvers, services, templates, and extension profile tables.
## Updated Files
- `app_project_plan_global_modular_islamic.md`
- `phase_1_school_context_execution_plan_islamic.md`
- `phase_2_core_contracts_execution_plan_islamic.md`
- `phase_3_modular_finance_execution_plan_islamic.md`
- `phase_4_modular_attendance_execution_plan_islamic.md`
- `phase_5_modular_student_lifecycle_execution_plan_islamic.md`
- `phase_6_modular_communication_execution_plan_islamic.md`
## Key Renames
```text
App\Domain\SundaySchool -> App\Domain\IslamicSundaySchool
sunday_school -> islamic_sunday_school
Sunday School profile -> Islamic Sunday School profile
church/ministry/pastoral terms -> masjid/community, volunteer/program, imam/admin or religious-sensitive terms
Bible curriculum -> Qur'an/Arabic/Islamic studies curriculum
```
## Non-Negotiable Boundary
`SchoolCore` must not import `IslamicSundaySchool`.
Allowed:
```text
IslamicSundaySchool -> SchoolCore contracts
SchoolCore -> Shared neutral infrastructure
Controllers -> contracts
```
Forbidden:
```text
SchoolCore -> IslamicSundaySchool
SchoolCore contracts containing Islamic-specific vocabulary
Controllers hardcoding Islamic Sunday School rules
```
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
# Phase 9 module ownership. Replace usernames/team slugs with real owners before enforcing.
/app/Domain/SchoolCore/Context/ @platform-owner
/app/Domain/SchoolCore/Finance/ @finance-owner @platform-owner
/app/Domain/SchoolCore/Attendance/ @attendance-owner @platform-owner
/app/Domain/SchoolCore/Students/ @students-owner @platform-owner
/app/Domain/SchoolCore/Communication/ @communication-owner @platform-owner
/app/Domain/SchoolCore/Reporting/ @reporting-owner @data-security-owner
/app/Domain/IslamicSundaySchool/ @islamic-sunday-school-owner @platform-owner
/app/Http/Controllers/ @api-owner
/routes/ @api-owner @platform-owner
/docs/ @platform-owner
@@ -0,0 +1,19 @@
## Modular Architecture Checklist
- [ ] SchoolContext is used where required.
- [ ] No SchoolCore dependency on IslamicSundaySchool.
- [ ] Controllers remain thin and delegate to contracts.
- [ ] FormRequest validates mutable endpoint input.
- [ ] Resource or ApiResponse controls output.
- [ ] Authorization is tested.
- [ ] Wrong-school access is tested for scoped data.
- [ ] Sensitive fields are protected or masked.
- [ ] New route appears in route inventory.
- [ ] New report/export is audited.
- [ ] Bulk communication requires preview.
- [ ] Finance changes avoid float math and direct controller mutation.
- [ ] Module owner reviewed.
## Risk Notes
List any boundary exceptions, deprecated paths, new routes, new reports/exports, or sensitive data exposure. If this section is empty for a high-risk change, assume the PR is lying by omission.
@@ -0,0 +1,29 @@
name: Architecture Governance
on:
pull_request:
push:
branches: [ main ]
jobs:
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Validate composer
run: composer validate --no-check-publish || true
- name: Install dependencies
run: composer install --no-interaction --prefer-dist || true
- name: Run architecture tests
run: php artisan test tests/Architecture || true
- name: Run architecture scan in warning mode
run: php artisan app:architecture-scan --fail-on=none || true
- name: Generate route inventory
run: php artisan api:route-inventory --markdown || true
- name: Check route inventory
run: php artisan app:route-inventory-check || true
- name: Generate dependency map
run: php artisan app:dependency-map --markdown || true
@@ -0,0 +1,23 @@
name: Release Gate
on:
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
release-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- run: composer install --no-interaction --prefer-dist || true
- run: php artisan test tests/Architecture
- run: php artisan app:architecture-scan --fail-on=error
- run: php artisan api:route-inventory --markdown
- run: php artisan app:route-inventory-check
- run: php artisan app:docs-coverage
- run: php artisan app:dependency-map --markdown
@@ -0,0 +1,27 @@
name: Release Readiness
on:
workflow_dispatch:
pull_request:
paths:
- 'app/**'
- 'config/**'
- 'routes/**'
- 'docs/release/**'
- '.github/workflows/release-readiness.yml'
jobs:
release-readiness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- run: composer install --no-interaction --prefer-dist
- run: php artisan app:release-feature-flags
- run: php artisan app:release-migration-validate
- run: php artisan app:release-shadow-compare
- run: php artisan app:legacy-unsafe-route-audit
- run: php artisan app:release-readiness-gate --warning-mode
- run: php artisan test tests/Feature/Release tests/Architecture/Phase10ReleaseReadinessArchitectureTest.php
@@ -0,0 +1,764 @@
# Phase 1 Execution Plan: SchoolContext Foundation
# Islamic Sunday School Alignment Update
Phase 1 must support **Islamic Sunday School** as a domain profile, not generic Sunday School or church-oriented Sunday School.
Add the domain profile value:
```text
islamic_sunday_school
```
`SchoolContext` may expose this profile neutrally as `domain_profile`, but `SchoolCore` services must not branch on Islamic-specific concepts directly. Any Islamic Sunday School behavior must be resolved through extension bindings, policy contracts, or context-aware providers.
Examples of extension-only concepts:
```text
masjid_id
program_session_id
halaqa_id
quran_level_id
arabic_level_id
islamic_studies_track_id
volunteer_team_id
imam_admin_note_permission
```
The core still uses neutral terms: `school`, `term`, `session`, `program`, `group`, `family`, `guardian`, `student`, and `staff`.
---
## 1. Objective
Create a mandatory, immutable `SchoolContext` foundation so every reusable service can operate safely across different school systems.
This phase is the base layer for the modular platform. Without it, `SchoolCore` cannot be reliably reused by Islamic Sunday School, traditional schools, training centers, after-school programs, or any other future domain. A modular platform without tenant/context isolation is just a data leak wearing a nicer folder structure.
## 2. Target Outcome
At the end of Phase 1:
- Every new core service receives an explicit `SchoolContext`.
- Controllers no longer rely only on scattered `auth()`, request values, global configuration, or session-like assumptions to determine school scope.
- Cross-school access fails by default.
- Islamic Sunday School can resolve its own domain profile through context, without forcing Islamic Sunday School concepts into global services.
- Finance, attendance, students, files, reporting, and communication can begin migration into `SchoolCore` safely.
## 3. Current Problem to Fix
The current app has school/year/semester context scattered across:
- authenticated user fields such as `school_id`, `school_year`, and `semester`;
- global configuration access such as `Configuration::getConfig('school_year')` and `Configuration::getConfig('semester')`;
- request query/body values;
- controller-specific assumptions;
- service-level fallback behavior;
- model methods that directly read global configuration;
- finance, attendance, reporting, and student queries that depend on string school-year/semester filters.
This makes the app hard to reuse for multiple school types because context is implicit instead of explicit.
## 4. Design Principle
`SchoolContext` is not a convenience object. It is a safety boundary.
Core rule:
```php
SchoolCore services must not guess school, year, term, actor, timezone, currency, or domain profile.
```
They must receive those values from an explicit context object created at the API boundary.
## 5. Proposed Namespace Structure
Create these namespaces:
```txt
app/
Domain/
SchoolCore/
Context/
SchoolContext.php
SchoolContextResolver.php
SchoolContextFactory.php
SchoolContextValidator.php
SchoolContextStore.php
SchoolContextException.php
Contracts/
RequiresSchoolContext.php
Support/
SchoolContextAware.php
Http/
Middleware/
ResolveSchoolContext.php
```
Optional later namespaces:
```txt
app/
Domain/
IslamicSundaySchool/
Context/
IslamicSundaySchoolContextExtender.php
```
Do not create the Islamic Sunday School extender in Phase 1 unless there is a concrete field or rule that cannot live in the neutral core context. Premature extension objects are how clean architecture becomes a museum of unused abstractions.
## 6. `SchoolContext` Value Object
### 6.1 Required Fields
Initial version:
```php
final readonly class SchoolContext
{
public function __construct(
public int $schoolId,
public int $actorUserId,
public array $actorRoleIds,
public ?string $schoolYear,
public ?string $term,
public string $timezone,
public string $locale,
public string $currency,
public string $domainProfile,
) {}
}
```
### 6.2 Field Rules
| Field | Required? | Purpose |
|---|---:|---|
| `schoolId` | Yes | Primary isolation boundary. Every scoped query must use it where schema supports it. |
| `actorUserId` | Yes | Audit, authorization, ownership checks. |
| `actorRoleIds` | Yes | Policy/gate checks without repeated role queries. |
| `schoolYear` | Nullable during migration | Current academic year or legacy string value. |
| `term` | Nullable during migration | Current term/semester. Keep name neutral in core. |
| `timezone` | Yes | Date boundaries for attendance, deadlines, reports, finance timestamps. |
| `locale` | Yes | Formatting, translation, report language. |
| `currency` | Yes | Finance defaults. |
| `domainProfile` | Yes | Example: `standard_school`, `islamic_sunday_school`, `training_center`. |
### 6.3 Naming Decision
Use `term` in the core context, not `semester`.
Reason: `semester` is one possible academic model. A global school platform may use quarters, trimesters, sessions, Sunday program cycles, training cohorts, or rolling terms. Use `semester` only inside legacy adapters until migration is complete.
## 7. Context Resolution Strategy
### 7.1 Resolution Priority
`SchoolContextResolver` should resolve context in this order:
1. Authenticated user from JWT/Auth.
2. Explicit school selector from request header or route, if allowed.
3. User default school, currently likely `users.school_id`.
4. Current school year/term from request, if allowed and validated.
5. Config fallback for legacy behavior, such as current `Configuration::getConfig('school_year')` and `Configuration::getConfig('semester')`.
6. Safe defaults for timezone/locale/currency.
### 7.2 Recommended Request Headers
Support these headers during migration:
```txt
X-School-Id
X-School-Year
X-School-Term
X-Domain-Profile
```
Rules:
- `X-School-Id` may only switch context if the actor belongs to that school or has platform-level permission.
- `X-School-Year` and `X-School-Term` must be validated against available academic periods once those models exist.
- `X-Domain-Profile` must not override server-side school configuration unless explicitly allowed for test/dev tooling.
## 8. Middleware Plan
### 8.1 Create `ResolveSchoolContext`
Middleware behavior:
```php
public function handle(Request $request, Closure $next): Response
{
$context = $this->resolver->resolve($request);
$this->validator->assertValid($context, $request->user());
$this->store->set($context);
$request->attributes->set(SchoolContext::class, $context);
return $next($request);
}
```
### 8.2 Middleware Order
Run after authentication and before authorization-sensitive controllers:
```txt
ApiJwtAuth
ResolveSchoolContext
RequirePermission / policies / controller
```
Do not run it before authentication unless supporting public routes. Public routes should either not use `SchoolContext`, or should use an explicit public context resolver with sharply limited behavior.
### 8.3 Route Group Rollout
Start with high-risk route groups only:
```txt
/api/finance/*
/api/payments/*
/api/invoices/*
/api/students/*
/api/attendance/*
/api/scanner/*
/api/files/*
/api/reports/*
/api/messages/*
/api/roles/*
```
Then expand to all authenticated API routes after compatibility is verified.
## 9. Service Container Bindings
Create container bindings:
```php
$this->app->singleton(SchoolContextStore::class);
$this->app->bind(SchoolContextResolver::class);
$this->app->bind(SchoolContextValidator::class);
$this->app->bind(SchoolContextFactory::class);
```
Add a helper only if necessary:
```php
school_context(): SchoolContext
```
Do not let the helper become the main service API. Core services should receive context explicitly in command DTOs or method arguments. A global helper is useful for legacy adapters, not for clean new code. Humans love turning helpers into hidden dependencies, and then act surprised when tests are brittle.
## 10. Service Usage Pattern
### 10.1 Preferred Command DTO Pattern
```php
final readonly class RecordPaymentCommand
{
public function __construct(
public SchoolContext $context,
public int $invoiceId,
public int $studentId,
public string $method,
public int $amountCents,
public ?UploadedFile $file,
public ?string $idempotencyKey,
) {}
}
```
### 10.2 Allowed Transitional Pattern
```php
public function recordPayment(SchoolContext $context, array $payload): PaymentResult
```
### 10.3 Forbidden New Pattern
```php
public function recordPayment(array $payload): PaymentResult
{
$user = auth()->user();
$schoolYear = Configuration::getConfig('school_year');
}
```
That pattern is legacy-only and must be marked as such.
## 11. Repository and Query Scoping
### 11.1 Scope Helper
Create a reusable query helper or trait:
```php
trait AppliesSchoolContext
{
protected function applySchoolScope(Builder $query, SchoolContext $context, string $column = 'school_id'): Builder
{
return $query->where($column, $context->schoolId);
}
}
```
### 11.2 Legacy Schema Handling
Some tables may not have `school_id`. For Phase 1, classify tables into:
| Type | Handling |
|---|---|
| Has `school_id` | Apply strict `school_id` filter. |
| Has academic year/term only | Apply `schoolYear` and `term` filters as legacy scope. |
| Global lookup table | Explicitly mark as global. |
| Missing required scope | Add to schema remediation backlog. |
Do not silently use `Schema::hasColumn()` inside business logic forever. Temporary schema detection is acceptable only inside migration adapters with comments and removal tickets.
## 12. Migration Strategy
### Step 1: Add classes without changing behavior
Create:
- `SchoolContext`
- `SchoolContextFactory`
- `SchoolContextResolver`
- `SchoolContextValidator`
- `SchoolContextStore`
- `ResolveSchoolContext` middleware
Add tests for object creation and resolution.
### Step 2: Resolve context for authenticated routes
Enable middleware in a small route group first. Recommended first group:
```txt
finance/payment read-only endpoint or student read-only endpoint
```
Avoid starting with payment mutation until context resolution has tests. Bravery is not a rollout strategy.
### Step 3: Add context to audit/logging
Every high-risk action should have actor and school context available, even before full service refactor.
Add to logs where possible:
```txt
school_id
actor_user_id
school_year
term
domain_profile
request_id
```
### Step 4: Convert first vertical slice
Choose one low-risk read path first:
```txt
Student list/read
```
Then one high-risk mutation path:
```txt
Manual payment record/edit after tests exist
```
### Step 5: Enforce context in new services
Any new `SchoolCore` service must accept context. No exceptions except explicitly global lookup services.
## 13. First Vertical Slice: Student Read Context
Use student read/list endpoints as the first validation slice because they are easier to test than finance mutations.
### Tasks
- Add `ResolveSchoolContext` to student read route group.
- Inject/request `SchoolContext` in controller.
- Pass context to student query service.
- Apply `school_id` scope where available.
- If student table lacks correct school scoping, document schema gap.
- Add wrong-school read test.
### Acceptance Criteria
- User from School A can list School A students.
- User from School A cannot list School B students.
- Missing/invalid context returns `403` or `422`, not a silent fallback.
- Existing valid Islamic Sunday School requests still work.
## 14. Second Vertical Slice: Payment File Access Context
This should connect to the P0 fix already identified.
### Tasks
- Add context to payment file download route.
- Change route from filename-only to payment/file-id-based access.
- Load payment under `school_id` context.
- Authorize actor against payment/invoice/family/finance role.
- Resolve physical file only from stored payment record.
- Log access attempt with context.
### Acceptance Criteria
- Finance/admin from same school can download payment file.
- Parent/guardian can download only if policy allows and relationship matches.
- User from another school cannot download even if they know the filename.
- Unknown payment returns `404`.
- Known payment with unauthorized actor returns `403`.
## 15. Validation Rules
`SchoolContextValidator` must check:
- actor user exists;
- actor is active/not suspended;
- `schoolId` is positive;
- actor belongs to selected school;
- selected school is active;
- selected year/term is allowed for selected school when academic-period tables are available;
- timezone is valid IANA timezone;
- locale is supported;
- currency is valid ISO currency code;
- domain profile is supported.
During migration, when academic period tables are not yet reliable, log warnings instead of blocking all requests. Do not pretend this is perfect. Transitional systems lie unless you make the lies visible.
## 16. Authorization Integration
Policies and gates should receive or resolve context.
Preferred:
```php
$this->authorize('view', [$student, $context]);
```
Alternative transitional behavior:
```php
$context = app(SchoolContextStore::class)->current();
$this->authorize('view', $student);
```
Policy checks must compare resource scope against context:
```php
return $student->school_id === $context->schoolId;
```
If a model does not have `school_id`, the policy must use a documented relationship path.
## 17. Jobs, Events, and Notifications
Any job created from a scoped action must carry context values explicitly.
### Required Job Fields
```txt
school_id
actor_user_id
school_year
term
domain_profile
locale
timezone
request_id or correlation_id
```
Do not let queued jobs recalculate context from the current logged-in user. There is no current logged-in user in a worker. This is apparently easy to forget until production sends the wrong family a message.
## 18. Files and Storage
File services must receive context.
Recommended storage path pattern:
```txt
schools/{school_id}/{module}/{resource_type}/{resource_id}/{filename}
```
Examples:
```txt
schools/12/payments/checks/8821/check_8821.pdf
schools/12/students/documents/440/immunization.pdf
```
Rules:
- never serve files by arbitrary filename alone;
- never infer authorization from path;
- always authorize resource first;
- path resolution happens after authorization;
- logs include context and resource ID.
## 19. Reporting
Reports must include context in every query.
Minimum rules:
- every report query must accept `SchoolContext`;
- report export filenames should include school/year/term where appropriate;
- cross-school report access must fail;
- raw SQL reports must bind context parameters explicitly.
## 20. Test Plan
### 20.1 Unit Tests
Create tests for:
- `SchoolContext` construction;
- invalid school ID rejection;
- invalid timezone rejection;
- invalid currency rejection;
- resolver reads authenticated user school;
- resolver rejects unauthorized `X-School-Id` switch;
- resolver applies legacy school year/term fallback;
- context store returns current context;
- context store throws when missing context.
### 20.2 Feature Tests
Create tests for:
- authenticated route receives context;
- unauthenticated route cannot resolve protected context;
- School A user cannot access School B student;
- School A finance user cannot access School B payment file;
- `X-School-Id` override works only for authorized multi-school/platform actor;
- timezone affects attendance date boundary;
- context is logged for high-risk mutation.
### 20.3 Architecture Tests
Add static tests/rules:
```txt
SchoolCore services must not call auth()
SchoolCore services must not read Request directly
SchoolCore services must not call Configuration::getConfig directly
SchoolCore services must accept SchoolContext for scoped operations
SchoolCore must not import IslamicSundaySchool namespace
```
## 21. Implementation Tickets
### SCX-001: Create `SchoolContext` value object
**Goal:** Define immutable context object used by core services.
**Tasks:**
- Create `App\Domain\SchoolCore\Context\SchoolContext`.
- Add constructor fields listed in this plan.
- Add helper methods:
- `isIslamicSundaySchool(): bool`
- `requiresTerm(): bool`
- `auditPayload(): array`
- `withTerm(?string $term): self`
- Add unit tests.
**Done when:** object is immutable, tested, and contains no request/auth dependencies.
### SCX-002: Create context resolver and factory
**Goal:** Build context from authenticated request safely.
**Tasks:**
- Create `SchoolContextFactory`.
- Create `SchoolContextResolver`.
- Read user, headers, legacy config, and defaults.
- Validate switch permissions for `X-School-Id`.
- Return deterministic context.
**Done when:** resolver works for normal user, admin user, invalid school, and missing school cases.
### SCX-003: Create context validator
**Goal:** Prevent invalid or unauthorized context.
**Tasks:**
- Create `SchoolContextValidator`.
- Validate actor belongs to selected school.
- Validate school active state where model exists.
- Validate timezone/locale/currency/domain profile.
- Add migration warnings for missing academic period tables.
**Done when:** invalid contexts fail before controller logic executes.
### SCX-004: Add middleware
**Goal:** Attach context to authenticated API requests.
**Tasks:**
- Create `ResolveSchoolContext` middleware.
- Register middleware alias.
- Add to selected route groups after auth.
- Store context in request attributes and `SchoolContextStore`.
**Done when:** protected routes can retrieve context and tests prove middleware order.
### SCX-005: Add query scoping helpers
**Goal:** Make school scoping repeatable.
**Tasks:**
- Create `AppliesSchoolContext` trait or query helper.
- Add strict school scope behavior.
- Add legacy year/term scope behavior.
- Add table classification list for global vs scoped tables.
**Done when:** first read service uses helper and wrong-school reads fail.
### SCX-006: Convert first student read/list slice
**Goal:** Prove context works on a low-risk endpoint.
**Tasks:**
- Add context middleware to selected student route.
- Pass context to student query service.
- Scope query by school.
- Add cross-school tests.
**Done when:** same-school student reads pass and cross-school reads fail.
### SCX-007: Convert payment file access slice
**Goal:** Use context on a high-risk P0 issue.
**Tasks:**
- Replace filename-only access with payment-id-based access.
- Load payment using context.
- Authorize resource before file path resolution.
- Add logs and tests.
**Done when:** guessed filenames cannot access files and cross-school access fails.
### SCX-008: Add job/event context payload convention
**Goal:** Prevent async work from losing school/actor context.
**Tasks:**
- Define `ContextPayload` array format.
- Add helper to create payload from `SchoolContext`.
- Update one notification or payment event as pilot.
- Add tests.
**Done when:** queued pilot job does not depend on live auth/session/request state.
### SCX-009: Add architecture enforcement
**Goal:** Stop new code from bypassing context.
**Tasks:**
- Add static/architecture tests.
- Ban `auth()`, `request()`, and direct `Configuration::getConfig()` inside new `SchoolCore` services.
- Ban `SchoolCore -> IslamicSundaySchool` imports.
**Done when:** CI fails on forbidden dependency direction or hidden context access.
## 22. Rollout Timeline by Workstream
### Workstream A: Foundation
1. `SchoolContext`
2. Factory/resolver/validator
3. Middleware/store
4. Tests
### Workstream B: First Usage
1. Student read/list
2. Payment file access
3. Context-aware logging
### Workstream C: Enforcement
1. Static rules
2. Architecture tests
3. Migration checklist for all future services
## 23. Acceptance Criteria for Phase 1
Phase 1 is complete only when all of this is true:
- `SchoolContext` exists and is immutable.
- Context is resolved for selected authenticated API routes.
- Context includes school, actor, year/term, timezone, locale, currency, and domain profile.
- Cross-school student read test passes.
- Cross-school payment file access test passes.
- At least one read service and one high-risk access path use explicit context.
- New `SchoolCore` services are forbidden from reading request/auth/config directly.
- `SchoolCore` cannot depend on `IslamicSundaySchool` namespace.
- Islamic Sunday School still works through context profile and legacy adapters.
- Known schema gaps are documented instead of silently hidden.
## 24. Risks
| Risk | Impact | Mitigation |
|---|---:|---|
| Tables lack `school_id` | Cross-school isolation may be incomplete | Classify tables and add schema remediation tickets. |
| Legacy code depends on global config | Context behavior may differ from old behavior | Use resolver fallback during migration and log warnings. |
| Too much migration at once | High regression risk | Start with student read/list, then payment file access. |
| Developers bypass context | Architecture decay | Add static rules and CI checks. |
| Islamic Sunday School assumptions leak into core | Platform becomes non-reusable | Enforce dependency direction and neutral vocabulary. |
## 25. Non-Goals
Phase 1 does not include:
- moving every service into `SchoolCore`;
- fully refactoring finance;
- fully refactoring attendance;
- replacing all `semester` fields;
- adding a full multi-tenant school administration UI;
- converting every controller;
- renaming every legacy table.
Trying to do all of that in Phase 1 would be how a refactor becomes a bonfire.
## 26. Immediate Next Implementation Order
Use this order:
```txt
1. Create SchoolContext value object
2. Create resolver/factory/validator/store
3. Add middleware after ApiJwtAuth
4. Add unit tests for resolver and validator
5. Add student read/list context slice
6. Add cross-school student tests
7. Add payment file context slice
8. Add cross-school payment file tests
9. Add architecture rules
10. Document remaining schema gaps
```
## 27. Definition of Success
A developer should be able to write this in a future core service:
```php
public function listStudents(SchoolContext $context, StudentListQuery $query): StudentListResult
```
And the service should not need to know whether the school is a Islamic Sunday School, a private academy, a tutoring center, or a training program.
That is the point. The global platform owns the universal school workflow. Islamic Sunday School supplies policies, labels, calendars, and domain-specific extensions. The core stays boring, reusable, and annoyingly difficult to misuse. Good architecture is mostly removing opportunities for future nonsense.
@@ -0,0 +1,942 @@
# Phase 2 Execution Plan: Core Contracts and Module Boundaries
# Islamic Sunday School Alignment Update
Phase 2 must define contracts that support **Islamic Sunday School** through extension implementations, not by adding Islamic-specific language to the core interfaces.
Preferred extension namespace:
```text
App\Domain\IslamicSundaySchool
```
The core contracts remain school-neutral. Islamic Sunday School implementations may bind policies for:
```text
Qur'an progression
Arabic level placement
Islamic studies tracks
halaqa/class grouping
masjid family/community profile
program-session attendance
volunteer team communication
tuition, sadaqah, scholarship, and fee classification
```
Do not put these terms in `SchoolCore` contracts. The contracts define capabilities. The extension defines Islamic Sunday School behavior.
---
## Purpose
Phase 2 establishes the contract layer for the modular school platform. The goal is to define stable interfaces for shared school operations before moving implementation logic into `SchoolCore`.
This phase prevents the platform from becoming Sunday-School-shaped by accident. Islamic Sunday School may extend the global platform, but the global platform must not know Islamic Sunday School exists.
## Phase 2 Outcome
At the end of this phase:
- `SchoolCore` exposes stable contracts for core school operations.
- Islamic Sunday School behavior plugs into the platform through policies, providers, and extension services.
- Controllers and application services begin depending on contracts instead of concrete legacy services.
- Dependency direction is enforced by automated tests.
- No `SchoolCore` class imports from `IslamicSundaySchool`.
- New implementation work has a clear place to go instead of continuing the existing service sprawl.
## Dependency Rule
The dependency direction must be enforced as architecture law:
```text
Http / Controllers / Requests / Resources
Application Services / Use Cases
SchoolCore Contracts
SchoolCore Implementations
Domain Extensions, such as IslamicSundaySchool, may provide policies/providers
```
Allowed:
```text
IslamicSundaySchool → SchoolCore
Controllers → SchoolCore Contracts
Application Services → SchoolCore Contracts
SchoolCore Implementations → SchoolCore Models / Repositories
```
Forbidden:
```text
SchoolCore → IslamicSundaySchool
SchoolCore → Http Request
SchoolCore → auth()
SchoolCore → controller classes
SchoolCore → route-specific assumptions
SchoolCore → Islamic Sunday School program calendar assumptions
SchoolCore → masjid/community/ministry vocabulary unless stored as extension metadata
```
## Required Namespace Structure
Create this structure first. Do not migrate every class immediately. Empty boundaries are acceptable at the start. Fake modularity is not.
```text
app/
Domain/
SchoolCore/
Contracts/
Context/
Identity/
Students/
Guardians/
Enrollment/
Attendance/
Academics/
Finance/
Communication/
Files/
Reporting/
Authorization/
Support/
IslamicSundaySchool/
Contracts/
Providers/
Policies/
Attendance/
Curriculum/
Ministry/
Families/
Reports/
Shared/
Contracts/
ValueObjects/
Exceptions/
Support/
Providers/
SchoolCoreServiceProvider.php
IslamicSundaySchoolServiceProvider.php
```
## Contract Design Rules
Every contract must follow these rules:
1. Accept `SchoolContext` explicitly or be resolved from a context-aware application service.
2. Use DTOs/value objects for input where payloads are complex.
3. Return DTOs/read models where behavior crosses module boundaries.
4. Never accept raw `Request` objects.
5. Never call `auth()` inside domain services.
6. Never use Islamic Sunday School terminology in `SchoolCore` contracts.
7. Never expose Eloquent models as the required public contract unless the service is strictly internal.
8. Define authorization expectations explicitly.
9. Define transaction ownership explicitly for mutation methods.
10. Include failure behavior in the contract documentation.
## Core Contracts to Create
### Context
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface RequiresSchoolContext
{
public function setSchoolContext(SchoolContext $context): void;
}
```
### Students
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Students\Data\CreateStudentData;
use App\Domain\SchoolCore\Students\Data\UpdateStudentData;
use App\Domain\SchoolCore\Students\Data\StudentReadModel;
interface StudentServiceContract
{
public function create(SchoolContext $context, CreateStudentData $data): StudentReadModel;
public function update(SchoolContext $context, int $studentId, UpdateStudentData $data): StudentReadModel;
public function find(SchoolContext $context, int $studentId): ?StudentReadModel;
public function archive(SchoolContext $context, int $studentId, string $reason): void;
}
```
### Student Identifier Generation
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface StudentIdentifierGeneratorContract
{
public function generate(SchoolContext $context, int $studentId): string;
}
```
### Guardians
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Guardians\Data\GuardianRelationshipData;
interface GuardianServiceContract
{
public function attachGuardian(SchoolContext $context, int $studentId, GuardianRelationshipData $data): void;
public function detachGuardian(SchoolContext $context, int $studentId, int $guardianId): void;
public function listForStudent(SchoolContext $context, int $studentId): array;
}
```
### Enrollment
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Enrollment\Data\EnrollmentData;
interface EnrollmentServiceContract
{
public function enroll(SchoolContext $context, int $studentId, EnrollmentData $data): void;
public function transfer(SchoolContext $context, int $studentId, EnrollmentData $data): void;
public function withdraw(SchoolContext $context, int $studentId, string $reason): void;
}
```
### Attendance
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Attendance\Data\AttendanceMarkData;
use App\Domain\SchoolCore\Attendance\Data\AttendanceScanData;
use App\Domain\SchoolCore\Attendance\Data\AttendanceResult;
interface AttendanceServiceContract
{
public function markStudent(SchoolContext $context, AttendanceMarkData $data): AttendanceResult;
public function processScan(SchoolContext $context, AttendanceScanData $data): AttendanceResult;
public function summarizeDay(SchoolContext $context, string $date): array;
}
```
### Attendance Policy
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use DateTimeInterface;
interface AttendancePolicyContract
{
public function isSchoolDay(SchoolContext $context, DateTimeInterface $date): bool;
public function determineStatus(SchoolContext $context, DateTimeInterface $scanTime, array $session): string;
public function duplicateScanWindowSeconds(SchoolContext $context): int;
}
```
### Academic Calendar
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use DateTimeInterface;
interface AcademicCalendarProviderContract
{
public function currentAcademicYear(SchoolContext $context): ?int;
public function currentTerm(SchoolContext $context): ?int;
public function sessionsForDate(SchoolContext $context, DateTimeInterface $date): array;
}
```
### Academics
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface GradeScalePolicyContract
{
public function gradeForScore(SchoolContext $context, float $score): string;
public function passingScore(SchoolContext $context): float;
}
```
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface PromotionPolicyContract
{
public function canPromote(SchoolContext $context, int $studentId): bool;
public function nextPlacement(SchoolContext $context, int $studentId): array;
}
```
### Finance
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Finance\Data\CreateInvoiceData;
use App\Domain\SchoolCore\Finance\Data\InvoiceReadModel;
interface InvoiceServiceContract
{
public function create(SchoolContext $context, CreateInvoiceData $data): InvoiceReadModel;
public function recalculate(SchoolContext $context, int $invoiceId): InvoiceReadModel;
public function find(SchoolContext $context, int $invoiceId): ?InvoiceReadModel;
}
```
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Finance\Data\RecordPaymentData;
use App\Domain\SchoolCore\Finance\Data\EditPaymentData;
use App\Domain\SchoolCore\Finance\Data\PaymentReadModel;
interface PaymentServiceContract
{
public function record(SchoolContext $context, RecordPaymentData $data): PaymentReadModel;
public function edit(SchoolContext $context, int $paymentId, EditPaymentData $data): PaymentReadModel;
public function void(SchoolContext $context, int $paymentId, string $reason): void;
}
```
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface PaymentAllocationPolicyContract
{
public function allocate(SchoolContext $context, int $invoiceId, float $amount): array;
}
```
### Files
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface FileAccessPolicyContract
{
public function canAccess(SchoolContext $context, string $filePurpose, int $ownerId, array $metadata = []): bool;
}
```
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface FileStorageServiceContract
{
public function store(SchoolContext $context, string $purpose, mixed $file, array $metadata = []): string;
public function resolvePath(SchoolContext $context, string $fileReference): string;
public function delete(SchoolContext $context, string $fileReference): void;
}
```
### Communication
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Communication\Data\MessageData;
use App\Domain\SchoolCore\Communication\Data\RecipientQuery;
interface CommunicationServiceContract
{
public function previewRecipients(SchoolContext $context, RecipientQuery $query): array;
public function send(SchoolContext $context, MessageData $message): array;
}
```
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
use App\Domain\SchoolCore\Communication\Data\RecipientQuery;
interface RecipientResolverContract
{
public function resolve(SchoolContext $context, RecipientQuery $query): array;
}
```
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface CommunicationPreferencePolicyContract
{
public function mayContact(SchoolContext $context, int $recipientId, string $channel, string $purpose): bool;
}
```
### Reporting
```php
namespace App\Domain\SchoolCore\Contracts;
use App\Domain\SchoolCore\Context\SchoolContext;
interface ReportServiceContract
{
public function generate(SchoolContext $context, string $reportKey, array $filters = []): array;
}
```
## DTO and Read Model Rules
Create DTOs only where contracts need them. Do not invent DTOs for every two-field method just to feel enterprise.
Recommended naming:
```text
CreateStudentData
UpdateStudentData
StudentReadModel
AttendanceMarkData
AttendanceScanData
AttendanceResult
CreateInvoiceData
RecordPaymentData
EditPaymentData
PaymentReadModel
MessageData
RecipientQuery
```
Rules:
- DTOs must be immutable where practical.
- DTOs must be created from validated request data, not raw request data.
- DTOs must not call the database.
- Read models must not expose sensitive fields by default.
- Read models must include `school_id` only when needed for authorization/debugging, not casually leaked in every response.
## Service Provider Bindings
Create `SchoolCoreServiceProvider`.
```php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Domain\SchoolCore\Contracts\StudentServiceContract;
use App\Domain\SchoolCore\Students\Services\StudentService;
class SchoolCoreServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(StudentServiceContract::class, StudentService::class);
// Bind additional core contracts as implementations are introduced.
}
}
```
Create `IslamicSundaySchoolServiceProvider` for extension policies.
```php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Domain\SchoolCore\Contracts\AttendancePolicyContract;
use App\Domain\IslamicIslamicSundaySchool\Attendance\IslamicSundaySchoolAttendancePolicy;
class IslamicSundaySchoolServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(AttendancePolicyContract::class, IslamicSundaySchoolAttendancePolicy::class);
}
}
```
Important: core contract bindings may point to core defaults. Islamic Sunday School may override only extension contracts/policies, not replace the entire platform unless a module-specific reason is documented.
## Binding Strategy
Use this three-level binding model:
### 1. Core service contracts
These are stable platform services.
Examples:
```text
StudentServiceContract → SchoolCore\Students\Services\StudentService
PaymentServiceContract → SchoolCore\Finance\Services\PaymentService
AttendanceServiceContract → SchoolCore\Attendance\Services\AttendanceService
```
### 2. Policy/provider contracts
These are extension points.
Examples:
```text
AttendancePolicyContract → IslamicSundaySchool\Attendance\IslamicSundaySchoolAttendancePolicy
AcademicCalendarProviderContract → IslamicSundaySchool\Providers\IslamicSundaySchoolCalendarProvider
PaymentAllocationPolicyContract → IslamicSundaySchool\Policies\IslamicSundaySchoolPaymentAllocationPolicy
```
### 3. Infrastructure contracts
These provide storage, messaging, and integration adapters.
Examples:
```text
FileStorageServiceContract → LaravelLocalFileStorageService
CommunicationServiceContract → LaravelCommunicationService
```
## First Migration Slice: Student Identifier Generation
This is the safest first slice because it fixes a real design bug without moving the whole student module.
### Current Problem
Student creation currently risks race conditions when identifiers are generated using `max(id) + 1` before the student row is safely persisted.
### Target Design
```text
StudentController
→ CreateStudentRequest
→ CreateStudentData
→ StudentServiceContract
→ StudentService
→ StudentIdentifierGeneratorContract
```
### Tasks
1. Create `StudentIdentifierGeneratorContract`.
2. Create default implementation: `SequentialStudentIdentifierGenerator`.
3. Generate identifier after student insert using the persisted student ID.
4. Add unique database constraint for `(school_id, school_id_number)` or equivalent identifier column.
5. Add retry behavior for collisions.
6. Add concurrent creation test.
7. Keep the existing API response compatible.
### Acceptance Criteria
- Two concurrent student creations cannot produce the same identifier.
- Student ID format can be changed by binding a different generator.
- Islamic Sunday School-specific identifier formatting does not live inside core student creation logic.
- Existing student creation endpoint still works.
## Second Migration Slice: Payment File Access Policy
This slice validates that contracts can enforce security boundaries.
### Current Problem
Filename-based file serving risks unauthorized access if a user can guess a payment file name.
### Target Design
```text
PaymentManualController
→ PaymentFileDownloadRequest
→ PaymentServiceContract or PaymentFileService
→ FileAccessPolicyContract
→ FileStorageServiceContract
```
### Tasks
1. Create `FileAccessPolicyContract`.
2. Create `PaymentFileAccessPolicy` implementation.
3. Replace filename-only lookup with payment-ID-based lookup.
4. Resolve file path from the payment record.
5. Authorize against school context and actor permissions.
6. Add audit log entry for file access.
7. Return `403` for unauthorized access.
8. Return `404` for missing file or missing payment.
9. Add tests for guessed filename access.
### Acceptance Criteria
- Users cannot download payment files by guessing filenames.
- Finance/admin users can download authorized payment files.
- Parents can only download files tied to their own financial records, if product rules allow parent access.
- Cross-school access fails.
- Payment file access uses `SchoolContext`.
## Third Migration Slice: Attendance Policy Stub
This slice prepares Phase 4 without fully extracting scanner logic yet.
### Target Design
```text
ScannerController
→ AttendanceServiceContract
→ AttendancePolicyContract
→ AcademicCalendarProviderContract
```
### Tasks
1. Create `AttendancePolicyContract`.
2. Create default `StandardSchoolAttendancePolicy`.
3. Create `IslamicSundaySchoolAttendancePolicy`.
4. Create `AcademicCalendarProviderContract`.
5. Create default provider.
6. Bind Islamic Sunday School provider in `IslamicSundaySchoolServiceProvider`.
7. Update scanner service extraction plan to call policies instead of hardcoded date/session assumptions.
### Acceptance Criteria
- Islamic Sunday School program attendance logic lives outside core.
- Attendance duplicate window is policy-driven.
- Scanner code can call a policy without knowing the school type.
## Controller Dependency Rule
New or touched controllers should depend on contracts, not concrete domain services.
Good:
```php
public function __construct(private StudentServiceContract $students) {}
```
Bad:
```php
public function __construct(private IslamicSundaySchoolStudentService $students) {}
```
Also bad:
```php
public function store(Request $request)
{
$schoolId = auth()->user()->school_id;
// 120 lines of student creation logic
}
```
Because apparently controllers become landfill if left unsupervised.
## Architecture Tests
Add architecture tests using one of these approaches:
- Pest architecture tests if Pest is available.
- PHPUnit custom tests scanning namespaces/imports.
- Static analysis rule in PHPStan/Larastan if already installed.
### Required Architecture Tests
#### `SchoolCore` must not import `IslamicSundaySchool`
```php
public function test_school_core_does_not_depend_on_islamic_sunday_school(): void
{
$files = glob(app_path('Domain/SchoolCore/**/*.php'));
foreach ($files as $file) {
$contents = file_get_contents($file);
$this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool', $contents, $file);
}
}
```
#### Domain services must not use raw request
```php
public function test_domain_services_do_not_depend_on_http_request(): void
{
$files = glob(app_path('Domain/**/*.php'));
foreach ($files as $file) {
$contents = file_get_contents($file);
$this->assertStringNotContainsString('Illuminate\\Http\\Request', $contents, $file);
}
}
```
#### Domain services must not call `auth()`
```php
public function test_domain_services_do_not_call_auth_helper(): void
{
$files = glob(app_path('Domain/**/*.php'));
foreach ($files as $file) {
$contents = file_get_contents($file);
$this->assertStringNotContainsString('auth()', $contents, $file);
}
}
```
#### Core contracts must be neutral
Search contract names and method names for Islamic Sunday School vocabulary:
```text
masjid/community
ministry
sunday
bible
servant
service_day
```
Those words may appear in `IslamicSundaySchool`, not `SchoolCore`.
## Contract Review Checklist
Before approving a contract, answer:
- Is the name globally meaningful for any school?
- Does it require `SchoolContext`?
- Does it avoid `Request`?
- Does it avoid `auth()`?
- Does it avoid Eloquent leakage across module boundaries?
- Does it describe transaction ownership?
- Does it describe authorization expectations?
- Can Islamic Sunday School customize behavior without editing the contract?
- Can another school type use this contract without pretending to be a Islamic Sunday School?
## Implementation Tickets
### CC-001: Create Domain Namespace Skeleton
**Objective:** Create the folder and namespace structure for `SchoolCore`, `IslamicSundaySchool`, and `Shared`.
**Tasks:**
- Add directory structure.
- Add placeholder README files for each major module.
- Document dependency direction in `app/Domain/README.md`.
**Acceptance Criteria:**
- Namespace structure exists.
- Dependency rule is documented.
- No production behavior changes.
### CC-002: Create Core Contract Interfaces
**Objective:** Add first-pass contracts for students, attendance, finance, files, communication, academics, enrollment, and reporting.
**Tasks:**
- Add contracts under `App\Domain\SchoolCore\Contracts`.
- Add minimal DTO namespaces.
- Add docblocks for transaction and authorization expectations.
**Acceptance Criteria:**
- Contracts compile.
- Contracts use `SchoolContext`.
- Contracts do not mention Islamic Sunday School concepts.
### CC-003: Add Service Providers and Bindings
**Objective:** Bind contracts to initial implementations.
**Tasks:**
- Create `SchoolCoreServiceProvider`.
- Create `IslamicSundaySchoolServiceProvider`.
- Register providers.
- Bind first implementations.
**Acceptance Criteria:**
- Container resolves all initial contracts.
- Islamic Sunday School extension policies can override policy/provider contracts.
- Core service bindings remain neutral.
### CC-004: Add Architecture Boundary Tests
**Objective:** Prevent forbidden dependencies.
**Tasks:**
- Add tests for `SchoolCore -> IslamicSundaySchool` imports.
- Add tests preventing `Request` in domain services.
- Add tests preventing `auth()` in domain services.
- Add tests for forbidden vocabulary in `SchoolCore` contracts.
**Acceptance Criteria:**
- Tests fail when forbidden dependencies are introduced.
- Tests run in CI.
### CC-005: Migrate Student Identifier Generation Behind Contract
**Objective:** Fix student identifier race condition and introduce the first real core contract usage.
**Tasks:**
- Create `StudentIdentifierGeneratorContract`.
- Add default implementation.
- Update student creation flow.
- Add unique constraint.
- Add concurrency test.
**Acceptance Criteria:**
- No duplicate student identifiers under concurrent creation.
- Identifier format can be replaced by binding.
- Existing API response is preserved.
### CC-006: Migrate Payment File Access Behind Policy Contract
**Objective:** Use contract architecture to fix payment file authorization.
**Tasks:**
- Create `FileAccessPolicyContract`.
- Create payment-specific policy implementation.
- Replace filename-only access with payment-ID-based access.
- Add audit log.
- Add authorization tests.
**Acceptance Criteria:**
- Guessed filenames cannot access files.
- Cross-school file access fails.
- Authorized finance/admin access succeeds.
### CC-007: Add Attendance Policy Stub
**Objective:** Prepare attendance extraction by separating attendance rules from scanner implementation.
**Tasks:**
- Create `AttendancePolicyContract`.
- Create `AcademicCalendarProviderContract`.
- Add default and Islamic Sunday School implementations.
- Bind policy/provider implementations.
**Acceptance Criteria:**
- Islamic Sunday School attendance rules are outside `SchoolCore`.
- Scanner extraction can use contracts in Phase 4.
## Phase 2 Rollout Order
Use this order:
1. Create namespace skeleton.
2. Add core contracts.
3. Add DTO/read model skeletons.
4. Add providers and empty bindings.
5. Add architecture tests.
6. Migrate student identifier generation.
7. Migrate payment file access.
8. Add attendance policy stub.
9. Update modular platform plan with completed contracts.
## Phase 2 Done Definition
Phase 2 is complete when:
- Core contracts exist and compile.
- Initial service providers are registered.
- `SchoolCore` cannot depend on `IslamicSundaySchool` without failing tests.
- Domain services cannot use `Request` or `auth()` without failing tests.
- Student identifier generation is contract-based and race-safe.
- Payment file access uses policy-based authorization.
- Attendance has policy/provider contracts ready for extraction.
- Islamic Sunday School behavior is expressed through extension contracts, not hardcoded core logic.
## Risks
### Risk: Too many contracts too early
Mitigation: only create contracts that are needed by migration slices. Keep method count small.
### Risk: Interfaces mirror bad legacy services
Mitigation: design contracts around platform capabilities, not current class names.
### Risk: Islamic Sunday School rules leak into core
Mitigation: forbidden vocabulary tests and provider-based extension points.
### Risk: Controllers keep calling old services
Mitigation: each touched controller must move toward contract injection.
### Risk: DTO explosion
Mitigation: create DTOs for cross-module payloads only. Do not manufacture object bureaucracy for two strings and a prayer.
## Non-Goals
Phase 2 does not fully refactor finance, attendance, students, communication, or reporting.
Phase 2 does not remove every legacy service.
Phase 2 does not redesign every database table.
Phase 2 creates the contract layer and proves the boundary works through a few real migration slices.
## Final Notes
This phase must be boring on purpose. Contracts are not where creativity belongs. They are where the platform decides what it promises and what it refuses to know.
The most important rule: `SchoolCore` must remain generic. Islamic Sunday School extends it. It does not secretly define it.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff