# Full Integration Refactor Plan

## Purpose

This document is the active cross-application backlog for integrating `frontend/`, `backend/`, and the PostgreSQL database.

It must track only unfinished integration work and hard implementation rules. Completed migration history belongs in the feature-specific docs under `backend/docs/`, `frontend/docs/`, and supporting audit docs.

When an item is completed, remove it from this plan and update the owning feature documentation instead of adding a completed checklist entry here.

## Current Application Shape

- `frontend/` is the only product frontend.
- `backend/` is the source of truth for auth, tenant ownership, roles, permissions, users, staff profiles, campuses, persisted workflows, content catalogs, email, files, and database access.
- `ref-frontend/` is a temporary generated reference. It is not runtime code and must be deleted after all needed endpoint-contract reference value is exhausted.

## Non-Negotiable Rules

- No frontend persisted workflow may use mock, sample, seed, or fallback records.
- No frontend runtime code may import backend seed files.
- Frontend runtime constants may contain only UI config, route/module metadata, query keys, timing values, style tokens, static UI labels, and intentionally static product copy.
- Backend seeds are for database seeding only.
- Frontend seed data is allowed only in tests or `frontend/src/test-seeds/`.
- Backend owns tenant scoping and permissions. Frontend route hiding is UX only.
- All frontend backend calls go through typed modules in `frontend/src/shared/api/`.
- New frontend workflows must follow `View -> Business Logic -> API/Data Access -> Backend`.
- View components must not import `frontend/src/shared/api/`.
- Business logic must not import view components.
- API modules must not import business or view code.
- All imports use the `@` alias.
- No `any`, unsafe casts, disabled TypeScript rules, disabled ESLint rules, compatibility bypasses, silent failures, or legacy re-export surfaces.
- Frontend auth must use backend-owned HttpOnly cookies only. Access and refresh tokens must never be stored in frontend browser storage.
- Secrets live only in backend `.env` or deployment environment variables. Frontend env values must be public browser-safe values only.
- New backend modules must include migration/model/service/route, tenant and role enforcement, docs, and focused verification.
- Every changed workflow must update the relevant docs before it is considered complete.

## Current Verification Baseline

Frontend current baseline:

- `npm run typecheck` passes.
- `npm run lint` passes.
- `npm run test` passes with 51 files and 198 tests.
- `npm run build` passes.
- `npm run test:e2e` passes with 4 backend-free Playwright smoke tests.
- `npm run test:e2e:content` exists for backend-seeded content catalog integration tests. It requires backend migrations, backend seeders, and a running backend server.

Backend current baseline:

- Runtime code is 100% TypeScript + native ESM; the JS->TS / CJS->ESM migration is complete.
- `npm run typecheck` (`tsc --noEmit`) passes.
- `npm run lint` (`eslint .`) passes with no broad ignores.
- `npm test` (`node --test` via `tsx`) passes: 2 files, 15 tests (error-handler + import-boundaries).
- `npm run verify` (typecheck + lint + test) is the combined gate and is green.
- `npm run build` (`tsc` + `tsc-alias -f` + email-template asset copy) produces a runnable `dist/`.
- Migrations/seeders run via Umzug (`npm run db:migrate`, `npm run db:seed`); a run against the configured local database is still pending (Workstream 1).

## Active Workstreams

### 1. Backend Migration And Runtime Verification

Status: open.

Problem:

Backend product modules exist, but the configured local database migration/seed run has not been verified as a passing gate in this plan.

Required work:

1. Run `npm run db:migrate` against the configured local database.
2. Run `npm run db:seed`.
3. Start backend with the documented env file.
4. Verify public content catalog routes, auth routes, and product module routes respond.
5. Record exact failing migration/seed/runtime errors if any.

Acceptance criteria:

- `npm run db:migrate` passes.
- `npm run db:seed` passes.
- Backend starts without generated default secrets.
- `GET /api/public/content-catalog/:contentType` works for the required seeded content catalog types.
- Any remaining backend runtime blocker is captured as a specific follow-up with file/error references.

### 2. Tenant Boundary Audit And Tests

Status: open and high priority.

Problem:

Generated backend code has partial tenant scoping. Multi-tenant correctness cannot rely on frontend filtering.

Required work:

1. Audit all tenant-owned generated and product routes for organization scoping.
2. ~~Resolve inconsistent `organizationsId` versus `organizationId` usage~~ — done: unified on `organizationId` for all models (renamed the `users.organizationsId` column via migration `20260609000000-rename-users-organizationsid-to-organizationid`, updated db/api scoping, the auth DTO, and the frontend `CurrentUser` type). Verify the tenant-scoping `where` now correctly targets each model's `organizationId`.
3. Ensure create/update/delete paths cannot accept another tenant's organization or campus.
4. Ensure list/count/autocomplete endpoints are tenant-scoped.
5. Add backend tests proving cross-tenant records are not visible or mutable.

Acceptance criteria:

- Tenant isolation tests cover users, campuses, staff, students, attendance, messages, documents, and product module tables.
- A non-global user cannot read or mutate another tenant's data.
- Campus-scoped users cannot mutate another campus unless their backend permission explicitly allows it.

### 3. Role Model Decision

Status: open.

Problem:

Backend currently maps generated roles to product roles. It is not yet a final product decision whether this mapping is the supported model or product roles should become first-class persisted backend roles/permissions.

Required decision:

- Option A: keep generated-role to product-role mapping as the supported model.
- Option B: create first-class persisted product roles: `Teacher`, `Para`, `Office Manager`, `Director`, `Superintendent`.

Required work after decision:

1. Document the chosen role model.
2. Align `/api/auth/me` permissions and product role payload with the chosen model.
3. Add backend role/permission tests for staff, director, and superintendent paths.
4. Keep frontend navigation driven by backend role/permissions.

Acceptance criteria:

- Role model is documented in backend docs.
- Backend tests prove role access for product modules.
- No frontend role grants capability that backend does not enforce.

### 4. Product Onboarding Contract

Status: blocked by customer decision.

Problem:

Generated auth signup/profile endpoints exist, but they do not define the product workflow for company creation, campus creation, user creation, staff profile creation, role assignment, campus assignment, or profile updates.

No-go rules:

- Do not implement frontend registration.
- Do not implement profile creation/editing UI.
- Do not treat generated signup/profile endpoints as the product onboarding contract.
- Do not add temporary compatibility paths.

Required customer decisions:

1. Who creates a company.
2. Who creates campuses.
3. Who invites or creates users.
4. How staff profiles are created and linked to users.
5. How roles and campuses are assigned.
6. Which profile fields users may update themselves.
7. Which profile fields require director/superintendent/admin permissions.

Required work after decision:

1. Add backend onboarding contract and docs.
2. Add tenant-scoped backend workflows.
3. Add frontend typed API modules.
4. Add frontend business hooks.
5. Add UI only after backend contract exists.
6. Add authenticated Playwright workflows using backend-seeded fixtures.

Acceptance criteria:

- Onboarding workflow is customer-approved and documented.
- Backend owns all creation, assignment, and permission checks.
- Frontend only exposes flows backed by typed backend contracts.

### 5. Refresh Token Maintenance

Status: open.

Problem:

Cookie auth and refresh rotation exist, but scheduled cleanup for expired/revoked refresh-token rows remains unresolved.

Required work:

1. Define refresh-token retention period.
2. Add cleanup job or operational command.
3. Ensure cleanup is observable.
4. Document operational usage.

Acceptance criteria:

- Expired and revoked refresh-token rows are cleaned after the approved retention window.
- Cleanup failures are visible and not silent.
- Auth behavior remains unchanged for valid sessions.

### 6. API Documentation Hardening

Status: open.

Problem:

Markdown docs exist for migrated modules, but Swagger/OpenAPI coverage for product-specific and cookie-session endpoints is incomplete.

Required work:

1. Document `/api/auth/signin/local`.
2. Document `/api/auth/refresh`.
3. Document `/api/auth/signout`.
4. Document `/api/auth/me`.
5. Document product module endpoints that are not covered by generated Swagger output.
6. Document response and error shapes per endpoint.

Acceptance criteria:

- API docs match actual route payloads and response shapes.
- Cookie auth behavior is explicit.
- Frontend API contract tests remain aligned with docs.

### 7. Policy And Safety Acknowledgment Persistence

Status: open pending product contract.

Problem:

Policy content is document-backed, but policy/protocol acknowledgments are not yet persisted.

Required decisions:

1. Which policies/protocols require acknowledgment.
2. Which roles must acknowledge.
3. Whether acknowledgments are per document version.
4. Who can report acknowledgment status.

Required work after decision:

1. Add acknowledgment backend model/migration/service/route.
2. Enforce tenant and role scope.
3. Add frontend typed API and business workflow.
4. Add report views only if required.

Acceptance criteria:

- Acknowledgments survive reload.
- Acknowledgment status is tenant-scoped.
- Unauthorized roles cannot view individual acknowledgment records.

### 8. Attendance Source Contracts

Status: partially open.

Current state:

- Campus attendance daily aggregate summaries are implemented for the current UI.
- Staff attendance snapshot/reporting is read-only.
- Student/class attendance source-of-truth workflow is not defined.

Open decisions:

1. Whether campus attendance aggregates are manually entered, imported, or derived from student attendance records.
2. Which external or internal source owns staff attendance writes/imports.
3. Whether student-level attendance UI is required.

Required work after decision:

1. Add write/import endpoints only after source contract exists.
2. Keep derived summaries server-side if summaries are derived.
3. Add backend tests for source-of-truth calculations.
4. Add frontend workflows only after backend contracts exist.

Acceptance criteria:

- Attendance source of truth is documented.
- UI values can be traced to backend records or server-side derivation.
- No frontend-only attendance source remains in persisted workflows.

### 9. Generated Audio Provider Contract

Status: open pending provider decision.

Problem:

Classroom timer uses built-in Web Audio sounds. AI-generated audio UI is intentionally not exposed because no backend audio provider contract exists.

No-go rules:

- Do not add generated audio UI.
- Do not call external audio providers from frontend runtime.
- Do not add frontend API keys or provider secrets.

Required work after decision:

1. Define backend provider contract.
2. Keep provider secrets in backend env.
3. Add backend service with native provider errors where required.
4. Add typed frontend API and explicit loading/error states.

Acceptance criteria:

- Generated audio is backend-mediated.
- No provider secret reaches the browser.
- Provider failures remain visible.

### 10. File Upload And Download Permissions

Status: open.

Problem:

Backend file and document routes exist. Product file workflows need explicit permission verification before new upload/download UI is added.

Required work:

1. Audit upload permissions.
2. Audit download permissions.
3. Verify tenant and document ownership checks.
4. Add typed frontend upload client only for approved workflows.

Acceptance criteria:

- Unauthorized users cannot access another tenant's files.
- Upload/download behavior is documented and tested before new UI is added.

### 11. Public Backend Route Audit

Status: open.

Problem:

Public routes must be intentionally public. This includes public content catalog routes and any generated/template public integrations.

Required work:

1. List all routes without auth middleware.
2. Mark each as public-by-design or requiring auth.
3. Add auth where required.
4. Document intentionally public routes.

Acceptance criteria:

- Every unauthenticated backend route is documented.
- No tenant-owned data is exposed through accidental public routes.

### 12. Backend-Seeded Authenticated E2E

Status: blocked by onboarding/profile fixtures.

Problem:

Current Playwright coverage includes backend-free smoke tests and backend-seeded content catalog tests. Authenticated persisted workflows need backend-seeded users, roles, campuses, staff profiles, and known credentials.

Required prerequisites:

1. Product onboarding/profile contract.
2. Backend-seeded auth fixtures.
3. Tenant-scoped campus/staff fixtures.
4. Stable test credentials stored only in ignored local env or dedicated test seed config.

Required workflows after prerequisites:

1. Login to dashboard.
2. Director creates or edits a FRAME entry and sees it after reload.
3. Staff completes QBS quiz and director/superintendent sees compliance.
4. Office enters campus attendance and superintendent sees aggregate.
5. Director submits walkthrough and sees summary update.
6. Staff marks a sign learned and progress persists after reload.

Acceptance criteria:

- Authenticated e2e tests use backend seeds, not frontend mock data.
- Tests do not require production secrets.
- Tests are documented and repeatable.

### 13. Accessibility Test Coverage

Status: open.

Required work:

1. Add axe/Playwright accessibility checks for login.
2. Add axe/Playwright checks for dashboard.
3. Add checks for sidebar navigation.
4. Add checks for modal dialogs.
5. Add checks for forms with validation.
6. Add checks for tables/reports.

Acceptance criteria:

- Accessibility tests run in a documented command.
- Critical violations block completion of the refactor.

### 14. `ref-frontend/` Removal

Status: open.

Required work:

1. Confirm no active docs require `ref-frontend/` for endpoint contract reference.
2. Confirm no scripts import or run `ref-frontend/`.
3. Delete `ref-frontend/`.
4. Update root docs and any setup docs.

Acceptance criteria:

- Only `frontend/` and `backend/` remain as active application code.
- No docs describe `ref-frontend/` as needed for normal development.

### 15. OAuth Provider Strategy Modernization

Status: open. Deferred until after the backend TypeScript/ESM migration (now complete), to keep auth-flow changes separate from the language/module migration.

Problem:

Social login uses `passport-google-oauth2` (0.2.0, last published 2022) and `passport-microsoft` (2.1.0). The Google strategy is low-maintenance and should be modernized, ideally consolidating both providers on a single maintained OAuth/OIDC library.

Required work:

1. Choose target: minimal swap to `passport-google-oauth20`, or consolidate both providers on `openid-client`.
2. Replace the Google (and optionally Microsoft) passport strategy in `backend/src/auth/auth.ts`.
3. Keep the existing cookie-based session and JWT issuance unchanged.
4. Verify callback URLs, scopes, and the social-signup user flow end to end.
5. Update auth docs (`backend/docs/auth-profile.md`, `cookie-auth.md`).

Acceptance criteria:

- Google and Microsoft sign-in work end to end with the chosen library.
- No deprecated/unmaintained OAuth strategy remains in dependencies.
- Cookie/JWT auth behavior is unchanged for existing sessions.
- This change is isolated from the language/module migration (separate PR/task).

### 16. Permission-Based Frontend Authorization

Status: open.

Problem:

The backend already authorizes every request by **permission**, not by role. `backend/src/middlewares/check-permissions.ts` exposes `checkCrudPermissions(entity)`, which derives a permission name `${METHOD}_${ENTITY}` (e.g. `READ_CAMPUSES`, `CREATE_USERS`, `DELETE_ROLES` via `METHOD_MAP`) and grants access when any of the following holds, in order: (1) self-access — `currentUser.id === req.params.id`/`req.body.id`; (2) the user has a direct `custom_permissions` entry with that name; (3) the user's `app_role` (or the `Public` role for unauthenticated/no-role requests) has that permission via `role.getPermissions()`. An admin can therefore create roles and attach permissions (`Roles ↔ Permissions` many-to-many through `RolesDBApi.setPermissions`), plus assign per-user `custom_permissions`.

The frontend, however, does **not** gate on permissions. Module/page access is currently decided by `productRole` (`teacher | para | office | director | superintendent`) in `frontend/src/business/app-shell/selectors.ts` (`canAccessModule(modules, moduleId, userRole)`), and per the architecture contract frontend route hiding is **UX-only**. The user DTO already carries a `permissions: string[]` array (built by `getPermissionNames` in `services/auth.ts`), but the frontend ignores it. This makes the frontend authorization model inconsistent with the backend: a role with customized permissions is correctly enforced by the backend but not reflected in what the UI shows or allows.

Goal: make the frontend gate UI affordances (routes/menu items/buttons/request triggers) by **permission** — using the same `${METHOD}_${ENTITY}` permission names the backend checks — while keeping the backend as the sole source of truth (frontend gating stays UX-only; it must never be the only enforcement).

Required work:

1. **Expose permissions reliably in the auth contract.** Confirm `GET /api/auth/me` and the sign-in response include the resolved `permissions: string[]` (effective permissions = role permissions ∪ `custom_permissions`). Add `custom_permissions` to the resolution if not already merged. Document the exact field in `backend/docs/auth-profile.md`.
2. **Define a shared permission vocabulary.** Add a typed catalog of permission names on the frontend (`frontend/src/shared/auth/permissions.ts`) mirroring backend naming `${METHOD}_${ENTITY}` (READ/CREATE/UPDATE/DELETE × entity). Keep it in `shared/constants`-style UI config; do not import backend code.
3. **Add a permission selector/hook.** Implement `hasPermission(user, permissionName)` and `hasAnyPermission/hasAllPermissions` in `frontend/src/business/auth/` (pure functions over `CurrentUser.permissions`), plus a `usePermissions()` hook for components. Include the self-access nuance only where relevant (the backend allows self-access regardless of permission).
4. **Gate routes by permission.** Replace/augment role-based `canAccessModule` with permission-based checks. Each route/module declares the permission(s) it requires; the router redirects/hides when the user lacks them. Keep `productRole` only where a true role concept is still needed (e.g. dashboards), not for resource access.
5. **Gate UI affordances.** Hide or disable create/edit/delete buttons and other action triggers based on the matching permission (e.g. hide "Add campus" without `CREATE_CAMPUSES`). Avoid firing requests the backend will reject with 403.
6. **Handle 403 consistently.** Ensure the API layer surfaces backend `forbidden` responses to a single handler (toast + no crash), so permission drift between UI and backend degrades gracefully.
7. **Admin UI for roles/permissions.** Verify the roles management screens let an admin create a role, attach/detach permissions, and assign `custom_permissions` to a user — backed by the existing `roles`/`permissions`/`users` endpoints.
8. **Tests.** Unit-test `hasPermission`/selectors with permission fixtures; add route-guard tests proving a user without `READ_X` cannot open module X; add a backend-seeded e2e proving UI affordances match backend enforcement for at least one CRUD entity.
9. **Docs.** Update `frontend/docs/frontend-architecture.md` and `backend/docs/auth-profile.md` to describe the permission-based frontend model and the `${METHOD}_${ENTITY}` contract.

Acceptance criteria:

- Frontend route/menu/affordance visibility is driven by the user's effective permissions, using the same names the backend enforces.
- The backend remains the sole authority: removing a frontend check never grants real access (backend still returns 403).
- An admin can create a role, assign permissions, and the change is reflected in both backend enforcement and frontend UI for affected users.
- `productRole`-based gating is removed for resource access (kept only for genuine role-specific UI), with no remaining role↔permission inconsistency.
- Frontend `typecheck`, `lint`, `test` pass; a seeded e2e proves UI/permission alignment for at least one entity.

### 17. API Surface Coverage And Dead-Endpoint Decision

Status: open (analysis complete; decision pending).

Problem:

The backend exposes the full Flatlogic-generated CRUD surface for all 39 models plus
template auth/file/search routes, but the product frontend only consumes a small set of
custom feature endpoints. The unused surface is dead code and attack surface; it must be
either pruned or intentionally wired.

Method (how this was established):

- Enumerated every backend route from `backend/src/routes/*` plus its mount prefix in `backend/src/index.ts`.
- Enumerated every frontend HTTP call. All frontend HTTP goes through `frontend/src/shared/api/*` over `httpClient` (`fetch`); there are no stray `fetch`/`axios`/`XMLHttpRequest` calls elsewhere, so the frontend call list is exhaustive.

Findings:

- **Frontend is fully wired:** every `shared/api` method targets a real, mounted backend endpoint. There are **0 orphan/broken frontend calls**.
- **Backend is only partially consumed:** of ~278 endpoints, the frontend uses **~35 (~13%)**; **~243 (~87%) have no frontend consumer.**
- The mismatch is one-directional: the frontend calls nothing extra; the backend carries a large unused layer.

Consumed endpoints (the only wired surface — 35):

- `auth` (4 of 16): `POST /api/auth/signin/local`, `GET /api/auth/me`, `POST /api/auth/refresh`, `POST /api/auth/signout`.
- `campuses` (1): `GET /api/public/campuses` (the authenticated `/api/campuses` CRUD is NOT used).
- `content-catalog` (6): `GET /api/public/content-catalog/:contentType`, `GET /api/content-catalog`, `GET /api/content-catalog/:contentType`, `POST /api/content-catalog`, `PUT /api/content-catalog/:contentType`, `DELETE /api/content-catalog/:contentType`.
- `campus_attendance` (4): `GET /configs`, `PUT /configs/:campusKey`, `GET /summaries`, `PUT /summaries/:campusKey/:date`.
- `communications` (4): `GET /parent-messages`, `POST /parent-messages`, `GET /events`, `POST /events`.
- `frame_entries` (3): `GET /`, `POST /`, `PUT /:id`.
- `personality_quiz_results` (3): `GET /me`, `PUT /me`, `GET /distribution`.
- `safety_quiz_results` (2): `GET /`, `POST /`.
- `staff_attendance` (2): `GET /records`, `GET /summary`.
- `user_progress` (3): `GET /`, `POST /`, `DELETE /by-item`.
- `walkthrough_checkins` (3): `GET /`, `POST /`, `DELETE /:id`.

Unused backend endpoints (no frontend consumer):

1. **Generic CRUD template — 25 route groups × 9 endpoints = 225, none called:**
   `academic_years`, `assessments`, `assessment_results`, `attendance_records`,
   `attendance_sessions`, `campuses` (the `/api/campuses` CRUD), `classes`,
   `class_enrollments`, `class_subjects`, `fee_plans`, `grades`, `guardians`,
   `invoices`, `message_recipients`, `messages`, `organizations`, `payments`,
   `permissions`, `roles`, `staff`, `students`, `subjects`, `timetable_periods`,
   `timetables`, `users`.
   Each exposes the identical shape: `POST /`, `POST /bulk-import`, `PUT /:id`,
   `DELETE /:id`, `POST /deleteByIds`, `GET /`, `GET /count`, `GET /autocomplete`,
   `GET /:id`.
2. **`auth` extras — 12, not called:** `POST /api/auth/signup`, `PUT /api/auth/profile`,
   `PUT /api/auth/password-reset`, `PUT /api/auth/password-update`,
   `POST /api/auth/send-password-reset-email`,
   `POST /api/auth/send-email-address-verification-email`,
   `PUT /api/auth/verify-email`, `GET /api/auth/email-configured`,
   `GET /api/auth/signin/google` (+ `/callback`), `GET /api/auth/signin/microsoft` (+ `/callback`).
3. **`file` — 2, not called:** `GET /api/file/download`, `POST /api/file/upload/:table/:field`
   (the frontend never uploads; `DocumentMutationDto` has no `file`).
4. **`search` — 1, not called:** `GET /api/search`.

**Decision (owner, recorded):** the generic CRUD layer is **WIRE — kept, not
pruned.** These ~24 groups are not dead code; they will be used and integrated
with the frontend later (likely modeled on `ref-frontend`). No generic CRUD group
is to be removed. The `auth`/`file`/`search` extras (items 2–4 below) remain
individually decision-gated.

Required work:

1. Generic CRUD groups: **WIRE** (decided above) — build the frontend that uses
   them (e.g. real `students`/`staff`/`guardians`/`invoices` management) and bring
   each group up to the target backend architecture. Do **not** prune them.
2. `auth` extras: keep `signup`/password-reset/verify-email only if onboarding/recovery is
   in scope (see Workstream 4); otherwise prune. OAuth callbacks tie to Workstream 15.
3. `file`: keep only if document/avatar upload is on the roadmap (ties to Workstream 10);
   otherwise prune.
4. `search`: prune unless a search UI is planned.
5. After any prune, re-run backend `typecheck`/`lint`, and regenerate
   `database-schema.md` only if models change.

Cross-references: Workstream 10 (file upload), 11 (public route audit), 15 (OAuth), 16
(permission-based authorization).

Acceptance criteria:

- Every backend endpoint is classified as **used**, **prune**, or **wire (planned)**, with no “unclassified” remainder.
- Pruned routes are removed cleanly (route + service + db/api + permission wiring) with `typecheck`/`lint` green and no dangling imports.
- The frontend remains fully wired (0 orphan calls) after changes.
- This document reflects the final surface.

Performance hardening applied (owner chose **wire**, not prune, for the generic CRUD layer — it will back near-term management UIs modeled on `ref-frontend`):

- `findBy` in all 24 generic-CRUD `db/api/*.ts` now loads its associations via a single `Promise.all` instead of sequential awaited getters (detail-endpoint latency drops from sum to max of the association queries). `users.findBy` was done earlier.
- List pagination now has shared defaults via `@/shared/constants/pagination` (`resolvePagination`, default page size 10 to match the `ref-frontend` grids, capped at 100). Applied to every generic-CRUD `findAll` and to the feature lists (`user_progress`, `safety_quiz_results`, `walkthrough_checkins`, `frame_entries`, `content_catalog`, `communications` parent-messages/events, `campus_attendance` configs), which were reverted from `findAll` back to `findAndCountAll` so `count` is the true total for the pager. `staff_attendance /records` and `campus_attendance /summaries` keep their pre-existing per-endpoint limits.
- Fixed a latent CRUD tenant-scoping bug: routes and `db/api` create/update read `currentUser.organization?.id` (singular), but `findBy` only ever populates `organizations` (plural) + the `organizationId` scalar, so that read was always `undefined` (non-global creates silently set no organization). All 22 `db/api` + 24 route reads now use `currentUser.organizationId`; the dead 3-field fallback term was dropped from the 4 feature services; and the non-existent `organization?: { id }` field was removed from the `CurrentUser` type so the mistake cannot recur.
- The redundant existence-check `findBy` in the 22 CRUD `update` services was removed; they now rely on `DbApi.update` returning `null` (avoids loading every association just to validate existence). `remove` already had no pre-check.
- The per-request `UsersDBApi.findBy` (passport JWT strategy → `req.currentUser`, read by every guarded route) was collapsed from `findOne` + 4 parallel association getters + a `getPermissions()` into a **single** eager-loaded query (`findOne` + `include` of `app_role`+`permissions`, `staff_user`, `custom_permissions`, `organizations`). Its returned `app_role` now carries `permissions`, and `middlewares/check-permissions.ts` was reordered to read that eager-loaded `permissions` array before falling back to `getPermissions()` — removing the extra per-request permissions query. Same returned shape/fields as before (`AuthenticatedUser`); ~6–7 queries per request → 1. The cached `Public` role still works (its record carries `permissions`).
- `AuthService.currentUserProfile` (the `GET /me`, signin and refresh responses) now uses a dedicated `UsersDBApi.findProfileById` — a single eager-loaded query (`findByPk` + scoped `include`/`attributes`) returning only the columns and relations the profile DTO reads — instead of the heavy generic `findBy` (1 `findOne` + parallel association getters + `getPermissions`) plus a separate `getCampus`. Required idiomatic `NonAttribute` association declarations on the `Users`/`Roles`/`Staff` models so the include is type-safe without casts. The per-request passport `findBy` that populates `req.currentUser` is unchanged (it is the auth gate read by every guard); `/me` still performs that auth fetch plus this one lean profile query.

## Strict Implementation Sequence

Use this order unless the user explicitly reprioritizes:

1. Backend migration and seed verification.
2. Tenant boundary audit and tests.
3. Role model decision and enforcement tests.
4. Public route audit.
5. API documentation hardening.
6. Product onboarding after customer decision.
7. Authenticated backend-seeded e2e after onboarding/profile fixtures exist.
8. Remaining optional product contracts: acknowledgments, attendance imports, generated audio, file upload/download UI.
9. Accessibility test coverage.
10. Remove `ref-frontend/`.
11. OAuth provider strategy modernization.
12. Permission-based frontend authorization (align frontend gating with backend permission checks).
13. API surface coverage decision: classify every endpoint and prune or wire the unused backend layer (Workstream 17).

## Definition Of Done

The integration refactor is complete only when all of the following are true:

- Backend migrations and seeders pass on the configured local database.
- Backend lint passes without broad ignores.
- Backend tenant isolation tests prove cross-tenant data is not visible or mutable.
- Backend role/permission tests cover product staff, director, and superintendent paths.
- Every unauthenticated backend route is documented as public-by-design.
- Product onboarding contract is customer-approved or explicitly excluded from release scope.
- Every visible frontend workflow is backed by a typed backend API contract.
- No frontend runtime workflow depends on mock, sample, seed, or fallback records for persisted behavior.
- Frontend `typecheck`, `lint`, `test`, `build`, `test:e2e`, and documented seeded e2e suites pass in their required environments.
- Backend auth/API docs match actual route behavior.
- Required accessibility checks pass.
- `ref-frontend/` is deleted or a dated, explicit exception explains why it is still needed.
