10 KiB
10 KiB
Changelog — 2026-07-18 — P2-1 backup daemon + P2-5 bulk marks verify
Branch: ops/sqlite-backup
Worktree: .worktrees/ops-sqlite-backup/
Owner: developer (devops)
Date: 2026-07-18
This change picks up the prior P2-1 / P2-5 work (see
.harness/changelogs/2026-07-17-p2-backup.md) and finishes the items
that were explicitly deferred:
- Adds the cross-platform Node fallback for the backup script (so
Windows dev boxes do not need the
sqlite3CLI), with a graceful delegation path inside the existing bash script. - Adds an explicit CSV header check to the bulk marks controller so a
teacher uploading the wrong export gets a clear
400instead of N identical "missing assignment_id" errors. - Adds three Playwright E2E specs for
POST /api/marks/bulk— happy path, one-bad-row, and non-CSV upload — all green on this branch. - Adds the
verify-restore.shend-to-end smoke to the dev-machine verification chain (it was deferred to the production host in the previous attempt because of a server-side schema gap that has now been closed). - Drives-by a missing
education_level_configsmigration so the server can boot on a freshnpm run db:init.
No demo accounts, schema contracts, or RBAC surface are touched.
Files added
| Path | Purpose |
|---|---|
scripts/backup/africa-alert-backup.js |
Cross-platform Node fallback. Uses better-sqlite3's online backup API to take a hot snapshot, copies -wal/-shm sidecars, writes sha256, and prunes the local retention window. All exit codes and log line shapes mirror africa-alert-backup.sh. Resolves better-sqlite3 from the project's server/node_modules. |
client/e2e/bulk-marks.spec.ts |
Three Playwright specs for POST /api/marks/bulk. Reuses the existing Playwright config; goes through the API directly via pwRequest.newContext (no browser navigation). |
server/src/database/migrations/knex/2026071800000000_education_level_configs.js |
New knex migration that creates the education_level_configs table the feature/shapeshift controller branch added (but never migrated). Idempotent (CREATE TABLE IF NOT EXISTS) and seeds the four canonical Primary/Secondary/High School/Tertiary rows so is_default has something to point at on first boot. |
Files modified
| Path | Change |
|---|---|
scripts/backup/africa-alert-backup.sh |
(a) Header comment now explains the Windows fallback. (b) If sqlite3 is not on PATH, log a WARN and exec node africa-alert-backup.js instead of hard-failing — so bash ./africa-alert-backup.sh works on a Windows dev box without the sqlite3 CLI. |
scripts/backup/verify-restore.sh |
(a) pick_free_port() now prefers Node over Python; falls back to a high constant if neither works. The previous version was tripped by the Windows Microsoft-Store "python" alias hijacking command -v python. (b) The cleanup() function tolerates rm -rf failures on Windows (where SQLite sidecars can be held open briefly by the OS after the Node child exits). |
server/src/controllers/marks.controller.js |
Adds an explicit CSV header check after csv-parse. The header assignment_id,student_id,score,feedback is now required; a missing column returns 400 with { error, expected, actual } instead of producing N rows of "missing assignment_id" errors. Per-row error reporting was already in place from the previous PR and is unchanged. |
docs/BACKUP.md |
Adds a §3 cross-platform note that explains the node scripts/backup/africa-alert-backup.js path for Windows developers. The Linux install/run instructions are unchanged. |
Sub-tasks completed
- G.1
africa-alert-backup.sh— done (with Windows delegation). - G.2
install-backup-cron.sh— unchanged from the previous PR; logs the next-run estimate tologs/backup-install.logas required. - G.3
restore.sh— unchanged from the previous PR. - G.4
verify-restore.sh— done. Verified to exit 0 against the latest snapshot on this Windows dev box. - G.5
install-backup-cron.shlogging — unchanged from previous PR. - G.6
docs/BACKUP.md— done. Added the Windows dev fallback note (§3) referencing the new Node script. - G.7 Bulk marks audit — done. Header check added (see marks.controller.js
diff). The remaining three gaps noted in the previous changelog (no
teacher-ownership guard, no
dry_runmode, loose score typing) are re-confirmed here and remain Track B / Track C follow-ups. - G.8 Playwright specs — done.
client/e2e/bulk-marks.spec.tscontains three specs, all passing on this branch. - G.9 Manual smoke — out of scope for the agent; documented here.
- G.10 Commit + push — done.
Verification results
| Gate | Command | Result |
|---|---|---|
| G6 — backup | bash scripts/backup/africa-alert-backup.sh |
exit 0; backup file backups/school-YYYYmmdd-HHMMSS.db written, sha256 written, retention sweep deleted=0. |
| G6 — verify-restore | bash scripts/backup/verify-restore.sh "$(ls -1t backups/school-*.db | head -n1)" |
exit 0. Server boots against the snapshot, /api/auth/login returns 200 with admin@school.com / admin123. |
| G4 — Playwright bulk-marks | cd client && npx playwright test e2e/bulk-marks.spec.ts |
3/3 passing (~2.6s wall time): happy-path 30 inserted, one-bad-row 29 inserted + 1 errored, non-CSV PNG rejected with 400. |
| G4 — Playwright full suite | cd client && npx playwright test |
The 17 previously-passing fixes.spec.ts + rbac.spec.ts + paynow.spec.ts specs still pass. The new bulk-marks.spec.ts adds 3 more (20 green). Several offline.spec.ts cases time out on this Windows box — these are environmental (Vitest/dev-server warm-up) and are not regressed by this change. |
| G1 — typecheck | cd server && npx tsc --noEmit |
Cannot run via npm run typecheck because the project does not include typescript in server/package.json (documented in .harness/AGENTS.md open follow-ups). With a one-shot install via npx -y -p typescript@5.3.3 tsc --noEmit --allowJs --target es2020 --module commonjs --skipLibCheck <files> against src/index.js, src/controllers/marks.controller.js, src/database/migrations/knex/2026071800000000_education_level_configs.js, and scripts/backup/africa-alert-backup.js — exit 0. (--skipLibCheck is required because the installed @types/node has a TS-version mismatch that is unrelated to this PR.) |
bash -n syntax checks for the bash scripts were also run (the
previous PR's verification approach) — all four pass.
Deviations from the plan
- Plan asked for
npm run db:backupto be wired up. Not done in this PR. The new Node fallback exists asscripts/backup/africa-alert-backup.jsand the docs point operators at it directly. Adding annpm run db:backupscript is a one-liner inserver/package.jsonand is left for a follow-up so this PR does not gain a package.json edit. - Plan referenced an expected CSV header
student_id,name,score,total,term,academic_year. The actual controller (server/src/controllers/marks.controller.js) requiresassignment_id,student_id,score,feedback— it writes to thesubmissionstable (assignment-driven), not thegradestable (exam-driven). The audit and the new header check are written against the actual schema, with a note in the controller comment that future schema drift should keep these in sync. The plan's expected header matches a different endpoint (/api/grades/bulkor similar) that does not yet exist; if/when that endpoint lands it should follow the same header-check pattern added here. - Drive-by migration
2026071800000000_education_level_configs.js. The previous PR's verification chain puntedverify-restore.shto the production host becausenpm run db:initdid not create this table, which the SyncEngine's startup check throws on. The plan's task gate G6 explicitly requiredverify-restore.shto exit 0 on this dev machine, so a missing migration is closed here. The new migration is idempotent (CREATE TABLE IF NOT EXISTS) and seeds the four canonical level rows soschoolSettings.controller.js'sis_default=1lookup has a row to find on first boot. install-backup-cron.shandrestore.shnot modified. The previous PR's versions are kept verbatim. Both passbash -nand operate only on Linux cron / systemctl so there is no Windows dev path to surface.- G1 typecheck not directly runnable. The project's own
npm run typecheckscript invokestscbuttypescriptis not inserver/package.json. Documented as an open follow-up in.harness/AGENTS.md. The typecheck was run via a one-shotnpx -y -p typescript@5.3.3install against the touched files only (with--skipLibCheckto work around an unrelated@types/nodemismatch) and passed cleanly.
Follow-ups
- Wire
npm run db:backup(server) andnpm run db:restore/db:verify-restoreto call the new scripts. One-liner. - On the production host, after deploy: re-run
bash -non the four scripts (paranoia),sudo install -m 0755 scripts/backup/*.sh /usr/local/bin/,sudo install -m 0755 scripts/backup/africa-alert-backup.js /usr/local/bin/, thensudo /usr/local/bin/install-backup-cron.sh. Verify withcrontab -l | grep africa-alert. - Wire
logs/backup.logto the operator's monitoring (Promtail, mailx, Telegram webhook, etc.). The contract is indocs/BACKUP.md§6. - Promote the 12-weekly retention in
docs/BACKUP.md§5 to a real cron entry. The runbook gives the script; the cron line is left for the operator. - Address the four
marks.controller.jsaudit items (teacher-ownership guard,dry_runmode, integer score enforcement, mime allowlist re-review) in the Track B / Track C backlogs. - Add
server/package.jsondevDependency ontypescriptand a realtsc --noEmitinvocation so the G1 typecheck gate runs without a one-shotnpx -pinstall. - Several pre-existing schema/code gaps surfaced during verification
(
education_level_configs— fixed here;subjects_newtable referenced by/api/classesand/api/subjects;schemes_of_workreferenced by the same;offline.spec.tstimeouts on Windows). These are out of scope for P2-1/P2-5 but should be tracked.