cli

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

commit bd6661559deb7f88a647ecd308550fa68d0b4867
parent 4a7b84d31254d9b2b8c2de1659c2115b768e3587
Author: triesap <tyson@radroots.org>
Date:   Wed,  8 Jul 2026 21:03:15 +0000

transport: split profile and status output

- separate profile metadata from canonical transport status rows
- remove profile-level transport kind and implementation state fields
- render transport rows with shared readiness and usability labels
- cover local, Nostr, hybrid, proxy, and Reticulum profile output

Diffstat:
Msrc/out/terminal/renderers/runtime.rs | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/runtime/transport.rs | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/view/runtime.rs | 33+++++++++++++++++++++++++++++----
Mtests/target_cli.rs | 201++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
4 files changed, 440 insertions(+), 76 deletions(-)

diff --git a/src/out/terminal/renderers/runtime.rs b/src/out/terminal/renderers/runtime.rs @@ -98,15 +98,14 @@ fn transport_profile_document(envelope: &OutputEnvelope, result: &Value) -> Term common::push_path_field(&mut document, "State", result, &["state"]); common::push_path_field(&mut document, "Source", result, &["source"]); common::push_path_field(&mut document, "Profile", result, &["profile_id"]); - common::push_path_field(&mut document, "Kind", result, &["transport_kind"]); + common::push_path_field(&mut document, "Profile kind", result, &["profile_kind"]); common::push_path_field(&mut document, "Configured", result, &["configured_state"]); - common::push_path_field( + common::push_bool_field( &mut document, - "Implementation", + "Delivery usable", result, - &["implementation_state"], + &["profile_delivery_usable"], ); - common::push_bool_field(&mut document, "Usable", result, &["usable_for_delivery"]); common::push_path_field(&mut document, "Message", result, &["message"]); common::push_path_field( &mut document, @@ -138,6 +137,7 @@ fn transport_profile_document(envelope: &OutputEnvelope, result: &Value) -> Term "No Nostr relays configured", )); } + push_transport_status_table(&mut document, result, &["transport_statuses"]); document } @@ -146,34 +146,74 @@ fn transport_status_document(envelope: &OutputEnvelope, result: &Value) -> Termi let mut document = common::document_with_title(envelope, title); common::push_path_field(&mut document, "State", result, &["state"]); common::push_path_field(&mut document, "Source", result, &["source"]); - let rows = common::array(result, &["transports"]) + common::push_path_field( + &mut document, + "Profile", + result, + &["active_profile", "profile_id"], + ); + common::push_path_field( + &mut document, + "Profile kind", + result, + &["active_profile", "profile_kind"], + ); + common::push_path_field( + &mut document, + "Configured", + result, + &["active_profile", "configured_state"], + ); + common::push_bool_field( + &mut document, + "Delivery usable", + result, + &["active_profile", "profile_delivery_usable"], + ); + common::push_path_field( + &mut document, + "Message", + result, + &["active_profile", "message"], + ); + push_transport_status_table(&mut document, result, &["transports"]); + document +} + +fn push_transport_status_table(document: &mut TerminalDocument, result: &Value, path: &[&str]) { + let rows = common::array(result, path) .into_iter() .flatten() .map(|transport| { TerminalTableRow::new(vec![ - common::string(transport, &["profile_id"]).unwrap_or_default(), common::string(transport, &["transport_kind"]).unwrap_or_default(), - common::string(transport, &["configured_state"]).unwrap_or_default(), + common::string(transport, &["profile_id"]).unwrap_or_default(), common::string(transport, &["implementation_state"]).unwrap_or_default(), - common::bool_path(transport, &["usable_for_delivery"]) + common::string(transport, &["readiness"]).unwrap_or_default(), + common::bool_path(transport, &["publish_usable"]) + .map(|value| if value { "yes" } else { "no" }.to_owned()) + .unwrap_or_default(), + common::bool_path(transport, &["fetch_usable"]) .map(|value| if value { "yes" } else { "no" }.to_owned()) .unwrap_or_default(), + common::string(transport, &["endpoint_uri"]).unwrap_or_default(), ]) }) .collect::<Vec<_>>(); document.sections.push(common::table_section( "Transports", vec![ - TerminalTableColumn::new("Profile", 7, 18), TerminalTableColumn::new("Kind", 4, 12), - TerminalTableColumn::new("Configured", 10, 18), + TerminalTableColumn::new("Profile", 7, 18), TerminalTableColumn::new("Implementation", 14, 24), - TerminalTableColumn::new("Usable", 6, 6), + TerminalTableColumn::new("Readiness", 9, 20), + TerminalTableColumn::new("Publish", 7, 7), + TerminalTableColumn::new("Fetch", 5, 5), + TerminalTableColumn::new("Endpoint", 8, 32), ], rows, "No transports reported", )); - document } fn transport_outbox_status_document(envelope: &OutputEnvelope, result: &Value) -> TerminalDocument { diff --git a/src/runtime/transport.rs b/src/runtime/transport.rs @@ -2,7 +2,11 @@ use std::fs; use std::path::PathBuf; use radroots_sdk::{PushOutboxRequest, SyncStatusRequest}; -use radroots_transport::RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE; +use radroots_transport::{ + RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, + RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportReadinessState, + RadrootsTransportStatus, +}; use serde_json::Value as JsonValue; use toml::{Value, map::Map}; @@ -11,7 +15,8 @@ use crate::runtime::RuntimeError; use crate::runtime::config::{RuntimeConfig, TransportProfileKind}; use crate::runtime::sdk::{CliSdkAdapterError, CliSdkSession, sdk_nostr_relay_url_policy}; use crate::view::runtime::{ - TransportOutboxPushView, TransportOutboxStatusView, TransportProfileView, TransportStatusView, + TransportOutboxPushView, TransportOutboxStatusView, TransportProfileSummaryView, + TransportProfileView, TransportRuntimeStatusView, TransportStatusView, }; const TRANSPORT_SOURCE: &str = "transport profile config"; @@ -118,26 +123,13 @@ pub fn set_profile( } pub fn status(config: &RuntimeConfig) -> TransportStatusView { - let mut transports = vec![active_profile_view(config)]; - if !matches!( - config.transport.profile, - TransportProfileKind::ReticulumPreview | TransportProfileKind::Hybrid - ) { - transports.push(profile_view_from_parts( - "reticulum_preview", - Vec::new(), - Some("reject_delivery_attempts".to_owned()), - None, - None, - None, - "preview_unavailable", - )); - } + let profile = active_profile_view(config); TransportStatusView { state: "ready".to_owned(), source: TRANSPORT_SOURCE.to_owned(), - transports, + active_profile: profile.summary(), + transports: profile.transport_statuses, } } @@ -314,31 +306,24 @@ fn profile_view_from_parts( proxy_token_secret_id: Option<String>, configured_state: &str, ) -> TransportProfileView { - let transport_kind = match profile_id { - "nostr" => "nostr", - "reticulum_preview" => "reticulum", - "hybrid" => "hybrid", - "proxy" => "proxy", - _ => "local", - }; - let implementation_state = match profile_id { - "nostr" => "available", - "reticulum_preview" => "preview_unavailable", - "hybrid" => "available_with_preview", - "proxy" => "delegated", - _ => "local_only", - }; - let usable_for_delivery = - matches!(profile_id, "nostr" | "hybrid" | "proxy") && configured_state == "configured"; + let transport_statuses = transport_statuses_from_parts( + profile_id, + nostr_relays.as_slice(), + proxy_url.as_deref(), + configured_state, + ); + let profile_delivery_usable = transport_statuses + .iter() + .any(|status| status.publish_usable); let message = match profile_id { - "nostr" if usable_for_delivery => "Nostr relay transport is configured for delivery", + "nostr" if profile_delivery_usable => "Nostr relay transport is configured for delivery", "nostr" => "Nostr transport requires configured Nostr relay targets", "reticulum_preview" => RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, - "hybrid" if usable_for_delivery => { + "hybrid" if profile_delivery_usable => { "Hybrid transport publishes through configured Nostr relays and reports Reticulum preview status" } "hybrid" => "Hybrid transport requires configured Nostr relay targets", - "proxy" if usable_for_delivery => { + "proxy" if profile_delivery_usable => { "Proxy transport delegates delivery to the configured endpoint" } "proxy" => "Proxy transport requires a configured token file or token secret id", @@ -358,10 +343,9 @@ fn profile_view_from_parts( state: configured_state.to_owned(), source: TRANSPORT_SOURCE.to_owned(), profile_id: profile_id.to_owned(), - transport_kind: transport_kind.to_owned(), + profile_kind: profile_id.to_owned(), configured_state: configured_state.to_owned(), - implementation_state: implementation_state.to_owned(), - usable_for_delivery, + profile_delivery_usable, message: message.to_owned(), nostr_relays, reticulum_preview_behavior, @@ -369,12 +353,147 @@ fn profile_view_from_parts( proxy_token_source, proxy_token_file, proxy_token_secret_id, - actions: profile_actions(profile_id, usable_for_delivery), + transport_statuses, + actions: profile_actions(profile_id, profile_delivery_usable), + } +} + +fn transport_statuses_from_parts( + profile_id: &str, + nostr_relays: &[String], + proxy_url: Option<&str>, + configured_state: &str, +) -> Vec<TransportRuntimeStatusView> { + match profile_id { + "nostr" => vec![transport_runtime_status_view(nostr_transport_status( + profile_id, + !nostr_relays.is_empty(), + ))], + "reticulum_preview" => { + vec![transport_runtime_status_view( + reticulum_preview_transport_status(profile_id), + )] + } + "hybrid" => vec![ + transport_runtime_status_view(nostr_transport_status( + profile_id, + !nostr_relays.is_empty(), + )), + transport_runtime_status_view(reticulum_preview_transport_status(profile_id)), + ], + "proxy" => vec![transport_runtime_status_view(proxy_transport_status( + profile_id, + proxy_url, + configured_state == "configured", + ))], + _ => vec![transport_runtime_status_view(local_transport_status( + profile_id, + ))], } } -fn profile_actions(profile_id: &str, usable_for_delivery: bool) -> Vec<String> { - if usable_for_delivery { +fn local_transport_status(profile_id: &str) -> RadrootsTransportStatus { + RadrootsTransportStatus::new( + RadrootsTransportKind::Local, + RadrootsTransportImplementationState::Available, + RadrootsTransportReadinessState::Ready, + ) + .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, + if targets_configured { + RadrootsTransportImplementationState::Available + } else { + RadrootsTransportImplementationState::Misconfigured + }, + if targets_configured { + RadrootsTransportReadinessState::Ready + } else { + RadrootsTransportReadinessState::Misconfigured + }, + ) + .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, + RadrootsTransportImplementationState::PreviewUnavailable, + RadrootsTransportReadinessState::PreviewUnavailable, + ) + .with_profile_id(profile_id) + .with_endpoint_uri(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI) + .with_redacted_message(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE) +} + +fn proxy_transport_status( + profile_id: &str, + proxy_url: Option<&str>, + token_ready: bool, +) -> RadrootsTransportStatus { + let mut status = RadrootsTransportStatus::new( + RadrootsTransportKind::Proxy, + if token_ready { + RadrootsTransportImplementationState::Available + } else { + RadrootsTransportImplementationState::Misconfigured + }, + if token_ready { + RadrootsTransportReadinessState::Ready + } else { + RadrootsTransportReadinessState::Misconfigured + }, + ) + .with_profile_id(profile_id) + .with_publish_usable(token_ready); + if let Some(proxy_url) = proxy_url { + status = status.with_endpoint_uri(proxy_url); + } + status +} + +fn transport_runtime_status_view(status: RadrootsTransportStatus) -> TransportRuntimeStatusView { + TransportRuntimeStatusView { + transport_kind: status.kind.canonical_label(), + profile_id: status.profile_id, + endpoint_uri: status.endpoint_uri, + implementation_state: transport_implementation_state_label(status.implementation_state) + .to_owned(), + readiness: transport_readiness_state_label(status.readiness).to_owned(), + publish_usable: status.publish_usable, + fetch_usable: status.fetch_usable, + redacted_message: status.redacted_message, + } +} + +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 transport_readiness_state_label(state: RadrootsTransportReadinessState) -> &'static str { + match state { + RadrootsTransportReadinessState::Ready => "ready", + RadrootsTransportReadinessState::Disabled => "disabled", + RadrootsTransportReadinessState::Misconfigured => "misconfigured", + RadrootsTransportReadinessState::PreviewUnavailable => "preview_unavailable", + } +} + +fn profile_actions(profile_id: &str, profile_delivery_usable: bool) -> Vec<String> { + if profile_delivery_usable { return Vec::new(); } match profile_id { @@ -445,6 +564,7 @@ fn validate_proxy_token_material( trait TransportProfileViewMessage { fn with_message(self, message: String) -> Self; + fn summary(&self) -> TransportProfileSummaryView; } impl TransportProfileViewMessage for TransportProfileView { @@ -452,6 +572,16 @@ impl TransportProfileViewMessage for TransportProfileView { self.message = message; self } + + fn summary(&self) -> TransportProfileSummaryView { + TransportProfileSummaryView { + profile_id: self.profile_id.clone(), + profile_kind: self.profile_kind.clone(), + configured_state: self.configured_state.clone(), + profile_delivery_usable: self.profile_delivery_usable, + message: self.message.clone(), + } + } } pub(crate) fn update_app_config_table( diff --git a/src/view/runtime.rs b/src/view/runtime.rs @@ -3272,10 +3272,9 @@ pub struct TransportProfileView { pub state: String, pub source: String, pub profile_id: String, - pub transport_kind: String, + pub profile_kind: String, pub configured_state: String, - pub implementation_state: String, - pub usable_for_delivery: bool, + pub profile_delivery_usable: bool, pub message: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub nostr_relays: Vec<String>, @@ -3289,6 +3288,7 @@ pub struct TransportProfileView { pub proxy_token_file: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub proxy_token_secret_id: Option<String>, + pub transport_statuses: Vec<TransportRuntimeStatusView>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub actions: Vec<String>, } @@ -3297,7 +3297,32 @@ pub struct TransportProfileView { pub struct TransportStatusView { pub state: String, pub source: String, - pub transports: Vec<TransportProfileView>, + pub active_profile: TransportProfileSummaryView, + pub transports: Vec<TransportRuntimeStatusView>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct TransportProfileSummaryView { + pub profile_id: String, + pub profile_kind: String, + pub configured_state: String, + pub profile_delivery_usable: bool, + pub message: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct TransportRuntimeStatusView { + pub transport_kind: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub profile_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub endpoint_uri: Option<String>, + pub implementation_state: String, + pub readiness: String, + pub publish_usable: bool, + pub fetch_usable: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub redacted_message: Option<String>, } #[derive(Debug, Clone, Serialize)] diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -1297,9 +1297,24 @@ fn transport_profile_reticulum_preview_output_is_transport_specific() { assert_eq!(value["operation_id"], "transport.profile.get"); assert_eq!(result["profile_id"], "reticulum_preview"); - assert_eq!(result["transport_kind"], "reticulum"); - assert_eq!(result["implementation_state"], "preview_unavailable"); - assert_eq!(result["usable_for_delivery"], false); + assert_eq!(result["profile_kind"], "reticulum_preview"); + 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]["implementation_state"], + "preview_unavailable" + ); + assert_eq!( + result["transport_statuses"][0]["readiness"], + "preview_unavailable" + ); + assert_eq!(result["transport_statuses"][0]["publish_usable"], false); + assert_eq!(result["transport_statuses"][0]["fetch_usable"], false); assert_eq!(result["reticulum_preview_behavior"], "defer_delivery_plans"); assert_eq!(result["message"], RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE); assert!( @@ -1319,6 +1334,32 @@ fn transport_profile_reticulum_preview_output_is_transport_specific() { } #[test] +fn transport_profile_local_only_output_uses_profile_and_local_status_fields() { + let sandbox = RadrootsCliSandbox::new(); + + let value = sandbox.json_success(&["--format", "json", "transport", "profile", "get"]); + let result = &value["result"]; + let statuses = result["transport_statuses"] + .as_array() + .expect("transport statuses"); + let local = &statuses[0]; + + assert_eq!(value["operation_id"], "transport.profile.get"); + assert_eq!(result["profile_id"], "local_only"); + assert_eq!(result["profile_kind"], "local_only"); + assert_eq!(result["profile_delivery_usable"], false); + 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["profile_id"], "local_only"); + assert_eq!(local["implementation_state"], "available"); + assert_eq!(local["readiness"], "ready"); + assert_eq!(local["publish_usable"], false); + assert_eq!(local["fetch_usable"], false); +} + +#[test] fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { let sandbox = RadrootsCliSandbox::new(); @@ -1338,13 +1379,37 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { assert_eq!(value["operation_id"], "transport.profile.set"); assert_eq!(value["result"]["profile_id"], "hybrid"); - assert_eq!(value["result"]["transport_kind"], "hybrid"); + assert_eq!(value["result"]["profile_kind"], "hybrid"); + assert!(value["result"].get("transport_kind").is_none()); assert_eq!(value["result"]["state"], "configured"); assert_eq!( value["result"]["nostr_relays"][0], "wss://relay.example.com" ); assert_eq!( + value["result"]["transport_statuses"] + .as_array() + .unwrap() + .len(), + 2 + ); + assert_eq!( + value["result"]["transport_statuses"][0]["transport_kind"], + "nostr" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["implementation_state"], + "available" + ); + assert_eq!( + value["result"]["transport_statuses"][1]["transport_kind"], + "reticulum" + ); + assert_eq!( + value["result"]["transport_statuses"][1]["readiness"], + "preview_unavailable" + ); + assert_eq!( value["result"]["reticulum_preview_behavior"], "defer_delivery_plans" ); @@ -1369,6 +1434,59 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() { } #[test] +fn transport_status_hybrid_output_reports_nostr_and_reticulum_rows() { + let sandbox = RadrootsCliSandbox::new(); + sandbox.write_app_config( + "[transport]\nprofile = \"hybrid\"\n\n[transport.nostr]\nrelay_urls = [\"wss://relay.example.com\"]\n\n[transport.reticulum_preview]\nbehavior = \"defer_delivery_plans\"\n", + ); + + let value = sandbox.json_success(&["--format", "json", "transport", "status"]); + let result = &value["result"]; + let transports = result["transports"].as_array().expect("transports"); + let nostr = &transports[0]; + let reticulum = &transports[1]; + + assert_eq!(value["operation_id"], "transport.status"); + assert_eq!(result["active_profile"]["profile_id"], "hybrid"); + 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_state"], "available"); + assert_eq!(nostr["readiness"], "ready"); + assert_eq!(nostr["publish_usable"], true); + assert_eq!(nostr["fetch_usable"], true); + assert_eq!(reticulum["transport_kind"], "reticulum"); + assert_eq!(reticulum["implementation_state"], "preview_unavailable"); + assert_eq!(reticulum["readiness"], "preview_unavailable"); + assert_eq!(reticulum["publish_usable"], false); + assert_eq!(reticulum["fetch_usable"], false); +} + +#[test] +fn transport_status_nostr_output_uses_canonical_transport_row() { + let sandbox = RadrootsCliSandbox::new(); + configure_nostr_transport(&sandbox, "wss://relay.example.com"); + + let value = sandbox.json_success(&["--format", "json", "transport", "status"]); + let result = &value["result"]; + let transports = result["transports"].as_array().expect("transports"); + let nostr = &transports[0]; + + assert_eq!(value["operation_id"], "transport.status"); + assert_eq!(result["active_profile"]["profile_id"], "nostr"); + 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["profile_id"], "nostr"); + assert_eq!(nostr["implementation_state"], "available"); + assert_eq!(nostr["readiness"], "ready"); + assert_eq!(nostr["publish_usable"], true); + assert_eq!(nostr["fetch_usable"], true); +} + +#[test] fn transport_status_reticulum_preview_output_reports_unusable_preview_state() { let sandbox = RadrootsCliSandbox::new(); sandbox.write_app_config( @@ -1383,14 +1501,23 @@ fn transport_status_reticulum_preview_output_reports_unusable_preview_state() { assert_eq!(value["operation_id"], "transport.status"); assert_eq!(result["state"], "ready"); assert_eq!(transports.len(), 1); - assert_eq!(active["profile_id"], "reticulum_preview"); + assert_eq!(result["active_profile"]["profile_id"], "reticulum_preview"); + assert_eq!( + result["active_profile"]["profile_kind"], + "reticulum_preview" + ); + assert_eq!(result["active_profile"]["profile_delivery_usable"], false); assert_eq!(active["transport_kind"], "reticulum"); assert_eq!(active["implementation_state"], "preview_unavailable"); - assert_eq!(active["usable_for_delivery"], false); - assert_eq!(active["reticulum_preview_behavior"], "defer_delivery_plans"); - assert_eq!(active["message"], RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE); + assert_eq!(active["readiness"], "preview_unavailable"); + assert_eq!(active["publish_usable"], false); + assert_eq!(active["fetch_usable"], false); + assert_eq!( + active["redacted_message"], + RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE + ); assert!( - !active["message"] + !active["redacted_message"] .as_str() .expect("message") .contains("Nostr") @@ -1503,8 +1630,9 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces() fs::read_to_string(manifest_dir.join("src/runtime/transport.rs")).expect("read source"); for required in [ "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE", - "if !matches!(", - "TransportProfileKind::ReticulumPreview | TransportProfileKind::Hybrid", + "RadrootsTransportStatus::new", + "RadrootsTransportKind::Reticulum", + "transport_runtime_status_view", "validate_proxy_token_material", "proxy_token_ready(&validation_config)", "crate::runtime::sdk::validate_proxy_bearer_token(config)", @@ -1714,7 +1842,10 @@ fn config_get_exposes_proxy_missing_token_state() { assert_eq!(value["result"]["write_plane"]["state"], "unconfigured"); assert_eq!(value["result"]["transport"]["profile_id"], "proxy"); assert_eq!(value["result"]["transport"]["state"], "unconfigured"); - assert_eq!(value["result"]["transport"]["usable_for_delivery"], false); + assert_eq!( + value["result"]["transport"]["profile_delivery_usable"], + false + ); assert_eq!( value["result"]["transport"]["proxy_url"], "http://127.0.0.1:7070" @@ -1753,7 +1884,10 @@ fn config_get_proxy_with_token_file_reports_ready_transport() { assert_eq!(value["result"]["publish"]["reason"], Value::Null); assert_eq!(value["result"]["transport"]["profile_id"], "proxy"); assert_eq!(value["result"]["transport"]["state"], "configured"); - assert_eq!(value["result"]["transport"]["usable_for_delivery"], true); + assert_eq!( + value["result"]["transport"]["profile_delivery_usable"], + true + ); assert_eq!( value["result"]["transport"]["proxy_token_source"], "token_file" @@ -1791,7 +1925,10 @@ fn config_get_proxy_with_token_secret_id_reports_ready_transport() { assert_eq!(value["result"]["publish"]["executable"], true); assert_eq!(value["result"]["transport"]["profile_id"], "proxy"); assert_eq!(value["result"]["transport"]["state"], "configured"); - assert_eq!(value["result"]["transport"]["usable_for_delivery"], true); + assert_eq!( + value["result"]["transport"]["profile_delivery_usable"], + true + ); assert_eq!( value["result"]["transport"]["proxy_token_source"], "token_secret_id" @@ -1913,7 +2050,23 @@ fn transport_profile_set_proxy_persists_token_file_without_printing_token_materi assert_eq!(value["operation_id"], "transport.profile.set"); assert_eq!(value["result"]["profile_id"], "proxy"); assert_eq!(value["result"]["state"], "configured"); - assert_eq!(value["result"]["usable_for_delivery"], true); + assert_eq!(value["result"]["profile_delivery_usable"], true); + assert_eq!( + value["result"]["transport_statuses"][0]["transport_kind"], + "proxy" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["implementation_state"], + "available" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["readiness"], + "ready" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["publish_usable"], + true + ); assert_eq!(value["result"]["proxy_token_source"], "token_file"); assert_eq!( value["result"]["proxy_token_file"], @@ -1962,7 +2115,23 @@ fn transport_profile_set_proxy_persists_token_secret_id_without_printing_token_m assert_eq!(value["operation_id"], "transport.profile.set"); assert_eq!(value["result"]["profile_id"], "proxy"); assert_eq!(value["result"]["state"], "configured"); - assert_eq!(value["result"]["usable_for_delivery"], true); + assert_eq!(value["result"]["profile_delivery_usable"], true); + assert_eq!( + value["result"]["transport_statuses"][0]["transport_kind"], + "proxy" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["implementation_state"], + "available" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["readiness"], + "ready" + ); + assert_eq!( + value["result"]["transport_statuses"][0]["publish_usable"], + true + ); assert_eq!(value["result"]["proxy_token_source"], "token_secret_id"); assert!(value["result"]["proxy_token_file"].is_null()); assert_eq!(