use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use uuid::Uuid;
use crate::connection::{IdentityError, RealmId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RealmLocator {
pub state_root: PathBuf,
pub realm: RealmId,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case", tag = "kind")]
pub enum RealmSelection {
Explicit { realm_id: String },
Isolated,
WorkspaceDerived { root: PathBuf },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct RealmConfig {
pub selection: RealmSelection,
pub instance_id: Option<String>,
pub backend_hint: Option<String>,
pub state_root: Option<PathBuf>,
}
impl Default for RealmConfig {
fn default() -> Self {
Self {
selection: RealmSelection::Isolated,
instance_id: None,
backend_hint: None,
state_root: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(default)]
pub struct ContextConfig {
pub context_root: Option<PathBuf>,
pub user_config_root: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(default)]
pub struct RuntimeBootstrap {
pub realm: RealmConfig,
pub context: ContextConfig,
}
#[derive(Debug, thiserror::Error)]
pub enum RuntimeBootstrapError {
#[error("`--realm` and `--isolated` cannot be used together")]
ConflictingSelection,
#[error("invalid explicit realm id: {0}")]
InvalidRealmId(String),
#[error(
"realm '{realm_id}' exists under both candidate state roots \
('{}' and '{}'); refusing to choose. Run `rkat storage doctor` to \
inspect both copies and `rkat storage migrate` to reconcile them, \
or pass --state-root to pick one explicitly.",
.local.display(), .global.display()
)]
RealmSplitBrain {
realm_id: String,
local: PathBuf,
global: PathBuf,
},
#[error("failed probing realm root '{}': {source}", .path.display())]
RootProbeFailed {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("realm manifest at '{}' is unreadable: {detail}", .path.display())]
ManifestUnreadable { path: PathBuf, detail: String },
#[error(
"realm '{requested}' collides with realm '{existing}' under '{}' \
(both ids sanitize to the same directory name); choose a \
non-colliding realm id or a different state root",
.root.display()
)]
RealmDirectoryCollision {
requested: String,
existing: String,
root: PathBuf,
},
}
pub fn default_state_root() -> PathBuf {
dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("meerkat")
.join("realms")
}
pub fn default_surface_instance_root(surface: &str) -> PathBuf {
dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("meerkat")
.join(surface)
}
pub const REALM_MANIFEST_FILE_NAME: &str = "realm_manifest.json";
pub fn sanitize_realm_id(realm_id: &str) -> String {
realm_id
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
ch
} else {
'_'
}
})
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RealmRootDefault {
ProjectLocal,
UserGlobal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RealmRootChoice {
Explicit,
ExistingLocal,
ExistingGlobal,
Default,
}
#[derive(Debug, Clone)]
pub struct DualRootResolution {
pub locator: RealmLocator,
pub choice: RealmRootChoice,
pub candidate_roots: Vec<PathBuf>,
}
#[derive(Deserialize)]
struct ManifestIdentityProbe {
realm_id: String,
}
pub fn realm_exists_under(
realms_root: &Path,
realm: &RealmId,
) -> Result<bool, RuntimeBootstrapError> {
let manifest_path = realms_root
.join(sanitize_realm_id(realm.as_str()))
.join(REALM_MANIFEST_FILE_NAME);
let bytes = match std::fs::read(&manifest_path) {
Ok(bytes) => bytes,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(source) => {
return Err(RuntimeBootstrapError::RootProbeFailed {
path: manifest_path,
source,
});
}
};
let probe: ManifestIdentityProbe = serde_json::from_slice(&bytes).map_err(|err| {
RuntimeBootstrapError::ManifestUnreadable {
path: manifest_path.clone(),
detail: err.to_string(),
}
})?;
if probe.realm_id == realm.as_str() {
Ok(true)
} else {
Err(RuntimeBootstrapError::RealmDirectoryCollision {
requested: realm.as_str().to_string(),
existing: probe.realm_id,
root: realms_root.to_path_buf(),
})
}
}
fn physically_same_root(a: &Path, b: &Path) -> bool {
if a == b {
return true;
}
match (std::fs::canonicalize(a), std::fs::canonicalize(b)) {
(Ok(canonical_a), Ok(canonical_b)) => canonical_a == canonical_b,
_ => false,
}
}
impl RealmConfig {
pub fn selection_from_inputs(
realm: Option<String>,
isolated: bool,
default: RealmSelection,
) -> Result<RealmSelection, RuntimeBootstrapError> {
if realm.is_some() && isolated {
return Err(RuntimeBootstrapError::ConflictingSelection);
}
if let Some(realm_id) = realm {
validate_explicit_realm_id(&realm_id)?;
return Ok(RealmSelection::Explicit { realm_id });
}
if isolated {
return Ok(RealmSelection::Isolated);
}
Ok(default)
}
pub fn resolve_locator(&self) -> Result<RealmLocator, RuntimeBootstrapError> {
let state_root = self.state_root.clone().unwrap_or_else(default_state_root);
let realm = self.resolve_realm_identity()?;
Ok(RealmLocator { state_root, realm })
}
pub fn resolve_realm_identity(&self) -> Result<RealmId, RuntimeBootstrapError> {
let realm_raw = match &self.selection {
RealmSelection::Explicit { realm_id } => realm_id.clone(),
RealmSelection::Isolated => generate_realm_id(),
RealmSelection::WorkspaceDerived { root } => derive_workspace_realm_id(root),
};
RealmId::parse(&realm_raw).map_err(|source| match source {
IdentityError::Empty => RuntimeBootstrapError::InvalidRealmId(realm_raw.clone()),
IdentityError::InvalidChar(_) => {
RuntimeBootstrapError::InvalidRealmId(realm_raw.clone())
}
})
}
pub fn resolve_locator_dual_root(
&self,
local_candidate: Option<&Path>,
default_root: RealmRootDefault,
) -> Result<DualRootResolution, RuntimeBootstrapError> {
self.resolve_locator_dual_root_with(local_candidate, &default_state_root(), default_root)
}
pub fn resolve_locator_dual_root_with(
&self,
local_candidate: Option<&Path>,
global_candidate: &Path,
default_root: RealmRootDefault,
) -> Result<DualRootResolution, RuntimeBootstrapError> {
let realm = self.resolve_realm_identity()?;
if let Some(explicit) = &self.state_root {
return Ok(DualRootResolution {
locator: RealmLocator {
state_root: explicit.clone(),
realm,
},
choice: RealmRootChoice::Explicit,
candidate_roots: vec![explicit.clone()],
});
}
let global_candidate = global_candidate.to_path_buf();
if let Some(local) = local_candidate
&& physically_same_root(local, &global_candidate)
{
let exists = realm_exists_under(local, &realm)?;
let root = local.to_path_buf();
return Ok(DualRootResolution {
locator: RealmLocator {
state_root: root.clone(),
realm,
},
choice: if exists {
RealmRootChoice::ExistingLocal
} else {
RealmRootChoice::Default
},
candidate_roots: vec![root],
});
}
let in_local = match local_candidate {
Some(root) => realm_exists_under(root, &realm)?,
None => false,
};
let in_global = realm_exists_under(&global_candidate, &realm)?;
let candidate_roots: Vec<PathBuf> = local_candidate
.map(Path::to_path_buf)
.into_iter()
.chain(std::iter::once(global_candidate.clone()))
.collect();
let (state_root, choice) = match (in_local, in_global) {
(true, true) => {
let local = local_candidate.map(Path::to_path_buf).unwrap_or_default();
return Err(RuntimeBootstrapError::RealmSplitBrain {
realm_id: realm.as_str().to_string(),
local,
global: global_candidate,
});
}
(true, false) => (
local_candidate.map(Path::to_path_buf).unwrap_or_default(),
RealmRootChoice::ExistingLocal,
),
(false, true) => (global_candidate, RealmRootChoice::ExistingGlobal),
(false, false) => {
let root = match (default_root, local_candidate) {
(RealmRootDefault::ProjectLocal, Some(local)) => local.to_path_buf(),
_ => global_candidate,
};
(root, RealmRootChoice::Default)
}
};
Ok(DualRootResolution {
locator: RealmLocator { state_root, realm },
choice,
candidate_roots,
})
}
}
pub fn validate_explicit_realm_id(realm_id: &str) -> Result<(), RuntimeBootstrapError> {
if realm_id.is_empty()
|| realm_id.len() > 64
|| realm_id.contains(':')
|| realm_id.chars().any(char::is_whitespace)
{
return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
}
let mut chars = realm_id.chars();
let first = chars
.next()
.ok_or_else(|| RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()))?;
if !first.is_ascii_alphanumeric() {
return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
}
if !chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') {
return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
}
if Uuid::parse_str(realm_id).is_ok() {
return Err(RuntimeBootstrapError::InvalidRealmId(realm_id.to_string()));
}
Ok(())
}
pub fn generate_realm_id() -> String {
format!("realm-{}", crate::time_compat::new_uuid_v7())
}
pub fn derive_workspace_realm_id(path: &Path) -> String {
let canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let key = canonical.to_string_lossy();
format!("ws-{}", fnv1a64_hex(&key))
}
pub fn fnv1a64_hex(input: &str) -> String {
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0100_0000_01b3;
let mut hash = OFFSET;
for b in input.as_bytes() {
hash ^= u64::from(*b);
hash = hash.wrapping_mul(PRIME);
}
format!("{hash:016x}")
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn selection_conflict_is_rejected() {
let result = RealmConfig::selection_from_inputs(
Some("team".to_string()),
true,
RealmSelection::Isolated,
);
assert!(matches!(
result,
Err(RuntimeBootstrapError::ConflictingSelection)
));
}
#[test]
fn explicit_realm_id_validation() {
assert!(validate_explicit_realm_id("team-alpha_1").is_ok());
assert!(validate_explicit_realm_id("bad:name").is_err());
assert!(validate_explicit_realm_id("").is_err());
assert!(validate_explicit_realm_id("550e8400-e29b-41d4-a716-446655440000").is_err());
}
#[test]
fn workspace_selection_is_deterministic() {
let root = PathBuf::from(".");
let cfg = RealmConfig {
selection: RealmSelection::WorkspaceDerived { root },
..RealmConfig::default()
};
let a = cfg.resolve_locator().map(|locator| locator.realm);
let b = cfg.resolve_locator().map(|locator| locator.realm);
assert!(a.is_ok());
assert_eq!(a.ok(), b.ok());
}
mod dual_root {
use super::*;
fn materialize_realm(realms_root: &Path, realm_id: &str) {
let dir = realms_root.join(sanitize_realm_id(realm_id));
std::fs::create_dir_all(&dir).expect("create realm dir");
let manifest = serde_json::json!({
"realm_id": realm_id,
"backend": "sqlite",
"created_at": "0",
});
std::fs::write(dir.join(REALM_MANIFEST_FILE_NAME), manifest.to_string())
.expect("write manifest");
}
fn explicit_cfg(realm_id: &str) -> RealmConfig {
RealmConfig {
selection: RealmSelection::Explicit {
realm_id: realm_id.to_string(),
},
..RealmConfig::default()
}
}
#[test]
fn explicit_state_root_wins_without_probing() {
let tmp = tempfile::tempdir().expect("tempdir");
let local = tmp.path().join("local");
materialize_realm(&local, "team");
let mut cfg = explicit_cfg("team");
cfg.state_root = Some(tmp.path().join("explicit"));
let global = tmp.path().join("global");
let resolved = cfg
.resolve_locator_dual_root_with(
Some(&local),
&global,
RealmRootDefault::ProjectLocal,
)
.expect("resolve");
assert_eq!(resolved.choice, RealmRootChoice::Explicit);
assert_eq!(resolved.locator.state_root, tmp.path().join("explicit"));
}
#[test]
fn realm_existing_only_locally_is_used_where_it_lies() {
let tmp = tempfile::tempdir().expect("tempdir");
let local = tmp.path().join("local");
materialize_realm(&local, "team");
let global = tmp.path().join("global");
let resolved = explicit_cfg("team")
.resolve_locator_dual_root_with(Some(&local), &global, RealmRootDefault::UserGlobal)
.expect("resolve");
assert_eq!(resolved.choice, RealmRootChoice::ExistingLocal);
assert_eq!(resolved.locator.state_root, local);
}
#[test]
fn absent_realm_falls_to_surface_default() {
let tmp = tempfile::tempdir().expect("tempdir");
let local = tmp.path().join("local");
std::fs::create_dir_all(&local).expect("mkdir");
let global = tmp.path().join("global");
let resolved = explicit_cfg("team")
.resolve_locator_dual_root_with(
Some(&local),
&global,
RealmRootDefault::ProjectLocal,
)
.expect("resolve");
assert_eq!(resolved.choice, RealmRootChoice::Default);
assert_eq!(resolved.locator.state_root, local);
}
#[test]
fn project_local_default_without_candidate_falls_back_global() {
let tmp = tempfile::tempdir().expect("tempdir");
let global = tmp.path().join("global");
let resolved = explicit_cfg("team")
.resolve_locator_dual_root_with(None, &global, RealmRootDefault::ProjectLocal)
.expect("resolve");
assert_eq!(resolved.choice, RealmRootChoice::Default);
assert_eq!(resolved.locator.state_root, global);
}
#[test]
fn isolated_identity_is_minted_exactly_once_per_resolution() {
let tmp = tempfile::tempdir().expect("tempdir");
let global = tmp.path().join("global");
let cfg = RealmConfig::default(); let resolved = cfg
.resolve_locator_dual_root_with(None, &global, RealmRootDefault::UserGlobal)
.expect("resolve");
assert_eq!(resolved.choice, RealmRootChoice::Default);
assert!(resolved.locator.realm.as_str().starts_with("realm-"));
}
#[test]
fn probe_recognizes_only_manifested_realms() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().join("realms");
let realm = RealmId::parse("team").expect("realm id");
assert!(!realm_exists_under(&root, &realm).expect("probe"));
std::fs::create_dir_all(root.join("team")).expect("mkdir");
assert!(!realm_exists_under(&root, &realm).expect("probe"));
materialize_realm(&root, "team");
assert!(realm_exists_under(&root, &realm).expect("probe"));
}
#[test]
fn probe_refuses_identity_colliding_directory() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().join("realms");
materialize_realm(&root, "a.b");
let realm = RealmId::parse("a_b").expect("realm id");
let err = realm_exists_under(&root, &realm).expect_err("collision must refuse");
match &err {
RuntimeBootstrapError::RealmDirectoryCollision {
requested,
existing,
..
} => {
assert_eq!(requested, "a_b");
assert_eq!(existing, "a.b");
}
other => panic!("wrong error: {other}"),
}
let resolved = explicit_cfg("a_b").resolve_locator_dual_root_with(
Some(&root),
&tmp.path().join("global"),
RealmRootDefault::ProjectLocal,
);
assert!(matches!(
resolved,
Err(RuntimeBootstrapError::RealmDirectoryCollision { .. })
));
}
#[test]
fn probe_refuses_unreadable_manifest_identity() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().join("realms");
let dir = root.join("team");
std::fs::create_dir_all(&dir).expect("mkdir");
std::fs::write(dir.join(REALM_MANIFEST_FILE_NAME), b"{ not json").expect("write");
let realm = RealmId::parse("team").expect("realm id");
let err = realm_exists_under(&root, &realm).expect_err("must refuse");
assert!(matches!(
err,
RuntimeBootstrapError::ManifestUnreadable { .. }
));
}
#[cfg(unix)]
#[test]
fn probe_io_errors_are_typed_not_absence() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().join("realms");
materialize_realm(&root, "team");
let manifest = root.join("team").join(REALM_MANIFEST_FILE_NAME);
let original = std::fs::metadata(&manifest).expect("meta").permissions();
std::fs::set_permissions(&manifest, std::fs::Permissions::from_mode(0o000))
.expect("chmod");
if std::fs::read(&manifest).is_ok() {
std::fs::set_permissions(&manifest, original).expect("restore");
return;
}
let realm = RealmId::parse("team").expect("realm id");
let result = realm_exists_under(&root, &realm);
std::fs::set_permissions(&manifest, original).expect("restore");
assert!(matches!(
result,
Err(RuntimeBootstrapError::RootProbeFailed { .. })
));
}
#[cfg(unix)]
#[test]
fn aliased_candidate_roots_are_one_root_not_split_brain() {
let tmp = tempfile::tempdir().expect("tempdir");
let global = tmp.path().join("global");
materialize_realm(&global, "team");
let local = tmp.path().join("local-alias");
std::os::unix::fs::symlink(&global, &local).expect("symlink");
let resolved = explicit_cfg("team")
.resolve_locator_dual_root_with(
Some(&local),
&global,
RealmRootDefault::ProjectLocal,
)
.expect("aliased roots must not refuse as split brain");
assert_eq!(resolved.choice, RealmRootChoice::ExistingLocal);
assert_eq!(resolved.locator.state_root, local);
assert_eq!(resolved.candidate_roots, vec![local]);
}
#[test]
fn resolution_exposes_probed_candidate_roots() {
let tmp = tempfile::tempdir().expect("tempdir");
let local = tmp.path().join("local");
std::fs::create_dir_all(&local).expect("mkdir");
let global = tmp.path().join("global");
let resolved = explicit_cfg("team")
.resolve_locator_dual_root_with(
Some(&local),
&global,
RealmRootDefault::ProjectLocal,
)
.expect("resolve");
assert_eq!(resolved.candidate_roots, vec![local, global.clone()]);
let mut cfg = explicit_cfg("team");
cfg.state_root = Some(tmp.path().join("explicit"));
let explicit = cfg
.resolve_locator_dual_root_with(None, &global, RealmRootDefault::UserGlobal)
.expect("resolve");
assert_eq!(explicit.candidate_roots, vec![tmp.path().join("explicit")]);
}
#[test]
fn split_brain_is_a_typed_refusal() {
let tmp = tempfile::tempdir().expect("tempdir");
let local = tmp.path().join("local");
let global = tmp.path().join("global");
materialize_realm(&local, "team");
materialize_realm(&global, "team");
let err = explicit_cfg("team")
.resolve_locator_dual_root_with(
Some(&local),
&global,
RealmRootDefault::ProjectLocal,
)
.expect_err("split brain must refuse");
match &err {
RuntimeBootstrapError::RealmSplitBrain {
realm_id,
local: found_local,
global: found_global,
} => {
assert_eq!(realm_id, "team");
assert_eq!(found_local, &local);
assert_eq!(found_global, &global);
}
other => panic!("wrong error: {other}"),
}
let message = err.to_string();
assert!(message.contains("rkat storage doctor"), "{message}");
}
#[test]
fn realm_existing_only_globally_is_used_where_it_lies() {
let tmp = tempfile::tempdir().expect("tempdir");
let local = tmp.path().join("local");
let global = tmp.path().join("global");
std::fs::create_dir_all(&local).expect("mkdir");
materialize_realm(&global, "team");
let resolved = explicit_cfg("team")
.resolve_locator_dual_root_with(
Some(&local),
&global,
RealmRootDefault::ProjectLocal,
)
.expect("resolve");
assert_eq!(resolved.choice, RealmRootChoice::ExistingGlobal);
assert_eq!(resolved.locator.state_root, global);
}
}
}