40227-vm/docs/full-integration-refactor-plan.md
2026-06-10 18:59:43 +02:00

44 KiB
Raw Blame History

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

The workstreams are numbered in dependency/execution order and grouped into five phases. Earlier phases unblock later ones; within a phase, items may run in parallel unless a dependency is stated. Renumbered from the previous flat backlog: the former Role Model Decision, Product Onboarding Contract, and Permission-Based Frontend Authorization workstreams are now merged into Workstream 3 (RBAC); the former standalone seed-verification step is merged into Workstream 4.

Phase 1 — Foundation (everything else depends on a backend that boots, migrates, and serves routes).

1. Backend Migration And Runtime Boot

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 (current seed set; the final RBAC seed content is delivered in Workstream 4, which re-verifies this step).
  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.

Phase 2 — Identity, Tenancy & Access Core (the security spine: tenant isolation, the role/permission model, the seeded fixtures, and the finalized protected API surface). Workstreams 57 finalize what is public, what exists, and what is permission-gated, so they belong with the access model rather than after it.

2. Tenant Boundary Audit And Tests

Status: open and high priority. Co-designed with Workstream 3: the campus/organization scoping here and the campusId-on-users model in §3.1 are the same scoping layer.

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. RBAC: Role Hierarchy, Scoped Provisioning, And Guards

Status: open and high priority. Umbrella workstream for the platform's user/role model. Merged here: the former Role Model Decision (role-model choice), the former Product Onboarding Contract (provisioning, §3.4), and the former Permission-Based Frontend Authorization (frontend guards, §3.6). Consumes the existing permission engine in middlewares/check-permissions.ts.

Decisions recorded (owner-approved):

  • Role model: first-class persisted roles in the roles table, each carrying a scope, with preset permission sets and code-enforced relational constraints. The GENERATED_ROLE_TO_PRODUCT_ROLE / STAFF_TYPE_TO_PRODUCT_ROLE mappings and the 5-value productRole are retired.
  • Frontend: permission-based guards plus an authentication guard; forbidden direct-URL access redirects to the 404 page; menu items the user cannot access are hidden.

3.0 Current state (analysis, verified)

  • Roles today (backend/src/db/seeders/20200430130760-user-roles.ts, backend/src/shared/constants/roles.ts): 7 generated roles (Super Administrator, Administrator, Platform Owner, Tenant Director, Campus Manager, Academic Coordinator, Finance Officer) + Public + a User default-role name referenced in code. globalAccess: true is set only on Super Administrator and Administrator. These map down to 5 productRole values (teacher | para | office | director | superintendent).
  • Authorization engine: middlewares/check-permissions.ts grants a request when (1) currentUser.id === req.params.id || req.body?.id (self-access), (2) a custom_permissions row matches, or (3) the effective role (assigned app_role, else cached Public) has the ${METHOD}_${ENTITY} permission. CRUD routers wire checkCrudPermissions(entity). This engine is sound and is kept.
  • Permission catalog: CRUD × 27 entities + READ_API_DOCS + CREATE_SEARCH. Product modules are not represented (no permission exists for the FRAME, walkthrough, quizzes, director-dashboard, attendance-fill, ESA/vocational/community pages, classrooms, etc.).
  • Multi-tenancy: organizationId on most models (unified, per Workstream 2). Campus link for a user is only indirect, via the staff row's campusId; staff_type is teacher | admin | support. There is no campusId on users and no first-class concept of director/owner/student/guardian.
  • Seeders: no organization row is seeded at all, and the seeded campuses (shared/constants/campuses.ts, 6 rows) have no organizationId. 20200430130760 assigns roles by raw SQL: super_admin@flatlogic.com→SuperAdmin, admin@flatlogic.com→Administrator, client@hello.com→PlatformOwner, john@doe.com→TenantDirector. This contradicts the product spec, which states admin@flatlogic.com (SEED_ADMIN_EMAIL) is the super admin. No staff profiles are seeded.
  • Frontend: ModuleRouteGuard + sidebar filter gate purely on the 5-value productRole (shared/constants/appData.ts MODULES[].roles, shared/constants/moduleRoutes.ts). CurrentUser.permissions exists but is unused. There is no authentication guard (an unauthenticated user reaching the shell is not redirected to /login), and canUserRoleAccessModuleRoute returns true for unknown paths (forbidden routes fall through to the catch-all rather than failing at the guard). Only /login and * (404) are public; there is no signup route.

3.1 Target role hierarchy and scope

Eleven roles across five scopes (guest is the unauthenticated case, not a stored user):

  1. super_admin — scope system. Unlimited across the whole platform.
  2. system_admin — scope system. Unlimited platform-wide except creating/deleting super_admin or other system_admin.
  3. owner — scope organization. Unlimited inside its own company.
  4. superintendent — scope organization. Unlimited inside the company except creating/deleting owner or other superintendent, and except deleting the company profile.
  5. director — scope campus. Unlimited inside its assigned campus; creates campus users and classrooms; grants extra per-user permissions.
  6. office_manager — scope campus. Reads all campus pages except the director dashboard; takes quizzes; leaves read receipts; may fill the attendance page.
  7. teacher — scope campus. Reads all campus pages except the director dashboard.
  8. support_staff — scope campus. Reads all campus pages except the director dashboard. (Replaces the current para value.)
  9. student — scope external. Reads only /community-partnerships, /vocational-opportunities, /esa-funding.
  10. guardian — scope external. Same external pages as student.
  11. guest — unauthenticated. Public routes only (/login, /signup, error page); any other URL redirects to 404.

Required work:

  1. Add a scope column to the roles model/migration: enum system | organization | campus | external | guest. Keep globalAccess for the two system roles (it already short-circuits tenant filtering in the CRUD layer).
  2. Replace the seeded role set with these 11 (plus keep Public as the unauthenticated fallback mapped to guest). Write a data migration mapping legacy roles → new roles (Super Administratorsuper_admin, Administratorsystem_admin, Platform Ownerowner, Tenant Directorsuperintendent, Campus Managerdirector, Academic Coordinatorteacher, Finance Officeroffice_manager; para/support HR type → support_staff).
  3. Retire GENERATED_ROLE_TO_PRODUCT_ROLE, STAFF_TYPE_TO_PRODUCT_ROLE, and PRODUCT_ROLE_VALUES; the role name + scope is now the source of truth. Replace productRole in the /api/auth/me DTO with role: { name, scope } (keep a stable string the frontend can switch on).
  4. Add a campus link for campus-scoped users. Recommended: a nullable campusId on users (authorization scope), independent of the HR staff row. organizationId remains the tenant key; campusId is required for director/office_manager/teacher/support_staff and optional for student/guardian; null for system/organization scopes. (Same scoping layer as Workstream 2.)

3.2 Permission catalog expansion

The frontend can only gate on permissions the backend actually checks, so every guarded page/action needs a permission name.

  1. Keep the CRUD × entity permissions; add classrooms (alias of classes if the same table, otherwise a new entity) to CRUD.
  2. Add product-feature permissions for the module routes and special actions, named in the existing ${VERB}_${THING} style — e.g. READ_DIRECTOR_DASHBOARD, FILL_ATTENDANCE, TAKE_QUIZ, ACK_READ_RECEIPT, READ_COMMUNITY_PARTNERSHIPS, READ_VOCATIONAL_OPPORTUNITIES, READ_ESA_FUNDING, plus one per remaining campus module page.
  3. Define the role → permission preset matrix (the "predefined permissions per role" from the spec) and seed it into rolesPermissionsPermissions. Directors additionally hand out per-user custom_permissions.
  4. Document the matrix in backend/docs/auth-profile.md (or a new backend/docs/rbac.md).

3.3 Relational role constraints (the "except …" rules)

The constraints are about who the target is, not just what verb on what entity, so a flat ${METHOD}_${ENTITY} permission cannot express them. Implement a policy layer enforced in the service layer (authoritative), with a middleware in front for early rejection:

  1. On user create/update/delete, compare actor scope+role against the target's role: system_admin cannot create/delete super_admin or system_admin; superintendent cannot create/delete owner or superintendent; campus roles cannot escalate above their scope; a director may only create/manage users within its own campusId.
  2. On organization delete, require super_admin/system_admin/owner (a superintendent is blocked).
  3. Cross-scope tenancy: every actor may only act within its organizationId (and campusId for campus scope) unless globalAccess.
  4. Fix the self-access bypass security hole: currentUser.id === req.body?.id (middlewares/check-permissions.ts, the self-access branch) lets any user pass a guard by putting their own id in the request body; restrict self-access to read/update of the caller's own profile only.

3.4 Scoped provisioning / onboarding contract (former Product Onboarding Contract)

Auto-provisioning chain, all backend-owned, each step sending an invitation email with a login link (services/email/list/invitation.ts already exists). No-go rules carried over from the former onboarding workstream: do not implement frontend self-registration; do not build profile creation/editing UI ahead of the backend contract; do not treat the generated signup/profile endpoints as the product contract; no temporary compatibility paths.

  1. super_admin/system_admin create an owner user → auto-create the company (organization) and link the owner; both start minimal (owner has only email+password; company has only the owner link).
  2. owner/superintendent create superintendents, other org users, and campuses; assign a director to a campus.
  3. director creates campus users (teacher, support_staff, office_manager, student, guardian) and classrooms, and grants extra custom_permissions.
  4. Every created user receives a login-link email; on first login they land on their own editable profile.
  5. Define which profile fields each role may self-edit vs. which require a higher role.

3.5 Backend guard coverage audit

  1. Confirm every route mounts authentication + checkCrudPermissions/checkPermissions (cross-reference Workstream 5 public-route audit so only intended routes are public).
  2. Add the §3.3 relational policy to the user, organization, campus, staff, and classroom write paths.
  3. Add the product-feature permission checks (§3.2) to the feature routes (FRAME, walkthrough, quizzes, attendance, communications, content-catalog, etc.).
  4. Ensure list/count/autocomplete are tenant- and campus-scoped (ties to Workstream 2).

3.6 Frontend guards (former Permission-Based Frontend Authorization)

Context: the backend already authorizes every request by permission, not role; the user DTO already carries permissions: string[], but the frontend ignores it and gates on the 5-value productRole. The goal is to gate UI affordances (routes/menu items/buttons/request triggers) by permission — using the same ${METHOD}_${ENTITY} names the backend checks — while the backend stays the sole source of truth (frontend gating is UX-only and must never be the only enforcement).

  1. AuthGuard. Wrap the shell: unauthenticated → redirect to /login; only /login, /signup, and the error page are reachable without a session.
  2. Expose permissions in the auth contract. Confirm GET /api/auth/me and the sign-in response include the resolved effective permissions: string[] (role permissions custom_permissions); merge custom_permissions if not already; document the field in backend/docs/auth-profile.md.
  3. Shared permission vocabulary. Add a typed catalog on the frontend (frontend/src/shared/auth/permissions.ts) mirroring backend ${METHOD}_${ENTITY} names; keep it as shared/constants-style UI config; do not import backend code.
  4. Permission selector/hook. Implement hasPermission/hasAnyPermission/hasAllPermissions in frontend/src/business/auth/ (pure functions over CurrentUser.permissions) plus a usePermissions() hook.
  5. Gate routes by permission. Replace productRole-based canUserRoleAccessModuleRoute/getAccessibleModules with permission checks; each MODULES[] entry declares the permission(s) it requires. Keep a role concept only where genuinely role-specific (e.g. dashboards), not for resource access.
  6. Forbidden direct-URL access redirects to the 404 page (change canUserRoleAccessModuleRoute's unknown-path return true and the guard's redirect target); inaccessible sidebar items are hidden.
  7. Encode the role-specific page sets. office_manager sees all campus pages except the director dashboard (plus quiz/read-receipt/attendance-fill affordances); teacher/support_staff see all campus pages except the director dashboard; student/guardian see only /community-partnerships, /vocational-opportunities, /esa-funding; director/superintendent/owner see their full scope; guest sees only public routes.
  8. Gate affordances + handle 403. Hide/disable create/edit/delete triggers by the matching permission (e.g. hide "Add campus" without CREATE_CAMPUSES); surface backend forbidden responses through one handler (toast + no crash) so UI/permission drift degrades gracefully.
  9. Roles/permissions admin UI. Let an admin/director create a role, attach/detach permissions, and assign custom_permissions to a user — backed by the existing roles/permissions/users endpoints.
  10. 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.

3.7 Tests

  1. Backend: relational-constraint tests (system_admin cannot delete super_admin; superintendent cannot delete owner/superintendent or the company; director scoped to its campus), tenant/campus isolation, and the provisioning chain (owner-create auto-creates company).
  2. Frontend: per-role route-guard tests (each role can/cannot open the right pages), sidebar-visibility tests, hasPermission/selector unit tests, unauthenticated→/login, and forbidden-URL→404.
  3. Backend-seeded authenticated e2e using the Workstream 4 fixtures (feeds Workstream 8) proving UI affordances match backend enforcement for at least one CRUD entity.

Acceptance criteria:

  • The 11 roles exist as first-class scoped roles; the legacy mapping and 5-value productRole are gone.
  • Backend is the sole authority: every route enforces authentication + permission + (where relational) the §3.3 policy; removing a frontend check never grants real access (backend still returns 403).
  • The "except …" constraints hold in tests for system_admin, superintendent, and director.
  • Frontend route/menu/affordance visibility is permission-driven; unauthenticated users are redirected to /login; forbidden direct URLs land on 404; guest sees only public routes.
  • An admin can create a role, assign permissions, and the change is reflected in both backend enforcement and frontend UI for affected users.
  • Owner-creation auto-creates the company; each provisioning step emails a login link.
  • Backend and frontend typecheck/lint/test pass; the Workstream 4 seed runs clean and backs an authenticated e2e.

4. RBAC Seed Data And Fixtures

Status: open — depends on Workstream 3 (run after the role/permission/model changes land). Implements the spec's seed requirement: a preset company with one campus and staff covering every role, plus one test user per role. Also re-verifies the npm run db:seed gate from Workstream 1 against the final seed content.

Required work:

  1. Reconcile the admin identity with the spec. Make admin@flatlogic.com (SEED_ADMIN_EMAIL) the super_admin (the spec's superadmin), not Administrator. Add a distinct system_admin seed user. Repurpose or delete the leftover template users (john@doe.com, client@hello.com, super_admin@flatlogic.com) so the seed set is exactly the role fixtures below.
  2. Seed one organization (company) and one campus linked to it (give the existing campuses seed rows an organizationId, or seed a dedicated test campus).
  3. Seed 10 users, one per stored rolesuper_admin, system_admin, owner, superintendent, director, office_manager, teacher, support_staff, student, guardian — each with app_roleId set, organizationId set (except the two system users), and campusId set for the campus-scoped and external users as applicable. (guest = no user.) Passwords come from SEED_ADMIN_PASSWORD/SEED_USER_PASSWORD (no plaintext in the repo); test credentials live only in ignored local env or a dedicated test-seed config (per Workstream 8).
  4. Seed staff profiles for the campus staff roles (director, office_manager, teacher, support_staff) so the campus has staff covering every staff role, each linked to the org, the campus, and its user.
  5. Make the seed idempotent and reversible (clean down), and ordered after the role/permission seeder.
  6. Re-verify the seeded data: one company, one campus under it, staff with all campus roles, and exactly one loginable user per role. Add a seed/integration check.

Acceptance criteria:

  • npm run db:seed produces exactly one company, one campus under that company, staff covering every campus role, and one user per role with correct role/org/campus links.
  • admin@flatlogic.com is the super_admin and can log in; each seeded role can log in and lands on the access surface defined in §3.6.
  • No template leftover users remain; no plaintext credentials are committed.

5. Public Backend Route Audit

Status: open. Feeds Workstream 3 §3.5 (guard coverage): the guard audit can only assert "every non-public route is authenticated" once the public set is fixed.

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.

6. API Surface Coverage And Dead-Endpoint Decision

Status: open (analysis complete; decision pending). Belongs with the access core: the permission catalog (§3.2) and guard audit (§3.5) must cover exactly the endpoints that survive this classification.

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 24 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 3 §3.4 provisioning); otherwise prune. OAuth callbacks tie to Workstream 15.
  3. file: keep only if document/avatar upload is on the roadmap (ties to Workstream 7); 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 7 (file upload), 5 (public route audit), 15 (OAuth), 3 (RBAC / 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); ~67 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.

7. File Upload And Download Permissions

Status: open. Permission-gated surface; aligns with Workstream 3 §3.2/§3.5 and Workstream 6 (file extras).

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.

Phase 3 — Verification & Quality (locks in correctness once the access model and fixtures exist).

8. Backend-Seeded Authenticated E2E

Status: depends on Workstream 4 fixtures and Workstream 3 guards.

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 (Workstream 3 §3.4).
  2. Backend-seeded auth fixtures (Workstream 4).
  3. Tenant-scoped campus/staff fixtures (Workstream 4).
  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.

9. API Documentation Hardening

Status: open. Run after the route surface is finalized (Workstreams 3, 5, 6) so the docs describe the real endpoints.

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.

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

Phase 4 — Product Contracts (each gated by a customer/provider decision; independent of one another and parallelizable once the access core is in place).

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

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

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

Phase 5 — Operations, Modernization & Cleanup (low coupling; can run any time after Phase 2, scheduled last to avoid mixing with the access-model changes).

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

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

Phased Execution Order

Run in workstream-number order unless the user explicitly reprioritizes. The phases are the dependency gates; within a phase, items may proceed in parallel.

  1. Phase 1 — Foundation: Workstream 1 (backend migrates, boots, serves routes).
  2. Phase 2 — Identity, Tenancy & Access Core: Workstream 2 (tenant isolation) → Workstream 3 (RBAC: roles, permissions, constraints, provisioning, guards) → Workstream 4 (RBAC seed/fixtures), with Workstream 5 (public route audit), Workstream 6 (API surface classify/wire), and Workstream 7 (file permissions) finalizing the protected surface alongside Workstream 3.
  3. Phase 3 — Verification & Quality: Workstream 8 (authenticated seeded e2e) → Workstream 9 (API docs) → Workstream 10 (accessibility).
  4. Phase 4 — Product Contracts (customer-gated, parallel): Workstreams 11 (acknowledgments), 12 (attendance source), 13 (generated audio).
  5. Phase 5 — Operations, Modernization & Cleanup: Workstreams 14 (refresh-token cleanup), 15 (OAuth modernization), 16 (ref-frontend/ removal).

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.
  • The 11-role scoped hierarchy (Workstream 3) is enforced backend-side, including the relational "except …" constraints for system_admin, superintendent, and director; the legacy generated-role mapping and 5-value productRole are removed.
  • Backend role/permission tests cover product staff, director, and superintendent paths.
  • Frontend gating is permission-driven with an authentication guard; unauthenticated users redirect to /login, forbidden direct URLs land on 404, and guest sees only public routes.
  • The RBAC seed (Workstream 4) provisions one company, one campus with staff covering every campus role, and one loginable user per role, with admin@flatlogic.com as the super admin.
  • Every unauthenticated backend route is documented as public-by-design.
  • Product onboarding contract is implemented per Workstream 3 §3.4 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.