commit 5ddb930d7d2ded7758c765b4a3dc86720e5164c2
parent 915d09d1d2c4d3259f70a58533d69d95e5768e70
Author: triesap <tyson@radroots.org>
Date: Thu, 9 Apr 2026 16:55:37 +0000
runtime: report path provenance
Diffstat:
| M | src/config.rs | | | 15 | +++++++++++++++ |
| M | src/main.rs | | | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/paths.rs | | | 94 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
3 files changed, 181 insertions(+), 10 deletions(-)
diff --git a/src/config.rs b/src/config.rs
@@ -368,6 +368,21 @@ replay_overlap_secs = 45
.expect("interactive-user contract");
assert_eq!(contract.active_profile, "interactive_user");
+ assert_eq!(contract.path_overrides.profile_source, "caller");
+ assert_eq!(contract.path_overrides.root_source, "host_defaults");
+ assert_eq!(contract.path_overrides.repo_local_root, None);
+ assert_eq!(contract.path_overrides.repo_local_root_source, None);
+ assert_eq!(
+ contract.path_overrides.subordinate_path_override_source,
+ "config_artifact"
+ );
+ assert_eq!(
+ contract.path_overrides.subordinate_path_override_keys,
+ vec![
+ "config.service.logs_dir".to_owned(),
+ "config.subscriber.state.path".to_owned(),
+ ]
+ );
assert_eq!(
contract.allowed_profiles,
vec![
diff --git a/src/main.rs b/src/main.rs
@@ -46,13 +46,18 @@ fn run_load_hook() -> &'static std::sync::Mutex<Option<Result<(cli_args, config:
struct RhiRuntimeStartupReport {
active_profile: String,
config_path: PathBuf,
+ config_path_source: String,
canonical_config_path: PathBuf,
logs_dir: PathBuf,
+ logs_dir_source: String,
canonical_logs_dir: PathBuf,
identity_path: PathBuf,
+ identity_path_source: String,
canonical_identity_path: PathBuf,
subscriber_state_path: PathBuf,
+ subscriber_state_path_source: String,
canonical_subscriber_state_path: PathBuf,
+ path_overrides: paths::RhiRuntimePathOverrideContractOutput,
default_shared_secret_backend: String,
allowed_shared_secret_backends: Vec<String>,
}
@@ -101,33 +106,89 @@ fn runtime_startup_report(
.config
.clone()
.unwrap_or_else(|| contract.canonical_config_path.clone()),
+ config_path_source: cli_or_profile_path_source(
+ args.service.config.is_some(),
+ &args
+ .service
+ .config
+ .clone()
+ .unwrap_or_else(|| contract.canonical_config_path.clone()),
+ &contract.canonical_config_path,
+ ),
canonical_config_path: contract.canonical_config_path.clone(),
logs_dir: PathBuf::from(settings.config.service.logs_dir.as_str()),
+ logs_dir_source: config_or_profile_path_source(
+ &PathBuf::from(settings.config.service.logs_dir.as_str()),
+ &contract.canonical_logs_dir,
+ ),
canonical_logs_dir: contract.canonical_logs_dir.clone(),
identity_path: args
.service
.identity
.clone()
.unwrap_or_else(|| contract.canonical_identity_path.clone()),
+ identity_path_source: cli_or_profile_path_source(
+ args.service.identity.is_some(),
+ &args
+ .service
+ .identity
+ .clone()
+ .unwrap_or_else(|| contract.canonical_identity_path.clone()),
+ &contract.canonical_identity_path,
+ ),
canonical_identity_path: contract.canonical_identity_path.clone(),
subscriber_state_path: settings.config.subscriber.state.path.clone(),
+ subscriber_state_path_source: config_or_profile_path_source(
+ &settings.config.subscriber.state.path,
+ &contract.canonical_subscriber_state_path,
+ ),
canonical_subscriber_state_path: contract.canonical_subscriber_state_path.clone(),
+ path_overrides: contract.path_overrides.clone(),
default_shared_secret_backend: contract.default_shared_secret_backend.clone(),
allowed_shared_secret_backends: contract.allowed_shared_secret_backends.clone(),
}
}
+fn cli_or_profile_path_source(
+ is_cli_arg: bool,
+ actual_path: &PathBuf,
+ canonical_path: &PathBuf,
+) -> String {
+ if is_cli_arg {
+ "cli_arg".to_owned()
+ } else {
+ config_or_profile_path_source(actual_path, canonical_path)
+ }
+}
+
+fn config_or_profile_path_source(actual_path: &PathBuf, canonical_path: &PathBuf) -> String {
+ if actual_path == canonical_path {
+ "profile_default".to_owned()
+ } else {
+ "config_artifact".to_owned()
+ }
+}
+
#[cfg(not(test))]
fn log_runtime_startup_report(report: &RhiRuntimeStartupReport) {
info!(
active_profile = report.active_profile.as_str(),
+ profile_source = report.path_overrides.profile_source.as_str(),
+ root_source = report.path_overrides.root_source.as_str(),
+ repo_local_root = ?report.path_overrides.repo_local_root,
+ repo_local_root_source = ?report.path_overrides.repo_local_root_source,
+ subordinate_path_override_source = report.path_overrides.subordinate_path_override_source.as_str(),
config_path = %report.config_path.display(),
+ config_path_source = report.config_path_source.as_str(),
canonical_config_path = %report.canonical_config_path.display(),
logs_dir = %report.logs_dir.display(),
+ logs_dir_source = report.logs_dir_source.as_str(),
canonical_logs_dir = %report.canonical_logs_dir.display(),
identity_path = %report.identity_path.display(),
+ identity_path_source = report.identity_path_source.as_str(),
canonical_identity_path = %report.canonical_identity_path.display(),
subscriber_state_path = %report.subscriber_state_path.display(),
+ subscriber_state_path_source = report.subscriber_state_path_source.as_str(),
canonical_subscriber_state_path = %report.canonical_subscriber_state_path.display(),
default_shared_secret_backend = report.default_shared_secret_backend.as_str(),
allowed_shared_secret_backends = ?report.allowed_shared_secret_backends,
@@ -189,6 +250,17 @@ mod tests {
"service_host".to_string(),
"repo_local".to_string(),
],
+ path_overrides: paths::RhiRuntimePathOverrideContractOutput {
+ profile_source: "caller".to_string(),
+ root_source: "host_defaults".to_string(),
+ repo_local_root: None,
+ repo_local_root_source: None,
+ subordinate_path_override_source: "config_artifact".to_string(),
+ subordinate_path_override_keys: vec![
+ "config.service.logs_dir".to_string(),
+ "config.subscriber.state.path".to_string(),
+ ],
+ },
default_shared_secret_backend: "encrypted_file".to_string(),
allowed_shared_secret_backends: vec!["encrypted_file".to_string()],
canonical_config_path: PathBuf::from(
@@ -306,19 +378,24 @@ mod tests {
RhiRuntimeStartupReport {
active_profile: "interactive_user".to_string(),
config_path: PathBuf::from("/tmp/rhi/config.toml"),
+ config_path_source: "cli_arg".to_string(),
canonical_config_path: PathBuf::from(
"/home/treesap/.radroots/config/workers/rhi/config.toml"
),
logs_dir: PathBuf::from("/tmp/rhi/logs"),
+ logs_dir_source: "config_artifact".to_string(),
canonical_logs_dir: PathBuf::from("/home/treesap/.radroots/logs/workers/rhi"),
identity_path: PathBuf::from("/tmp/rhi/identity.secret.json"),
+ identity_path_source: "cli_arg".to_string(),
canonical_identity_path: PathBuf::from(
"/home/treesap/.radroots/secrets/workers/rhi/identity.secret.json"
),
subscriber_state_path: PathBuf::from("/tmp/rhi/state.json"),
+ subscriber_state_path_source: "config_artifact".to_string(),
canonical_subscriber_state_path: PathBuf::from(
"/home/treesap/.radroots/data/workers/rhi/trade-listing/state.json"
),
+ path_overrides: sample_runtime_contract().path_overrides,
default_shared_secret_backend: "encrypted_file".to_string(),
allowed_shared_secret_backends: vec!["encrypted_file".to_string()],
}
@@ -342,12 +419,17 @@ mod tests {
let report = runtime_startup_report(&args, &settings, &contract);
assert_eq!(report.config_path, contract.canonical_config_path);
+ assert_eq!(report.config_path_source, "profile_default");
assert_eq!(report.logs_dir, contract.canonical_logs_dir);
+ assert_eq!(report.logs_dir_source, "profile_default");
assert_eq!(report.identity_path, contract.canonical_identity_path);
+ assert_eq!(report.identity_path_source, "profile_default");
assert_eq!(
report.subscriber_state_path,
contract.canonical_subscriber_state_path
);
+ assert_eq!(report.subscriber_state_path_source, "profile_default");
+ assert_eq!(report.path_overrides, contract.path_overrides);
assert_eq!(report.default_shared_secret_backend, "encrypted_file");
assert_eq!(
report.allowed_shared_secret_backends,
diff --git a/src/paths.rs b/src/paths.rs
@@ -15,6 +15,9 @@ const RHI_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RHI_PATHS_REPO_LOCAL_ROOT";
const RHI_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file";
const RHI_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"];
const RHI_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"];
+const SUBORDINATE_PATH_OVERRIDE_SOURCE: &str = "config_artifact";
+const SUBORDINATE_PATH_OVERRIDE_KEYS: [&str; 2] =
+ ["config.service.logs_dir", "config.subscriber.state.path"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RhiRuntimePaths {
@@ -28,6 +31,7 @@ pub(crate) struct RhiRuntimePaths {
pub struct RhiRuntimeContractOutput {
pub active_profile: String,
pub allowed_profiles: Vec<String>,
+ pub path_overrides: RhiRuntimePathOverrideContractOutput,
pub default_shared_secret_backend: String,
pub allowed_shared_secret_backends: Vec<String>,
pub canonical_config_path: PathBuf,
@@ -36,6 +40,25 @@ pub struct RhiRuntimeContractOutput {
pub canonical_subscriber_state_path: PathBuf,
}
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub struct RhiRuntimePathOverrideContractOutput {
+ pub profile_source: String,
+ pub root_source: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub repo_local_root: Option<PathBuf>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub repo_local_root_source: Option<String>,
+ pub subordinate_path_override_source: String,
+ pub subordinate_path_override_keys: Vec<String>,
+}
+
+struct RhiRuntimePathSelection {
+ profile: RadrootsPathProfile,
+ profile_source: String,
+ repo_local_root: Option<PathBuf>,
+ repo_local_root_source: Option<String>,
+}
+
fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> {
match value {
"interactive_user" => Ok(RadrootsPathProfile::InteractiveUser),
@@ -48,15 +71,33 @@ fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> {
}
pub(crate) fn process_path_selection() -> Result<(RadrootsPathProfile, Option<PathBuf>)> {
+ let selection = process_path_selection_with_sources()?;
+ Ok((selection.profile, selection.repo_local_root))
+}
+
+fn process_path_selection_with_sources() -> Result<RhiRuntimePathSelection> {
let profile = match std::env::var(RHI_PATHS_PROFILE_ENV) {
- Ok(value) => parse_path_profile(&value)?,
- Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser,
+ Ok(value) => (
+ parse_path_profile(&value)?,
+ format!("process_env:{RHI_PATHS_PROFILE_ENV}"),
+ ),
+ Err(std::env::VarError::NotPresent) => {
+ (RadrootsPathProfile::InteractiveUser, "default".to_owned())
+ }
Err(std::env::VarError::NotUnicode(_)) => {
bail!("{RHI_PATHS_PROFILE_ENV} must be valid utf-8 when set")
}
};
- let repo_local_root = std::env::var_os(RHI_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from);
- Ok((profile, repo_local_root))
+ let repo_local_root_raw = std::env::var_os(RHI_PATHS_REPO_LOCAL_ROOT_ENV);
+ let repo_local_root = repo_local_root_raw.as_ref().map(PathBuf::from);
+ Ok(RhiRuntimePathSelection {
+ profile: profile.0,
+ profile_source: profile.1,
+ repo_local_root,
+ repo_local_root_source: repo_local_root_raw
+ .as_ref()
+ .map(|_| format!("process_env:{RHI_PATHS_REPO_LOCAL_ROOT_ENV}")),
+ })
}
fn path_overrides_for(
@@ -119,12 +160,8 @@ pub fn default_subscriber_state_path_for_process() -> Result<PathBuf> {
}
pub fn runtime_contract_for_process() -> Result<RhiRuntimeContractOutput> {
- let (profile, repo_local_root) = process_path_selection()?;
- runtime_contract_with_resolver(
- &RadrootsPathResolver::current(),
- profile,
- repo_local_root.as_deref(),
- )
+ let selection = process_path_selection_with_sources()?;
+ runtime_contract_with_selection(&RadrootsPathResolver::current(), &selection)
}
pub(crate) fn runtime_contract_with_resolver(
@@ -132,6 +169,23 @@ pub(crate) fn runtime_contract_with_resolver(
profile: RadrootsPathProfile,
repo_local_root: Option<&Path>,
) -> Result<RhiRuntimeContractOutput> {
+ runtime_contract_with_selection(
+ resolver,
+ &RhiRuntimePathSelection {
+ profile,
+ profile_source: "caller".to_owned(),
+ repo_local_root: repo_local_root.map(Path::to_path_buf),
+ repo_local_root_source: repo_local_root.map(|_| "caller".to_owned()),
+ },
+ )
+}
+
+fn runtime_contract_with_selection(
+ resolver: &RadrootsPathResolver,
+ selection: &RhiRuntimePathSelection,
+) -> Result<RhiRuntimeContractOutput> {
+ let profile = selection.profile;
+ let repo_local_root = selection.repo_local_root.as_deref();
let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?;
Ok(RhiRuntimeContractOutput {
active_profile: profile.to_string(),
@@ -139,6 +193,17 @@ pub(crate) fn runtime_contract_with_resolver(
.into_iter()
.map(str::to_owned)
.collect(),
+ path_overrides: RhiRuntimePathOverrideContractOutput {
+ profile_source: selection.profile_source.clone(),
+ root_source: root_source_for_profile(profile).to_owned(),
+ repo_local_root: selection.repo_local_root.clone(),
+ repo_local_root_source: selection.repo_local_root_source.clone(),
+ subordinate_path_override_source: SUBORDINATE_PATH_OVERRIDE_SOURCE.to_owned(),
+ subordinate_path_override_keys: SUBORDINATE_PATH_OVERRIDE_KEYS
+ .into_iter()
+ .map(str::to_owned)
+ .collect(),
+ },
default_shared_secret_backend: RHI_DEFAULT_SHARED_SECRET_BACKEND.to_owned(),
allowed_shared_secret_backends: RHI_ALLOWED_SHARED_SECRET_BACKENDS
.into_iter()
@@ -150,3 +215,12 @@ pub(crate) fn runtime_contract_with_resolver(
canonical_subscriber_state_path: paths.subscriber_state_path,
})
}
+
+fn root_source_for_profile(profile: RadrootsPathProfile) -> &'static str {
+ match profile {
+ RadrootsPathProfile::InteractiveUser => "host_defaults",
+ RadrootsPathProfile::ServiceHost => "service_host_defaults",
+ RadrootsPathProfile::RepoLocal => "repo_local_root",
+ RadrootsPathProfile::MobileNative => "mobile_native_defaults",
+ }
+}