Skip to main content

kaizen/core_loop/
alerts.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2use 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}