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}