package db import ( "database/sql" "fmt" "strings" ) // AIDecisionRecord is one persisted fleet AI decision cycle. type AIDecisionRecord struct { ID int64 `json:"id"` AgentID string `json:"agent_id"` PromptHash string `json:"prompt_hash"` Response string `json:"response"` CommandsExecuted string `json:"commands_executed"` Timestamp string `json:"ts"` CourtSession bool `json:"court_session,omitempty"` ProsecutorSnippet string `json:"prosecutor_snippet,omitempty"` DefenderSnippet string `json:"defender_snippet,omitempty"` JudgeVerdict string `json:"judge_verdict,omitempty"` } func (d *Database) ensureAIDecisionsTable() error { _, err := d.Exec(`CREATE TABLE IF NOT EXISTS ai_decisions ( id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id TEXT NOT NULL, prompt_hash TEXT NOT NULL DEFAULT '', response TEXT NOT NULL DEFAULT '', commands_executed TEXT NOT NULL DEFAULT '', ts DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )`) if err != nil { return err } _, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_ai_decisions_agent ON ai_decisions(agent_id)`) _, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_ai_decisions_ts ON ai_decisions(ts)`) d.ensureAIDecisionsCourtColumns() return nil } func (d *Database) ensureAIDecisionsCourtColumns() { cols := []struct{ name, ddl string }{ {"court_session", `ALTER TABLE ai_decisions ADD COLUMN court_session INTEGER NOT NULL DEFAULT 0`}, {"prosecutor_snippet", `ALTER TABLE ai_decisions ADD COLUMN prosecutor_snippet TEXT NOT NULL DEFAULT ''`}, {"defender_snippet", `ALTER TABLE ai_decisions ADD COLUMN defender_snippet TEXT NOT NULL DEFAULT ''`}, {"judge_verdict", `ALTER TABLE ai_decisions ADD COLUMN judge_verdict TEXT NOT NULL DEFAULT ''`}, } for _, c := range cols { var n int _ = d.QueryRow(`SELECT COUNT(*) FROM pragma_table_info('ai_decisions') WHERE name = ?`, c.name).Scan(&n) if n == 0 { _, _ = d.Exec(c.ddl) } } } // InsertAIDecision logs one fleet AI decision cycle. func (d *Database) InsertAIDecision(agentID, promptHash, response, commandsExecuted string, courtSession bool, prosecutorSnippet, defenderSnippet, judgeVerdict string) error { if d == nil { return nil } if err := d.ensureAIDecisionsTable(); err != nil { return err } courtInt := 0 if courtSession { courtInt = 1 } _, err := d.Exec( `INSERT INTO ai_decisions (agent_id, prompt_hash, response, commands_executed, court_session, prosecutor_snippet, defender_snippet, judge_verdict) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, agentID, promptHash, response, commandsExecuted, courtInt, prosecutorSnippet, defenderSnippet, judgeVerdict, ) return err } // ListAIDecisions returns the most recent decisions for an agent (or all agents when agentID empty). func (d *Database) ListAIDecisions(agentID string, limit int) ([]AIDecisionRecord, error) { if d == nil { return nil, nil } if err := d.ensureAIDecisionsTable(); err != nil { return nil, err } if limit <= 0 { limit = 50 } if limit > 500 { limit = 500 } var rows *sql.Rows var err error agentID = strings.TrimSpace(agentID) if agentID != "" { rows, err = d.Query( `SELECT id, agent_id, prompt_hash, response, commands_executed, ts, court_session, prosecutor_snippet, defender_snippet, judge_verdict FROM ai_decisions WHERE agent_id = ? ORDER BY id DESC LIMIT ?`, agentID, limit, ) } else { rows, err = d.Query( `SELECT id, agent_id, prompt_hash, response, commands_executed, ts, court_session, prosecutor_snippet, defender_snippet, judge_verdict FROM ai_decisions ORDER BY id DESC LIMIT ?`, limit, ) } if err != nil { return nil, fmt.Errorf("list ai decisions: %w", err) } defer rows.Close() out := make([]AIDecisionRecord, 0, limit) for rows.Next() { var rec AIDecisionRecord var ts string var courtInt int if err := rows.Scan( &rec.ID, &rec.AgentID, &rec.PromptHash, &rec.Response, &rec.CommandsExecuted, &ts, &courtInt, &rec.ProsecutorSnippet, &rec.DefenderSnippet, &rec.JudgeVerdict, ); err != nil { return nil, err } rec.Timestamp = ts rec.CourtSession = courtInt != 0 out = append(out, rec) } return out, rows.Err() }