package api import ( "encoding/json" "net/http" "time" fleetai "crypto-miner-server/internal/ai" ) // ScoutConstellationHandler serves active scout venue constellations. type ScoutConstellationHandler struct { hub *WSHub } // NewScoutConstellationHandler returns a REST handler backed by the WS hub. func NewScoutConstellationHandler(hub *WSHub) *ScoutConstellationHandler { return &ScoutConstellationHandler{hub: hub} } // GET /api/v1/scout/constellations func (h *ScoutConstellationHandler) GetConstellations(w http.ResponseWriter, _ *http.Request) { if h.hub == nil { writeJSON(w, map[string]interface{}{"constellations": []fleetai.ScoutConstellation{}}) return } writeJSON(w, map[string]interface{}{ "constellations": h.hub.scoutConstellationSnapshot(), "generated_at": time.Now().UTC().Format(time.RFC3339), }) } func (h *WSHub) ensureScoutConstellations() { if h.scoutConstellations == nil { h.scoutConstellations = fleetai.NewScoutConstellationRegistry() } if h.scoutAgents == nil { h.scoutAgents = make(map[string]bool) } } func (h *WSHub) scoutConstellationSnapshot() []fleetai.ScoutConstellation { h.scoutConstellationMu.Lock() defer h.scoutConstellationMu.Unlock() h.ensureScoutConstellations() return h.scoutConstellations.Snapshot() } func (h *WSHub) scoutConstellationForAgent(agentID string) *fleetai.ScoutConstellation { h.scoutConstellationMu.Lock() defer h.scoutConstellationMu.Unlock() h.ensureScoutConstellations() return h.scoutConstellations.ForAgent(agentID) } func (h *WSHub) ingestScoutConstellationReport(agentID, ssid string, serviceCount int) { h.scoutConstellationMu.Lock() defer h.scoutConstellationMu.Unlock() h.ensureScoutConstellations() h.scoutAgents[agentID] = true constellation, changed := h.scoutConstellations.Record(agentID, ssid, serviceCount, time.Now().UTC()) if !changed { return } h.broadcastScoutConstellationsLocked() h.pushScoutConstellationPolicyLocked(constellation) } func (h *WSHub) broadcastScoutConstellationsLocked() { snapshot := h.scoutConstellations.Snapshot() h.broadcastDashboard(Message{ Type: "scout_constellations", Payload: mustMarshal(map[string]interface{}{ "constellations": snapshot, "generated_at": time.Now().UTC().Format(time.RFC3339), }), }) } func (h *WSHub) pushScoutConstellationPolicyLocked(constellation fleetai.ScoutConstellation) { spreadPolicy := fleetai.BuildScoutSpreadPolicy(constellation) temp := fleetai.PersonaSpreadTemperament(constellation.PersonaPack) policy := FleetAgentPolicy{SpreadTemperament: &temp} for _, agentID := range constellation.AgentIDs { payload := marshalPolicyUpdatePayload("scout-constellation-"+constellation.SSID, policy) var body map[string]interface{} _ = json.Unmarshal(payload, &body) if body == nil { body = map[string]interface{}{} } body["spread_policy"] = spreadPolicy out, _ := json.Marshal(body) _ = h.SendToAgent(agentID, Message{Type: "policy_update", Payload: out}) } } func (h *WSHub) scoutSpreadPolicyForAuth(agentID string) map[string]interface{} { c := h.scoutConstellationForAgent(agentID) if c == nil { return nil } return fleetai.BuildScoutSpreadPolicy(*c) }