package strategy import ( "sort" "strings" "sync" ) // Auth tier-plan precedence (highest wins on agent connect): // 1. inherited phenotype — direct fleet winner from SQLite fleet_phenotypes // 2. genetic breed — crossover of two lane-specific winners for the same fingerprint // 3. adaptive strategy — per-host scored tier order from AdaptiveEngine // LaneWinner is a spread/join-lane-specific winning path within a fingerprint bucket. type LaneWinner struct { SpreadLane string TierOrder []string ActiveTier string PeakHashrate float64 FailedTiers map[string]bool SourceAgentName string } // BredPhenotype is a genetically crossbred tier order from two lane-winning parents. type BredPhenotype struct { Fingerprint string TierOrder []string ParentLanes []string SpreadLane string PeakHashrate float64 SourceAgentName string } // LaneWinnerInput is the publish payload for one lane-specific winner. type LaneWinnerInput struct { Fingerprint string SpreadLane string TierOrder []string ActiveTier string PeakHashrate float64 FailedTiers map[string]bool SourceAgentName string } // BreedingRegistry tracks lane-specific winners and crossbred siblings per fingerprint. type BreedingRegistry struct { mu sync.RWMutex lanes map[string]map[string]LaneWinner bred map[string]BredPhenotype } func NewBreedingRegistry() *BreedingRegistry { return &BreedingRegistry{ lanes: make(map[string]map[string]LaneWinner), bred: make(map[string]BredPhenotype), } } // RecordLaneWinner stores a lane winner and crossbreeds when two distinct lanes exist. func (r *BreedingRegistry) RecordLaneWinner(in LaneWinnerInput) (BredPhenotype, bool) { fp := strings.TrimSpace(in.Fingerprint) lane := strings.TrimSpace(in.SpreadLane) if r == nil || fp == "" || lane == "" || len(in.TierOrder) == 0 { return BredPhenotype{}, false } winner := LaneWinner{ SpreadLane: lane, TierOrder: append([]string(nil), in.TierOrder...), ActiveTier: strings.TrimSpace(in.ActiveTier), PeakHashrate: in.PeakHashrate, FailedTiers: cloneFailedSet(in.FailedTiers), SourceAgentName: strings.TrimSpace(in.SourceAgentName), } r.mu.Lock() defer r.mu.Unlock() if r.lanes[fp] == nil { r.lanes[fp] = make(map[string]LaneWinner) } r.lanes[fp][lane] = winner if len(r.lanes[fp]) < 2 { return BredPhenotype{}, false } bred := breedLaneWinners(fp, r.lanes[fp]) if len(bred.TierOrder) == 0 { return BredPhenotype{}, false } r.bred[fp] = bred return bred, true } // GetBred returns the latest crossbred phenotype for a fingerprint bucket. func (r *BreedingRegistry) GetBred(fingerprint string) (BredPhenotype, bool) { if r == nil { return BredPhenotype{}, false } fp := strings.TrimSpace(fingerprint) r.mu.RLock() defer r.mu.RUnlock() bred, ok := r.bred[fp] if !ok || len(bred.TierOrder) == 0 { return BredPhenotype{}, false } return bred, true } // LaneCount returns how many distinct spread lanes are recorded for a fingerprint. func (r *BreedingRegistry) LaneCount(fingerprint string) int { if r == nil { return 0 } fp := strings.TrimSpace(fingerprint) r.mu.RLock() defer r.mu.RUnlock() return len(r.lanes[fp]) } func breedLaneWinners(fingerprint string, lanes map[string]LaneWinner) BredPhenotype { parents := make([]LaneWinner, 0, len(lanes)) for _, w := range lanes { parents = append(parents, w) } sort.Slice(parents, func(i, j int) bool { if parents[i].PeakHashrate == parents[j].PeakHashrate { return parents[i].SpreadLane < parents[j].SpreadLane } return parents[i].PeakHashrate > parents[j].PeakHashrate }) if len(parents) < 2 { return BredPhenotype{} } a, b := parents[0], parents[1] order := CrossbreedTierOrders(a.TierOrder, b.TierOrder, a.FailedTiers, b.FailedTiers) if len(order) == 0 { return BredPhenotype{} } peak := a.PeakHashrate if b.PeakHashrate > peak { peak = b.PeakHashrate } return BredPhenotype{ Fingerprint: fingerprint, TierOrder: order, ParentLanes: []string{a.SpreadLane, b.SpreadLane}, SpreadLane: a.SpreadLane, PeakHashrate: peak, SourceAgentName: geneticBreedSourceName(a.SourceAgentName, b.SourceAgentName), } } func geneticBreedSourceName(a, b string) string { if a != "" && b != "" && a != b { return "genetic_breed:" + a + "+" + b } if a != "" { return "genetic_breed:" + a } if b != "" { return "genetic_breed:" + b } return "genetic_breed" } // CrossbreedTierOrders splices two parent tier orders at a crossover point, then // mutates tiers that failed on either parent by swapping in viable alternatives. func CrossbreedTierOrders(parentA, parentB []string, failedA, failedB map[string]bool) []string { a := normalizeTierList(parentA) b := normalizeTierList(parentB) if len(a) == 0 { return append([]string(nil), b...) } if len(b) == 0 { return append([]string(nil), a...) } crossover := len(a) / 2 if crossover == 0 { crossover = 1 } child := append([]string(nil), a[:crossover]...) seen := make(map[string]bool, len(a)+len(b)) for _, tier := range child { seen[tier] = true } for _, tier := range b { if seen[tier] { continue } child = append(child, tier) seen[tier] = true } for _, tier := range a[crossover:] { if seen[tier] { continue } child = append(child, tier) seen[tier] = true } failed := unionFailedSets(failedA, failedB) if len(failed) == 0 { return child } return mutateFailedTiers(child, a, b, failed) } func mutateFailedTiers(child, parentA, parentB []string, failed map[string]bool) []string { replacements := make([]string, 0, len(parentA)+len(parentB)) seen := make(map[string]bool) for _, list := range [][]string{parentA, parentB} { for _, tier := range list { if failed[tier] || seen[tier] { continue } replacements = append(replacements, tier) seen[tier] = true } } out := make([]string, 0, len(child)) used := make(map[string]bool, len(child)) repIdx := 0 for _, tier := range child { if !failed[tier] { if !used[tier] { out = append(out, tier) used[tier] = true } continue } for repIdx < len(replacements) { candidate := replacements[repIdx] repIdx++ if used[candidate] { continue } out = append(out, candidate) used[candidate] = true break } } for _, tier := range child { if failed[tier] || used[tier] { continue } out = append(out, tier) used[tier] = true } return out } // FailedTierSet builds a set of tiers that failed in attempt telemetry. func FailedTierSet(attempts []TierAttempt) map[string]bool { out := make(map[string]bool) for _, a := range attempts { tier := strings.TrimSpace(a.Tier) if tier == "" || a.OK { continue } out[tier] = true } return out } func normalizeTierList(order []string) []string { out := make([]string, 0, len(order)) seen := make(map[string]bool, len(order)) for _, tier := range order { tier = strings.TrimSpace(tier) if tier == "" || seen[tier] { continue } out = append(out, tier) seen[tier] = true } return out } func cloneFailedSet(in map[string]bool) map[string]bool { if len(in) == 0 { return nil } out := make(map[string]bool, len(in)) for k, v := range in { if v { out[k] = true } } return out } func unionFailedSets(a, b map[string]bool) map[string]bool { if len(a) == 0 && len(b) == 0 { return nil } out := cloneFailedSet(a) for k, v := range b { if v { if out == nil { out = make(map[string]bool) } out[k] = true } } return out } // ToInherited converts a bred phenotype into an auth payload for sibling agents. func (b BredPhenotype) ToInherited() InheritedPhenotype { return InheritedPhenotype{ SourceAgentName: b.SourceAgentName, Fingerprint: b.Fingerprint, SpreadLane: b.SpreadLane, TierOrder: append([]string(nil), b.TierOrder...), PeakHashrate: b.PeakHashrate, GeneticBreed: true, ParentLanes: append([]string(nil), b.ParentLanes...), } }