38 KiB
AGENTS.md — Africa Alert PWA
Audience. This file is the single source of truth for any AI coding agent (or human contributor) working in this repository. Read it end-to-end before making changes.
Status. Living document. Update it in the same PR that changes the architecture, the coding standards, or the team-agent roster.
1. Project Overview
1.1 Purpose
Africa Alert is a Progressive Web App (PWA) school-management system for African schools. It unifies the four stakeholders of a school — administrators, teachers, students, and parents — into one role-aware interface, and ships with offline support so it works in low-connectivity environments (the primary deployment context is Zimbabwe, based on the Paynow payment-gateway integration at https://www.paynow.co.zw).
1.2 Key Business Goals
| Goal | What it means in code |
|---|---|
| Unified school operations | One app for student records, classes, attendance, fees, messaging, calendar, exams, payroll, inventory, hostel, transport, and front-office. |
| Offline-first PWA | The React client must install and run without a network. A local SQLite mirror keeps working data available offline; a sync engine reconciles to Supabase when online. |
| Role-based experience | Four user roles (admin / teacher / student / parent) with distinct dashboards, navigation, and permissions. |
| Cashless fee collection | Integration with Paynow Zimbabwe (Ecocash, Visa, etc.) so parents can pay fees online. |
| Easy deployment | One-command docker-compose up for non-technical school operators. |
1.3 High-Level Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Africa Alert System │
└─────────────────────────────────────────────────────────────────────┘
Browser / Mobile PWA
┌──────────────────────────────────────────┐
│ React 18 + Vite + TypeScript │
│ - React Router 6 (role-gated routes) │
│ - Zustand stores (auth, api, exams, …) │
│ - Axios HTTP client │
│ - vite-plugin-pwa (service worker) │
│ - Recharts, Lucide icons │
└──────────────┬───────────────────────────┘
│ /api/* (Vite dev proxy :3001)
▼
┌──────────────────────────────────────────┐ ┌─────────────────────┐
│ Node.js 20 + Express API (port 3001) │ sync │ Supabase (Postgres)│
│ - JWT auth (jsonwebtoken) │ ◀────▶ │ api.next_gen. │
│ - bcrypt password hashing │ 30 s │ techarvest.co.zw │
│ - 12 controllers (one per module) │ └─────────────────────┘
│ - SQLite (better-sqlite3) local mirror │ ▲
│ - SyncEngine (push/pull, server-wins) │ ───────────────┘
│ - Paynow integration
│ - Multer file uploads
└──────────────────────────────────────────┘
│
▼
./data/school.db (SQLite, persistent volume in Docker)
All in one container via multi-stage Dockerfile + docker-compose.
Architectural style. A classic three-tier monolith (React SPA → Express REST → SQLite) with a bidirectional sync adapter to a managed Postgres (Supabase) for backup and cross-device replication. There is no microservice decomposition; modules are Express Router() instances mounted under /api/<module>.
2. Technology Stack
2.1 Frontend (client/)
- Language: TypeScript 5.3+
- Framework: React 18.2 (function components + hooks)
- Build tool: Vite 5 (
vite.config.ts) - Routing: react-router-dom 6
- State management: Zustand 4 (with
persistmiddleware for auth) - HTTP client: Axios 1
- Charts: Recharts 2
- Icons: lucide-react
- PWA: vite-plugin-pwa (Workbox runtime caching, autoUpdate)
2.2 Backend (server/)
- Runtime: Node.js 20 (Alpine in Docker)
- Framework: Express 4
- Storage: SQLite via
better-sqlite312 (WAL mode,foreign_keys = ON) - Auth:
jsonwebtoken9 (HS256, 7-day expiry) +bcryptjs2 - CORS:
cors2 (currently wide-open; see §6 Security) - Uploads:
multer1.4 - Cloud sync:
@supabase/supabase-js2 - IDs:
uuid9 (for syncuidcolumns) - Dev loop:
nodemon3
2.3 Database
- Primary (local): SQLite (single file at
server/data/school.db, mode WAL). - Replica (cloud): Supabase Postgres. Push/pull through the Supabase REST API.
- Schema source of truth:
server/src/database/init.js(CREATE TABLE IF NOT EXISTS — see §10 for migration plan). - Soft-delete + sync metadata on every table:
last_synced_at DATETIME, sync_status TEXT DEFAULT 'pending' CHECK(sync_status IN ('synced','pending','conflict')), is_deleted INTEGER DEFAULT 0
2.4 Infrastructure
- Containerization: Docker (multi-stage
Dockerfile—client-build→server-build→production) + Docker Compose v3.8. - Base image:
node:20-alpine. - Persistent volumes:
./data(SQLite) and./uploads(file uploads). - Ports: 3000 (static client via
npx serve), 3001 (API). - Entrypoint:
docker-entrypoint.sh— runs DB init on first boot, then launches API + static server.
2.5 DevOps
- CI/CD: None configured. Recommended: GitHub Actions — see §13.
- Backups: Manual
cp -r data data-backup-…(documented inDOCKER_README.md). No automation. - Monitoring / health check: Not implemented. Recommended:
/api/healthendpoint, see §13. - Secrets management: Currently via
docker-compose.ymlenv block (with a hardcoded fallback secret — see §6). Recommended: Docker secrets or a.envfile withdotenv(NOT committed).
2.6 AI/ML Components
- None. No ML models, embeddings, or AI services are integrated. The system is purely transactional. If you add AI features (e.g. attendance anomaly detection, grade prediction), add a new sub-section here.
3. Team AI Agents
These are the persistent agent roles this repo expects AI agents to assume. Each role is scoped so two agents can work in parallel without stepping on each other.
3.1 Architect Agent
Owns: client/src/App.tsx, client/vite.config.ts, server/src/index.js, docker-compose.yml, Dockerfile, docker-entrypoint.sh, AGENTS.md.
Responsibilities:
- System architecture and module boundaries.
- Scalability (SQLite → Postgres migration, sync sharding, multi-tenant tenancy).
- Security review (authn, authz, secrets, CORS, rate limiting, payload validation).
- Design decisions and ADRs (architecture-decision records — see §11).
- Cross-cutting concerns: error handling, logging, observability.
Out of scope: Implementing feature CRUD — that's the Backend / Frontend agents.
3.2 Backend Agent
Owns: server/src/** (controllers, services, middleware, db), server/package.json.
Responsibilities:
- Express API development.
- Business logic, validation, transactions.
- Database schema and migrations (when extracted from
init.js). - Sync engine correctness (push/pull ordering, conflict resolution).
- Backend testing (Jest / Vitest + supertest, once set up — see §13).
- Paynow integration and webhook security.
Out of scope: React UI, deployment infrastructure.
3.3 Frontend Agent
Owns: client/src/** (components, pages, stores, hooks), client/package.json, client/vite.config.ts, PWA manifest, service-worker config.
Responsibilities:
- UI / UX implementation.
- Accessibility (WCAG 2.1 AA — semantic HTML, focus management, ARIA only when needed).
- State management (Zustand stores) and API caching.
- Offline / service-worker behavior (Workbox strategies, runtime caching).
- Frontend testing (Vitest + React Testing Library, once set up — see §13).
- Form validation and user-facing error handling.
Out of scope: Server endpoints, DB schema.
3.4 DevOps Agent
Owns: Dockerfile, docker-compose.yml, docker-entrypoint.sh, .github/ or .gitlab/ (when created), CI/CD pipelines, backup scripts, deployment docs.
Responsibilities:
- CI/CD pipelines (lint → typecheck → test → build → deploy).
- Container hardening (non-root user, healthcheck, multi-stage size optimization).
- Infrastructure-as-code (when promoted beyond Docker Compose).
- Monitoring / observability (logs, metrics, traces).
- Backups (automated SQLite snapshots + restore drills).
- Secrets management (
.env, Docker secrets, vault).
Out of scope: Application logic.
3.5 QA Agent
Owns: tests/ (once created), test plans, regression suites, performance baselines.
Responsibilities:
- Test plans for each user role (admin / teacher / student / parent).
- Automated test suite (unit + integration + e2e — Playwright recommended for e2e).
- Regression testing on every PR.
- Performance testing (especially the exam timer and bulk attendance endpoints).
- Accessibility audits (axe-core, manual screen-reader checks).
Out of scope: Writing new feature code; QA flags defects, the owning agent fixes them.
3.6 Documentation Agent
Owns: README.md, DOCKER_README.md, IMPLEMENTATION_SUMMARY.md, SCREENS.md, this file, OpenAPI/Swagger specs (when added), ADRs in docs/adr/.
Responsibilities:
- Keep
README.mdin sync with the running app. - Maintain
SCREENS.mdas the canonical UI map (it currently lists screens and the permissions matrix — keep that current as features land). - Generate / curate API documentation (OpenAPI from route comments or a
swagger-jsdocsetup). - Architecture diagrams (use Mermaid in
.mdfiles — they render in GitHub / GitLab). - Runbooks for common operations (DB reset, sync repair, backup restore).
Out of scope: Writing the code that the docs describe; that is owned by the implementing agent.
4. Coding Standards
4.1 Naming Conventions
| Layer | Convention | Example |
|---|---|---|
| React components | PascalCase, file matches export | AdminDashboard.tsx exports AdminDashboard |
| Hooks | useX camelCase |
useAuthStore |
| Zustand stores | camelCase, use prefix |
useExamsStore |
| TypeScript types / interfaces | PascalCase | interface User { … } |
| Express controllers | camelCase + .controller.js |
exams.controller.js |
| Express services | camelCase + .service.js |
syncEngine.js (existing convention) — keep aligned |
| Database columns | snake_case | first_name, sync_status |
| Environment variables | SCREAMING_SNAKE_CASE | JWT_SECRET, SUPABASE_URL |
| Branches | feature/<kebab> or fix/<kebab> |
feature/online-exams, fix/login-redirect |
4.2 Folder Organization
- All backend code under
server/src/. Subfolders:controllers/,services/,database/,middleware/,routes/(when split out). - All frontend code under
client/src/. Subfolders:components/,pages/,store/,hooks/,utils/,services/. - Role-specific pages live under
client/src/pages/<role>/(admin / teacher / student / parent). Cross-role pages stay atclient/src/pages/. - Shared modules (e.g. an
Avatarused by every role) go inclient/src/components/, never in a role subfolder. - Database artifacts (
init.js, future migrations) live inserver/src/database/. Do not scatter SQL across controllers.
4.3 Commit Message Format
Use Conventional Commits:
<type>(<scope>): <short imperative summary>
<body — explain WHY, not what>
<footer — breaking changes, issue refs>
- Types:
feat,fix,refactor,docs,test,chore,ci,perf,build. - Scope examples:
client,server,db,docker,auth,exams,paynow,sync. - Example:
feat(exams): auto-grade multiple-choice on submit.
4.4 Documentation Requirements
- Every public module (controller, store, hook, component used across roles) gets a JSDoc / TSDoc header explaining its purpose and one example.
- Every non-trivial function gets a short JSDoc with
@param/@returnsfor edge cases. - Every API route is documented in
docs/API.md(or via generated OpenAPI — see §13). - Every architectural decision gets an ADR in
docs/adr/NNNN-title.md(usedocs/adr/0001-record-architecture-decisions.mdas the template).
4.5 Testing Requirements
- Every new controller has at least one integration test (supertest) covering the happy path and one error case.
- Every new store has a unit test for each action.
- Every new page has a smoke test (renders without crashing) and an interaction test for the primary user flow.
- Bug fixes add a regression test that fails before the fix and passes after.
- Coverage gate: target ≥ 70 % lines on the backend, ≥ 60 % on the frontend (raise over time — see §13).
5. Pull Request Rules
A PR is mergeable only when all of the following are true. The Author is responsible for self-checking; the Reviewer verifies.
5.1 Review Requirements
- Minimum 1 reviewer for changes under 200 lines, 2 reviewers for ≥ 200 lines or any change touching
server/src/index.js,client/src/App.tsx,Dockerfile,docker-compose.yml, orserver/src/database/init.js. - The owning agent (per §3) must approve changes in their owned area.
- The Architect agent must approve any change that crosses module boundaries or modifies the auth / sync layer.
5.2 Testing Requirements
- All new and modified code paths are covered by tests (see §4.5).
- The full test suite passes locally AND in CI.
- No tests are skipped or
.onlyleft in.
5.3 Security Checks
- No secrets in code or committed
.envfiles. Verify withgit diff --stagedbefore pushing. - Any new dependency has been audited (
npm audit --omit=devclean; criticals = 0). - Any new endpoint goes through
authand (where applicable) role-check middleware. - User-supplied input is validated; raw SQL is parameterized (it already is in this repo — keep that standard).
- Webhook handlers (Paynow, future Supabase webhooks) verify signatures.
5.4 Documentation Checks
README.mdandSCREENS.mdupdated if the user-visible behaviour changes.- This
AGENTS.mdupdated if the architecture, standards, or agent roster changes. - New API endpoints documented in
docs/API.md(or OpenAPI). - ADRs added for any non-obvious decision.
5.5 Lint / Typecheck / Build
npm run lint(or the configured linter) — 0 errors.npm run typecheck(frontend) — 0 errors.npm run build— succeeds.
6. Definition of Done
A user story is Done when all of the following hold:
- Code builds successfully —
docker-compose build(ornpm run buildin each subproject) succeeds. - Tests pass — full test suite green, coverage meets the gate in §4.5.
- Documentation updated — README, SCREENS, and AGENTS touched where applicable.
- Security checks completed — secrets scan clean, deps audited, auth in place, input validated.
- Linting passes — 0 lint errors, 0 type errors.
- Demo verified — the change has been exercised end-to-end against a running dev environment.
- PR merged — at least one approval, CI green, branch deleted.
7. Development Workflow
7.1 Branch Strategy
This repo follows a strict two-branch model — Git Flow style, with main as production.
| Branch | Purpose | Pushable from | Invariant |
|---|---|---|---|
dev |
Work and test. All day-to-day work, new features, refactors, and bug fixes land here. The integration branch for in-progress code. | Any local commit / merge | May be broken. May have unreleased features. |
main |
Production. Only receives code that has been tested on dev. Every commit on main MUST be deployable. |
A green test run against dev, followed by a deliberate merge |
Must always be deployable. No broken builds, no half-finished features, no WIP commits. |
feature/<kebab> |
A new feature. | — | Branched from dev. Merged back into dev (PR). |
fix/<kebab> |
A bug fix. | — | Branched from dev. Merged back into dev (PR). |
hotfix/<kebab> |
An emergency fix that must reach production without going through the full dev test cycle. |
— | Branched from main. Merged into both main and dev so dev doesn't drift. Used sparingly. |
Promotion path: dev → main. Promote only after:
- All required tests pass on
dev(whatever the team has agreed — currently there are no automated tests, so this is at minimum: manual smoke + a greendocker-compose up). The audit recommends a real test suite — see §13.4. - The
devtip is reviewed and accepted. - A human (the maintainer) merges
devintomainwith a deliberate commit (no fast-forward). Tag the resultingmaintip as a release (see §7.3).
Why this matters. If you commit straight to main, or merge an untested feature into main, you break the "always deployable" invariant. Subsequent deployments will ship broken code. Treat any direct commit to main as a release-blocker.
Branch names: kebab-case, ≤ 50 chars, no prefixes like my/. Use the worktree-management skill — every code change goes in .worktrees/feature-<name>/ (or fix-<name>, hotfix-<name>).
7.2 PR Workflow
- Pick or open an issue describing the change.
- Branch from
dev(notmain) unless this is a hotfix (§7.1). - Implement + test locally.
- Run the Definition of Done checklist (§6).
- Open a PR targeting
dev. Title in Conventional Commits format. Description links the issue and lists the user-visible behaviour change. - Address review feedback.
- Merge via squash (or whatever the team agrees on). Delete the source branch.
- Promote to
mainseparately, after a green test run ondev(§7.1).
7.3 Release Workflow
mainis the source of release tags. When you promotedev→mainand that merge is a release candidate:- Tag the new
maintip asvMAJOR.MINOR.PATCH(semver). - Maintain a
CHANGELOG.mdper release (group: Added / Changed / Fixed / Removed).
- Tag the new
- Backwards-incompatible changes bump MAJOR and write an ADR + migration guide.
7.4 Deployment Workflow
- Container image is the unit of deployment.
Dockerfileproduces one image that runs both API and static client. docker-compose up -don the target host. Persistent volumes:data/,uploads/.- Pre-deploy checklist:
- DB backup taken (
cp -r data data-backup-$(date +%F)). .envfile present and validated (no missing required vars).- Image tag matches the release tag.
- DB backup taken (
- Post-deploy: run smoke tests, verify
/api/health(once added), check sync status (GET /api/sync/status).
8. Security Guidelines
8.1 Secret Management
- Never commit secrets. The current repo already has a hardcoded JWT secret in
docker-compose.yml(africa-alert-production-secret-2024) and inserver/src/index.js(africa-alert-secret-key-2024). Action required — see §13.3. - Use environment variables. Provide an
.env.example(NOT.env) that lists every required var. - Production secrets live in a secret manager (Docker secrets, HashiCorp Vault, cloud KMS). Rotate JWT secrets at least quarterly.
- The
Supabase service keyandPaynow integration keyare particularly sensitive — never log them, never return them to the client.
8.2 Dependency Scanning
npm audit --omit=devon every PR. Block on anyhighorcriticalCVE.- Dependabot / Renovate configured to open weekly PRs.
- Pin major versions in
package.json; letpackage-lock.jsonpin exact.
8.3 Authentication Standards
- All
/api/*routes (except/api/auth/loginand/api/auth/register) require a valid JWT in theAuthorization: Bearer <token>header. - Passwords stored with bcrypt (cost factor ≥ 10). Never store plaintext.
- Tokens expire in 7 days (current setting). Add refresh-token flow for longer sessions.
- Rate-limit
/api/auth/login(e.g.express-rate-limit, 5 attempts per 15 min per IP) to slow credential stuffing. - Invalidate tokens on logout (blocklist or short-lived access tokens + refresh tokens).
8.4 Authorization Standards
- Every endpoint must check both authentication (
authmiddleware) and role where applicable. - Use a role-based check helper:
requireRole('admin', 'teacher')— do not sprinkleif (req.user.role !== 'admin')across controllers (the codebase already has this duplication — see §13.2). - Never trust client-supplied user IDs. Always read
req.user.idfor the "current user". - Resource-level access (a student reading their own grade) must be enforced in the controller, not only in the UI.
9. AI Collaboration Rules
These rules apply to every AI agent (Mavis rein, GitHub Copilot, or any other tool) working in this repo. They are non-negotiable.
- Do not modify unrelated files. Stay inside the area you own (§3). Cross-cutting changes require an Architect-agent review.
- Explain major architectural changes in an ADR before writing code. Surface the trade-offs in the PR description.
- Create tests for every new feature in the same change. No untested code lands on
dev. - Update documentation when changing functionality. README, SCREENS, AGENTS, and the API doc all reflect what the code actually does.
- Prefer maintainability over cleverness. A 50-line explicit controller beats a 10-line abstract one that nobody can debug at 2am.
- Ask before destructive changes. Deleting files, force-pushing, dropping tables, hard-resetting the DB — always confirm with the user first.
- Read this file end-to-end before your first change in the repo. If anything is unclear or out of date, flag it in the PR.
- Use the worktree workflow. All code changes go in
.worktrees/feature-<name>/— never in the main checkout. Seeworktree-managementskill. - Reference code with
path:linewhen discussing it in chat or PRs — never paste a wall of context. - Be conservative with new dependencies. Each new
package.jsonentry increases the supply-chain surface. Justify it.
10. Repository Knowledge Base
A condensed map of the codebase. New agents: read this section first.
10.1 Important Modules
| Module | Frontend | Backend Controller | Notes |
|---|---|---|---|
| Auth | client/src/store/auth.ts |
POST /api/auth/login, /register in server/src/index.js |
JWT + bcrypt; 7-day expiry |
| Users | client/src/pages/Students.tsx, Teachers.tsx, admin/Users.tsx |
controllers/users.controller.js |
Roles: admin / teacher / student / parent (+ accountant / librarian / nurse in schema) |
| Classes & Subjects | client/src/pages/Classes.tsx |
inline in server/src/index.js (classes + subjects) |
|
| Enrollments | — | inline in server/src/index.js |
|
| Attendance | client/src/pages/Attendance.tsx |
inline + controllers/attendance.controller.js |
Supports bulk upsert |
| Fees | client/src/pages/Fees.tsx |
inline + controllers/paynow.controller.js |
Paynow integration for online payment |
| Messages | client/src/pages/Messages.tsx |
controllers/messages.controller.js |
|
| Calendar / Events | client/src/pages/Events.tsx |
controllers/calendar.controller.js |
|
| Exams | client/src/pages/exams/ExamViews.tsx + client/src/store/exams.ts |
controllers/exams.controller.js |
Full exam lifecycle: groups → questions → schedules → attempts → grading |
| Assignments | client/src/store/assignments.ts |
controllers/assignments.controller.js |
|
| Grades | client/src/pages/student/Grades.tsx |
controllers/grades.controller.js |
|
| Reports | client/src/pages/admin/Reports.tsx |
controllers/reports.controller.js |
|
| Settings | client/src/pages/admin/Settings.tsx |
controllers/settings.controller.js |
|
| Departments | client/src/pages/admin/Departments.tsx |
controllers/departments.controller.js |
|
| Payroll & HR | (frontend TBD) | DB only (init.js) |
8 tables: staff_roles, salary_grades, staff_records, leave_types, leave_requests, staff_attendance, payroll_runs, payslips |
| Inventory | (frontend TBD) | DB only | 7 tables: item_categories, suppliers, store_locations, items, item_stock, stock_transactions, item_issues |
| Hostel | (frontend TBD) | DB only | 6 tables |
| Transport | (frontend TBD) | DB only | 5 tables |
| Front Office | (frontend TBD) | DB only | 5 tables |
| Sync | — | services/SyncEngine.js |
Bidirectional, server-wins conflict policy, 30 s interval |
10.2 Key Services
- SyncEngine (
server/src/services/SyncEngine.js) — singleton (getSyncEngine()). Runs everySYNC_INTERVALms (default 30000). Pushes localsync_status = 'pending'rows to Supabase REST, pulls remoteupdated_at >= lastSyncrows back. Logs errors tosync_logs. Exposes/api/sync/statusand/api/sync/force. - Paynow client (
server/src/controllers/paynow.controller.js) — integrates withhttps://www.paynow.co.zw/interface/initiatetransaction. Endpoints:POST /initiate,GET /status/:id,POST /webhook,GET /student/:id,DELETE /:id. - Auth — JWT signed with
JWT_SECRET(env), 7-day expiry. Role embedded in payload.
10.3 Database Schema (39+ tables)
Full schema lives in server/src/database/init.js (1675 lines). All tables share the sync columns: uid, last_synced_at, sync_status, is_deleted. Core groups:
- Core (11): users, classes, subjects, enrollments, attendance, fee_groups, student_fees, payments, messages, events, expenses, grades, sync_logs, sync_config, system_settings.
- Payroll & HR (8): staff_roles, salary_grades, staff_records, leave_types, leave_requests, staff_attendance, payroll_runs, payslips.
- Inventory (7): item_categories, suppliers, store_locations, items, item_stock, stock_transactions, item_issues.
- Exams (5): exam_groups, question_banks, exam_schedules, exam_attempts, exam_answers.
- Hostel (6): hostels, room_types, rooms, room_assignments, hostel_fees.
- Transport (5): vehicles, routes, pickup_points, vehicle_routes, transport_allocations.
- Front Office (5): admission_enquiries, visitor_logs, phone_call_logs, postal_dispatch, complaints.
User roles supported by the schema CHECK constraint: admin, teacher, student, parent, accountant, librarian, nurse. The frontend currently only authenticates the first four.
10.4 API Structure
- All routes mounted under
/api/<module>inserver/src/index.js. - 12 controller routers + 2 inline route blocks (auth, dashboard stats) + 2 inline CRUD blocks (classes, subjects, enrollments, attendance, fees, messages, events, expenses).
- Response shape: JSON. Errors are
{ "error": "<message>" }(no error code, no stack trace in production — see §13.4). - All authenticated routes set
req.user = { id, email, role }from the JWT.
10.5 Infrastructure Components
- Dockerfile — multi-stage:
client-build(Vite build) →server-build(npm ci) →production(Alpine runtime, copies built artefacts, runsdocker-entrypoint.sh). - docker-compose.yml — single
appservice. Mounts./dataand./uploadsas volumes. Exposes 3000 (client) and 3001 (API). Currently hardcodesJWT_SECRET— see §13.3. - docker-entrypoint.sh — initializes DB on first run, starts API in background, then
npx servefor the static client.
10.6 Environment Variables
| Name | Default | Required | Purpose |
|---|---|---|---|
PORT |
3001 |
no | API port |
JWT_SECRET |
⚠️ hardcoded fallback | yes (prod) | Signs JWTs. MUST be set in production. |
DB_PATH |
./data/school.db |
no | SQLite path |
SUPABASE_URL |
https://api.next_gen.techarvest.co.zw |
no | Cloud sync target |
SUPABASE_KEY |
empty | no (offline mode if missing) | Cloud sync auth |
SYNC_INTERVAL |
30000 |
no | Sync period in ms |
PAYNOW_INTEGRATION_ID |
empty | yes (prod) | Paynow merchant id |
PAYNOW_INTEGRATION_KEY |
empty | yes (prod) | Paynow signing key |
PAYNOW_RETURN_URL |
http://localhost:3000/fees |
no | Post-payment redirect |
PAYNOW_BLOCKING_URL |
http://localhost:3000/api/payments/webhook |
no | Webhook target |
NODE_ENV |
production (in Docker) |
no | Standard Node env |
11. Architecture Decision Records (ADRs)
Place new ADRs in docs/adr/NNNN-<title>.md. Use MADR or a simplified template:
# NNNN. <Title>
- **Status:** Proposed | Accepted | Superseded by NNNN
- **Date:** YYYY-MM-DD
- **Deciders:** <agents / humans>
## Context
<what's the situation>
## Decision
<what we chose>
## Consequences
<what becomes easier, what becomes harder>
Suggested first ADRs to write:
- 0001 — Use SQLite as the local mirror + Supabase as cloud (current model).
- 0002 — Server-wins conflict resolution in sync.
- 0003 — JWT-only auth (no session cookies).
- 0004 — Single-container deployment.
- 0005 — Role-based access (admin / teacher / student / parent).
12. Onboarding Checklist (for any new agent)
Use this when picking up the repo fresh:
- Read this
AGENTS.mdend-to-end. - Skim
README.md,SCREENS.md,IMPLEMENTATION_SUMMARY.md,DOCKER_README.md. - Run
docker-compose up -dand log in asadmin@school.com / admin123. - Click through every dashboard and module once.
- Read
server/src/index.jsandserver/src/services/SyncEngine.js. - Read
client/src/App.tsxandclient/src/store/auth.ts. - Read
server/src/database/init.js(it's long — focus on theCHECKconstraints and the sync columns). - Skim one controller per module (e.g.
exams.controller.js,paynow.controller.js). - Run the dev servers in worktrees per the
worktree-managementskill. - Pick a small, scoped issue from the backlog and follow the PR workflow in §7.
13. Recommendations (action items for the human maintainer)
The audit identified the following. Treat them as a prioritised backlog — not a demand. Every recommendation has a one-line rationale.
13.1 Architecture Improvements (priority: medium → high)
- M1 — Move schema out of
init.jsinto versioned migrations (usenode-pg-migratestyle orknex).init.jsis 1675 lines and grows linearly with every new module. Why: currentCREATE TABLE IF NOT EXISTSpattern cannot represent ALTER statements, so any schema change is a full DB rebuild. - M2 — Extract a shared
authmiddleware module. Each controller currently re-declares its ownauth(e.g.exams.controller.js:17,paynow.controller.js:18,index.js:55). Why: drift risk — a security fix to one will be missed in the others. - M3 — Introduce a service layer between controllers and the DB. Right now controllers inline SQL strings. Extract
users.service.js,exams.service.js, etc. Why: testability (mock the service, not the DB) and reuse (the sync engine will share services). - M4 — Add request validation. Use
zodorjoischemas at the route boundary. Why: the current code trustsreq.bodyto have the right shape; a malformed payload currently throws inside better-sqlite3. - M5 — Make the sync engine resilient to a missing/disrupted Supabase. It already no-ops on missing key; harden the timeout, retry, and circuit-breaker paths. Why: when sync is down, the API must not slow down.
- M6 — Multi-tenant design (if applicable). If multiple schools will use one deployment, add a
school_idto every table and a tenant-scoping middleware. Why: retrofitting tenant IDs is a painful migration.
13.2 Missing Documentation (priority: medium)
- D1 —
docs/API.md(ordocs/openapi.yaml). No central API reference. Generate from JSDoc withswagger-jsdoc+swagger-ui-express. - D2 —
docs/adr/with the suggested ADRs from §11. - D3 —
.env.exampleat repo root listing every variable from §10.6. - D4 — Operations runbook (
docs/runbook.md): how to reset the DB, repair a broken sync, restore from backup, rotate secrets. - D5 — Document the schema in
docs/schema.md(Mermaid ER diagram is fine). - D6 — Document the
scopes/ permissions in a code-readable form (a JSONpermissions.jsonthat the server can import), so frontend and backend share one source of truth. Currently the matrix lives only inSCREENS.mdand is duplicated inApp.tsxandNav.tsx.
13.3 Security Improvements (priority: HIGH)
- S1 — Remove the hardcoded JWT secret fallbacks.
server/src/index.js:25and every controller that doesprocess.env.JWT_SECRET || 'africa-alert-secret-key-2024'. Refuse to start withoutJWT_SECRETin production. Why: anyone reading the public repo can mint admin JWTs. - S2 — Remove the hardcoded
JWT_SECRETfromdocker-compose.yml(line 18). It is currentlyafrica-alert-production-secret-2024— a production-named secret committed to git. Why: it is now public knowledge. Rotate immediately in any environment that ever used this value. - S3 — Add
express-rate-limitto/api/auth/loginand/api/auth/register. No throttling today. - S4 — Tighten CORS.
app.use(cors())is wide-open (server/src/index.js:36). Restrict to known origins. - S5 — Add a request-size limit (
express.json({ limit: '1mb' })). Default is 100kb, but be explicit. - S6 — Verify Paynow webhook signatures. Currently the webhook handler accepts any payload; the hash check appears in
generateHashbut is it verified on inbound? Auditpaynow.controller.jsend-to-end. - S7 — Add
helmetfor sensible HTTP security headers. - S8 — Scan dependencies in CI (
npm audit+ a Dependabot config).
13.4 Testing Improvements (priority: medium → high)
- T1 — Add a test runner to both packages. No tests exist today. Backend: Jest + supertest. Frontend: Vitest + React Testing Library + @testing-library/jest-dom. E2E: Playwright.
- T2 — Write integration tests for the auth flow first (login, bad password, expired token, role denial).
- T3 — Write integration tests for the exam timer + auto-submit (race-condition-prone).
- T4 — Write a regression test for the sync engine's conflict resolution (server-wins).
- T5 — Add a coverage gate in CI (see §4.5).
- T6 — Add a smoke test for the Docker image — start the container, hit
/api/health(once added), assert 200.
13.5 DevOps Improvements (priority: medium)
- O1 — Add
/api/healthand/api/readyendpoints. Liveness vs readiness (the latter should return 503 until the DB is initialized). - O2 — Add CI (GitHub Actions recommended): install → lint → typecheck → test → build → docker build.
- O3 — Pin Node version in
.nvmrcand inDockerfile(FROM node:20.x-alpine). Pinning tonode:20-alpineis OK but explicit20.x.yis safer. - O4 — Add a non-root
USERin the production Dockerfile stage. Containers currently run as root. - O5 — Add a
HEALTHCHECKinstruction to the Dockerfile. - O6 — Automate SQLite backups (cron sidecar or a simple
node-crontask in the API). Ship a documentedrestore.sh. - O7 — Add log aggregation. Today logs go to stdout only. Pipe to a log shipper (Loki, CloudWatch, etc.) in production.
- O8 — Add
.dockerignoreto keepnode_modules,data/,dist/,uploads/,.git/,.harness/out of the build context. - O9 — Split the Docker image into
client(Nginx servingdist/) +apifor cleaner scaling. Optional — current single-image design is simpler to operate.
13.6 Documentation-as-Product (priority: low → medium)
- P1 — Promote
SCREENS.mdto a generated artefact. It is already excellent; consider a small script that builds it from the route table. - P2 — Add a
CONTRIBUTING.mdlinking to this file and the worktree workflow. - P3 — Add a
SECURITY.mdwith a disclosure address (e.g.security@…).
14. Open Questions for the Maintainer
These are decisions the audit surfaced that only you can answer. Each is one PR away from being resolved.
Which branch is the integration target —Resolved 2026-06-03 —devormain?devis for work + test,mainis for deployable code only. Documented in §7.1.- Do you plan to scale beyond a single SQLite file? If so, when — that determines whether to invest in the migration to Postgres now or in 6 months.
- Are
accountant,librarian,nurse(defined in the schema) planned roles? They have no UI today. - Will the same deployment serve multiple schools, or one-school-per-install?
- Who hosts the Supabase project, and who rotates the service key?
- Is there a separate staging environment, or does
docker-compose upon a developer laptop serve as staging? - What is the on-call story for a school that loses connectivity during exam day?
- Are there any school-data privacy requirements I should encode into the architecture (e.g. POPIA, GDPR, local equivalents)?
Document version: 1.0 — written 2026-06-03 by an initial repository audit. Update in the same PR as any change to the architecture, the standards, or the agent roster.