package db import ( "database/sql" "fmt" "strings" ) // ClearanceEvent is one clearance elevation audit row. type ClearanceEvent struct { ID int64 `json:"id"` AgentID string `json:"agent_id"` FromLevel int `json:"from_level"` ToLevel int `json:"to_level"` Reason string `json:"reason"` Source string `json:"source"` Timestamp string `json:"ts"` } func (d *Database) ensureClearanceEventsTable() error { _, err := d.Exec(`CREATE TABLE IF NOT EXISTS clearance_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id TEXT NOT NULL, from_level INTEGER NOT NULL DEFAULT 0, to_level INTEGER NOT NULL DEFAULT 0, reason TEXT NOT NULL DEFAULT '', source TEXT NOT NULL DEFAULT '', ts DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP )`) if err != nil { return err } _, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_clearance_events_agent ON clearance_events(agent_id)`) _, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_clearance_events_ts ON clearance_events(ts)`) return nil } // InsertClearanceEvent logs a clearance elevation. func (d *Database) InsertClearanceEvent(agentID string, fromLevel, toLevel int, reason, source string) error { if d == nil { return nil } if err := d.ensureClearanceEventsTable(); err != nil { return err } _, err := d.Exec( `INSERT INTO clearance_events (agent_id, from_level, to_level, reason, source) VALUES (?, ?, ?, ?, ?)`, agentID, fromLevel, toLevel, reason, source, ) return err } // ListClearanceEvents returns recent clearance events for an agent (or fleet-wide when agentID empty). func (d *Database) ListClearanceEvents(agentID string, limit int) ([]ClearanceEvent, error) { if d == nil { return nil, nil } if err := d.ensureClearanceEventsTable(); 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, from_level, to_level, reason, source, ts FROM clearance_events WHERE agent_id = ? ORDER BY id DESC LIMIT ?`, agentID, limit, ) } else { rows, err = d.Query( `SELECT id, agent_id, from_level, to_level, reason, source, ts FROM clearance_events ORDER BY id DESC LIMIT ?`, limit, ) } if err != nil { return nil, fmt.Errorf("list clearance events: %w", err) } defer rows.Close() out := make([]ClearanceEvent, 0, limit) for rows.Next() { var rec ClearanceEvent var ts string if err := rows.Scan(&rec.ID, &rec.AgentID, &rec.FromLevel, &rec.ToLevel, &rec.Reason, &rec.Source, &ts); err != nil { return nil, err } rec.Timestamp = ts out = append(out, rec) } return out, rows.Err() }