Add Strain Hospice for graceful low-win strain retirement.
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.
This commit is contained in:
AetherForge
2026-06-07 09:26:39 -07:00
parent 894b7a50ae
commit d605ef4adb
18 changed files with 701 additions and 18 deletions

View File

@@ -44,9 +44,10 @@ type LaneWinnerInput struct {
// 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
mu sync.RWMutex
lanes map[string]map[string]LaneWinner
bred map[string]BredPhenotype
hospice map[string]bool
}
func NewBreedingRegistry() *BreedingRegistry {
@@ -56,6 +57,25 @@ func NewBreedingRegistry() *BreedingRegistry {
}
}
// 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)
@@ -63,6 +83,9 @@ func (r *BreedingRegistry) RecordLaneWinner(in LaneWinnerInput) (BredPhenotype,
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...),
@@ -81,7 +104,7 @@ func (r *BreedingRegistry) RecordLaneWinner(in LaneWinnerInput) (BredPhenotype,
if len(r.lanes[fp]) < 2 {
return BredPhenotype{}, false
}
bred := breedLaneWinners(fp, r.lanes[fp])
bred := breedLaneWinners(fp, r.lanes[fp], r.hospice)
if len(bred.TierOrder) == 0 {
return BredPhenotype{}, false
}
@@ -115,9 +138,28 @@ func (r *BreedingRegistry) LaneCount(fingerprint string) int {
return len(r.lanes[fp])
}
func breedLaneWinners(fingerprint string, lanes map[string]LaneWinner) BredPhenotype {
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 {