myc

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

commit 3e47b6c508f338570748139f810b4d71bd26b8bf
parent adc3cdc49e3f08636c9825135d5edf2f5dcbf130
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Apr 2026 16:24:03 +0000

myc: report path override posture

Diffstat:
M.env.example | 10+++++++++-
Msrc/config.rs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/operability_cli.rs | 23+++++++++++++++++++++++
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"]) );