Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
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.
362 lines
8.7 KiB
Go
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...),
|
|
}
|
|
}
|