myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

runtime_context.rs (5109B)


      1 //! Sealed bootstrap binding for one canonical Myc service instance.
      2 
      3 use core::fmt;
      4 use std::{error::Error, path::Path};
      5 
      6 use radroots_runtime_paths::{
      7     RadrootsPathProfile, RadrootsPathResolver, RadrootsServiceInstanceArtifacts, RuntimeContext,
      8     RuntimeContextBootstrap, RuntimeContextSource, ServiceId, default_service_instance_artifacts,
      9 };
     10 
     11 use crate::{MycBootstrapProfileV1, MycCliInvocationV1};
     12 
     13 const MYC_SERVICE_ID: &str = "myc";
     14 
     15 /// Stable source-free classification for Myc runtime-context failures.
     16 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     17 pub enum MycRuntimeContextErrorKind {
     18     InvalidServiceIdentity,
     19     InvalidBootstrapBinding,
     20     PathSelection,
     21 }
     22 
     23 impl MycRuntimeContextErrorKind {
     24     const fn message(self) -> &'static str {
     25         match self {
     26             Self::InvalidServiceIdentity => "Myc service identity is invalid",
     27             Self::InvalidBootstrapBinding => "Myc bootstrap selectors are inconsistent",
     28             Self::PathSelection => "Myc runtime path selection failed",
     29         }
     30     }
     31 }
     32 
     33 /// One redacted Myc runtime-context failure.
     34 #[derive(Clone, Copy, PartialEq, Eq)]
     35 pub struct MycRuntimeContextError {
     36     kind: MycRuntimeContextErrorKind,
     37 }
     38 
     39 impl MycRuntimeContextError {
     40     const fn new(kind: MycRuntimeContextErrorKind) -> Self {
     41         Self { kind }
     42     }
     43 
     44     #[must_use]
     45     pub const fn kind(self) -> MycRuntimeContextErrorKind {
     46         self.kind
     47     }
     48 }
     49 
     50 impl fmt::Debug for MycRuntimeContextError {
     51     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     52         formatter
     53             .debug_struct("MycRuntimeContextError")
     54             .field("kind", &self.kind)
     55             .finish()
     56     }
     57 }
     58 
     59 impl fmt::Display for MycRuntimeContextError {
     60     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     61         formatter.write_str(self.kind.message())
     62     }
     63 }
     64 
     65 impl Error for MycRuntimeContextError {}
     66 
     67 /// Immutable canonical paths and bootstrap selection for one Myc instance.
     68 ///
     69 /// Construction is sealed to the validated CLI invocation and the shared
     70 /// runtime-path resolver. Callers cannot forge an alternate service identity,
     71 /// path set, artifact name, or selected configuration path:
     72 ///
     73 /// ```compile_fail
     74 /// use myc::MycRuntimeContext;
     75 ///
     76 /// let _ = MycRuntimeContext {
     77 ///     context: todo!(),
     78 ///     artifacts: todo!(),
     79 ///     selected_config_path: todo!(),
     80 ///     profile: todo!(),
     81 /// };
     82 /// ```
     83 #[derive(Clone, PartialEq, Eq)]
     84 pub struct MycRuntimeContext {
     85     context: RuntimeContext,
     86     artifacts: RadrootsServiceInstanceArtifacts,
     87     selected_config_path: std::path::PathBuf,
     88     profile: MycBootstrapProfileV1,
     89 }
     90 
     91 impl MycRuntimeContext {
     92     #[must_use]
     93     pub fn context(&self) -> &RuntimeContext {
     94         &self.context
     95     }
     96 
     97     #[must_use]
     98     pub fn artifacts(&self) -> &RadrootsServiceInstanceArtifacts {
     99         &self.artifacts
    100     }
    101 
    102     #[must_use]
    103     pub fn selected_config_path(&self) -> &Path {
    104         &self.selected_config_path
    105     }
    106 
    107     #[must_use]
    108     pub const fn profile(&self) -> MycBootstrapProfileV1 {
    109         self.profile
    110     }
    111 }
    112 
    113 impl fmt::Debug for MycRuntimeContext {
    114     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    115         formatter
    116             .debug_struct("MycRuntimeContext")
    117             .field("profile", &self.profile)
    118             .field("service", &MYC_SERVICE_ID)
    119             .field("instance", &"[redacted]")
    120             .field("paths", &"[redacted]")
    121             .finish()
    122     }
    123 }
    124 
    125 /// Resolves one validated CLI selection into the sole Myc path authority.
    126 pub fn resolve_myc_runtime_context(
    127     resolver: &RadrootsPathResolver,
    128     invocation: &MycCliInvocationV1,
    129 ) -> Result<MycRuntimeContext, MycRuntimeContextError> {
    130     let profile = invocation.profile();
    131     let path_profile = match profile {
    132         MycBootstrapProfileV1::ServiceHost => RadrootsPathProfile::ServiceHost,
    133         MycBootstrapProfileV1::Interactive => RadrootsPathProfile::InteractiveUser,
    134         MycBootstrapProfileV1::RepoLocal => RadrootsPathProfile::RepoLocal,
    135     };
    136     let bootstrap = RuntimeContextBootstrap::new(
    137         path_profile,
    138         invocation.repo_local_root().map(Path::to_path_buf),
    139         RuntimeContextSource::BootstrapCli,
    140         RuntimeContextSource::BootstrapCli,
    141     )
    142     .map_err(|_| {
    143         MycRuntimeContextError::new(MycRuntimeContextErrorKind::InvalidBootstrapBinding)
    144     })?;
    145     let service = ServiceId::new(MYC_SERVICE_ID).map_err(|_| {
    146         MycRuntimeContextError::new(MycRuntimeContextErrorKind::InvalidServiceIdentity)
    147     })?;
    148     let context =
    149         RuntimeContext::resolve(resolver, bootstrap, service, invocation.instance().clone())
    150             .map_err(|_| MycRuntimeContextError::new(MycRuntimeContextErrorKind::PathSelection))?;
    151     let artifacts = default_service_instance_artifacts(context.paths());
    152     let selected_config_path = invocation
    153         .config_path()
    154         .map(Path::to_path_buf)
    155         .unwrap_or_else(|| artifacts.config().to_path_buf());
    156 
    157     Ok(MycRuntimeContext {
    158         context,
    159         artifacts,
    160         selected_config_path,
    161         profile,
    162     })
    163 }