cli

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

commit 6343ebe1310bde309a5bfe63ff86f104dd0545d1
parent 1e0b18f6d0d0dfe38d694595b928c506694b7af7
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Jul 2026 17:24:28 +0000

transport: align CLI status contract

- serialize CLI transport and sync status rows with canonical transport fields

- add Reticulum preview scope and inert agent endpoint config

- remove old SDK target-policy helper usage from CLI runtimes

Diffstat:
Msrc/cli/input.rs | 10++++++++++
Msrc/cli/transport.rs | 4++++
Msrc/out/terminal/renderers/runtime.rs | 2+-
Msrc/runtime/config.rs | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/runtime/order.rs | 2+-
Msrc/runtime/sdk.rs | 44++++++++++++++++++++++++++++++++++++--------
Msrc/runtime/sync.rs | 184++++++++++++++++++++++++++-----------------------------------------------------
Msrc/runtime/transport.rs | 145++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/view/runtime.rs | 8++++++--
Mtests/target_cli.rs | 57++++++++++++++++++++++++++++++++++++++-------------------
10 files changed, 331 insertions(+), 227 deletions(-)

diff --git a/src/cli/input.rs b/src/cli/input.rs @@ -337,6 +337,16 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { Value::String(behavior.as_str().to_owned()), ); } + insert_string( + &mut input, + "reticulum_preview_scope", + &args.reticulum_preview_scope, + ); + insert_string( + &mut input, + "reticulum_preview_agent_endpoint", + &args.reticulum_preview_agent_endpoint, + ); insert_string(&mut input, "proxy_url", &args.proxy_url); insert_path(&mut input, "proxy_token_file", &args.proxy_token_file); insert_string( diff --git a/src/cli/transport.rs b/src/cli/transport.rs @@ -35,6 +35,10 @@ pub struct TransportProfileSetArgs { pub nostr_relay: Vec<String>, #[arg(long = "reticulum-preview-behavior", value_enum)] pub reticulum_preview_behavior: Option<ReticulumPreviewBehaviorArg>, + #[arg(long = "reticulum-preview-scope")] + pub reticulum_preview_scope: Option<String>, + #[arg(long = "reticulum-preview-agent-endpoint")] + pub reticulum_preview_agent_endpoint: Option<String>, #[arg(long = "proxy-url")] pub proxy_url: Option<String>, #[arg(long = "proxy-token-file", value_name = "PATH")] diff --git a/src/out/terminal/renderers/runtime.rs b/src/out/terminal/renderers/runtime.rs @@ -186,7 +186,7 @@ fn push_transport_status_table(document: &mut TerminalDocument, result: &Value, .flatten() .map(|transport| { TerminalTableRow::new(vec![ - common::string(transport, &["transport_kind"]).unwrap_or_default(), + common::string(transport, &["transport"]).unwrap_or_default(), common::string(transport, &["profile_id"]).unwrap_or_default(), common::bool_path(transport, &["configured"]) .map(|value| if value { "yes" } else { "no" }.to_owned()) diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use radroots_runtime::{parse_bool_value, parse_strict_env_file, parse_u64_value}; use radroots_runtime_paths::RadrootsPathResolver; use radroots_secret_vault::{RadrootsHostVaultPolicy, RadrootsSecretBackend}; +use radroots_transport::{RADROOTS_RETICULUM_PREVIEW_SCOPE_ID, RadrootsTransportMeshScopeId}; use radroots_transport_nostr::{ RadrootsRelayTransportError, RadrootsRelayUrl, RadrootsRelayUrlPolicy, }; @@ -45,6 +46,10 @@ const ENV_CLI_TRANSPORT_PROFILE: &str = "RADROOTS_CLI_TRANSPORT_PROFILE"; const ENV_CLI_TRANSPORT_NOSTR_RELAY_URLS: &str = "RADROOTS_CLI_TRANSPORT_NOSTR_RELAY_URLS"; const ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_BEHAVIOR: &str = "RADROOTS_CLI_TRANSPORT_RETICULUM_PREVIEW_BEHAVIOR"; +const ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE: &str = + "RADROOTS_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE"; +const ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_AGENT_ENDPOINT: &str = + "RADROOTS_CLI_TRANSPORT_RETICULUM_PREVIEW_AGENT_ENDPOINT"; const ENV_CLI_TRANSPORT_PROXY_URL: &str = "RADROOTS_CLI_TRANSPORT_PROXY_URL"; const ENV_CLI_TRANSPORT_PROXY_TOKEN_FILE: &str = "RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE"; const ENV_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID: &str = @@ -69,6 +74,8 @@ const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[ ENV_CLI_TRANSPORT_PROFILE, ENV_CLI_TRANSPORT_NOSTR_RELAY_URLS, ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_BEHAVIOR, + ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE, + ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_AGENT_ENDPOINT, ENV_CLI_TRANSPORT_PROXY_URL, ENV_CLI_TRANSPORT_PROXY_TOKEN_FILE, ENV_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID, @@ -252,6 +259,8 @@ pub struct TransportConfig { pub source: TransportConfigSource, pub nostr_relay_urls: Vec<String>, pub reticulum_preview_behavior: ReticulumPreviewBehavior, + pub reticulum_preview_scope: String, + pub reticulum_preview_agent_endpoint: Option<String>, pub proxy: ProxyTransportConfig, } @@ -262,6 +271,8 @@ impl TransportConfig { source: TransportConfigSource::Defaults, nostr_relay_urls: Vec::new(), reticulum_preview_behavior: ReticulumPreviewBehavior::RejectDeliveryAttempts, + reticulum_preview_scope: RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned(), + reticulum_preview_agent_endpoint: None, proxy: ProxyTransportConfig::default(), } } @@ -275,6 +286,8 @@ impl TransportConfig { source: TransportConfigSource::Defaults, nostr_relay_urls, reticulum_preview_behavior: ReticulumPreviewBehavior::RejectDeliveryAttempts, + reticulum_preview_scope: RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned(), + reticulum_preview_agent_endpoint: None, proxy: ProxyTransportConfig::default(), } } @@ -538,6 +551,8 @@ struct NostrTransportFileConfig { #[serde(default, deny_unknown_fields)] struct ReticulumPreviewFileConfig { behavior: Option<String>, + scope: Option<String>, + agent_endpoint: Option<String>, } #[derive(Debug, Default, Deserialize)] @@ -986,6 +1001,28 @@ fn resolve_transport_config( }) .transpose()? .unwrap_or(ReticulumPreviewBehavior::RejectDeliveryAttempts); + let reticulum_preview_scope = + env_value(env, env_file, &[ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE]) + .map(|value| { + parse_reticulum_preview_scope( + ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE, + value.as_str(), + ) + }) + .transpose()? + .unwrap_or_else(|| RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned()); + let reticulum_preview_agent_endpoint = env_value( + env, + env_file, + &[ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_AGENT_ENDPOINT], + ) + .map(|value| { + parse_reticulum_preview_agent_endpoint( + ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_AGENT_ENDPOINT, + value.as_str(), + ) + }) + .transpose()?; return Ok(TransportConfig { profile, source: TransportConfigSource::Environment, @@ -996,6 +1033,8 @@ fn resolve_transport_config( .transpose()? .unwrap_or_default(), reticulum_preview_behavior, + reticulum_preview_scope, + reticulum_preview_agent_endpoint, proxy: resolve_transport_proxy_env(env, env_file)?, }); } @@ -1021,6 +1060,8 @@ fn resolve_transport_config( source: TransportConfigSource::Defaults, nostr_relay_urls: Vec::new(), reticulum_preview_behavior: ReticulumPreviewBehavior::RejectDeliveryAttempts, + reticulum_preview_scope: RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned(), + reticulum_preview_agent_endpoint: None, proxy: ProxyTransportConfig::default(), }) } @@ -1044,6 +1085,8 @@ fn resolve_transport_file_config( source, nostr_relay_urls: Vec::new(), reticulum_preview_behavior: ReticulumPreviewBehavior::RejectDeliveryAttempts, + reticulum_preview_scope: RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned(), + reticulum_preview_agent_endpoint: None, proxy: ProxyTransportConfig::default(), }); }; @@ -1068,11 +1111,36 @@ fn resolve_transport_file_config( }) .transpose()? .unwrap_or(ReticulumPreviewBehavior::RejectDeliveryAttempts); + let reticulum_preview_scope = transport + .reticulum_preview + .as_ref() + .and_then(|preview| preview.scope.as_deref()) + .map(|value| { + parse_reticulum_preview_scope( + format!("{source_label}.reticulum_preview.scope").as_str(), + value, + ) + }) + .transpose()? + .unwrap_or_else(|| RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned()); + let reticulum_preview_agent_endpoint = transport + .reticulum_preview + .as_ref() + .and_then(|preview| preview.agent_endpoint.as_deref()) + .map(|value| { + parse_reticulum_preview_agent_endpoint( + format!("{source_label}.reticulum_preview.agent_endpoint").as_str(), + value, + ) + }) + .transpose()?; Ok(TransportConfig { profile, source, nostr_relay_urls, reticulum_preview_behavior, + reticulum_preview_scope, + reticulum_preview_agent_endpoint, proxy: resolve_transport_proxy_file(transport.proxy.as_ref(), source_label)?, }) } @@ -1909,6 +1977,30 @@ fn parse_reticulum_preview_behavior( } } +fn parse_reticulum_preview_scope(source: &str, value: &str) -> Result<String, RuntimeError> { + RadrootsTransportMeshScopeId::parse(value) + .map(|scope| scope.as_str().to_owned()) + .map_err(|error| RuntimeError::Config(format!("{source} is invalid: {error}"))) +} + +fn parse_reticulum_preview_agent_endpoint( + source: &str, + value: &str, +) -> Result<String, RuntimeError> { + if value.is_empty() + || value != value.trim() + || value + .chars() + .any(|ch| ch.is_ascii_control() || ch.is_ascii_whitespace()) + || value.find(':').is_none() + { + return Err(RuntimeError::Config(format!( + "{source} must be a non-empty URI-like endpoint without whitespace" + ))); + } + Ok(value.to_owned()) +} + fn resolve_account_secret_backend( env: &dyn Environment, env_file: &EnvFileValues, @@ -2871,7 +2963,7 @@ RADROOTS_CLI_LOGGING_STDOUT=true fs::create_dir_all(&app_config_dir).expect("app config dir"); fs::write( app_config_dir.join("config.toml"), - "[transport]\nprofile = \"hybrid\"\n\n[transport.nostr]\nrelay_urls = [\"wss://relay.user\", \"wss://relay.backup\"]\n\n[transport.reticulum_preview]\nbehavior = \"defer_delivery_plans\"\n", + "[transport]\nprofile = \"hybrid\"\n\n[transport.nostr]\nrelay_urls = [\"wss://relay.user\", \"wss://relay.backup\"]\n\n[transport.reticulum_preview]\nbehavior = \"defer_delivery_plans\"\nscope = \"farmers_market\"\nagent_endpoint = \"reticulum-agent:local\"\n", ) .expect("write user config"); @@ -2893,6 +2985,14 @@ RADROOTS_CLI_LOGGING_STDOUT=true resolved.transport.reticulum_preview_behavior, super::ReticulumPreviewBehavior::DeferDeliveryPlans ); + assert_eq!(resolved.transport.reticulum_preview_scope, "farmers_market"); + assert_eq!( + resolved + .transport + .reticulum_preview_agent_endpoint + .as_deref(), + Some("reticulum-agent:local") + ); } #[test] diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -175,7 +175,7 @@ fn trade_satisfaction_policy(mode: PublishMode) -> Result<SatisfactionPolicy, Ru } fn trade_target_policy() -> TargetPolicy { - TargetPolicy::use_transport_profile() + TargetPolicy::default_profile() } fn trade_privacy_confirmation(confirm_public_note: bool) -> PrivacyPreflightConfirmation { diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -16,12 +16,12 @@ use radroots_nostr_connect::prelude::{ RadrootsNostrConnectClientTarget, RadrootsNostrConnectError, RadrootsNostrConnectUri, }; use radroots_sdk::{ - HybridProfile, NostrProfile, NostrRelayUrlPolicy, ProxyProfile, RadrootsClient, + HybridProfile, MeshScopeId, NostrProfile, NostrRelayUrlPolicy, ProxyProfile, RadrootsClient, RadrootsClientBuilder, RadrootsSdkError, RadrootsSdkLocalKeySigner, RadrootsSdkMycNip46RequestPolicy, RadrootsSdkMycNip46Signer, RadrootsSdkNip46Transport, RadrootsSdkNip46TransportFuture, RadrootsSdkSignerProvider, RadrootsSdkStorageConfig, - ReticulumPreviewBehavior as SdkReticulumPreviewBehavior, ReticulumPreviewProfile, TargetPolicy, - TransportProfile, + ReticulumPreviewAgentEndpoint, ReticulumPreviewBehavior as SdkReticulumPreviewBehavior, + ReticulumPreviewProfile, TargetPolicy, TransportProfile, }; use radroots_transport_nostr::{ RadrootsNostrClientFetchAdapter, RadrootsRelayFetchRequest, RadrootsRelayFetchedEventsReceipt, @@ -638,7 +638,7 @@ pub fn sdk_nostr_relay_url_policy(config: &RuntimeConfig) -> NostrRelayUrlPolicy } pub fn sdk_target_policy(_config: &RuntimeConfig) -> TargetPolicy { - TargetPolicy::use_transport_profile() + TargetPolicy::default_profile() } fn sdk_transport_profile(config: &RuntimeConfig) -> Result<TransportProfile, RuntimeError> { @@ -653,7 +653,7 @@ fn sdk_transport_profile(config: &RuntimeConfig) -> Result<TransportProfile, Run Ok(TransportProfile::nostr(profile)) } TransportProfileKind::ReticulumPreview => Ok(TransportProfile::reticulum_preview( - sdk_reticulum_preview_profile(config), + sdk_reticulum_preview_profile(config)?, )), TransportProfileKind::Hybrid => { let nostr = NostrProfile::new( @@ -663,7 +663,7 @@ fn sdk_transport_profile(config: &RuntimeConfig) -> Result<TransportProfile, Run .map_err(|error| RuntimeError::Config(error.to_string()))?; Ok(TransportProfile::hybrid(HybridProfile::new( nostr, - sdk_reticulum_preview_profile(config), + sdk_reticulum_preview_profile(config)?, ))) } TransportProfileKind::Proxy => { @@ -674,7 +674,9 @@ fn sdk_transport_profile(config: &RuntimeConfig) -> Result<TransportProfile, Run } } -fn sdk_reticulum_preview_profile(config: &RuntimeConfig) -> ReticulumPreviewProfile { +fn sdk_reticulum_preview_profile( + config: &RuntimeConfig, +) -> Result<ReticulumPreviewProfile, RuntimeError> { let behavior = match config.transport.reticulum_preview_behavior { ReticulumPreviewBehavior::RejectDeliveryAttempts => { SdkReticulumPreviewBehavior::RejectDeliveryAttempts @@ -683,7 +685,18 @@ fn sdk_reticulum_preview_profile(config: &RuntimeConfig) -> ReticulumPreviewProf SdkReticulumPreviewBehavior::DeferDeliveryPlans } }; - ReticulumPreviewProfile::preview_unavailable().with_behavior(behavior) + let scope = MeshScopeId::parse(config.transport.reticulum_preview_scope.as_str()) + .map_err(|error| RuntimeError::Config(error.to_string()))?; + let mut profile = ReticulumPreviewProfile::preview_unavailable() + .with_behavior(behavior) + .with_scope(scope); + if let Some(agent_endpoint) = config.transport.reticulum_preview_agent_endpoint.as_ref() { + profile = profile.with_agent_endpoint( + ReticulumPreviewAgentEndpoint::parse(agent_endpoint.as_str()) + .map_err(|error| RuntimeError::Config(error.to_string()))?, + ); + } + Ok(profile) } pub(crate) fn validate_proxy_bearer_token(config: &RuntimeConfig) -> Result<(), RuntimeError> { @@ -1221,6 +1234,9 @@ mod tests { ); config.transport.profile = TransportProfileKind::Hybrid; config.transport.reticulum_preview_behavior = ReticulumPreviewBehavior::DeferDeliveryPlans; + config.transport.reticulum_preview_scope = "farmers_market".to_owned(); + config.transport.reticulum_preview_agent_endpoint = + Some("reticulum-agent:local".to_owned()); let sdk_config = CliSdkConfig::from_runtime_config(&config).expect("sdk config"); @@ -1235,6 +1251,18 @@ mod tests { profile.reticulum_preview().behavior().as_str(), "defer_delivery_plans" ); + assert_eq!( + profile.reticulum_preview().scope().as_str(), + "farmers_market" + ); + assert_eq!( + profile + .reticulum_preview() + .agent_endpoint() + .expect("agent endpoint") + .as_str(), + "reticulum-agent:local" + ); } #[test] diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs @@ -23,8 +23,8 @@ use radroots_sdk::{ use radroots_sql_core::{SqlExecutor, SqliteExecutor}; use radroots_transport::{ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, - RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportReadinessState, - RadrootsTransportStatus, RadrootsTransportTarget, + RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportStatus, + RadrootsTransportTarget, }; use radroots_transport_nostr::{ RadrootsRelayFetchFailure, RadrootsRelayFetchedEventsReceipt, RadrootsRelayTransportError, @@ -605,17 +605,13 @@ fn sdk_transport_status_view( status: &radroots_sdk::SyncTransportStatusSummary, ) -> SyncTransportStatusView { SyncTransportStatusView { - transport_kind: status.transport_kind.clone(), + transport: status.transport.clone(), profile_id: status.profile_id.clone(), endpoint_uri: status.endpoint_uri.clone(), - configured: sync_transport_status_configured(&status.readiness), - implementation: status.implementation_state.clone(), - usable_for_delivery: status.publish_usable, - message: sync_transport_status_message( - &status.transport_kind, - status.publish_usable, - status.redacted_message.as_deref(), - ), + configured: status.configured, + implementation: status.implementation.clone(), + usable_for_delivery: status.usable_for_delivery, + message: status.message.clone(), } } @@ -949,8 +945,10 @@ fn sync_transport_statuses(config: &RuntimeConfig) -> Vec<SyncTransportStatusVie TransportProfileKind::LocalOnly => vec![sync_transport_status_view( RadrootsTransportStatus::new( RadrootsTransportKind::Local, - RadrootsTransportImplementationState::Available, - RadrootsTransportReadinessState::Ready, + true, + RadrootsTransportImplementationState::Real, + false, + "Local-only profile writes only to local state", ) .with_profile_id(profile_id), )], @@ -978,20 +976,17 @@ fn sync_transport_statuses(config: &RuntimeConfig) -> Vec<SyncTransportStatusVie vec![sync_transport_status_view( RadrootsTransportStatus::new( RadrootsTransportKind::Proxy, + auth_configured, + RadrootsTransportImplementationState::Real, + auth_configured, if auth_configured { - RadrootsTransportImplementationState::Available - } else { - RadrootsTransportImplementationState::Misconfigured - }, - if auth_configured { - RadrootsTransportReadinessState::Ready + "Proxy transport delegates delivery to the configured endpoint" } else { - RadrootsTransportReadinessState::Misconfigured + "Proxy transport requires a configured token file or token secret id" }, ) .with_profile_id(profile_id) - .with_endpoint_uri(config.transport.proxy.url.as_str()) - .with_publish_usable(auth_configured), + .with_endpoint_uri(config.transport.proxy.url.as_str()), )] } } @@ -1000,99 +995,46 @@ fn sync_transport_statuses(config: &RuntimeConfig) -> Vec<SyncTransportStatusVie fn nostr_transport_status(profile_id: &str, targets_configured: bool) -> RadrootsTransportStatus { RadrootsTransportStatus::new( RadrootsTransportKind::Nostr, + targets_configured, + RadrootsTransportImplementationState::Real, + targets_configured, if targets_configured { - RadrootsTransportImplementationState::Available - } else { - RadrootsTransportImplementationState::Misconfigured - }, - if targets_configured { - RadrootsTransportReadinessState::Ready + "Nostr relay transport is configured for delivery" } else { - RadrootsTransportReadinessState::Misconfigured + "Nostr transport requires configured Nostr relay targets" }, ) .with_profile_id(profile_id) - .with_publish_usable(targets_configured) - .with_fetch_usable(targets_configured) } fn reticulum_preview_transport_status(profile_id: &str) -> RadrootsTransportStatus { RadrootsTransportStatus::new( RadrootsTransportKind::Reticulum, + true, RadrootsTransportImplementationState::PreviewUnavailable, - RadrootsTransportReadinessState::PreviewUnavailable, + false, + RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, ) .with_profile_id(profile_id) .with_endpoint_uri(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI) - .with_redacted_message(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE) } fn sync_transport_status_view(status: RadrootsTransportStatus) -> SyncTransportStatusView { - let configured = transport_status_configured(&status); - let usable_for_delivery = status.publish_usable; - let message = transport_status_message(&status); SyncTransportStatusView { - transport_kind: status.kind.canonical_label(), + transport: status.kind.canonical_label(), profile_id: status.profile_id, endpoint_uri: status.endpoint_uri, - configured, - implementation: transport_implementation_state_label(status.implementation_state) - .to_owned(), - usable_for_delivery, - message, + configured: status.configured, + implementation: transport_implementation_label(status.implementation).to_owned(), + usable_for_delivery: status.usable_for_delivery, + message: status.message, } } -fn transport_status_configured(status: &RadrootsTransportStatus) -> bool { - matches!( - status.readiness, - RadrootsTransportReadinessState::Ready - | RadrootsTransportReadinessState::PreviewUnavailable - ) -} - -fn sync_transport_status_configured(readiness: &str) -> bool { - matches!(readiness, "ready" | "preview_unavailable") -} - -fn transport_status_message(status: &RadrootsTransportStatus) -> String { - if let Some(message) = &status.redacted_message { - return message.clone(); - } - sync_transport_status_message(&status.kind.canonical_label(), status.publish_usable, None) -} - -fn sync_transport_status_message( - transport_kind: &str, - usable_for_delivery: bool, - redacted_message: Option<&str>, -) -> String { - if let Some(message) = redacted_message { - return message.to_owned(); - } - match transport_kind { - "local" => "Local-only profile writes only to local state".to_owned(), - "nostr" if usable_for_delivery => { - "Nostr relay transport is configured for delivery".to_owned() - } - "nostr" => "Nostr transport requires configured Nostr relay targets".to_owned(), - "reticulum" => RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned(), - "mesh" => "Mesh transport status is not available".to_owned(), - "proxy" if usable_for_delivery => { - "Proxy transport delegates delivery to the configured endpoint".to_owned() - } - "proxy" => "Proxy transport requires a configured token file or token secret id".to_owned(), - _ => "Transport status is not available".to_owned(), - } -} - -fn transport_implementation_state_label( - state: RadrootsTransportImplementationState, -) -> &'static str { +fn transport_implementation_label(state: RadrootsTransportImplementationState) -> &'static str { match state { - RadrootsTransportImplementationState::Available => "available", - RadrootsTransportImplementationState::Disabled => "disabled", - RadrootsTransportImplementationState::Misconfigured => "misconfigured", + RadrootsTransportImplementationState::Real => "real", + RadrootsTransportImplementationState::Mock => "mock", RadrootsTransportImplementationState::PreviewUnavailable => "preview_unavailable", } } @@ -1927,13 +1869,13 @@ mod tests { assert_eq!(view.configured_transport_target_count, 0); assert!(view.configured_transport_targets.is_empty()); assert_eq!(view.transport_statuses.len(), 1); - assert_eq!(view.transport_statuses[0].transport_kind, "nostr"); + assert_eq!(view.transport_statuses[0].transport, "nostr"); assert_eq!( view.transport_statuses[0].profile_id.as_deref(), Some("nostr") ); assert!(!view.transport_statuses[0].configured); - assert_eq!(view.transport_statuses[0].implementation, "misconfigured"); + assert_eq!(view.transport_statuses[0].implementation, "real"); assert!(!view.transport_statuses[0].usable_for_delivery); assert!(view.target_transport_endpoints.is_empty()); assert_eq!( @@ -1965,9 +1907,9 @@ mod tests { "reticulum" ); assert_eq!(view.transport_statuses.len(), 2); - assert_eq!(view.transport_statuses[0].transport_kind, "nostr"); + assert_eq!(view.transport_statuses[0].transport, "nostr"); assert!(view.transport_statuses[0].usable_for_delivery); - assert_eq!(view.transport_statuses[1].transport_kind, "reticulum"); + assert_eq!(view.transport_statuses[1].transport, "reticulum"); assert!(view.transport_statuses[1].configured); assert_eq!( view.transport_statuses[1].implementation, @@ -2003,15 +1945,15 @@ mod tests { "reticulum" ); assert_eq!(view.transport_statuses.len(), 2); - assert_eq!(view.transport_statuses[0].transport_kind, "nostr"); + assert_eq!(view.transport_statuses[0].transport, "nostr"); assert_eq!( view.transport_statuses[0].profile_id.as_deref(), Some("hybrid") ); assert!(!view.transport_statuses[0].configured); - assert_eq!(view.transport_statuses[0].implementation, "misconfigured"); + assert_eq!(view.transport_statuses[0].implementation, "real"); assert!(!view.transport_statuses[0].usable_for_delivery); - assert_eq!(view.transport_statuses[1].transport_kind, "reticulum"); + assert_eq!(view.transport_statuses[1].transport, "reticulum"); assert!(view.transport_statuses[1].configured); assert_eq!( view.transport_statuses[1].implementation, @@ -2034,7 +1976,7 @@ mod tests { assert_eq!(snapshot.configured_transport_target_count, 2); assert_eq!(snapshot.configured_transport_targets.len(), 2); assert_eq!(snapshot.transport_statuses.len(), 2); - assert_eq!(snapshot.transport_statuses[1].transport_kind, "reticulum"); + assert_eq!(snapshot.transport_statuses[1].transport, "reticulum"); assert_eq!( snapshot.transport_statuses[1].implementation, "preview_unavailable" @@ -2154,24 +2096,22 @@ mod tests { ], transport_statuses: vec![ SyncTransportStatusSummary { - transport_kind: "nostr".to_owned(), + transport: "nostr".to_owned(), profile_id: Some("hybrid".to_owned()), endpoint_uri: None, - implementation_state: "available".to_owned(), - readiness: "ready".to_owned(), - publish_usable: true, - fetch_usable: true, - redacted_message: None, + configured: true, + implementation: "real".to_owned(), + usable_for_delivery: true, + message: "Nostr relay transport is configured for delivery".to_owned(), }, SyncTransportStatusSummary { - transport_kind: "reticulum".to_owned(), + transport: "reticulum".to_owned(), profile_id: Some("hybrid".to_owned()), endpoint_uri: Some(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned()), - implementation_state: "preview_unavailable".to_owned(), - readiness: "preview_unavailable".to_owned(), - publish_usable: false, - fetch_usable: false, - redacted_message: Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned()), + configured: true, + implementation: "preview_unavailable".to_owned(), + usable_for_delivery: false, + message: RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned(), }, ], }; @@ -2539,14 +2479,13 @@ mod tests { }) .collect(), transport_statuses: vec![SyncTransportStatusSummary { - transport_kind: "nostr".to_owned(), + transport: "nostr".to_owned(), profile_id: Some("nostr".to_owned()), endpoint_uri: None, - implementation_state: "available".to_owned(), - readiness: "ready".to_owned(), - publish_usable: true, - fetch_usable: true, - redacted_message: None, + configured: true, + implementation: "real".to_owned(), + usable_for_delivery: true, + message: "Nostr relay transport is configured for delivery".to_owned(), }], }, } @@ -2588,14 +2527,13 @@ mod tests { endpoint_fingerprint: "1".repeat(64), }], transport_statuses: vec![SyncTransportStatusSummary { - transport_kind: "reticulum".to_owned(), + transport: "reticulum".to_owned(), profile_id: Some("reticulum_preview".to_owned()), endpoint_uri: Some(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned()), - implementation_state: "preview_unavailable".to_owned(), - readiness: "preview_unavailable".to_owned(), - publish_usable: false, - fetch_usable: false, - redacted_message: Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned()), + configured: true, + implementation: "preview_unavailable".to_owned(), + usable_for_delivery: false, + message: RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned(), }], }, } diff --git a/src/runtime/transport.rs b/src/runtime/transport.rs @@ -6,9 +6,9 @@ use radroots_sdk::{ SyncStatusRequest, }; use radroots_transport::{ - RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, - RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportReadinessState, - RadrootsTransportStatus, + RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID, + RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportImplementationState, + RadrootsTransportKind, RadrootsTransportStatus, }; use serde_json::Value as JsonValue; use toml::{Value, map::Map}; @@ -53,8 +53,17 @@ pub fn set_profile( "reticulum_preview" => { let behavior = string_input(input, "reticulum_preview_behavior") .unwrap_or("reject_delivery_attempts"); + let scope = string_input(input, "reticulum_preview_scope") + .unwrap_or(RADROOTS_RETICULUM_PREVIEW_SCOPE_ID); let mut preview = Map::new(); preview.insert("behavior".to_owned(), Value::String(behavior.to_owned())); + preview.insert("scope".to_owned(), Value::String(scope.to_owned())); + if let Some(agent_endpoint) = string_input(input, "reticulum_preview_agent_endpoint") { + preview.insert( + "agent_endpoint".to_owned(), + Value::String(agent_endpoint.to_owned()), + ); + } transport.insert("reticulum_preview".to_owned(), Value::Table(preview)); } "hybrid" => { @@ -66,6 +75,8 @@ pub fn set_profile( } let behavior = string_input(input, "reticulum_preview_behavior") .unwrap_or("reject_delivery_attempts"); + let scope = string_input(input, "reticulum_preview_scope") + .unwrap_or(RADROOTS_RETICULUM_PREVIEW_SCOPE_ID); let mut nostr = Map::new(); nostr.insert( "relay_urls".to_owned(), @@ -73,6 +84,13 @@ pub fn set_profile( ); let mut preview = Map::new(); preview.insert("behavior".to_owned(), Value::String(behavior.to_owned())); + preview.insert("scope".to_owned(), Value::String(scope.to_owned())); + if let Some(agent_endpoint) = string_input(input, "reticulum_preview_agent_endpoint") { + preview.insert( + "agent_endpoint".to_owned(), + Value::String(agent_endpoint.to_owned()), + ); + } transport.insert("nostr".to_owned(), Value::Table(nostr)); transport.insert("reticulum_preview".to_owned(), Value::Table(preview)); } @@ -118,6 +136,8 @@ pub fn set_profile( kind, string_array_input(input, "nostr_relays"), string_input(input, "reticulum_preview_behavior").map(str::to_owned), + reticulum_preview_scope_for_profile(kind, input), + string_input(input, "reticulum_preview_agent_endpoint").map(str::to_owned), string_input(input, "proxy_url").map(str::to_owned), string_input(input, "proxy_token_file").map(str::to_owned), string_input(input, "proxy_token_secret_id").map(str::to_owned), @@ -272,6 +292,8 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView { None, None, None, + None, + None, "configured", ), TransportProfileKind::Nostr => profile_view_from_parts( @@ -281,6 +303,8 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView { None, None, None, + None, + None, if config.transport.nostr_relay_urls.is_empty() { "unconfigured" } else { @@ -297,6 +321,8 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView { .as_str() .to_owned(), ), + Some(config.transport.reticulum_preview_scope.clone()), + config.transport.reticulum_preview_agent_endpoint.clone(), None, None, None, @@ -312,6 +338,8 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView { .as_str() .to_owned(), ), + Some(config.transport.reticulum_preview_scope.clone()), + config.transport.reticulum_preview_agent_endpoint.clone(), None, None, None, @@ -331,6 +359,8 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView { "proxy", Vec::new(), None, + None, + None, Some(config.transport.proxy.url.clone()), config .transport @@ -357,6 +387,8 @@ fn profile_view_from_parts( profile_id: &str, nostr_relays: Vec<String>, reticulum_preview_behavior: Option<String>, + reticulum_preview_scope: Option<String>, + reticulum_preview_agent_endpoint: Option<String>, proxy_url: Option<String>, proxy_token_file: Option<String>, proxy_token_secret_id: Option<String>, @@ -405,6 +437,8 @@ fn profile_view_from_parts( message: message.to_owned(), nostr_relays, reticulum_preview_behavior, + reticulum_preview_scope, + reticulum_preview_agent_endpoint, proxy_url, proxy_token_source, proxy_token_file, @@ -451,41 +485,39 @@ fn transport_statuses_from_parts( fn local_transport_status(profile_id: &str) -> RadrootsTransportStatus { RadrootsTransportStatus::new( RadrootsTransportKind::Local, - RadrootsTransportImplementationState::Available, - RadrootsTransportReadinessState::Ready, + true, + RadrootsTransportImplementationState::Real, + false, + "Local-only profile writes only to local state", ) .with_profile_id(profile_id) - .with_redacted_message("Local-only profile writes only to local state") } fn nostr_transport_status(profile_id: &str, targets_configured: bool) -> RadrootsTransportStatus { RadrootsTransportStatus::new( RadrootsTransportKind::Nostr, + targets_configured, + RadrootsTransportImplementationState::Real, + targets_configured, if targets_configured { - RadrootsTransportImplementationState::Available - } else { - RadrootsTransportImplementationState::Misconfigured - }, - if targets_configured { - RadrootsTransportReadinessState::Ready + "Nostr relay transport is configured for delivery" } else { - RadrootsTransportReadinessState::Misconfigured + "Nostr transport requires configured Nostr relay targets" }, ) .with_profile_id(profile_id) - .with_publish_usable(targets_configured) - .with_fetch_usable(targets_configured) } fn reticulum_preview_transport_status(profile_id: &str) -> RadrootsTransportStatus { RadrootsTransportStatus::new( RadrootsTransportKind::Reticulum, + true, RadrootsTransportImplementationState::PreviewUnavailable, - RadrootsTransportReadinessState::PreviewUnavailable, + false, + RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, ) .with_profile_id(profile_id) .with_endpoint_uri(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI) - .with_redacted_message(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE) } fn proxy_transport_status( @@ -495,19 +527,16 @@ fn proxy_transport_status( ) -> RadrootsTransportStatus { let mut status = RadrootsTransportStatus::new( RadrootsTransportKind::Proxy, + token_ready, + RadrootsTransportImplementationState::Real, + token_ready, if token_ready { - RadrootsTransportImplementationState::Available - } else { - RadrootsTransportImplementationState::Misconfigured - }, - if token_ready { - RadrootsTransportReadinessState::Ready + "Proxy transport delegates delivery to the configured endpoint" } else { - RadrootsTransportReadinessState::Misconfigured + "Proxy transport requires a configured token file or token secret id" }, ) - .with_profile_id(profile_id) - .with_publish_usable(token_ready); + .with_profile_id(profile_id); if let Some(proxy_url) = proxy_url { status = status.with_endpoint_uri(proxy_url); } @@ -515,61 +544,33 @@ fn proxy_transport_status( } fn transport_runtime_status_view(status: RadrootsTransportStatus) -> TransportRuntimeStatusView { - let configured = transport_status_configured(&status); - let usable_for_delivery = status.publish_usable; - let message = transport_status_message(&status); TransportRuntimeStatusView { - transport_kind: status.kind.canonical_label(), + transport: status.kind.canonical_label(), profile_id: status.profile_id, endpoint_uri: status.endpoint_uri, - configured, - implementation: transport_implementation_state_label(status.implementation_state) - .to_owned(), - usable_for_delivery, - message, + configured: status.configured, + implementation: transport_implementation_label(status.implementation).to_owned(), + usable_for_delivery: status.usable_for_delivery, + message: status.message, } } -fn transport_status_configured(status: &RadrootsTransportStatus) -> bool { - matches!( - status.readiness, - RadrootsTransportReadinessState::Ready - | RadrootsTransportReadinessState::PreviewUnavailable - ) -} - -fn transport_status_message(status: &RadrootsTransportStatus) -> String { - if let Some(message) = &status.redacted_message { - return message.clone(); - } - match status.kind { - RadrootsTransportKind::Local => "Local-only profile writes only to local state".to_owned(), - RadrootsTransportKind::Nostr if status.publish_usable => { - "Nostr relay transport is configured for delivery".to_owned() - } - RadrootsTransportKind::Nostr => { - "Nostr transport requires configured Nostr relay targets".to_owned() - } - RadrootsTransportKind::Reticulum => RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned(), - RadrootsTransportKind::Mesh => "Mesh transport status is not available".to_owned(), - RadrootsTransportKind::Proxy if status.publish_usable => { - "Proxy transport delegates delivery to the configured endpoint".to_owned() - } - RadrootsTransportKind::Proxy => { - "Proxy transport requires a configured token file or token secret id".to_owned() - } - RadrootsTransportKind::Custom(_) => "Custom transport status is not available".to_owned(), +fn transport_implementation_label(state: RadrootsTransportImplementationState) -> &'static str { + match state { + RadrootsTransportImplementationState::Real => "real", + RadrootsTransportImplementationState::Mock => "mock", + RadrootsTransportImplementationState::PreviewUnavailable => "preview_unavailable", } } -fn transport_implementation_state_label( - state: RadrootsTransportImplementationState, -) -> &'static str { - match state { - RadrootsTransportImplementationState::Available => "available", - RadrootsTransportImplementationState::Disabled => "disabled", - RadrootsTransportImplementationState::Misconfigured => "misconfigured", - RadrootsTransportImplementationState::PreviewUnavailable => "preview_unavailable", +fn reticulum_preview_scope_for_profile(profile_id: &str, input: &OperationData) -> Option<String> { + match profile_id { + "reticulum_preview" | "hybrid" => Some( + string_input(input, "reticulum_preview_scope") + .unwrap_or(RADROOTS_RETICULUM_PREVIEW_SCOPE_ID) + .to_owned(), + ), + _ => None, } } diff --git a/src/view/runtime.rs b/src/view/runtime.rs @@ -3281,6 +3281,10 @@ pub struct TransportProfileView { #[serde(skip_serializing_if = "Option::is_none")] pub reticulum_preview_behavior: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] + pub reticulum_preview_scope: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub reticulum_preview_agent_endpoint: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] pub proxy_url: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub proxy_token_source: Option<String>, @@ -3312,7 +3316,7 @@ pub struct TransportProfileSummaryView { #[derive(Debug, Clone, Serialize)] pub struct TransportRuntimeStatusView { - pub transport_kind: String, + pub transport: String, #[serde(skip_serializing_if = "Option::is_none")] pub profile_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] @@ -3551,7 +3555,7 @@ pub struct SyncTransportTargetView { #[derive(Debug, Clone, Serialize)] pub struct SyncTransportStatusView { - pub transport_kind: String, + pub transport: String, #[serde(skip_serializing_if = "Option::is_none")] pub profile_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -93,6 +93,7 @@ fn configure_nostr_transport(sandbox: &RadrootsCliSandbox, relay_url: &str) { fn assert_no_removed_transport_status_fields(status: &Value) { for field in [ + "transport_kind", "implementation_state", "readiness", "publish_usable", @@ -1426,10 +1427,7 @@ fn transport_profile_reticulum_preview_output_is_transport_specific() { assert_eq!(result["profile_delivery_usable"], false); assert!(result.get("transport_kind").is_none()); assert!(result.get("implementation_state").is_none()); - assert_eq!( - result["transport_statuses"][0]["transport_kind"], - "reticulum" - ); + assert_eq!(result["transport_statuses"][0]["transport"], "reticulum"); assert_eq!( result["transport_statuses"][0]["implementation"], "preview_unavailable" @@ -1445,6 +1443,7 @@ fn transport_profile_reticulum_preview_output_is_transport_specific() { ); assert_no_removed_transport_status_fields(&result["transport_statuses"][0]); assert_eq!(result["reticulum_preview_behavior"], "defer_delivery_plans"); + assert_eq!(result["reticulum_preview_scope"], "local_preview"); assert_eq!(result["message"], RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE); assert!( !result["message"] @@ -1480,9 +1479,9 @@ fn transport_profile_local_only_output_uses_profile_and_local_status_fields() { assert!(result.get("transport_kind").is_none()); assert!(result.get("implementation_state").is_none()); assert_eq!(statuses.len(), 1); - assert_eq!(local["transport_kind"], "local"); + assert_eq!(local["transport"], "local"); assert_eq!(local["profile_id"], "local_only"); - assert_eq!(local["implementation"], "available"); + assert_eq!(local["implementation"], "real"); assert_eq!(local["configured"], true); assert_eq!(local["usable_for_delivery"], false); assert_eq!( @@ -1508,6 +1507,10 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { "wss://relay.example.com", "--reticulum-preview-behavior", "defer-delivery-plans", + "--reticulum-preview-scope", + "farmers_market", + "--reticulum-preview-agent-endpoint", + "reticulum-agent:local", ]); assert_eq!(value["operation_id"], "transport.profile.set"); @@ -1527,12 +1530,12 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { 2 ); assert_eq!( - value["result"]["transport_statuses"][0]["transport_kind"], + value["result"]["transport_statuses"][0]["transport"], "nostr" ); assert_eq!( value["result"]["transport_statuses"][0]["implementation"], - "available" + "real" ); assert_eq!(value["result"]["transport_statuses"][0]["configured"], true); assert_eq!( @@ -1540,7 +1543,7 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { true ); assert_eq!( - value["result"]["transport_statuses"][1]["transport_kind"], + value["result"]["transport_statuses"][1]["transport"], "reticulum" ); assert_eq!( @@ -1558,6 +1561,11 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { value["result"]["reticulum_preview_behavior"], "defer_delivery_plans" ); + assert_eq!(value["result"]["reticulum_preview_scope"], "farmers_market"); + assert_eq!( + value["result"]["reticulum_preview_agent_endpoint"], + "reticulum-agent:local" + ); let value = sandbox.json_success(&["--format", "json", "transport", "profile", "get"]); assert_eq!(value["result"]["profile_id"], "hybrid"); @@ -1569,6 +1577,11 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { value["result"]["reticulum_preview_behavior"], "defer_delivery_plans" ); + assert_eq!(value["result"]["reticulum_preview_scope"], "farmers_market"); + assert_eq!( + value["result"]["reticulum_preview_agent_endpoint"], + "reticulum-agent:local" + ); let config = fs::read_to_string(sandbox.root().join("config/apps/cli/config.toml")) .expect("read app config"); @@ -1576,6 +1589,8 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { assert!(config.contains("[transport.nostr]")); assert!(config.contains("[transport.reticulum_preview]")); assert!(config.contains("behavior = \"defer_delivery_plans\"")); + assert!(config.contains("scope = \"farmers_market\"")); + assert!(config.contains("agent_endpoint = \"reticulum-agent:local\"")); } #[test] @@ -1596,11 +1611,11 @@ fn transport_status_hybrid_output_reports_nostr_and_reticulum_rows() { assert_eq!(result["active_profile"]["profile_kind"], "hybrid"); assert_eq!(result["active_profile"]["profile_delivery_usable"], true); assert_eq!(transports.len(), 2); - assert_eq!(nostr["transport_kind"], "nostr"); - assert_eq!(nostr["implementation"], "available"); + assert_eq!(nostr["transport"], "nostr"); + assert_eq!(nostr["implementation"], "real"); assert_eq!(nostr["configured"], true); assert_eq!(nostr["usable_for_delivery"], true); - assert_eq!(reticulum["transport_kind"], "reticulum"); + assert_eq!(reticulum["transport"], "reticulum"); assert_eq!(reticulum["implementation"], "preview_unavailable"); assert_eq!(reticulum["configured"], true); assert_eq!(reticulum["usable_for_delivery"], false); @@ -1624,9 +1639,9 @@ fn transport_status_nostr_output_uses_canonical_transport_row() { assert_eq!(result["active_profile"]["profile_kind"], "nostr"); assert_eq!(result["active_profile"]["profile_delivery_usable"], true); assert_eq!(transports.len(), 1); - assert_eq!(nostr["transport_kind"], "nostr"); + assert_eq!(nostr["transport"], "nostr"); assert_eq!(nostr["profile_id"], "nostr"); - assert_eq!(nostr["implementation"], "available"); + assert_eq!(nostr["implementation"], "real"); assert_eq!(nostr["configured"], true); assert_eq!(nostr["usable_for_delivery"], true); assert_no_removed_transport_status_fields(nostr); @@ -1653,7 +1668,7 @@ fn transport_status_reticulum_preview_output_reports_unusable_preview_state() { "reticulum_preview" ); assert_eq!(result["active_profile"]["profile_delivery_usable"], false); - assert_eq!(active["transport_kind"], "reticulum"); + assert_eq!(active["transport"], "reticulum"); assert_eq!(active["implementation"], "preview_unavailable"); assert_eq!(active["configured"], true); assert_eq!(active["usable_for_delivery"], false); @@ -1876,6 +1891,7 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces() "pub struct TransportOutboxStatusView", ); for required in [ + "pub transport: String,", "pub configured: bool,", "pub implementation: String,", "pub usable_for_delivery: bool,", @@ -1887,6 +1903,7 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces() ); } for forbidden in [ + "pub transport_kind:", "pub implementation_state:", "pub readiness:", "pub publish_usable:", @@ -1951,6 +1968,7 @@ fn sync_transport_status_source_boundary_rejects_retired_relay_shaped_generic_ou "pub configured_transport_target_count: usize,", "pub configured_transport_targets: Vec<SyncTransportTargetView>,", "pub transport_statuses: Vec<SyncTransportStatusView>,", + "pub transport: String,", "pub configured: bool,", "pub implementation: String,", "pub usable_for_delivery: bool,", @@ -1972,6 +1990,7 @@ fn sync_transport_status_source_boundary_rejects_retired_relay_shaped_generic_ou "pub struct TransportTargetFailureView", ); for forbidden in [ + "pub transport_kind:", "pub implementation_state:", "pub readiness:", "pub publish_usable:", @@ -2330,12 +2349,12 @@ fn transport_profile_set_proxy_persists_token_file_without_printing_token_materi assert_eq!(value["result"]["state"], "configured"); assert_eq!(value["result"]["profile_delivery_usable"], true); assert_eq!( - value["result"]["transport_statuses"][0]["transport_kind"], + value["result"]["transport_statuses"][0]["transport"], "proxy" ); assert_eq!( value["result"]["transport_statuses"][0]["implementation"], - "available" + "real" ); assert_eq!(value["result"]["transport_statuses"][0]["configured"], true); assert_eq!( @@ -2393,12 +2412,12 @@ fn transport_profile_set_proxy_persists_token_secret_id_without_printing_token_m assert_eq!(value["result"]["state"], "configured"); assert_eq!(value["result"]["profile_delivery_usable"], true); assert_eq!( - value["result"]["transport_statuses"][0]["transport_kind"], + value["result"]["transport_statuses"][0]["transport"], "proxy" ); assert_eq!( value["result"]["transport_statuses"][0]["implementation"], - "available" + "real" ); assert_eq!(value["result"]["transport_statuses"][0]["configured"], true); assert_eq!(