cli

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

commit b4765d78c6edfa261cbf0aa19af9156abec98834
parent c685656cd9604417e9ce965770fa66740e595283
Author: triesap <tyson@radroots.org>
Date:   Tue,  7 Jul 2026 04:29:34 +0000

transport: remove publish mirror config

- Remove derived publish and relay runtime config mirrors in favor of direct transport profile state.
- Treat local-only and Reticulum preview profiles as non-networked publish states.
- Rename publish_transport output and validation surfaces to transport_profile.
- Delete the orphaned relay command source and validate focused CLI transport tests.

Diffstat:
Dsrc/cli/relay.rs | 12------------
Msrc/main.rs | 14+++++++-------
Msrc/ops/exec/basket.rs | 15++-------------
Msrc/ops/exec/core.rs | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/ops/exec/farm.rs | 21+++++----------------
Msrc/ops/exec/listing.rs | 15++-------------
Msrc/ops/exec/market.rs | 17+++--------------
Msrc/ops/exec/runtime.rs | 15++-------------
Msrc/ops/exec/trade.rs | 17+++--------------
Msrc/out/terminal/renderers/common.rs | 6+++---
Msrc/out/terminal/renderers/farm.rs | 2+-
Msrc/out/terminal/renderers/listing.rs | 2+-
Msrc/registry/mod.rs | 6+++---
Msrc/runtime/config.rs | 255+++++++++++--------------------------------------------------------------------
Msrc/runtime/farm.rs | 12+++++++-----
Msrc/runtime/find.rs | 6+++---
Msrc/runtime/listing.rs | 6+++---
Msrc/runtime/order.rs | 62+++++++++++++++++++++++++++++++++++++-------------------------
Msrc/runtime/provider.rs | 56+++++++++++++++++++++++++++-----------------------------
Msrc/runtime/sdk.rs | 15++-------------
Msrc/runtime/sync.rs | 57++++++++++++++++++++++++++-------------------------------
Msrc/view/runtime.rs | 4++--
Mtests/target_cli.rs | 6+++---
23 files changed, 246 insertions(+), 478 deletions(-)

diff --git a/src/cli/relay.rs b/src/cli/relay.rs @@ -1,12 +0,0 @@ -use clap::{Args, Subcommand}; - -#[derive(Debug, Clone, Args)] -pub struct RelayArgs { - #[command(subcommand)] - pub command: RelayCommand, -} - -#[derive(Debug, Clone, Copy, Subcommand)] -pub enum RelayCommand { - List, -} diff --git a/src/main.rs b/src/main.rs @@ -372,7 +372,7 @@ fn validate_request_contract( config: &RuntimeConfig, ) -> Result<(), OperationAdapterError> { validate_pre_runtime_request_contract(request)?; - validate_publish_transport_contract(request, config)?; + validate_transport_profile_contract(request, config)?; validate_signer_mode_contract(request, config)?; validate_network_contract(request, config)?; Ok(()) @@ -407,7 +407,7 @@ fn validate_signer_mode_contract( ) -> Result<(), OperationAdapterError> { let spec = request.spec(); if matches!(config.signer.backend, SignerBackend::Myc) - && requires_local_signer_mode_for_publish_transport(spec.operation_id, config) + && requires_local_signer_mode_for_transport_profile(spec.operation_id, config) { return Err(OperationAdapterError::SignerModeDeferred { operation_id: spec.operation_id.to_owned(), @@ -468,7 +468,7 @@ fn validate_network_contract( } } -fn requires_local_signer_mode_for_publish_transport( +fn requires_local_signer_mode_for_transport_profile( operation_id: &str, config: &RuntimeConfig, ) -> bool { @@ -477,19 +477,19 @@ fn requires_local_signer_mode_for_publish_transport( } fn requires_pre_runtime_transport_target(operation_id: &str) -> bool { - !is_publish_transport_routed_operation(operation_id) + !is_transport_profile_routed_operation(operation_id) } fn allows_offline_local_mutation(operation_id: &str) -> bool { matches!(operation_id, "listing.publish") } -fn validate_publish_transport_contract( +fn validate_transport_profile_contract( request: &TargetOperationRequest, config: &RuntimeConfig, ) -> Result<(), OperationAdapterError> { let spec = request.spec(); - if !is_publish_transport_routed_operation(spec.operation_id) { + if !is_transport_profile_routed_operation(spec.operation_id) { return Ok(()); } if request.context().dry_run @@ -529,7 +529,7 @@ fn validate_publish_transport_contract( Ok(()) } -fn is_publish_transport_routed_operation(operation_id: &str) -> bool { +fn is_transport_profile_routed_operation(operation_id: &str) -> bool { matches!( operation_id, "farm.publish" diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs @@ -1333,9 +1333,8 @@ mod tests { use crate::runtime::account; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; const LISTING_ADDR: &str = "30402:1111111111111111111111111111111111111111111111111111111111111111:AAAAAAAAAAAAAAAAAAAAAg"; @@ -1907,16 +1906,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/ops/exec/core.rs b/src/ops/exec/core.rs @@ -27,7 +27,7 @@ use crate::runtime::account::{ remove_account, resolve_account_resolution, resolve_account_selector, secret_backend_status, select_account, snapshot, unresolved_account_reason, }; -use crate::runtime::config::{PublishTransport, RuntimeConfig, SignerBackend}; +use crate::runtime::config::{RuntimeConfig, SignerBackend, TransportProfileKind}; use crate::runtime::logging::LoggingState; use crate::runtime::sdk::CliSdkAdapterError; use crate::runtime::signer::resolve_signer_status; @@ -794,22 +794,22 @@ fn publish_runtime_view( signed_write_required: bool, account: &AccountResolution, ) -> PublishRuntimeView { - let relay_ready = !config.relay.urls.is_empty(); - let source = config.publish.source.as_str().to_owned(); + let relay_ready = !config.transport.nostr_relay_urls.is_empty(); + let source = config.transport.source.as_str().to_owned(); let relay = PublishRelayRuntimeView { ready: relay_ready, - count: config.relay.urls.len(), - source: config.relay.source.as_str().to_owned(), + count: config.transport.nostr_relay_urls.len(), + source: config.transport.source.as_str().to_owned(), }; - match config.publish.transport { - PublishTransport::Nostr => { + match config.transport.profile { + TransportProfileKind::Nostr => { let (state, executable, reason) = nostr_publish_readiness(config, relay_ready, signed_write_required, account); PublishRuntimeView { - transport: config.publish.transport.as_str().to_owned(), + transport: config.transport.profile.as_str().to_owned(), source, - transport_family: config.publish.transport.transport_family().to_owned(), + transport_family: config.transport.profile.transport_family().to_owned(), state: state.to_owned(), executable, reason: reason.clone(), @@ -818,17 +818,17 @@ fn publish_runtime_view( provider: PublishProviderRuntimeView { provider_runtime_id: "nostr".to_owned(), state: state.to_owned(), - source: config.relay.source.as_str().to_owned(), + source: config.transport.source.as_str().to_owned(), reason, }, } } - PublishTransport::Proxy => { + TransportProfileKind::Proxy => { let (state, executable, reason) = proxy_publish_readiness(config); PublishRuntimeView { - transport: config.publish.transport.as_str().to_owned(), + transport: config.transport.profile.as_str().to_owned(), source, - transport_family: config.publish.transport.transport_family().to_owned(), + transport_family: config.transport.profile.transport_family().to_owned(), state: state.to_owned(), executable, reason: reason.clone(), @@ -842,6 +842,42 @@ fn publish_runtime_view( }, } } + TransportProfileKind::LocalOnly => PublishRuntimeView { + transport: config.transport.profile.as_str().to_owned(), + source, + transport_family: config.transport.profile.transport_family().to_owned(), + state: "local_only".to_owned(), + executable: false, + reason: Some( + "local_only transport profile does not perform network publish".to_owned(), + ), + signed_write_required, + relay, + provider: PublishProviderRuntimeView { + provider_runtime_id: "local_only".to_owned(), + state: "local_only".to_owned(), + source: config.transport.source.as_str().to_owned(), + reason: Some("local_only transport profile writes only to local state".to_owned()), + }, + }, + TransportProfileKind::ReticulumPreview => PublishRuntimeView { + transport: config.transport.profile.as_str().to_owned(), + source, + transport_family: config.transport.profile.transport_family().to_owned(), + state: "preview_unavailable".to_owned(), + executable: false, + reason: Some( + "reticulum preview transport does not perform MVP network delivery".to_owned(), + ), + signed_write_required, + relay, + provider: PublishProviderRuntimeView { + provider_runtime_id: "reticulum_preview".to_owned(), + state: "preview_unavailable".to_owned(), + source: config.transport.source.as_str().to_owned(), + reason: Some("reticulum preview is non-networked in the MVP".to_owned()), + }, + }, } } @@ -900,7 +936,9 @@ fn nostr_publish_readiness( } fn proxy_publish_readiness(config: &RuntimeConfig) -> (&'static str, bool, Option<String>) { - if config.publish.proxy.token_file.is_none() && config.publish.proxy.token_secret_id.is_none() { + if config.transport.proxy.token_file.is_none() + && config.transport.proxy.token_secret_id.is_none() + { return ( "unconfigured", false, @@ -1072,9 +1110,9 @@ fn publish_recovery_actions( } let mut actions = Vec::new(); - match config.publish.transport { - PublishTransport::Nostr => { - if config.relay.urls.is_empty() { + match config.transport.profile { + TransportProfileKind::Nostr => { + if config.transport.nostr_relay_urls.is_empty() { push_unique( &mut actions, "radroots transport profile set --kind nostr --nostr-relay wss://relay.example.com", @@ -1092,7 +1130,7 @@ fn publish_recovery_actions( } } } - PublishTransport::Proxy => { + TransportProfileKind::Proxy => { if self::proxy_token_configured(config) { if publish.signed_write_required && matches!(config.signer.backend, SignerBackend::Myc) @@ -1106,12 +1144,24 @@ fn publish_recovery_actions( ); } } + TransportProfileKind::LocalOnly => { + push_unique( + &mut actions, + "radroots transport profile set --kind nostr --nostr-relay wss://relay.example.com", + ); + } + TransportProfileKind::ReticulumPreview => { + push_unique( + &mut actions, + "radroots transport profile set --kind nostr --nostr-relay wss://relay.example.com", + ); + } } actions } fn proxy_token_configured(config: &RuntimeConfig) -> bool { - config.publish.proxy.token_file.is_some() || config.publish.proxy.token_secret_id.is_some() + config.transport.proxy.token_file.is_some() || config.transport.proxy.token_secret_id.is_some() } fn push_unique(actions: &mut Vec<String>, action: impl Into<String>) { @@ -1200,9 +1250,8 @@ mod tests { }; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::runtime::logging::LoggingState; @@ -1448,16 +1497,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/ops/exec/farm.rs b/src/ops/exec/farm.rs @@ -16,7 +16,7 @@ use crate::ops::{ OperationResult, OperationResultData, OperationService, }; use crate::runtime::RuntimeError; -use crate::runtime::config::{PublishTransport, RuntimeConfig}; +use crate::runtime::config::{RuntimeConfig, TransportProfileKind}; use crate::view::runtime::{CommandDisposition, FarmPublishView}; pub struct FarmOperationService<'a> { @@ -232,7 +232,7 @@ impl OperationService<FarmPublishRequest> for FarmOperationService<'_> { request.operation_id(), )); } - if matches!(self.config.publish.transport, PublishTransport::Nostr) { + if matches!(self.config.transport.profile, TransportProfileKind::Nostr) { require_relay_target(&request, self.config)?; } @@ -358,7 +358,7 @@ fn require_relay_target<P>( where P: OperationRequestPayload, { - if !config.relay.urls.is_empty() { + if !config.transport.nostr_relay_urls.is_empty() { return Ok(()); } @@ -596,9 +596,8 @@ mod tests { }; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::view::runtime::{ FarmPrivateExactLocationView, FarmPrivateLocationCandidateView, FarmPrivateLocationView, @@ -877,16 +876,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/ops/exec/listing.rs b/src/ops/exec/listing.rs @@ -473,9 +473,8 @@ mod tests { }; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; #[test] @@ -607,16 +606,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/ops/exec/market.rs b/src/ops/exec/market.rs @@ -253,9 +253,8 @@ mod tests { }; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::view::runtime::{ FindPriceView, FindQuantityView, FindResultProvenanceView, FindResultView, FindView, @@ -311,7 +310,7 @@ mod tests { let dir = tempdir().expect("tempdir"); let mut config = sample_config(dir.path()); config.output.dry_run = true; - config.relay.urls = vec!["wss://relay.example.com".to_owned()]; + config.transport.nostr_relay_urls = vec!["wss://relay.example.com".to_owned()]; crate::runtime::store::init(&config).expect("store init"); let service = OperationAdapter::new(MarketOperationService::new(&config)); @@ -671,16 +670,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/ops/exec/runtime.rs b/src/ops/exec/runtime.rs @@ -341,9 +341,8 @@ mod tests { }; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; #[test] @@ -475,16 +474,6 @@ mod tests { transport: crate::runtime::config::TransportConfig::from_nostr_relay_urls( relays.clone(), ), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: relays, - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/ops/exec/trade.rs b/src/ops/exec/trade.rs @@ -1267,9 +1267,8 @@ mod tests { }; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::view::runtime::OrderDecisionView; @@ -1683,7 +1682,7 @@ mod tests { fn order_event_list_requires_seller_account_with_account_action() { let dir = tempdir().expect("tempdir"); let mut config = sample_config(dir.path()); - config.relay.urls = vec!["ws://127.0.0.1:9".to_owned()]; + config.transport.nostr_relay_urls = vec!["ws://127.0.0.1:9".to_owned()]; let service = OperationAdapter::new(TradeOperationService::new(&config)); let context = OperationContext::default(); let request = OperationRequest::new(context.clone(), TradeEventListRequest::default()) @@ -1813,16 +1812,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/out/terminal/renderers/common.rs b/src/out/terminal/renderers/common.rs @@ -239,7 +239,7 @@ fn add_terminal_display_fields( if let Some(state) = terminal_state(display) { push_field(document, "State", terminal_status_label(state)); } - if let Some(mode) = terminal_publish_transport(display) { + if let Some(mode) = terminal_transport_profile(display) { push_field(document, "Transport", transport_label(mode)); } if let Some(state) = terminal_publish_state(display) { @@ -282,10 +282,10 @@ fn terminal_state(result: &Value) -> Option<&str> { string_path(result, &["state"]) } -fn terminal_publish_transport(result: &Value) -> Option<&str> { +fn terminal_transport_profile(result: &Value) -> Option<&str> { string_path(result, &["publish", "mode"]) .or_else(|| string_path(result, &["checks", "publish", "mode"])) - .or_else(|| string_path(result, &["publish_transport"])) + .or_else(|| string_path(result, &["transport_profile"])) } fn terminal_publish_state(result: &Value) -> Option<&str> { diff --git a/src/out/terminal/renderers/farm.rs b/src/out/terminal/renderers/farm.rs @@ -154,7 +154,7 @@ fn farm_readiness_document(envelope: &OutputEnvelope, result: &Value) -> Termina &["listing_defaults_state"], ); common::push_path_field(&mut document, "Publish", result, &["publish_state"]); - if let Some(mode) = common::string(result, &["publish_transport"]) { + if let Some(mode) = common::string(result, &["transport_profile"]) { common::push_field(&mut document, "Transport", transport_label(mode.as_str())); } common::push_bool_field(&mut document, "Executable", result, &["publish_executable"]); diff --git a/src/out/terminal/renderers/listing.rs b/src/out/terminal/renderers/listing.rs @@ -224,7 +224,7 @@ fn listing_publish_document(envelope: &OutputEnvelope, result: &Value) -> Termin result, &[ &["publish", "mode"], - &["publish_transport"], + &["transport_profile"], &["transport"], &["source"], ], diff --git a/src/registry/mod.rs b/src/registry/mod.rs @@ -235,7 +235,7 @@ pub fn requires_local_signer_mode(operation_id: &str) -> bool { ) } -pub fn requires_nostr_publish_transport(operation_id: &str) -> bool { +pub fn requires_nostr_transport_profile(operation_id: &str) -> bool { matches!( operation_id, "sync.push" @@ -270,7 +270,7 @@ mod tests { use super::{ ApprovalPolicy, NetworkRequirement, OPERATION_REGISTRY, OperationRole, RiskLevel, get_operation, network_requirement, requires_local_signer_mode, - requires_nostr_publish_transport, + requires_nostr_transport_profile, }; const EXPECTED_OPERATION_IDS: &[&str] = &[ @@ -625,7 +625,7 @@ mod tests { fn registry_nostr_publish_requirements_are_explicit() { let publish = OPERATION_REGISTRY .iter() - .filter(|operation| requires_nostr_publish_transport(operation.operation_id)) + .filter(|operation| requires_nostr_transport_profile(operation.operation_id)) .map(|operation| operation.operation_id) .collect::<BTreeSet<_>>(); let expected = [ diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -198,6 +198,15 @@ impl TransportProfileKind { Self::Proxy => "proxy", } } + + pub fn transport_family(self) -> &'static str { + match self { + Self::LocalOnly => "local", + Self::Nostr => "nostr", + Self::ReticulumPreview => "reticulum", + Self::Proxy => "proxy", + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -268,56 +277,6 @@ impl TransportConfig { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PublishTransport { - Nostr, - Proxy, -} - -impl PublishTransport { - pub fn as_str(self) -> &'static str { - match self { - Self::Nostr => "nostr", - Self::Proxy => "proxy", - } - } - - pub fn transport_family(self) -> &'static str { - match self { - Self::Nostr => "nostr", - Self::Proxy => "proxy", - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PublishTransportSource { - Flags, - Environment, - UserConfig, - WorkspaceConfig, - Defaults, -} - -impl PublishTransportSource { - pub fn as_str(self) -> &'static str { - match self { - Self::Flags => "cli flags · transport profile", - Self::Environment => "environment · transport profile", - Self::UserConfig => "user config · transport profile", - Self::WorkspaceConfig => "workspace config · transport profile", - Self::Defaults => "defaults · transport profile", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PublishConfig { - pub transport: PublishTransport, - pub source: PublishTransportSource, - pub proxy: ProxyTransportConfig, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProxyTransportConfig { pub url: String, @@ -335,47 +294,6 @@ impl Default for ProxyTransportConfig { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RelayPublishPolicy { - Any, -} - -impl RelayPublishPolicy { - pub fn as_str(self) -> &'static str { - match self { - Self::Any => "any", - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RelayConfigSource { - Flags, - Environment, - UserConfig, - WorkspaceConfig, - Defaults, -} - -impl RelayConfigSource { - pub fn as_str(self) -> &'static str { - match self { - Self::Flags => "cli flags · local first", - Self::Environment => "environment · local first", - Self::UserConfig => "user config · local first", - Self::WorkspaceConfig => "workspace config · local first", - Self::Defaults => "defaults · local first", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RelayConfig { - pub urls: Vec<String>, - pub publish_policy: RelayPublishPolicy, - pub source: RelayConfigSource, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct LocalConfig { pub root: PathBuf, @@ -530,8 +448,6 @@ pub struct RuntimeConfig { pub identity: IdentityConfig, pub signer: SignerConfig, pub transport: TransportConfig, - pub publish: PublishConfig, - pub relay: RelayConfig, pub local: LocalConfig, pub myc: MycConfig, pub hyf: HyfConfig, @@ -875,9 +791,7 @@ impl RuntimeConfig { app_config.as_ref(), workspace_config.as_ref(), )?, - transport: transport.clone(), - publish: publish_config_from_transport(&transport), - relay: relay_config_from_transport(&transport), + transport, local: LocalConfig { root: paths.app_data_root.join(DEFAULT_LOCAL_STATE_DIR), replica_db_path: paths @@ -1195,41 +1109,6 @@ fn resolve_transport_proxy_file( }) } -fn publish_config_from_transport(transport: &TransportConfig) -> PublishConfig { - PublishConfig { - transport: match transport.profile { - TransportProfileKind::Proxy => PublishTransport::Proxy, - TransportProfileKind::LocalOnly - | TransportProfileKind::Nostr - | TransportProfileKind::ReticulumPreview => PublishTransport::Nostr, - }, - source: match transport.source { - TransportConfigSource::Environment => PublishTransportSource::Environment, - TransportConfigSource::UserConfig => PublishTransportSource::UserConfig, - TransportConfigSource::WorkspaceConfig => PublishTransportSource::WorkspaceConfig, - TransportConfigSource::Defaults => PublishTransportSource::Defaults, - }, - proxy: transport.proxy.clone(), - } -} - -fn relay_config_from_transport(transport: &TransportConfig) -> RelayConfig { - RelayConfig { - urls: if matches!(transport.profile, TransportProfileKind::Nostr) { - transport.nostr_relay_urls.clone() - } else { - Vec::new() - }, - publish_policy: RelayPublishPolicy::Any, - source: match transport.source { - TransportConfigSource::Environment => RelayConfigSource::Environment, - TransportConfigSource::UserConfig => RelayConfigSource::UserConfig, - TransportConfigSource::WorkspaceConfig => RelayConfigSource::WorkspaceConfig, - TransportConfigSource::Defaults => RelayConfigSource::Defaults, - }, - } -} - fn resolve_rhi_config( env: &dyn Environment, env_file: &EnvFileValues, @@ -2046,8 +1925,7 @@ mod tests { CapabilityBindingSource, CapabilityBindingTargetKind, DEFAULT_HYF_EXECUTABLE, DEFAULT_LOG_FILTER, DEFAULT_MYC_STATUS_TIMEOUT_MS, DEFAULT_RPC_URL, EnvFileValues, Environment, HyfConfig, INFERENCE_HYF_STDIO_CAPABILITY, InteractionConfig, OutputConfig, - OutputFormat, PathsConfig, PublishConfig, PublishTransport, PublishTransportSource, - RelayConfigSource, RelayPublishPolicy, RuntimeConfig, SignerBackend, TransportConfigSource, + OutputFormat, PathsConfig, RuntimeConfig, SignerBackend, TransportConfigSource, TransportProfileKind, Verbosity, parse_env_file_values, }; use crate::cli::global::{RuntimeInvocationArgs, RuntimeOutputFormatArg}; @@ -2282,19 +2160,13 @@ mod tests { TransportConfigSource::Environment ); assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Environment, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } + resolved.transport.nostr_relay_urls, + vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] ); assert_eq!( - resolved.relay.urls, - vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] + resolved.transport.source, + TransportConfigSource::Environment ); - assert_eq!(resolved.relay.source, RelayConfigSource::Environment); - assert_eq!(resolved.relay.publish_policy, RelayPublishPolicy::Any); assert_eq!(resolved.myc.executable, PathBuf::from("bin/myc-cli")); assert_eq!(resolved.myc.status_timeout_ms, 2500); assert_eq!( @@ -2392,18 +2264,13 @@ mod tests { TransportConfigSource::Environment ); assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Environment, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } + resolved.transport.nostr_relay_urls, + vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] ); assert_eq!( - resolved.relay.urls, - vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()] + resolved.transport.source, + TransportConfigSource::Environment ); - assert_eq!(resolved.relay.source, RelayConfigSource::Environment); assert_eq!(resolved.myc.executable, PathBuf::from("bin/myc")); assert_eq!(resolved.myc.status_timeout_ms, 3500); assert_eq!( @@ -2471,8 +2338,8 @@ mod tests { RadrootsSecretBackend::HostVault(RadrootsHostVaultPolicy::desktop()) ); assert_eq!(resolved.signer.backend, SignerBackend::Local); - assert_eq!(resolved.publish.transport, PublishTransport::Nostr); - assert_eq!(resolved.relay.urls, Vec::<String>::new()); + assert_eq!(resolved.transport.profile, TransportProfileKind::Nostr); + assert_eq!(resolved.transport.nostr_relay_urls, Vec::<String>::new()); assert_eq!(resolved.myc.executable, PathBuf::from("myc")); assert_eq!( resolved.myc.status_timeout_ms, @@ -2796,15 +2663,13 @@ RADROOTS_CLI_HYF_EXECUTABLE=bin/hyfd TransportConfigSource::Environment ); assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Environment, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } + resolved.transport.nostr_relay_urls, + vec!["wss://relay.env-file".to_owned()] + ); + assert_eq!( + resolved.transport.source, + TransportConfigSource::Environment ); - assert_eq!(resolved.relay.urls, vec!["wss://relay.env-file".to_owned()]); - assert_eq!(resolved.relay.source, RelayConfigSource::Environment); assert_eq!(resolved.myc.executable, PathBuf::from("bin/myc")); assert_eq!(resolved.myc.status_timeout_ms, 4500); assert_eq!( @@ -2947,14 +2812,13 @@ RADROOTS_CLI_LOGGING_STDOUT=true assert_eq!(resolved.transport.profile, TransportProfileKind::Nostr); assert_eq!(resolved.transport.source, TransportConfigSource::UserConfig); assert_eq!( - resolved.relay.urls, + resolved.transport.nostr_relay_urls, vec![ "wss://relay.user".to_owned(), "wss://relay.workspace".to_owned() ] ); - assert_eq!(resolved.relay.source, RelayConfigSource::UserConfig); - assert_eq!(resolved.relay.publish_policy, RelayPublishPolicy::Any); + assert_eq!(resolved.transport.source, TransportConfigSource::UserConfig); } #[test] @@ -2994,36 +2858,6 @@ RADROOTS_CLI_LOGGING_STDOUT=true resolved.transport.source, TransportConfigSource::Environment ); - assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Proxy, - source: PublishTransportSource::Environment, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } - ); - - let env = repo_local_env( - workspace_root.clone(), - repo_local_root.clone(), - user_home.clone(), - BTreeMap::from([( - "RADROOTS_CLI_TRANSPORT_PROFILE".to_owned(), - "proxy".to_owned(), - )]), - ); - let resolved = - RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default()) - .expect("resolve environment publish transport"); - assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Proxy, - source: PublishTransportSource::Environment, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } - ); - let env = repo_local_env( workspace_root.clone(), repo_local_root.clone(), @@ -3035,15 +2869,6 @@ RADROOTS_CLI_LOGGING_STDOUT=true .expect("resolve user transport profile"); assert_eq!(resolved.transport.profile, TransportProfileKind::Nostr); assert_eq!(resolved.transport.source, TransportConfigSource::UserConfig); - assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::UserConfig, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } - ); - fs::remove_file(app_config_dir.join("config.toml")).expect("remove user config"); let env = repo_local_env( workspace_root.clone(), @@ -3059,15 +2884,6 @@ RADROOTS_CLI_LOGGING_STDOUT=true resolved.transport.source, TransportConfigSource::WorkspaceConfig ); - assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Proxy, - source: PublishTransportSource::WorkspaceConfig, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } - ); - fs::remove_file(repo_local_root.join("config.toml")).expect("remove workspace config"); let env = repo_local_env(workspace_root, repo_local_root, user_home, BTreeMap::new()); let resolved = @@ -3075,14 +2891,6 @@ RADROOTS_CLI_LOGGING_STDOUT=true .expect("resolve default transport profile"); assert_eq!(resolved.transport.profile, TransportProfileKind::LocalOnly); assert_eq!(resolved.transport.source, TransportConfigSource::Defaults); - assert_eq!( - resolved.publish, - PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - } - ); } #[test] @@ -3503,7 +3311,10 @@ target = "workflow-default" let config = RuntimeConfig::resolve_with_env_file(&args, &env, &EnvFileValues::default()) .expect("valid relay url"); - assert_eq!(config.relay.urls, vec!["ws://[::1]:443/relay"]); + assert_eq!( + config.transport.nostr_relay_urls, + vec!["ws://[::1]:443/relay"] + ); } #[test] diff --git a/src/runtime/farm.rs b/src/runtime/farm.rs @@ -27,7 +27,7 @@ use crate::cli::global::{ }; use crate::runtime::RuntimeError; use crate::runtime::account::{self, AccountRecordView}; -use crate::runtime::config::{PublishTransport, RuntimeConfig, SignerBackend}; +use crate::runtime::config::{RuntimeConfig, SignerBackend, TransportProfileKind}; use crate::runtime::farm_config::{ self, FarmConfigDocument, FarmConfigScope, FarmConfigSelection, FarmListingDefaults, FarmMissingField, FarmPublicationStatus, ResolvedFarmConfig, SUPPORTED_FARM_CONFIG_VERSION, @@ -474,7 +474,7 @@ pub fn status( config_valid: false, account_state: "not_checked".to_owned(), listing_defaults_state: "missing".to_owned(), - publish_transport: config.publish.transport.as_str().to_owned(), + transport_profile: config.transport.profile.as_str().to_owned(), publish_state: "not_checked".to_owned(), publish_executable: false, publish_reason: None, @@ -538,7 +538,7 @@ pub fn status( config_valid: true, account_state: account_state.to_owned(), listing_defaults_state: listing_defaults_state.to_owned(), - publish_transport: config.publish.transport.as_str().to_owned(), + transport_profile: config.transport.profile.as_str().to_owned(), publish_state: publish.state.to_owned(), publish_executable: publish.executable, publish_reason: publish.reason, @@ -621,7 +621,9 @@ fn relay_farm_publish_readiness( config: &RuntimeConfig, account: &AccountRecordView, ) -> FarmPublishReadiness { - if matches!(config.publish.transport, PublishTransport::Nostr) && config.relay.urls.is_empty() { + if matches!(config.transport.profile, TransportProfileKind::Nostr) + && config.transport.nostr_relay_urls.is_empty() + { return FarmPublishReadiness { state: "unconfigured", executable: false, @@ -1152,7 +1154,7 @@ fn sdk_enqueued_publish_view( deduplicated: matches!(enqueue.state, SdkMutationState::AlreadyQueued), target_relays: push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()), + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()), connected_relays: push_event .map(sdk_push_connected_relays) .unwrap_or_default(), diff --git a/src/runtime/find.rs b/src/runtime/find.rs @@ -51,7 +51,7 @@ pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView, source: FIND_SOURCE.to_owned(), query, count: 0, - relay_count: config.relay.urls.len(), + relay_count: config.transport.nostr_relay_urls.len(), replica_db: config.local.replica_db_path.display().to_string(), freshness: missing_freshness(), results: Vec::new(), @@ -71,7 +71,7 @@ pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView, .map(|rewrite| rewrite.query_terms.clone()) .unwrap_or_else(|| normalize_query_terms(args.query.clone())); let rows = db.trade_product_search(effective_query_terms.as_slice())?; - let relay_count = config.relay.urls.len(); + let relay_count = config.transport.nostr_relay_urls.len(); let result_provenance = FindResultProvenanceView { origin: "local_replica.trade_product".to_owned(), freshness: freshness.display.clone(), @@ -159,7 +159,7 @@ pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView, } fn refresh_market_if_needed(config: &RuntimeConfig) -> Result<(), RuntimeError> { - if config.output.dry_run || config.relay.urls.is_empty() { + if config.output.dry_run || config.transport.nostr_relay_urls.is_empty() { return Ok(()); } let executor = SqliteExecutor::open(&config.local.replica_db_path)?; diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs @@ -1616,7 +1616,7 @@ pub fn get( let provenance = FindResultProvenanceView { origin: "local_replica.trade_product".to_owned(), freshness: freshness.display.clone(), - relay_count: config.relay.urls.len(), + relay_count: config.transport.nostr_relay_urls.len(), }; if !config.local.replica_db_path.exists() { @@ -1724,7 +1724,7 @@ pub fn get( fn refresh_market_listing_if_needed(config: &RuntimeConfig) -> Result<(), RuntimeError> { if !config.local.replica_db_path.exists() || config.output.dry_run - || config.relay.urls.is_empty() + || config.transport.nostr_relay_urls.is_empty() { return Ok(()); } @@ -1902,7 +1902,7 @@ fn sdk_enqueued_publish_view( let reason = sdk_publish_reason(args, push_event); let target_relays = push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()); + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); let connected_relays = push_event .map(sdk_push_connected_relays) .unwrap_or_default(); diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -1164,7 +1164,7 @@ pub fn event_list( config: &RuntimeConfig, order_id: Option<&str>, ) -> Result<OrderEventListView, RuntimeError> { - if config.relay.urls.is_empty() { + if config.transport.nostr_relay_urls.is_empty() { return Ok(order_event_list_unconfigured( None, ORDER_ACTOR_CONTEXT_NETWORK_ONLY, @@ -1181,16 +1181,20 @@ pub fn event_list( None, ORDER_ACTOR_CONTEXT_NETWORK_ONLY, "trade event list requires a selected seller account".to_owned(), - config.relay.urls.clone(), + config.transport.nostr_relay_urls.clone(), vec!["radroots account create".to_owned()], )); } }; let seller_pubkey = actor_context.seller_pubkey; let filter = order_request_filter(seller_pubkey.as_str(), order_id)?; - let receipt = - fetch_relay_events_via_shared_transport(&config.relay.urls, now_unix_ms(), 1_000, filter) - .map_err(order_relay_fetch_error)?; + let receipt = fetch_relay_events_via_shared_transport( + &config.transport.nostr_relay_urls, + now_unix_ms(), + 1_000, + filter, + ) + .map_err(order_relay_fetch_error)?; if receipt.connected_relays.is_empty() && !receipt.failed_relays.is_empty() { return Ok(order_event_list_unavailable( seller_pubkey, @@ -1588,7 +1592,7 @@ fn sdk_trade_decision_outcome_view( view.prev_event_id = Some(plan.request_event_id.to_string()); view.event_id = Some(plan.expected_event_id.to_string()); view.event_kind = Some(KIND_ORDER_DECISION); - view.target_relays = config.relay.urls.clone(); + view.target_relays = config.transport.nostr_relay_urls.clone(); view.reason = Some(format!( "dry run requested; seller trade {} publication skipped", args.decision.command() @@ -1630,7 +1634,7 @@ fn sdk_trade_revision_outcome_view( }) .collect(); view.economics = Some(revision.economics); - view.target_relays = config.relay.urls.clone(); + view.target_relays = config.transport.nostr_relay_urls.clone(); view.reason = Some("dry run requested; seller revision proposal publication skipped".to_owned()); view.actions = vec![format!("radroots trade status get {}", status.order_id)]; @@ -1665,7 +1669,7 @@ fn sdk_trade_revision_decision_outcome_view( view.prev_event_id = Some(plan.previous_event_id.to_string()); view.event_id = Some(plan.expected_event_id.to_string()); view.event_kind = Some(KIND_ORDER_REVISION_DECISION); - view.target_relays = config.relay.urls.clone(); + view.target_relays = config.transport.nostr_relay_urls.clone(); view.reason = Some(format!( "dry run requested; buyer revision {} publication skipped", args.decision.command() @@ -1696,7 +1700,7 @@ fn sdk_trade_cancellation_outcome_view( view.prev_event_id = Some(plan.previous_event_id.to_string()); view.event_id = Some(plan.expected_event_id.to_string()); view.event_kind = Some(KIND_ORDER_CANCELLATION); - view.target_relays = config.relay.urls.clone(); + view.target_relays = config.transport.nostr_relay_urls.clone(); view.reason = Some("dry run requested; buyer trade cancellation publication skipped".to_owned()); view.actions = vec![format!("radroots trade status get {}", status.order_id)]; @@ -1920,7 +1924,7 @@ fn order_decision_base_view( event_kind: None, inventory: None, dry_run, - target_relays: config.relay.urls.clone(), + target_relays: config.transport.nostr_relay_urls.clone(), connected_relays: Vec::new(), acknowledged_relays: Vec::new(), failed_relays: Vec::new(), @@ -1960,7 +1964,7 @@ fn order_revision_base_view( economics: None, inventory: None, dry_run, - target_relays: config.relay.urls.clone(), + target_relays: config.transport.nostr_relay_urls.clone(), connected_relays: Vec::new(), acknowledged_relays: Vec::new(), failed_relays: Vec::new(), @@ -2001,7 +2005,7 @@ fn order_revision_decision_base_view( economics: None, inventory: None, dry_run, - target_relays: config.relay.urls.clone(), + target_relays: config.transport.nostr_relay_urls.clone(), connected_relays: Vec::new(), acknowledged_relays: Vec::new(), failed_relays: Vec::new(), @@ -2038,7 +2042,7 @@ fn order_cancellation_base_view( event_kind: None, cancellation_reason: Some(args.reason.clone()), dry_run, - target_relays: config.relay.urls.clone(), + target_relays: config.transport.nostr_relay_urls.clone(), connected_relays: Vec::new(), acknowledged_relays: Vec::new(), failed_relays: Vec::new(), @@ -2406,7 +2410,7 @@ fn sdk_enqueued_order_decision_view( view.event_kind = Some(KIND_ORDER_DECISION); view.target_relays = push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()); + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); view.connected_relays = push_event .map(sdk_push_connected_relays) .unwrap_or_default(); @@ -2515,7 +2519,7 @@ fn sdk_enqueued_order_revision_view( view.event_kind = Some(KIND_ORDER_REVISION_PROPOSAL); view.target_relays = push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()); + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); view.connected_relays = push_event .map(sdk_push_connected_relays) .unwrap_or_default(); @@ -2558,7 +2562,7 @@ fn sdk_enqueued_order_revision_decision_view( } view.target_relays = push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()); + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); view.connected_relays = push_event .map(sdk_push_connected_relays) .unwrap_or_default(); @@ -2595,7 +2599,7 @@ fn sdk_enqueued_order_cancellation_view( view.event_kind = Some(KIND_ORDER_CANCELLATION); view.target_relays = push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()); + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()); view.connected_relays = push_event .map(sdk_push_connected_relays) .unwrap_or_default(); @@ -4477,7 +4481,7 @@ fn order_rebind_existing_request_check( config: &RuntimeConfig, loaded: &LoadedOrderDraft, ) -> Result<OrderRebindExistingRequestCheck, RuntimeError> { - if config.relay.urls.is_empty() { + if config.transport.nostr_relay_urls.is_empty() { return Ok(OrderRebindExistingRequestCheck { state: "skipped_no_relays".to_owned(), event_ids: Vec::new(), @@ -4488,9 +4492,13 @@ fn order_rebind_existing_request_check( loaded.document.order.seller_pubkey.as_str(), Some(loaded.document.order.order_id.as_str()), )?; - let receipt = - fetch_relay_events_via_shared_transport(&config.relay.urls, now_unix_ms(), 1_000, filter) - .map_err(order_relay_fetch_error)?; + let receipt = fetch_relay_events_via_shared_transport( + &config.transport.nostr_relay_urls, + now_unix_ms(), + 1_000, + filter, + ) + .map_err(order_relay_fetch_error)?; let mut event_ids = receipt .events .iter() @@ -4901,9 +4909,13 @@ fn sdk_trade_submit_outcome_view( outcome: TradeMutationOutcome<TradeSubmitPlan, TradeSubmitReceipt>, ) -> OrderSubmitView { match outcome { - TradeMutationOutcome::DryRun { plan } => { - order_submit_dry_run_view(config, loaded, args, plan, config.relay.urls.clone()) - } + TradeMutationOutcome::DryRun { plan } => order_submit_dry_run_view( + config, + loaded, + args, + plan, + config.transport.nostr_relay_urls.clone(), + ), TradeMutationOutcome::Enqueued { receipt } => { sdk_enqueued_order_submit_view(config, loaded, args, receipt, None) } @@ -4943,7 +4955,7 @@ fn sdk_enqueued_order_submit_view( deduplicated: matches!(enqueue.state, SdkMutationState::AlreadyQueued), target_relays: push_event .map(sdk_push_target_relays) - .unwrap_or_else(|| config.relay.urls.clone()), + .unwrap_or_else(|| config.transport.nostr_relay_urls.clone()), connected_relays: push_event .map(sdk_push_connected_relays) .unwrap_or_default(), diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs @@ -2,7 +2,7 @@ use crate::runtime::config::{ CapabilityBindingInspection, CapabilityBindingInspectionState, INFERENCE_HYF_STDIO_CAPABILITY, }; -use crate::runtime::config::{PublishTransport, RuntimeConfig}; +use crate::runtime::config::{RuntimeConfig, TransportProfileKind}; #[cfg(test)] use crate::runtime::hyf; use crate::view::runtime::PublishRuntimeView; @@ -21,7 +21,7 @@ pub enum ProviderProvenance { DirectConfig, #[cfg(test)] Disabled, - PublishTransport, + TransportProfile, #[cfg(test)] Unavailable, } @@ -37,7 +37,7 @@ impl ProviderProvenance { Self::DirectConfig => "direct_config", #[cfg(test)] Self::Disabled => "disabled", - Self::PublishTransport => "publish_transport", + Self::TransportProfile => "transport_profile", #[cfg(test)] Self::Unavailable => "unavailable", } @@ -95,23 +95,33 @@ pub fn resolve_write_plane_provider( config: &RuntimeConfig, publish: &PublishRuntimeView, ) -> WritePlaneProviderView { - let (provider_runtime_id, binding_model, detail) = match config.publish.transport { - PublishTransport::Nostr => ( + let (provider_runtime_id, binding_model, detail) = match config.transport.profile { + TransportProfileKind::Nostr => ( "nostr", "nostr_transport", "Nostr transport profile is selected; readiness is reported under publish", ), - PublishTransport::Proxy => ( + TransportProfileKind::Proxy => ( "proxy", "proxy_transport", "proxy transport profile is selected; readiness is reported under publish", ), + TransportProfileKind::LocalOnly => ( + "local_only", + "local_transport", + "local_only transport profile does not provide network publish", + ), + TransportProfileKind::ReticulumPreview => ( + "reticulum_preview", + "reticulum_preview", + "reticulum preview transport is non-networked in the MVP", + ), }; WritePlaneProviderView { provider_runtime_id: provider_runtime_id.to_owned(), binding_model: binding_model.to_owned(), state: publish.state.clone(), - provenance: ProviderProvenance::PublishTransport.as_str().to_owned(), + provenance: ProviderProvenance::TransportProfile.as_str().to_owned(), source: publish.source.clone(), target_kind: None, target: None, @@ -260,9 +270,7 @@ mod tests { AccountConfig, AccountSecretContractConfig, CapabilityBindingConfig, CapabilityBindingSource, CapabilityBindingTargetKind, HyfConfig, IdentityConfig, InteractionConfig, LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, - RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, - SignerConfig, Verbosity, + PathsConfig, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::view::runtime::{ PublishProviderRuntimeView, PublishRelayRuntimeView, PublishRuntimeView, @@ -327,16 +335,6 @@ mod tests { backend: SignerBackend::Local, }, transport: crate::runtime::config::TransportConfig::local_only(), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - 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"), @@ -368,22 +366,22 @@ mod tests { reason: Option<&str>, ) -> PublishRuntimeView { PublishRuntimeView { - transport: config.publish.transport.as_str().to_owned(), - source: config.publish.source.as_str().to_owned(), - transport_family: config.publish.transport.transport_family().to_owned(), + transport: config.transport.profile.as_str().to_owned(), + source: config.transport.source.as_str().to_owned(), + transport_family: config.transport.profile.transport_family().to_owned(), state: state.to_owned(), executable: state == "ready", reason: reason.map(str::to_owned), signed_write_required: true, relay: PublishRelayRuntimeView { - ready: !config.relay.urls.is_empty(), - count: config.relay.urls.len(), - source: config.relay.source.as_str().to_owned(), + ready: !config.transport.nostr_relay_urls.is_empty(), + count: config.transport.nostr_relay_urls.len(), + source: config.transport.source.as_str().to_owned(), }, provider: PublishProviderRuntimeView { - provider_runtime_id: config.publish.transport.as_str().to_owned(), + provider_runtime_id: config.transport.profile.as_str().to_owned(), state: state.to_owned(), - source: config.publish.source.as_str().to_owned(), + source: config.transport.source.as_str().to_owned(), reason: reason.map(str::to_owned), }, } @@ -403,7 +401,7 @@ mod tests { assert_eq!(view.state, "unconfigured"); assert_eq!( view.provenance, - ProviderProvenance::PublishTransport.as_str() + ProviderProvenance::TransportProfile.as_str() ); assert!(view.target.is_none()); assert!(view.detail.contains("configured Nostr relay")); diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -718,9 +718,8 @@ mod tests { use super::*; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RhiConfig, RpcConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RhiConfig, + RpcConfig, SignerBackend, SignerConfig, Verbosity, }; struct DirectRrRsDependency { @@ -2235,16 +2234,6 @@ mod tests { transport: crate::runtime::config::TransportConfig::from_nostr_relay_urls( relays.clone(), ), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: relays, - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Flags, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs @@ -223,7 +223,7 @@ where let mut view = empty_action_from_snapshot(snapshot, "pull"); view.state = "ready".to_owned(); view.reason = Some("dry run requested; relay fetch skipped".to_owned()); - view.target_relays = config.relay.urls.clone(); + view.target_relays = config.transport.nostr_relay_urls.clone(); view.fetched_count = Some(0); view.ingested_count = Some(0); view.publishable_count = None; @@ -237,7 +237,7 @@ where } let started_at = unix_now(); - let receipt = match fetcher(&config.relay.urls, scope.filter()) { + let receipt = match fetcher(&config.transport.nostr_relay_urls, scope.filter()) { Ok(receipt) if receipt.connected_relays.is_empty() && !receipt.failed_relays.is_empty() => { let target_relays = receipt.target_relays; let failed_relays = relay_failures(receipt.failed_relays); @@ -249,7 +249,7 @@ where &executor, &sync_record_from_failure( scope, - &config.relay.urls, + &config.transport.nostr_relay_urls, target_relays.clone(), failed_relays.clone(), started_at, @@ -274,8 +274,8 @@ where &executor, &sync_record_from_failure( scope, - &config.relay.urls, - config.relay.urls.clone(), + &config.transport.nostr_relay_urls, + config.transport.nostr_relay_urls.clone(), Vec::new(), started_at, failure_reason.clone(), @@ -285,7 +285,7 @@ where view.state = "unavailable".to_owned(); view.reason = Some(failure_reason); view.reason_code = Some("relay_fetch_failed".to_owned()); - view.target_relays = config.relay.urls.clone(); + view.target_relays = config.transport.nostr_relay_urls.clone(); view.freshness = freshness_for_scope_from_executor(config, &executor, scope)?; return Ok(view); } @@ -296,7 +296,13 @@ where let ingest = ingest_events(&executor, &receipt, scope)?; record_sync_run( &executor, - &sync_record_from_ingest(scope, &config.relay.urls, &receipt, &ingest, started_at)?, + &sync_record_from_ingest( + scope, + &config.transport.nostr_relay_urls, + &receipt, + &ingest, + started_at, + )?, )?; let failed_relays = relay_failures(receipt.failed_relays); let failed_count = ingest.failed_count + failed_relays.len(); @@ -311,8 +317,8 @@ where source: INGEST_SOURCE.to_owned(), local_root: config.local.root.display().to_string(), replica_db: "ready".to_owned(), - relay_count: config.relay.urls.len(), - publish_policy: config.relay.publish_policy.as_str().to_owned(), + relay_count: config.transport.nostr_relay_urls.len(), + publish_policy: "any".to_owned(), freshness, queue: derived_projection_sync_queue(queue.expected_count, queue.pending_count), target_relays: receipt.target_relays, @@ -423,7 +429,7 @@ fn sdk_sync_status_view(config: &RuntimeConfig, receipt: SyncStatusReceipt) -> S local_root: config.local.root.display().to_string(), replica_db: "derived_projection_not_checked".to_owned(), relay_count, - publish_policy: config.relay.publish_policy.as_str().to_owned(), + publish_policy: "any".to_owned(), freshness: sdk_sync_freshness(&receipt), queue: sdk_sync_queue(&receipt), reason: None, @@ -521,8 +527,8 @@ fn sdk_push_action_view( source: SDK_PUSH_SOURCE.to_owned(), local_root: config.local.root.display().to_string(), replica_db: "derived_projection_not_checked".to_owned(), - relay_count: config.relay.urls.len(), - publish_policy: config.relay.publish_policy.as_str().to_owned(), + relay_count: config.transport.nostr_relay_urls.len(), + publish_policy: "any".to_owned(), freshness, queue, target_relays, @@ -737,8 +743,8 @@ fn inspect_sync(config: &RuntimeConfig) -> Result<SyncSnapshot, RuntimeError> { source: SYNC_SOURCE.to_owned(), local_root: config.local.root.display().to_string(), replica_db: "missing".to_owned(), - relay_count: config.relay.urls.len(), - publish_policy: config.relay.publish_policy.as_str().to_owned(), + relay_count: config.transport.nostr_relay_urls.len(), + publish_policy: "any".to_owned(), freshness: missing_freshness(), queue: derived_projection_sync_queue(0, 0), reason: Some("local replica database is not initialized".to_owned()), @@ -751,8 +757,8 @@ fn inspect_sync(config: &RuntimeConfig) -> Result<SyncSnapshot, RuntimeError> { let queue = radroots_replica_sync_status(&executor)?; let freshness = freshness_for_scope_from_executor(config, &executor, RelayIngestScope::SyncPull)?; - let relay_count = config.relay.urls.len(); - let publish_policy = config.relay.publish_policy.as_str().to_owned(); + let relay_count = config.transport.nostr_relay_urls.len(); + let publish_policy = "any".to_owned(); let mut actions = Vec::new(); if relay_count == 0 { @@ -820,7 +826,7 @@ pub(crate) fn relay_provenance_relays_for_scope( let executor = SqliteExecutor::open(&config.local.replica_db_path)?; migrations::run_all_up(&executor)?; ensure_sync_run_table(&executor)?; - let current_fingerprint = relay_set_fingerprint(&config.relay.urls); + let current_fingerprint = relay_set_fingerprint(&config.transport.nostr_relay_urls); let Some(run) = latest_sync_run(&executor, scope)? else { return Ok(Vec::new()); }; @@ -842,7 +848,7 @@ pub(crate) fn freshness_for_scope_from_executor( let now = unix_now(); let age_seconds = last_event_at.map(|last_event_at| now.saturating_sub(last_event_at)); ensure_sync_run_table(executor)?; - let current_fingerprint = relay_set_fingerprint(&config.relay.urls); + let current_fingerprint = relay_set_fingerprint(&config.transport.nostr_relay_urls); let latest = latest_sync_run(executor, scope)?; let current = latest .as_ref() @@ -1465,9 +1471,8 @@ mod tests { use crate::cli::global::{FindQueryArgs, RecordLookupArgs}; use crate::runtime::config::{ AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, - PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, RelayConfigSource, - RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig, RpcConfig, + RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; const FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA"; @@ -2348,16 +2353,6 @@ mod tests { transport: crate::runtime::config::TransportConfig::from_nostr_relay_urls( relays.clone(), ), - publish: PublishConfig { - transport: PublishTransport::Nostr, - source: PublishTransportSource::Defaults, - proxy: crate::runtime::config::ProxyTransportConfig::default(), - }, - relay: RelayConfig { - urls: relays, - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, local: LocalConfig { root: data.join("apps/cli/replica"), replica_db_path: data.join("apps/cli/replica/replica.sqlite"), diff --git a/src/view/runtime.rs b/src/view/runtime.rs @@ -903,7 +903,7 @@ pub struct FarmStatusView { pub config_valid: bool, pub account_state: String, pub listing_defaults_state: String, - pub publish_transport: String, + pub transport_profile: String, pub publish_state: String, pub publish_executable: bool, #[serde(skip_serializing_if = "Option::is_none")] @@ -2810,7 +2810,7 @@ pub struct SellMutationView { #[serde(default)] pub deduplicated: bool, #[serde(skip_serializing_if = "Option::is_none")] - pub publish_transport: Option<String>, + pub transport_profile: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub job_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -1728,7 +1728,7 @@ fn farm_readiness_check_reports_mode_specific_publish_gates() { } else { &relay_value["result"] }; - assert_eq!(relay_detail["publish_transport"], "nostr"); + assert_eq!(relay_detail["transport_profile"], "nostr"); assert_eq!(relay_detail["publish_state"], "unconfigured"); assert_eq!(relay_detail["publish_executable"], false); assert_eq!( @@ -1762,7 +1762,7 @@ signer_session_ref = "session_test" assert!(output.status.success()); assert_eq!(proxy_value["operation_id"], "farm.readiness.check"); - assert_contains(&proxy_value["result"]["publish_transport"], "proxy"); + assert_contains(&proxy_value["result"]["transport_profile"], "proxy"); assert_eq!(proxy_value["result"]["publish_state"], "ready"); assert_eq!(proxy_value["result"]["publish_executable"], true); assert_eq!(proxy_value["result"]["reason"], Value::Null); @@ -3750,7 +3750,7 @@ fn order_status_get_invalid_order_id_uses_sdk_error_contract() { } #[test] -fn removed_publish_transport_flag_is_rejected() { +fn removed_transport_profile_flag_is_rejected() { let sandbox = RadrootsCliSandbox::new(); let output = sandbox .command()