Files
AetherForge d605ef4adb
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add Strain Hospice for graceful low-win strain retirement.
Archive failed epidemiology strains to museum hospice with SQLite persistence, operator/AI/court triggers, breeding and graft guards, topology museum nodes, and Seer plus oath ledger accountability.
2026-06-07 09:26:39 -07:00

362 lines
8.7 KiB
Go

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
hospice map[string]bool
}
func NewBreedingRegistry() *BreedingRegistry {
return &BreedingRegistry{
lanes: make(map[string]map[string]LaneWinner),
bred: make(map[string]BredPhenotype),
}
}
// SetHospiceStrains updates the retired-strain set used to skip breeding parents.
func (r *BreedingRegistry) SetHospiceStrains(strains map[string]bool) {
if r == nil {
return
}
r.mu.Lock()
defer r.mu.Unlock()
if len(strains) == 0 {
r.hospice = nil
return
}
r.hospice = make(map[string]bool, len(strains))
for k, v := range strains {
if v {
r.hospice[NormalizeStrainID(k)] = true
}
}
}
// 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
}
if LaneInHospice(lane, r.hospiceSnapshot()) {
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], r.hospice)
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 (r *BreedingRegistry) hospiceSnapshot() map[string]bool {
if r == nil {
return nil
}
r.mu.RLock()
defer r.mu.RUnlock()
if len(r.hospice) == 0 {
return nil
}
out := make(map[string]bool, len(r.hospice))
for k, v := range r.hospice {
out[k] = v
}
return out
}
func breedLaneWinners(fingerprint string, lanes map[string]LaneWinner, hospice map[string]bool) BredPhenotype {
parents := make([]LaneWinner, 0, len(lanes))
for _, w := range lanes {
if LaneInHospice(w.SpreadLane, hospice) {
continue
}
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...),
}
}