package api import ( "fmt" "sync" fleetai "crypto-miner-server/internal/ai" "crypto-miner-server/internal/clearance" "crypto-miner-server/internal/models" ) const stuckHostFailedTierThreshold = 14 // ClearanceManager returns the hub's session clearance tracker. func (h *WSHub) ClearanceManager() *ClearanceManager { if h == nil { return nil } return h.clearance } // ClearanceManager tracks per-agent session clearance and broadcasts changes. type ClearanceManager struct { hub *WSHub mu sync.RWMutex // agentClearance holds live session clearance keyed by agent ID. agentClearance map[string]int } func NewClearanceManager(hub *WSHub) *ClearanceManager { return &ClearanceManager{ hub: hub, agentClearance: make(map[string]int), } } // InitAgent sets baseline clearance when an agent connects. func (m *ClearanceManager) InitAgent(agentID string, agent *models.Agent) int { if m == nil { return clearance.DefaultClearance(agent) } level := clearance.DefaultClearance(agent) m.mu.Lock() m.agentClearance[agentID] = level m.mu.Unlock() // Baseline clearance is included in auth_response; push only on elevation. return level } // RemoveAgent drops session clearance state. func (m *ClearanceManager) RemoveAgent(agentID string) { if m == nil { return } m.mu.Lock() delete(m.agentClearance, agentID) m.mu.Unlock() } // Level returns the current session clearance for an agent. func (m *ClearanceManager) Level(agentID string) int { if m == nil { return clearance.L0 } m.mu.RLock() level, ok := m.agentClearance[agentID] m.mu.RUnlock() if ok { return level } if m.hub != nil && m.hub.db != nil { if agent, err := m.hub.db.GetAgent(agentID); err == nil && agent != nil { return clearance.DefaultClearance(agent) } } return clearance.L0 } // RequestElevation raises clearance when needed and notifies dashboards. func (m *ClearanceManager) RequestElevation(agentID string, toLevel int, reason, source string) (int, error) { if m == nil { return clearance.L0, nil } current := m.Level(agentID) newLevel, err := clearance.RequestElevation(m.hub.db, agentID, current, toLevel, reason, source) if err != nil { return current, err } if newLevel <= current { return current, nil } m.mu.Lock() m.agentClearance[agentID] = newLevel m.mu.Unlock() m.pushToAgent(agentID, newLevel) m.broadcastElevation(agentID, current, newLevel, reason, source) return newLevel, nil } func (m *ClearanceManager) pushToAgent(agentID string, level int) { if m == nil || m.hub == nil { return } _ = m.hub.SendToAgent(agentID, Message{ Type: "clearance_update", Payload: mustMarshal(map[string]interface{}{ "clearance_level": level, }), }) } func (m *ClearanceManager) broadcastElevation(agentID string, fromLevel, toLevel int, reason, source string) { if m == nil || m.hub == nil { return } m.hub.broadcastDashboard(Message{ Type: "clearance_elevated", Payload: mustMarshal(map[string]interface{}{ "agent_id": agentID, "from_level": fromLevel, "to_level": toLevel, "reason": reason, "source": source, }), }) } // ClearanceGuardExecutor wraps FleetAIExecutor with clearance enforcement. type ClearanceGuardExecutor struct { Inner *FleetAIExecutor Clearance *ClearanceManager } func (e *ClearanceGuardExecutor) Execute(agentID string, cmd fleetai.Command) (string, error) { if e == nil || e.Inner == nil { return "", fmt.Errorf("executor unavailable") } level := clearance.L0 if e.Clearance != nil { level = e.Clearance.Level(agentID) } if err := clearance.EnforceClearance(cmd.Type, cmd.Args, level); err != nil { return "", err } return e.Inner.Execute(agentID, cmd) }