44 KiB
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
.envor 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 typecheckpasses.npm run lintpasses.npm run testpasses with 51 files and 198 tests.npm run buildpasses.npm run test:e2epasses with 4 backend-free Playwright smoke tests.npm run test:e2e:contentexists 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 --testviatsx) 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 runnabledist/.- 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:
- Run
npm run db:migrateagainst the configured local database. - Run
npm run db:seed(current seed set; the final RBAC seed content is delivered in Workstream 4, which re-verifies this step). - Start backend with the documented env file.
- Verify public content catalog routes, auth routes, and product module routes respond.
- Record exact failing migration/seed/runtime errors if any.
Acceptance criteria:
npm run db:migratepasses.npm run db:seedpasses.- Backend starts without generated default secrets.
GET /api/public/content-catalog/:contentTypeworks 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 5–7 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:
- Audit all tenant-owned generated and product routes for organization scoping.
Resolve inconsistent— done: unified onorganizationsIdversusorganizationIdusageorganizationIdfor all models (renamed theusers.organizationsIdcolumn via migration20260609000000-rename-users-organizationsid-to-organizationid, updated db/api scoping, the auth DTO, and the frontendCurrentUsertype). Verify the tenant-scopingwherenow correctly targets each model'sorganizationId.- Ensure create/update/delete paths cannot accept another tenant's organization or campus.
- Ensure list/count/autocomplete endpoints are tenant-scoped.
- 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
rolestable, each carrying ascope, with preset permission sets and code-enforced relational constraints. TheGENERATED_ROLE_TO_PRODUCT_ROLE/STAFF_TYPE_TO_PRODUCT_ROLEmappings and the 5-valueproductRoleare 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+ aUserdefault-role name referenced in code.globalAccess: trueis set only onSuper AdministratorandAdministrator. These map down to 5productRolevalues (teacher | para | office | director | superintendent). - Authorization engine:
middlewares/check-permissions.tsgrants a request when (1)currentUser.id === req.params.id || req.body?.id(self-access), (2) acustom_permissionsrow matches, or (3) the effective role (assignedapp_role, else cachedPublic) has the${METHOD}_${ENTITY}permission. CRUD routers wirecheckCrudPermissions(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:
organizationIdon most models (unified, per Workstream 2). Campus link for a user is only indirect, via thestaffrow'scampusId;staff_typeisteacher | admin | support. There is nocampusIdonusersand 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 noorganizationId.20200430130760assigns 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 statesadmin@flatlogic.com(SEED_ADMIN_EMAIL) is the super admin. No staff profiles are seeded. - Frontend:
ModuleRouteGuard+ sidebar filter gate purely on the 5-valueproductRole(shared/constants/appData.tsMODULES[].roles,shared/constants/moduleRoutes.ts).CurrentUser.permissionsexists but is unused. There is no authentication guard (an unauthenticated user reaching the shell is not redirected to/login), andcanUserRoleAccessModuleRoutereturnstruefor unknown paths (forbidden routes fall through to the catch-all rather than failing at the guard). Only/loginand*(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):
super_admin— scopesystem. Unlimited across the whole platform.system_admin— scopesystem. Unlimited platform-wide except creating/deletingsuper_adminor othersystem_admin.owner— scopeorganization. Unlimited inside its own company.superintendent— scopeorganization. Unlimited inside the company except creating/deletingowneror othersuperintendent, and except deleting the company profile.director— scopecampus. Unlimited inside its assigned campus; creates campus users and classrooms; grants extra per-user permissions.office_manager— scopecampus. Reads all campus pages except the director dashboard; takes quizzes; leaves read receipts; may fill the attendance page.teacher— scopecampus. Reads all campus pages except the director dashboard.support_staff— scopecampus. Reads all campus pages except the director dashboard. (Replaces the currentparavalue.)student— scopeexternal. Reads only/community-partnerships,/vocational-opportunities,/esa-funding.guardian— scopeexternal. Same external pages asstudent.guest— unauthenticated. Public routes only (/login,/signup, error page); any other URL redirects to 404.
Required work:
- Add a
scopecolumn to therolesmodel/migration: enumsystem | organization | campus | external | guest. KeepglobalAccessfor the two system roles (it already short-circuits tenant filtering in the CRUD layer). - Replace the seeded role set with these 11 (plus keep
Publicas the unauthenticated fallback mapped toguest). Write a data migration mapping legacy roles → new roles (Super Administrator→super_admin,Administrator→system_admin,Platform Owner→owner,Tenant Director→superintendent,Campus Manager→director,Academic Coordinator→teacher,Finance Officer→office_manager;para/supportHR type →support_staff). - Retire
GENERATED_ROLE_TO_PRODUCT_ROLE,STAFF_TYPE_TO_PRODUCT_ROLE, andPRODUCT_ROLE_VALUES; the role name + scope is now the source of truth. ReplaceproductRolein the/api/auth/meDTO withrole: { name, scope }(keep a stable string the frontend can switch on). - Add a campus link for campus-scoped users. Recommended: a nullable
campusIdonusers(authorization scope), independent of the HRstaffrow.organizationIdremains the tenant key;campusIdis required fordirector/office_manager/teacher/support_staffand optional forstudent/guardian; null forsystem/organizationscopes. (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.
- Keep the CRUD × entity permissions; add
classrooms(alias ofclassesif the same table, otherwise a new entity) to CRUD. - 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. - Define the role → permission preset matrix (the "predefined permissions per role" from the spec) and seed it into
rolesPermissionsPermissions. Directors additionally hand out per-usercustom_permissions. - Document the matrix in
backend/docs/auth-profile.md(or a newbackend/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:
- On user create/update/delete, compare actor scope+role against the target's role:
system_admincannot create/deletesuper_adminorsystem_admin;superintendentcannot create/deleteownerorsuperintendent; campus roles cannot escalate above their scope; a director may only create/manage users within its owncampusId. - On organization delete, require
super_admin/system_admin/owner(asuperintendentis blocked). - Cross-scope tenancy: every actor may only act within its
organizationId(andcampusIdfor campus scope) unlessglobalAccess. - 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.
super_admin/system_admincreate anowneruser → auto-create the company (organization) and link the owner; both start minimal (owner has only email+password; company has only the owner link).owner/superintendentcreate superintendents, other org users, and campuses; assign adirectorto a campus.directorcreates campus users (teacher,support_staff,office_manager,student,guardian) and classrooms, and grants extracustom_permissions.- Every created user receives a login-link email; on first login they land on their own editable profile.
- Define which profile fields each role may self-edit vs. which require a higher role.
3.5 Backend guard coverage audit
- Confirm every route mounts authentication +
checkCrudPermissions/checkPermissions(cross-reference Workstream 5 public-route audit so only intended routes are public). - Add the §3.3 relational policy to the user, organization, campus, staff, and classroom write paths.
- Add the product-feature permission checks (§3.2) to the feature routes (FRAME, walkthrough, quizzes, attendance, communications, content-catalog, etc.).
- 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).
- AuthGuard. Wrap the shell: unauthenticated → redirect to
/login; only/login,/signup, and the error page are reachable without a session. - Expose permissions in the auth contract. Confirm
GET /api/auth/meand the sign-in response include the resolved effectivepermissions: string[](role permissions ∪custom_permissions); mergecustom_permissionsif not already; document the field inbackend/docs/auth-profile.md. - Shared permission vocabulary. Add a typed catalog on the frontend (
frontend/src/shared/auth/permissions.ts) mirroring backend${METHOD}_${ENTITY}names; keep it asshared/constants-style UI config; do not import backend code. - Permission selector/hook. Implement
hasPermission/hasAnyPermission/hasAllPermissionsinfrontend/src/business/auth/(pure functions overCurrentUser.permissions) plus ausePermissions()hook. - Gate routes by permission. Replace
productRole-basedcanUserRoleAccessModuleRoute/getAccessibleModuleswith permission checks; eachMODULES[]entry declares the permission(s) it requires. Keep a role concept only where genuinely role-specific (e.g. dashboards), not for resource access. - Forbidden direct-URL access redirects to the 404 page (change
canUserRoleAccessModuleRoute's unknown-pathreturn trueand the guard's redirect target); inaccessible sidebar items are hidden. - Encode the role-specific page sets.
office_managersees all campus pages except the director dashboard (plus quiz/read-receipt/attendance-fill affordances);teacher/support_staffsee all campus pages except the director dashboard;student/guardiansee only/community-partnerships,/vocational-opportunities,/esa-funding;director/superintendent/ownersee their full scope;guestsees only public routes. - Gate affordances + handle 403. Hide/disable create/edit/delete triggers by the matching permission (e.g. hide "Add campus" without
CREATE_CAMPUSES); surface backendforbiddenresponses through one handler (toast + no crash) so UI/permission drift degrades gracefully. - Roles/permissions admin UI. Let an admin/director create a role, attach/detach permissions, and assign
custom_permissionsto a user — backed by the existingroles/permissions/usersendpoints. - Docs. Update
frontend/docs/frontend-architecture.mdandbackend/docs/auth-profile.mdto describe the permission-based frontend model and the${METHOD}_${ENTITY}contract.
3.7 Tests
- Backend: relational-constraint tests (
system_admincannot deletesuper_admin;superintendentcannot deleteowner/superintendentor the company; director scoped to its campus), tenant/campus isolation, and the provisioning chain (owner-create auto-creates company). - 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. - 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
productRoleare 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, anddirector. - 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/testpass; 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:
- Reconcile the admin identity with the spec. Make
admin@flatlogic.com(SEED_ADMIN_EMAIL) thesuper_admin(the spec's superadmin), notAdministrator. Add a distinctsystem_adminseed 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. - Seed one organization (company) and one campus linked to it (give the existing
campusesseed rows anorganizationId, or seed a dedicated test campus). - Seed 10 users, one per stored role —
super_admin,system_admin,owner,superintendent,director,office_manager,teacher,support_staff,student,guardian— each withapp_roleIdset,organizationIdset (except the twosystemusers), andcampusIdset for the campus-scoped and external users as applicable. (guest= no user.) Passwords come fromSEED_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). - 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. - Make the seed idempotent and reversible (clean
down), and ordered after the role/permission seeder. - 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:seedproduces 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.comis thesuper_adminand 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:
- List all routes without auth middleware.
- Mark each as public-by-design or requiring auth.
- Add auth where required.
- 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 inbackend/src/index.ts. - Enumerated every frontend HTTP call. All frontend HTTP goes through
frontend/src/shared/api/*overhttpClient(fetch); there are no strayfetch/axios/XMLHttpRequestcalls elsewhere, so the frontend call list is exhaustive.
Findings:
- Frontend is fully wired: every
shared/apimethod 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/campusesCRUD 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):
- Generic CRUD template — 25 route groups × 9 endpoints = 225, none called:
academic_years,assessments,assessment_results,attendance_records,attendance_sessions,campuses(the/api/campusesCRUD),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. authextras — 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).file— 2, not called:GET /api/file/download,POST /api/file/upload/:table/:field(the frontend never uploads;DocumentMutationDtohas nofile).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:
- Generic CRUD groups: WIRE (decided above) — build the frontend that uses
them (e.g. real
students/staff/guardians/invoicesmanagement) and bring each group up to the target backend architecture. Do not prune them. authextras: keepsignup/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.file: keep only if document/avatar upload is on the roadmap (ties to Workstream 7); otherwise prune.search: prune unless a search UI is planned.- After any prune, re-run backend
typecheck/lint, and regeneratedatabase-schema.mdonly 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/lintgreen 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):
findByin all 24 generic-CRUDdb/api/*.tsnow loads its associations via a singlePromise.allinstead of sequential awaited getters (detail-endpoint latency drops from sum to max of the association queries).users.findBywas done earlier.- List pagination now has shared defaults via
@/shared/constants/pagination(resolvePagination, default page size 10 to match theref-frontendgrids, capped at 100). Applied to every generic-CRUDfindAlland to the feature lists (user_progress,safety_quiz_results,walkthrough_checkins,frame_entries,content_catalog,communicationsparent-messages/events,campus_attendanceconfigs), which were reverted fromfindAllback tofindAndCountAllsocountis the true total for the pager.staff_attendance /recordsandcampus_attendance /summarieskeep their pre-existing per-endpoint limits. - Fixed a latent CRUD tenant-scoping bug: routes and
db/apicreate/update readcurrentUser.organization?.id(singular), butfindByonly ever populatesorganizations(plural) + theorganizationIdscalar, so that read was alwaysundefined(non-global creates silently set no organization). All 22db/api+ 24 route reads now usecurrentUser.organizationId; the dead 3-field fallback term was dropped from the 4 feature services; and the non-existentorganization?: { id }field was removed from theCurrentUsertype so the mistake cannot recur. - The redundant existence-check
findByin the 22 CRUDupdateservices was removed; they now rely onDbApi.updatereturningnull(avoids loading every association just to validate existence).removealready had no pre-check. - The per-request
UsersDBApi.findBy(passport JWT strategy →req.currentUser, read by every guarded route) was collapsed fromfindOne+ 4 parallel association getters + agetPermissions()into a single eager-loaded query (findOne+includeofapp_role+permissions,staff_user,custom_permissions,organizations). Its returnedapp_rolenow carriespermissions, andmiddlewares/check-permissions.tswas reordered to read that eager-loadedpermissionsarray before falling back togetPermissions()— removing the extra per-request permissions query. Same returned shape/fields as before (AuthenticatedUser); ~6–7 queries per request → 1. The cachedPublicrole still works (its record carriespermissions). AuthService.currentUserProfile(theGET /me, signin and refresh responses) now uses a dedicatedUsersDBApi.findProfileById— a single eager-loaded query (findByPk+ scopedinclude/attributes) returning only the columns and relations the profile DTO reads — instead of the heavy genericfindBy(1findOne+ parallel association getters +getPermissions) plus a separategetCampus. Required idiomaticNonAttributeassociation declarations on theUsers/Roles/Staffmodels so the include is type-safe without casts. The per-request passportfindBythat populatesreq.currentUseris unchanged (it is the auth gate read by every guard);/mestill 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:
- Audit upload permissions.
- Audit download permissions.
- Verify tenant and document ownership checks.
- 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:
- Product onboarding/profile contract (Workstream 3 §3.4).
- Backend-seeded auth fixtures (Workstream 4).
- Tenant-scoped campus/staff fixtures (Workstream 4).
- Stable test credentials stored only in ignored local env or dedicated test seed config.
Required workflows after prerequisites:
- Login to dashboard.
- Director creates or edits a FRAME entry and sees it after reload.
- Staff completes QBS quiz and director/superintendent sees compliance.
- Office enters campus attendance and superintendent sees aggregate.
- Director submits walkthrough and sees summary update.
- 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:
- Document
/api/auth/signin/local. - Document
/api/auth/refresh. - Document
/api/auth/signout. - Document
/api/auth/me. - Document product module endpoints that are not covered by generated Swagger output.
- 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:
- Add axe/Playwright accessibility checks for login.
- Add axe/Playwright checks for dashboard.
- Add checks for sidebar navigation.
- Add checks for modal dialogs.
- Add checks for forms with validation.
- 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:
- Which policies/protocols require acknowledgment.
- Which roles must acknowledge.
- Whether acknowledgments are per document version.
- Who can report acknowledgment status.
Required work after decision:
- Add acknowledgment backend model/migration/service/route.
- Enforce tenant and role scope.
- Add frontend typed API and business workflow.
- 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:
- Whether campus attendance aggregates are manually entered, imported, or derived from student attendance records.
- Which external or internal source owns staff attendance writes/imports.
- Whether student-level attendance UI is required.
Required work after decision:
- Add write/import endpoints only after source contract exists.
- Keep derived summaries server-side if summaries are derived.
- Add backend tests for source-of-truth calculations.
- 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:
- Define backend provider contract.
- Keep provider secrets in backend env.
- Add backend service with native provider errors where required.
- 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:
- Define refresh-token retention period.
- Add cleanup job or operational command.
- Ensure cleanup is observable.
- 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:
- Choose target: minimal swap to
passport-google-oauth20, or consolidate both providers onopenid-client. - Replace the Google (and optionally Microsoft) passport strategy in
backend/src/auth/auth.ts. - Keep the existing cookie-based session and JWT issuance unchanged.
- Verify callback URLs, scopes, and the social-signup user flow end to end.
- 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:
- Confirm no active docs require
ref-frontend/for endpoint contract reference. - Confirm no scripts import or run
ref-frontend/. - Delete
ref-frontend/. - Update root docs and any setup docs.
Acceptance criteria:
- Only
frontend/andbackend/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.
- Phase 1 — Foundation: Workstream 1 (backend migrates, boots, serves routes).
- 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.
- Phase 3 — Verification & Quality: Workstream 8 (authenticated seeded e2e) → Workstream 9 (API docs) → Workstream 10 (accessibility).
- Phase 4 — Product Contracts (customer-gated, parallel): Workstreams 11 (acknowledgments), 12 (attendance source), 13 (generated audio).
- 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, anddirector; the legacy generated-role mapping and 5-valueproductRoleare 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.comas 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.