Skip to main content

agent_store/
policy.rs

1//! Declarative storage policy.
2//!
3//! The backend a consumer opens is a **reviewed configuration choice**, never a
4//! hardcoded commitment in code. [`StorePolicy`] is that seam: its [`Default`]
5//! is today's behavior — a local SQLite file, no daemon — so adopting a policy
6//! changes nothing until a value is deliberately set, and changing direction
7//! (to Postgres, later) is a config edit rather than a rewrite.
8//!
9//! The policy is **flat by design**: one reviewable knob per line, embedded in
10//! a consumer's existing config (`[store] backend = "sqlite"`). This crate owns
11//! only the vocabulary — *resolution* lives in each consumer, because how a
12//! backend opens (pragmas, connection ownership, domain-specific SQL) is
13//! consumer-specific.
14
15use serde::{Deserialize, Serialize};
16
17/// Which storage backend a [`StorePolicy`] selects.
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
19#[serde(rename_all = "lowercase")]
20pub enum BackendKind {
21    /// Bundled SQLite — zero system deps, no daemon. The fleet default.
22    #[default]
23    Sqlite,
24    /// PostgreSQL — opt-in, only where an operator already runs a server.
25    /// Reserved: consumers reject it until their Postgres path is wired.
26    Postgres,
27}
28
29/// A declarative storage policy.
30///
31/// Flat and minimal today (one knob); grows new fields — a Postgres URL
32/// reference, a coordination doorbell mode — as those features land. The
33/// `#[serde(default)]` makes every field optional, so a bare `[store]` section
34/// (or none at all) resolves to the safe, daemonless SQLite default.
35#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
36#[serde(default)]
37pub struct StorePolicy {
38    /// Which backend to open.
39    pub backend: BackendKind,
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn default_is_safe_sqlite() {
48        // Regression: the default must stay SQLite, so adopting a policy is a
49        // no-op until a value is deliberately set (the low-risk guarantee).
50        assert_eq!(StorePolicy::default().backend, BackendKind::Sqlite);
51    }
52
53    #[test]
54    fn deserializes_flat_lowercase_knob() {
55        let p: StorePolicy = toml::from_str(r#"backend = "postgres""#).unwrap();
56        assert_eq!(p.backend, BackendKind::Postgres);
57
58        let p: StorePolicy = toml::from_str(r#"backend = "sqlite""#).unwrap();
59        assert_eq!(p.backend, BackendKind::Sqlite);
60    }
61
62    #[test]
63    fn empty_config_is_the_default() {
64        let p: StorePolicy = toml::from_str("").unwrap();
65        assert_eq!(p.backend, BackendKind::Sqlite);
66    }
67
68    #[test]
69    fn unknown_backend_is_an_error() {
70        assert!(toml::from_str::<StorePolicy>(r#"backend = "mongo""#).is_err());
71    }
72}