kaizen/core_loop/
alerts.rs1use crate::core_loop::{AlertEvent, AlertSeverity};
3use crate::store::Store;
4use anyhow::Result;
5use rusqlite::{OptionalExtension, params};
6
7pub use crate::core_loop::alert_checks::check_builtin;
8
9pub fn emit(
10 store: &Store,
11 source_key: &str,
12 name: &str,
13 severity: AlertSeverity,
14 message: &str,
15 session_id: Option<&str>,
16 now_ms: u64,
17) -> Result<AlertEvent> {
18 let id = uuid::Uuid::now_v7().to_string();
19 store.conn().execute(
20 "INSERT OR IGNORE INTO alert_events
21 (id, source_key, name, severity, message, session_id, created_at_ms)
22 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
23 params![
24 id,
25 source_key,
26 name,
27 severity.as_str(),
28 message,
29 session_id,
30 now_ms as i64
31 ],
32 )?;
33 by_source(store, source_key)
34}
35
36pub fn list(store: &Store) -> Result<Vec<AlertEvent>> {
37 let mut stmt = store.conn().prepare("SELECT id, source_key, name, severity, message, session_id, created_at_ms FROM alert_events ORDER BY created_at_ms DESC")?;
38 let rows = stmt.query_map([], row)?;
39 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
40}
41
42fn by_source(store: &Store, source_key: &str) -> Result<AlertEvent> {
43 let sql = "SELECT id, source_key, name, severity, message, session_id, created_at_ms FROM alert_events WHERE source_key = ?1";
44 store
45 .conn()
46 .query_row(sql, params![source_key], row)
47 .optional()?
48 .ok_or_else(|| anyhow::anyhow!("alert missing after insert"))
49}
50
51fn row(r: &rusqlite::Row<'_>) -> rusqlite::Result<AlertEvent> {
52 Ok(AlertEvent {
53 id: r.get(0)?,
54 source_key: r.get(1)?,
55 name: r.get(2)?,
56 severity: severity(r.get::<_, String>(3)?.as_str()),
57 message: r.get(4)?,
58 session_id: r.get(5)?,
59 created_at_ms: r.get::<_, i64>(6)? as u64,
60 })
61}
62
63fn severity(s: &str) -> AlertSeverity {
64 match s {
65 "critical" => AlertSeverity::Critical,
66 "info" => AlertSeverity::Info,
67 _ => AlertSeverity::Warning,
68 }
69}