package interceptors import ( "sync" "time" "github.com/safedep/pmg/analyzer" ) // CooldownBlock records a package blocked by the dependency cooldown policy. type CooldownBlock struct { Name string Version string PublishDate time.Time DaysAgo int DaysLeft int CooldownDays int } // AnalysisStats contains aggregated statistics from analysis results type AnalysisStats struct { TotalAnalyzed int AllowedCount int ConfirmedCount int BlockedCount int UserCancelledCount int CooldownBlockedCount int } // AnalysisStatsCollector tracks analysis statistics during proxy execution. // It is separate from the cache to allow different cache implementations // without coupling them to reporting concerns. type AnalysisStatsCollector struct { mu sync.RWMutex stats AnalysisStats blockedPackages []*analyzer.PackageVersionAnalysisResult confirmedPackages []*analyzer.PackageVersionAnalysisResult cooldownBlocks []CooldownBlock } // NewAnalysisStatsCollector creates a new stats collector func NewAnalysisStatsCollector() *AnalysisStatsCollector { return &AnalysisStatsCollector{} } // RecordAllowed records a package that was allowed (safe) func (c *AnalysisStatsCollector) RecordAllowed(result *analyzer.PackageVersionAnalysisResult) { if result == nil { return } c.mu.Lock() defer c.mu.Unlock() c.stats.TotalAnalyzed++ c.stats.AllowedCount++ } // RecordBlocked records a package that was automatically blocked (ActionBlock) func (c *AnalysisStatsCollector) RecordBlocked(result *analyzer.PackageVersionAnalysisResult) { if result == nil { return } c.mu.Lock() defer c.mu.Unlock() c.stats.TotalAnalyzed++ c.stats.BlockedCount++ c.blockedPackages = append(c.blockedPackages, result) } // RecordUserCancelled records a package that was blocked because user declined confirmation (ActionConfirm declined) func (c *AnalysisStatsCollector) RecordUserCancelled(result *analyzer.PackageVersionAnalysisResult) { if result == nil { return } c.mu.Lock() defer c.mu.Unlock() c.stats.TotalAnalyzed++ c.stats.UserCancelledCount++ // User cancelled packages are counted as blocked as well c.stats.BlockedCount++ c.blockedPackages = append(c.blockedPackages, result) } // RecordConfirmed records a package where user confirmed installation despite warning func (c *AnalysisStatsCollector) RecordConfirmed(result *analyzer.PackageVersionAnalysisResult) { if result == nil { return } c.mu.Lock() defer c.mu.Unlock() c.stats.TotalAnalyzed++ c.stats.ConfirmedCount++ c.confirmedPackages = append(c.confirmedPackages, result) } // GetStats returns the current statistics func (c *AnalysisStatsCollector) GetStats() AnalysisStats { c.mu.RLock() defer c.mu.RUnlock() return c.stats } // GetBlockedPackages returns all blocked packages func (c *AnalysisStatsCollector) GetBlockedPackages() []*analyzer.PackageVersionAnalysisResult { c.mu.RLock() defer c.mu.RUnlock() // Return a copy to avoid race conditions result := make([]*analyzer.PackageVersionAnalysisResult, len(c.blockedPackages)) copy(result, c.blockedPackages) return result } // GetConfirmedPackages returns all confirmed packages func (c *AnalysisStatsCollector) GetConfirmedPackages() []*analyzer.PackageVersionAnalysisResult { c.mu.RLock() defer c.mu.RUnlock() // Return a copy to avoid race conditions result := make([]*analyzer.PackageVersionAnalysisResult, len(c.confirmedPackages)) copy(result, c.confirmedPackages) return result } // RecordCooldownBlocked records a package blocked by the dependency cooldown policy. // It increments both the cooldown-specific counter and the overall BlockedCount so that // inferOutcome correctly treats cooldown-only sessions as OutcomeBlocked. func (c *AnalysisStatsCollector) RecordCooldownBlocked(name, version string, publishDate time.Time, daysAgo, daysLeft, cooldownDays int) { c.mu.Lock() defer c.mu.Unlock() c.stats.TotalAnalyzed++ c.stats.BlockedCount++ c.stats.CooldownBlockedCount++ c.cooldownBlocks = append(c.cooldownBlocks, CooldownBlock{ Name: name, Version: version, PublishDate: publishDate, DaysAgo: daysAgo, DaysLeft: daysLeft, CooldownDays: cooldownDays, }) } // GetCooldownBlocks returns all packages blocked by the cooldown policy. func (c *AnalysisStatsCollector) GetCooldownBlocks() []CooldownBlock { c.mu.RLock() defer c.mu.RUnlock() result := make([]CooldownBlock, len(c.cooldownBlocks)) copy(result, c.cooldownBlocks) return result }