provider.rs (16717B)
1 #[cfg(test)] 2 use crate::runtime::config::{ 3 CapabilityBindingInspection, CapabilityBindingInspectionState, INFERENCE_HYF_STDIO_CAPABILITY, 4 }; 5 use crate::runtime::config::{PublishTransport, RuntimeConfig}; 6 #[cfg(test)] 7 use crate::runtime::hyf; 8 use crate::view::runtime::PublishRuntimeView; 9 10 #[cfg(test)] 11 const WRITE_PLANE_TARGET_DETAIL: &str = 12 "write-plane targets are resolved by mode-specific publish commands"; 13 14 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 15 pub enum ProviderProvenance { 16 #[cfg(test)] 17 ExplicitBinding, 18 #[cfg(test)] 19 ManagedDefault, 20 #[cfg(test)] 21 DirectConfig, 22 #[cfg(test)] 23 Disabled, 24 PublishTransport, 25 #[cfg(test)] 26 Unavailable, 27 } 28 29 impl ProviderProvenance { 30 pub fn as_str(self) -> &'static str { 31 match self { 32 #[cfg(test)] 33 Self::ExplicitBinding => "explicit_binding", 34 #[cfg(test)] 35 Self::ManagedDefault => "managed_default", 36 #[cfg(test)] 37 Self::DirectConfig => "direct_config", 38 #[cfg(test)] 39 Self::Disabled => "disabled", 40 Self::PublishTransport => "publish_transport", 41 #[cfg(test)] 42 Self::Unavailable => "unavailable", 43 } 44 } 45 } 46 47 #[cfg(test)] 48 #[derive(Debug, Clone, PartialEq, Eq)] 49 pub struct ResolvedProviderView { 50 pub capability_id: String, 51 pub provider_runtime_id: String, 52 pub binding_model: String, 53 pub state: String, 54 pub provenance: String, 55 pub source: String, 56 pub target_kind: Option<String>, 57 pub target: Option<String>, 58 } 59 60 #[derive(Debug, Clone, PartialEq, Eq)] 61 pub struct WritePlaneProviderView { 62 pub provider_runtime_id: String, 63 pub binding_model: String, 64 pub state: String, 65 pub provenance: String, 66 pub source: String, 67 pub target_kind: Option<String>, 68 pub target: Option<String>, 69 pub detail: String, 70 } 71 72 #[cfg(test)] 73 #[derive(Debug, Clone, PartialEq, Eq)] 74 pub struct ResolvedWritePlaneTarget { 75 pub url: String, 76 } 77 78 #[cfg(test)] 79 #[derive(Debug, Clone, PartialEq, Eq)] 80 pub struct HyfProviderView { 81 pub provider_runtime_id: String, 82 pub binding_model: String, 83 pub state: String, 84 pub provenance: String, 85 pub source: String, 86 pub target_kind: Option<String>, 87 pub target: Option<String>, 88 pub executable: String, 89 pub reason: Option<String>, 90 pub protocol_version: Option<u64>, 91 pub deterministic_available: Option<bool>, 92 } 93 94 pub fn resolve_write_plane_provider( 95 config: &RuntimeConfig, 96 publish: &PublishRuntimeView, 97 ) -> WritePlaneProviderView { 98 let (provider_runtime_id, binding_model, detail) = match config.publish.transport { 99 PublishTransport::DirectNostrRelay => ( 100 "direct_nostr_relay", 101 "direct_relay_publish", 102 "direct relay publish is selected; readiness is reported under publish", 103 ), 104 PublishTransport::RadrootsdProxy => ( 105 "radrootsd_proxy", 106 "daemon_proxy_publish", 107 "radrootsd_proxy publish is selected; readiness is reported under publish", 108 ), 109 }; 110 WritePlaneProviderView { 111 provider_runtime_id: provider_runtime_id.to_owned(), 112 binding_model: binding_model.to_owned(), 113 state: publish.state.clone(), 114 provenance: ProviderProvenance::PublishTransport.as_str().to_owned(), 115 source: publish.source.clone(), 116 target_kind: None, 117 target: None, 118 detail: publish.reason.clone().unwrap_or_else(|| detail.to_owned()), 119 } 120 } 121 122 #[cfg(test)] 123 pub fn resolve_actor_write_plane_target( 124 config: &RuntimeConfig, 125 ) -> Result<ResolvedWritePlaneTarget, String> { 126 let _ = config; 127 Err(WRITE_PLANE_TARGET_DETAIL.to_owned()) 128 } 129 130 #[cfg(test)] 131 pub fn resolve_hyf_provider(config: &RuntimeConfig) -> HyfProviderView { 132 let binding = inspect_binding(config, INFERENCE_HYF_STDIO_CAPABILITY); 133 let status = hyf::resolve_runtime_status(config); 134 let binding_configured = binding.state == CapabilityBindingInspectionState::Configured; 135 let provenance = if binding_configured { 136 binding_provenance(&binding) 137 } else if status.state == "disabled" { 138 ProviderProvenance::Disabled 139 } else { 140 ProviderProvenance::DirectConfig 141 } 142 .as_str() 143 .to_owned(); 144 let target_kind = hyf_target_kind(config, &binding); 145 let target = hyf_target(config, &binding); 146 let executable = hyf_executable(config, &binding, &status); 147 let source = if binding_configured { 148 binding.source.clone() 149 } else { 150 status.source.clone() 151 }; 152 153 HyfProviderView { 154 provider_runtime_id: binding.provider_runtime_id, 155 binding_model: binding.binding_model, 156 state: status.state, 157 provenance, 158 source, 159 target_kind, 160 target, 161 executable, 162 reason: status.reason, 163 protocol_version: status.protocol_version, 164 deterministic_available: status.deterministic_available, 165 } 166 } 167 168 #[cfg(test)] 169 pub fn resolve_capability_providers(config: &RuntimeConfig) -> Vec<ResolvedProviderView> { 170 let hyf = resolve_hyf_provider(config); 171 172 vec![ResolvedProviderView { 173 capability_id: INFERENCE_HYF_STDIO_CAPABILITY.to_owned(), 174 provider_runtime_id: hyf.provider_runtime_id, 175 binding_model: hyf.binding_model, 176 state: hyf.state, 177 provenance: hyf.provenance, 178 source: hyf.source, 179 target_kind: hyf.target_kind, 180 target: hyf.target, 181 }] 182 } 183 184 #[cfg(test)] 185 fn inspect_binding(config: &RuntimeConfig, capability_id: &str) -> CapabilityBindingInspection { 186 config 187 .inspect_capability_bindings() 188 .into_iter() 189 .find(|binding| binding.capability_id == capability_id) 190 .expect("provider capability binding inspection must exist") 191 } 192 193 #[cfg(test)] 194 fn binding_provenance(binding: &CapabilityBindingInspection) -> ProviderProvenance { 195 match binding.state { 196 CapabilityBindingInspectionState::Configured => match binding.target_kind.as_deref() { 197 Some("managed_instance") => ProviderProvenance::ManagedDefault, 198 _ => ProviderProvenance::ExplicitBinding, 199 }, 200 CapabilityBindingInspectionState::Disabled => ProviderProvenance::Disabled, 201 CapabilityBindingInspectionState::NotConfigured => ProviderProvenance::Unavailable, 202 } 203 } 204 205 #[cfg(test)] 206 fn hyf_target_kind( 207 config: &RuntimeConfig, 208 binding: &CapabilityBindingInspection, 209 ) -> Option<String> { 210 if binding.state == CapabilityBindingInspectionState::Configured { 211 return binding.target_kind.clone(); 212 } 213 if config.hyf.enabled { 214 return Some("direct_config".to_owned()); 215 } 216 None 217 } 218 219 #[cfg(test)] 220 fn hyf_target(config: &RuntimeConfig, binding: &CapabilityBindingInspection) -> Option<String> { 221 if binding.state == CapabilityBindingInspectionState::Configured { 222 return binding.target.clone(); 223 } 224 if config.hyf.enabled { 225 return Some(config.hyf.executable.display().to_string()); 226 } 227 None 228 } 229 230 #[cfg(test)] 231 fn hyf_executable( 232 config: &RuntimeConfig, 233 binding: &CapabilityBindingInspection, 234 status: &hyf::HyfStatusView, 235 ) -> String { 236 if binding.state == CapabilityBindingInspectionState::Configured 237 && binding.target_kind.as_deref() == Some("explicit_endpoint") 238 { 239 return binding 240 .target 241 .clone() 242 .unwrap_or_else(|| status.executable.clone()); 243 } 244 if !config.hyf.enabled { 245 return status.executable.clone(); 246 } 247 status.executable.clone() 248 } 249 250 #[cfg(test)] 251 mod tests { 252 use std::path::PathBuf; 253 254 use radroots_runtime_paths::RadrootsMigrationReport; 255 use radroots_secret_vault::RadrootsSecretBackend; 256 257 use super::{ 258 ProviderProvenance, resolve_actor_write_plane_target, resolve_capability_providers, 259 resolve_hyf_provider, resolve_write_plane_provider, 260 }; 261 use crate::runtime::config::{ 262 AccountConfig, AccountSecretContractConfig, CapabilityBindingConfig, 263 CapabilityBindingSource, CapabilityBindingTargetKind, HyfConfig, IdentityConfig, 264 InteractionConfig, LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, 265 OutputFormat, PathsConfig, PublishConfig, PublishTransport, PublishTransportSource, 266 RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, 267 SignerBackend, SignerConfig, Verbosity, 268 }; 269 use crate::view::runtime::{ 270 PublishProviderRuntimeView, PublishRelayRuntimeView, PublishRuntimeView, 271 }; 272 273 fn sample_config(bindings: Vec<CapabilityBindingConfig>, hyf_enabled: bool) -> RuntimeConfig { 274 RuntimeConfig { 275 output: OutputConfig { 276 format: OutputFormat::Human, 277 verbosity: Verbosity::Normal, 278 color: true, 279 dry_run: false, 280 }, 281 interaction: InteractionConfig { 282 input_enabled: true, 283 assume_yes: false, 284 stdin_tty: true, 285 stdout_tty: true, 286 prompts_allowed: true, 287 confirmations_allowed: true, 288 }, 289 paths: PathsConfig { 290 profile: "interactive_user".into(), 291 profile_source: "default".into(), 292 allowed_profiles: vec!["interactive_user".into()], 293 root_source: "host_defaults".into(), 294 repo_local_root: None, 295 repo_local_root_source: None, 296 subordinate_path_override_source: "runtime_config".into(), 297 app_namespace: "apps/cli".into(), 298 shared_accounts_namespace: "shared/accounts".into(), 299 shared_identities_namespace: "shared/identities".into(), 300 app_config_path: PathBuf::from("/tmp/config/apps/cli/config.toml"), 301 workspace_config_path: None, 302 app_data_root: PathBuf::from("/tmp/data"), 303 app_logs_root: PathBuf::from("/tmp/logs"), 304 shared_accounts_data_root: PathBuf::from("/tmp/shared/accounts"), 305 shared_accounts_secrets_root: PathBuf::from("/tmp/shared/accounts-secrets"), 306 default_identity_path: PathBuf::from("/tmp/default-identity.json"), 307 }, 308 migration: MigrationConfig { 309 report: RadrootsMigrationReport::empty(), 310 }, 311 logging: LoggingConfig { 312 filter: "info".into(), 313 directory: None, 314 stdout: true, 315 }, 316 account: AccountConfig { 317 selector: None, 318 store_path: PathBuf::from("/tmp/store.json"), 319 secrets_dir: PathBuf::from("/tmp/secrets"), 320 secret_backend: RadrootsSecretBackend::EncryptedFile, 321 secret_fallback: None, 322 }, 323 account_secret_contract: AccountSecretContractConfig { 324 default_backend: "host_vault".into(), 325 default_fallback: Some("encrypted_file".into()), 326 allowed_backends: vec!["host_vault".into(), "encrypted_file".into()], 327 host_vault_policy: Some("desktop".into()), 328 uses_protected_store: true, 329 }, 330 identity: IdentityConfig { 331 path: PathBuf::from("/tmp/default-identity.json"), 332 }, 333 signer: SignerConfig { 334 backend: SignerBackend::Local, 335 }, 336 publish: PublishConfig { 337 transport: PublishTransport::DirectNostrRelay, 338 source: PublishTransportSource::Defaults, 339 radrootsd_proxy: crate::runtime::config::RadrootsdProxyConfig::default(), 340 }, 341 relay: RelayConfig { 342 urls: Vec::new(), 343 publish_policy: RelayPublishPolicy::Any, 344 source: RelayConfigSource::Defaults, 345 }, 346 local: LocalConfig { 347 root: PathBuf::from("/tmp/local"), 348 replica_db_path: PathBuf::from("/tmp/local/replica.sqlite"), 349 backups_dir: PathBuf::from("/tmp/local/backups"), 350 exports_dir: PathBuf::from("/tmp/local/exports"), 351 }, 352 myc: MycConfig { 353 executable: PathBuf::from("myc"), 354 status_timeout_ms: 2_000, 355 }, 356 hyf: HyfConfig { 357 enabled: hyf_enabled, 358 executable: PathBuf::from("hyfd"), 359 }, 360 rpc: RpcConfig { 361 url: "http://127.0.0.1:7070".into(), 362 }, 363 rhi: crate::runtime::config::RhiConfig { 364 trusted_worker_pubkeys: Vec::new(), 365 }, 366 capability_bindings: bindings, 367 } 368 } 369 370 fn publish_view( 371 config: &RuntimeConfig, 372 state: &str, 373 reason: Option<&str>, 374 ) -> PublishRuntimeView { 375 PublishRuntimeView { 376 transport: config.publish.transport.as_str().to_owned(), 377 source: config.publish.source.as_str().to_owned(), 378 transport_family: config.publish.transport.transport_family().to_owned(), 379 state: state.to_owned(), 380 executable: state == "ready", 381 reason: reason.map(str::to_owned), 382 signed_write_required: true, 383 relay: PublishRelayRuntimeView { 384 ready: !config.relay.urls.is_empty(), 385 count: config.relay.urls.len(), 386 source: config.relay.source.as_str().to_owned(), 387 }, 388 provider: PublishProviderRuntimeView { 389 provider_runtime_id: config.publish.transport.as_str().to_owned(), 390 state: state.to_owned(), 391 source: config.publish.source.as_str().to_owned(), 392 reason: reason.map(str::to_owned), 393 }, 394 } 395 } 396 397 #[test] 398 fn write_plane_provider_tracks_direct_relay_publish() { 399 let config = sample_config(Vec::new(), false); 400 let publish = publish_view( 401 &config, 402 "unconfigured", 403 Some("direct_nostr_relay publish transport requires a configured relay"), 404 ); 405 let view = resolve_write_plane_provider(&config, &publish); 406 assert_eq!(view.provider_runtime_id, "direct_nostr_relay"); 407 assert_eq!(view.binding_model, "direct_relay_publish"); 408 assert_eq!(view.state, "unconfigured"); 409 assert_eq!( 410 view.provenance, 411 ProviderProvenance::PublishTransport.as_str() 412 ); 413 assert!(view.target.is_none()); 414 assert!(view.detail.contains("configured relay")); 415 } 416 417 #[test] 418 fn actor_write_plane_target_fails_closed() { 419 let error = resolve_actor_write_plane_target(&sample_config(Vec::new(), false)) 420 .expect_err("write plane target"); 421 assert_eq!( 422 error, 423 "write-plane targets are resolved by mode-specific publish commands" 424 ); 425 } 426 427 #[test] 428 fn hyf_uses_direct_config_when_enabled_without_binding() { 429 let view = resolve_hyf_provider(&sample_config(Vec::new(), true)); 430 assert_eq!(view.provenance, ProviderProvenance::DirectConfig.as_str()); 431 assert_eq!(view.target_kind.as_deref(), Some("direct_config")); 432 assert_eq!(view.target.as_deref(), Some("hyfd")); 433 } 434 435 #[test] 436 fn hyf_binding_remains_visible_when_runtime_is_disabled() { 437 let binding = CapabilityBindingConfig { 438 capability_id: "inference.hyf_stdio".into(), 439 provider_runtime_id: "hyf".into(), 440 binding_model: "stdio_service".into(), 441 source: CapabilityBindingSource::UserConfig, 442 target_kind: CapabilityBindingTargetKind::ExplicitEndpoint, 443 target: "bin/hyfd-user".into(), 444 managed_account_ref: None, 445 signer_session_ref: None, 446 }; 447 let view = resolve_hyf_provider(&sample_config(vec![binding], false)); 448 assert_eq!(view.state, "disabled"); 449 assert_eq!( 450 view.provenance, 451 ProviderProvenance::ExplicitBinding.as_str() 452 ); 453 assert_eq!(view.source, "user config [[capability_binding]]"); 454 assert_eq!(view.target_kind.as_deref(), Some("explicit_endpoint")); 455 assert_eq!(view.target.as_deref(), Some("bin/hyfd-user")); 456 assert_eq!(view.executable, "bin/hyfd-user"); 457 } 458 459 #[test] 460 fn capability_provider_list_only_covers_active_hyf_provider() { 461 let providers = resolve_capability_providers(&sample_config(Vec::new(), false)); 462 assert_eq!(providers.len(), 1); 463 assert_eq!(providers[0].capability_id, "inference.hyf_stdio"); 464 } 465 }