lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit fdad8c0bae79b21938963670c43497fb4ffb826e
parent da29d88e8f0b8316fa91287d6a7ef184a57459c0
Author: triesap <tyson@radroots.org>
Date:   Sun, 12 Apr 2026 04:37:39 +0000

runtime_manager: add managed runtime facade helpers

Diffstat:
Mcrates/runtime_manager/src/lib.rs | 13+++++++++----
Acrates/runtime_manager/src/managed.rs | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/runtime_paths/src/lib.rs | 13+++++++------
Acrates/runtime_paths/src/service.rs | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 559 insertions(+), 10 deletions(-)

diff --git a/crates/runtime_manager/src/lib.rs b/crates/runtime_manager/src/lib.rs @@ -2,6 +2,7 @@ pub mod error; pub mod lifecycle; +pub mod managed; pub mod model; pub mod paths; pub mod registry; @@ -12,6 +13,10 @@ pub use lifecycle::{ read_secret_file, remove_instance_artifacts, start_process, stop_process, write_instance_metadata, write_managed_file, write_secret_file, }; +pub use managed::{ + active_management_mode_for_profile, load_management_context, resolve_runtime_target, + runtime_group, ManagedRuntimeContext, ManagedRuntimeGroup, ManagedRuntimeTarget, +}; pub use model::{ BootstrapRuntimeContract, LifecycleContract, ManagedRuntimeHealthState, ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry, @@ -19,8 +24,8 @@ pub use model::{ RadrootsRuntimeManagementContract, RuntimeGroups, }; pub use paths::{ - ManagedRuntimeInstancePaths, ManagedRuntimeSharedPaths, bootstrap_runtime, - resolve_instance_paths, resolve_shared_paths, + bootstrap_runtime, resolve_instance_paths, resolve_shared_paths, ManagedRuntimeInstancePaths, + ManagedRuntimeSharedPaths, }; pub use registry::{instance, load_registry, remove_instance, save_registry, upsert_instance}; @@ -51,9 +56,9 @@ mod tests { use tempfile::tempdir; use crate::{ - ManagedRuntimeHealthState, ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, bootstrap_runtime, instance, load_registry, parse_contract_str, resolve_instance_paths, - resolve_shared_paths, save_registry, upsert_instance, + resolve_shared_paths, save_registry, upsert_instance, ManagedRuntimeHealthState, + ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, }; fn assert_error_contains(err: &crate::RadrootsRuntimeManagerError, parts: &[&str]) { diff --git a/crates/runtime_manager/src/managed.rs b/crates/runtime_manager/src/managed.rs @@ -0,0 +1,326 @@ +use std::path::PathBuf; + +use radroots_runtime_paths::{RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver}; + +use crate::{ + load_registry, resolve_instance_paths, resolve_shared_paths, BootstrapRuntimeContract, + ManagedRuntimeInstancePaths, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry, + ManagementModeContract, RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError, +}; + +#[derive(Debug, Clone)] +pub struct ManagedRuntimeContext { + pub contract: RadrootsRuntimeManagementContract, + pub shared_paths: crate::ManagedRuntimeSharedPaths, + pub registry: ManagedRuntimeInstanceRegistry, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ManagedRuntimeGroup { + ActiveManagedTarget, + DefinedManagedTarget, + BootstrapOnly, + Unknown, +} + +impl ManagedRuntimeGroup { + pub fn as_str(self) -> &'static str { + match self { + Self::ActiveManagedTarget => "active_managed_target", + Self::DefinedManagedTarget => "defined_managed_target", + Self::BootstrapOnly => "bootstrap_only", + Self::Unknown => "unknown", + } + } + + pub fn posture(self) -> &'static str { + match self { + Self::ActiveManagedTarget => "active_managed_target", + Self::DefinedManagedTarget => "defined_future_target", + Self::BootstrapOnly => "bootstrap_only_direct_binding", + Self::Unknown => "unknown_runtime", + } + } +} + +#[derive(Debug, Clone)] +pub struct ManagedRuntimeTarget { + pub runtime_id: String, + pub instance_id: String, + pub instance_source: String, + pub runtime_group: ManagedRuntimeGroup, + pub management_mode: Option<String>, + pub mode_contract: Option<ManagementModeContract>, + pub bootstrap: Option<BootstrapRuntimeContract>, + pub instance_record: Option<ManagedRuntimeInstanceRecord>, + pub predicted_paths: Option<ManagedRuntimeInstancePaths>, + pub registry_path: PathBuf, +} + +pub fn load_management_context( + contract: RadrootsRuntimeManagementContract, + resolver: &RadrootsPathResolver, + profile: RadrootsPathProfile, + overrides: &RadrootsPathOverrides, +) -> Result<ManagedRuntimeContext, RadrootsRuntimeManagerError> { + let mode_id = active_management_mode_for_profile(&contract, profile)?; + let shared_paths = resolve_shared_paths(&contract, resolver, profile, overrides, mode_id)?; + let registry = load_registry(&shared_paths.instance_registry_path)?; + Ok(ManagedRuntimeContext { + contract, + shared_paths, + registry, + }) +} + +pub fn active_management_mode_for_profile<'a>( + contract: &'a RadrootsRuntimeManagementContract, + profile: RadrootsPathProfile, +) -> Result<&'a str, RadrootsRuntimeManagerError> { + let profile_id = profile.to_string(); + contract + .mode + .iter() + .find(|(_, mode)| { + mode.contract_state == "active" + && mode + .supported_profiles + .iter() + .any(|entry| entry == &profile_id) + }) + .map(|(mode_id, _)| mode_id.as_str()) + .ok_or_else(|| RadrootsRuntimeManagerError::UnsupportedProfile { + mode_id: "active".to_owned(), + profile: profile_id, + }) +} + +pub fn resolve_runtime_target( + context: &ManagedRuntimeContext, + runtime_id: &str, + requested_instance_id: Option<&str>, +) -> ManagedRuntimeTarget { + let runtime_group = runtime_group(&context.contract, runtime_id); + let bootstrap = context.contract.bootstrap.get(runtime_id).cloned(); + let instance_id = requested_instance_id + .map(ToOwned::to_owned) + .or_else(|| { + bootstrap + .as_ref() + .map(|entry| entry.default_instance_id.clone()) + }) + .unwrap_or_else(|| "default".to_owned()); + let instance_source = if requested_instance_id.is_some() { + "command_arg".to_owned() + } else if bootstrap.is_some() { + "bootstrap_default".to_owned() + } else { + "implicit_default".to_owned() + }; + let management_mode = bootstrap + .as_ref() + .map(|entry| entry.management_mode.clone()); + let mode_contract = management_mode + .as_ref() + .and_then(|mode_id| context.contract.mode.get(mode_id).cloned()); + let instance_record = context + .registry + .instances + .iter() + .find(|record| record.runtime_id == runtime_id && record.instance_id == instance_id) + .cloned(); + let predicted_paths = if runtime_group == ManagedRuntimeGroup::ActiveManagedTarget { + Some(resolve_instance_paths( + &context.shared_paths, + runtime_id, + instance_id.as_str(), + )) + } else { + None + }; + + ManagedRuntimeTarget { + runtime_id: runtime_id.to_owned(), + instance_id, + instance_source, + runtime_group, + management_mode, + mode_contract, + bootstrap, + instance_record, + predicted_paths, + registry_path: context.shared_paths.instance_registry_path.clone(), + } +} + +pub fn runtime_group( + contract: &RadrootsRuntimeManagementContract, + runtime_id: &str, +) -> ManagedRuntimeGroup { + if contract + .managed_runtime_targets + .active + .iter() + .any(|entry| entry == runtime_id) + { + ManagedRuntimeGroup::ActiveManagedTarget + } else if contract + .managed_runtime_targets + .defined + .iter() + .any(|entry| entry == runtime_id) + { + ManagedRuntimeGroup::DefinedManagedTarget + } else if contract + .managed_runtime_targets + .bootstrap_only + .iter() + .any(|entry| entry == runtime_id) + { + ManagedRuntimeGroup::BootstrapOnly + } else { + ManagedRuntimeGroup::Unknown + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use radroots_runtime_paths::{ + RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, + RadrootsPlatform, + }; + + use super::{ + active_management_mode_for_profile, load_management_context, resolve_runtime_target, + ManagedRuntimeGroup, + }; + use crate::{parse_contract_str, ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord}; + + const CONTRACT: &str = r#" +schema = "radroots-runtime-management" +schema_version = 1 +owner_doc = "docs/migration/radroots-modular-runtime-management-bootstrap-rcl.md" +runtime_registry = "registry.toml" +distribution_contract = "distribution.toml" +capabilities_contract = "capabilities.toml" + +[defaults] +instance_cardinality = "single_default_instance" +managed_runtime_lookup = "shared_instance_registry" +explicit_runtime_endpoint_overrides_precede_managed_instance_binding = true +global_path_mutation_forbidden = true + +[management_clients] +active = ["cli"] +defined = [] + +[managed_runtime_targets] +active = ["radrootsd"] +defined = ["myc"] +bootstrap_only = ["hyf"] + +[lifecycle] +actions = ["install", "start"] +destructive_actions = [] +health_states = ["not_installed", "running"] + +[mode.interactive_user_managed] +contract_state = "active" +platforms = ["linux"] +supported_profiles = ["interactive_user", "repo_local"] +service_manager_integration = false +uses_absolute_binary_paths = true +default_instance_cardinality = "single_default_instance" + +[paths.interactive_user_managed] +shared_namespace = "shared/runtime-manager" +instance_registry_root_class = "config" +instance_registry_rel = "shared/runtime-manager/instances.toml" +artifact_cache_root_class = "cache" +artifact_cache_rel = "shared/runtime-manager/artifacts" +install_root_class = "data" +install_root_rel = "shared/runtime-manager/installs" +state_root_class = "data" +state_root_rel = "shared/runtime-manager/state" +logs_root_class = "logs" +logs_root_rel = "shared/runtime-manager" +run_root_class = "run" +run_root_rel = "shared/runtime-manager" +secrets_root_class = "secrets" +secrets_namespace_rel = "shared/runtime-manager" + +[instance_metadata] +required_fields = ["runtime_id"] +optional_fields = ["notes"] + +[bootstrap.radrootsd] +runtime_id = "radrootsd" +management_mode = "interactive_user_managed" +default_instance_id = "local" +install_strategy = "archive_unpack" +config_format = "toml" +requires_bootstrap_secret = true +requires_config_bootstrap = true +requires_signer_provider = false +health_surface = "jsonrpc_status" +preferred_cli_binding = true +"#; + + #[test] + fn active_management_mode_matches_supported_profile() { + let contract = parse_contract_str(CONTRACT).expect("contract"); + let mode_id = + active_management_mode_for_profile(&contract, RadrootsPathProfile::InteractiveUser) + .expect("mode"); + assert_eq!(mode_id, "interactive_user_managed"); + } + + #[test] + fn resolve_runtime_target_uses_bootstrap_default_instance_id() { + let contract = parse_contract_str(CONTRACT).expect("contract"); + let resolver = RadrootsPathResolver::new( + RadrootsPlatform::Linux, + RadrootsHostEnvironment { + home_dir: Some(PathBuf::from("/home/treesap")), + ..RadrootsHostEnvironment::default() + }, + ); + let mut context = load_management_context( + contract, + &resolver, + RadrootsPathProfile::InteractiveUser, + &RadrootsPathOverrides::default(), + ) + .expect("context"); + context + .registry + .instances + .push(ManagedRuntimeInstanceRecord { + runtime_id: "radrootsd".to_owned(), + instance_id: "local".to_owned(), + management_mode: "interactive_user_managed".to_owned(), + install_state: ManagedRuntimeInstallState::Configured, + binary_path: PathBuf::from("/tmp/bin/radrootsd"), + config_path: PathBuf::from("/tmp/config.toml"), + logs_path: PathBuf::from("/tmp/logs"), + run_path: PathBuf::from("/tmp/run"), + installed_version: "0.1.0-alpha.2".to_owned(), + health_endpoint: None, + secret_material_ref: None, + last_started_at: None, + last_stopped_at: None, + notes: None, + }); + + let target = resolve_runtime_target(&context, "radrootsd", None); + assert_eq!(target.instance_id, "local"); + assert_eq!(target.instance_source, "bootstrap_default"); + assert_eq!( + target.runtime_group, + ManagedRuntimeGroup::ActiveManagedTarget + ); + assert!(target.predicted_paths.is_some()); + } +} diff --git a/crates/runtime_paths/src/lib.rs b/crates/runtime_paths/src/lib.rs @@ -6,21 +6,22 @@ pub mod migration; pub mod namespace; pub mod platform; pub mod roots; +pub mod service; pub use conventions::{ - DEFAULT_CONFIG_FILE_NAME, DEFAULT_SERVICE_IDENTITY_FILE_NAME, - DEFAULT_SHARED_IDENTITY_FILE_NAME, RadrootsBootstrapPaths, default_namespaced_bootstrap_paths, - default_shared_identity_path, default_shared_runtime_logs_dir, + default_namespaced_bootstrap_paths, default_shared_identity_path, + default_shared_runtime_logs_dir, RadrootsBootstrapPaths, DEFAULT_CONFIG_FILE_NAME, + DEFAULT_SERVICE_IDENTITY_FILE_NAME, DEFAULT_SHARED_IDENTITY_FILE_NAME, }; pub use error::RadrootsRuntimePathsError; pub use migration::{ - RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE, - RadrootsLegacyPathCandidate, RadrootsLegacyPathDetection, RadrootsMigrationReport, - inspect_legacy_paths, + inspect_legacy_paths, RadrootsLegacyPathCandidate, RadrootsLegacyPathDetection, + RadrootsMigrationReport, RADROOTS_MIGRATION_COMPATIBILITY_WINDOW, RADROOTS_MIGRATION_POSTURE, }; pub use namespace::{RadrootsRuntimeNamespace, RadrootsRuntimeNamespaceKind}; pub use platform::{RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPlatform}; pub use roots::{RadrootsPathOverrides, RadrootsPathResolver, RadrootsPaths}; +pub use service::{RadrootsRuntimePathSelection, RadrootsRuntimePathSelectionError}; #[cfg(test)] mod tests { diff --git a/crates/runtime_paths/src/service.rs b/crates/runtime_paths/src/service.rs @@ -0,0 +1,217 @@ +use std::path::PathBuf; + +use thiserror::Error; + +use crate::{ + RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsPaths, + RadrootsRuntimeNamespace, RadrootsRuntimePathsError, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsRuntimePathSelection { + pub profile: RadrootsPathProfile, + pub profile_source: String, + pub repo_local_root: Option<PathBuf>, + pub repo_local_root_source: Option<String>, +} + +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum RadrootsRuntimePathSelectionError { + #[error("{env_var} must be valid utf-8 when set")] + NonUnicodeEnv { env_var: String }, + + #[error( + "{env_var} must be `interactive_user`, `service_host`, or `repo_local`; found `{value}`" + )] + InvalidProfileEnv { env_var: String, value: String }, + + #[error("{repo_local_root_env} must be set when {profile_env}=repo_local")] + MissingRepoLocalRoot { + profile_env: String, + repo_local_root_env: String, + }, + + #[error(transparent)] + Paths(#[from] RadrootsRuntimePathsError), +} + +impl RadrootsRuntimePathSelection { + pub fn caller(profile: RadrootsPathProfile, repo_local_root: Option<PathBuf>) -> Self { + Self { + profile, + profile_source: "caller".to_owned(), + repo_local_root_source: repo_local_root.as_ref().map(|_| "caller".to_owned()), + repo_local_root, + } + } + + pub fn from_env( + profile_env: &'static str, + repo_local_root_env: &'static str, + default_profile: RadrootsPathProfile, + ) -> Result<Self, RadrootsRuntimePathSelectionError> { + let (profile, profile_source) = match std::env::var(profile_env) { + Ok(value) => ( + parse_profile(profile_env, value.as_str())?, + format!("process_env:{profile_env}"), + ), + Err(std::env::VarError::NotPresent) => (default_profile, "default".to_owned()), + Err(std::env::VarError::NotUnicode(_)) => { + return Err(RadrootsRuntimePathSelectionError::NonUnicodeEnv { + env_var: profile_env.to_owned(), + }); + } + }; + let repo_local_root_raw = std::env::var_os(repo_local_root_env); + let repo_local_root = repo_local_root_raw.as_ref().map(PathBuf::from); + Ok(Self { + profile, + profile_source, + repo_local_root, + repo_local_root_source: repo_local_root_raw + .as_ref() + .map(|_| format!("process_env:{repo_local_root_env}")), + }) + } + + pub fn root_source(&self) -> &'static str { + match self.profile { + RadrootsPathProfile::InteractiveUser => "host_defaults", + RadrootsPathProfile::ServiceHost => "service_host_defaults", + RadrootsPathProfile::RepoLocal => "repo_local_root", + RadrootsPathProfile::MobileNative => "mobile_native_defaults", + } + } + + pub fn overrides( + &self, + profile_env: &'static str, + repo_local_root_env: &'static str, + ) -> Result<RadrootsPathOverrides, RadrootsRuntimePathSelectionError> { + match self.profile { + RadrootsPathProfile::RepoLocal => { + let Some(repo_local_root) = self.repo_local_root.as_ref() else { + return Err(RadrootsRuntimePathSelectionError::MissingRepoLocalRoot { + profile_env: profile_env.to_owned(), + repo_local_root_env: repo_local_root_env.to_owned(), + }); + }; + Ok(RadrootsPathOverrides::repo_local(repo_local_root)) + } + _ => Ok(RadrootsPathOverrides::default()), + } + } + + pub fn resolve_service_roots( + &self, + resolver: &RadrootsPathResolver, + service_id: &str, + profile_env: &'static str, + repo_local_root_env: &'static str, + ) -> Result<RadrootsPaths, RadrootsRuntimePathSelectionError> { + let namespace = RadrootsRuntimeNamespace::service(service_id)?; + let overrides = self.overrides(profile_env, repo_local_root_env)?; + let roots = resolver.resolve(self.profile, &overrides)?; + Ok(roots.namespaced(&namespace)) + } +} + +fn parse_profile( + env_var: &'static str, + value: &str, +) -> Result<RadrootsPathProfile, RadrootsRuntimePathSelectionError> { + match value { + "interactive_user" => Ok(RadrootsPathProfile::InteractiveUser), + "service_host" => Ok(RadrootsPathProfile::ServiceHost), + "repo_local" => Ok(RadrootsPathProfile::RepoLocal), + other => Err(RadrootsRuntimePathSelectionError::InvalidProfileEnv { + env_var: env_var.to_owned(), + value: other.to_owned(), + }), + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{ + RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform, + }; + + use super::{RadrootsRuntimePathSelection, RadrootsRuntimePathSelectionError}; + + #[test] + fn caller_selection_preserves_profile_and_sources() { + let selection = + RadrootsRuntimePathSelection::caller(RadrootsPathProfile::InteractiveUser, None); + assert_eq!(selection.profile, RadrootsPathProfile::InteractiveUser); + assert_eq!(selection.profile_source, "caller"); + assert_eq!(selection.repo_local_root, None); + assert_eq!(selection.repo_local_root_source, None); + assert_eq!(selection.root_source(), "host_defaults"); + } + + #[test] + fn caller_selection_marks_repo_local_source() { + let selection = RadrootsRuntimePathSelection::caller( + RadrootsPathProfile::RepoLocal, + Some(PathBuf::from("/repo/.local/radroots")), + ); + + assert_eq!(selection.profile_source, "caller"); + assert_eq!(selection.repo_local_root_source.as_deref(), Some("caller")); + assert_eq!(selection.root_source(), "repo_local_root"); + } + + #[test] + fn resolve_service_roots_uses_repo_local_override() { + let selection = RadrootsRuntimePathSelection::caller( + RadrootsPathProfile::RepoLocal, + Some(PathBuf::from("/repo/.local/radroots")), + ); + let resolver = + RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); + + let roots = selection + .resolve_service_roots(&resolver, "radrootsd", "PROFILE_ENV", "ROOT_ENV") + .expect("service roots"); + + assert_eq!( + roots.config, + PathBuf::from("/repo/.local/radroots/config/services/radrootsd") + ); + assert_eq!( + roots.data, + PathBuf::from("/repo/.local/radroots/data/services/radrootsd") + ); + assert_eq!( + roots.logs, + PathBuf::from("/repo/.local/radroots/logs/services/radrootsd") + ); + assert_eq!( + roots.run, + PathBuf::from("/repo/.local/radroots/run/services/radrootsd") + ); + assert_eq!( + roots.secrets, + PathBuf::from("/repo/.local/radroots/secrets/services/radrootsd") + ); + } + + #[test] + fn overrides_require_repo_local_root_for_repo_local_profile() { + let selection = RadrootsRuntimePathSelection::caller(RadrootsPathProfile::RepoLocal, None); + let err = selection + .overrides("RADROOTS_TEST_PROFILE", "RADROOTS_TEST_ROOT") + .expect_err("repo local root"); + + assert_eq!( + err, + RadrootsRuntimePathSelectionError::MissingRepoLocalRoot { + profile_env: "RADROOTS_TEST_PROFILE".to_owned(), + repo_local_root_env: "RADROOTS_TEST_ROOT".to_owned(), + } + ); + } +}