# 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.
