lib

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

managed.rs (12167B)


      1 use core::fmt;
      2 
      3 use radroots_runtime_distribution::HardenedServiceTarget;
      4 use radroots_runtime_paths::{InstanceId, RadrootsPathProfile, RuntimeContext, ServiceId};
      5 #[cfg(any(target_os = "linux", target_os = "macos"))]
      6 use radroots_service_host::AdminTransportLimits;
      7 
      8 #[cfg(any(target_os = "linux", target_os = "macos"))]
      9 use crate::ManagedRuntimeStatusClient;
     10 use crate::{
     11     ManagedCliCommand, ManagedCliInvocation, ManagementModeContract,
     12     RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError,
     13 };
     14 
     15 /// Validated frozen runtime-management contract.
     16 ///
     17 /// Instance identity, profile, and canonical paths are deliberately absent;
     18 /// those values enter only through a sealed [`RuntimeContext`] when a target is
     19 /// resolved.
     20 ///
     21 /// ```compile_fail
     22 /// use radroots_runtime_manager::ManagedRuntimeContext;
     23 ///
     24 /// let _ = ManagedRuntimeContext { contract: todo!() };
     25 /// ```
     26 #[derive(Clone)]
     27 pub struct ManagedRuntimeContext {
     28     contract: RadrootsRuntimeManagementContract,
     29 }
     30 
     31 impl ManagedRuntimeContext {
     32     pub fn new(
     33         contract: RadrootsRuntimeManagementContract,
     34     ) -> Result<Self, RadrootsRuntimeManagerError> {
     35         crate::validate_hardened_management_contract(&contract)?;
     36         Ok(Self { contract })
     37     }
     38 
     39     #[must_use]
     40     pub fn contract(&self) -> &RadrootsRuntimeManagementContract {
     41         &self.contract
     42     }
     43 }
     44 
     45 impl fmt::Debug for ManagedRuntimeContext {
     46     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     47         formatter
     48             .debug_struct("ManagedRuntimeContext")
     49             .field("schema", &self.contract.schema)
     50             .field("schema_version", &self.contract.schema_version)
     51             .finish_non_exhaustive()
     52     }
     53 }
     54 
     55 /// Sealed management capability for one validated Myc or RHI runtime context.
     56 ///
     57 /// The target owns the sole service-instance identity/profile/path authority.
     58 /// It exposes typed CLI-v1 and status-v1 integration but no filesystem,
     59 /// process, PID, log, credential, or distribution-artifact mutation surface.
     60 ///
     61 /// ```compile_fail
     62 /// use radroots_runtime_manager::ManagedRuntimeTarget;
     63 ///
     64 /// let _ = ManagedRuntimeTarget {
     65 ///     context: todo!(),
     66 ///     service_target: todo!(),
     67 ///     management_mode: String::new(),
     68 ///     mode_contract: todo!(),
     69 /// };
     70 /// ```
     71 #[derive(Clone)]
     72 pub struct ManagedRuntimeTarget {
     73     context: RuntimeContext,
     74     service_target: HardenedServiceTarget,
     75     management_mode: String,
     76     mode_contract: ManagementModeContract,
     77 }
     78 
     79 impl ManagedRuntimeTarget {
     80     #[must_use]
     81     pub fn runtime_context(&self) -> &RuntimeContext {
     82         &self.context
     83     }
     84 
     85     #[must_use]
     86     pub fn service_id(&self) -> &ServiceId {
     87         self.context.service()
     88     }
     89 
     90     #[must_use]
     91     pub fn instance_id(&self) -> &InstanceId {
     92         self.context.instance()
     93     }
     94 
     95     #[must_use]
     96     pub fn profile(&self) -> RadrootsPathProfile {
     97         self.context.profile()
     98     }
     99 
    100     #[must_use]
    101     pub fn service_target(&self) -> &HardenedServiceTarget {
    102         &self.service_target
    103     }
    104 
    105     #[must_use]
    106     pub fn management_mode(&self) -> &str {
    107         &self.management_mode
    108     }
    109 
    110     #[must_use]
    111     pub fn mode_contract(&self) -> &ManagementModeContract {
    112         &self.mode_contract
    113     }
    114 
    115     pub fn cli_invocation(
    116         &self,
    117         command: ManagedCliCommand,
    118     ) -> Result<ManagedCliInvocation, RadrootsRuntimeManagerError> {
    119         ManagedCliInvocation::for_context(&self.context, command)
    120     }
    121 
    122     #[cfg(any(target_os = "linux", target_os = "macos"))]
    123     pub fn status_client(
    124         &self,
    125         limits: AdminTransportLimits,
    126     ) -> Result<ManagedRuntimeStatusClient, RadrootsRuntimeManagerError> {
    127         ManagedRuntimeStatusClient::for_context(&self.context, limits)
    128     }
    129 }
    130 
    131 impl fmt::Debug for ManagedRuntimeTarget {
    132     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    133         formatter
    134             .debug_struct("ManagedRuntimeTarget")
    135             .field("service_id", self.context.service())
    136             .field("instance_id", self.context.instance())
    137             .field("profile", &self.context.profile())
    138             .field("management_mode", &self.management_mode)
    139             .field("paths", &"[redacted]")
    140             .finish_non_exhaustive()
    141     }
    142 }
    143 
    144 pub fn resolve_runtime_target(
    145     context: &ManagedRuntimeContext,
    146     runtime_context: RuntimeContext,
    147 ) -> Result<ManagedRuntimeTarget, RadrootsRuntimeManagerError> {
    148     let service_target = context
    149         .contract
    150         .service_targets
    151         .get(runtime_context.service())
    152         .cloned()
    153         .ok_or(RadrootsRuntimeManagerError::UnsupportedServiceTarget)?;
    154     let management_mode =
    155         active_management_mode_for_profile(&context.contract, runtime_context.profile())?;
    156     let mode_contract = context
    157         .contract
    158         .mode
    159         .get(management_mode)
    160         .cloned()
    161         .ok_or(RadrootsRuntimeManagerError::InvalidContract)?;
    162     Ok(ManagedRuntimeTarget {
    163         context: runtime_context,
    164         service_target,
    165         management_mode: management_mode.to_owned(),
    166         mode_contract,
    167     })
    168 }
    169 
    170 fn active_management_mode_for_profile(
    171     contract: &RadrootsRuntimeManagementContract,
    172     profile: RadrootsPathProfile,
    173 ) -> Result<&str, RadrootsRuntimeManagerError> {
    174     let profile_id = profile.to_string();
    175     contract
    176         .mode
    177         .iter()
    178         .find(|(_, mode)| {
    179             mode.contract_state == "active"
    180                 && mode
    181                     .supported_profiles
    182                     .iter()
    183                     .any(|entry| entry == &profile_id)
    184         })
    185         .map(|(mode_id, _)| mode_id.as_str())
    186         .ok_or(RadrootsRuntimeManagerError::UnsupportedProfile)
    187 }
    188 
    189 #[cfg(test)]
    190 mod tests {
    191     use std::path::PathBuf;
    192 
    193     use radroots_runtime_paths::{
    194         InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver,
    195         RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId,
    196     };
    197     #[cfg(any(target_os = "linux", target_os = "macos"))]
    198     use radroots_service_host::AdminTransportLimits;
    199 
    200     use super::{
    201         ManagedRuntimeContext, active_management_mode_for_profile, resolve_runtime_target,
    202     };
    203     use crate::{
    204         HARDENED_MANAGEMENT_CONTRACT, ManagedCliCommand, RadrootsRuntimeManagerError,
    205         parse_contract_str,
    206     };
    207 
    208     fn management_context() -> ManagedRuntimeContext {
    209         ManagedRuntimeContext::new(
    210             parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract"),
    211         )
    212         .expect("management context")
    213     }
    214 
    215     fn runtime_context(
    216         profile: RadrootsPathProfile,
    217         service: &str,
    218         instance: &str,
    219     ) -> RuntimeContext {
    220         let root = (profile == RadrootsPathProfile::RepoLocal)
    221             .then(|| PathBuf::from("/sensitive/project-root"));
    222         RuntimeContext::resolve(
    223             &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
    224             RuntimeContextBootstrap::new(
    225                 profile,
    226                 root,
    227                 if profile == RadrootsPathProfile::RepoLocal {
    228                     RuntimeContextSource::BootstrapCli
    229                 } else {
    230                     RuntimeContextSource::SafeDefault
    231                 },
    232                 RuntimeContextSource::BootstrapCli,
    233             )
    234             .expect("bootstrap"),
    235             ServiceId::new(service).expect("service"),
    236             InstanceId::new(instance).expect("instance"),
    237         )
    238         .expect("runtime context")
    239     }
    240 
    241     #[test]
    242     fn context_accepts_only_the_exact_contract() {
    243         let context = management_context();
    244         assert_eq!(context.contract().service_targets.len(), 2);
    245 
    246         let mut direct = parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract");
    247         direct.lifecycle.actions.push("start".to_owned());
    248         assert!(matches!(
    249             ManagedRuntimeContext::new(direct),
    250             Err(RadrootsRuntimeManagerError::InvalidContract)
    251         ));
    252     }
    253 
    254     #[test]
    255     fn exact_myc_and_rhi_contexts_resolve_without_identity_duplication() {
    256         let management = management_context();
    257         let myc = resolve_runtime_target(
    258             &management,
    259             runtime_context(RadrootsPathProfile::RepoLocal, "myc", "primary"),
    260         )
    261         .expect("myc target");
    262         let rhi = resolve_runtime_target(
    263             &management,
    264             runtime_context(RadrootsPathProfile::ServiceHost, "rhi", "secondary"),
    265         )
    266         .expect("rhi target");
    267 
    268         assert_eq!(myc.service_id().as_str(), "myc");
    269         assert_eq!(myc.instance_id().as_str(), "primary");
    270         assert_eq!(myc.profile(), RadrootsPathProfile::RepoLocal);
    271         assert_eq!(myc.service_target().service_id(), myc.service_id());
    272         assert_eq!(myc.management_mode(), "interactive_user_managed");
    273         assert!(!myc.mode_contract().service_manager_integration);
    274         let invocation = myc
    275             .cli_invocation(ManagedCliCommand::Doctor)
    276             .expect("Myc doctor invocation");
    277         assert_eq!(invocation.profile(), RadrootsPathProfile::RepoLocal);
    278         assert_eq!(invocation.command(), ManagedCliCommand::Doctor);
    279 
    280         #[cfg(any(target_os = "linux", target_os = "macos"))]
    281         {
    282             let status_client = myc
    283                 .status_client(AdminTransportLimits::DEFAULT)
    284                 .expect("Myc status client");
    285             assert!(!format!("{status_client:?}").contains("sensitive"));
    286         }
    287 
    288         assert_eq!(rhi.service_id().as_str(), "rhi");
    289         assert_eq!(rhi.instance_id().as_str(), "secondary");
    290         assert_eq!(rhi.management_mode(), "service_host_managed");
    291         assert!(rhi.mode_contract().service_manager_integration);
    292         assert_eq!(rhi.runtime_context().service(), rhi.service_id());
    293 
    294         for rendered in [
    295             format!("{management:?}"),
    296             format!("{myc:?}"),
    297             format!("{rhi:?}"),
    298         ] {
    299             assert!(!rendered.contains("sensitive"));
    300             assert!(!rendered.contains("state.sqlite"));
    301             assert!(!rendered.contains("admin.sock"));
    302         }
    303     }
    304 
    305     #[test]
    306     fn unsupported_service_and_profile_fail_without_fallback() {
    307         let management = management_context();
    308         let unsupported = runtime_context(RadrootsPathProfile::ServiceHost, "radrootsd", "default");
    309         assert!(matches!(
    310             resolve_runtime_target(&management, unsupported),
    311             Err(RadrootsRuntimeManagerError::UnsupportedServiceTarget)
    312         ));
    313 
    314         let mobile = RuntimeContextBootstrap::new(
    315             RadrootsPathProfile::MobileNative,
    316             None,
    317             RuntimeContextSource::SafeDefault,
    318             RuntimeContextSource::BootstrapCli,
    319         );
    320         assert!(mobile.is_err());
    321     }
    322 
    323     #[test]
    324     fn inactive_management_mode_never_matches_a_supported_profile() {
    325         let mut contract = parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract");
    326         contract
    327             .mode
    328             .get_mut("interactive_user_managed")
    329             .expect("interactive mode")
    330             .contract_state = "inactive".to_owned();
    331 
    332         assert_eq!(
    333             active_management_mode_for_profile(&contract, RadrootsPathProfile::RepoLocal),
    334             Err(RadrootsRuntimeManagerError::UnsupportedProfile)
    335         );
    336     }
    337 
    338     #[test]
    339     fn production_manager_has_no_direct_io_process_or_artifact_authority() {
    340         let source = include_str!("managed.rs")
    341             .split("\n#[cfg(test)]")
    342             .next()
    343             .expect("production source");
    344         for forbidden in [
    345             "std::fs",
    346             "std::process",
    347             "load_registry",
    348             "save_registry",
    349             "register_instance",
    350             "remove_instance",
    351             "ManagedRuntimeInstancePaths",
    352             "ManagedRuntimeArtifactName",
    353             "inspect_runtime_",
    354             "start_process",
    355             "stop_process",
    356             "extract_binary_archive",
    357         ] {
    358             assert!(
    359                 !source.contains(forbidden),
    360                 "manager retained `{forbidden}`"
    361             );
    362         }
    363     }
    364 }