Files
AetherForge/server/internal/api/court_chamber_test.go
AetherForge 5853f80c48
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add adversarial L4 court chamber with Seer court_debate feed.
Prosecutor and Public Defender use real fleet telemetry only; Judge dispatches L4 commands and emits full transcripts via seer_events and emberwake_court_debate when ai_control_enabled.
2026-06-07 09:21:44 -07:00

176 lines
5.2 KiB
Go

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)
}
}