Skip to main content

meerkat_core/
runtime_bootstrap.rs

1//! Surface/runtime bootstrap contracts shared across interfaces.
2//!
3//! This module defines how runtimes resolve realm identity and filesystem roots
4//! without relying on ambient process state by default.
5
6use serde::{Deserialize, Serialize};
7use std::path::{Path, PathBuf};
8use uuid::Uuid;
9
10/// Canonical state sharing locator.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct RealmLocator {
13    pub state_root: PathBuf,
14    pub realm_id: String,
15}
16
17/// Realm selection mode.
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
19#[serde(rename_all = "snake_case", tag = "kind")]
20pub enum RealmSelection {
21    Explicit { realm_id: String },
22    Isolated,
23    WorkspaceDerived { root: PathBuf },
24}
25
26/// Realm/runtime settings.
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28#[serde(default)]
29pub struct RealmConfig {
30    pub selection: RealmSelection,
31    pub instance_id: Option<String>,
32    /// String hint (e.g. "sqlite", "jsonl"), interpreted by surface/store layers.
33    pub backend_hint: Option<String>,
34    /// Root directory containing all realm directories.
35    pub state_root: Option<PathBuf>,
36}
37
38impl Default for RealmConfig {
39    fn default() -> Self {
40        Self {
41            selection: RealmSelection::Isolated,
42            instance_id: None,
43            backend_hint: None,
44            state_root: None,
45        }
46    }
47}
48
49/// Filesystem convention settings.
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
51#[serde(default)]
52pub struct ContextConfig {
53    pub context_root: Option<PathBuf>,
54    pub user_config_root: Option<PathBuf>,
55}
56
57/// Top-level runtime bootstrap payload.
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
59#[serde(default)]
60pub struct RuntimeBootstrap {
61    pub realm: RealmConfig,
62    pub context: ContextConfig,
63}
64
65/// Errors resolving runtime bootstrap.
66#[derive(Debug, thiserror::Error)]
67pub enum RuntimeBootstrapError {
68    #[error("`--realm` and `--isolated` cannot be used together")]
69    ConflictingSelection,
70    #[error("invalid explicit realm id: {0}")]
71    InvalidRealmId(String),
72}
73
74/// Default global state root shared across surfaces.
75pub fn default_state_root() -> PathBuf {
76    dirs::data_dir()
77        .unwrap_or_else(|| PathBuf::from("."))
78        .join("meerkat")
79        .join("realms")
80}
81
82impl RealmConfig {
83    /// Build selection from common CLI inputs, with a provided default mode.
84    pub fn selection_from_inputs(
85        realm: Option<String>,
86        isolated: bool,
87        default: RealmSelection,
88    ) -> Result<RealmSelection, RuntimeBootstrapError> {
89        if realm.is_some() && isolated {
90            return Err(RuntimeBootstrapError::ConflictingSelection);
91        }
92        if let Some(realm_id) = realm {
93            validate_explicit_realm_id(&realm_id)?;
94            return Ok(RealmSelection::Explicit { realm_id });
95        }
96        if isolated {
97            return Ok(RealmSelection::Isolated);
98        }
99        Ok(default)
100    }
101
102    /// Resolve a concrete `(state_root, realm_id)` locator.
103    pub fn resolve_locator(&self) -> Result<RealmLocator, RuntimeBootstrapError> {
104        let state_root = self.state_root.clone().unwrap_or_else(default_state_root);
105        let realm_id = match &self.selection {
106            RealmSelection::Explicit { realm_id } => realm_id.clone(),
107            RealmSelection::Isolated => generate_realm_id(),
108            RealmSelection::WorkspaceDerived { root } => derive_workspace_realm_id(root),
109        };
110        Ok(RealmLocator {
111            state_root,
112            realm_id,
113        })
114    }
115}
116
117pub fn validate_explicit_realm_id(realm_id: &str) -> Result<(), RuntimeBootstrapError> {
118    if realm_id.is_empty()
119        || realm_id.len() > 64
120        || realm_id.contains(':')
121        || realm_id.chars().any(char::is_whitespace)
122    {
123        return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
124    }
125    let mut chars = realm_id.chars();
126    let first = chars
127        .next()
128        .ok_or_else(|| RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()))?;
129    if !first.is_ascii_alphanumeric() {
130        return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
131    }
132    if !chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') {
133        return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
134    }
135    // Reserve UUID-looking IDs for session ids, preventing locator ambiguity.
136    if Uuid::parse_str(realm_id).is_ok() {
137        return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
138    }
139    Ok(())
140}
141
142pub fn generate_realm_id() -> String {
143    format!("realm-{}", Uuid::now_v7())
144}
145
146pub fn derive_workspace_realm_id(path: &Path) -> String {
147    let canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
148    let key = canonical.to_string_lossy();
149    format!("ws-{}", fnv1a64_hex(&key))
150}
151
152pub fn fnv1a64_hex(input: &str) -> String {
153    const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
154    const PRIME: u64 = 0x0100_0000_01b3;
155    let mut hash = OFFSET;
156    for b in input.as_bytes() {
157        hash ^= u64::from(*b);
158        hash = hash.wrapping_mul(PRIME);
159    }
160    format!("{hash:016x}")
161}
162
163#[cfg(test)]
164#[allow(clippy::expect_used)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn selection_conflict_is_rejected() {
170        let result = RealmConfig::selection_from_inputs(
171            Some("team".to_string()),
172            true,
173            RealmSelection::Isolated,
174        );
175        assert!(matches!(
176            result,
177            Err(RuntimeBootstrapError::ConflictingSelection)
178        ));
179    }
180
181    #[test]
182    fn explicit_realm_id_validation() {
183        assert!(validate_explicit_realm_id("team-alpha_1").is_ok());
184        assert!(validate_explicit_realm_id("bad:name").is_err());
185        assert!(validate_explicit_realm_id("").is_err());
186        assert!(validate_explicit_realm_id("550e8400-e29b-41d4-a716-446655440000").is_err());
187    }
188
189    #[test]
190    fn workspace_selection_is_deterministic() {
191        let root = PathBuf::from(".");
192        let cfg = RealmConfig {
193            selection: RealmSelection::WorkspaceDerived { root },
194            ..RealmConfig::default()
195        };
196        let a = cfg.resolve_locator().map(|locator| locator.realm_id);
197        let b = cfg.resolve_locator().map(|locator| locator.realm_id);
198        assert!(a.is_ok());
199        assert_eq!(a.ok(), b.ok());
200    }
201}