commit 3e47b6c508f338570748139f810b4d71bd26b8bf
parent adc3cdc49e3f08636c9825135d5edf2f5dcbf130
Author: triesap <tyson@radroots.org>
Date: Thu, 9 Apr 2026 16:24:03 +0000
myc: report path override posture
Diffstat:
3 files changed, 90 insertions(+), 1 deletion(-)
diff --git a/.env.example b/.env.example
@@ -11,6 +11,13 @@ MYC_LOGGING_FILTER=info,myc=info
MYC_LOGGING_STDOUT=true
MYC_CUSTODY_EXTERNAL_COMMAND_TIMEOUT_SECS=10
+# The canonical control plane is profile/root selection:
+# MYC_PATHS_PROFILE selects interactive_user, service_host, or repo_local
+# MYC_PATHS_REPO_LOCAL_ROOT selects the repo-local root when profile=repo_local
+# The leaf path keys below remain supported as config-file compatibility
+# overrides for fixture, migration, and break-glass use; do not export them as
+# the normal process-env control plane.
+#
# service_host defaults resolve to:
# MYC_LOGGING_OUTPUT_DIR=/var/log/radroots/services/myc
# MYC_PATHS_STATE_DIR=/var/lib/radroots/services/myc/state
@@ -18,7 +25,8 @@ MYC_CUSTODY_EXTERNAL_COMMAND_TIMEOUT_SECS=10
# MYC_PATHS_USER_IDENTITY_PATH=/etc/radroots/secrets/services/myc/user-identity.json
# MYC_DISCOVERY_APP_IDENTITY_PATH=/etc/radroots/secrets/services/myc/discovery-app-identity.json
# MYC_DISCOVERY_NIP05_OUTPUT_PATH=/var/lib/radroots/services/myc/public/.well-known/nostr.json
-# set explicit path variables only when overriding the canonical profile-derived locations
+# leave explicit leaf path variables commented unless this config artifact is
+# intentionally overriding a profile-derived location
MYC_PATHS_SIGNER_IDENTITY_BACKEND=encrypted_file
# shared backends: encrypted_file, host_vault, external_command, plaintext_file
# runtime-specific custody mode: managed_account
diff --git a/src/config.rs b/src/config.rs
@@ -168,6 +168,15 @@ pub struct MycRuntimeContractOutput {
pub runtime_specific_custody_modes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub host_vault_policy: Option<String>,
+ pub path_overrides: MycRuntimePathOverrideContractOutput,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub struct MycRuntimePathOverrideContractOutput {
+ pub canonical_root_selection: &'static str,
+ pub canonical_subordinate_path_override: &'static str,
+ pub leaf_path_env_posture: &'static str,
+ pub compatibility_leaf_path_keys: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -381,6 +390,17 @@ const MYC_ALLOWED_SHARED_SECRET_BACKENDS: [MycIdentityBackend; 4] = [
const MYC_RUNTIME_SPECIFIC_CUSTODY_MODES: [&str; 1] = ["managed_account"];
const MYC_DEFAULT_SHARED_SECRET_BACKEND: MycIdentityBackend = MycIdentityBackend::EncryptedFile;
const MYC_HOST_VAULT_POLICY: &str = "desktop";
+const MYC_CANONICAL_ROOT_SELECTION: &str = "profile_root_env_or_repo_wrapper";
+const MYC_CANONICAL_SUBORDINATE_PATH_OVERRIDE: &str = "config_artifact";
+const MYC_LEAF_PATH_ENV_POSTURE: &str = "compatibility_break_glass";
+const MYC_COMPATIBILITY_LEAF_PATH_KEYS: [&str; 6] = [
+ "MYC_LOGGING_OUTPUT_DIR",
+ "MYC_PATHS_STATE_DIR",
+ "MYC_PATHS_SIGNER_IDENTITY_PATH",
+ "MYC_PATHS_USER_IDENTITY_PATH",
+ "MYC_DISCOVERY_APP_IDENTITY_PATH",
+ "MYC_DISCOVERY_NIP05_OUTPUT_PATH",
+];
impl MycRuntimeContractOutput {
pub fn for_active_profile(active_profile: MycPathProfile) -> Self {
@@ -394,6 +414,21 @@ impl MycRuntimeContractOutput {
.map(str::to_owned)
.collect(),
host_vault_policy: Some(MYC_HOST_VAULT_POLICY.to_owned()),
+ path_overrides: MycRuntimePathOverrideContractOutput::current(),
+ }
+ }
+}
+
+impl MycRuntimePathOverrideContractOutput {
+ pub fn current() -> Self {
+ Self {
+ canonical_root_selection: MYC_CANONICAL_ROOT_SELECTION,
+ canonical_subordinate_path_override: MYC_CANONICAL_SUBORDINATE_PATH_OVERRIDE,
+ leaf_path_env_posture: MYC_LEAF_PATH_ENV_POSTURE,
+ compatibility_leaf_path_keys: MYC_COMPATIBILITY_LEAF_PATH_KEYS
+ .into_iter()
+ .map(str::to_owned)
+ .collect(),
}
}
}
@@ -2966,5 +3001,28 @@ MYC_PERSISTENCE_SIGNER_STATE_BACKEND=sqlite
MycConfig::runtime_specific_custody_modes()
);
assert_eq!(contract.host_vault_policy, MycConfig::host_vault_policy());
+ assert_eq!(
+ contract.path_overrides.canonical_root_selection,
+ "profile_root_env_or_repo_wrapper"
+ );
+ assert_eq!(
+ contract.path_overrides.canonical_subordinate_path_override,
+ "config_artifact"
+ );
+ assert_eq!(
+ contract.path_overrides.leaf_path_env_posture,
+ "compatibility_break_glass"
+ );
+ assert_eq!(
+ contract.path_overrides.compatibility_leaf_path_keys,
+ [
+ "MYC_LOGGING_OUTPUT_DIR",
+ "MYC_PATHS_STATE_DIR",
+ "MYC_PATHS_SIGNER_IDENTITY_PATH",
+ "MYC_PATHS_USER_IDENTITY_PATH",
+ "MYC_DISCOVERY_APP_IDENTITY_PATH",
+ "MYC_DISCOVERY_NIP05_OUTPUT_PATH"
+ ]
+ );
}
}
diff --git a/tests/operability_cli.rs b/tests/operability_cli.rs
@@ -84,6 +84,29 @@ fn status_summary_command_emits_machine_readable_json() {
"interactive_user"
);
assert_eq!(
+ value["runtime_contract"]["path_overrides"]["canonical_root_selection"],
+ "profile_root_env_or_repo_wrapper"
+ );
+ assert_eq!(
+ value["runtime_contract"]["path_overrides"]["canonical_subordinate_path_override"],
+ "config_artifact"
+ );
+ assert_eq!(
+ value["runtime_contract"]["path_overrides"]["leaf_path_env_posture"],
+ "compatibility_break_glass"
+ );
+ assert_eq!(
+ value["runtime_contract"]["path_overrides"]["compatibility_leaf_path_keys"],
+ json!([
+ "MYC_LOGGING_OUTPUT_DIR",
+ "MYC_PATHS_STATE_DIR",
+ "MYC_PATHS_SIGNER_IDENTITY_PATH",
+ "MYC_PATHS_USER_IDENTITY_PATH",
+ "MYC_DISCOVERY_APP_IDENTITY_PATH",
+ "MYC_DISCOVERY_NIP05_OUTPUT_PATH"
+ ])
+ );
+ assert_eq!(
value["runtime_contract"]["allowed_profiles"],
json!(["interactive_user", "service_host", "repo_local"])
);