fix: Gate AI subsystems and intelligence endpoints on AI enabled state. Related to #885

- Add IsAIEnabled() method to AISettingsHandler for consistent checks
- Gate baseline learning, pattern detector, and correlation detector initialization
  in StartPatrol() on AI being enabled
- Add AI enabled checks to all /api/ai/intelligence/* endpoints as defense-in-depth
- Return empty results with "AI is not enabled" message when AI is disabled

This ensures no AI-related data is collected, persisted, or returned when AI is disabled,
preventing the "undismissable alerts" issue where old AI findings would appear.
This commit is contained in:
rcourtman
2025-12-30 22:06:07 +00:00
parent 59eca65ff6
commit 5c2db10780
3 changed files with 169 additions and 66 deletions
+5
View File
@@ -78,6 +78,11 @@ func (h *AISettingsHandler) StartPatrol(ctx context.Context) {
h.aiService.StartPatrol(ctx)
}
// IsAIEnabled returns true if AI features are enabled
func (h *AISettingsHandler) IsAIEnabled() bool {
return h.aiService.IsEnabled()
}
// SetPatrolThresholdProvider sets the threshold provider for the patrol service
func (h *AISettingsHandler) SetPatrolThresholdProvider(provider ai.ThresholdProvider) {
h.aiService.SetPatrolThresholdProvider(provider)
+90
View File
@@ -20,6 +20,17 @@ func (h *AISettingsHandler) HandleGetPatterns(w http.ResponseWriter, r *http.Req
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"patterns": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write patterns response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -92,6 +103,17 @@ func (h *AISettingsHandler) HandleGetPredictions(w http.ResponseWriter, r *http.
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"predictions": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write predictions response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -166,6 +188,17 @@ func (h *AISettingsHandler) HandleGetCorrelations(w http.ResponseWriter, r *http
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"correlations": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write correlations response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -244,6 +277,17 @@ func (h *AISettingsHandler) HandleGetRecentChanges(w http.ResponseWriter, r *htt
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"changes": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write changes response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -322,6 +366,17 @@ func (h *AISettingsHandler) HandleGetBaselines(w http.ResponseWriter, r *http.Re
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"baselines": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write baselines response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -395,6 +450,17 @@ func (h *AISettingsHandler) HandleGetRemediations(w http.ResponseWriter, r *http
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"remediations": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write remediations response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -539,6 +605,17 @@ func (h *AISettingsHandler) HandleGetAnomalies(w http.ResponseWriter, r *http.Re
return
}
// AI must be enabled to return intelligence data
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"anomalies": []interface{}{},
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write anomalies response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
@@ -771,6 +848,19 @@ func (h *AISettingsHandler) HandleGetLearningStatus(w http.ResponseWriter, r *ht
return
}
// AI must be enabled to return learning status
if !h.aiService.IsEnabled() {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
"resources_baselined": 0,
"total_metrics": 0,
"status": "ai_disabled",
"message": "AI is not enabled",
}); err != nil {
log.Error().Err(err).Msg("Failed to write learning status response")
}
return
}
patrol := h.aiService.GetPatrolService()
if patrol == nil {
if err := utils.WriteJSONResponse(w, map[string]interface{}{
+74 -66
View File
@@ -1588,18 +1588,22 @@ func (r *Router) StartPatrol(ctx context.Context) {
r.aiSettingsHandler.SetMetricsHistoryProvider(adapter)
}
// Initialize baseline store for anomaly detection
// Uses config dir for persistence
baselineCfg := ai.DefaultBaselineConfig()
if r.persistence != nil {
baselineCfg.DataDir = r.persistence.DataDir()
}
baselineStore := ai.NewBaselineStore(baselineCfg)
if baselineStore != nil {
r.aiSettingsHandler.SetBaselineStore(baselineStore)
// Only initialize baseline learning if AI is enabled
// This prevents anomaly data from being collected and displayed when AI is disabled
if r.aiSettingsHandler.IsAIEnabled() {
// Initialize baseline store for anomaly detection
// Uses config dir for persistence
baselineCfg := ai.DefaultBaselineConfig()
if r.persistence != nil {
baselineCfg.DataDir = r.persistence.DataDir()
}
baselineStore := ai.NewBaselineStore(baselineCfg)
if baselineStore != nil {
r.aiSettingsHandler.SetBaselineStore(baselineStore)
// Start background baseline learning loop
go r.startBaselineLearning(ctx, baselineStore, metricsHistory)
// Start background baseline learning loop
go r.startBaselineLearning(ctx, baselineStore, metricsHistory)
}
}
}
}
@@ -1626,63 +1630,67 @@ func (r *Router) StartPatrol(ctx context.Context) {
r.aiSettingsHandler.SetRemediationLog(remediationLog)
}
// Initialize pattern detector for failure prediction
patternDetector := ai.NewPatternDetector(ai.PatternDetectorConfig{
MaxEvents: 5000,
MinOccurrences: 3,
PatternWindow: 90 * 24 * time.Hour,
PredictionLimit: 30 * 24 * time.Hour,
DataDir: dataDir,
})
if patternDetector != nil {
r.aiSettingsHandler.SetPatternDetector(patternDetector)
// Only initialize pattern and correlation detectors if AI is enabled
// This prevents these subsystems from collecting data and displaying findings when AI is disabled
if r.aiSettingsHandler.IsAIEnabled() {
// Initialize pattern detector for failure prediction
patternDetector := ai.NewPatternDetector(ai.PatternDetectorConfig{
MaxEvents: 5000,
MinOccurrences: 3,
PatternWindow: 90 * 24 * time.Hour,
PredictionLimit: 30 * 24 * time.Hour,
DataDir: dataDir,
})
if patternDetector != nil {
r.aiSettingsHandler.SetPatternDetector(patternDetector)
// Wire alert history to pattern detector for event tracking
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
alertManager.OnAlertHistory(func(alert alerts.Alert) {
// Convert alert type to trackable event
patternDetector.RecordFromAlert(alert.ResourceID, alert.Type+"_"+string(alert.Level), alert.StartTime)
})
log.Info().Msg("AI Pattern Detector: Wired to alert history for failure prediction")
}
}
// Initialize correlation detector for multi-resource relationships
correlationDetector := ai.NewCorrelationDetector(ai.CorrelationConfig{
MaxEvents: 10000,
CorrelationWindow: 10 * time.Minute,
MinOccurrences: 3,
RetentionWindow: 30 * 24 * time.Hour,
DataDir: dataDir,
})
if correlationDetector != nil {
r.aiSettingsHandler.SetCorrelationDetector(correlationDetector)
// Wire alert history to correlation detector
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
alertManager.OnAlertHistory(func(alert alerts.Alert) {
// Record as correlation event
eventType := ai.CorrelationEventType(ai.CorrelationEventAlert)
switch alert.Type {
case "cpu":
eventType = ai.CorrelationEventHighCPU
case "memory":
eventType = ai.CorrelationEventHighMem
case "disk":
eventType = ai.CorrelationEventDiskFull
case "offline", "connectivity":
eventType = ai.CorrelationEventOffline
}
correlationDetector.RecordEvent(ai.CorrelationEvent{
ResourceID: alert.ResourceID,
ResourceName: alert.ResourceName,
ResourceType: alert.Type,
EventType: eventType,
Timestamp: alert.StartTime,
Value: alert.Value,
// Wire alert history to pattern detector for event tracking
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
alertManager.OnAlertHistory(func(alert alerts.Alert) {
// Convert alert type to trackable event
patternDetector.RecordFromAlert(alert.ResourceID, alert.Type+"_"+string(alert.Level), alert.StartTime)
})
})
log.Info().Msg("AI Correlation Detector: Wired to alert history for multi-resource analysis")
log.Info().Msg("AI Pattern Detector: Wired to alert history for failure prediction")
}
}
// Initialize correlation detector for multi-resource relationships
correlationDetector := ai.NewCorrelationDetector(ai.CorrelationConfig{
MaxEvents: 10000,
CorrelationWindow: 10 * time.Minute,
MinOccurrences: 3,
RetentionWindow: 30 * 24 * time.Hour,
DataDir: dataDir,
})
if correlationDetector != nil {
r.aiSettingsHandler.SetCorrelationDetector(correlationDetector)
// Wire alert history to correlation detector
if alertManager := r.monitor.GetAlertManager(); alertManager != nil {
alertManager.OnAlertHistory(func(alert alerts.Alert) {
// Record as correlation event
eventType := ai.CorrelationEventType(ai.CorrelationEventAlert)
switch alert.Type {
case "cpu":
eventType = ai.CorrelationEventHighCPU
case "memory":
eventType = ai.CorrelationEventHighMem
case "disk":
eventType = ai.CorrelationEventDiskFull
case "offline", "connectivity":
eventType = ai.CorrelationEventOffline
}
correlationDetector.RecordEvent(ai.CorrelationEvent{
ResourceID: alert.ResourceID,
ResourceName: alert.ResourceName,
ResourceType: alert.Type,
EventType: eventType,
Timestamp: alert.StartTime,
Value: alert.Value,
})
})
log.Info().Msg("AI Correlation Detector: Wired to alert history for multi-resource analysis")
}
}
}