diff --git a/backend/routes/ivantiFindings.js b/backend/routes/ivantiFindings.js index 8788c3a..c9afe79 100644 --- a/backend/routes/ivantiFindings.js +++ b/backend/routes/ivantiFindings.js @@ -954,6 +954,21 @@ async function syncFindings() { console.error('[Ivanti Findings] Archive detection failed (non-fatal):', err.message); } + // Remove archived findings from ivanti_findings to prevent stale re-detection + if (archiveResult.disappearedIds && archiveResult.disappearedIds.length > 0) { + try { + const { rowCount } = await pool.query( + `DELETE FROM ivanti_findings WHERE state = 'open' AND CAST(id AS TEXT) = ANY($1::text[])`, + [archiveResult.disappearedIds.map(String)] + ); + if (rowCount > 0) { + console.log(`[Ivanti Findings] Removed ${rowCount} archived findings from ivanti_findings`); + } + } catch (err) { + console.error('[Ivanti Findings] Failed to clean archived findings from table (non-fatal):', err.message); + } + } + // Read previous counts BEFORE syncClosedCount updates them — needed for anomaly deltas let previousOpenCount = 0; let previousClosedCount = 0; @@ -973,9 +988,23 @@ async function syncFindings() { await syncFPWorkflowCounts(allFindings, apiKey, clientId, skipTls); // Post-sync: BU drift checker for newly archived findings + // Filter out findings that were already in ARCHIVED state from a previous sync — + // only pass genuinely new disappearances to avoid re-classifying the same set every cycle. let classificationBreakdown = { bu_reassignment: 0, severity_drift: 0, closed_on_platform: 0, decommissioned: 0 }; try { - classificationBreakdown = await runBUDriftChecker(archiveResult.disappearedIds, apiKey, clientId, skipTls); + let idsToCheck = archiveResult.disappearedIds || []; + if (idsToCheck.length > 0) { + const { rows: alreadyArchived } = await pool.query( + `SELECT finding_id FROM ivanti_finding_archives + WHERE current_state = 'ARCHIVED' + AND last_transition_at < NOW() - INTERVAL '2 hours'` + ); + const alreadyArchivedSet = new Set(alreadyArchived.map(r => String(r.finding_id))); + const newlyArchivedOnly = idsToCheck.filter(id => !alreadyArchivedSet.has(String(id))); + console.log(`[BU Drift Checker] ${idsToCheck.length} disappeared total, ${newlyArchivedOnly.length} genuinely new (${alreadyArchivedSet.size} already archived, skipped)`); + idsToCheck = newlyArchivedOnly; + } + classificationBreakdown = await runBUDriftChecker(idsToCheck, apiKey, clientId, skipTls); } catch (err) { console.error('[Ivanti Findings] BU drift checker failed (non-fatal):', err.message); }