3.3 KiB
3.3 KiB
2026-07-16 — P0 sync dedupe (WT-E)
Closes P0-6 from the live audit (3fb3d84): server/src/services/SyncEngine.js tablesToSync listed 5 duplicate pairs and a 3-way settings conflict, causing redundant sync cycles and potential upload drift.
Files
| File | Change |
|---|---|
server/src/database/migrations/2026-07-16-dedupe-tables.js |
NEW. One-shot migration. Backs up the DB to data/school.db.pre-dedupe-backup, copies any non-duplicate rows from the legacy tables to the canonical ones, drops the legacy tables. Idempotent — running it twice is a no-op. |
server/src/services/SyncEngine.js |
Removed subjects_new, attendance_new, messages_new, calendar_events from tablesToSync (lines 30, 35, 40, 42). Added a startup check: any table in tablesToSync that doesn't exist in sqlite_master throws a clear error at engine init. |
server/src/database/init.js |
Removed the CREATE TABLE IF NOT EXISTS blocks for the 4 dropped legacy tables. |
.harness/changelogs/2026-07-16-p0-sync-dedupe.md |
This file. |
~/.mavis/scratchpads/mvs_88aff8965ad4492d83c427623f15d3f0/p0-sync-dedupe-decision.md |
DECISION-0 (canonical table per pair, with live ref + row counts). |
Migration contract
- Pre-check: the script inspects
sqlite_masterfor each canonical + legacy table and reports what it found before doing anything. If the schema doesn't match the expected shape, it aborts. - Backup:
data/school.dbis copied todata/school.db.pre-dedupe-backupBEFORE any change. The backup is left in place for the operator to delete after they confirm. - Copy: for each pair, the script does
INSERT INTO canonical (uid, …) SELECT … FROM legacy WHERE NOT EXISTS (SELECT 1 FROM canonical WHERE canonical.uid = legacy.uid). Duplicateuids in the source are skipped (a warning is logged, not a failure — they're meant to be duplicates by construction). - Drop:
DROP TABLE legacyafter the copy completes successfully. - Idempotency: running the script twice is a no-op. The first run does the work; the second run sees no legacy tables to drop and no rows to copy.
- Transaction: the whole migration is wrapped in a single
db.transaction(() => { … })(). If anything fails, the DB is restored from the backup automatically.
Verification
data/school.db.pre-dedupe-backupexists and is openable.tablesToSynchas 4 fewer entries.node -e "const e = require('./src/services/SyncEngine').getSyncEngine(); console.log('OK')"succeeds (startup check passes).- A manual
POST /api/sync/forcereturns 200 and thesync_logstable shows each remaining table appearing exactly once. Select-String -Path 'server\\src' -Pattern 'subjects_new|attendance_new|messages_new|calendar_events'returns 0 controller refs (the init.js CREATE blocks are gone; the migration script itself is the only mention).
What did NOT change (deliberately)
settings/system_settings/school_settings: all 3 kept. Different schemas, different purposes. A future PR can rationalize them.- No controller SQL was touched. The bare-named tables were already canonical in controller queries; the legacy
_newtables had only stale migration-script mentions. - No sync_status data migration. Both versions of every pair share the same
sync_statusCHECK constraint and default value.