managed.rs (51787B)
1 use std::path::PathBuf; 2 3 use radroots_runtime_paths::{ 4 RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimePathSelection, 5 }; 6 7 use crate::{ 8 BootstrapRuntimeContract, ManagedRuntimeHealthState, ManagedRuntimeInstallState, 9 ManagedRuntimeInstancePaths, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry, 10 ManagementModeContract, RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError, 11 load_registry, resolve_instance_paths, resolve_shared_paths, 12 }; 13 14 #[derive(Debug, Clone)] 15 pub struct ManagedRuntimeContext { 16 pub contract: RadrootsRuntimeManagementContract, 17 pub shared_paths: crate::ManagedRuntimeSharedPaths, 18 pub registry: ManagedRuntimeInstanceRegistry, 19 } 20 21 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 22 pub enum ManagedRuntimeGroup { 23 ActiveManagedTarget, 24 DefinedManagedTarget, 25 BootstrapOnly, 26 Unknown, 27 } 28 29 impl ManagedRuntimeGroup { 30 pub fn as_str(self) -> &'static str { 31 match self { 32 Self::ActiveManagedTarget => "active_managed_target", 33 Self::DefinedManagedTarget => "defined_managed_target", 34 Self::BootstrapOnly => "bootstrap_only", 35 Self::Unknown => "unknown", 36 } 37 } 38 39 pub fn posture(self) -> &'static str { 40 match self { 41 Self::ActiveManagedTarget => "active_managed_target", 42 Self::DefinedManagedTarget => "defined_future_target", 43 Self::BootstrapOnly => "bootstrap_only_direct_binding", 44 Self::Unknown => "unknown_runtime", 45 } 46 } 47 } 48 49 #[derive(Debug, Clone)] 50 pub struct ManagedRuntimeTarget { 51 pub runtime_id: String, 52 pub instance_id: String, 53 pub instance_source: String, 54 pub runtime_group: ManagedRuntimeGroup, 55 pub management_mode: Option<String>, 56 pub mode_contract: Option<ManagementModeContract>, 57 pub bootstrap: Option<BootstrapRuntimeContract>, 58 pub instance_record: Option<ManagedRuntimeInstanceRecord>, 59 pub predicted_paths: Option<ManagedRuntimeInstancePaths>, 60 pub registry_path: PathBuf, 61 } 62 63 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 64 pub enum ManagedRuntimeInspectionAvailability { 65 Success, 66 Unconfigured, 67 Unsupported, 68 } 69 70 #[derive(Debug, Clone, PartialEq, Eq)] 71 pub struct ManagedRuntimeInspection<T> { 72 pub availability: ManagedRuntimeInspectionAvailability, 73 pub view: T, 74 } 75 76 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 77 pub enum ManagedRuntimeLifecycleAction { 78 Install, 79 Uninstall, 80 Start, 81 Stop, 82 Restart, 83 ConfigSet, 84 } 85 86 impl ManagedRuntimeLifecycleAction { 87 pub fn as_str(self) -> &'static str { 88 match self { 89 Self::Install => "install", 90 Self::Uninstall => "uninstall", 91 Self::Start => "start", 92 Self::Stop => "stop", 93 Self::Restart => "restart", 94 Self::ConfigSet => "config_set", 95 } 96 } 97 } 98 99 #[derive(Debug, Clone, PartialEq, Eq)] 100 pub struct ManagedRuntimeStatusInspection { 101 pub runtime_id: String, 102 pub instance_id: String, 103 pub instance_source: String, 104 pub runtime_group: String, 105 pub management_posture: String, 106 pub state: String, 107 pub source: String, 108 pub detail: String, 109 pub management_mode: Option<String>, 110 pub service_manager_integration: Option<bool>, 111 pub uses_absolute_binary_paths: Option<bool>, 112 pub preferred_cli_binding: Option<bool>, 113 pub install_state: String, 114 pub health_state: String, 115 pub health_source: String, 116 pub registry_path: PathBuf, 117 pub lifecycle_actions: Vec<String>, 118 pub instance_paths: Option<ManagedRuntimeInstancePaths>, 119 pub instance_record: Option<ManagedRuntimeInstanceRecord>, 120 } 121 122 #[derive(Debug, Clone, PartialEq, Eq)] 123 pub struct ManagedRuntimeLogsInspection { 124 pub runtime_id: String, 125 pub instance_id: String, 126 pub instance_source: String, 127 pub runtime_group: String, 128 pub state: String, 129 pub source: String, 130 pub detail: String, 131 pub stdout_log_path: Option<PathBuf>, 132 pub stderr_log_path: Option<PathBuf>, 133 pub stdout_log_present: bool, 134 pub stderr_log_present: bool, 135 } 136 137 #[derive(Debug, Clone, PartialEq, Eq)] 138 pub struct ManagedRuntimeConfigInspection { 139 pub runtime_id: String, 140 pub instance_id: String, 141 pub instance_source: String, 142 pub runtime_group: String, 143 pub state: String, 144 pub source: String, 145 pub detail: String, 146 pub config_format: Option<String>, 147 pub config_path: Option<PathBuf>, 148 pub config_present: bool, 149 pub requires_bootstrap_secret: Option<bool>, 150 pub requires_config_bootstrap: Option<bool>, 151 pub requires_signer_provider: Option<bool>, 152 } 153 154 #[derive(Debug, Clone, PartialEq, Eq)] 155 pub struct ManagedRuntimeActionInspection { 156 pub action: String, 157 pub runtime_id: String, 158 pub instance_id: String, 159 pub instance_source: String, 160 pub runtime_group: String, 161 pub state: String, 162 pub source: String, 163 pub detail: String, 164 pub mutates_bindings: bool, 165 pub next_step: Option<String>, 166 } 167 168 pub fn load_management_context( 169 contract: RadrootsRuntimeManagementContract, 170 resolver: &RadrootsPathResolver, 171 profile: RadrootsPathProfile, 172 overrides: &RadrootsPathOverrides, 173 ) -> Result<ManagedRuntimeContext, RadrootsRuntimeManagerError> { 174 let mode_id = active_management_mode_for_profile(&contract, profile)?; 175 let shared_paths = resolve_shared_paths(&contract, resolver, profile, overrides, mode_id)?; 176 let registry = load_registry(&shared_paths.instance_registry_path)?; 177 Ok(ManagedRuntimeContext { 178 contract, 179 shared_paths, 180 registry, 181 }) 182 } 183 184 pub fn load_management_context_with_selection( 185 contract: RadrootsRuntimeManagementContract, 186 resolver: &RadrootsPathResolver, 187 selection: &RadrootsRuntimePathSelection, 188 ) -> Result<ManagedRuntimeContext, RadrootsRuntimeManagerError> { 189 let overrides = selection.caller_overrides()?; 190 load_management_context(contract, resolver, selection.profile, &overrides) 191 } 192 193 pub fn active_management_mode_for_profile( 194 contract: &RadrootsRuntimeManagementContract, 195 profile: RadrootsPathProfile, 196 ) -> Result<&str, RadrootsRuntimeManagerError> { 197 let profile_id = profile.to_string(); 198 contract 199 .mode 200 .iter() 201 .find(|(_, mode)| { 202 mode.contract_state == "active" 203 && mode 204 .supported_profiles 205 .iter() 206 .any(|entry| entry == &profile_id) 207 }) 208 .map(|(mode_id, _)| mode_id.as_str()) 209 .ok_or_else(|| RadrootsRuntimeManagerError::UnsupportedProfile { 210 mode_id: "active".to_owned(), 211 profile: profile_id, 212 }) 213 } 214 215 pub fn resolve_runtime_target( 216 context: &ManagedRuntimeContext, 217 runtime_id: &str, 218 requested_instance_id: Option<&str>, 219 ) -> ManagedRuntimeTarget { 220 let runtime_group = runtime_group(&context.contract, runtime_id); 221 let bootstrap = context.contract.bootstrap.get(runtime_id).cloned(); 222 let instance_id = requested_instance_id 223 .map(ToOwned::to_owned) 224 .or_else(|| { 225 bootstrap 226 .as_ref() 227 .map(|entry| entry.default_instance_id.clone()) 228 }) 229 .unwrap_or_else(|| "default".to_owned()); 230 let instance_source = if requested_instance_id.is_some() { 231 "command_arg".to_owned() 232 } else if bootstrap.is_some() { 233 "bootstrap_default".to_owned() 234 } else { 235 "implicit_default".to_owned() 236 }; 237 let management_mode = bootstrap 238 .as_ref() 239 .map(|entry| entry.management_mode.clone()); 240 let mode_contract = management_mode 241 .as_ref() 242 .and_then(|mode_id| context.contract.mode.get(mode_id).cloned()); 243 let instance_record = context 244 .registry 245 .instances 246 .iter() 247 .find(|record| record.runtime_id == runtime_id && record.instance_id == instance_id) 248 .cloned(); 249 let predicted_paths = if runtime_group == ManagedRuntimeGroup::ActiveManagedTarget { 250 Some(resolve_instance_paths( 251 &context.shared_paths, 252 runtime_id, 253 instance_id.as_str(), 254 )) 255 } else { 256 None 257 }; 258 259 ManagedRuntimeTarget { 260 runtime_id: runtime_id.to_owned(), 261 instance_id, 262 instance_source, 263 runtime_group, 264 management_mode, 265 mode_contract, 266 bootstrap, 267 instance_record, 268 predicted_paths, 269 registry_path: context.shared_paths.instance_registry_path.clone(), 270 } 271 } 272 273 pub fn inspect_runtime_status( 274 target: &ManagedRuntimeTarget, 275 lifecycle_actions: &[String], 276 ) -> ManagedRuntimeInspection<ManagedRuntimeStatusInspection> { 277 let availability = if target.runtime_group == ManagedRuntimeGroup::Unknown { 278 ManagedRuntimeInspectionAvailability::Unconfigured 279 } else { 280 ManagedRuntimeInspectionAvailability::Success 281 }; 282 283 ManagedRuntimeInspection { 284 availability, 285 view: ManagedRuntimeStatusInspection { 286 runtime_id: target.runtime_id.clone(), 287 instance_id: target.instance_id.clone(), 288 instance_source: target.instance_source.clone(), 289 runtime_group: target.runtime_group.as_str().to_owned(), 290 management_posture: target.runtime_group.posture().to_owned(), 291 state: status_state(target).to_owned(), 292 source: "runtime management contract + shared instance registry".to_owned(), 293 detail: status_detail(target), 294 management_mode: target.management_mode.clone(), 295 service_manager_integration: target 296 .mode_contract 297 .as_ref() 298 .map(|mode| mode.service_manager_integration), 299 uses_absolute_binary_paths: target 300 .mode_contract 301 .as_ref() 302 .map(|mode| mode.uses_absolute_binary_paths), 303 preferred_cli_binding: target 304 .bootstrap 305 .as_ref() 306 .map(|entry| entry.preferred_cli_binding), 307 install_state: target 308 .instance_record 309 .as_ref() 310 .map(|record| install_state_label(record.install_state)) 311 .unwrap_or_else(|| install_state_label(ManagedRuntimeInstallState::NotInstalled)) 312 .to_owned(), 313 health_state: infer_health_state(target).0.to_owned(), 314 health_source: infer_health_state(target).1.to_owned(), 315 registry_path: target.registry_path.clone(), 316 lifecycle_actions: if target.runtime_group == ManagedRuntimeGroup::ActiveManagedTarget { 317 lifecycle_actions.to_vec() 318 } else { 319 Vec::new() 320 }, 321 instance_paths: target.predicted_paths.clone(), 322 instance_record: target.instance_record.clone(), 323 }, 324 } 325 } 326 327 pub fn inspect_runtime_logs( 328 target: &ManagedRuntimeTarget, 329 ) -> ManagedRuntimeInspection<ManagedRuntimeLogsInspection> { 330 let stdout_log_path = target 331 .predicted_paths 332 .as_ref() 333 .map(|paths| paths.stdout_log_path.clone()); 334 let stderr_log_path = target 335 .predicted_paths 336 .as_ref() 337 .map(|paths| paths.stderr_log_path.clone()); 338 let availability = match target.runtime_group { 339 ManagedRuntimeGroup::Unknown => ManagedRuntimeInspectionAvailability::Unconfigured, 340 ManagedRuntimeGroup::ActiveManagedTarget => ManagedRuntimeInspectionAvailability::Success, 341 ManagedRuntimeGroup::DefinedManagedTarget | ManagedRuntimeGroup::BootstrapOnly => { 342 if target.instance_record.is_some() { 343 ManagedRuntimeInspectionAvailability::Success 344 } else { 345 ManagedRuntimeInspectionAvailability::Unsupported 346 } 347 } 348 }; 349 let detail = match target.runtime_group { 350 ManagedRuntimeGroup::ActiveManagedTarget => { 351 "runtime logs report the managed stdout/stderr locations for the active managed instance" 352 .to_owned() 353 } 354 ManagedRuntimeGroup::DefinedManagedTarget => format!( 355 "runtime `{}` is only a defined future managed target; no active generic logs surface exists without a registered instance", 356 target.runtime_id 357 ), 358 ManagedRuntimeGroup::BootstrapOnly => format!( 359 "runtime `{}` remains bootstrap_only and direct-bindable in this wave; generic managed logs are not admitted", 360 target.runtime_id 361 ), 362 ManagedRuntimeGroup::Unknown => unknown_runtime_detail(target), 363 }; 364 365 ManagedRuntimeInspection { 366 availability, 367 view: ManagedRuntimeLogsInspection { 368 runtime_id: target.runtime_id.clone(), 369 instance_id: target.instance_id.clone(), 370 instance_source: target.instance_source.clone(), 371 runtime_group: target.runtime_group.as_str().to_owned(), 372 state: match availability { 373 ManagedRuntimeInspectionAvailability::Success => "ready".to_owned(), 374 ManagedRuntimeInspectionAvailability::Unconfigured => "unknown_runtime".to_owned(), 375 ManagedRuntimeInspectionAvailability::Unsupported => "unsupported".to_owned(), 376 }, 377 source: "runtime management contract + shared instance registry".to_owned(), 378 detail, 379 stdout_log_path: stdout_log_path.clone(), 380 stderr_log_path: stderr_log_path.clone(), 381 stdout_log_present: path_present(stdout_log_path.as_ref()).unwrap_or_else(|| { 382 target 383 .instance_record 384 .as_ref() 385 .is_some_and(|record| record.logs_path.join("stdout.log").exists()) 386 }), 387 stderr_log_present: path_present(stderr_log_path.as_ref()).unwrap_or_else(|| { 388 target 389 .instance_record 390 .as_ref() 391 .is_some_and(|record| record.logs_path.join("stderr.log").exists()) 392 }), 393 }, 394 } 395 } 396 397 pub fn inspect_runtime_config( 398 target: &ManagedRuntimeTarget, 399 ) -> ManagedRuntimeInspection<ManagedRuntimeConfigInspection> { 400 let availability = match target.runtime_group { 401 ManagedRuntimeGroup::Unknown => ManagedRuntimeInspectionAvailability::Unconfigured, 402 ManagedRuntimeGroup::ActiveManagedTarget => ManagedRuntimeInspectionAvailability::Success, 403 ManagedRuntimeGroup::DefinedManagedTarget | ManagedRuntimeGroup::BootstrapOnly => { 404 if target.instance_record.is_some() { 405 ManagedRuntimeInspectionAvailability::Success 406 } else { 407 ManagedRuntimeInspectionAvailability::Unsupported 408 } 409 } 410 }; 411 let config_path = target 412 .instance_record 413 .as_ref() 414 .map(|record| record.config_path.clone()); 415 let detail = match target.runtime_group { 416 ManagedRuntimeGroup::ActiveManagedTarget => { 417 if config_path.is_some() { 418 "runtime config show reports the managed config location without mutating bindings" 419 .to_owned() 420 } else { 421 format!( 422 "managed runtime `{}` has no registered instance config yet", 423 target.runtime_id 424 ) 425 } 426 } 427 ManagedRuntimeGroup::DefinedManagedTarget => format!( 428 "runtime `{}` is only a defined future managed target; generic config surfaces are not admitted without a registered instance", 429 target.runtime_id 430 ), 431 ManagedRuntimeGroup::BootstrapOnly => format!( 432 "runtime `{}` remains bootstrap_only and direct-bindable in this wave; generic managed config is not admitted", 433 target.runtime_id 434 ), 435 ManagedRuntimeGroup::Unknown => unknown_runtime_detail(target), 436 }; 437 438 ManagedRuntimeInspection { 439 availability, 440 view: ManagedRuntimeConfigInspection { 441 runtime_id: target.runtime_id.clone(), 442 instance_id: target.instance_id.clone(), 443 instance_source: target.instance_source.clone(), 444 runtime_group: target.runtime_group.as_str().to_owned(), 445 state: match availability { 446 ManagedRuntimeInspectionAvailability::Success => { 447 if config_path.is_some() { 448 "ready".to_owned() 449 } else { 450 "not_installed".to_owned() 451 } 452 } 453 ManagedRuntimeInspectionAvailability::Unconfigured => "unknown_runtime".to_owned(), 454 ManagedRuntimeInspectionAvailability::Unsupported => "unsupported".to_owned(), 455 }, 456 source: "runtime management contract + shared instance registry".to_owned(), 457 detail, 458 config_format: target 459 .bootstrap 460 .as_ref() 461 .map(|entry| entry.config_format.clone()), 462 config_path: config_path.clone(), 463 config_present: config_path.as_ref().is_some_and(|path| path.exists()), 464 requires_bootstrap_secret: target 465 .bootstrap 466 .as_ref() 467 .map(|entry| entry.requires_bootstrap_secret), 468 requires_config_bootstrap: target 469 .bootstrap 470 .as_ref() 471 .map(|entry| entry.requires_config_bootstrap), 472 requires_signer_provider: target 473 .bootstrap 474 .as_ref() 475 .map(|entry| entry.requires_signer_provider), 476 }, 477 } 478 } 479 480 pub fn inspect_runtime_action( 481 target: &ManagedRuntimeTarget, 482 action: ManagedRuntimeLifecycleAction, 483 detail_override: Option<String>, 484 ) -> ManagedRuntimeInspection<ManagedRuntimeActionInspection> { 485 let (availability, state, detail, next_step) = match target.runtime_group { 486 ManagedRuntimeGroup::ActiveManagedTarget => ( 487 ManagedRuntimeInspectionAvailability::Unsupported, 488 "deferred", 489 detail_override.unwrap_or_else(|| { 490 format!( 491 "runtime {} `{}` is not supported for this managed target", 492 action.as_str().replace('_', " "), 493 target.runtime_id 494 ) 495 }), 496 None, 497 ), 498 ManagedRuntimeGroup::DefinedManagedTarget => ( 499 ManagedRuntimeInspectionAvailability::Unsupported, 500 "unsupported", 501 detail_override.unwrap_or_else(|| { 502 format!( 503 "runtime `{}` is only a defined future managed target; `{}` is not admitted in the current wave", 504 target.runtime_id, 505 action.as_str().replace('_', " ") 506 ) 507 }), 508 None, 509 ), 510 ManagedRuntimeGroup::BootstrapOnly => ( 511 ManagedRuntimeInspectionAvailability::Unsupported, 512 "unsupported", 513 detail_override.unwrap_or_else(|| { 514 format!( 515 "runtime `{}` remains bootstrap_only and direct-bindable in this wave; generic managed `{}` is not admitted", 516 target.runtime_id, 517 action.as_str().replace('_', " ") 518 ) 519 }), 520 None, 521 ), 522 ManagedRuntimeGroup::Unknown => ( 523 ManagedRuntimeInspectionAvailability::Unconfigured, 524 "unknown_runtime", 525 detail_override.unwrap_or_else(|| unknown_runtime_detail(target)), 526 None, 527 ), 528 }; 529 530 ManagedRuntimeInspection { 531 availability, 532 view: ManagedRuntimeActionInspection { 533 action: action.as_str().to_owned(), 534 runtime_id: target.runtime_id.clone(), 535 instance_id: target.instance_id.clone(), 536 instance_source: target.instance_source.clone(), 537 runtime_group: target.runtime_group.as_str().to_owned(), 538 state: state.to_owned(), 539 source: "generic runtime-management command family".to_owned(), 540 detail, 541 mutates_bindings: false, 542 next_step, 543 }, 544 } 545 } 546 547 fn status_state(target: &ManagedRuntimeTarget) -> &'static str { 548 match target.runtime_group { 549 ManagedRuntimeGroup::ActiveManagedTarget => match target.instance_record.as_ref() { 550 Some(record) => install_state_label(record.install_state), 551 None => "not_installed", 552 }, 553 ManagedRuntimeGroup::DefinedManagedTarget => "defined_not_active", 554 ManagedRuntimeGroup::BootstrapOnly => "bootstrap_only", 555 ManagedRuntimeGroup::Unknown => "unknown_runtime", 556 } 557 } 558 559 fn status_detail(target: &ManagedRuntimeTarget) -> String { 560 match target.runtime_group { 561 ManagedRuntimeGroup::ActiveManagedTarget => match &target.instance_record { 562 Some(record) => format!( 563 "managed runtime `{}` instance `{}` is registered with config at {}", 564 target.runtime_id, 565 target.instance_id, 566 record.config_path.display() 567 ), 568 None => format!( 569 "managed runtime `{}` has no registered instance `{}` in {}", 570 target.runtime_id, 571 target.instance_id, 572 target.registry_path.display() 573 ), 574 }, 575 ManagedRuntimeGroup::DefinedManagedTarget => format!( 576 "runtime `{}` is defined in the management contract but not yet admitted as an active managed target", 577 target.runtime_id 578 ), 579 ManagedRuntimeGroup::BootstrapOnly => format!( 580 "runtime `{}` is bootstrap_only in the management contract and remains direct-bindable outside managed lifecycle in this wave", 581 target.runtime_id 582 ), 583 ManagedRuntimeGroup::Unknown => unknown_runtime_detail(target), 584 } 585 } 586 587 fn unknown_runtime_detail(target: &ManagedRuntimeTarget) -> String { 588 format!( 589 "runtime `{}` is not present in the current runtime-management contract", 590 target.runtime_id 591 ) 592 } 593 594 fn infer_health_state(target: &ManagedRuntimeTarget) -> (&'static str, &'static str) { 595 let Some(record) = &target.instance_record else { 596 return ( 597 health_state_label(ManagedRuntimeHealthState::NotInstalled), 598 "registry_absent", 599 ); 600 }; 601 if record.install_state == ManagedRuntimeInstallState::Failed { 602 return ( 603 health_state_label(ManagedRuntimeHealthState::Failed), 604 "registry_install_state", 605 ); 606 } 607 608 if let Some(paths) = target.predicted_paths.as_ref() { 609 if crate::process_running(paths).unwrap_or(false) { 610 return ( 611 health_state_label(ManagedRuntimeHealthState::Running), 612 "process_probe", 613 ); 614 } 615 } else if record.run_path.join("runtime.pid").exists() { 616 return ( 617 health_state_label(ManagedRuntimeHealthState::Running), 618 "pid_file_presence", 619 ); 620 } 621 622 if record.install_state == ManagedRuntimeInstallState::NotInstalled { 623 ( 624 health_state_label(ManagedRuntimeHealthState::NotInstalled), 625 "registry_install_state", 626 ) 627 } else { 628 ( 629 health_state_label(ManagedRuntimeHealthState::Stopped), 630 "pid_file_absent", 631 ) 632 } 633 } 634 635 fn install_state_label(state: ManagedRuntimeInstallState) -> &'static str { 636 match state { 637 ManagedRuntimeInstallState::NotInstalled => "not_installed", 638 ManagedRuntimeInstallState::Installed => "installed", 639 ManagedRuntimeInstallState::Configured => "configured", 640 ManagedRuntimeInstallState::Failed => "failed", 641 } 642 } 643 644 fn health_state_label(state: ManagedRuntimeHealthState) -> &'static str { 645 match state { 646 ManagedRuntimeHealthState::NotInstalled => "not_installed", 647 ManagedRuntimeHealthState::Stopped => "stopped", 648 ManagedRuntimeHealthState::Starting => "starting", 649 ManagedRuntimeHealthState::Running => "running", 650 ManagedRuntimeHealthState::Degraded => "degraded", 651 ManagedRuntimeHealthState::Failed => "failed", 652 } 653 } 654 655 fn path_present(path: Option<&PathBuf>) -> Option<bool> { 656 path.map(|value| value.exists()) 657 } 658 659 pub fn runtime_group( 660 contract: &RadrootsRuntimeManagementContract, 661 runtime_id: &str, 662 ) -> ManagedRuntimeGroup { 663 if contract 664 .managed_runtime_targets 665 .active 666 .iter() 667 .any(|entry| entry == runtime_id) 668 { 669 ManagedRuntimeGroup::ActiveManagedTarget 670 } else if contract 671 .managed_runtime_targets 672 .defined 673 .iter() 674 .any(|entry| entry == runtime_id) 675 { 676 ManagedRuntimeGroup::DefinedManagedTarget 677 } else if contract 678 .managed_runtime_targets 679 .bootstrap_only 680 .iter() 681 .any(|entry| entry == runtime_id) 682 { 683 ManagedRuntimeGroup::BootstrapOnly 684 } else { 685 ManagedRuntimeGroup::Unknown 686 } 687 } 688 689 #[cfg(test)] 690 mod tests { 691 use std::{ 692 fs, 693 path::{Path, PathBuf}, 694 }; 695 696 use radroots_runtime_paths::{ 697 RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, 698 RadrootsPlatform, RadrootsRuntimePathSelection, 699 }; 700 use tempfile::tempdir; 701 702 use super::{ 703 ManagedRuntimeContext, ManagedRuntimeGroup, ManagedRuntimeInspectionAvailability, 704 ManagedRuntimeLifecycleAction, active_management_mode_for_profile, health_state_label, 705 inspect_runtime_action, inspect_runtime_config, inspect_runtime_logs, 706 inspect_runtime_status, load_management_context, load_management_context_with_selection, 707 resolve_runtime_target, runtime_group, 708 }; 709 use crate::{ 710 ManagedRuntimeHealthState, ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, 711 parse_contract_str, 712 }; 713 714 const CONTRACT: &str = r#" 715 schema = "radroots-runtime-management" 716 schema_version = 1 717 owner_doc = "docs/execution/rcl/radroots-modular-runtime-management-bootstrap-rcl.md" 718 runtime_registry = "registry.toml" 719 distribution_contract = "distribution.toml" 720 capabilities_contract = "capabilities.toml" 721 722 [defaults] 723 instance_cardinality = "single_default_instance" 724 managed_runtime_lookup = "shared_instance_registry" 725 explicit_runtime_endpoint_overrides_precede_managed_instance_binding = true 726 global_path_mutation_forbidden = true 727 728 [management_clients] 729 active = ["cli"] 730 defined = [] 731 732 [managed_runtime_targets] 733 active = ["radrootsd"] 734 defined = ["myc"] 735 bootstrap_only = ["hyf"] 736 737 [lifecycle] 738 actions = ["install", "start"] 739 destructive_actions = [] 740 health_states = ["not_installed", "running"] 741 742 [mode.interactive_user_managed] 743 contract_state = "active" 744 platforms = ["linux"] 745 supported_profiles = ["interactive_user", "repo_local"] 746 service_manager_integration = false 747 uses_absolute_binary_paths = true 748 default_instance_cardinality = "single_default_instance" 749 750 [mode.service_host_managed] 751 contract_state = "defined" 752 platforms = ["linux"] 753 supported_profiles = ["service_host"] 754 service_manager_integration = true 755 uses_absolute_binary_paths = true 756 default_instance_cardinality = "single_default_instance" 757 758 [paths.interactive_user_managed] 759 shared_namespace = "shared/runtime-manager" 760 instance_registry_root_class = "config" 761 instance_registry_rel = "shared/runtime-manager/instances.toml" 762 artifact_cache_root_class = "cache" 763 artifact_cache_rel = "shared/runtime-manager/artifacts" 764 install_root_class = "data" 765 install_root_rel = "shared/runtime-manager/installs" 766 state_root_class = "data" 767 state_root_rel = "shared/runtime-manager/state" 768 logs_root_class = "logs" 769 logs_root_rel = "shared/runtime-manager" 770 run_root_class = "run" 771 run_root_rel = "shared/runtime-manager" 772 secrets_root_class = "secrets" 773 secrets_namespace_rel = "shared/runtime-manager" 774 775 [instance_metadata] 776 required_fields = ["runtime_id"] 777 optional_fields = ["notes"] 778 779 [bootstrap.radrootsd] 780 runtime_id = "radrootsd" 781 management_mode = "interactive_user_managed" 782 default_instance_id = "local" 783 install_strategy = "archive_unpack" 784 config_format = "toml" 785 requires_bootstrap_secret = true 786 requires_config_bootstrap = true 787 requires_signer_provider = false 788 health_surface = "jsonrpc_status" 789 preferred_cli_binding = true 790 "#; 791 792 fn resolver_for_home(home_dir: PathBuf) -> RadrootsPathResolver { 793 RadrootsPathResolver::new( 794 RadrootsPlatform::Linux, 795 RadrootsHostEnvironment { 796 home_dir: Some(home_dir), 797 ..RadrootsHostEnvironment::default() 798 }, 799 ) 800 } 801 802 fn repo_local_context(root: &Path) -> ManagedRuntimeContext { 803 let contract = parse_contract_str(CONTRACT).expect("contract"); 804 let resolver = 805 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 806 load_management_context( 807 contract, 808 &resolver, 809 RadrootsPathProfile::RepoLocal, 810 &RadrootsPathOverrides::repo_local(root), 811 ) 812 .expect("context") 813 } 814 815 fn sample_record( 816 runtime_id: &str, 817 instance_id: &str, 818 install_state: ManagedRuntimeInstallState, 819 root: &Path, 820 ) -> ManagedRuntimeInstanceRecord { 821 let instance_root = root.join(runtime_id).join(instance_id); 822 ManagedRuntimeInstanceRecord { 823 runtime_id: runtime_id.to_owned(), 824 instance_id: instance_id.to_owned(), 825 management_mode: "interactive_user_managed".to_owned(), 826 install_state, 827 binary_path: instance_root.join("bin/runtime"), 828 config_path: instance_root.join("config/runtime.toml"), 829 logs_path: instance_root.join("logs"), 830 run_path: instance_root.join("run"), 831 installed_version: "0.1.0-alpha.2".to_owned(), 832 health_endpoint: Some("jsonrpc_status".to_owned()), 833 secret_material_ref: None, 834 last_started_at: None, 835 last_stopped_at: None, 836 notes: Some("managed test record".to_owned()), 837 } 838 } 839 840 #[test] 841 fn active_management_mode_matches_supported_profile() { 842 let contract = parse_contract_str(CONTRACT).expect("contract"); 843 let mode_id = 844 active_management_mode_for_profile(&contract, RadrootsPathProfile::InteractiveUser) 845 .expect("mode"); 846 assert_eq!(mode_id, "interactive_user_managed"); 847 } 848 849 #[test] 850 fn active_management_mode_rejects_profiles_without_active_mode() { 851 let contract = parse_contract_str(CONTRACT).expect("contract"); 852 let err = active_management_mode_for_profile(&contract, RadrootsPathProfile::ServiceHost) 853 .expect_err("service host mode is defined but inactive"); 854 855 assert!(err.to_string().contains("service_host")); 856 } 857 858 #[test] 859 fn management_context_reports_selection_and_context_errors() { 860 let dir = tempdir().expect("tempdir"); 861 let contract = parse_contract_str(CONTRACT).expect("contract"); 862 let resolver = 863 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 864 let selection = RadrootsRuntimePathSelection::caller(RadrootsPathProfile::RepoLocal, None); 865 let err = load_management_context_with_selection(contract.clone(), &resolver, &selection) 866 .expect_err("repo local selection without root should fail"); 867 assert!(err.to_string().contains("repo_local")); 868 869 let err = load_management_context( 870 contract.clone(), 871 &resolver, 872 RadrootsPathProfile::ServiceHost, 873 &RadrootsPathOverrides::default(), 874 ) 875 .expect_err("service host mode is inactive"); 876 assert!(err.to_string().contains("service_host")); 877 878 let root = dir.path().join("runtime-root"); 879 let overrides = RadrootsPathOverrides::repo_local(&root); 880 fs::create_dir_all(root.join("config/shared/runtime-manager")).expect("registry parent"); 881 fs::create_dir(root.join("config/shared/runtime-manager/instances.toml")) 882 .expect("registry directory"); 883 let err = load_management_context( 884 contract, 885 &resolver, 886 RadrootsPathProfile::RepoLocal, 887 &overrides, 888 ) 889 .expect_err("directory registry path should fail"); 890 assert!(err.to_string().contains("read runtime instance registry")); 891 } 892 893 #[test] 894 fn resolve_runtime_target_uses_bootstrap_default_instance_id() { 895 let contract = parse_contract_str(CONTRACT).expect("contract"); 896 let resolver = resolver_for_home(PathBuf::from("/home/treesap")); 897 let mut context = load_management_context( 898 contract, 899 &resolver, 900 RadrootsPathProfile::InteractiveUser, 901 &RadrootsPathOverrides::default(), 902 ) 903 .expect("context"); 904 context 905 .registry 906 .instances 907 .push(ManagedRuntimeInstanceRecord { 908 runtime_id: "radrootsd".to_owned(), 909 instance_id: "local".to_owned(), 910 management_mode: "interactive_user_managed".to_owned(), 911 install_state: ManagedRuntimeInstallState::Configured, 912 binary_path: PathBuf::from("/tmp/bin/radrootsd"), 913 config_path: PathBuf::from("/tmp/config.toml"), 914 logs_path: PathBuf::from("/tmp/logs"), 915 run_path: PathBuf::from("/tmp/run"), 916 installed_version: "0.1.0-alpha.2".to_owned(), 917 health_endpoint: None, 918 secret_material_ref: None, 919 last_started_at: None, 920 last_stopped_at: None, 921 notes: None, 922 }); 923 924 let target = resolve_runtime_target(&context, "radrootsd", None); 925 assert_eq!(target.instance_id, "local"); 926 assert_eq!(target.instance_source, "bootstrap_default"); 927 assert_eq!( 928 target.runtime_group, 929 ManagedRuntimeGroup::ActiveManagedTarget 930 ); 931 assert!(target.predicted_paths.is_some()); 932 } 933 934 #[test] 935 fn load_context_with_selection_uses_caller_path_selection() { 936 let dir = tempdir().expect("tempdir"); 937 let root = dir.path().join("runtime-root"); 938 let contract = parse_contract_str(CONTRACT).expect("contract"); 939 let resolver = 940 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 941 let selection = RadrootsRuntimePathSelection::caller( 942 RadrootsPathProfile::RepoLocal, 943 Some(root.clone()), 944 ); 945 946 let context = load_management_context_with_selection(contract, &resolver, &selection) 947 .expect("selection context"); 948 949 assert_eq!( 950 context.shared_paths.instance_registry_path, 951 root.join("config/shared/runtime-manager/instances.toml") 952 ); 953 assert!(context.registry.instances.is_empty()); 954 } 955 956 #[test] 957 fn runtime_groups_and_action_labels_cover_declared_surfaces() { 958 let contract = parse_contract_str(CONTRACT).expect("contract"); 959 assert_eq!( 960 runtime_group(&contract, "radrootsd"), 961 ManagedRuntimeGroup::ActiveManagedTarget 962 ); 963 assert_eq!( 964 runtime_group(&contract, "myc"), 965 ManagedRuntimeGroup::DefinedManagedTarget 966 ); 967 assert_eq!( 968 runtime_group(&contract, "hyf"), 969 ManagedRuntimeGroup::BootstrapOnly 970 ); 971 assert_eq!( 972 runtime_group(&contract, "unknown"), 973 ManagedRuntimeGroup::Unknown 974 ); 975 976 assert_eq!( 977 ManagedRuntimeGroup::ActiveManagedTarget.as_str(), 978 "active_managed_target" 979 ); 980 assert_eq!( 981 ManagedRuntimeGroup::DefinedManagedTarget.posture(), 982 "defined_future_target" 983 ); 984 assert_eq!( 985 ManagedRuntimeGroup::BootstrapOnly.posture(), 986 "bootstrap_only_direct_binding" 987 ); 988 assert_eq!(ManagedRuntimeGroup::Unknown.as_str(), "unknown"); 989 990 let actions = [ 991 (ManagedRuntimeLifecycleAction::Install, "install"), 992 (ManagedRuntimeLifecycleAction::Uninstall, "uninstall"), 993 (ManagedRuntimeLifecycleAction::Start, "start"), 994 (ManagedRuntimeLifecycleAction::Stop, "stop"), 995 (ManagedRuntimeLifecycleAction::Restart, "restart"), 996 (ManagedRuntimeLifecycleAction::ConfigSet, "config_set"), 997 ]; 998 for (action, expected) in actions { 999 assert_eq!(action.as_str(), expected); 1000 } 1001 } 1002 1003 #[test] 1004 fn resolve_runtime_target_covers_requested_and_non_active_sources() { 1005 let dir = tempdir().expect("tempdir"); 1006 let mut context = repo_local_context(dir.path()); 1007 context.registry.instances.push(sample_record( 1008 "myc", 1009 "default", 1010 ManagedRuntimeInstallState::Configured, 1011 dir.path(), 1012 )); 1013 1014 let requested = resolve_runtime_target(&context, "radrootsd", Some("manual")); 1015 assert_eq!(requested.instance_id, "manual"); 1016 assert_eq!(requested.instance_source, "command_arg"); 1017 assert_eq!( 1018 requested.runtime_group, 1019 ManagedRuntimeGroup::ActiveManagedTarget 1020 ); 1021 assert!(requested.predicted_paths.is_some()); 1022 1023 let defined = resolve_runtime_target(&context, "myc", None); 1024 assert_eq!(defined.instance_id, "default"); 1025 assert_eq!(defined.instance_source, "implicit_default"); 1026 assert_eq!( 1027 defined.runtime_group, 1028 ManagedRuntimeGroup::DefinedManagedTarget 1029 ); 1030 assert!(defined.predicted_paths.is_none()); 1031 assert!(defined.instance_record.is_some()); 1032 1033 let bootstrap = resolve_runtime_target(&context, "hyf", None); 1034 assert_eq!(bootstrap.instance_source, "implicit_default"); 1035 assert_eq!(bootstrap.runtime_group, ManagedRuntimeGroup::BootstrapOnly); 1036 assert!(bootstrap.management_mode.is_none()); 1037 1038 let unknown = resolve_runtime_target(&context, "unknown", Some("manual")); 1039 assert_eq!(unknown.instance_id, "manual"); 1040 assert_eq!(unknown.instance_source, "command_arg"); 1041 assert_eq!(unknown.runtime_group, ManagedRuntimeGroup::Unknown); 1042 assert!(unknown.predicted_paths.is_none()); 1043 } 1044 1045 #[test] 1046 fn status_inspection_covers_install_and_health_states() { 1047 let dir = tempdir().expect("tempdir"); 1048 let context = repo_local_context(dir.path()); 1049 let active_missing = resolve_runtime_target(&context, "radrootsd", None); 1050 let status = 1051 inspect_runtime_status(&active_missing, &["install".to_owned(), "start".to_owned()]); 1052 assert_eq!( 1053 status.availability, 1054 ManagedRuntimeInspectionAvailability::Success 1055 ); 1056 assert_eq!(status.view.state, "not_installed"); 1057 assert_eq!(status.view.health_state, "not_installed"); 1058 assert_eq!(status.view.health_source, "registry_absent"); 1059 assert_eq!(status.view.lifecycle_actions, ["install", "start"]); 1060 1061 let mut context = repo_local_context(dir.path()); 1062 context.registry.instances.push(sample_record( 1063 "radrootsd", 1064 "local", 1065 ManagedRuntimeInstallState::Configured, 1066 dir.path(), 1067 )); 1068 let active_configured = resolve_runtime_target(&context, "radrootsd", None); 1069 let configured_status = inspect_runtime_status(&active_configured, &[]); 1070 assert_eq!(configured_status.view.state, "configured"); 1071 assert_eq!(configured_status.view.health_state, "stopped"); 1072 assert_eq!(configured_status.view.health_source, "pid_file_absent"); 1073 assert_eq!(configured_status.view.install_state, "configured"); 1074 1075 let predicted = active_configured 1076 .predicted_paths 1077 .as_ref() 1078 .expect("predicted active paths"); 1079 fs::create_dir_all(&predicted.run_dir).expect("run dir"); 1080 fs::write(&predicted.pid_file_path, std::process::id().to_string()).expect("pid"); 1081 let running_status = inspect_runtime_status(&active_configured, &[]); 1082 assert_eq!(running_status.view.health_state, "running"); 1083 assert_eq!(running_status.view.health_source, "process_probe"); 1084 fs::remove_file(&predicted.pid_file_path).expect("remove pid"); 1085 1086 let mut context = repo_local_context(dir.path()); 1087 context.registry.instances.push(sample_record( 1088 "radrootsd", 1089 "local", 1090 ManagedRuntimeInstallState::Failed, 1091 dir.path(), 1092 )); 1093 let active_failed = resolve_runtime_target(&context, "radrootsd", None); 1094 let failed_status = inspect_runtime_status(&active_failed, &[]); 1095 assert_eq!(failed_status.view.state, "failed"); 1096 assert_eq!(failed_status.view.health_state, "failed"); 1097 assert_eq!(failed_status.view.health_source, "registry_install_state"); 1098 1099 let mut context = repo_local_context(dir.path()); 1100 context.registry.instances.push(sample_record( 1101 "radrootsd", 1102 "local", 1103 ManagedRuntimeInstallState::NotInstalled, 1104 dir.path(), 1105 )); 1106 let active_not_installed = resolve_runtime_target(&context, "radrootsd", None); 1107 let not_installed_status = inspect_runtime_status(&active_not_installed, &[]); 1108 assert_eq!(not_installed_status.view.health_state, "not_installed"); 1109 assert_eq!( 1110 not_installed_status.view.health_source, 1111 "registry_install_state" 1112 ); 1113 1114 let mut context = repo_local_context(dir.path()); 1115 let defined_record = sample_record( 1116 "myc", 1117 "default", 1118 ManagedRuntimeInstallState::Installed, 1119 dir.path(), 1120 ); 1121 fs::create_dir_all(&defined_record.run_path).expect("run dir"); 1122 fs::write(defined_record.run_path.join("runtime.pid"), "42").expect("pid"); 1123 context.registry.instances.push(defined_record); 1124 let defined = resolve_runtime_target(&context, "myc", None); 1125 let defined_status = inspect_runtime_status(&defined, &["install".to_owned()]); 1126 assert_eq!(defined_status.view.state, "defined_not_active"); 1127 assert_eq!(defined_status.view.health_state, "running"); 1128 assert_eq!(defined_status.view.health_source, "pid_file_presence"); 1129 assert!(defined_status.view.lifecycle_actions.is_empty()); 1130 1131 let no_pid_dir = tempdir().expect("no-pid tempdir"); 1132 let mut context = repo_local_context(no_pid_dir.path()); 1133 context.registry.instances.push(sample_record( 1134 "myc", 1135 "default", 1136 ManagedRuntimeInstallState::Installed, 1137 no_pid_dir.path(), 1138 )); 1139 let defined_without_pid = resolve_runtime_target(&context, "myc", None); 1140 let defined_without_pid_status = inspect_runtime_status(&defined_without_pid, &[]); 1141 assert_eq!(defined_without_pid_status.view.health_state, "stopped"); 1142 assert_eq!( 1143 defined_without_pid_status.view.health_source, 1144 "pid_file_absent" 1145 ); 1146 1147 let bootstrap = resolve_runtime_target(&context, "hyf", None); 1148 let bootstrap_status = inspect_runtime_status(&bootstrap, &[]); 1149 assert_eq!(bootstrap_status.view.state, "bootstrap_only"); 1150 assert_eq!( 1151 bootstrap_status.view.management_posture, 1152 "bootstrap_only_direct_binding" 1153 ); 1154 assert_eq!( 1155 health_state_label(ManagedRuntimeHealthState::Starting), 1156 "starting" 1157 ); 1158 assert_eq!( 1159 health_state_label(ManagedRuntimeHealthState::Degraded), 1160 "degraded" 1161 ); 1162 1163 let unknown = resolve_runtime_target(&context, "unknown", None); 1164 let unknown_status = inspect_runtime_status(&unknown, &[]); 1165 assert_eq!( 1166 unknown_status.availability, 1167 ManagedRuntimeInspectionAvailability::Unconfigured 1168 ); 1169 assert_eq!(unknown_status.view.state, "unknown_runtime"); 1170 } 1171 1172 #[test] 1173 fn logs_and_config_inspections_cover_availability_paths() { 1174 let dir = tempdir().expect("tempdir"); 1175 let mut context = repo_local_context(dir.path()); 1176 context.registry.instances.push(sample_record( 1177 "radrootsd", 1178 "local", 1179 ManagedRuntimeInstallState::Configured, 1180 dir.path(), 1181 )); 1182 let active = resolve_runtime_target(&context, "radrootsd", None); 1183 let predicted = active.predicted_paths.as_ref().expect("predicted paths"); 1184 fs::create_dir_all(&predicted.logs_dir).expect("predicted logs dir"); 1185 fs::write(&predicted.stdout_log_path, "stdout").expect("stdout"); 1186 fs::write(&predicted.stderr_log_path, "stderr").expect("stderr"); 1187 let config_path = active 1188 .instance_record 1189 .as_ref() 1190 .expect("record") 1191 .config_path 1192 .clone(); 1193 fs::create_dir_all(config_path.parent().expect("config parent")).expect("config parent"); 1194 fs::write(&config_path, "listen = true").expect("config"); 1195 1196 let active_logs = inspect_runtime_logs(&active); 1197 assert_eq!( 1198 active_logs.availability, 1199 ManagedRuntimeInspectionAvailability::Success 1200 ); 1201 assert_eq!(active_logs.view.state, "ready"); 1202 assert!(active_logs.view.stdout_log_present); 1203 assert!(active_logs.view.stderr_log_present); 1204 assert!(active_logs.view.stdout_log_path.is_some()); 1205 1206 let active_config = inspect_runtime_config(&active); 1207 assert_eq!(active_config.view.state, "ready"); 1208 assert!(active_config.view.config_present); 1209 assert_eq!(active_config.view.config_format.as_deref(), Some("toml")); 1210 assert_eq!(active_config.view.requires_bootstrap_secret, Some(true)); 1211 assert_eq!(active_config.view.requires_config_bootstrap, Some(true)); 1212 assert_eq!(active_config.view.requires_signer_provider, Some(false)); 1213 1214 let empty_dir = tempdir().expect("empty tempdir"); 1215 let empty_context = repo_local_context(empty_dir.path()); 1216 let active_missing = resolve_runtime_target(&empty_context, "radrootsd", None); 1217 let missing_logs = inspect_runtime_logs(&active_missing); 1218 assert_eq!(missing_logs.view.state, "ready"); 1219 assert!(!missing_logs.view.stdout_log_present); 1220 assert!(!missing_logs.view.stderr_log_present); 1221 let missing_config = inspect_runtime_config(&active_missing); 1222 assert_eq!(missing_config.view.state, "not_installed"); 1223 assert!(!missing_config.view.config_present); 1224 1225 let mut context = repo_local_context(dir.path()); 1226 let defined_record = sample_record( 1227 "myc", 1228 "default", 1229 ManagedRuntimeInstallState::Configured, 1230 dir.path(), 1231 ); 1232 fs::create_dir_all(&defined_record.logs_path).expect("defined logs dir"); 1233 fs::write(defined_record.logs_path.join("stdout.log"), "stdout").expect("defined stdout"); 1234 fs::create_dir_all(defined_record.config_path.parent().expect("config parent")) 1235 .expect("defined config parent"); 1236 fs::write(&defined_record.config_path, "enabled = true").expect("defined config"); 1237 context.registry.instances.push(defined_record); 1238 let defined = resolve_runtime_target(&context, "myc", None); 1239 let defined_logs = inspect_runtime_logs(&defined); 1240 assert_eq!( 1241 defined_logs.availability, 1242 ManagedRuntimeInspectionAvailability::Success 1243 ); 1244 assert_eq!(defined_logs.view.state, "ready"); 1245 assert!(defined_logs.view.stdout_log_present); 1246 assert!(!defined_logs.view.stderr_log_present); 1247 assert!(defined_logs.view.stdout_log_path.is_none()); 1248 let defined_config = inspect_runtime_config(&defined); 1249 assert_eq!( 1250 defined_config.availability, 1251 ManagedRuntimeInspectionAvailability::Success 1252 ); 1253 assert_eq!(defined_config.view.state, "ready"); 1254 assert!(defined_config.view.config_present); 1255 1256 let defined_without_record = resolve_runtime_target(&empty_context, "myc", None); 1257 assert_eq!( 1258 inspect_runtime_logs(&defined_without_record).availability, 1259 ManagedRuntimeInspectionAvailability::Unsupported 1260 ); 1261 assert_eq!( 1262 inspect_runtime_config(&defined_without_record).availability, 1263 ManagedRuntimeInspectionAvailability::Unsupported 1264 ); 1265 1266 let bootstrap = resolve_runtime_target(&empty_context, "hyf", None); 1267 assert_eq!( 1268 inspect_runtime_logs(&bootstrap).availability, 1269 ManagedRuntimeInspectionAvailability::Unsupported 1270 ); 1271 assert_eq!( 1272 inspect_runtime_config(&bootstrap).availability, 1273 ManagedRuntimeInspectionAvailability::Unsupported 1274 ); 1275 1276 let unknown = resolve_runtime_target(&empty_context, "unknown", None); 1277 assert_eq!( 1278 inspect_runtime_logs(&unknown).availability, 1279 ManagedRuntimeInspectionAvailability::Unconfigured 1280 ); 1281 assert_eq!( 1282 inspect_runtime_config(&unknown).availability, 1283 ManagedRuntimeInspectionAvailability::Unconfigured 1284 ); 1285 } 1286 1287 #[test] 1288 fn action_inspection_covers_all_group_postures() { 1289 let dir = tempdir().expect("tempdir"); 1290 let context = repo_local_context(dir.path()); 1291 let active = resolve_runtime_target(&context, "radrootsd", None); 1292 let defined = resolve_runtime_target(&context, "myc", None); 1293 let bootstrap = resolve_runtime_target(&context, "hyf", None); 1294 let unknown = resolve_runtime_target(&context, "unknown", None); 1295 1296 let active_install = 1297 inspect_runtime_action(&active, ManagedRuntimeLifecycleAction::Install, None); 1298 assert_eq!( 1299 active_install.availability, 1300 ManagedRuntimeInspectionAvailability::Unsupported 1301 ); 1302 assert_eq!(active_install.view.state, "deferred"); 1303 assert!(active_install.view.detail.contains("runtime install")); 1304 assert!(!active_install.view.mutates_bindings); 1305 assert!(active_install.view.next_step.is_none()); 1306 1307 let overridden = inspect_runtime_action( 1308 &active, 1309 ManagedRuntimeLifecycleAction::ConfigSet, 1310 Some("custom detail".to_owned()), 1311 ); 1312 assert_eq!(overridden.view.action, "config_set"); 1313 assert_eq!(overridden.view.detail, "custom detail"); 1314 1315 let defined_start = 1316 inspect_runtime_action(&defined, ManagedRuntimeLifecycleAction::Start, None); 1317 assert_eq!(defined_start.view.state, "unsupported"); 1318 assert!( 1319 defined_start 1320 .view 1321 .detail 1322 .contains("defined future managed target") 1323 ); 1324 1325 let bootstrap_stop = 1326 inspect_runtime_action(&bootstrap, ManagedRuntimeLifecycleAction::Stop, None); 1327 assert_eq!(bootstrap_stop.view.state, "unsupported"); 1328 assert!(bootstrap_stop.view.detail.contains("bootstrap_only")); 1329 1330 let unknown_restart = 1331 inspect_runtime_action(&unknown, ManagedRuntimeLifecycleAction::Restart, None); 1332 assert_eq!( 1333 unknown_restart.availability, 1334 ManagedRuntimeInspectionAvailability::Unconfigured 1335 ); 1336 assert_eq!(unknown_restart.view.state, "unknown_runtime"); 1337 } 1338 }