Skip to main content

cfgd_core/state/
decisions.rs

1use rusqlite::params;
2
3use super::StateStore;
4use super::types::PendingDecision;
5use crate::errors::Result;
6
7impl StateStore {
8    /// Upsert a pending decision. If an unresolved decision already exists for this
9    /// (source, resource) pair, updates the summary and resets the timestamp.
10    pub fn upsert_pending_decision(
11        &self,
12        source: &str,
13        resource: &str,
14        tier: &str,
15        action: &str,
16        summary: &str,
17    ) -> Result<i64> {
18        let timestamp = crate::utc_now_iso8601();
19        // Try to update an existing unresolved row first
20        let updated = self.conn.execute(
21            "UPDATE pending_decisions SET tier = ?1, action = ?2, summary = ?3, created_at = ?4
22                 WHERE source = ?5 AND resource = ?6 AND resolved_at IS NULL",
23            params![tier, action, summary, timestamp, source, resource],
24        )?;
25
26        if updated > 0 {
27            let id = self
28                .conn
29                .query_row(
30                    "SELECT id FROM pending_decisions WHERE source = ?1 AND resource = ?2 AND resolved_at IS NULL",
31                    params![source, resource],
32                    |row| row.get(0),
33                )
34                ?;
35            return Ok(id);
36        }
37
38        self.conn.execute(
39            "INSERT INTO pending_decisions (source, resource, tier, action, summary, created_at)
40                 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
41            params![source, resource, tier, action, summary, timestamp],
42        )?;
43        Ok(self.conn.last_insert_rowid())
44    }
45
46    /// Get all unresolved pending decisions.
47    pub fn pending_decisions(&self) -> Result<Vec<PendingDecision>> {
48        let mut stmt = self
49            .conn
50            .prepare(
51                "SELECT id, source, resource, tier, action, summary, created_at, resolved_at, resolution
52                 FROM pending_decisions WHERE resolved_at IS NULL ORDER BY created_at DESC",
53            )
54            ?;
55
56        let rows = stmt
57            .query_map([], |row| {
58                Ok(PendingDecision {
59                    id: row.get(0)?,
60                    source: row.get(1)?,
61                    resource: row.get(2)?,
62                    tier: row.get(3)?,
63                    action: row.get(4)?,
64                    summary: row.get(5)?,
65                    created_at: row.get(6)?,
66                    resolved_at: row.get(7)?,
67                    resolution: row.get(8)?,
68                })
69            })?
70            .collect::<std::result::Result<Vec<_>, _>>()?;
71
72        Ok(rows)
73    }
74
75    /// Get pending decisions for a specific source.
76    pub fn pending_decisions_for_source(&self, source: &str) -> Result<Vec<PendingDecision>> {
77        let mut stmt = self
78            .conn
79            .prepare(
80                "SELECT id, source, resource, tier, action, summary, created_at, resolved_at, resolution
81                 FROM pending_decisions WHERE source = ?1 AND resolved_at IS NULL ORDER BY created_at DESC",
82            )
83            ?;
84
85        let rows = stmt
86            .query_map(params![source], |row| {
87                Ok(PendingDecision {
88                    id: row.get(0)?,
89                    source: row.get(1)?,
90                    resource: row.get(2)?,
91                    tier: row.get(3)?,
92                    action: row.get(4)?,
93                    summary: row.get(5)?,
94                    created_at: row.get(6)?,
95                    resolved_at: row.get(7)?,
96                    resolution: row.get(8)?,
97                })
98            })?
99            .collect::<std::result::Result<Vec<_>, _>>()?;
100
101        Ok(rows)
102    }
103
104    /// Resolve a pending decision by resource path.
105    pub fn resolve_decision(&self, resource: &str, resolution: &str) -> Result<bool> {
106        let timestamp = crate::utc_now_iso8601();
107        let updated = self.conn.execute(
108            "UPDATE pending_decisions SET resolved_at = ?1, resolution = ?2
109                 WHERE resource = ?3 AND resolved_at IS NULL",
110            params![timestamp, resolution, resource],
111        )?;
112        Ok(updated > 0)
113    }
114
115    /// Resolve all pending decisions for a source.
116    pub fn resolve_decisions_for_source(&self, source: &str, resolution: &str) -> Result<usize> {
117        let timestamp = crate::utc_now_iso8601();
118        let updated = self.conn.execute(
119            "UPDATE pending_decisions SET resolved_at = ?1, resolution = ?2
120                 WHERE source = ?3 AND resolved_at IS NULL",
121            params![timestamp, resolution, source],
122        )?;
123        Ok(updated)
124    }
125
126    /// Resolve all pending decisions.
127    pub fn resolve_all_decisions(&self, resolution: &str) -> Result<usize> {
128        let timestamp = crate::utc_now_iso8601();
129        let updated = self.conn.execute(
130            "UPDATE pending_decisions SET resolved_at = ?1, resolution = ?2
131                 WHERE resolved_at IS NULL",
132            params![timestamp, resolution],
133        )?;
134        Ok(updated)
135    }
136}