Files
AetherForge/server/internal/db/ai_decisions.go
AetherForge d9f36f182c
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add phenotype cloning, failure atlas, AI court session, and clearance L0-L4
2026-06-07 02:41:54 -07:00

133 lines
4.1 KiB
Go

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