commit 13a8c2c08441ec4ae069ce083c2b290300018061
parent 5b0f2d9bcabada0beb8768356c3ac354b9c20aa5
Author: triesap <tyson@radroots.org>
Date: Sun, 16 Aug 2026 03:03:25 +0000
runtime-manager: quarantine legacy lifecycle authority
Diffstat:
11 files changed, 318 insertions(+), 3531 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3403,12 +3403,9 @@ dependencies = [
name = "radroots_runtime_manager"
version = "0.1.0-alpha"
dependencies = [
- "flate2",
"radroots_runtime_distribution",
"radroots_runtime_paths",
"serde",
- "tar",
- "tempfile",
"thiserror 1.0.69",
"toml 0.8.23",
]
diff --git a/crates/runtime_manager/Cargo.toml b/crates/runtime_manager/Cargo.toml
@@ -6,20 +6,15 @@ edition.workspace = true
authors = ["Tyson Lupul <tyson@radroots.org>"]
rust-version.workspace = true
license.workspace = true
-description = "Runtime lifecycle layer for Radroots"
+description = "Metadata-only hardened service target resolver for Radroots"
repository.workspace = true
homepage.workspace = true
documentation = "https://docs.rs/radroots_runtime_manager"
readme = "README"
[dependencies]
-flate2 = { workspace = true }
radroots_runtime_distribution = { workspace = true }
radroots_runtime_paths = { workspace = true }
serde = { workspace = true, features = ["derive"] }
-tar = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
-
-[dev-dependencies]
-tempfile = { workspace = true }
diff --git a/crates/runtime_manager/README b/crates/runtime_manager/README
@@ -1,30 +1,19 @@
# radroots_runtime_manager
-This is the README for `radroots_runtime_manager`, which provides managed
-runtime lifecycle and inspection helpers for the `radroots` core libraries.
+`radroots_runtime_manager` validates the frozen runtime-management contract and
+resolves explicitly selected Myc and RHI service-instance identities to sealed,
+metadata-only targets.
-## Overview
-
- * schema-checked management contract parsing and shared management constants;
- * manager-owned shared and per-instance tracking paths kept separate from
- canonical service-instance paths supplied by a sealed `RuntimeContext`;
- * typed, deterministic registry load, save, and lookup helpers that
- persist service/instance identities without duplicating service paths or
- secrets;
- * lifecycle helpers for contained archive install, process start and stop,
- and context-bound non-secret configuration writes; the manager has no
- credential read/write authority;
- * sealed Myc/RHI target metadata and context resolution without service
- registration, lifecycle admission, or PID/config/log probing; and
- * cleanup behavior limited to manager-owned install and tracking artifacts,
-preserving canonical service state and secrets.
+The crate performs no filesystem, registry, process, archive, artifact,
+configuration, log, PID, install, removal, or lifecycle work. It exposes no
+runtime paths or raw persistence helpers. Final service lifecycle and artifact
+behavior remains unavailable until the separately governed Steps 219 and 220.
Complete management TOML documents are rejected before parsing when they
exceed exactly 1,048,576 UTF-8 bytes. Bounded documents still require the exact
-schema, version, closed field set, and complete hardened-service inventory.
-
-Myc and RHI remain metadata-only management targets until their public CLI,
-Unix-admin, status, and artifact integrations are separately implemented.
+schema, version, closed field set, empty lifecycle inventory, and complete
+Myc/RHI metadata inventory. Directly constructed contracts are revalidated
+before a management context can be created.
## Copyright
diff --git a/crates/runtime_manager/src/error.rs b/crates/runtime_manager/src/error.rs
@@ -1,10 +1,6 @@
use thiserror::Error;
-/// Stable, path-free runtime-management failures.
-///
-/// Filesystem paths, file contents, and dependency-owned causes are retained
-/// only inside the operation that handles them. Ordinary `Display`, `Debug`,
-/// and error-chain traversal therefore cannot disclose them.
+/// Stable, value-free runtime-management metadata failures.
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
pub enum RadrootsRuntimeManagerError {
#[error("runtime management contract exceeds its size limit")]
@@ -19,60 +15,6 @@ pub enum RadrootsRuntimeManagerError {
InvalidContract,
#[error("management mode does not support the selected profile")]
UnsupportedProfile,
- #[error("runtime has no bootstrap entry in runtime management contract")]
- UnknownBootstrapRuntime,
#[error("runtime is not a hardened service target")]
UnsupportedServiceTarget,
- #[error("hardened service target is metadata-only until service integration is complete")]
- MetadataOnlyServiceTarget,
- #[error("runtime context does not share the manager path scope")]
- RuntimeContextMismatch,
- #[error("read runtime instance registry failed: {kind}")]
- ReadRegistry { kind: std::io::ErrorKind },
- #[error("parse runtime instance registry failed")]
- ParseRegistry,
- #[error("runtime instance registry schema is unsupported")]
- UnexpectedRegistrySchema,
- #[error("runtime instance registry version is unsupported")]
- UnexpectedRegistryVersion,
- #[error("runtime instance registry contains a duplicate service instance")]
- DuplicateRegistryInstance,
- #[error("serialize runtime instance registry failed")]
- SerializeRegistry,
- #[error("create runtime instance registry parent failed: {kind}")]
- CreateRegistryParent { kind: std::io::ErrorKind },
- #[error("write runtime instance registry failed: {kind}")]
- WriteRegistry { kind: std::io::ErrorKind },
- #[error("create managed runtime directory failed: {kind}")]
- CreateDirectory { kind: std::io::ErrorKind },
- #[error("copy managed runtime binary failed: {kind}")]
- CopyBinary { kind: std::io::ErrorKind },
- #[error("write managed runtime config failed: {kind}")]
- WriteManagedConfig { kind: std::io::ErrorKind },
- #[error("read managed runtime file failed: {kind}")]
- ReadManagedFile { kind: std::io::ErrorKind },
- #[error("open managed runtime log failed: {kind}")]
- OpenLogFile { kind: std::io::ErrorKind },
- #[error("spawn managed runtime process failed: {kind}")]
- SpawnProcess { kind: std::io::ErrorKind },
- #[error("write managed runtime pid failed: {kind}")]
- WritePidFile { kind: std::io::ErrorKind },
- #[error("read managed runtime pid failed: {kind}")]
- ReadPidFile { kind: std::io::ErrorKind },
- #[error("managed runtime pid is malformed")]
- ParsePidFile,
- #[error("remove manager-owned runtime path failed: {kind}")]
- RemovePath { kind: std::io::ErrorKind },
- #[error("set managed runtime file permissions failed: {kind}")]
- SetPermissions { kind: std::io::ErrorKind },
- #[error("signal managed runtime process failed: {kind}")]
- ExecuteProcessSignal { kind: std::io::ErrorKind },
- #[error("managed runtime process did not stop")]
- StopProcess,
- #[error("managed runtime archive format is unsupported")]
- UnsupportedArchiveFormat,
- #[error("unpack managed runtime archive failed: {kind}")]
- UnpackArchive { kind: std::io::ErrorKind },
- #[error("managed runtime artifact name is invalid")]
- InvalidArtifactName,
}
diff --git a/crates/runtime_manager/src/lib.rs b/crates/runtime_manager/src/lib.rs
@@ -1,34 +1,16 @@
#![forbid(unsafe_code)]
-pub mod error;
-pub mod lifecycle;
-pub mod managed;
-pub mod model;
-pub mod paths;
-pub mod registry;
+mod error;
+mod managed;
+mod model;
pub use error::RadrootsRuntimeManagerError;
-pub use lifecycle::{
- ManagedRuntimeArtifactName, ensure_instance_layout, extract_binary_archive, install_binary,
- process_running, remove_instance_artifacts, start_process, stop_process, write_instance_config,
-};
-pub use managed::{
- ManagedRuntimeActionInspection, ManagedRuntimeConfigInspection, ManagedRuntimeContext,
- ManagedRuntimeGroup, ManagedRuntimeInspection, ManagedRuntimeInspectionAvailability,
- ManagedRuntimeLifecycleAction, ManagedRuntimeLogsInspection, ManagedRuntimeStatusInspection,
- ManagedRuntimeTarget, active_management_mode_for_profile, inspect_runtime_action,
- inspect_runtime_config, inspect_runtime_logs, inspect_runtime_status, load_management_context,
- resolve_runtime_target, runtime_group,
-};
+pub use managed::{ManagedRuntimeContext, ManagedRuntimeTarget, resolve_runtime_target};
pub use model::{
- BootstrapRuntimeContract, LifecycleContract, ManagedRuntimeHealthState,
- ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry,
- ManagementDefaults, ManagementModeContract, ManagementPathContract,
- RUNTIME_INSTANCE_REGISTRY_SCHEMA, RUNTIME_INSTANCE_REGISTRY_VERSION,
- RadrootsRuntimeManagementContract, RuntimeGroups,
+ BootstrapRuntimeContract, InstanceMetadataContract, LifecycleContract, ManagementDefaults,
+ ManagementModeContract, ManagementPathContract, RadrootsRuntimeManagementContract,
+ RuntimeGroups,
};
-pub use paths::{ManagedRuntimeInstancePaths, ManagedRuntimeSharedPaths, bootstrap_runtime};
-pub use registry::{instance, load_registry, save_registry};
pub const RUNTIME_MANAGEMENT_SCHEMA: &str = "radroots-runtime-management";
pub const RUNTIME_MANAGEMENT_SCHEMA_VERSION: u32 = 1;
@@ -79,15 +61,23 @@ mod tests {
const CONTRACT: &str = HARDENED_MANAGEMENT_CONTRACT;
#[test]
- fn contract_parser_accepts_only_the_expected_schema() {
+ fn contract_parser_accepts_only_the_exact_static_inventory() {
let contract = parse_contract_str(CONTRACT).expect("contract");
assert_eq!(contract.schema, RUNTIME_MANAGEMENT_SCHEMA);
-
- let wrong = CONTRACT.replace(
- "schema = \"radroots-runtime-management\"",
- "schema = \"wrong\"",
- );
- assert!(parse_contract_str(&wrong).is_err());
+ assert_eq!(contract.service_targets.len(), 2);
+ assert!(contract.bootstrap.is_empty());
+
+ for raw in [
+ CONTRACT.replace("schema_version = 1", "schema_version = 2"),
+ CONTRACT.replace(
+ "defined = [\"myc\", \"rhi\"]",
+ "active = [\"myc\"]\ndefined = [\"rhi\"]",
+ ),
+ CONTRACT.replace("actions = []", "actions = [\"start\"]"),
+ format!("{CONTRACT}\nunknown = true\n"),
+ ] {
+ assert!(parse_contract_str(&raw).is_err());
+ }
assert!(parse_contract_str("schema = [").is_err());
}
@@ -116,7 +106,7 @@ mod tests {
}
#[test]
- fn contract_errors_redact_raw_schema_values_and_parser_causes() {
+ fn contract_errors_are_value_free_and_source_free() {
for (raw, secret) in [
(
CONTRACT.replace(
@@ -130,43 +120,39 @@ mod tests {
"secret-value",
),
] {
- let err = parse_contract_str(&raw).expect_err("invalid contract");
- let rendered = format!("{err} {err:?}");
+ let error = parse_contract_str(&raw).expect_err("invalid contract");
+ let rendered = format!("{error} {error:?}");
assert!(!rendered.contains(secret));
- assert!(err.source().is_none());
+ assert!(error.source().is_none());
}
}
#[test]
- fn manager_source_no_longer_consumes_legacy_path_selection_or_raw_identity_joins() {
- let sources = [
- include_str!("error.rs"),
- include_str!("lifecycle.rs"),
- include_str!("managed.rs"),
- include_str!("model.rs"),
- include_str!("paths.rs"),
- include_str!("registry.rs"),
- ];
+ fn root_surface_exposes_no_legacy_runtime_authority() {
+ let source = include_str!("lib.rs")
+ .split("\n#[cfg(test)]")
+ .next()
+ .expect("production source");
for forbidden in [
- "RadrootsRuntimePathSelection",
- "load_management_context_with_selection",
- "PathBuf::from(runtime_id).join(instance_id)",
- "record.config_path",
- "record.logs_path",
- "record.run_path",
- "workers/rhi",
- "pub fn read_secret_file",
- "pub fn write_secret_file",
- "pub fn write_managed_file",
- "pub fn registry_mut",
- "pub fn upsert_instance",
- "pub fn resolve_shared_paths",
- "pub fn resolve_instance_paths",
+ "mod lifecycle",
+ "mod paths",
+ "mod registry",
+ "ManagedRuntimeArtifactName",
+ "ManagedRuntimeInstancePaths",
+ "ManagedRuntimeSharedPaths",
+ "ManagedRuntimeInstanceRegistry",
+ "ManagedRuntimeInstanceRecord",
+ "ManagedRuntimeLifecycleAction",
+ "load_registry",
+ "save_registry",
+ "start_process",
+ "stop_process",
+ "install_binary",
+ "extract_binary_archive",
+ "remove_instance_artifacts",
+ "write_instance_config",
] {
- assert!(
- sources.iter().all(|source| !source.contains(forbidden)),
- "runtime manager retained forbidden source `{forbidden}`"
- );
+ assert!(!source.contains(forbidden), "root retained `{forbidden}`");
}
}
}
diff --git a/crates/runtime_manager/src/lifecycle.rs b/crates/runtime_manager/src/lifecycle.rs
@@ -1,1387 +0,0 @@
-use std::fs::{self, File, OpenOptions};
-use std::path::{Path, PathBuf};
-use std::process::{Command, Stdio};
-use std::thread;
-use std::time::Duration;
-
-#[cfg(unix)]
-use std::process::{ExitStatus, Output};
-
-use flate2::read::GzDecoder;
-
-use crate::error::RadrootsRuntimeManagerError;
-use crate::paths::ManagedRuntimeInstancePaths;
-
-type SpawnProcess = fn(&Path, &[String], &[(String, String)], File, File) -> std::io::Result<u32>;
-
-/// A validated single-component manager-owned executable artifact name.
-#[derive(Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeArtifactName(String);
-
-impl ManagedRuntimeArtifactName {
- pub fn new(value: &str) -> Result<Self, RadrootsRuntimeManagerError> {
- if value.is_empty()
- || value.len() > 128
- || !value.as_bytes()[0].is_ascii_alphanumeric()
- || !value
- .bytes()
- .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
- || Path::new(value).components().count() != 1
- {
- return Err(RadrootsRuntimeManagerError::InvalidArtifactName);
- }
- Ok(Self(value.to_owned()))
- }
-
- #[must_use]
- pub fn as_str(&self) -> &str {
- &self.0
- }
-}
-
-impl core::fmt::Debug for ManagedRuntimeArtifactName {
- fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- formatter.write_str("ManagedRuntimeArtifactName([redacted])")
- }
-}
-
-pub fn ensure_instance_layout(
- paths: &ManagedRuntimeInstancePaths,
-) -> Result<(), RadrootsRuntimeManagerError> {
- for path in [paths.install_dir(), paths.logs_dir(), paths.run_dir()] {
- fs::create_dir_all(path).map_err(|source| {
- RadrootsRuntimeManagerError::CreateDirectory {
- kind: source.kind(),
- }
- })?;
- }
- Ok(())
-}
-
-pub fn install_binary(
- source_binary_path: impl AsRef<Path>,
- paths: &ManagedRuntimeInstancePaths,
- binary_name: &ManagedRuntimeArtifactName,
-) -> Result<PathBuf, RadrootsRuntimeManagerError> {
- install_binary_path(source_binary_path.as_ref(), paths, binary_name)
-}
-
-fn install_binary_path(
- source_binary_path: &Path,
- paths: &ManagedRuntimeInstancePaths,
- binary_name: &ManagedRuntimeArtifactName,
-) -> Result<PathBuf, RadrootsRuntimeManagerError> {
- ensure_instance_layout(paths)?;
- let installed_binary_path = paths.install_dir().join(binary_name.as_str());
- fs::copy(source_binary_path, &installed_binary_path).map_err(|source| {
- RadrootsRuntimeManagerError::CopyBinary {
- kind: source.kind(),
- }
- })?;
- set_executable_mode(&installed_binary_path)?;
- Ok(installed_binary_path)
-}
-
-pub fn extract_binary_archive(
- archive_path: impl AsRef<Path>,
- archive_format: &str,
- paths: &ManagedRuntimeInstancePaths,
- binary_name: &ManagedRuntimeArtifactName,
-) -> Result<PathBuf, RadrootsRuntimeManagerError> {
- extract_binary_archive_path(archive_path.as_ref(), archive_format, paths, binary_name)
-}
-
-fn extract_binary_archive_path(
- archive_path: &Path,
- archive_format: &str,
- paths: &ManagedRuntimeInstancePaths,
- binary_name: &ManagedRuntimeArtifactName,
-) -> Result<PathBuf, RadrootsRuntimeManagerError> {
- remove_path_if_exists(paths.install_dir())?;
- ensure_instance_layout(paths)?;
-
- match archive_format {
- "tar.gz" => unpack_tar_gz_archive(archive_path, paths.install_dir())?,
- _ => return Err(RadrootsRuntimeManagerError::UnsupportedArchiveFormat),
- }
-
- let installed_binary_path = paths.install_dir().join(binary_name.as_str());
- let resolved_binary_path = if installed_binary_path.is_file() {
- installed_binary_path
- } else {
- find_binary_with_name(paths.install_dir(), binary_name.as_str()).ok_or(
- RadrootsRuntimeManagerError::ReadManagedFile {
- kind: std::io::ErrorKind::NotFound,
- },
- )?
- };
- set_executable_mode(&resolved_binary_path)?;
- Ok(resolved_binary_path)
-}
-
-pub fn write_instance_config(
- paths: &ManagedRuntimeInstancePaths,
- contents: &str,
-) -> Result<(), RadrootsRuntimeManagerError> {
- let path = paths.config_path();
- ensure_parent_dir(&path)?;
- fs::write(path, contents).map_err(|source| RadrootsRuntimeManagerError::WriteManagedConfig {
- kind: source.kind(),
- })
-}
-
-pub fn start_process(
- paths: &ManagedRuntimeInstancePaths,
- binary_name: &ManagedRuntimeArtifactName,
- args: &[String],
- envs: &[(String, String)],
-) -> Result<u32, RadrootsRuntimeManagerError> {
- start_process_path(
- &paths.install_dir().join(binary_name.as_str()),
- args,
- envs,
- paths,
- )
-}
-
-fn start_process_path(
- binary_path: &Path,
- args: &[String],
- envs: &[(String, String)],
- paths: &ManagedRuntimeInstancePaths,
-) -> Result<u32, RadrootsRuntimeManagerError> {
- start_process_with(binary_path, args, envs, paths, spawn_process)
-}
-
-fn start_process_with(
- binary_path: &Path,
- args: &[String],
- envs: &[(String, String)],
- paths: &ManagedRuntimeInstancePaths,
- spawn: SpawnProcess,
-) -> Result<u32, RadrootsRuntimeManagerError> {
- ensure_instance_layout(paths)?;
- let stdout = open_log_file(paths.stdout_log_path())?;
- let stderr = open_log_file(paths.stderr_log_path())?;
- let pid = spawn(binary_path, args, envs, stdout, stderr).map_err(|source| {
- RadrootsRuntimeManagerError::SpawnProcess {
- kind: source.kind(),
- }
- })?;
- fs::write(paths.pid_file_path(), pid.to_string()).map_err(|source| {
- RadrootsRuntimeManagerError::WritePidFile {
- kind: source.kind(),
- }
- })?;
- Ok(pid)
-}
-
-fn spawn_process(
- binary_path: &Path,
- args: &[String],
- envs: &[(String, String)],
- stdout: File,
- stderr: File,
-) -> std::io::Result<u32> {
- Command::new(binary_path)
- .args(args)
- .envs(envs.iter().map(|(key, value)| (key, value)))
- .stdin(Stdio::null())
- .stdout(Stdio::from(stdout))
- .stderr(Stdio::from(stderr))
- .spawn()
- .map(|child| child.id())
-}
-
-pub fn process_running(
- paths: &ManagedRuntimeInstancePaths,
-) -> Result<bool, RadrootsRuntimeManagerError> {
- let Some(pid) = read_pid(paths)? else {
- return Ok(false);
- };
- Ok(process_running_for_pid(pid))
-}
-
-pub fn stop_process(
- paths: &ManagedRuntimeInstancePaths,
-) -> Result<bool, RadrootsRuntimeManagerError> {
- let Some(pid) = read_pid(paths)? else {
- return Ok(false);
- };
- if !process_running_for_pid(pid) {
- remove_pid_file(paths)?;
- return Ok(false);
- }
-
- let mut is_running = process_running_for_pid;
- let mut terminate = terminate_process;
- let mut force_kill = force_kill_process;
- let mut sleep = thread::sleep;
- stop_process_for_pid(
- paths,
- pid,
- &mut is_running,
- &mut terminate,
- &mut force_kill,
- &mut sleep,
- )
-}
-
-pub fn remove_instance_artifacts(
- paths: &ManagedRuntimeInstancePaths,
-) -> Result<(), RadrootsRuntimeManagerError> {
- for path in [paths.install_dir(), paths.logs_dir(), paths.run_dir()] {
- remove_path_if_exists(path)?;
- }
- Ok(())
-}
-
-fn stop_process_for_pid(
- paths: &ManagedRuntimeInstancePaths,
- pid: u32,
- is_running: &mut dyn FnMut(u32) -> bool,
- terminate: &mut dyn FnMut(u32) -> Result<(), RadrootsRuntimeManagerError>,
- force_kill: &mut dyn FnMut(u32) -> Result<(), RadrootsRuntimeManagerError>,
- sleep: &mut dyn FnMut(Duration),
-) -> Result<bool, RadrootsRuntimeManagerError> {
- terminate(pid)?;
- for _ in 0..20 {
- if !is_running(pid) {
- remove_pid_file(paths)?;
- return Ok(true);
- }
- sleep(Duration::from_millis(100));
- }
-
- force_kill(pid)?;
- for _ in 0..20 {
- if !is_running(pid) {
- remove_pid_file(paths)?;
- return Ok(true);
- }
- sleep(Duration::from_millis(100));
- }
-
- Err(RadrootsRuntimeManagerError::StopProcess)
-}
-
-fn unpack_tar_gz_archive(
- archive_path: &Path,
- destination_dir: &Path,
-) -> Result<(), RadrootsRuntimeManagerError> {
- let archive_file = File::open(archive_path).map_err(|source| {
- RadrootsRuntimeManagerError::ReadManagedFile {
- kind: source.kind(),
- }
- })?;
- let decoder = GzDecoder::new(archive_file);
- let mut archive = tar::Archive::new(decoder);
- archive
- .unpack(destination_dir)
- .map_err(|source| RadrootsRuntimeManagerError::UnpackArchive {
- kind: source.kind(),
- })
-}
-
-fn find_binary_with_name(root: &Path, binary_name: &str) -> Option<PathBuf> {
- let entries = fs::read_dir(root).ok()?;
- for entry in entries.flatten() {
- let path = entry.path();
- if path.is_dir() {
- if let Some(found) = find_binary_with_name(&path, binary_name) {
- return Some(found);
- }
- continue;
- }
- if path.file_name().and_then(|name| name.to_str()) == Some(binary_name) {
- return Some(path);
- }
- }
- None
-}
-
-fn open_log_file(path: &Path) -> Result<File, RadrootsRuntimeManagerError> {
- ensure_parent_dir(path)?;
- OpenOptions::new()
- .create(true)
- .append(true)
- .open(path)
- .map_err(|source| RadrootsRuntimeManagerError::OpenLogFile {
- kind: source.kind(),
- })
-}
-
-fn ensure_parent_dir(path: &Path) -> Result<(), RadrootsRuntimeManagerError> {
- let Some(parent) = path.parent() else {
- return Ok(());
- };
- fs::create_dir_all(parent).map_err(|source| RadrootsRuntimeManagerError::CreateDirectory {
- kind: source.kind(),
- })
-}
-
-fn read_pid(
- paths: &ManagedRuntimeInstancePaths,
-) -> Result<Option<u32>, RadrootsRuntimeManagerError> {
- let raw = match fs::read_to_string(paths.pid_file_path()) {
- Ok(raw) => raw,
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
- Err(source) => {
- return Err(RadrootsRuntimeManagerError::ReadPidFile {
- kind: source.kind(),
- });
- }
- };
- let trimmed = raw.trim();
- if trimmed.is_empty() {
- return Ok(None);
- }
- trimmed
- .parse::<u32>()
- .map(Some)
- .map_err(|_| RadrootsRuntimeManagerError::ParsePidFile)
-}
-
-fn remove_pid_file(paths: &ManagedRuntimeInstancePaths) -> Result<(), RadrootsRuntimeManagerError> {
- match fs::remove_file(paths.pid_file_path()) {
- Ok(()) => Ok(()),
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
- Err(source) => Err(RadrootsRuntimeManagerError::RemovePath {
- kind: source.kind(),
- }),
- }
-}
-
-fn remove_path_if_exists(path: &Path) -> Result<(), RadrootsRuntimeManagerError> {
- let state = match fs::metadata(path) {
- Ok(metadata) if metadata.is_dir() => Ok(Some(ExistingPathKind::Directory)),
- Ok(_) => Ok(Some(ExistingPathKind::File)),
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
- Err(source) => Err(source),
- };
- remove_path_from_state(path, state, remove_dir_all_path, remove_file_path)
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum ExistingPathKind {
- Directory,
- File,
-}
-
-fn remove_path_from_state(
- path: &Path,
- state: Result<Option<ExistingPathKind>, std::io::Error>,
- remove_dir_all: fn(&Path) -> std::io::Result<()>,
- remove_file: fn(&Path) -> std::io::Result<()>,
-) -> Result<(), RadrootsRuntimeManagerError> {
- match state {
- Ok(Some(ExistingPathKind::Directory)) => {
- remove_dir_all(path).map_err(|source| RadrootsRuntimeManagerError::RemovePath {
- kind: source.kind(),
- })
- }
- Ok(Some(ExistingPathKind::File)) => {
- remove_file(path).map_err(|source| RadrootsRuntimeManagerError::RemovePath {
- kind: source.kind(),
- })
- }
- Ok(None) => Ok(()),
- Err(source) => Err(RadrootsRuntimeManagerError::ReadManagedFile {
- kind: source.kind(),
- }),
- }
-}
-
-fn remove_dir_all_path(path: &Path) -> std::io::Result<()> {
- fs::remove_dir_all(path)
-}
-
-fn remove_file_path(path: &Path) -> std::io::Result<()> {
- fs::remove_file(path)
-}
-
-#[cfg(unix)]
-fn set_executable_mode(path: &Path) -> Result<(), RadrootsRuntimeManagerError> {
- apply_mode(path, 0o755, set_permissions_path)
-}
-
-#[cfg(not(unix))]
-fn set_executable_mode(_path: &Path) -> Result<(), RadrootsRuntimeManagerError> {
- Ok(())
-}
-
-#[cfg(unix)]
-fn apply_mode(
- path: &Path,
- mode: u32,
- set_permissions: fn(&Path, fs::Permissions) -> std::io::Result<()>,
-) -> Result<(), RadrootsRuntimeManagerError> {
- use std::os::unix::fs::PermissionsExt;
-
- let metadata =
- fs::metadata(path).map_err(|source| RadrootsRuntimeManagerError::ReadManagedFile {
- kind: source.kind(),
- })?;
- let mut permissions = metadata.permissions();
- permissions.set_mode(mode);
- set_permissions(path, permissions).map_err(|source| {
- RadrootsRuntimeManagerError::SetPermissions {
- kind: source.kind(),
- }
- })
-}
-
-#[cfg(unix)]
-fn set_permissions_path(path: &Path, permissions: fs::Permissions) -> std::io::Result<()> {
- fs::set_permissions(path, permissions)
-}
-
-#[cfg(unix)]
-fn process_running_for_pid(pid: u32) -> bool {
- let pid_arg = pid.to_string();
- let running = Command::new("kill")
- .args(["-0", pid_arg.as_str()])
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .status()
- .map(|status| status.success())
- .unwrap_or(false);
- if !running {
- return false;
- }
-
- ps_output_for_pid(pid_arg.as_str())
- .map(process_running_state_from_ps_output)
- .unwrap_or(true)
-}
-
-#[cfg(unix)]
-fn ps_output_for_pid(pid_arg: &str) -> std::io::Result<Output> {
- Command::new("ps")
- .args(["-o", "stat=", "-p", pid_arg])
- .stdout(Stdio::piped())
- .stderr(Stdio::null())
- .output()
-}
-
-#[cfg(unix)]
-fn process_running_state_from_ps_output(output: Output) -> bool {
- if !output.status.success() {
- return true;
- }
- let state = String::from_utf8_lossy(output.stdout.as_slice());
- !state.trim_start().starts_with('Z')
-}
-
-#[cfg(windows)]
-fn process_running_for_pid(pid: u32) -> bool {
- Command::new("tasklist")
- .args(["/FI", format!("PID eq {pid}").as_str()])
- .stdout(Stdio::piped())
- .stderr(Stdio::null())
- .output()
- .map(|output| {
- output.status.success()
- && String::from_utf8_lossy(output.stdout.as_slice())
- .contains(pid.to_string().as_str())
- })
- .unwrap_or(false)
-}
-
-#[cfg(not(any(unix, windows)))]
-fn process_running_for_pid(_pid: u32) -> bool {
- false
-}
-
-#[cfg(unix)]
-fn terminate_process(pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- signal_process(pid, "-TERM")
-}
-
-#[cfg(unix)]
-fn force_kill_process(pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- signal_process(pid, "-KILL")
-}
-
-#[cfg(unix)]
-fn signal_process(pid: u32, signal: &str) -> Result<(), RadrootsRuntimeManagerError> {
- signal_process_with(pid, signal, execute_signal_command)
-}
-
-#[cfg(unix)]
-fn execute_signal_command(pid: u32, signal: &str) -> std::io::Result<ExitStatus> {
- Command::new("kill")
- .args([signal, pid.to_string().as_str()])
- .stdout(Stdio::null())
- .stderr(Stdio::piped())
- .status()
-}
-
-#[cfg(unix)]
-fn signal_process_with(
- pid: u32,
- signal: &str,
- runner: fn(u32, &str) -> std::io::Result<ExitStatus>,
-) -> Result<(), RadrootsRuntimeManagerError> {
- let status = runner(pid, signal).map_err(|source| {
- RadrootsRuntimeManagerError::ExecuteProcessSignal {
- kind: source.kind(),
- }
- })?;
- if status.success() {
- Ok(())
- } else {
- Err(RadrootsRuntimeManagerError::StopProcess)
- }
-}
-
-#[cfg(windows)]
-fn terminate_process(pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- force_kill_process(pid)
-}
-
-#[cfg(windows)]
-fn force_kill_process(pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- let status = Command::new("taskkill")
- .args(["/PID", pid.to_string().as_str(), "/T", "/F"])
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .status()
- .map_err(|source| RadrootsRuntimeManagerError::ExecuteProcessSignal {
- kind: source.kind(),
- })?;
- if status.success() {
- Ok(())
- } else {
- Err(RadrootsRuntimeManagerError::StopProcess)
- }
-}
-
-#[cfg(not(any(unix, windows)))]
-fn terminate_process(_pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- Err(RadrootsRuntimeManagerError::StopProcess)
-}
-
-#[cfg(not(any(unix, windows)))]
-fn force_kill_process(_pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- Err(RadrootsRuntimeManagerError::StopProcess)
-}
-
-#[cfg(test)]
-mod tests {
- use std::fs;
- #[cfg(unix)]
- use std::fs::File;
- use std::io;
- use std::path::Path;
- #[cfg(unix)]
- use std::process::ExitStatus;
- #[cfg(unix)]
- use std::thread;
- use std::time::Duration;
-
- use radroots_runtime_paths::{
- InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver,
- RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId,
- };
- use tempfile::tempdir;
-
- use super::{
- ExistingPathKind, ManagedRuntimeArtifactName, ensure_instance_layout, ensure_parent_dir,
- extract_binary_archive, find_binary_with_name, install_binary, open_log_file,
- process_running, read_pid, remove_instance_artifacts, remove_path_from_state,
- remove_path_if_exists, start_process_with, stop_process, stop_process_for_pid,
- write_instance_config,
- };
- #[cfg(unix)]
- use super::{
- apply_mode, force_kill_process, process_running_for_pid,
- process_running_state_from_ps_output, set_executable_mode, signal_process,
- signal_process_with, start_process, terminate_process,
- };
- use crate::error::RadrootsRuntimeManagerError;
- use crate::paths::{ManagedRuntimeInstancePaths, resolve_instance_paths, resolve_shared_paths};
-
- fn sample_paths(root: &Path) -> ManagedRuntimeInstancePaths {
- fn context(root: &Path, service: &str) -> RuntimeContext {
- RuntimeContext::resolve(
- &RadrootsPathResolver::new(
- RadrootsPlatform::Linux,
- RadrootsHostEnvironment::default(),
- ),
- RuntimeContextBootstrap::new(
- RadrootsPathProfile::RepoLocal,
- Some(root.to_path_buf()),
- RuntimeContextSource::BootstrapCli,
- RuntimeContextSource::BootstrapCli,
- )
- .expect("bootstrap"),
- ServiceId::new(service).expect("service"),
- InstanceId::new("local").expect("instance"),
- )
- .expect("context")
- }
-
- let shared = resolve_shared_paths(&context(root, "runtime-manager"));
- resolve_instance_paths(&shared, &context(root, "radrootsd"))
- }
-
- fn artifact(value: &str) -> ManagedRuntimeArtifactName {
- ManagedRuntimeArtifactName::new(value).expect("artifact name")
- }
-
- fn assert_safe_error(err: &RadrootsRuntimeManagerError, expected: &str, forbidden: &[&str]) {
- use std::error::Error as _;
-
- let rendered = format!("{err} {err:?}");
- assert!(
- rendered.contains(expected),
- "expected `{rendered}` to contain `{expected}`"
- );
- for part in forbidden {
- assert!(
- !rendered.contains(part),
- "expected `{rendered}` not to contain `{part}`"
- );
- }
- assert!(err.source().is_none());
- }
-
- #[cfg(unix)]
- fn exit_status(code: i32) -> ExitStatus {
- std::process::Command::new("sh")
- .args(["-c", &format!("exit {code}")])
- .status()
- .expect("exit status")
- }
-
- #[cfg(unix)]
- fn output_with_status(status: ExitStatus, stdout: &[u8]) -> std::process::Output {
- std::process::Output {
- status,
- stdout: stdout.to_vec(),
- stderr: Vec::new(),
- }
- }
-
- fn ok_remove_path(_path: &Path) -> io::Result<()> {
- Ok(())
- }
-
- fn deny_remove_path(_path: &Path) -> io::Result<()> {
- Err(io::Error::new(
- io::ErrorKind::PermissionDenied,
- "remove path denied",
- ))
- }
-
- fn ok_runtime_signal(_pid: u32) -> Result<(), RadrootsRuntimeManagerError> {
- Ok(())
- }
-
- fn noop_runtime_sleep(_duration: Duration) {}
-
- fn runtime_is_stopped(_pid: u32) -> bool {
- false
- }
-
- fn runtime_is_running(_pid: u32) -> bool {
- true
- }
-
- #[test]
- fn layout_creates_only_manager_owned_install_and_tracking_roots() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- assert!(paths.install_dir().is_dir());
- assert!(paths.logs_dir().is_dir());
- assert!(paths.run_dir().is_dir());
- assert!(!paths.config_dir().exists());
- assert!(!paths.state_dir().exists());
- assert!(!paths.secrets_dir().exists());
- }
-
- #[test]
- fn install_binary_copies_source_into_install_dir() {
- let dir = tempdir().expect("tempdir");
- let source = dir.path().join("radrootsd");
- fs::write(&source, "#!/bin/sh\nexit 0\n").expect("source");
- let paths = sample_paths(dir.path());
- let installed = install_binary(&source, &paths, &artifact("radrootsd")).expect("install");
- assert!(installed.is_file());
- assert!(installed.starts_with(paths.install_dir()));
- }
-
- #[test]
- fn artifact_names_reject_absolute_parent_and_multicomponent_escapes() {
- for invalid in [
- "",
- "/tmp/escape",
- "../escape",
- "nested/escape",
- r"nested\escape",
- ".",
- "..",
- " secret",
- ] {
- let err = ManagedRuntimeArtifactName::new(invalid).expect_err("reject artifact name");
- if invalid.is_empty() {
- assert_safe_error(&err, "artifact name is invalid", &[]);
- } else {
- assert_safe_error(&err, "artifact name is invalid", &[invalid]);
- }
- }
-
- let maximum = format!("a{}", "b".repeat(127));
- assert!(ManagedRuntimeArtifactName::new(&maximum).is_ok());
- assert!(ManagedRuntimeArtifactName::new(&format!("{maximum}c")).is_err());
- }
-
- #[cfg(unix)]
- #[test]
- fn extract_binary_archive_unpacks_tar_gz() {
- let dir = tempdir().expect("tempdir");
- let archive_root = dir.path().join("archive");
- fs::create_dir_all(archive_root.join("bin")).expect("archive dir");
- fs::write(archive_root.join("bin/radrootsd"), "#!/bin/sh\nexit 0\n").expect("binary");
- let archive_path = dir.path().join("radrootsd.tar.gz");
- let file = File::create(&archive_path).expect("archive file");
- let encoder = flate2::write::GzEncoder::new(file, flate2::Compression::default());
- let mut builder = tar::Builder::new(encoder);
- builder
- .append_path_with_name(
- archive_root.join("bin/radrootsd"),
- "radrootsd/bin/radrootsd",
- )
- .expect("append path");
- builder.finish().expect("finish archive");
- let encoder = builder.into_inner().expect("into encoder");
- encoder.finish().expect("finish gzip");
-
- let paths = sample_paths(dir.path());
- let installed =
- extract_binary_archive(&archive_path, "tar.gz", &paths, &artifact("radrootsd"))
- .expect("extract");
- assert!(installed.is_file());
- }
-
- #[cfg(unix)]
- #[test]
- fn extract_binary_archive_uses_direct_binary_when_present_at_root() {
- let dir = tempdir().expect("tempdir");
- let archive_root = dir.path().join("archive");
- fs::create_dir_all(&archive_root).expect("archive dir");
- fs::write(archive_root.join("radrootsd"), "#!/bin/sh\nexit 0\n").expect("binary");
- let archive_path = dir.path().join("radrootsd.tar.gz");
- let file = File::create(&archive_path).expect("archive file");
- let encoder = flate2::write::GzEncoder::new(file, flate2::Compression::default());
- let mut builder = tar::Builder::new(encoder);
- builder
- .append_path_with_name(archive_root.join("radrootsd"), "radrootsd")
- .expect("append path");
- builder.finish().expect("finish archive");
- let encoder = builder.into_inner().expect("into encoder");
- encoder.finish().expect("finish gzip");
-
- let paths = sample_paths(dir.path());
- let installed =
- extract_binary_archive(&archive_path, "tar.gz", &paths, &artifact("radrootsd"))
- .expect("extract");
- assert_eq!(installed, paths.install_dir().join("radrootsd"));
- }
-
- #[cfg(unix)]
- #[test]
- fn start_and_stop_process_manage_pid_file() {
- let dir = tempdir().expect("tempdir");
- let binary = dir.path().join("sleepy.sh");
- fs::write(&binary, "#!/bin/sh\nexec sleep 30\n").expect("script");
- let paths = sample_paths(dir.path());
- install_binary(&binary, &paths, &artifact("sleepy.sh")).expect("install");
- let envs = vec![("RADROOTS_RUNTIME_MANAGER_TEST".to_owned(), "1".to_owned())];
- let pid = start_process(&paths, &artifact("sleepy.sh"), &Vec::new(), &envs).expect("start");
- assert!(pid > 0);
- thread::sleep(Duration::from_millis(100));
- assert!(paths.pid_file_path().is_file());
- assert!(process_running(&paths).expect("running"));
- assert!(stop_process(&paths).expect("stop"));
- assert!(!paths.pid_file_path().exists());
- }
-
- #[test]
- fn remove_instance_artifacts_removes_layout_roots() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::create_dir_all(paths.state_dir()).expect("canonical state sentinel");
- fs::create_dir_all(paths.secrets_dir()).expect("canonical secrets sentinel");
- fs::write(paths.state_dir().join("state.sqlite"), "state").expect("state sentinel");
- fs::write(paths.secrets_dir().join("identity.secret"), "secret").expect("secret sentinel");
- remove_instance_artifacts(&paths).expect("remove");
- assert!(!paths.install_dir().exists());
- assert!(!paths.logs_dir().exists());
- assert!(!paths.run_dir().exists());
- assert!(paths.state_dir().join("state.sqlite").is_file());
- assert!(paths.secrets_dir().join("identity.secret").is_file());
- }
-
- #[test]
- fn ensure_instance_layout_reports_directory_errors() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- fs::create_dir_all(paths.install_dir().parent().expect("install parent")).expect("parent");
- fs::write(paths.install_dir(), "occupied").expect("file");
-
- let err = ensure_instance_layout(&paths).expect_err("file path should fail");
- assert_safe_error(
- &err,
- "create managed runtime directory",
- &[paths.install_dir().to_string_lossy().as_ref()],
- );
- }
-
- #[test]
- fn install_binary_reports_copy_errors() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- let err = install_binary(dir.path().join("missing"), &paths, &artifact("radrootsd"))
- .expect_err("missing source should fail");
- assert_safe_error(
- &err,
- "copy managed runtime binary",
- &[
- dir.path().join("missing").to_string_lossy().as_ref(),
- paths
- .install_dir()
- .join("radrootsd")
- .to_string_lossy()
- .as_ref(),
- ],
- );
- }
-
- #[test]
- fn extract_binary_archive_reports_unsupported_format() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- let archive_path = dir.path().join("radrootsd.zip");
-
- let err = extract_binary_archive(&archive_path, "zip", &paths, &artifact("radrootsd"))
- .expect_err("unsupported archive format should fail");
- assert_safe_error(
- &err,
- "archive format is unsupported",
- &[archive_path.to_string_lossy().as_ref(), "zip"],
- );
- }
-
- #[test]
- fn extract_binary_archive_reports_missing_archive() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- let archive_path = dir.path().join("missing.tar.gz");
-
- let err = extract_binary_archive(&archive_path, "tar.gz", &paths, &artifact("radrootsd"))
- .expect_err("missing archive should fail");
- assert_safe_error(
- &err,
- "read managed runtime file",
- &[archive_path.to_string_lossy().as_ref()],
- );
- }
-
- #[cfg(unix)]
- #[test]
- fn extract_binary_archive_reports_missing_binary_in_archive() {
- let dir = tempdir().expect("tempdir");
- let archive_root = dir.path().join("archive");
- fs::create_dir_all(archive_root.join("bin")).expect("archive dir");
- fs::write(archive_root.join("bin/other"), "#!/bin/sh\nexit 0\n").expect("binary");
- let archive_path = dir.path().join("radrootsd.tar.gz");
- let file = File::create(&archive_path).expect("archive file");
- let encoder = flate2::write::GzEncoder::new(file, flate2::Compression::default());
- let mut builder = tar::Builder::new(encoder);
- builder
- .append_path_with_name(archive_root.join("bin/other"), "radrootsd/bin/other")
- .expect("append path");
- builder.finish().expect("finish archive");
- let encoder = builder.into_inner().expect("into encoder");
- encoder.finish().expect("finish gzip");
-
- let paths = sample_paths(dir.path());
- let err = extract_binary_archive(&archive_path, "tar.gz", &paths, &artifact("radrootsd"))
- .expect_err("archive should not resolve missing binary");
- assert_safe_error(
- &err,
- "read managed runtime file",
- &[
- paths
- .install_dir()
- .join("radrootsd")
- .to_string_lossy()
- .as_ref(),
- archive_path.to_string_lossy().as_ref(),
- ],
- );
- }
-
- #[cfg(unix)]
- #[test]
- fn extract_binary_archive_reports_unpack_errors() {
- let dir = tempdir().expect("tempdir");
- let archive_path = dir.path().join("invalid.tar.gz");
- fs::write(&archive_path, "not a gzip archive").expect("write archive");
- let paths = sample_paths(dir.path());
-
- let err = extract_binary_archive(&archive_path, "tar.gz", &paths, &artifact("radrootsd"))
- .expect_err("invalid archive should fail");
- assert_safe_error(
- &err,
- "unpack managed runtime archive",
- &[
- archive_path.to_string_lossy().as_ref(),
- "not a gzip archive",
- ],
- );
- }
-
- #[test]
- fn write_instance_config_is_context_bound_and_reports_redacted_errors() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- let config = paths.config_path();
- write_instance_config(&paths, "enabled = true").expect("write config");
- assert_eq!(
- fs::read_to_string(&config).expect("read config"),
- "enabled = true"
- );
- assert!(!paths.secrets_dir().exists());
-
- fs::remove_file(&config).expect("remove config");
- fs::create_dir(&config).expect("occupy config path");
- let err = write_instance_config(&paths, "credential = 'secret-value'")
- .expect_err("directory write should fail");
- assert_safe_error(
- &err,
- "write managed runtime config",
- &[config.to_string_lossy().as_ref(), "secret-value"],
- );
- }
-
- #[test]
- fn start_process_reports_spawn_errors() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- let binary = paths.install_dir().join("unavailable");
- let err = start_process_with(
- &binary,
- &[],
- &[],
- &paths,
- |_binary, _args, _envs, _stdout, _stderr| {
- Err(io::Error::new(
- io::ErrorKind::PermissionDenied,
- "injected spawn denial",
- ))
- },
- )
- .expect_err("injected spawn failure");
- assert_safe_error(
- &err,
- "spawn managed runtime process",
- &[binary.to_string_lossy().as_ref(), "injected spawn denial"],
- );
- }
-
- #[cfg(unix)]
- #[test]
- fn start_process_reports_pid_file_write_errors() {
- let dir = tempdir().expect("tempdir");
- let binary = dir.path().join("sleepy.sh");
- fs::write(&binary, "#!/bin/sh\nexec sleep 1\n").expect("script");
- let paths = sample_paths(dir.path());
- fs::create_dir_all(paths.pid_file_path()).expect("occupy pid path");
- install_binary(&binary, &sample_paths(dir.path()), &artifact("sleepy.sh"))
- .expect("install");
-
- let err = start_process(&paths, &artifact("sleepy.sh"), &[], &[])
- .expect_err("pid file write should fail");
- assert_safe_error(
- &err,
- "write managed runtime pid",
- &[paths.pid_file_path().to_string_lossy().as_ref()],
- );
- }
-
- #[test]
- fn process_running_and_stop_process_handle_missing_pid_file() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
-
- assert!(!process_running(&paths).expect("missing pid should be false"));
- assert!(!stop_process(&paths).expect("missing pid stop should be false"));
- }
-
- #[test]
- fn process_running_reports_invalid_pid_file() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::write(paths.pid_file_path(), "not-a-pid").expect("write pid");
-
- let err = process_running(&paths).expect_err("invalid pid should fail");
- assert_safe_error(
- &err,
- "managed runtime pid is malformed",
- &[
- paths.pid_file_path().to_string_lossy().as_ref(),
- "not-a-pid",
- ],
- );
- }
-
- #[test]
- fn stop_process_clears_stale_pid_file() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::write(paths.pid_file_path(), "999999").expect("write pid");
-
- assert!(!stop_process(&paths).expect("stale pid should return false"));
- assert!(!paths.pid_file_path().exists());
- }
-
- #[test]
- fn stop_process_for_pid_uses_force_kill_after_terminate_attempts() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::write(paths.pid_file_path(), "42").expect("write pid");
-
- let mut polls = 0_u32;
- let mut is_running = |_pid| {
- polls += 1;
- polls <= 20
- };
- let mut terminate = ok_runtime_signal;
- let mut force_kill = ok_runtime_signal;
- let mut sleep = noop_runtime_sleep;
- let stopped = stop_process_for_pid(
- &paths,
- 42,
- &mut is_running,
- &mut terminate,
- &mut force_kill,
- &mut sleep,
- )
- .expect("force-kill path should stop");
-
- assert!(stopped);
- assert!(!paths.pid_file_path().exists());
- assert_eq!(polls, 21);
- }
-
- #[test]
- fn stop_process_for_pid_stops_after_terminate_poll() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::write(paths.pid_file_path(), "42").expect("write pid");
-
- let mut is_running = runtime_is_stopped;
- let mut terminate = ok_runtime_signal;
- let mut force_kill = ok_runtime_signal;
- let mut sleep = noop_runtime_sleep;
- let stopped = stop_process_for_pid(
- &paths,
- 42,
- &mut is_running,
- &mut terminate,
- &mut force_kill,
- &mut sleep,
- )
- .expect("terminate poll should stop");
-
- assert!(stopped);
- assert!(!paths.pid_file_path().exists());
- }
-
- #[test]
- fn stop_process_for_pid_reports_failure_after_force_kill_attempts() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::write(paths.pid_file_path(), "42").expect("write pid");
-
- let mut sleeps = 0_u32;
- let mut is_running = runtime_is_running;
- let mut terminate = ok_runtime_signal;
- let mut force_kill = ok_runtime_signal;
- let mut sleep = |_duration| {
- sleeps += 1;
- };
- let err = stop_process_for_pid(
- &paths,
- 42,
- &mut is_running,
- &mut terminate,
- &mut force_kill,
- &mut sleep,
- )
- .expect_err("force-kill exhaustion should fail");
-
- assert_safe_error(&err, "managed runtime process did not stop", &["42"]);
- assert_eq!(sleeps, 40);
- assert!(paths.pid_file_path().exists());
- }
-
- #[test]
- fn ensure_parent_dir_without_parent_is_a_noop() {
- ensure_parent_dir(Path::new("/")).expect("root path should have no parent");
- }
-
- #[test]
- fn ensure_parent_dir_reports_directory_creation_errors() {
- let dir = tempdir().expect("tempdir");
- let file_parent = dir.path().join("occupied");
- fs::write(&file_parent, "file").expect("parent file");
-
- let err =
- ensure_parent_dir(&file_parent.join("child")).expect_err("file parent should fail");
- assert_safe_error(
- &err,
- "create managed runtime directory",
- &[file_parent.to_string_lossy().as_ref()],
- );
- }
-
- #[test]
- fn find_binary_with_name_handles_nested_and_missing_files() {
- let dir = tempdir().expect("tempdir");
- fs::create_dir_all(dir.path().join("nested")).expect("nested dir");
- fs::write(dir.path().join("nested/radrootsd"), "binary").expect("binary");
-
- assert_eq!(
- find_binary_with_name(dir.path(), "radrootsd"),
- Some(dir.path().join("nested/radrootsd"))
- );
- assert_eq!(find_binary_with_name(dir.path(), "missing"), None);
- }
-
- #[test]
- fn open_log_file_creates_file_and_reports_directory_errors() {
- let dir = tempdir().expect("tempdir");
- let file_path = dir.path().join("logs/stdout.log");
- let file = open_log_file(&file_path).expect("open log");
- drop(file);
- assert!(file_path.is_file());
-
- let bad_path = dir.path().join("bad");
- fs::create_dir(&bad_path).expect("create dir");
- let err = open_log_file(&bad_path).expect_err("directory open should fail");
- assert_safe_error(
- &err,
- "open managed runtime log",
- &[bad_path.to_string_lossy().as_ref()],
- );
- }
-
- #[test]
- fn read_pid_handles_empty_missing_and_read_error_cases() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
-
- assert_eq!(read_pid(&paths).expect("missing pid"), None);
-
- ensure_instance_layout(&paths).expect("layout");
- fs::write(paths.pid_file_path(), " ").expect("write pid");
- assert_eq!(read_pid(&paths).expect("empty pid"), None);
-
- fs::remove_file(paths.pid_file_path()).expect("remove pid file");
- fs::create_dir(paths.pid_file_path()).expect("occupy pid path");
- let err = read_pid(&paths).expect_err("directory pid file should fail");
- assert_safe_error(
- &err,
- "read managed runtime pid",
- &[paths.pid_file_path().to_string_lossy().as_ref()],
- );
- }
-
- #[test]
- fn remove_path_if_exists_handles_files_directories_and_missing_paths() {
- let dir = tempdir().expect("tempdir");
- let file_path = dir.path().join("file.txt");
- let dir_path = dir.path().join("subdir");
- fs::write(&file_path, "data").expect("file");
- fs::create_dir(&dir_path).expect("dir");
-
- remove_path_if_exists(&file_path).expect("remove file");
- remove_path_if_exists(&dir_path).expect("remove dir");
- remove_path_if_exists(dir.path().join("missing").as_path()).expect("remove missing");
-
- assert!(!file_path.exists());
- assert!(!dir_path.exists());
- }
-
- #[test]
- fn remove_path_from_state_reports_dir_file_and_metadata_errors() {
- let dir = tempdir().expect("tempdir");
- let dir_path = dir.path().join("subdir");
- let file_path = dir.path().join("file.txt");
- let metadata_path = dir.path().join("metadata");
- ok_remove_path(Path::new("/")).expect("noop remove path");
-
- let dir_err = remove_path_from_state(
- &dir_path,
- Ok(Some(ExistingPathKind::Directory)),
- deny_remove_path,
- ok_remove_path,
- )
- .expect_err("directory removal should fail");
- assert_safe_error(
- &dir_err,
- "remove manager-owned runtime path",
- &[dir_path.to_string_lossy().as_ref(), "remove path denied"],
- );
-
- let file_err = remove_path_from_state(
- &file_path,
- Ok(Some(ExistingPathKind::File)),
- ok_remove_path,
- deny_remove_path,
- )
- .expect_err("file removal should fail");
- assert_safe_error(
- &file_err,
- "remove manager-owned runtime path",
- &[file_path.to_string_lossy().as_ref(), "remove path denied"],
- );
-
- let metadata_err = remove_path_from_state(
- &metadata_path,
- Err(io::Error::new(
- io::ErrorKind::PermissionDenied,
- "metadata lookup failed",
- )),
- ok_remove_path,
- ok_remove_path,
- )
- .expect_err("metadata lookup should fail");
- assert_safe_error(
- &metadata_err,
- "read managed runtime file",
- &[
- metadata_path.to_string_lossy().as_ref(),
- "metadata lookup failed",
- ],
- );
- }
-
- #[test]
- fn remove_pid_file_reports_directory_errors() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- ensure_instance_layout(&paths).expect("layout");
- fs::create_dir(paths.pid_file_path()).expect("occupy pid path");
-
- let err = super::remove_pid_file(&paths).expect_err("directory pid path should fail");
- assert_safe_error(
- &err,
- "remove manager-owned runtime path",
- &[paths.pid_file_path().to_string_lossy().as_ref()],
- );
- }
-
- #[test]
- fn remove_pid_file_accepts_missing_pid_paths() {
- let dir = tempdir().expect("tempdir");
- let paths = sample_paths(dir.path());
- super::remove_pid_file(&paths).expect("missing pid file should be ignored");
- }
-
- #[cfg(unix)]
- #[test]
- fn executable_mode_reports_missing_path_errors() {
- let dir = tempdir().expect("tempdir");
- let missing = dir.path().join("missing");
-
- let err = set_executable_mode(&missing).expect_err("missing executable should fail");
- assert_safe_error(
- &err,
- "read managed runtime file",
- &[missing.to_string_lossy().as_ref()],
- );
- }
-
- #[cfg(unix)]
- #[test]
- fn apply_mode_reports_set_permissions_errors() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("radrootsd");
- fs::write(&path, "binary").expect("binary");
-
- let err = apply_mode(&path, 0o755, |_path, _permissions| {
- Err(io::Error::new(
- io::ErrorKind::PermissionDenied,
- "set permissions failed",
- ))
- })
- .expect_err("set permissions should fail");
- assert_safe_error(
- &err,
- "set managed runtime file permissions",
- &[path.to_string_lossy().as_ref(), "set permissions failed"],
- );
- }
-
- #[cfg(unix)]
- #[test]
- fn signal_helpers_cover_failure_paths() {
- let missing_pid = 999_999_u32;
- assert!(!process_running_for_pid(missing_pid));
-
- let err = terminate_process(missing_pid).expect_err("terminate should fail");
- assert_safe_error(&err, "managed runtime process did not stop", &["999999"]);
-
- let err = force_kill_process(missing_pid).expect_err("force kill should fail");
- assert_safe_error(&err, "managed runtime process did not stop", &["999999"]);
-
- let err = signal_process(missing_pid, "-BOGUS").expect_err("invalid signal should fail");
- assert_safe_error(&err, "managed runtime process did not stop", &["999999"]);
- }
-
- #[cfg(unix)]
- #[test]
- fn signal_process_with_reports_execution_errors() {
- let err = signal_process_with(42, "-TERM", |_pid, _signal| {
- Err(io::Error::new(
- io::ErrorKind::NotFound,
- "kill executable missing",
- ))
- })
- .expect_err("signal execution should fail");
- assert_safe_error(
- &err,
- "signal managed runtime process",
- &["42", "-TERM", "kill executable missing"],
- );
- }
-
- #[cfg(unix)]
- #[test]
- fn process_running_state_from_ps_output_handles_non_success_and_zombies() {
- assert!(process_running_state_from_ps_output(output_with_status(
- exit_status(1),
- b"",
- )));
- assert!(!process_running_state_from_ps_output(output_with_status(
- exit_status(0),
- b"Z+",
- )));
- assert!(process_running_state_from_ps_output(output_with_status(
- exit_status(0),
- b"S+",
- )));
- }
-}
diff --git a/crates/runtime_manager/src/managed.rs b/crates/runtime_manager/src/managed.rs
@@ -1,67 +1,61 @@
use core::fmt;
-use std::path::Path;
use radroots_runtime_distribution::HardenedServiceTarget;
-use radroots_runtime_paths::{RadrootsPathProfile, RuntimeContext, RuntimeContextSource};
+use radroots_runtime_paths::{InstanceId, RadrootsPathProfile, ServiceId};
-use crate::paths::resolve_shared_paths;
use crate::{
- BootstrapRuntimeContract, ManagedRuntimeHealthState, ManagedRuntimeInstallState,
- ManagedRuntimeInstancePaths, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry,
ManagementModeContract, RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError,
- load_registry,
};
+/// Validated metadata-only runtime-management context.
+///
+/// The context owns no filesystem path, registry, process, artifact, or
+/// lifecycle capability. Its fields are private so a caller cannot bypass the
+/// exact contract validation performed by [`ManagedRuntimeContext::new`].
+///
+/// ```compile_fail
+/// use radroots_runtime_manager::ManagedRuntimeContext;
+///
+/// let _ = ManagedRuntimeContext {
+/// contract: todo!(),
+/// profile: todo!(),
+/// management_mode: String::new(),
+/// };
+/// ```
#[derive(Clone)]
pub struct ManagedRuntimeContext {
contract: RadrootsRuntimeManagementContract,
- manager_context: RuntimeContext,
- shared_paths: crate::ManagedRuntimeSharedPaths,
- registry: ManagedRuntimeInstanceRegistry,
+ profile: RadrootsPathProfile,
+ management_mode: String,
}
impl ManagedRuntimeContext {
- #[must_use]
- pub fn contract(&self) -> &RadrootsRuntimeManagementContract {
- &self.contract
+ pub fn new(
+ contract: RadrootsRuntimeManagementContract,
+ profile: RadrootsPathProfile,
+ ) -> Result<Self, RadrootsRuntimeManagerError> {
+ crate::validate_hardened_management_contract(&contract)?;
+ let management_mode = active_management_mode_for_profile(&contract, profile)?.to_owned();
+ Ok(Self {
+ contract,
+ profile,
+ management_mode,
+ })
}
#[must_use]
- pub fn manager_context(&self) -> &RuntimeContext {
- &self.manager_context
+ pub fn contract(&self) -> &RadrootsRuntimeManagementContract {
+ &self.contract
}
#[must_use]
- pub fn shared_paths(&self) -> &crate::ManagedRuntimeSharedPaths {
- &self.shared_paths
+ pub const fn profile(&self) -> RadrootsPathProfile {
+ self.profile
}
#[must_use]
- pub fn registry(&self) -> &ManagedRuntimeInstanceRegistry {
- &self.registry
- }
-
- pub fn register_instance(
- &mut self,
- runtime_context: &RuntimeContext,
- _install_state: ManagedRuntimeInstallState,
- ) -> Result<(), RadrootsRuntimeManagerError> {
- ensure_context_scope(&self.manager_context, runtime_context)?;
- match self.contract.service_targets.get(runtime_context.service()) {
- Some(_) => Err(RadrootsRuntimeManagerError::MetadataOnlyServiceTarget),
- None => Err(RadrootsRuntimeManagerError::UnsupportedServiceTarget),
- }
- }
-
- pub fn remove_instance(
- &mut self,
- runtime_context: &RuntimeContext,
- ) -> Result<Option<ManagedRuntimeInstanceRecord>, RadrootsRuntimeManagerError> {
- ensure_context_scope(&self.manager_context, runtime_context)?;
- match self.contract.service_targets.get(runtime_context.service()) {
- Some(_) => Err(RadrootsRuntimeManagerError::MetadataOnlyServiceTarget),
- None => Err(RadrootsRuntimeManagerError::UnsupportedServiceTarget),
- }
+ pub fn management_mode(&self) -> &str {
+ &self.management_mode
}
}
@@ -69,86 +63,53 @@ impl fmt::Debug for ManagedRuntimeContext {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ManagedRuntimeContext")
- .field("manager_context", &self.manager_context)
- .field("shared_paths", &self.shared_paths)
- .field("registry", &"[redacted]")
+ .field("profile", &self.profile)
+ .field("management_mode", &self.management_mode)
.finish_non_exhaustive()
}
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ManagedRuntimeGroup {
- ActiveManagedTarget,
- DefinedManagedTarget,
- BootstrapOnly,
- Unknown,
-}
-
-impl ManagedRuntimeGroup {
- #[must_use]
- 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",
- }
- }
-
- #[must_use]
- 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",
- }
- }
-}
-
-/// A sealed, internally cross-bound management target.
+/// Sealed metadata for one explicitly selected Myc or RHI instance.
+///
+/// The target deliberately carries no resolved runtime paths and exposes no
+/// lifecycle, registry, filesystem, process, or artifact capability.
///
/// ```compile_fail
/// use radroots_runtime_manager::ManagedRuntimeTarget;
///
/// let _ = ManagedRuntimeTarget {
-/// context: todo!(),
-/// instance_source: todo!(),
-/// runtime_group: todo!(),
-/// management_mode: None,
-/// mode_contract: None,
-/// bootstrap: None,
-/// instance_record: None,
-/// predicted_paths: None,
+/// service_id: todo!(),
+/// instance_id: todo!(),
+/// profile: todo!(),
+/// service_target: todo!(),
+/// management_mode: String::new(),
+/// mode_contract: todo!(),
/// };
/// ```
#[derive(Clone)]
pub struct ManagedRuntimeTarget {
- context: RuntimeContext,
- instance_source: RuntimeContextSource,
- runtime_group: ManagedRuntimeGroup,
+ service_id: ServiceId,
+ instance_id: InstanceId,
+ profile: RadrootsPathProfile,
service_target: HardenedServiceTarget,
- management_mode: Option<String>,
- mode_contract: Option<ManagementModeContract>,
- bootstrap: Option<BootstrapRuntimeContract>,
- instance_record: Option<ManagedRuntimeInstanceRecord>,
- predicted_paths: Option<ManagedRuntimeInstancePaths>,
+ management_mode: String,
+ mode_contract: ManagementModeContract,
}
impl ManagedRuntimeTarget {
#[must_use]
- pub fn context(&self) -> &RuntimeContext {
- &self.context
+ pub fn service_id(&self) -> &ServiceId {
+ &self.service_id
}
#[must_use]
- pub fn instance_source(&self) -> RuntimeContextSource {
- self.instance_source
+ pub fn instance_id(&self) -> &InstanceId {
+ &self.instance_id
}
#[must_use]
- pub fn runtime_group(&self) -> ManagedRuntimeGroup {
- self.runtime_group
+ pub const fn profile(&self) -> RadrootsPathProfile {
+ self.profile
}
#[must_use]
@@ -157,28 +118,13 @@ impl ManagedRuntimeTarget {
}
#[must_use]
- pub fn management_mode(&self) -> Option<&str> {
- self.management_mode.as_deref()
- }
-
- #[must_use]
- pub fn mode_contract(&self) -> Option<&ManagementModeContract> {
- self.mode_contract.as_ref()
+ pub fn management_mode(&self) -> &str {
+ &self.management_mode
}
#[must_use]
- pub fn bootstrap(&self) -> Option<&BootstrapRuntimeContract> {
- self.bootstrap.as_ref()
- }
-
- #[must_use]
- pub fn instance_record(&self) -> Option<&ManagedRuntimeInstanceRecord> {
- self.instance_record.as_ref()
- }
-
- #[must_use]
- pub fn predicted_paths(&self) -> Option<&ManagedRuntimeInstancePaths> {
- self.predicted_paths.as_ref()
+ pub fn mode_contract(&self) -> &ManagementModeContract {
+ &self.mode_contract
}
}
@@ -186,131 +132,43 @@ impl fmt::Debug for ManagedRuntimeTarget {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ManagedRuntimeTarget")
- .field("context", &self.context)
- .field("runtime_group", &self.runtime_group)
- .field("service_target", &self.service_target)
- .field("predicted_paths", &self.predicted_paths)
+ .field("service_id", &self.service_id)
+ .field("instance_id", &self.instance_id)
+ .field("profile", &self.profile)
+ .field("management_mode", &self.management_mode)
.finish_non_exhaustive()
}
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ManagedRuntimeInspectionAvailability {
- Success,
- Unconfigured,
- Unsupported,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeInspection<T> {
- pub availability: ManagedRuntimeInspectionAvailability,
- pub view: T,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ManagedRuntimeLifecycleAction {
- Install,
- Uninstall,
- Start,
- Stop,
- Restart,
- ConfigSet,
-}
-
-impl ManagedRuntimeLifecycleAction {
- #[must_use]
- pub fn as_str(self) -> &'static str {
- match self {
- Self::Install => "install",
- Self::Uninstall => "uninstall",
- Self::Start => "start",
- Self::Stop => "stop",
- Self::Restart => "restart",
- Self::ConfigSet => "config_set",
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeStatusInspection {
- pub runtime_id: String,
- pub instance_id: String,
- pub instance_source: RuntimeContextSource,
- pub runtime_group: String,
- pub management_posture: String,
- pub state: String,
- pub source: String,
- pub detail: String,
- pub management_mode: Option<String>,
- pub service_manager_integration: Option<bool>,
- pub uses_absolute_binary_paths: Option<bool>,
- pub preferred_cli_binding: Option<bool>,
- pub install_state: String,
- pub health_state: String,
- pub health_source: String,
- pub lifecycle_actions: Vec<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeLogsInspection {
- pub runtime_id: String,
- pub instance_id: String,
- pub instance_source: RuntimeContextSource,
- pub runtime_group: String,
- pub state: String,
- pub source: String,
- pub detail: String,
- pub stdout_log_present: bool,
- pub stderr_log_present: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeConfigInspection {
- pub runtime_id: String,
- pub instance_id: String,
- pub instance_source: RuntimeContextSource,
- pub runtime_group: String,
- pub state: String,
- pub source: String,
- pub detail: String,
- pub config_format: Option<String>,
- pub config_present: bool,
- pub requires_bootstrap_secret: Option<bool>,
- pub requires_config_bootstrap: Option<bool>,
- pub requires_signer_provider: Option<bool>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeActionInspection {
- pub action: String,
- pub runtime_id: String,
- pub instance_id: String,
- pub instance_source: RuntimeContextSource,
- pub runtime_group: String,
- pub state: String,
- pub source: String,
- pub detail: String,
- pub mutates_bindings: bool,
- pub next_step: Option<String>,
-}
+pub fn resolve_runtime_target(
+ context: &ManagedRuntimeContext,
+ service_id: ServiceId,
+ instance_id: InstanceId,
+) -> Result<ManagedRuntimeTarget, RadrootsRuntimeManagerError> {
+ let service_target = context
+ .contract
+ .service_targets
+ .get(&service_id)
+ .cloned()
+ .ok_or(RadrootsRuntimeManagerError::UnsupportedServiceTarget)?;
+ let mode_contract = context
+ .contract
+ .mode
+ .get(&context.management_mode)
+ .cloned()
+ .ok_or(RadrootsRuntimeManagerError::InvalidContract)?;
-pub fn load_management_context(
- contract: RadrootsRuntimeManagementContract,
- manager_context: RuntimeContext,
-) -> Result<ManagedRuntimeContext, RadrootsRuntimeManagerError> {
- crate::validate_hardened_management_contract(&contract)?;
- active_management_mode_for_profile(&contract, manager_context.profile())?;
- let shared_paths = resolve_shared_paths(&manager_context);
- let registry = load_registry(shared_paths.instance_registry_path())?;
- Ok(ManagedRuntimeContext {
- contract,
- manager_context,
- shared_paths,
- registry,
+ Ok(ManagedRuntimeTarget {
+ service_id,
+ instance_id,
+ profile: context.profile,
+ service_target,
+ management_mode: context.management_mode.clone(),
+ mode_contract,
})
}
-pub fn active_management_mode_for_profile(
+fn active_management_mode_for_profile(
contract: &RadrootsRuntimeManagementContract,
profile: RadrootsPathProfile,
) -> Result<&str, RadrootsRuntimeManagerError> {
@@ -329,892 +187,131 @@ pub fn active_management_mode_for_profile(
.ok_or(RadrootsRuntimeManagerError::UnsupportedProfile)
}
-pub fn resolve_runtime_target(
- context: &ManagedRuntimeContext,
- runtime_context: RuntimeContext,
-) -> Result<ManagedRuntimeTarget, RadrootsRuntimeManagerError> {
- ensure_context_scope(&context.manager_context, &runtime_context)?;
- let runtime_id = runtime_context.service().as_str();
- let runtime_group = runtime_group(&context.contract, runtime_id);
- let service_target = context
- .contract
- .service_targets
- .get(runtime_context.service())
- .cloned()
- .ok_or(RadrootsRuntimeManagerError::UnsupportedServiceTarget)?;
- let bootstrap = context.contract.bootstrap.get(runtime_id).cloned();
- let management_mode = Some(
- active_management_mode_for_profile(&context.contract, runtime_context.profile())?
- .to_owned(),
- );
- let mode_contract = management_mode
- .as_ref()
- .and_then(|mode_id| context.contract.mode.get(mode_id).cloned());
- let instance_record = None;
- let predicted_paths = None;
-
- Ok(ManagedRuntimeTarget {
- instance_source: runtime_context.sources().instance(),
- context: runtime_context,
- runtime_group,
- service_target,
- management_mode,
- mode_contract,
- bootstrap,
- instance_record,
- predicted_paths,
- })
-}
-
-fn ensure_context_scope(
- manager: &RuntimeContext,
- target: &RuntimeContext,
-) -> Result<(), RadrootsRuntimeManagerError> {
- if manager.profile() != target.profile()
- || context_roots(manager)
- .iter()
- .zip(context_roots(target))
- .any(|(left, right)| left != &right)
- {
- return Err(RadrootsRuntimeManagerError::RuntimeContextMismatch);
- }
- Ok(())
-}
-
-fn context_roots(context: &RuntimeContext) -> [&Path; 6] {
- let paths = context.paths();
- [
- instance_root(paths.config()),
- instance_root(paths.state()),
- instance_root(paths.cache()),
- instance_root(paths.logs()),
- instance_root(paths.run()),
- instance_root(paths.secrets()),
- ]
-}
-
-fn instance_root(path: &Path) -> &Path {
- path.parent()
- .and_then(Path::parent)
- .and_then(Path::parent)
- .expect("RuntimeContext service paths contain the sealed services/service/instance suffix")
-}
-
-#[must_use]
-pub fn inspect_runtime_status(
- target: &ManagedRuntimeTarget,
- lifecycle_actions: &[String],
-) -> ManagedRuntimeInspection<ManagedRuntimeStatusInspection> {
- let availability = managed_inspection_availability(target);
- let (health_state, health_source) = infer_health_state(target);
-
- ManagedRuntimeInspection {
- availability,
- view: ManagedRuntimeStatusInspection {
- runtime_id: target.context.service().to_string(),
- instance_id: target.context.instance().to_string(),
- instance_source: target.instance_source,
- runtime_group: target.runtime_group.as_str().to_owned(),
- management_posture: target.runtime_group.posture().to_owned(),
- state: status_state(target).to_owned(),
- source: "runtime management contract + typed instance registry".to_owned(),
- detail: status_detail(target),
- management_mode: target.management_mode.clone(),
- service_manager_integration: target
- .mode_contract
- .as_ref()
- .map(|mode| mode.service_manager_integration),
- uses_absolute_binary_paths: target
- .mode_contract
- .as_ref()
- .map(|mode| mode.uses_absolute_binary_paths),
- preferred_cli_binding: target
- .bootstrap
- .as_ref()
- .map(BootstrapRuntimeContract::preferred_cli_binding),
- install_state: target
- .instance_record
- .as_ref()
- .map(|record| install_state_label(record.install_state()))
- .unwrap_or_else(|| install_state_label(ManagedRuntimeInstallState::NotInstalled))
- .to_owned(),
- health_state: health_state.to_owned(),
- health_source: health_source.to_owned(),
- lifecycle_actions: if target.runtime_group == ManagedRuntimeGroup::ActiveManagedTarget {
- lifecycle_actions.to_vec()
- } else {
- Vec::new()
- },
- },
- }
-}
-
-#[must_use]
-pub fn inspect_runtime_logs(
- target: &ManagedRuntimeTarget,
-) -> ManagedRuntimeInspection<ManagedRuntimeLogsInspection> {
- let availability = managed_inspection_availability(target);
- let stdout_log_present = (availability == ManagedRuntimeInspectionAvailability::Success)
- && target
- .predicted_paths
- .as_ref()
- .is_some_and(|paths| paths.stdout_log_path().exists());
- let stderr_log_present = (availability == ManagedRuntimeInspectionAvailability::Success)
- && target
- .predicted_paths
- .as_ref()
- .is_some_and(|paths| paths.stderr_log_path().exists());
-
- ManagedRuntimeInspection {
- availability,
- view: ManagedRuntimeLogsInspection {
- runtime_id: target.context.service().to_string(),
- instance_id: target.context.instance().to_string(),
- instance_source: target.instance_source,
- runtime_group: target.runtime_group.as_str().to_owned(),
- state: availability_state(availability),
- source: "runtime management contract + manager-owned tracking".to_owned(),
- detail: logs_detail(target),
- stdout_log_present,
- stderr_log_present,
- },
- }
-}
-
-#[must_use]
-pub fn inspect_runtime_config(
- target: &ManagedRuntimeTarget,
-) -> ManagedRuntimeInspection<ManagedRuntimeConfigInspection> {
- let availability = managed_inspection_availability(target);
- let config_path = (availability == ManagedRuntimeInspectionAvailability::Success)
- .then_some(())
- .and(target.instance_record.as_ref())
- .and_then(|_| {
- target
- .predicted_paths
- .as_ref()
- .map(ManagedRuntimeInstancePaths::config_path)
- });
- let config_present = config_path.as_deref().is_some_and(Path::exists);
-
- ManagedRuntimeInspection {
- availability,
- view: ManagedRuntimeConfigInspection {
- runtime_id: target.context.service().to_string(),
- instance_id: target.context.instance().to_string(),
- instance_source: target.instance_source,
- runtime_group: target.runtime_group.as_str().to_owned(),
- state: match availability {
- ManagedRuntimeInspectionAvailability::Success if config_path.is_some() => {
- "ready".to_owned()
- }
- ManagedRuntimeInspectionAvailability::Success => "not_installed".to_owned(),
- other => availability_state(other),
- },
- source: "runtime context + typed instance registry".to_owned(),
- detail: config_detail(target, config_path.is_some()),
- config_format: Some(target.service_target.config_format().as_str().to_owned()),
- config_present,
- requires_bootstrap_secret: None,
- requires_config_bootstrap: Some(true),
- requires_signer_provider: None,
- },
- }
-}
-
-#[must_use]
-pub fn inspect_runtime_action(
- target: &ManagedRuntimeTarget,
- action: ManagedRuntimeLifecycleAction,
-) -> ManagedRuntimeInspection<ManagedRuntimeActionInspection> {
- let (availability, state, detail) = match target.runtime_group {
- ManagedRuntimeGroup::ActiveManagedTarget => (
- ManagedRuntimeInspectionAvailability::Unsupported,
- "deferred",
- format!(
- "runtime {} `{}` is not supported for this managed target",
- action.as_str().replace('_', " "),
- target.context.service()
- ),
- ),
- ManagedRuntimeGroup::DefinedManagedTarget => (
- ManagedRuntimeInspectionAvailability::Unsupported,
- "unsupported",
- format!(
- "runtime `{}` is only a defined future managed target; `{}` is not admitted in the current wave",
- target.context.service(),
- action.as_str().replace('_', " ")
- ),
- ),
- ManagedRuntimeGroup::BootstrapOnly => (
- ManagedRuntimeInspectionAvailability::Unsupported,
- "unsupported",
- format!(
- "runtime `{}` remains bootstrap_only; generic managed `{}` is not admitted",
- target.context.service(),
- action.as_str().replace('_', " ")
- ),
- ),
- ManagedRuntimeGroup::Unknown => (
- ManagedRuntimeInspectionAvailability::Unconfigured,
- "unknown_runtime",
- unknown_runtime_detail(target),
- ),
- };
-
- ManagedRuntimeInspection {
- availability,
- view: ManagedRuntimeActionInspection {
- action: action.as_str().to_owned(),
- runtime_id: target.context.service().to_string(),
- instance_id: target.context.instance().to_string(),
- instance_source: target.instance_source,
- runtime_group: target.runtime_group.as_str().to_owned(),
- state: state.to_owned(),
- source: "generic runtime-management command family".to_owned(),
- detail,
- mutates_bindings: false,
- next_step: None,
- },
- }
-}
-
-fn managed_inspection_availability(
- target: &ManagedRuntimeTarget,
-) -> ManagedRuntimeInspectionAvailability {
- match target.runtime_group {
- ManagedRuntimeGroup::Unknown => ManagedRuntimeInspectionAvailability::Unconfigured,
- ManagedRuntimeGroup::ActiveManagedTarget => ManagedRuntimeInspectionAvailability::Success,
- ManagedRuntimeGroup::DefinedManagedTarget | ManagedRuntimeGroup::BootstrapOnly => {
- if target.instance_record.is_some() {
- ManagedRuntimeInspectionAvailability::Success
- } else {
- ManagedRuntimeInspectionAvailability::Unsupported
- }
- }
- }
-}
-
-fn availability_state(availability: ManagedRuntimeInspectionAvailability) -> String {
- match availability {
- ManagedRuntimeInspectionAvailability::Success => "ready",
- ManagedRuntimeInspectionAvailability::Unconfigured => "unknown_runtime",
- ManagedRuntimeInspectionAvailability::Unsupported => "unsupported",
- }
- .to_owned()
-}
-
-fn status_state(target: &ManagedRuntimeTarget) -> &'static str {
- match target.runtime_group {
- ManagedRuntimeGroup::ActiveManagedTarget => target
- .instance_record
- .as_ref()
- .map(|record| install_state_label(record.install_state()))
- .unwrap_or("not_installed"),
- ManagedRuntimeGroup::DefinedManagedTarget => "defined_not_active",
- ManagedRuntimeGroup::BootstrapOnly => "bootstrap_only",
- ManagedRuntimeGroup::Unknown => "unknown_runtime",
- }
-}
-
-fn status_detail(target: &ManagedRuntimeTarget) -> String {
- match target.runtime_group {
- ManagedRuntimeGroup::ActiveManagedTarget if target.instance_record.is_some() => format!(
- "managed runtime `{}` instance `{}` is registered",
- target.context.service(),
- target.context.instance()
- ),
- ManagedRuntimeGroup::ActiveManagedTarget => format!(
- "managed runtime `{}` has no registered instance `{}`",
- target.context.service(),
- target.context.instance()
- ),
- ManagedRuntimeGroup::DefinedManagedTarget => format!(
- "runtime `{}` is defined but not yet an active managed target",
- target.context.service()
- ),
- ManagedRuntimeGroup::BootstrapOnly => format!(
- "runtime `{}` is bootstrap_only in the management contract",
- target.context.service()
- ),
- ManagedRuntimeGroup::Unknown => unknown_runtime_detail(target),
- }
-}
-
-fn logs_detail(target: &ManagedRuntimeTarget) -> String {
- match target.runtime_group {
- ManagedRuntimeGroup::ActiveManagedTarget => {
- "runtime logs use manager-owned stdout/stderr tracking".to_owned()
- }
- ManagedRuntimeGroup::DefinedManagedTarget => format!(
- "runtime `{}` is a defined future managed target",
- target.context.service()
- ),
- ManagedRuntimeGroup::BootstrapOnly => format!(
- "runtime `{}` is bootstrap_only; generic managed logs are not admitted",
- target.context.service()
- ),
- ManagedRuntimeGroup::Unknown => unknown_runtime_detail(target),
- }
-}
-
-fn config_detail(target: &ManagedRuntimeTarget, registered: bool) -> String {
- match target.runtime_group {
- ManagedRuntimeGroup::ActiveManagedTarget if registered => {
- "runtime config is derived from the validated service context".to_owned()
- }
- ManagedRuntimeGroup::ActiveManagedTarget => format!(
- "managed runtime `{}` has no registered instance config",
- target.context.service()
- ),
- ManagedRuntimeGroup::DefinedManagedTarget => format!(
- "runtime `{}` is a defined future managed target",
- target.context.service()
- ),
- ManagedRuntimeGroup::BootstrapOnly => format!(
- "runtime `{}` is bootstrap_only; generic managed config is not admitted",
- target.context.service()
- ),
- ManagedRuntimeGroup::Unknown => unknown_runtime_detail(target),
- }
-}
-
-fn unknown_runtime_detail(target: &ManagedRuntimeTarget) -> String {
- format!(
- "runtime `{}` is not present in the current runtime-management contract",
- target.context.service()
- )
-}
-
-fn infer_health_state(target: &ManagedRuntimeTarget) -> (&'static str, &'static str) {
- if target.runtime_group != ManagedRuntimeGroup::ActiveManagedTarget {
- return (
- health_state_label(ManagedRuntimeHealthState::NotInstalled),
- "metadata_only",
- );
- }
- let Some(record) = &target.instance_record else {
- return (
- health_state_label(ManagedRuntimeHealthState::NotInstalled),
- "registry_absent",
- );
- };
- if record.install_state() == ManagedRuntimeInstallState::Failed {
- return (
- health_state_label(ManagedRuntimeHealthState::Failed),
- "registry_install_state",
- );
- }
- if target
- .predicted_paths
- .as_ref()
- .is_some_and(|paths| crate::process_running(paths).unwrap_or(false))
- {
- return (
- health_state_label(ManagedRuntimeHealthState::Running),
- "process_probe",
- );
- }
- if record.install_state() == ManagedRuntimeInstallState::NotInstalled {
- (
- health_state_label(ManagedRuntimeHealthState::NotInstalled),
- "registry_install_state",
- )
- } else {
- (
- health_state_label(ManagedRuntimeHealthState::Stopped),
- "pid_file_absent",
- )
- }
-}
-
-fn install_state_label(state: ManagedRuntimeInstallState) -> &'static str {
- match state {
- ManagedRuntimeInstallState::NotInstalled => "not_installed",
- ManagedRuntimeInstallState::Installed => "installed",
- ManagedRuntimeInstallState::Configured => "configured",
- ManagedRuntimeInstallState::Failed => "failed",
- }
-}
-
-fn health_state_label(state: ManagedRuntimeHealthState) -> &'static str {
- match state {
- ManagedRuntimeHealthState::NotInstalled => "not_installed",
- ManagedRuntimeHealthState::Stopped => "stopped",
- ManagedRuntimeHealthState::Starting => "starting",
- ManagedRuntimeHealthState::Running => "running",
- ManagedRuntimeHealthState::Degraded => "degraded",
- ManagedRuntimeHealthState::Failed => "failed",
- }
-}
-
-#[must_use]
-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::fs;
-
- use radroots_runtime_paths::{
- InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver,
- RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId,
- };
- use tempfile::tempdir;
+ use radroots_runtime_paths::{InstanceId, RadrootsPathProfile, ServiceId};
- use super::{
- ManagedRuntimeGroup, ManagedRuntimeInspectionAvailability, ManagedRuntimeLifecycleAction,
- active_management_mode_for_profile, inspect_runtime_action, inspect_runtime_config,
- inspect_runtime_logs, inspect_runtime_status, load_management_context,
- resolve_runtime_target, runtime_group,
- };
- use crate::paths::resolve_instance_paths;
- use crate::{
- HARDENED_MANAGEMENT_CONTRACT, ManagedRuntimeHealthState, ManagedRuntimeInstallState,
- ManagedRuntimeInstanceRecord, RadrootsRuntimeManagerError, parse_contract_str,
- };
+ use super::{ManagedRuntimeContext, resolve_runtime_target};
+ use crate::{HARDENED_MANAGEMENT_CONTRACT, RadrootsRuntimeManagerError, parse_contract_str};
- const CONTRACT: &str = HARDENED_MANAGEMENT_CONTRACT;
-
- fn context(service: &str, instance: &str, root: &std::path::Path) -> RuntimeContext {
- RuntimeContext::resolve(
- &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
- RuntimeContextBootstrap::new(
- RadrootsPathProfile::RepoLocal,
- Some(root.to_path_buf()),
- RuntimeContextSource::BootstrapCli,
- RuntimeContextSource::BootstrapCli,
- )
- .expect("bootstrap"),
- ServiceId::new(service).expect("service"),
- InstanceId::new(instance).expect("instance"),
+ fn context(profile: RadrootsPathProfile) -> ManagedRuntimeContext {
+ ManagedRuntimeContext::new(
+ parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract"),
+ profile,
)
- .expect("context")
+ .expect("management context")
}
- fn manager(root: &std::path::Path) -> super::ManagedRuntimeContext {
- load_management_context(
- parse_contract_str(CONTRACT).expect("contract"),
- context("runtime-manager", "default", root),
+ fn target(
+ context: &ManagedRuntimeContext,
+ service: &str,
+ instance: &str,
+ ) -> super::ManagedRuntimeTarget {
+ resolve_runtime_target(
+ context,
+ ServiceId::new(service).expect("service"),
+ InstanceId::new(instance).expect("instance"),
)
- .expect("manager")
- }
-
- #[test]
- fn manager_loads_from_its_context_and_rejects_inactive_profiles() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- assert_eq!(
- manager.manager_context().service().as_str(),
- "runtime-manager"
- );
- assert!(manager.registry().instances.is_empty());
- assert!(
- manager
- .shared_paths()
- .instance_registry_path()
- .ends_with("services/runtime-manager/default/instances.toml")
- );
-
- let contract = parse_contract_str(CONTRACT).expect("contract");
- assert_eq!(
- active_management_mode_for_profile(&contract, RadrootsPathProfile::RepoLocal)
- .expect("active mode"),
- "interactive_user_managed"
- );
- assert_eq!(
- active_management_mode_for_profile(&contract, RadrootsPathProfile::ServiceHost)
- .expect("service-host mode"),
- "service_host_managed"
- );
+ .expect("target")
}
#[test]
- fn targets_bind_exact_typed_contexts_and_multi_instance_records() {
- let dir = tempdir().expect("tempdir");
- let mut manager = manager(dir.path());
- let primary = context("myc", "primary", dir.path());
- let secondary = context("myc", "secondary", dir.path());
-
- let primary_target = resolve_runtime_target(&manager, primary.clone()).expect("primary");
- let secondary_target =
- resolve_runtime_target(&manager, secondary.clone()).expect("secondary");
- assert!(primary_target.instance_record.is_none());
- assert!(secondary_target.instance_record.is_none());
- assert_eq!(primary_target.context, primary);
- assert_eq!(secondary_target.context, secondary);
- assert_ne!(
- primary_target.context.paths(),
- secondary_target.context.paths()
- );
- assert_eq!(
- primary_target.runtime_group,
- ManagedRuntimeGroup::DefinedManagedTarget
- );
- assert!(primary_target.predicted_paths.is_none());
- assert_eq!(primary_target.service_target().service_id().as_str(), "myc");
- assert_eq!(
- manager.register_instance(&primary, ManagedRuntimeInstallState::Configured),
- Err(RadrootsRuntimeManagerError::MetadataOnlyServiceTarget)
- );
- assert_eq!(
- manager.remove_instance(&primary),
- Err(RadrootsRuntimeManagerError::MetadataOnlyServiceTarget)
- );
- }
+ fn contexts_accept_only_exact_contracts_and_supported_profiles() {
+ for (profile, mode) in [
+ (RadrootsPathProfile::RepoLocal, "interactive_user_managed"),
+ (RadrootsPathProfile::ServiceHost, "service_host_managed"),
+ ] {
+ let context = context(profile);
+ assert_eq!(context.profile(), profile);
+ assert_eq!(context.management_mode(), mode);
+ assert_eq!(context.contract().service_targets.len(), 2);
+ }
- #[test]
- fn manager_rejects_same_identity_from_a_different_root_scope() {
- let first = tempdir().expect("first");
- let second = tempdir().expect("second");
- let mut manager = manager(first.path());
- let mismatched = context("myc", "primary", second.path());
+ for profile in [
+ RadrootsPathProfile::InteractiveUser,
+ RadrootsPathProfile::MobileNative,
+ ] {
+ let contract = parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract");
+ assert!(matches!(
+ ManagedRuntimeContext::new(contract, profile),
+ Err(RadrootsRuntimeManagerError::UnsupportedProfile)
+ ));
+ }
+ let mut direct = parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract");
+ direct.lifecycle.actions.push("start".to_owned());
assert!(matches!(
- manager.register_instance(&mismatched, ManagedRuntimeInstallState::Configured),
- Err(RadrootsRuntimeManagerError::RuntimeContextMismatch)
+ ManagedRuntimeContext::new(direct, RadrootsPathProfile::RepoLocal),
+ Err(RadrootsRuntimeManagerError::InvalidContract)
));
- assert!(matches!(
- resolve_runtime_target(&manager, mismatched),
- Err(RadrootsRuntimeManagerError::RuntimeContextMismatch)
- ));
- assert!(manager.registry().instances().is_empty());
- }
-
- #[test]
- fn groups_and_unknown_targets_remain_contract_controlled() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- let contract = manager.contract();
- assert_eq!(
- runtime_group(contract, "radrootsd"),
- ManagedRuntimeGroup::Unknown
- );
- assert_eq!(
- runtime_group(contract, "myc"),
- ManagedRuntimeGroup::DefinedManagedTarget
- );
- assert_eq!(runtime_group(contract, "hyf"), ManagedRuntimeGroup::Unknown);
- assert_eq!(
- runtime_group(contract, "unknown"),
- ManagedRuntimeGroup::Unknown
- );
-
- assert_eq!(
- resolve_runtime_target(&manager, context("unknown", "default", dir.path()))
- .expect_err("unknown target"),
- RadrootsRuntimeManagerError::UnsupportedServiceTarget
- );
- }
-
- #[test]
- fn status_is_metadata_only_without_probing_manager_tracking() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- let service = context("myc", "primary", dir.path());
- let target = resolve_runtime_target(&manager, service).expect("target");
- assert!(target.predicted_paths().is_none());
-
- let status = inspect_runtime_status(&target, &["start".to_owned()]);
- assert_eq!(
- status.availability,
- ManagedRuntimeInspectionAvailability::Unsupported
- );
- assert_eq!(status.view.health_state, "not_installed");
- assert_eq!(status.view.health_source, "metadata_only");
- assert!(status.view.lifecycle_actions.is_empty());
- assert_eq!(
- status.view.instance_source,
- RuntimeContextSource::BootstrapCli
- );
- let rendered = format!("{status:?}");
- assert!(!rendered.contains(dir.path().to_string_lossy().as_ref()));
- assert!(!status.view.detail.contains('/'));
- }
-
- #[test]
- fn log_and_config_inspections_remain_non_io_for_metadata_only_services() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- let service = context("rhi", "default", dir.path());
- let target = resolve_runtime_target(&manager, service).expect("target");
- assert!(target.predicted_paths().is_none());
- fs::create_dir_all(target.context().paths().logs()).expect("service logs");
- fs::write(target.context().paths().logs().join("stdout.log"), "stdout")
- .expect("service stdout");
- fs::create_dir_all(target.context().paths().config()).expect("service config");
- fs::write(
- target.context().paths().config().join("config.toml"),
- "enabled = true",
- )
- .expect("service config");
-
- let logs = inspect_runtime_logs(&target);
- assert_eq!(
- logs.availability,
- ManagedRuntimeInspectionAvailability::Unsupported
- );
- assert!(!logs.view.stdout_log_present);
- assert!(!logs.view.stderr_log_present);
- let config = inspect_runtime_config(&target);
- assert_eq!(
- config.availability,
- ManagedRuntimeInspectionAvailability::Unsupported
- );
- assert!(!config.view.config_present);
- assert_eq!(config.view.config_format.as_deref(), Some("toml"));
- for rendered in [format!("{logs:?}"), format!("{config:?}")] {
- assert!(!rendered.contains(dir.path().to_string_lossy().as_ref()));
- }
- }
-
- #[test]
- fn actions_do_not_mutate_bindings_for_any_group() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- for service in ["myc", "rhi"] {
- let target = resolve_runtime_target(&manager, context(service, "default", dir.path()))
- .expect("target");
- let action = inspect_runtime_action(&target, ManagedRuntimeLifecycleAction::ConfigSet);
- assert_eq!(
- action.availability,
- ManagedRuntimeInspectionAvailability::Unsupported
- );
- assert!(!action.view.mutates_bindings);
- assert!(action.view.next_step.is_none());
- }
}
#[test]
- fn durable_management_contract_requires_explicit_instances_and_rejects_any_drift() {
- let contract = parse_contract_str(HARDENED_MANAGEMENT_CONTRACT).expect("contract");
- assert_eq!(contract.service_targets.len(), 2);
- assert!(contract.bootstrap.is_empty());
-
- for raw in [
- HARDENED_MANAGEMENT_CONTRACT.replace("schema_version = 1", "schema_version = 2"),
- HARDENED_MANAGEMENT_CONTRACT.replace(
- "defined = [\"myc\", \"rhi\"]",
- "active = [\"myc\"]\ndefined = [\"rhi\"]",
- ),
- HARDENED_MANAGEMENT_CONTRACT.replace(
- "managed_runtime_lookup = \"typed_instance_registry\"",
- "managed_runtime_lookup = \"different\"",
- ),
- HARDENED_MANAGEMENT_CONTRACT.replace("active = [\"cli\"]", "active = [\"other\"]"),
- HARDENED_MANAGEMENT_CONTRACT.replace(
- "supported_profiles = [\"interactive\", \"repo_local\"]",
- "supported_profiles = [\"interactive\"]",
- ),
- HARDENED_MANAGEMENT_CONTRACT.replace(
- "required_fields = [\"service_id\", \"instance_id\"]",
- "required_fields = [\"service_id\"]",
- ),
- HARDENED_MANAGEMENT_CONTRACT.replace(
- "distribution_contract = \"hardened-service-targets.v1.toml\"",
- "distribution_contract = \"different.toml\"",
- ),
- format!("{HARDENED_MANAGEMENT_CONTRACT}\nunknown = true\n"),
+ fn exact_myc_and_rhi_instances_resolve_to_static_metadata_only() {
+ let context = context(RadrootsPathProfile::RepoLocal);
+ let myc = target(&context, "myc", "primary");
+ let rhi = target(&context, "rhi", "secondary");
+
+ assert_eq!(myc.service_id().as_str(), "myc");
+ assert_eq!(myc.instance_id().as_str(), "primary");
+ assert_eq!(myc.profile(), RadrootsPathProfile::RepoLocal);
+ assert_eq!(myc.service_target().service_id(), myc.service_id());
+ assert_eq!(myc.management_mode(), "interactive_user_managed");
+ assert!(!myc.mode_contract().service_manager_integration);
+
+ assert_eq!(rhi.service_id().as_str(), "rhi");
+ assert_eq!(rhi.instance_id().as_str(), "secondary");
+ assert_eq!(rhi.service_target().service_id(), rhi.service_id());
+
+ for rendered in [
+ format!("{context:?}"),
+ format!("{myc:?}"),
+ format!("{rhi:?}"),
] {
- assert!(parse_contract_str(&raw).is_err());
+ assert!(!rendered.contains('/'));
+ assert!(!rendered.contains("state.sqlite"));
+ assert!(!rendered.contains("instances.toml"));
}
}
#[test]
- fn sealed_target_accessors_debug_and_all_metadata_groups_are_qualified() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- let mut target = resolve_runtime_target(&manager, context("myc", "primary", dir.path()))
- .expect("target");
-
- assert_eq!(target.instance_source(), RuntimeContextSource::BootstrapCli);
- assert_eq!(
- target.runtime_group(),
- ManagedRuntimeGroup::DefinedManagedTarget
- );
- assert_eq!(target.management_mode(), Some("interactive_user_managed"));
- assert!(target.mode_contract().is_some());
- assert!(target.bootstrap().is_none());
- assert!(target.instance_record().is_none());
- assert!(target.predicted_paths().is_none());
- assert!(!format!("{target:?}").contains(dir.path().to_string_lossy().as_ref()));
- assert!(!format!("{manager:?}").contains(dir.path().to_string_lossy().as_ref()));
-
- for (group, label, posture) in [
- (
- ManagedRuntimeGroup::ActiveManagedTarget,
- "active_managed_target",
- "active_managed_target",
- ),
- (
- ManagedRuntimeGroup::DefinedManagedTarget,
- "defined_managed_target",
- "defined_future_target",
- ),
- (
- ManagedRuntimeGroup::BootstrapOnly,
- "bootstrap_only",
- "bootstrap_only_direct_binding",
- ),
- (ManagedRuntimeGroup::Unknown, "unknown", "unknown_runtime"),
- ] {
- target.runtime_group = group;
- assert_eq!(target.runtime_group().as_str(), label);
- assert_eq!(target.runtime_group().posture(), posture);
-
- let status = inspect_runtime_status(&target, &["start".to_owned()]);
- let logs = inspect_runtime_logs(&target);
- let config = inspect_runtime_config(&target);
- let action = inspect_runtime_action(&target, ManagedRuntimeLifecycleAction::ConfigSet);
- assert!(!status.view.detail.is_empty());
- assert!(!logs.view.detail.is_empty());
- assert!(!config.view.detail.is_empty());
- assert!(!action.view.detail.is_empty());
- }
+ fn unsupported_service_ids_fail_without_fallback_or_effects() {
+ let context = context(RadrootsPathProfile::ServiceHost);
+ let error = resolve_runtime_target(
+ &context,
+ ServiceId::new("radrootsd").expect("service"),
+ InstanceId::new("default").expect("instance"),
+ )
+ .expect_err("unsupported target");
+ assert_eq!(error, RadrootsRuntimeManagerError::UnsupportedServiceTarget);
}
#[test]
- fn dormant_active_target_paths_cover_registry_tracking_and_health_states() {
- let dir = tempdir().expect("tempdir");
- let manager = manager(dir.path());
- let runtime_context = context("myc", "primary", dir.path());
- let mut target = resolve_runtime_target(&manager, runtime_context).expect("target");
- let paths = resolve_instance_paths(manager.shared_paths(), target.context());
- fs::create_dir_all(paths.logs_dir()).expect("logs dir");
- fs::create_dir_all(paths.config_dir()).expect("config dir");
- fs::create_dir_all(paths.run_dir()).expect("run dir");
- fs::write(paths.stdout_log_path(), "stdout").expect("stdout");
- fs::write(paths.stderr_log_path(), "stderr").expect("stderr");
- fs::write(paths.config_path(), "enabled = true").expect("config");
-
- target.runtime_group = ManagedRuntimeGroup::ActiveManagedTarget;
- target.predicted_paths = Some(paths.clone());
- target.instance_record = Some(ManagedRuntimeInstanceRecord::new(
- target.context(),
- ManagedRuntimeInstallState::Installed,
- ));
-
- let status = inspect_runtime_status(&target, &["start".to_owned()]);
- assert_eq!(
- status.availability,
- ManagedRuntimeInspectionAvailability::Success
- );
- assert_eq!(status.view.state, "installed");
- assert_eq!(status.view.health_state, "stopped");
- assert_eq!(status.view.lifecycle_actions, ["start"]);
-
- let logs = inspect_runtime_logs(&target);
- assert!(logs.view.stdout_log_present);
- assert!(logs.view.stderr_log_present);
- let config = inspect_runtime_config(&target);
- assert_eq!(config.view.state, "ready");
- assert!(config.view.config_present);
-
- for state in [
- ManagedRuntimeInstallState::NotInstalled,
- ManagedRuntimeInstallState::Configured,
- ManagedRuntimeInstallState::Failed,
+ fn production_manager_is_metadata_only_and_contains_no_io_authority() {
+ let source = include_str!("managed.rs")
+ .split("\n#[cfg(test)]")
+ .next()
+ .expect("production source");
+ for forbidden in [
+ "std::fs",
+ "std::process",
+ "std::path",
+ "radroots_runtime_paths::RuntimeContext",
+ "load_registry",
+ "save_registry",
+ "register_instance",
+ "remove_instance",
+ "ManagedRuntimeInstancePaths",
+ "ManagedRuntimeArtifactName",
+ "inspect_runtime_",
+ "start_process",
+ "stop_process",
+ "extract_binary_archive",
] {
- target.instance_record =
- Some(ManagedRuntimeInstanceRecord::new(target.context(), state));
- let status = inspect_runtime_status(&target, &[]);
- assert!(!status.view.install_state.is_empty());
- assert!(!status.view.health_state.is_empty());
- }
-
- target.instance_record = None;
- assert_eq!(
- inspect_runtime_status(&target, &[]).view.state,
- "not_installed"
- );
- assert_eq!(inspect_runtime_config(&target).view.state, "not_installed");
-
- for action in [
- ManagedRuntimeLifecycleAction::Install,
- ManagedRuntimeLifecycleAction::Uninstall,
- ManagedRuntimeLifecycleAction::Start,
- ManagedRuntimeLifecycleAction::Stop,
- ManagedRuntimeLifecycleAction::Restart,
- ManagedRuntimeLifecycleAction::ConfigSet,
- ] {
- assert_eq!(
- inspect_runtime_action(&target, action).view.action,
- action.as_str()
+ assert!(
+ !source.contains(forbidden),
+ "metadata-only manager retained `{forbidden}`"
);
}
-
- for state in [
- ManagedRuntimeHealthState::NotInstalled,
- ManagedRuntimeHealthState::Stopped,
- ManagedRuntimeHealthState::Starting,
- ManagedRuntimeHealthState::Running,
- ManagedRuntimeHealthState::Degraded,
- ManagedRuntimeHealthState::Failed,
- ] {
- assert!(!super::health_state_label(state).is_empty());
- }
- }
-
- #[test]
- fn unsupported_registration_and_all_runtime_group_memberships_are_explicit() {
- let dir = tempdir().expect("tempdir");
- let mut manager = manager(dir.path());
- let unsupported = context("other", "default", dir.path());
- assert_eq!(
- manager.register_instance(&unsupported, ManagedRuntimeInstallState::Installed),
- Err(RadrootsRuntimeManagerError::UnsupportedServiceTarget)
- );
- assert_eq!(
- manager.remove_instance(&unsupported),
- Err(RadrootsRuntimeManagerError::UnsupportedServiceTarget)
- );
-
- let mut contract = manager.contract().clone();
- contract.managed_runtime_targets.active = vec!["active".to_owned()];
- contract.managed_runtime_targets.defined = vec!["defined".to_owned()];
- contract.managed_runtime_targets.bootstrap_only = vec!["bootstrap".to_owned()];
- assert_eq!(
- runtime_group(&contract, "active"),
- ManagedRuntimeGroup::ActiveManagedTarget
- );
- assert_eq!(
- runtime_group(&contract, "defined"),
- ManagedRuntimeGroup::DefinedManagedTarget
- );
- assert_eq!(
- runtime_group(&contract, "bootstrap"),
- ManagedRuntimeGroup::BootstrapOnly
- );
- assert_eq!(
- runtime_group(&contract, "other"),
- ManagedRuntimeGroup::Unknown
- );
}
}
diff --git a/crates/runtime_manager/src/model.rs b/crates/runtime_manager/src/model.rs
@@ -1,8 +1,9 @@
-use radroots_runtime_distribution::HardenedServiceTargets;
-use radroots_runtime_paths::{InstanceId, RuntimeContext, ServiceId};
-use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
+use radroots_runtime_distribution::HardenedServiceTargets;
+use radroots_runtime_paths::{InstanceId, ServiceId};
+use serde::Deserialize;
+
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct RadrootsRuntimeManagementContract {
@@ -122,135 +123,3 @@ impl BootstrapRuntimeContract {
self.preferred_cli_binding
}
}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum ManagedRuntimeInstallState {
- NotInstalled,
- Installed,
- Configured,
- Failed,
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum ManagedRuntimeHealthState {
- NotInstalled,
- Stopped,
- Starting,
- Running,
- Degraded,
- Failed,
-}
-
-/// Sealed registry state for one typed service instance.
-///
-/// ```compile_fail
-/// use radroots_runtime_manager::ManagedRuntimeInstanceRecord;
-///
-/// let _ = ManagedRuntimeInstanceRecord {
-/// service_id: todo!(),
-/// instance_id: todo!(),
-/// install_state: todo!(),
-/// };
-/// ```
-#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields)]
-pub struct ManagedRuntimeInstanceRecord {
- service_id: ServiceId,
- instance_id: InstanceId,
- install_state: ManagedRuntimeInstallState,
-}
-
-impl ManagedRuntimeInstanceRecord {
- #[cfg(test)]
- #[must_use]
- pub(crate) fn new(context: &RuntimeContext, install_state: ManagedRuntimeInstallState) -> Self {
- Self {
- service_id: context.service().clone(),
- instance_id: context.instance().clone(),
- install_state,
- }
- }
-
- #[must_use]
- pub fn service_id(&self) -> &ServiceId {
- &self.service_id
- }
-
- #[must_use]
- pub fn instance_id(&self) -> &InstanceId {
- &self.instance_id
- }
-
- #[must_use]
- pub fn install_state(&self) -> ManagedRuntimeInstallState {
- self.install_state
- }
-
- #[must_use]
- pub fn matches_context(&self, context: &RuntimeContext) -> bool {
- self.service_id == *context.service() && self.instance_id == *context.instance()
- }
-}
-
-impl core::fmt::Debug for ManagedRuntimeInstanceRecord {
- fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- formatter
- .debug_struct("ManagedRuntimeInstanceRecord")
- .field("service_id", &self.service_id)
- .field("instance_id", &self.instance_id)
- .field("install_state", &self.install_state)
- .finish()
- }
-}
-
-pub const RUNTIME_INSTANCE_REGISTRY_SCHEMA: &str = "radroots.service-instance-registry";
-pub const RUNTIME_INSTANCE_REGISTRY_VERSION: u32 = 1;
-
-/// A sealed, schema-fixed, normalized instance registry.
-///
-/// ```compile_fail
-/// use radroots_runtime_manager::ManagedRuntimeInstanceRegistry;
-///
-/// let _ = ManagedRuntimeInstanceRegistry {
-/// schema: "wrong".to_owned(),
-/// schema_version: 99,
-/// instances: Vec::new(),
-/// };
-/// ```
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(deny_unknown_fields)]
-pub struct ManagedRuntimeInstanceRegistry {
- pub(crate) schema: String,
- pub(crate) schema_version: u32,
- #[serde(default)]
- pub(crate) instances: Vec<ManagedRuntimeInstanceRecord>,
-}
-
-impl ManagedRuntimeInstanceRegistry {
- #[must_use]
- pub fn schema(&self) -> &str {
- &self.schema
- }
-
- #[must_use]
- pub fn schema_version(&self) -> u32 {
- self.schema_version
- }
-
- #[must_use]
- pub fn instances(&self) -> &[ManagedRuntimeInstanceRecord] {
- &self.instances
- }
-}
-
-impl Default for ManagedRuntimeInstanceRegistry {
- fn default() -> Self {
- Self {
- schema: RUNTIME_INSTANCE_REGISTRY_SCHEMA.to_string(),
- schema_version: RUNTIME_INSTANCE_REGISTRY_VERSION,
- instances: Vec::new(),
- }
- }
-}
diff --git a/crates/runtime_manager/src/paths.rs b/crates/runtime_manager/src/paths.rs
@@ -1,373 +0,0 @@
-use core::fmt;
-use std::path::{Path, PathBuf};
-
-use radroots_runtime_paths::{RuntimeContext, default_service_instance_artifacts};
-
-use crate::error::RadrootsRuntimeManagerError;
-use crate::model::RadrootsRuntimeManagementContract;
-
-/// Manager-owned paths derived from the manager's validated service context.
-///
-/// External callers cannot forge another root set:
-///
-/// ```compile_fail
-/// use std::path::PathBuf;
-/// use radroots_runtime_manager::ManagedRuntimeSharedPaths;
-///
-/// let _ = ManagedRuntimeSharedPaths {
-/// instance_registry_path: PathBuf::from("/tmp/escape"),
-/// artifact_cache_dir: PathBuf::from("/tmp/escape"),
-/// install_root: PathBuf::from("/tmp/escape"),
-/// };
-/// ```
-#[derive(Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeSharedPaths {
- instance_registry_path: PathBuf,
- artifact_cache_dir: PathBuf,
- install_root: PathBuf,
- logs_root: PathBuf,
- run_root: PathBuf,
-}
-
-impl ManagedRuntimeSharedPaths {
- #[must_use]
- pub fn instance_registry_path(&self) -> &Path {
- &self.instance_registry_path
- }
-
- #[must_use]
- pub fn artifact_cache_dir(&self) -> &Path {
- &self.artifact_cache_dir
- }
-
- #[must_use]
- pub fn install_root(&self) -> &Path {
- &self.install_root
- }
-
- #[must_use]
- pub fn logs_root(&self) -> &Path {
- &self.logs_root
- }
-
- #[must_use]
- pub fn run_root(&self) -> &Path {
- &self.run_root
- }
-}
-
-impl fmt::Debug for ManagedRuntimeSharedPaths {
- fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
- formatter.write_str("ManagedRuntimeSharedPaths([redacted])")
- }
-}
-
-/// Operational paths for one validated service instance.
-///
-/// Service-owned directories and fixed artifacts come only from the supplied
-/// [`RuntimeContext`]. The manager-owned install directory comes only from its
-/// own validated context. Process tracking and captured stdout/stderr remain
-/// under manager-owned roots, so lifecycle removal cannot delete canonical
-/// service state or secrets.
-///
-/// ```compile_fail
-/// use std::path::PathBuf;
-/// use radroots_runtime_manager::ManagedRuntimeInstancePaths;
-///
-/// let _ = ManagedRuntimeInstancePaths {
-/// install_dir: PathBuf::from("/tmp/escape"),
-/// };
-/// ```
-#[derive(Clone, PartialEq, Eq)]
-pub struct ManagedRuntimeInstancePaths {
- context: RuntimeContext,
- install_dir: PathBuf,
- logs_dir: PathBuf,
- run_dir: PathBuf,
- pid_file_path: PathBuf,
- stdout_log_path: PathBuf,
- stderr_log_path: PathBuf,
-}
-
-impl ManagedRuntimeInstancePaths {
- #[must_use]
- pub fn context(&self) -> &RuntimeContext {
- &self.context
- }
-
- #[must_use]
- pub fn install_dir(&self) -> &Path {
- &self.install_dir
- }
-
- #[must_use]
- pub fn config_dir(&self) -> &Path {
- self.context.paths().config()
- }
-
- #[must_use]
- pub fn state_dir(&self) -> &Path {
- self.context.paths().state()
- }
-
- #[must_use]
- pub fn logs_dir(&self) -> &Path {
- &self.logs_dir
- }
-
- #[must_use]
- pub fn run_dir(&self) -> &Path {
- &self.run_dir
- }
-
- #[must_use]
- pub fn secrets_dir(&self) -> &Path {
- self.context.paths().secrets()
- }
-
- #[must_use]
- pub fn config_path(&self) -> PathBuf {
- default_service_instance_artifacts(self.context.paths())
- .config()
- .to_path_buf()
- }
-
- #[must_use]
- pub fn state_database_path(&self) -> PathBuf {
- default_service_instance_artifacts(self.context.paths())
- .state_database()
- .to_path_buf()
- }
-
- #[must_use]
- pub fn state_lock_path(&self) -> PathBuf {
- default_service_instance_artifacts(self.context.paths())
- .state_lock()
- .to_path_buf()
- }
-
- #[must_use]
- pub fn admin_socket_path(&self) -> PathBuf {
- default_service_instance_artifacts(self.context.paths())
- .admin_socket()
- .to_path_buf()
- }
-
- #[must_use]
- pub fn pid_file_path(&self) -> &Path {
- &self.pid_file_path
- }
-
- #[must_use]
- pub fn stdout_log_path(&self) -> &Path {
- &self.stdout_log_path
- }
-
- #[must_use]
- pub fn stderr_log_path(&self) -> &Path {
- &self.stderr_log_path
- }
-}
-
-impl fmt::Debug for ManagedRuntimeInstancePaths {
- fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
- formatter.write_str("ManagedRuntimeInstancePaths([redacted])")
- }
-}
-
-#[must_use]
-pub(crate) fn resolve_shared_paths(context: &RuntimeContext) -> ManagedRuntimeSharedPaths {
- ManagedRuntimeSharedPaths {
- instance_registry_path: context.paths().config().join("instances.toml"),
- artifact_cache_dir: context.paths().cache().join("artifacts"),
- install_root: context.paths().state().join("installs"),
- logs_root: context.paths().logs().join("instances"),
- run_root: context.paths().run().join("instances"),
- }
-}
-
-#[must_use]
-#[cfg(test)]
-pub(crate) fn resolve_instance_paths(
- shared: &ManagedRuntimeSharedPaths,
- context: &RuntimeContext,
-) -> ManagedRuntimeInstancePaths {
- let suffix = PathBuf::from(context.service().as_str()).join(context.instance().as_str());
- let logs_dir = shared.logs_root.join(&suffix);
- let run_dir = shared.run_root.join(&suffix);
-
- ManagedRuntimeInstancePaths {
- context: context.clone(),
- install_dir: shared.install_root.join(suffix),
- logs_dir: logs_dir.clone(),
- run_dir: run_dir.clone(),
- pid_file_path: run_dir.join("runtime.pid"),
- stdout_log_path: logs_dir.join("stdout.log"),
- stderr_log_path: logs_dir.join("stderr.log"),
- }
-}
-
-pub fn bootstrap_runtime<'a>(
- contract: &'a RadrootsRuntimeManagementContract,
- runtime_id: &str,
-) -> Result<&'a crate::model::BootstrapRuntimeContract, RadrootsRuntimeManagerError> {
- contract
- .bootstrap
- .get(runtime_id)
- .ok_or(RadrootsRuntimeManagerError::UnknownBootstrapRuntime)
-}
-
-#[cfg(test)]
-mod tests {
- use std::path::PathBuf;
-
- use radroots_runtime_paths::{
- InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver,
- RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId,
- };
-
- use super::{resolve_instance_paths, resolve_shared_paths};
-
- fn repo_context(service: &str, instance: &str, root: &str) -> RuntimeContext {
- RuntimeContext::resolve(
- &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
- RuntimeContextBootstrap::new(
- RadrootsPathProfile::RepoLocal,
- Some(PathBuf::from(root)),
- RuntimeContextSource::BootstrapCli,
- RuntimeContextSource::BootstrapCli,
- )
- .expect("bootstrap"),
- ServiceId::new(service).expect("service"),
- InstanceId::new(instance).expect("instance"),
- )
- .expect("context")
- }
-
- fn service_host_context(service: &str, instance: &str) -> RuntimeContext {
- RuntimeContext::resolve(
- &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
- RuntimeContextBootstrap::new(
- RadrootsPathProfile::ServiceHost,
- None,
- RuntimeContextSource::SafeDefault,
- RuntimeContextSource::BootstrapCli,
- )
- .expect("bootstrap"),
- ServiceId::new(service).expect("service"),
- InstanceId::new(instance).expect("instance"),
- )
- .expect("context")
- }
-
- #[test]
- fn shared_paths_derive_only_from_the_manager_context() {
- let manager = repo_context("runtime-manager", "default", "/repo/.radroots");
- let paths = resolve_shared_paths(&manager);
-
- assert_eq!(
- paths.instance_registry_path(),
- PathBuf::from("/repo/.radroots/config/services/runtime-manager/default/instances.toml")
- );
- assert_eq!(
- paths.artifact_cache_dir(),
- PathBuf::from("/repo/.radroots/cache/services/runtime-manager/default/artifacts")
- );
- assert_eq!(
- paths.install_root(),
- PathBuf::from("/repo/.radroots/data/services/runtime-manager/default/installs")
- );
- assert_eq!(
- format!("{paths:?}"),
- "ManagedRuntimeSharedPaths([redacted])"
- );
- }
-
- #[test]
- fn instance_paths_use_the_exact_service_context_and_fixed_artifacts() {
- let shared = resolve_shared_paths(&repo_context(
- "runtime-manager",
- "default",
- "/repo/.radroots",
- ));
- let service = repo_context("myc", "north", "/repo/.radroots");
- let paths = resolve_instance_paths(&shared, &service);
-
- assert_eq!(
- paths.install_dir(),
- PathBuf::from(
- "/repo/.radroots/data/services/runtime-manager/default/installs/myc/north"
- )
- );
- assert_eq!(
- paths.config_path(),
- PathBuf::from("/repo/.radroots/config/services/myc/north/config.toml")
- );
- assert_eq!(
- paths.state_database_path(),
- PathBuf::from("/repo/.radroots/data/services/myc/north/state.sqlite")
- );
- assert_eq!(
- paths.state_lock_path(),
- PathBuf::from("/repo/.radroots/data/services/myc/north/state.lock")
- );
- assert_eq!(
- paths.admin_socket_path(),
- PathBuf::from("/repo/.radroots/run/services/myc/north/admin.sock")
- );
- assert_eq!(
- paths.stdout_log_path(),
- PathBuf::from(
- "/repo/.radroots/logs/services/runtime-manager/default/instances/myc/north/stdout.log"
- )
- );
- assert_eq!(
- format!("{paths:?}"),
- "ManagedRuntimeInstancePaths([redacted])"
- );
- }
-
- #[test]
- fn multi_instance_paths_cannot_cross_service_contexts() {
- let shared = resolve_shared_paths(&repo_context(
- "runtime-manager",
- "default",
- "/repo/.radroots",
- ));
- let north =
- resolve_instance_paths(&shared, &repo_context("rhi", "north", "/repo/.radroots"));
- let south =
- resolve_instance_paths(&shared, &repo_context("rhi", "south", "/repo/.radroots"));
-
- assert_ne!(north, south);
- assert!(north.state_dir().ends_with("services/rhi/north"));
- assert!(south.state_dir().ends_with("services/rhi/south"));
- assert!(!north.state_dir().starts_with(south.state_dir()));
- assert!(!south.state_dir().starts_with(north.state_dir()));
- }
-
- #[test]
- fn linux_service_host_paths_preserve_the_canonical_service_layout() {
- let manager = service_host_context("runtime-manager", "default");
- let shared = resolve_shared_paths(&manager);
- let service = service_host_context("myc", "primary");
- let paths = resolve_instance_paths(&shared, &service);
-
- assert_eq!(
- shared.instance_registry_path(),
- PathBuf::from("/etc/radroots/services/runtime-manager/default/instances.toml")
- );
- assert_eq!(
- paths.config_path(),
- PathBuf::from("/etc/radroots/services/myc/primary/config.toml")
- );
- assert_eq!(
- paths.state_database_path(),
- PathBuf::from("/var/lib/radroots/services/myc/primary/state.sqlite")
- );
- assert_eq!(
- paths.admin_socket_path(),
- PathBuf::from("/run/radroots/services/myc/primary/admin.sock")
- );
- }
-}
diff --git a/crates/runtime_manager/src/registry.rs b/crates/runtime_manager/src/registry.rs
@@ -1,392 +0,0 @@
-use std::fs;
-use std::path::Path;
-
-use radroots_runtime_paths::{InstanceId, ServiceId};
-
-use crate::error::RadrootsRuntimeManagerError;
-use crate::model::{
- ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry, RUNTIME_INSTANCE_REGISTRY_SCHEMA,
- RUNTIME_INSTANCE_REGISTRY_VERSION,
-};
-
-pub fn load_registry(
- path: impl AsRef<Path>,
-) -> Result<ManagedRuntimeInstanceRegistry, RadrootsRuntimeManagerError> {
- load_registry_path(path.as_ref())
-}
-
-fn load_registry_path(
- path: &Path,
-) -> Result<ManagedRuntimeInstanceRegistry, RadrootsRuntimeManagerError> {
- let raw = match fs::read_to_string(path) {
- Ok(raw) => raw,
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
- return Ok(ManagedRuntimeInstanceRegistry::default());
- }
- Err(source) => {
- return Err(RadrootsRuntimeManagerError::ReadRegistry {
- kind: source.kind(),
- });
- }
- };
-
- let registry = toml::from_str::<ManagedRuntimeInstanceRegistry>(&raw)
- .map_err(|_| RadrootsRuntimeManagerError::ParseRegistry)?;
- normalize_registry(registry)
-}
-
-pub fn save_registry(
- path: impl AsRef<Path>,
- registry: &ManagedRuntimeInstanceRegistry,
-) -> Result<(), RadrootsRuntimeManagerError> {
- save_registry_path(path.as_ref(), registry)
-}
-
-fn save_registry_path(
- path: &Path,
- registry: &ManagedRuntimeInstanceRegistry,
-) -> Result<(), RadrootsRuntimeManagerError> {
- save_registry_path_with(path, registry, toml::to_string_pretty)
-}
-
-fn save_registry_path_with(
- path: &Path,
- registry: &ManagedRuntimeInstanceRegistry,
- serializer: fn(&ManagedRuntimeInstanceRegistry) -> Result<String, toml::ser::Error>,
-) -> Result<(), RadrootsRuntimeManagerError> {
- ensure_registry_parent(path)?;
-
- let normalized = normalize_registry(registry.clone())?;
- let raw =
- serializer(&normalized).map_err(|_| RadrootsRuntimeManagerError::SerializeRegistry)?;
- fs::write(path, raw).map_err(|source| RadrootsRuntimeManagerError::WriteRegistry {
- kind: source.kind(),
- })
-}
-
-fn normalize_registry(
- mut registry: ManagedRuntimeInstanceRegistry,
-) -> Result<ManagedRuntimeInstanceRegistry, RadrootsRuntimeManagerError> {
- if registry.schema != RUNTIME_INSTANCE_REGISTRY_SCHEMA {
- return Err(RadrootsRuntimeManagerError::UnexpectedRegistrySchema);
- }
- if registry.schema_version != RUNTIME_INSTANCE_REGISTRY_VERSION {
- return Err(RadrootsRuntimeManagerError::UnexpectedRegistryVersion);
- }
- registry.instances.sort_by(|left, right| {
- left.service_id()
- .cmp(right.service_id())
- .then_with(|| left.instance_id().cmp(right.instance_id()))
- });
- if registry.instances.windows(2).any(|pair| {
- pair[0].service_id() == pair[1].service_id()
- && pair[0].instance_id() == pair[1].instance_id()
- }) {
- return Err(RadrootsRuntimeManagerError::DuplicateRegistryInstance);
- }
- Ok(registry)
-}
-
-#[cfg(test)]
-pub(crate) fn upsert_instance(
- registry: &mut ManagedRuntimeInstanceRegistry,
- record: ManagedRuntimeInstanceRecord,
-) {
- if let Some(existing) = registry.instances.iter_mut().find(|existing| {
- existing.service_id() == record.service_id()
- && existing.instance_id() == record.instance_id()
- }) {
- *existing = record;
- } else {
- registry.instances.push(record);
- registry.instances.sort_by(|left, right| {
- left.service_id()
- .cmp(right.service_id())
- .then_with(|| left.instance_id().cmp(right.instance_id()))
- });
- }
-}
-
-pub fn instance<'a>(
- registry: &'a ManagedRuntimeInstanceRegistry,
- service_id: &ServiceId,
- instance_id: &InstanceId,
-) -> Option<&'a ManagedRuntimeInstanceRecord> {
- registry
- .instances
- .iter()
- .find(|record| record.service_id() == service_id && record.instance_id() == instance_id)
-}
-
-#[cfg(test)]
-pub(crate) fn remove_instance(
- registry: &mut ManagedRuntimeInstanceRegistry,
- service_id: &ServiceId,
- instance_id: &InstanceId,
-) -> Option<ManagedRuntimeInstanceRecord> {
- let index = registry.instances.iter().position(|record| {
- record.service_id() == service_id && record.instance_id() == instance_id
- })?;
- Some(registry.instances.remove(index))
-}
-
-fn ensure_registry_parent(path: &Path) -> Result<(), RadrootsRuntimeManagerError> {
- let Some(parent) = path.parent() else {
- return Ok(());
- };
- if parent.as_os_str().is_empty() {
- return Ok(());
- }
- fs::create_dir_all(parent).map_err(|source| RadrootsRuntimeManagerError::CreateRegistryParent {
- kind: source.kind(),
- })
-}
-
-#[cfg(test)]
-mod tests {
- use std::fs;
- use std::path::{Path, PathBuf};
-
- use radroots_runtime_paths::{
- InstanceId, RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver,
- RadrootsPlatform, RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource, ServiceId,
- };
- use serde::ser::Error as _;
- use tempfile::tempdir;
-
- use super::{
- ensure_registry_parent, instance, load_registry, remove_instance, save_registry,
- save_registry_path_with, upsert_instance,
- };
- use crate::{
- ManagedRuntimeInstallState, ManagedRuntimeInstanceRecord, ManagedRuntimeInstanceRegistry,
- RadrootsRuntimeManagerError,
- };
-
- fn runtime_context(service_id: &str, instance_id: &str) -> RuntimeContext {
- RuntimeContext::resolve(
- &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
- RuntimeContextBootstrap::new(
- RadrootsPathProfile::RepoLocal,
- Some(PathBuf::from("/repo/.radroots")),
- RuntimeContextSource::BootstrapCli,
- RuntimeContextSource::BootstrapCli,
- )
- .expect("bootstrap"),
- ServiceId::new(service_id).expect("service"),
- InstanceId::new(instance_id).expect("instance"),
- )
- .expect("context")
- }
-
- fn sample_record(service_id: &str, instance_id: &str) -> ManagedRuntimeInstanceRecord {
- let context = runtime_context(service_id, instance_id);
- ManagedRuntimeInstanceRecord::new(&context, ManagedRuntimeInstallState::Configured)
- }
-
- fn assert_error_contains(err: &RadrootsRuntimeManagerError, parts: &[&str]) {
- use std::error::Error as _;
-
- let rendered = err.to_string();
- for part in parts {
- assert!(
- rendered.contains(part),
- "expected `{rendered}` to contain `{part}`"
- );
- }
- assert!(err.source().is_none());
- }
-
- #[test]
- fn load_registry_returns_default_when_file_is_missing() {
- let dir = tempdir().expect("tempdir");
- let registry = load_registry(dir.path().join("missing.toml")).expect("missing registry");
- assert_eq!(registry, ManagedRuntimeInstanceRegistry::default());
- }
-
- #[test]
- fn load_registry_reports_read_errors() {
- let dir = tempdir().expect("tempdir");
- let err = load_registry(dir.path()).expect_err("directory should fail");
- assert_error_contains(&err, &["read runtime instance registry", "is a directory"]);
- }
-
- #[test]
- fn load_registry_reports_parse_errors() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("instances.toml");
- fs::write(&path, "credential = 'secret-value'\nnot = [valid")
- .expect("write invalid registry");
-
- let err = load_registry(&path).expect_err("invalid registry should fail");
- assert_error_contains(&err, &["parse runtime instance registry"]);
- let rendered = format!("{err} {err:?}");
- assert!(!rendered.contains("secret-value"));
- assert!(!rendered.contains(path.to_string_lossy().as_ref()));
- }
-
- #[test]
- fn save_registry_reports_write_errors() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("registry-dir");
- fs::create_dir(&path).expect("create directory target");
-
- let err = save_registry(&path, &ManagedRuntimeInstanceRegistry::default())
- .expect_err("directory path should fail");
- assert_error_contains(&err, &["write runtime instance registry", "is a directory"]);
- }
-
- #[test]
- fn save_registry_reports_parent_creation_errors() {
- let dir = tempdir().expect("tempdir");
- let file_parent = dir.path().join("occupied");
- fs::write(&file_parent, "file").expect("occupied parent");
- let path = file_parent.join("instances.toml");
-
- let err = save_registry(&path, &ManagedRuntimeInstanceRegistry::default())
- .expect_err("file parent should fail");
- assert_error_contains(&err, &["create runtime instance registry parent"]);
- }
-
- #[test]
- fn save_registry_reports_serializer_errors() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("instances.toml");
-
- let err =
- save_registry_path_with(&path, &ManagedRuntimeInstanceRegistry::default(), |_| {
- Err(toml::ser::Error::custom(
- "forced registry serializer failure",
- ))
- })
- .expect_err("serializer should fail");
-
- assert_error_contains(&err, &["serialize runtime instance registry"]);
- }
-
- #[test]
- fn ensure_registry_parent_accepts_parentless_relative_paths() {
- ensure_registry_parent(Path::new("instances.toml")).expect("relative path parentless");
- ensure_registry_parent(Path::new("/")).expect("root path parentless");
- }
-
- #[test]
- fn upsert_instance_replaces_existing_and_sorts_new_records() {
- let mut registry = ManagedRuntimeInstanceRegistry::default();
- upsert_instance(&mut registry, sample_record("radrootsd", "b"));
- upsert_instance(&mut registry, sample_record("radrootsd", "a"));
- upsert_instance(&mut registry, sample_record("myc", "a"));
-
- let replacement = ManagedRuntimeInstanceRecord::new(
- &runtime_context("radrootsd", "b"),
- ManagedRuntimeInstallState::Failed,
- );
- upsert_instance(&mut registry, replacement);
-
- assert_eq!(registry.instances.len(), 3);
- assert_eq!(registry.instances[0].service_id().as_str(), "myc");
- assert_eq!(registry.instances[1].instance_id().as_str(), "a");
- assert_eq!(registry.instances[2].service_id().as_str(), "radrootsd");
- assert_eq!(registry.instances[2].instance_id().as_str(), "b");
- assert_eq!(
- registry.instances[2].install_state(),
- ManagedRuntimeInstallState::Failed
- );
- }
-
- #[test]
- fn instance_and_remove_instance_handle_missing_and_present_rows() {
- let mut registry = ManagedRuntimeInstanceRegistry::default();
- upsert_instance(&mut registry, sample_record("radrootsd", "local"));
-
- let myc = ServiceId::new("myc").expect("service");
- let radrootsd = ServiceId::new("radrootsd").expect("service");
- let local = InstanceId::new("local").expect("instance");
-
- assert!(instance(®istry, &myc, &local).is_none());
- assert!(remove_instance(&mut registry, &myc, &local).is_none());
-
- let removed = remove_instance(&mut registry, &radrootsd, &local).expect("remove");
- assert_eq!(removed.service_id().as_str(), "radrootsd");
- assert!(registry.instances.is_empty());
- }
-
- #[test]
- fn registry_round_trip_is_typed_sorted_and_contains_no_service_paths_or_secrets() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("instances.toml");
- let mut registry = ManagedRuntimeInstanceRegistry::default();
- upsert_instance(&mut registry, sample_record("rhi", "secondary"));
- upsert_instance(&mut registry, sample_record("myc", "primary"));
-
- save_registry(&path, ®istry).expect("save registry");
- let raw = fs::read_to_string(&path).expect("read registry");
- for forbidden in [
- "binary_path",
- "config_path",
- "logs_path",
- "run_path",
- "secrets_path",
- "secret_material_ref",
- "/repo/",
- "/etc/",
- ] {
- assert!(!raw.contains(forbidden), "registry leaked `{forbidden}`");
- }
-
- let loaded = load_registry(&path).expect("load registry");
- assert_eq!(loaded, registry);
- assert_eq!(loaded.instances[0].service_id().as_str(), "myc");
- assert_eq!(loaded.instances[1].service_id().as_str(), "rhi");
- }
-
- #[test]
- fn registry_rejects_schema_version_unknown_fields_and_duplicate_keys() {
- let dir = tempdir().expect("tempdir");
- let path = dir.path().join("instances.toml");
- let registry = ManagedRuntimeInstanceRegistry {
- instances: vec![sample_record("myc", "primary")],
- ..ManagedRuntimeInstanceRegistry::default()
- };
- save_registry(&path, ®istry).expect("save registry");
- let raw = fs::read_to_string(&path).expect("read registry");
-
- fs::write(
- &path,
- raw.replace("radroots.service-instance-registry", "wrong"),
- )
- .expect("write wrong schema");
- assert!(matches!(
- load_registry(&path),
- Err(RadrootsRuntimeManagerError::UnexpectedRegistrySchema)
- ));
-
- fs::write(
- &path,
- raw.replace("schema_version = 1", "schema_version = 2"),
- )
- .expect("write wrong version");
- assert!(matches!(
- load_registry(&path),
- Err(RadrootsRuntimeManagerError::UnexpectedRegistryVersion)
- ));
-
- fs::write(&path, format!("{raw}\nunknown = true\n")).expect("write unknown field");
- assert!(matches!(
- load_registry(&path),
- Err(RadrootsRuntimeManagerError::ParseRegistry)
- ));
-
- let duplicate = ManagedRuntimeInstanceRegistry {
- instances: vec![
- sample_record("myc", "primary"),
- sample_record("myc", "primary"),
- ],
- ..ManagedRuntimeInstanceRegistry::default()
- };
- assert!(matches!(
- save_registry(&path, &duplicate),
- Err(RadrootsRuntimeManagerError::DuplicateRegistryInstance)
- ));
- }
-}
diff --git a/crates/runtime_manager/tests/service_target_boundary.rs b/crates/runtime_manager/tests/service_target_boundary.rs
@@ -1,8 +1,19 @@
const MANAGEMENT_FIXTURE: &str = include_str!("fixtures/hardened_service_management.v1.toml");
const MANAGER_ROOT_SOURCE: &str = include_str!("../src/lib.rs");
+const MANAGER_SOURCE: &str = include_str!("../src/managed.rs");
+const MODEL_SOURCE: &str = include_str!("../src/model.rs");
+const MANIFEST: &str = include_str!("../Cargo.toml");
+const README: &str = include_str!("../README");
+
+fn production_source(source: &str) -> &str {
+ source
+ .split("\n#[cfg(test)]")
+ .next()
+ .expect("production source")
+}
#[test]
-fn hardened_services_remain_metadata_only_in_management_contract() {
+fn hardened_services_remain_exact_metadata_only_targets() {
for forbidden in [
"active = [\"myc",
"active = [\"rhi",
@@ -41,3 +52,56 @@ fn management_contract_is_bounded_before_toml_admission() {
"contract size must be checked before parsing"
);
}
+
+#[test]
+fn public_package_contains_only_metadata_resolution_authority() {
+ let production = [
+ production_source(MANAGER_ROOT_SOURCE),
+ production_source(MANAGER_SOURCE),
+ production_source(MODEL_SOURCE),
+ ]
+ .join("\n");
+ for forbidden in [
+ "std::fs",
+ "std::process",
+ "std::path",
+ "radroots_runtime_paths::RuntimeContext",
+ "ManagedRuntimeArtifactName",
+ "ManagedRuntimeInstancePaths",
+ "ManagedRuntimeSharedPaths",
+ "ManagedRuntimeInstanceRegistry",
+ "ManagedRuntimeInstanceRecord",
+ "load_registry",
+ "save_registry",
+ "register_instance",
+ "remove_instance",
+ "start_process",
+ "stop_process",
+ "process_running",
+ "install_binary",
+ "extract_binary_archive",
+ "remove_instance_artifacts",
+ "write_instance_config",
+ "inspect_runtime_",
+ ] {
+ assert!(
+ !production.contains(forbidden),
+ "production surface retained `{forbidden}`"
+ );
+ }
+
+ for forbidden_dependency in ["flate2", "tar =", "tempfile"] {
+ assert!(
+ !MANIFEST.contains(forbidden_dependency),
+ "manifest retained `{forbidden_dependency}`"
+ );
+ }
+
+ for required in [
+ "performs no filesystem, registry, process, archive, artifact",
+ "runtime paths or raw persistence helpers",
+ "Steps 219 and 220",
+ ] {
+ assert!(README.contains(required), "README omitted `{required}`");
+ }
+}