BREAKING CHANGE: Storage and system scans no longer create entries in update_logs **Problem** - Storage scans were appearing on Updates page (mixed with package updates) - System scans were appearing on Updates page (mixed with package updates) - Duplicate "Scan All" entries from collective + individual logging **Root Cause** Scan handlers were calling both ReportLog() and dedicated endpoints: - reportLogWithAck → POST /api/v1/agents/:id/logs → update_logs table - This caused storage/system metrics to appear alongside package updates **Fix** Removed ALL ReportLog() calls from scan handlers: 1. handleScanUpdatesV2 (lines 44-46): Removed collective logging 2. handleScanStorage (lines 103-105): Use only ReportStorageMetrics 3. handleScanSystem (lines 189-191): Use only ReportMetrics 4. handleScanDocker (lines 269-271): Use only ReportDockerImages **Verification** - All 4 handlers have working dedicated endpoints (verified via subagent) - Routes already registered: POST /storage-metrics, POST /metrics, etc. - Frontend queries correct endpoints (verified) - No data loss: dedicated endpoints store in proper tables **Result** - Storage scans → storage_metrics table → Storage page only ✅ - System scans → system reporting → System page only ✅ - Package updates → update_logs table → Updates page only ✅ - No duplicate "Scan All" entries ✅ **Files Changed** - aggregator-agent/cmd/agent/subsystem_handlers.go: Removed 20 lines of ReportLog calls - internal/api/handlers/agents.go: Command recovery enhancements - internal/api/handlers/updates.go: Subsystem extraction logic - internal/database/queries/commands.go: GetStuckCommands query
64 lines
1.6 KiB
Go
64 lines
1.6 KiB
Go
package guardian
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// IntervalGuardian protects against accidental check-in interval overrides
|
|
type IntervalGuardian struct {
|
|
mu sync.Mutex
|
|
lastCheckInValue int
|
|
violationCount int
|
|
}
|
|
|
|
// NewIntervalGuardian creates a new guardian with zero violations
|
|
func NewIntervalGuardian() *IntervalGuardian {
|
|
return &IntervalGuardian{
|
|
lastCheckInValue: 0,
|
|
violationCount: 0,
|
|
}
|
|
}
|
|
|
|
// SetBaseline records the expected check-in interval
|
|
func (g *IntervalGuardian) SetBaseline(interval int) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
g.lastCheckInValue = interval
|
|
}
|
|
|
|
// CheckForOverrideAttempt validates that proposed interval matches baseline
|
|
// Returns error if mismatch detected (indicating a regression)
|
|
func (g *IntervalGuardian) CheckForOverrideAttempt(currentBaseline, proposedValue int) error {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
if currentBaseline != proposedValue {
|
|
g.violationCount++
|
|
return fmt.Errorf("INTERVAL_OVERRIDE_DETECTED: baseline=%d, proposed=%d, violations=%d",
|
|
currentBaseline, proposedValue, g.violationCount)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetViolationCount returns total number of violations detected
|
|
func (g *IntervalGuardian) GetViolationCount() int {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
return g.violationCount
|
|
}
|
|
|
|
// Reset clears violation count (use after legitimate config change)
|
|
func (g *IntervalGuardian) Reset() {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
g.violationCount = 0
|
|
}
|
|
|
|
// GetBaseline returns current baseline value
|
|
func (g *IntervalGuardian) GetBaseline() int {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
return g.lastCheckInValue
|
|
}
|