// Migration: Add sync anomaly detection and BU drift monitoring tables // // Creates two new tables: // - ivanti_sync_anomaly_log — stores one row per sync cycle with the // anomaly summary breakdown (count deltas, classification, significance). // - ivanti_finding_bu_history — records BU change events detected on // individual findings across syncs. // // Safe to re-run — uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS. // // Usage: node backend/migrations/add_sync_anomaly_tables.js const path = require('path'); const sqlite3 = require('sqlite3').verbose(); const dbPath = path.join(__dirname, '..', 'cve_database.db'); const db = new sqlite3.Database(dbPath); console.log('Starting sync anomaly tables migration...'); function run(sql, params = []) { return new Promise((resolve, reject) => { db.run(sql, params, function (err) { if (err) reject(err); else resolve(this); }); }); } function all(sql, params = []) { return new Promise((resolve, reject) => { db.all(sql, params, (err, rows) => { if (err) reject(err); else resolve(rows || []); }); }); } async function migrate() { // 1. Create ivanti_sync_anomaly_log table await run(` CREATE TABLE IF NOT EXISTS ivanti_sync_anomaly_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, sync_timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, open_count_delta INTEGER NOT NULL DEFAULT 0, closed_count_delta INTEGER NOT NULL DEFAULT 0, newly_archived_count INTEGER NOT NULL DEFAULT 0, returned_count INTEGER NOT NULL DEFAULT 0, classification_json TEXT NOT NULL DEFAULT '{}', is_significant INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); console.log('✓ ivanti_sync_anomaly_log table ready'); // 2. Create ivanti_finding_bu_history table await run(` CREATE TABLE IF NOT EXISTS ivanti_finding_bu_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, finding_id TEXT NOT NULL, finding_title TEXT NOT NULL DEFAULT '', host_name TEXT NOT NULL DEFAULT '', previous_bu TEXT NOT NULL, new_bu TEXT NOT NULL, detected_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); console.log('✓ ivanti_finding_bu_history table ready'); // 3. Create indexes await run('CREATE INDEX IF NOT EXISTS idx_anomaly_sync_timestamp ON ivanti_sync_anomaly_log(sync_timestamp)'); console.log('✓ idx_anomaly_sync_timestamp index ready'); await run('CREATE INDEX IF NOT EXISTS idx_bu_history_finding_id ON ivanti_finding_bu_history(finding_id)'); console.log('✓ idx_bu_history_finding_id index ready'); await run('CREATE INDEX IF NOT EXISTS idx_bu_history_detected_at ON ivanti_finding_bu_history(detected_at)'); console.log('✓ idx_bu_history_detected_at index ready'); } migrate() .then(() => { console.log('Migration complete.'); db.close(); }) .catch((err) => { console.error('Migration failed:', err); db.close(); process.exit(1); });