cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit ba7c536e00a0775e279eafcdca36d7597b6deb37
parent bc4bca0e24f29650525394730d78b36613233779
Author: triesap <tyson@radroots.org>
Date:   Fri, 10 Apr 2026 19:32:55 +0000

runtime: add resolved provider views

Diffstat:
Msrc/commands/doctor.rs | 11++++-------
Msrc/commands/runtime.rs | 54++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/domain/runtime.rs | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/render/mod.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/runtime/config.rs | 2+-
Msrc/runtime/hyf.rs | 4++--
Msrc/runtime/mod.rs | 2+-
Asrc/runtime/provider.rs | 428+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/runtime/workflow.rs | 178-------------------------------------------------------------------------------
Mtests/runtime_show.rs | 34++++++++++++++++++++++++++++++++++
10 files changed, 634 insertions(+), 193 deletions(-)

diff --git a/src/commands/doctor.rs b/src/commands/doctor.rs @@ -3,10 +3,9 @@ use crate::domain::runtime::{ }; use crate::runtime::RuntimeError; use crate::runtime::config::{RuntimeConfig, SignerBackend}; -use crate::runtime::hyf::resolve_runtime_status as resolve_hyf_status; use crate::runtime::logging::LoggingState; +use crate::runtime::provider::{resolve_hyf_provider, resolve_workflow_provider}; use crate::runtime::signer::resolve_signer_status; -use crate::runtime::workflow::resolve_workflow_provider; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum DoctorSeverity { @@ -59,7 +58,7 @@ pub fn report( } } - checks.push(hyf_check(&resolve_hyf_status(config))); + checks.push(hyf_check(&resolve_hyf_provider(config))); checks.push(workflow_check(&resolve_workflow_provider(config))); checks.push(logging_check(config, logging)); checks.push(binding_check(config)); @@ -269,7 +268,7 @@ fn myc_check(myc: &crate::domain::runtime::MycStatusView) -> EvaluatedCheck { } } -fn hyf_check(hyf: &crate::runtime::hyf::HyfStatusView) -> EvaluatedCheck { +fn hyf_check(hyf: &crate::runtime::provider::HyfProviderView) -> EvaluatedCheck { let (severity, detail) = match hyf.state.as_str() { "disabled" => ( DoctorSeverity::Ok, @@ -302,9 +301,7 @@ fn hyf_check(hyf: &crate::runtime::hyf::HyfStatusView) -> EvaluatedCheck { } } -fn workflow_check( - workflow: &crate::runtime::workflow::WorkflowProviderStatusView, -) -> EvaluatedCheck { +fn workflow_check(workflow: &crate::runtime::provider::WorkflowProviderView) -> EvaluatedCheck { EvaluatedCheck { severity: DoctorSeverity::Ok, view: DoctorCheckView { diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs @@ -1,20 +1,28 @@ use crate::domain::runtime::{ AccountRuntimeView, AccountSecretRuntimeView, CapabilityBindingRuntimeView, - ConfigFilesRuntimeView, ConfigShowView, HyfRuntimeView, LegacyPathRuntimeView, - LocalRuntimeView, LoggingRuntimeView, MigrationRuntimeView, MycRuntimeView, OutputRuntimeView, - PathsRuntimeView, RelayRuntimeView, RpcRuntimeView, SignerRuntimeView, WorkflowRuntimeView, + ConfigFilesRuntimeView, ConfigShowView, HyfProviderRuntimeView, HyfRuntimeView, + LegacyPathRuntimeView, LocalRuntimeView, LoggingRuntimeView, MigrationRuntimeView, + MycRuntimeView, OutputRuntimeView, PathsRuntimeView, RelayRuntimeView, + ResolvedProviderRuntimeView, RpcRuntimeView, SignerRuntimeView, WorkflowRuntimeView, + WritePlaneRuntimeView, }; use crate::runtime::RuntimeError; use crate::runtime::config::RuntimeConfig; use crate::runtime::logging::LoggingState; -use crate::runtime::workflow::resolve_workflow_provider; +use crate::runtime::provider::{ + resolve_capability_providers, resolve_hyf_provider, resolve_workflow_provider, + resolve_write_plane_provider, +}; pub fn show( config: &RuntimeConfig, logging: &LoggingState, ) -> Result<ConfigShowView, RuntimeError> { let secret_backend = crate::runtime::accounts::secret_backend_status(config); + let write_plane = resolve_write_plane_provider(config); let workflow = resolve_workflow_provider(config); + let hyf_provider = resolve_hyf_provider(config); + let resolved_providers = resolve_capability_providers(config); Ok(ConfigShowView { source: "local runtime state".to_owned(), output: OutputRuntimeView { @@ -106,16 +114,41 @@ pub fn show( myc: MycRuntimeView { executable: config.myc.executable.display().to_string(), }, + write_plane: WritePlaneRuntimeView { + provider_runtime_id: write_plane.provider_runtime_id, + binding_model: write_plane.binding_model, + state: write_plane.state, + provenance: write_plane.provenance, + source: write_plane.source, + target_kind: write_plane.target_kind, + target: write_plane.target, + detail: write_plane.detail, + bridge_auth_configured: write_plane.bridge_auth_configured, + }, workflow: WorkflowRuntimeView { provider_runtime_id: workflow.provider_runtime_id, binding_model: workflow.binding_model, state: workflow.state, + provenance: workflow.provenance, source: workflow.source, target_kind: workflow.target_kind, target: workflow.target, hyf_helper_state: workflow.hyf_helper_state, hyf_helper_detail: workflow.hyf_helper_detail, }, + hyf_provider: HyfProviderRuntimeView { + provider_runtime_id: hyf_provider.provider_runtime_id, + binding_model: hyf_provider.binding_model, + state: hyf_provider.state, + provenance: hyf_provider.provenance, + source: hyf_provider.source, + target_kind: hyf_provider.target_kind, + target: hyf_provider.target, + executable: hyf_provider.executable, + reason: hyf_provider.reason, + protocol_version: hyf_provider.protocol_version, + deterministic_available: hyf_provider.deterministic_available, + }, hyf: HyfRuntimeView { enabled: config.hyf.enabled, executable: config.hyf.executable.display().to_string(), @@ -124,6 +157,19 @@ pub fn show( url: config.rpc.url.clone(), bridge_auth_configured: config.rpc.bridge_bearer_token.is_some(), }, + resolved_providers: resolved_providers + .into_iter() + .map(|provider| ResolvedProviderRuntimeView { + capability_id: provider.capability_id, + provider_runtime_id: provider.provider_runtime_id, + binding_model: provider.binding_model, + state: provider.state, + provenance: provider.provenance, + source: provider.source, + target_kind: provider.target_kind, + target: provider.target, + }) + .collect(), capability_bindings: config .inspect_capability_bindings() .into_iter() diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs @@ -118,10 +118,13 @@ pub struct ConfigShowView { pub relay: RelayRuntimeView, pub local: LocalRuntimeView, pub myc: MycRuntimeView, + pub write_plane: WritePlaneRuntimeView, pub workflow: WorkflowRuntimeView, + pub hyf_provider: HyfProviderRuntimeView, pub hyf: HyfRuntimeView, pub rpc: RpcRuntimeView, pub capability_bindings: Vec<CapabilityBindingRuntimeView>, + pub resolved_providers: Vec<ResolvedProviderRuntimeView>, } #[derive(Debug, Clone, Serialize)] @@ -251,6 +254,7 @@ pub struct WorkflowRuntimeView { pub provider_runtime_id: String, pub binding_model: String, pub state: String, + pub provenance: String, pub source: String, #[serde(skip_serializing_if = "Option::is_none")] pub target_kind: Option<String>, @@ -267,12 +271,61 @@ pub struct HyfRuntimeView { } #[derive(Debug, Clone, Serialize)] +pub struct HyfProviderRuntimeView { + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_kind: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option<String>, + pub executable: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub protocol_version: Option<u64>, + #[serde(skip_serializing_if = "Option::is_none")] + pub deterministic_available: Option<bool>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct WritePlaneRuntimeView { + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_kind: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option<String>, + pub detail: String, + pub bridge_auth_configured: bool, +} + +#[derive(Debug, Clone, Serialize)] pub struct RpcRuntimeView { pub url: String, pub bridge_auth_configured: bool, } #[derive(Debug, Clone, Serialize)] +pub struct ResolvedProviderRuntimeView { + pub capability_id: String, + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_kind: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option<String>, +} + +#[derive(Debug, Clone, Serialize)] pub struct CapabilityBindingRuntimeView { pub capability_id: String, pub provider_runtime_id: String, diff --git a/src/render/mod.rs b/src/render/mod.rs @@ -617,6 +617,27 @@ fn render_config_show( "myc", &[("executable", view.myc.executable.as_str())], )?; + let write_plane_target = format_runtime_target( + view.write_plane.target_kind.as_deref(), + view.write_plane.target.as_deref(), + ); + render_pairs( + stdout, + "write plane", + &[ + ("provider", view.write_plane.provider_runtime_id.as_str()), + ("binding model", view.write_plane.binding_model.as_str()), + ("state", view.write_plane.state.as_str()), + ("provenance", view.write_plane.provenance.as_str()), + ("source", view.write_plane.source.as_str()), + ("target", write_plane_target.as_str()), + ("detail", view.write_plane.detail.as_str()), + ( + "bridge auth configured", + yes_no(view.write_plane.bridge_auth_configured), + ), + ], + )?; let workflow_target = format_runtime_target( view.workflow.target_kind.as_deref(), view.workflow.target.as_deref(), @@ -628,6 +649,7 @@ fn render_config_show( ("provider", view.workflow.provider_runtime_id.as_str()), ("binding model", view.workflow.binding_model.as_str()), ("state", view.workflow.state.as_str()), + ("provenance", view.workflow.provenance.as_str()), ("source", view.workflow.source.as_str()), ("target", workflow_target.as_str()), ("hyf helper", view.workflow.hyf_helper_state.as_str()), @@ -645,6 +667,23 @@ fn render_config_show( ("executable", view.hyf.executable.as_str()), ], )?; + let hyf_provider_target = format_runtime_target( + view.hyf_provider.target_kind.as_deref(), + view.hyf_provider.target.as_deref(), + ); + let mut hyf_provider_rows = vec![ + ("provider", view.hyf_provider.provider_runtime_id.as_str()), + ("binding model", view.hyf_provider.binding_model.as_str()), + ("state", view.hyf_provider.state.as_str()), + ("provenance", view.hyf_provider.provenance.as_str()), + ("source", view.hyf_provider.source.as_str()), + ("target", hyf_provider_target.as_str()), + ("executable", view.hyf_provider.executable.as_str()), + ]; + if let Some(reason) = &view.hyf_provider.reason { + hyf_provider_rows.push(("reason", reason.as_str())); + } + render_pairs(stdout, "hyf provider", hyf_provider_rows.as_slice())?; render_pairs( stdout, "rpc", @@ -674,6 +713,28 @@ fn render_config_show( .collect(), }; render_table(stdout, &table)?; + writeln!(stdout)?; + writeln!(stdout, "resolved providers")?; + let resolved_table = Table { + headers: &["capability", "provider", "state", "provenance", "target"], + rows: view + .resolved_providers + .iter() + .map(|provider| { + vec![ + provider.capability_id.clone(), + provider.provider_runtime_id.clone(), + provider.state.clone(), + provider.provenance.clone(), + format_runtime_target( + provider.target_kind.as_deref(), + provider.target.as_deref(), + ), + ] + }) + .collect(), + }; + render_table(stdout, &resolved_table)?; writeln!(stdout, "source: {}", view.source)?; Ok(()) } diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -380,7 +380,7 @@ struct CapabilityBindingSpec { } pub(crate) const SIGNER_REMOTE_NIP46_CAPABILITY: &str = "signer.remote_nip46"; -const WRITE_PLANE_TRADE_JSONRPC_CAPABILITY: &str = "write_plane.trade_jsonrpc"; +pub(crate) const WRITE_PLANE_TRADE_JSONRPC_CAPABILITY: &str = "write_plane.trade_jsonrpc"; pub(crate) const WORKFLOW_TRADE_CAPABILITY: &str = "workflow.trade"; pub(crate) const INFERENCE_HYF_STDIO_CAPABILITY: &str = "inference.hyf_stdio"; diff --git a/src/runtime/hyf.rs b/src/runtime/hyf.rs @@ -378,7 +378,7 @@ mod tests { enabled: true, executable, }); - assert_eq!(view.state, "ready"); + assert_eq!(view.state, "ready", "reason: {:?}", view.reason); assert_eq!(view.protocol_version, Some(HYF_PROTOCOL_VERSION)); assert_eq!(view.deterministic_available, Some(true)); } @@ -396,7 +396,7 @@ mod tests { enabled: true, executable, }); - assert_eq!(view.state, "unavailable"); + assert_eq!(view.state, "unavailable", "reason: {:?}", view.reason); assert!( view.reason .as_deref() diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs @@ -11,9 +11,9 @@ pub mod myc; pub mod network; pub mod order; pub mod paths; +pub mod provider; pub mod signer; pub mod sync; -pub mod workflow; use std::process::ExitCode; diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs @@ -0,0 +1,428 @@ +use crate::runtime::config::{ + CapabilityBindingInspection, CapabilityBindingInspectionState, RuntimeConfig, + INFERENCE_HYF_STDIO_CAPABILITY, WORKFLOW_TRADE_CAPABILITY, + WRITE_PLANE_TRADE_JSONRPC_CAPABILITY, +}; +use crate::runtime::hyf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProviderProvenance { + ExplicitBinding, + ManagedDefault, + DirectConfig, + Disabled, + Unavailable, +} + +impl ProviderProvenance { + pub fn as_str(self) -> &'static str { + match self { + Self::ExplicitBinding => "explicit_binding", + Self::ManagedDefault => "managed_default", + Self::DirectConfig => "direct_config", + Self::Disabled => "disabled", + Self::Unavailable => "unavailable", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedProviderView { + pub capability_id: String, + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + pub target_kind: Option<String>, + pub target: Option<String>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WritePlaneProviderView { + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + pub target_kind: Option<String>, + pub target: Option<String>, + pub detail: String, + pub bridge_auth_configured: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkflowProviderView { + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + pub target_kind: Option<String>, + pub target: Option<String>, + pub hyf_helper_state: String, + pub hyf_helper_detail: String, +} + +impl WorkflowProviderView { + pub fn detail(&self) -> String { + match (self.target_kind.as_deref(), self.target.as_deref()) { + (Some(target_kind), Some(target)) if self.state == "configured" => { + format!( + "{} workflow provider configured via {} {}", + self.provider_runtime_id, target_kind, target + ) + } + _ if self.state == "configured" => { + format!("{} workflow provider configured", self.provider_runtime_id) + } + _ => self.source.clone(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HyfProviderView { + pub provider_runtime_id: String, + pub binding_model: String, + pub state: String, + pub provenance: String, + pub source: String, + pub target_kind: Option<String>, + pub target: Option<String>, + pub executable: String, + pub reason: Option<String>, + pub protocol_version: Option<u64>, + pub deterministic_available: Option<bool>, +} + +pub fn resolve_write_plane_provider(config: &RuntimeConfig) -> WritePlaneProviderView { + let _binding = inspect_binding(config, WRITE_PLANE_TRADE_JSONRPC_CAPABILITY); + WritePlaneProviderView { + provider_runtime_id: "radrootsd".to_owned(), + binding_model: "daemon_backed_jsonrpc".to_owned(), + state: "configured".to_owned(), + provenance: ProviderProvenance::DirectConfig.as_str().to_owned(), + source: "raw rpc config resolves the current write plane".to_owned(), + target_kind: None, + target: Some(config.rpc.url.clone()), + detail: "actor-authored durable writes still resolve through rpc.url until authoritative write-plane binding resolution lands".to_owned(), + bridge_auth_configured: config.rpc.bridge_bearer_token.is_some(), + } +} + +pub fn resolve_workflow_provider(config: &RuntimeConfig) -> WorkflowProviderView { + let binding = inspect_binding(config, WORKFLOW_TRADE_CAPABILITY); + let provenance = binding_provenance(&binding).as_str().to_owned(); + + WorkflowProviderView { + provider_runtime_id: binding.provider_runtime_id, + binding_model: binding.binding_model, + state: binding.state.as_str().to_owned(), + provenance, + source: binding.source, + target_kind: binding.target_kind, + target: binding.target, + hyf_helper_state: "not_implied".to_owned(), + hyf_helper_detail: + "cli bindings do not imply an rhi -> hyf helper path; any worker helper remains explicit and optional" + .to_owned(), + } +} + +pub fn resolve_hyf_provider(config: &RuntimeConfig) -> HyfProviderView { + let binding = inspect_binding(config, INFERENCE_HYF_STDIO_CAPABILITY); + let status = hyf::resolve_runtime_status(config); + let binding_configured = binding.state == CapabilityBindingInspectionState::Configured; + let provenance = if binding_configured { + binding_provenance(&binding) + } else if status.state == "disabled" { + ProviderProvenance::Disabled + } else { + ProviderProvenance::DirectConfig + } + .as_str() + .to_owned(); + let target_kind = hyf_target_kind(config, &binding); + let target = hyf_target(config, &binding); + let executable = hyf_executable(config, &binding, &status); + let source = if binding_configured { + binding.source.clone() + } else { + status.source.clone() + }; + + HyfProviderView { + provider_runtime_id: binding.provider_runtime_id, + binding_model: binding.binding_model, + state: status.state, + provenance, + source, + target_kind, + target, + executable, + reason: status.reason, + protocol_version: status.protocol_version, + deterministic_available: status.deterministic_available, + } +} + +pub fn resolve_capability_providers(config: &RuntimeConfig) -> Vec<ResolvedProviderView> { + let write = resolve_write_plane_provider(config); + let workflow = resolve_workflow_provider(config); + let hyf = resolve_hyf_provider(config); + + vec![ + ResolvedProviderView { + capability_id: WRITE_PLANE_TRADE_JSONRPC_CAPABILITY.to_owned(), + provider_runtime_id: write.provider_runtime_id, + binding_model: write.binding_model, + state: write.state, + provenance: write.provenance, + source: write.source, + target_kind: write.target_kind, + target: write.target, + }, + ResolvedProviderView { + capability_id: WORKFLOW_TRADE_CAPABILITY.to_owned(), + provider_runtime_id: workflow.provider_runtime_id, + binding_model: workflow.binding_model, + state: workflow.state, + provenance: workflow.provenance, + source: workflow.source, + target_kind: workflow.target_kind, + target: workflow.target, + }, + ResolvedProviderView { + capability_id: INFERENCE_HYF_STDIO_CAPABILITY.to_owned(), + provider_runtime_id: hyf.provider_runtime_id, + binding_model: hyf.binding_model, + state: hyf.state, + provenance: hyf.provenance, + source: hyf.source, + target_kind: hyf.target_kind, + target: hyf.target, + }, + ] +} + +fn inspect_binding(config: &RuntimeConfig, capability_id: &str) -> CapabilityBindingInspection { + config + .inspect_capability_bindings() + .into_iter() + .find(|binding| binding.capability_id == capability_id) + .expect("provider capability binding inspection must exist") +} + +fn binding_provenance(binding: &CapabilityBindingInspection) -> ProviderProvenance { + match binding.state { + CapabilityBindingInspectionState::Configured => match binding.target_kind.as_deref() { + Some("managed_instance") => ProviderProvenance::ManagedDefault, + _ => ProviderProvenance::ExplicitBinding, + }, + CapabilityBindingInspectionState::Disabled => ProviderProvenance::Disabled, + CapabilityBindingInspectionState::NotConfigured => ProviderProvenance::Unavailable, + } +} + +fn hyf_target_kind( + config: &RuntimeConfig, + binding: &CapabilityBindingInspection, +) -> Option<String> { + if binding.state == CapabilityBindingInspectionState::Configured { + return binding.target_kind.clone(); + } + if config.hyf.enabled { + return Some("direct_config".to_owned()); + } + None +} + +fn hyf_target(config: &RuntimeConfig, binding: &CapabilityBindingInspection) -> Option<String> { + if binding.state == CapabilityBindingInspectionState::Configured { + return binding.target.clone(); + } + if config.hyf.enabled { + return Some(config.hyf.executable.display().to_string()); + } + None +} + +fn hyf_executable( + config: &RuntimeConfig, + binding: &CapabilityBindingInspection, + status: &hyf::HyfStatusView, +) -> String { + if binding.state == CapabilityBindingInspectionState::Configured + && binding.target_kind.as_deref() == Some("explicit_endpoint") + { + return binding.target.clone().unwrap_or_else(|| status.executable.clone()); + } + if !config.hyf.enabled { + return status.executable.clone(); + } + status.executable.clone() +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use radroots_runtime_paths::RadrootsMigrationReport; + use radroots_secret_vault::RadrootsSecretBackend; + + use super::{ + ProviderProvenance, resolve_capability_providers, resolve_hyf_provider, + resolve_workflow_provider, resolve_write_plane_provider, + }; + use crate::runtime::config::{ + AccountConfig, AccountSecretContractConfig, CapabilityBindingConfig, + CapabilityBindingSource, CapabilityBindingTargetKind, HyfConfig, IdentityConfig, + LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, + PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, + SignerBackend, SignerConfig, Verbosity, + }; + + fn sample_config(bindings: Vec<CapabilityBindingConfig>, hyf_enabled: bool) -> RuntimeConfig { + RuntimeConfig { + output: OutputConfig { + format: OutputFormat::Human, + verbosity: Verbosity::Normal, + color: true, + dry_run: false, + }, + paths: PathsConfig { + profile: "interactive_user".into(), + profile_source: "default".into(), + allowed_profiles: vec!["interactive_user".into()], + root_source: "host_defaults".into(), + repo_local_root: None, + repo_local_root_source: None, + subordinate_path_override_source: "runtime_config".into(), + app_namespace: "apps/cli".into(), + shared_accounts_namespace: "shared/accounts".into(), + shared_identities_namespace: "shared/identities".into(), + app_config_path: PathBuf::from("/tmp/config.toml"), + workspace_config_path: PathBuf::from("/tmp/workspace-config.toml"), + app_data_root: PathBuf::from("/tmp/data"), + app_logs_root: PathBuf::from("/tmp/logs"), + shared_accounts_data_root: PathBuf::from("/tmp/shared/accounts"), + shared_accounts_secrets_root: PathBuf::from("/tmp/shared/accounts-secrets"), + default_identity_path: PathBuf::from("/tmp/default-identity.json"), + }, + migration: MigrationConfig { + report: RadrootsMigrationReport::empty(), + }, + logging: LoggingConfig { + filter: "info".into(), + directory: None, + stdout: true, + }, + account: AccountConfig { + selector: None, + store_path: PathBuf::from("/tmp/store.json"), + secrets_dir: PathBuf::from("/tmp/secrets"), + secret_backend: RadrootsSecretBackend::EncryptedFile, + secret_fallback: None, + }, + account_secret_contract: AccountSecretContractConfig { + default_backend: "host_vault".into(), + default_fallback: Some("encrypted_file".into()), + allowed_backends: vec!["host_vault".into(), "encrypted_file".into()], + host_vault_policy: Some("desktop".into()), + uses_protected_store: true, + }, + identity: IdentityConfig { + path: PathBuf::from("/tmp/default-identity.json"), + }, + signer: SignerConfig { + backend: SignerBackend::Local, + }, + relay: RelayConfig { + urls: Vec::new(), + publish_policy: RelayPublishPolicy::Any, + source: RelayConfigSource::Defaults, + }, + local: LocalConfig { + root: PathBuf::from("/tmp/local"), + replica_db_path: PathBuf::from("/tmp/local/replica.sqlite"), + backups_dir: PathBuf::from("/tmp/local/backups"), + exports_dir: PathBuf::from("/tmp/local/exports"), + }, + myc: MycConfig { + executable: PathBuf::from("myc"), + }, + hyf: HyfConfig { + enabled: hyf_enabled, + executable: PathBuf::from("hyfd"), + }, + rpc: RpcConfig { + url: "http://127.0.0.1:7070".into(), + bridge_bearer_token: None, + }, + capability_bindings: bindings, + } + } + + #[test] + fn write_plane_uses_direct_config_provenance() { + let view = resolve_write_plane_provider(&sample_config(Vec::new(), false)); + assert_eq!(view.provenance, ProviderProvenance::DirectConfig.as_str()); + assert_eq!(view.target.as_deref(), Some("http://127.0.0.1:7070")); + } + + #[test] + fn workflow_uses_explicit_binding_provenance_when_configured() { + let binding = CapabilityBindingConfig { + capability_id: "workflow.trade".into(), + provider_runtime_id: "rhi".into(), + binding_model: "out_of_process_worker".into(), + source: CapabilityBindingSource::WorkspaceConfig, + target_kind: CapabilityBindingTargetKind::ExplicitEndpoint, + target: "/tmp/rhi".into(), + managed_account_ref: None, + signer_session_ref: None, + }; + let view = resolve_workflow_provider(&sample_config(vec![binding], false)); + assert_eq!(view.provenance, ProviderProvenance::ExplicitBinding.as_str()); + assert_eq!(view.target_kind.as_deref(), Some("explicit_endpoint")); + } + + #[test] + fn hyf_uses_direct_config_when_enabled_without_binding() { + let view = resolve_hyf_provider(&sample_config(Vec::new(), true)); + assert_eq!(view.provenance, ProviderProvenance::DirectConfig.as_str()); + assert_eq!(view.target_kind.as_deref(), Some("direct_config")); + assert_eq!(view.target.as_deref(), Some("hyfd")); + } + + #[test] + fn hyf_binding_remains_visible_when_runtime_is_disabled() { + let binding = CapabilityBindingConfig { + capability_id: "inference.hyf_stdio".into(), + provider_runtime_id: "hyf".into(), + binding_model: "stdio_service".into(), + source: CapabilityBindingSource::UserConfig, + target_kind: CapabilityBindingTargetKind::ExplicitEndpoint, + target: "bin/hyfd-user".into(), + managed_account_ref: None, + signer_session_ref: None, + }; + let view = resolve_hyf_provider(&sample_config(vec![binding], false)); + assert_eq!(view.state, "disabled"); + assert_eq!(view.provenance, ProviderProvenance::ExplicitBinding.as_str()); + assert_eq!(view.source, "user config [[capability_binding]]"); + assert_eq!(view.target_kind.as_deref(), Some("explicit_endpoint")); + assert_eq!(view.target.as_deref(), Some("bin/hyfd-user")); + assert_eq!(view.executable, "bin/hyfd-user"); + } + + #[test] + fn capability_provider_list_covers_write_workflow_and_hyf() { + let providers = resolve_capability_providers(&sample_config(Vec::new(), false)); + assert_eq!(providers.len(), 3); + assert_eq!(providers[0].capability_id, "write_plane.trade_jsonrpc"); + assert_eq!(providers[1].capability_id, "workflow.trade"); + assert_eq!(providers[2].capability_id, "inference.hyf_stdio"); + } +} diff --git a/src/runtime/workflow.rs b/src/runtime/workflow.rs @@ -1,178 +0,0 @@ -use crate::runtime::config::{RuntimeConfig, WORKFLOW_TRADE_CAPABILITY}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct WorkflowProviderStatusView { - pub provider_runtime_id: String, - pub binding_model: String, - pub state: String, - pub source: String, - pub target_kind: Option<String>, - pub target: Option<String>, - pub hyf_helper_state: String, - pub hyf_helper_detail: String, -} - -impl WorkflowProviderStatusView { - pub fn detail(&self) -> String { - match (self.target_kind.as_deref(), self.target.as_deref()) { - (Some(target_kind), Some(target)) if self.state == "configured" => { - format!( - "{} workflow provider configured via {} {}", - self.provider_runtime_id, target_kind, target - ) - } - _ if self.state == "configured" => { - format!("{} workflow provider configured", self.provider_runtime_id) - } - _ => self.source.clone(), - } - } -} - -pub fn resolve_workflow_provider(config: &RuntimeConfig) -> WorkflowProviderStatusView { - let binding = config - .inspect_capability_bindings() - .into_iter() - .find(|binding| binding.capability_id == WORKFLOW_TRADE_CAPABILITY) - .expect("workflow.trade binding inspection must exist"); - - WorkflowProviderStatusView { - provider_runtime_id: binding.provider_runtime_id, - binding_model: binding.binding_model, - state: binding.state.as_str().to_owned(), - source: binding.source, - target_kind: binding.target_kind, - target: binding.target, - hyf_helper_state: "not_implied".to_owned(), - hyf_helper_detail: - "cli bindings do not imply an rhi -> hyf helper path; any worker helper remains explicit and optional" - .to_owned(), - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use radroots_runtime_paths::RadrootsMigrationReport; - use radroots_secret_vault::RadrootsSecretBackend; - - use super::resolve_workflow_provider; - use crate::runtime::config::{ - AccountConfig, AccountSecretContractConfig, CapabilityBindingConfig, - CapabilityBindingSource, CapabilityBindingTargetKind, HyfConfig, IdentityConfig, - LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, - SignerBackend, SignerConfig, Verbosity, - }; - - fn sample_config(workflow_binding: Option<CapabilityBindingConfig>) -> RuntimeConfig { - RuntimeConfig { - output: OutputConfig { - format: OutputFormat::Human, - verbosity: Verbosity::Normal, - color: true, - dry_run: false, - }, - paths: PathsConfig { - profile: "interactive_user".into(), - profile_source: "default".into(), - allowed_profiles: vec!["interactive_user".into()], - root_source: "host_defaults".into(), - repo_local_root: None, - repo_local_root_source: None, - subordinate_path_override_source: "runtime_config".into(), - app_namespace: "apps/cli".into(), - shared_accounts_namespace: "shared/accounts".into(), - shared_identities_namespace: "shared/identities".into(), - app_config_path: PathBuf::from("/tmp/config.toml"), - workspace_config_path: PathBuf::from("/tmp/workspace-config.toml"), - app_data_root: PathBuf::from("/tmp/data"), - app_logs_root: PathBuf::from("/tmp/logs"), - shared_accounts_data_root: PathBuf::from("/tmp/shared/accounts"), - shared_accounts_secrets_root: PathBuf::from("/tmp/shared/accounts-secrets"), - default_identity_path: PathBuf::from("/tmp/default-identity.json"), - }, - migration: MigrationConfig { - report: RadrootsMigrationReport::empty(), - }, - logging: LoggingConfig { - filter: "info".into(), - directory: None, - stdout: true, - }, - account: AccountConfig { - selector: None, - store_path: PathBuf::from("/tmp/store.json"), - secrets_dir: PathBuf::from("/tmp/secrets"), - secret_backend: RadrootsSecretBackend::EncryptedFile, - secret_fallback: None, - }, - account_secret_contract: AccountSecretContractConfig { - default_backend: "host_vault".into(), - default_fallback: Some("encrypted_file".into()), - allowed_backends: vec!["host_vault".into(), "encrypted_file".into()], - host_vault_policy: Some("desktop".into()), - uses_protected_store: true, - }, - identity: IdentityConfig { - path: PathBuf::from("/tmp/default-identity.json"), - }, - signer: SignerConfig { - backend: SignerBackend::Local, - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, - local: LocalConfig { - root: PathBuf::from("/tmp/local"), - replica_db_path: PathBuf::from("/tmp/local/replica.sqlite"), - backups_dir: PathBuf::from("/tmp/local/backups"), - exports_dir: PathBuf::from("/tmp/local/exports"), - }, - myc: MycConfig { - executable: PathBuf::from("myc"), - }, - hyf: HyfConfig { - enabled: false, - executable: PathBuf::from("hyfd"), - }, - rpc: RpcConfig { - url: "http://127.0.0.1:7070".into(), - bridge_bearer_token: None, - }, - capability_bindings: workflow_binding.into_iter().collect(), - } - } - - #[test] - fn workflow_provider_reports_not_configured_without_binding() { - let view = resolve_workflow_provider(&sample_config(None)); - assert_eq!(view.provider_runtime_id, "rhi"); - assert_eq!(view.binding_model, "out_of_process_worker"); - assert_eq!(view.state, "not_configured"); - assert_eq!(view.source, "no explicit capability binding"); - assert_eq!(view.hyf_helper_state, "not_implied"); - } - - #[test] - fn workflow_provider_reports_explicit_binding_details() { - let binding = CapabilityBindingConfig { - capability_id: "workflow.trade".into(), - provider_runtime_id: "rhi".into(), - binding_model: "out_of_process_worker".into(), - source: CapabilityBindingSource::WorkspaceConfig, - target_kind: CapabilityBindingTargetKind::ExplicitEndpoint, - target: "/tmp/rhi-binary".into(), - managed_account_ref: None, - signer_session_ref: None, - }; - let view = resolve_workflow_provider(&sample_config(Some(binding))); - assert_eq!(view.state, "configured"); - assert_eq!(view.target_kind.as_deref(), Some("explicit_endpoint")); - assert_eq!(view.target.as_deref(), Some("/tmp/rhi-binary")); - assert!(view.detail().contains("explicit_endpoint /tmp/rhi-binary")); - } -} diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs @@ -279,14 +279,40 @@ fn config_show_json_reports_default_bootstrap_state() { .to_string() ); assert_eq!(json["myc"]["executable"], "myc"); + assert_eq!(json["write_plane"]["provider_runtime_id"], "radrootsd"); + assert_eq!(json["write_plane"]["binding_model"], "daemon_backed_jsonrpc"); + assert_eq!(json["write_plane"]["state"], "configured"); + assert_eq!(json["write_plane"]["provenance"], "direct_config"); + assert_eq!( + json["write_plane"]["source"], + "raw rpc config resolves the current write plane" + ); + assert_eq!(json["write_plane"]["target"], "http://127.0.0.1:7070"); + assert_eq!(json["write_plane"]["bridge_auth_configured"], false); assert_eq!(json["workflow"]["provider_runtime_id"], "rhi"); assert_eq!(json["workflow"]["binding_model"], "out_of_process_worker"); assert_eq!(json["workflow"]["state"], "not_configured"); + assert_eq!(json["workflow"]["provenance"], "unavailable"); assert_eq!(json["workflow"]["source"], "no explicit capability binding"); assert_eq!(json["workflow"]["hyf_helper_state"], "not_implied"); + assert_eq!(json["hyf_provider"]["provider_runtime_id"], "hyf"); + assert_eq!(json["hyf_provider"]["binding_model"], "stdio_service"); + assert_eq!(json["hyf_provider"]["state"], "disabled"); + assert_eq!(json["hyf_provider"]["provenance"], "disabled"); + assert_eq!( + json["hyf_provider"]["source"], + "hyf status control request ยท local first" + ); assert_eq!(json["rpc"]["url"], "http://127.0.0.1:7070"); assert_eq!(json["rpc"]["bridge_auth_configured"], false); assert_eq!( + json["resolved_providers"] + .as_array() + .expect("resolved providers") + .len(), + 3 + ); + assert_eq!( json["capability_bindings"] .as_array() .expect("capability bindings") @@ -651,6 +677,7 @@ target = "bin/hyfd-user" assert_eq!(workflow["target"], "workflow-default"); assert_eq!(json["workflow"]["provider_runtime_id"], "rhi"); assert_eq!(json["workflow"]["state"], "configured"); + assert_eq!(json["workflow"]["provenance"], "managed_default"); assert_eq!( json["workflow"]["source"], "user config [[capability_binding]]" @@ -663,6 +690,13 @@ target = "bin/hyfd-user" .as_str() .is_some_and(|detail| detail.contains("do not imply")) ); + assert_eq!(json["write_plane"]["provenance"], "direct_config"); + assert_eq!(json["write_plane"]["target"], "http://127.0.0.1:7070"); + assert_eq!(json["hyf_provider"]["provider_runtime_id"], "hyf"); + assert_eq!(json["hyf_provider"]["provenance"], "explicit_binding"); + assert_eq!(json["hyf_provider"]["target_kind"], "explicit_endpoint"); + assert_eq!(json["hyf_provider"]["target"], "bin/hyfd-user"); + assert_eq!(json["hyf_provider"]["executable"], "bin/hyfd-user"); let inference = binding_by_capability(&json, "inference.hyf_stdio"); assert_eq!(inference["state"], "configured");