commit e8ed1c43b60e0785be54b4841e678eac1d30cb08
parent 1293bd27719474b8f0e8a9aabc0ea79c92aea8c8
Author: triesap <tyson@radroots.org>
Date: Wed, 15 Jul 2026 14:57:08 +0000
transport: align CLI with Reticulum transport identity
- replace preview and hybrid transport profile inputs with Reticulum and multi-target profiles
- separate radrootsd execution config from transport profile selection
- align SDK adapter, sync, mesh, and status views with Reticulum availability metadata
- remove stale proxy transport output and recovery hints
Diffstat:
36 files changed, 991 insertions(+), 2624 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1381,18 +1381,6 @@ dependencies = [
]
[[package]]
-name = "fallible-iterator"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
-
-[[package]]
-name = "fallible-streaming-iterator"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
-
-[[package]]
name = "fastrand"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1461,9 +1449,9 @@ dependencies = [
[[package]]
name = "flume"
-version = "0.11.1"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
dependencies = [
"futures-core",
"futures-sink",
@@ -1483,6 +1471,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1757,7 +1751,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
@@ -1765,32 +1759,28 @@ name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
-
-[[package]]
-name = "hashlink"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
- "hashbrown 0.14.5",
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
]
[[package]]
name = "hashlink"
-version = "0.9.1"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
+checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.5",
]
[[package]]
name = "hashlink"
-version = "0.10.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
dependencies = [
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
]
[[package]]
@@ -3705,12 +3695,13 @@ dependencies = [
name = "radroots_geocoder"
version = "0.1.0-alpha.2"
dependencies = [
+ "futures-executor",
"hex",
"radroots_runtime_paths",
"reqwest",
- "rusqlite",
"serde",
"sha2",
+ "sqlx",
"tempfile",
"thiserror 1.0.69",
"url",
@@ -3973,9 +3964,10 @@ name = "radroots_sql_core"
version = "0.1.0-alpha.2"
dependencies = [
"chrono",
- "rusqlite",
+ "futures-executor",
"serde",
"serde_json",
+ "sqlx",
"uuid",
]
@@ -4312,20 +4304,6 @@ dependencies = [
]
[[package]]
-name = "rusqlite"
-version = "0.32.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
-dependencies = [
- "bitflags",
- "fallible-iterator",
- "fallible-streaming-iterator",
- "hashlink 0.9.1",
- "libsqlite3-sys",
- "smallvec",
-]
-
-[[package]]
name = "rust-ini"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5834,9 +5812,9 @@ dependencies = [
[[package]]
name = "sqlx"
-version = "0.8.6"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
+checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -5845,12 +5823,13 @@ dependencies = [
[[package]]
name = "sqlx-core"
-version = "0.8.6"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
+checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
dependencies = [
"base64 0.22.1",
"bytes",
+ "cfg-if",
"crc",
"crossbeam-queue",
"either",
@@ -5859,12 +5838,11 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
- "hashbrown 0.15.5",
- "hashlink 0.10.0",
+ "hashbrown 0.16.1",
+ "hashlink 0.11.1",
"indexmap 2.13.1",
"log",
"memchr",
- "once_cell",
"percent-encoding",
"serde",
"sha2",
@@ -5878,9 +5856,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
-version = "0.8.6"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
+checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf"
dependencies = [
"proc-macro2",
"quote",
@@ -5891,15 +5869,15 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
-version = "0.8.6"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
+checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3"
dependencies = [
+ "cfg-if",
"dotenvy",
"either",
"heck",
"hex",
- "once_cell",
"proc-macro2",
"quote",
"serde",
@@ -5914,12 +5892,13 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
-version = "0.8.6"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
+checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
dependencies = [
"atoi",
"flume",
+ "form_urlencoded",
"futures-channel",
"futures-core",
"futures-executor",
@@ -5929,7 +5908,6 @@ dependencies = [
"log",
"percent-encoding",
"serde",
- "serde_urlencoded",
"sqlx-core",
"thiserror 2.0.18",
"tracing",
diff --git a/Cargo.toml b/Cargo.toml
@@ -49,7 +49,7 @@ radroots_transport_nostr = { path = "../lib/crates/transport_nostr", default-fea
] }
radroots_runtime = { path = "../lib/crates/runtime" }
radroots_runtime_paths = { path = "../lib/crates/runtime_paths" }
-radroots_sdk = { path = "../sdk/crates/sdk", features = ["local-runtime-radrootsd-proxy", "transport-nostr-runtime"] }
+radroots_sdk = { path = "../sdk/crates/sdk", features = ["local-runtime-radrootsd-execution", "transport-nostr-runtime"] }
radroots_secret_vault = { path = "../lib/crates/secret_vault", features = ["std", "os-keyring"] }
radroots_sql_core = { path = "../lib/crates/sql_core", features = ["native"] }
radroots_trade_sp1_host = { path = "../lib/crates/trade_sp1_host" }
diff --git a/src/cli/input.rs b/src/cli/input.rs
@@ -331,28 +331,17 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData {
Value::String(args.kind.as_str().to_owned()),
);
insert_string_array(&mut input, "nostr_relays", args.nostr_relay.as_slice());
- if let Some(behavior) = args.reticulum_preview_behavior {
+ if let Some(behavior) = args.reticulum_behavior {
input.insert(
- "reticulum_preview_behavior".to_owned(),
+ "reticulum_behavior".to_owned(),
Value::String(behavior.as_str().to_owned()),
);
}
+ insert_string(&mut input, "reticulum_scope", &args.reticulum_scope);
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(
- &mut input,
- "proxy_token_secret_id",
- &args.proxy_token_secret_id,
+ "reticulum_agent_endpoint",
+ &args.reticulum_agent_endpoint,
);
}
TransportProfileCommand::Get => {}
diff --git a/src/cli/mesh.rs b/src/cli/mesh.rs
@@ -34,14 +34,14 @@ pub struct MeshScopeSetArgs {
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum MeshScopeArg {
Disabled,
- LocalPreview,
+ Local,
}
impl MeshScopeArg {
pub fn as_str(self) -> &'static str {
match self {
Self::Disabled => "disabled",
- Self::LocalPreview => "local_preview",
+ Self::Local => "local",
}
}
}
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
@@ -92,7 +92,7 @@ pub enum TargetCommand {
Signer(SignerArgs),
#[command(about = "Manage transport profiles and outbox delivery.")]
Transport(TransportArgs),
- #[command(about = "Inspect mesh scope and Reticulum preview policy.")]
+ #[command(about = "Inspect mesh scope and Reticulum admission policy.")]
Mesh(MeshArgs),
#[command(about = "Initialize and inspect the local replica store.")]
Store(StoreArgs),
diff --git a/src/cli/transport.rs b/src/cli/transport.rs
@@ -1,5 +1,3 @@
-use std::path::PathBuf;
-
use clap::{Args, Subcommand, ValueEnum};
#[derive(Debug, Clone, Args)]
@@ -33,27 +31,20 @@ pub struct TransportProfileSetArgs {
pub kind: TransportProfileKindArg,
#[arg(long = "nostr-relay")]
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")]
- pub proxy_token_file: Option<PathBuf>,
- #[arg(long = "proxy-token-secret-id", value_name = "SECRET_ID")]
- pub proxy_token_secret_id: Option<String>,
+ #[arg(long = "reticulum-behavior", value_enum)]
+ pub reticulum_behavior: Option<ReticulumBehaviorArg>,
+ #[arg(long = "reticulum-scope")]
+ pub reticulum_scope: Option<String>,
+ #[arg(long = "reticulum-agent-endpoint")]
+ pub reticulum_agent_endpoint: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum TransportProfileKindArg {
LocalOnly,
Nostr,
- ReticulumPreview,
- Hybrid,
- Proxy,
+ Reticulum,
+ MultiTarget,
}
impl TransportProfileKindArg {
@@ -61,20 +52,19 @@ impl TransportProfileKindArg {
match self {
Self::LocalOnly => "local_only",
Self::Nostr => "nostr",
- Self::ReticulumPreview => "reticulum_preview",
- Self::Hybrid => "hybrid",
- Self::Proxy => "proxy",
+ Self::Reticulum => "reticulum",
+ Self::MultiTarget => "multi_target",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
-pub enum ReticulumPreviewBehaviorArg {
+pub enum ReticulumBehaviorArg {
RejectDeliveryAttempts,
DeferDeliveryPlans,
}
-impl ReticulumPreviewBehaviorArg {
+impl ReticulumBehaviorArg {
pub fn as_str(self) -> &'static str {
match self {
Self::RejectDeliveryAttempts => "reject_delivery_attempts",
diff --git a/src/main.rs b/src/main.rs
@@ -504,7 +504,7 @@ fn validate_transport_profile_contract(
}
if matches!(
config.transport.profile,
- TransportProfileKind::LocalOnly | TransportProfileKind::ReticulumPreview
+ TransportProfileKind::LocalOnly | TransportProfileKind::Reticulum
) {
return Err(OperationAdapterError::NetworkUnavailable {
operation_id: spec.operation_id.to_owned(),
@@ -517,7 +517,7 @@ fn validate_transport_profile_contract(
}
if matches!(
config.transport.profile,
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget
) && config.transport.nostr_relay_urls.is_empty()
{
return Err(OperationAdapterError::NetworkUnavailable {
@@ -528,23 +528,12 @@ fn validate_transport_profile_contract(
),
});
}
- if matches!(config.transport.profile, TransportProfileKind::Proxy)
- && let Err(error) = runtime::transport::proxy_token_ready(config)
- {
- return Err(OperationAdapterError::NetworkUnavailable {
- operation_id: spec.operation_id.to_owned(),
- message: format!(
- "`{}` requires a usable proxy token source: {error}",
- spec.cli_path,
- ),
- });
- }
Ok(())
}
fn transport_profile_delivery_unavailable_reason(config: &RuntimeConfig) -> Option<String> {
match config.transport.profile {
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid => {
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget => {
config.transport.nostr_relay_urls.is_empty().then(|| {
format!(
"active {} transport profile has no configured Nostr relay targets",
@@ -552,15 +541,11 @@ fn transport_profile_delivery_unavailable_reason(config: &RuntimeConfig) -> Opti
)
})
}
- TransportProfileKind::Proxy => match runtime::transport::proxy_token_ready(config) {
- Ok(()) => None,
- Err(error) => Some(error.to_string()),
- },
TransportProfileKind::LocalOnly => {
Some("active local_only transport profile cannot deliver".to_owned())
}
- TransportProfileKind::ReticulumPreview => {
- Some("active reticulum_preview transport profile cannot deliver".to_owned())
+ TransportProfileKind::Reticulum => {
+ Some("active reticulum transport profile cannot deliver".to_owned())
}
}
}
diff --git a/src/ops/error.rs b/src/ops/error.rs
@@ -552,7 +552,6 @@ fn sdk_error_exit_code(class: RadrootsSdkErrorClass) -> CliExitCode {
RadrootsSdkErrorClass::Clock
| RadrootsSdkErrorClass::Configuration
| RadrootsSdkErrorClass::Request => CliExitCode::InvalidInput,
- RadrootsSdkErrorClass::LocalMutation => CliExitCode::Conflict,
RadrootsSdkErrorClass::Storage => CliExitCode::RuntimeUnavailable,
RadrootsSdkErrorClass::Transport => CliExitCode::SyncOrNetworkFailure,
RadrootsSdkErrorClass::Unsupported => CliExitCode::RuntimeUnavailable,
@@ -565,7 +564,6 @@ fn sdk_error_class_name(class: RadrootsSdkErrorClass) -> &'static str {
RadrootsSdkErrorClass::Authorization => "authorization",
RadrootsSdkErrorClass::Clock => "clock",
RadrootsSdkErrorClass::Configuration => "configuration",
- RadrootsSdkErrorClass::LocalMutation => "local_mutation",
RadrootsSdkErrorClass::Request => "request",
RadrootsSdkErrorClass::Storage => "storage",
RadrootsSdkErrorClass::Transport => "transport",
@@ -581,8 +579,7 @@ fn sdk_recovery_next_actions(
recovery_actions
.iter()
.filter_map(|action| match action {
- RadrootsSdkRecoveryAction::RetryOutboxEnqueue
- | RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey
+ RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey
| RadrootsSdkRecoveryAction::FixRequest => Some(operation_retry_action(operation_id)),
RadrootsSdkRecoveryAction::InspectLocalStores => {
Some("radroots store status get".to_owned())
@@ -772,10 +769,7 @@ fn classify_runtime_failure(
},
};
}
- if contains_any(
- &lowered,
- &["provider", "write-plane", "write plane", "rpc", "proxy"],
- ) {
+ if contains_any(&lowered, &["provider", "write-plane", "write plane", "rpc"]) {
return match availability {
RuntimeFailureAvailability::Unconfigured => {
OperationAdapterError::ProviderUnconfigured {
@@ -804,7 +798,7 @@ fn looks_like_auth_failure(value: &str) -> bool {
value,
&[
"authentication",
- "daemon proxy auth",
+ "daemon execution auth",
"authorization",
"authorize",
"unauthorized",
@@ -843,7 +837,7 @@ fn looks_like_provider_failure(value: &str) -> bool {
"provider unconfigured",
"provider runtime",
"provider failed",
- "proxy provider",
+ "execution provider",
],
)
}
@@ -858,7 +852,7 @@ fn looks_like_operation_failure(value: &str) -> bool {
"unsupported operation",
"operation unavailable",
"operation disabled",
- "publish proxy disabled",
+ "publish execution disabled",
"publish.event is disabled",
],
)
diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs
@@ -6,7 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use radroots_event::order::RadrootsOrderEconomics;
use radroots_replica_schema::trade_product::{ITradeProductFieldsFilter, ITradeProductFindMany};
use radroots_replica_store::{ReplicaSql, trade_product};
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
@@ -880,7 +880,7 @@ fn basket_market_issues(
"current local replica data is required before quote creation; run `radroots store init` and `radroots market refresh`",
)]);
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path).map_err(|error| {
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path).map_err(|error| {
OperationAdapterError::Runtime(format!(
"open local replica {}: {error}",
config.local.replica_store_path.display()
@@ -955,7 +955,7 @@ fn basket_market_issues(
fn basket_product_bin_state(
config: &RuntimeConfig,
- executor: &SqliteExecutor,
+ executor: &SqlxSqliteExecutor,
item: &BasketItem,
) -> Result<BasketProductResolution, OperationAdapterError> {
if let Some(listing_addr) = item.listing_addr.as_deref().and_then(non_empty_ref) {
@@ -984,7 +984,7 @@ fn basket_product_bin_state(
return Ok(BasketProductResolution::Unresolved);
};
let lookup_executor =
- SqliteExecutor::open(&config.local.replica_store_path).map_err(|error| {
+ SqlxSqliteExecutor::open(&config.local.replica_store_path).map_err(|error| {
OperationAdapterError::Runtime(format!(
"open local replica {}: {error}",
config.local.replica_store_path.display()
@@ -1320,7 +1320,7 @@ mod tests {
use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts};
use radroots_replica_sync::{RadrootsReplicaIngestOutcome, radroots_replica_ingest_event};
use radroots_secret_vault::RadrootsSecretBackend;
- use radroots_sql_core::{SqlExecutor, SqliteExecutor};
+ use radroots_sql_core::{SqlExecutor, SqlxSqliteExecutor};
use serde_json::{Map, Value, json};
use tempfile::tempdir;
@@ -1821,7 +1821,7 @@ mod tests {
})
.expect("seed listing event");
let executor =
- SqliteExecutor::open(&config.local.replica_store_path).expect("open replica");
+ SqlxSqliteExecutor::open(&config.local.replica_store_path).expect("open replica");
assert_eq!(
radroots_replica_ingest_event(&executor, &event).expect("ingest listing"),
RadrootsReplicaIngestOutcome::Applied
@@ -1837,7 +1837,7 @@ mod tests {
fn duplicate_current_listing_row(config: &RuntimeConfig) {
let executor =
- SqliteExecutor::open(&config.local.replica_store_path).expect("open replica");
+ SqlxSqliteExecutor::open(&config.local.replica_store_path).expect("open replica");
let params = json!(["33333333-3333-3333-3333-333333333333", LISTING_ADDR]).to_string();
executor
.exec(
diff --git a/src/ops/exec/core.rs b/src/ops/exec/core.rs
@@ -804,7 +804,7 @@ fn publish_runtime_view(
};
match config.transport.profile {
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid => {
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget => {
let (state, executable, reason) =
nostr_publish_readiness(config, relay_ready, signed_write_required, account);
PublishRuntimeView {
@@ -824,25 +824,6 @@ fn publish_runtime_view(
},
}
}
- TransportProfileKind::Proxy => {
- let (state, executable, reason) = proxy_publish_readiness(config);
- PublishRuntimeView {
- transport: config.transport.profile.as_str().to_owned(),
- source,
- transport_family: config.transport.profile.transport_family().to_owned(),
- state: state.to_owned(),
- executable,
- reason: reason.clone(),
- signed_write_required,
- relay,
- provider: PublishProviderRuntimeView {
- provider_runtime_id: "proxy".to_owned(),
- state: state.to_owned(),
- source: "publish transport · transport profile".to_owned(),
- reason,
- },
- }
- }
TransportProfileKind::LocalOnly => PublishRuntimeView {
transport: config.transport.profile.as_str().to_owned(),
source,
@@ -861,18 +842,18 @@ fn publish_runtime_view(
reason: Some("local_only transport profile writes only to local state".to_owned()),
},
},
- TransportProfileKind::ReticulumPreview => PublishRuntimeView {
+ TransportProfileKind::Reticulum => PublishRuntimeView {
transport: config.transport.profile.as_str().to_owned(),
source,
transport_family: config.transport.profile.transport_family().to_owned(),
- state: "preview_unavailable".to_owned(),
+ state: "unavailable".to_owned(),
executable: false,
reason: Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned()),
signed_write_required,
relay,
provider: PublishProviderRuntimeView {
- provider_runtime_id: "reticulum_preview".to_owned(),
- state: "preview_unavailable".to_owned(),
+ provider_runtime_id: "reticulum".to_owned(),
+ state: "unavailable".to_owned(),
source: config.transport.source.as_str().to_owned(),
reason: Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned()),
},
@@ -934,23 +915,6 @@ fn nostr_publish_readiness(
("ready", true, None)
}
-fn proxy_publish_readiness(config: &RuntimeConfig) -> (&'static str, bool, Option<String>) {
- if let Err(error) = crate::runtime::transport::proxy_token_ready(config) {
- return ("unconfigured", false, Some(error.to_string()));
- }
-
- if matches!(config.signer.backend, SignerBackend::Myc) {
- let signer = resolve_signer_status(config);
- return if signer.state == "ready" {
- ("ready", true, None)
- } else {
- ("unconfigured", false, signer.reason)
- };
- }
-
- ("ready", true, None)
-}
-
fn signer_health_view(config: &RuntimeConfig, account: &AccountResolution) -> Value {
match config.signer.backend {
SignerBackend::Local => {
@@ -1101,7 +1065,7 @@ fn publish_recovery_actions(
let mut actions = Vec::new();
match config.transport.profile {
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid => {
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget => {
if config.transport.nostr_relay_urls.is_empty() {
push_unique(
&mut actions,
@@ -1120,27 +1084,13 @@ fn publish_recovery_actions(
}
}
}
- TransportProfileKind::Proxy => {
- if crate::runtime::transport::proxy_token_ready(config).is_ok() {
- if publish.signed_write_required
- && matches!(config.signer.backend, SignerBackend::Myc)
- {
- push_unique(&mut actions, "radroots signer status get");
- }
- } else {
- push_unique(
- &mut actions,
- "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID",
- );
- }
- }
TransportProfileKind::LocalOnly => {
push_unique(
&mut actions,
"radroots transport profile set --kind nostr --nostr-relay wss://relay.example.com",
);
}
- TransportProfileKind::ReticulumPreview => {
+ TransportProfileKind::Reticulum => {
push_unique(
&mut actions,
"radroots transport profile set --kind nostr --nostr-relay wss://relay.example.com",
diff --git a/src/ops/exec/farm.rs b/src/ops/exec/farm.rs
@@ -234,7 +234,7 @@ impl OperationService<FarmPublishRequest> for FarmOperationService<'_> {
}
if matches!(
self.config.transport.profile,
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget
) {
require_nostr_delivery_target(&request, self.config)?;
}
diff --git a/src/ops/mod.rs b/src/ops/mod.rs
@@ -598,7 +598,7 @@ mod tests {
(
OperationAdapterError::unavailable(
"listing.publish",
- "proxy provider is unavailable".to_owned(),
+ "transport provider is unavailable".to_owned(),
),
"provider_unavailable",
"provider",
diff --git a/src/out/envelope.rs b/src/out/envelope.rs
@@ -276,26 +276,26 @@ fn next_actions_from_actions_value(actions_value: Option<&Value>) -> Vec<NextAct
fn next_action_from_action_string(action: &str) -> Option<NextAction> {
let action = action.trim();
- if action
- == "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID"
- {
+ if action == "configure signer.remote_nip46 signer_session_ref" {
return Some(NextAction {
kind: NextActionKind::OperatorConfig,
- label: "configure proxy token source".to_owned(),
+ label: "configure signer session binding".to_owned(),
command: None,
description: Some(action.to_owned()),
- env_var: Some("RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE".to_owned()),
- config_key: None,
+ env_var: None,
+ config_key: Some("signer.remote_nip46.signer_session_ref".to_owned()),
});
}
- if action == "configure signer.remote_nip46 signer_session_ref" {
+ if action
+ == "configure RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE or RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID"
+ {
return Some(NextAction {
kind: NextActionKind::OperatorConfig,
- label: "configure signer session binding".to_owned(),
+ label: "configure radrootsd execution token source".to_owned(),
command: None,
description: Some(action.to_owned()),
- env_var: None,
- config_key: Some("signer.remote_nip46.signer_session_ref".to_owned()),
+ env_var: Some("RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE".to_owned()),
+ config_key: None,
});
}
let command = action.trim().strip_prefix("run ").unwrap_or(action).trim();
@@ -606,9 +606,9 @@ mod tests {
);
error.detail = Some(json!({
"actions": [
- "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID",
+ "configure RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE or RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID",
"configure signer.remote_nip46 signer_session_ref",
- "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID"
+ "configure RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE or RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID"
]
}));
let envelope = OutputEnvelope::failure(
@@ -625,12 +625,12 @@ mod tests {
);
assert_eq!(
envelope.next_actions[0].label,
- "configure proxy token source"
+ "configure radrootsd execution token source"
);
assert_eq!(envelope.next_actions[0].command, None);
assert_eq!(
envelope.next_actions[0].env_var.as_deref(),
- Some("RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE")
+ Some("RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE")
);
assert_eq!(
envelope.next_actions[1].kind,
diff --git a/src/out/terminal/actions.rs b/src/out/terminal/actions.rs
@@ -316,8 +316,8 @@ mod tests {
kind: NextActionKind::OperatorConfig,
label: "configure token".to_owned(),
command: None,
- description: Some("configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE".to_owned()),
- env_var: Some("RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE".to_owned()),
+ description: Some("configure RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE".to_owned()),
+ env_var: Some("RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE".to_owned()),
config_key: None,
};
@@ -327,7 +327,7 @@ mod tests {
assert_eq!(terminal[0].kind, TerminalActionKind::Setup);
assert_eq!(
terminal[0].description.as_deref(),
- Some("configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE")
+ Some("configure RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE")
);
}
}
diff --git a/src/out/terminal/renderers/runtime.rs b/src/out/terminal/renderers/runtime.rs
@@ -107,24 +107,6 @@ fn transport_profile_document(envelope: &OutputEnvelope, result: &Value) -> Term
&["profile_delivery_usable"],
);
common::push_path_field(&mut document, "Message", result, &["message"]);
- common::push_path_field(
- &mut document,
- "Proxy token",
- result,
- &["proxy_token_source"],
- );
- common::push_path_field(
- &mut document,
- "Proxy token file",
- result,
- &["proxy_token_file"],
- );
- common::push_path_field(
- &mut document,
- "Proxy token secret",
- result,
- &["proxy_token_secret_id"],
- );
if let Some(relays) = common::array(result, &["nostr_relays"]) {
let rows = relays
.iter()
@@ -233,9 +215,9 @@ fn transport_outbox_status_document(envelope: &OutputEnvelope, result: &Value) -
common::push_count_field(&mut document, "Terminal", result, &["terminal_count"]);
common::push_count_field(
&mut document,
- "Preview unavailable",
+ "Deferred",
result,
- &["preview_unavailable_count"],
+ &["deferred_until_implemented_count"],
);
common::push_count_field(
&mut document,
diff --git a/src/out/terminal/values.rs b/src/out/terminal/values.rs
@@ -5,9 +5,8 @@ use serde_json::Value;
pub fn transport_label(value: &str) -> String {
match value {
"nostr" => "nostr",
- "proxy" => "proxy",
+ "reticulum" => "reticulum",
"local" => "local",
- "preview" => "preview",
other => other,
}
.to_owned()
diff --git a/src/registry/mesh.rs b/src/registry/mesh.rs
@@ -55,7 +55,7 @@ pub const MESH_POLICY_CHECK: OperationSpec = operation!(
"mesh_policy_check",
"MeshPolicyCheckRequest",
"MeshPolicyCheckResult",
- "Evaluate mesh delivery policy for the active preview state.",
+ "Evaluate mesh delivery policy for the active Reticulum availability state.",
Any,
false,
None,
diff --git a/src/runtime/config.rs b/src/runtime/config.rs
@@ -7,7 +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::{RADROOTS_RETICULUM_SCOPE_ID, RadrootsTransportMeshScopeId};
use radroots_transport_nostr::{
RadrootsRelayTransportError, RadrootsRelayUrl, RadrootsRelayUrlPolicy,
};
@@ -44,16 +44,14 @@ const ENV_CLI_IDENTITY_PATH: &str = "RADROOTS_CLI_IDENTITY_PATH";
const ENV_CLI_SIGNER_BACKEND: &str = "RADROOTS_CLI_SIGNER_BACKEND";
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 =
- "RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID";
+const ENV_CLI_TRANSPORT_RETICULUM_BEHAVIOR: &str = "RADROOTS_CLI_TRANSPORT_RETICULUM_BEHAVIOR";
+const ENV_CLI_TRANSPORT_RETICULUM_SCOPE: &str = "RADROOTS_CLI_TRANSPORT_RETICULUM_SCOPE";
+const ENV_CLI_TRANSPORT_RETICULUM_AGENT_ENDPOINT: &str =
+ "RADROOTS_CLI_TRANSPORT_RETICULUM_AGENT_ENDPOINT";
+const ENV_CLI_RADROOTSD_EXECUTION_URL: &str = "RADROOTS_CLI_RADROOTSD_EXECUTION_URL";
+const ENV_CLI_RADROOTSD_EXECUTION_TOKEN_FILE: &str = "RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_FILE";
+const ENV_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID: &str =
+ "RADROOTS_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID";
const ENV_CLI_MYC_EXECUTABLE: &str = "RADROOTS_CLI_MYC_EXECUTABLE";
const ENV_CLI_MYC_STATUS_TIMEOUT_MS: &str = "RADROOTS_CLI_MYC_STATUS_TIMEOUT_MS";
const ENV_CLI_HYF_ENABLED: &str = "RADROOTS_CLI_HYF_ENABLED";
@@ -73,12 +71,12 @@ const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[
ENV_CLI_SIGNER_BACKEND,
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,
+ ENV_CLI_TRANSPORT_RETICULUM_BEHAVIOR,
+ ENV_CLI_TRANSPORT_RETICULUM_SCOPE,
+ ENV_CLI_TRANSPORT_RETICULUM_AGENT_ENDPOINT,
+ ENV_CLI_RADROOTSD_EXECUTION_URL,
+ ENV_CLI_RADROOTSD_EXECUTION_TOKEN_FILE,
+ ENV_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID,
ENV_CLI_MYC_EXECUTABLE,
ENV_CLI_MYC_STATUS_TIMEOUT_MS,
ENV_CLI_HYF_ENABLED,
@@ -192,9 +190,8 @@ pub struct SignerConfig {
pub enum TransportProfileKind {
LocalOnly,
Nostr,
- ReticulumPreview,
- Hybrid,
- Proxy,
+ Reticulum,
+ MultiTarget,
}
impl TransportProfileKind {
@@ -202,9 +199,8 @@ impl TransportProfileKind {
match self {
Self::LocalOnly => "local_only",
Self::Nostr => "nostr",
- Self::ReticulumPreview => "reticulum_preview",
- Self::Hybrid => "hybrid",
- Self::Proxy => "proxy",
+ Self::Reticulum => "reticulum",
+ Self::MultiTarget => "multi_target",
}
}
@@ -212,20 +208,19 @@ impl TransportProfileKind {
match self {
Self::LocalOnly => "local",
Self::Nostr => "nostr",
- Self::ReticulumPreview => "reticulum",
- Self::Hybrid => "hybrid",
- Self::Proxy => "proxy",
+ Self::Reticulum => "reticulum",
+ Self::MultiTarget => "multi_target",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ReticulumPreviewBehavior {
+pub enum ReticulumBehavior {
RejectDeliveryAttempts,
DeferDeliveryPlans,
}
-impl ReticulumPreviewBehavior {
+impl ReticulumBehavior {
pub fn as_str(self) -> &'static str {
match self {
Self::RejectDeliveryAttempts => "reject_delivery_attempts",
@@ -258,10 +253,10 @@ pub struct TransportConfig {
pub profile: TransportProfileKind,
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,
+ pub reticulum_behavior: ReticulumBehavior,
+ pub reticulum_scope: String,
+ pub reticulum_agent_endpoint: Option<String>,
+ pub radrootsd_execution: RadrootsdExecutionConfig,
}
impl TransportConfig {
@@ -270,10 +265,10 @@ impl TransportConfig {
profile: TransportProfileKind::LocalOnly,
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(),
+ reticulum_behavior: ReticulumBehavior::RejectDeliveryAttempts,
+ reticulum_scope: RADROOTS_RETICULUM_SCOPE_ID.to_owned(),
+ reticulum_agent_endpoint: None,
+ radrootsd_execution: RadrootsdExecutionConfig::default(),
}
}
@@ -285,22 +280,22 @@ impl TransportConfig {
profile: TransportProfileKind::Nostr,
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(),
+ reticulum_behavior: ReticulumBehavior::RejectDeliveryAttempts,
+ reticulum_scope: RADROOTS_RETICULUM_SCOPE_ID.to_owned(),
+ reticulum_agent_endpoint: None,
+ radrootsd_execution: RadrootsdExecutionConfig::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ProxyTransportConfig {
+pub struct RadrootsdExecutionConfig {
pub url: String,
pub token_file: Option<PathBuf>,
pub token_secret_id: Option<String>,
}
-impl Default for ProxyTransportConfig {
+impl Default for RadrootsdExecutionConfig {
fn default() -> Self {
Self {
url: DEFAULT_RPC_URL.to_owned(),
@@ -333,14 +328,14 @@ pub struct HyfConfig {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MeshScope {
Disabled,
- LocalPreview,
+ Local,
}
impl MeshScope {
pub fn as_str(self) -> &'static str {
match self {
Self::Disabled => "disabled",
- Self::LocalPreview => "local_preview",
+ Self::Local => "local",
}
}
}
@@ -537,8 +532,8 @@ struct IdentityFileConfig {
struct TransportFileConfig {
profile: Option<String>,
nostr: Option<NostrTransportFileConfig>,
- reticulum_preview: Option<ReticulumPreviewFileConfig>,
- proxy: Option<TransportProxyFileConfig>,
+ reticulum: Option<ReticulumFileConfig>,
+ radrootsd_execution: Option<RadrootsdExecutionFileConfig>,
}
#[derive(Debug, Default, Deserialize)]
@@ -549,7 +544,7 @@ struct NostrTransportFileConfig {
#[derive(Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
-struct ReticulumPreviewFileConfig {
+struct ReticulumFileConfig {
behavior: Option<String>,
scope: Option<String>,
agent_endpoint: Option<String>,
@@ -557,7 +552,7 @@ struct ReticulumPreviewFileConfig {
#[derive(Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
-struct TransportProxyFileConfig {
+struct RadrootsdExecutionFileConfig {
url: Option<String>,
token_file: Option<PathBuf>,
token_secret_id: Option<String>,
@@ -988,41 +983,25 @@ fn resolve_transport_config(
) -> Result<TransportConfig, RuntimeError> {
if let Some((key, value)) = env_value_entry(env, env_file, &[ENV_CLI_TRANSPORT_PROFILE]) {
let profile = parse_transport_profile_kind(key.as_str(), value.as_str())?;
- let reticulum_preview_behavior = env_value(
- env,
- env_file,
- &[ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_BEHAVIOR],
- )
- .map(|value| {
- parse_reticulum_preview_behavior(
- ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_BEHAVIOR,
- value.as_str(),
- )
- })
- .transpose()?
- .unwrap_or(ReticulumPreviewBehavior::RejectDeliveryAttempts);
- let reticulum_preview_scope =
- env_value(env, env_file, &[ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE])
+ let reticulum_behavior = env_value(env, env_file, &[ENV_CLI_TRANSPORT_RETICULUM_BEHAVIOR])
+ .map(|value| {
+ parse_reticulum_behavior(ENV_CLI_TRANSPORT_RETICULUM_BEHAVIOR, value.as_str())
+ })
+ .transpose()?
+ .unwrap_or(ReticulumBehavior::RejectDeliveryAttempts);
+ let reticulum_scope = env_value(env, env_file, &[ENV_CLI_TRANSPORT_RETICULUM_SCOPE])
+ .map(|value| parse_reticulum_scope(ENV_CLI_TRANSPORT_RETICULUM_SCOPE, value.as_str()))
+ .transpose()?
+ .unwrap_or_else(|| RADROOTS_RETICULUM_SCOPE_ID.to_owned());
+ let reticulum_agent_endpoint =
+ env_value(env, env_file, &[ENV_CLI_TRANSPORT_RETICULUM_AGENT_ENDPOINT])
.map(|value| {
- parse_reticulum_preview_scope(
- ENV_CLI_TRANSPORT_RETICULUM_PREVIEW_SCOPE,
+ parse_reticulum_agent_endpoint(
+ ENV_CLI_TRANSPORT_RETICULUM_AGENT_ENDPOINT,
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()?;
+ .transpose()?;
return Ok(TransportConfig {
profile,
source: TransportConfigSource::Environment,
@@ -1032,10 +1011,10 @@ 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)?,
+ reticulum_behavior,
+ reticulum_scope,
+ reticulum_agent_endpoint,
+ radrootsd_execution: resolve_radrootsd_execution_env(env, env_file)?,
});
}
@@ -1059,10 +1038,10 @@ fn resolve_transport_config(
profile: TransportProfileKind::LocalOnly,
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(),
+ reticulum_behavior: ReticulumBehavior::RejectDeliveryAttempts,
+ reticulum_scope: RADROOTS_RETICULUM_SCOPE_ID.to_owned(),
+ reticulum_agent_endpoint: None,
+ radrootsd_execution: RadrootsdExecutionConfig::default(),
})
}
@@ -1073,8 +1052,8 @@ fn resolve_transport_file_config(
) -> Result<TransportConfig, RuntimeError> {
let Some(profile_value) = transport.profile.as_deref() else {
if transport.nostr.is_some()
- || transport.reticulum_preview.is_some()
- || transport.proxy.is_some()
+ || transport.reticulum.is_some()
+ || transport.radrootsd_execution.is_some()
{
return Err(RuntimeError::Config(format!(
"{source_label}.profile must be set when profile-specific transport tables are present"
@@ -1084,10 +1063,10 @@ fn resolve_transport_file_config(
profile: TransportProfileKind::LocalOnly,
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(),
+ reticulum_behavior: ReticulumBehavior::RejectDeliveryAttempts,
+ reticulum_scope: RADROOTS_RETICULUM_SCOPE_ID.to_owned(),
+ reticulum_agent_endpoint: None,
+ radrootsd_execution: RadrootsdExecutionConfig::default(),
});
};
let profile =
@@ -1099,37 +1078,31 @@ fn resolve_transport_file_config(
.map(|urls| normalize_relay_urls(urls, format!("{source_label}.nostr.relay_urls").as_str()))
.transpose()?
.unwrap_or_default();
- let reticulum_preview_behavior = transport
- .reticulum_preview
+ let reticulum_behavior = transport
+ .reticulum
.as_ref()
- .and_then(|preview| preview.behavior.as_deref())
+ .and_then(|reticulum| reticulum.behavior.as_deref())
.map(|value| {
- parse_reticulum_preview_behavior(
- format!("{source_label}.reticulum_preview.behavior").as_str(),
- value,
- )
+ parse_reticulum_behavior(format!("{source_label}.reticulum.behavior").as_str(), value)
})
.transpose()?
- .unwrap_or(ReticulumPreviewBehavior::RejectDeliveryAttempts);
- let reticulum_preview_scope = transport
- .reticulum_preview
+ .unwrap_or(ReticulumBehavior::RejectDeliveryAttempts);
+ let reticulum_scope = transport
+ .reticulum
.as_ref()
- .and_then(|preview| preview.scope.as_deref())
+ .and_then(|reticulum| reticulum.scope.as_deref())
.map(|value| {
- parse_reticulum_preview_scope(
- format!("{source_label}.reticulum_preview.scope").as_str(),
- value,
- )
+ parse_reticulum_scope(format!("{source_label}.reticulum.scope").as_str(), value)
})
.transpose()?
- .unwrap_or_else(|| RADROOTS_RETICULUM_PREVIEW_SCOPE_ID.to_owned());
- let reticulum_preview_agent_endpoint = transport
- .reticulum_preview
+ .unwrap_or_else(|| RADROOTS_RETICULUM_SCOPE_ID.to_owned());
+ let reticulum_agent_endpoint = transport
+ .reticulum
.as_ref()
- .and_then(|preview| preview.agent_endpoint.as_deref())
+ .and_then(|reticulum| reticulum.agent_endpoint.as_deref())
.map(|value| {
- parse_reticulum_preview_agent_endpoint(
- format!("{source_label}.reticulum_preview.agent_endpoint").as_str(),
+ parse_reticulum_agent_endpoint(
+ format!("{source_label}.reticulum.agent_endpoint").as_str(),
value,
)
})
@@ -1138,53 +1111,61 @@ fn resolve_transport_file_config(
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)?,
+ reticulum_behavior,
+ reticulum_scope,
+ reticulum_agent_endpoint,
+ radrootsd_execution: resolve_radrootsd_execution_file(
+ transport.radrootsd_execution.as_ref(),
+ source_label,
+ )?,
})
}
-fn resolve_transport_proxy_env(
+fn resolve_radrootsd_execution_env(
env: &dyn Environment,
env_file: &EnvFileValues,
-) -> Result<ProxyTransportConfig, RuntimeError> {
- let url = env_value(env, env_file, &[ENV_CLI_TRANSPORT_PROXY_URL])
+) -> Result<RadrootsdExecutionConfig, RuntimeError> {
+ let url = env_value(env, env_file, &[ENV_CLI_RADROOTSD_EXECUTION_URL])
.unwrap_or_else(|| DEFAULT_RPC_URL.to_owned());
let token_file =
- env_value(env, env_file, &[ENV_CLI_TRANSPORT_PROXY_TOKEN_FILE]).map(PathBuf::from);
- let token_secret_id = env_value(env, env_file, &[ENV_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID]);
- validate_proxy_token_source(
+ env_value(env, env_file, &[ENV_CLI_RADROOTSD_EXECUTION_TOKEN_FILE]).map(PathBuf::from);
+ let token_secret_id = env_value(
+ env,
+ env_file,
+ &[ENV_CLI_RADROOTSD_EXECUTION_TOKEN_SECRET_ID],
+ );
+ validate_radrootsd_execution_token_source(
token_file.as_ref(),
token_secret_id.as_deref(),
- "environment proxy transport",
+ "environment radrootsd execution",
)?;
- Ok(ProxyTransportConfig {
+ Ok(RadrootsdExecutionConfig {
url: validate_rpc_url(url.as_str())?,
token_file,
token_secret_id,
})
}
-fn resolve_transport_proxy_file(
- proxy: Option<&TransportProxyFileConfig>,
+fn resolve_radrootsd_execution_file(
+ radrootsd_execution: Option<&RadrootsdExecutionFileConfig>,
source_label: &str,
-) -> Result<ProxyTransportConfig, RuntimeError> {
- let url = proxy
- .and_then(|proxy| proxy.url.clone())
+) -> Result<RadrootsdExecutionConfig, RuntimeError> {
+ let url = radrootsd_execution
+ .and_then(|execution| execution.url.clone())
.unwrap_or_else(|| DEFAULT_RPC_URL.to_owned());
- let token_file = proxy.and_then(|proxy| proxy.token_file.clone());
- let token_secret_id = proxy.and_then(|proxy| proxy.token_secret_id.clone());
- validate_proxy_token_source(
+ let token_file = radrootsd_execution.and_then(|execution| execution.token_file.clone());
+ let token_secret_id =
+ radrootsd_execution.and_then(|execution| execution.token_secret_id.clone());
+ validate_radrootsd_execution_token_source(
token_file.as_ref(),
token_secret_id.as_deref(),
- format!("{source_label}.proxy").as_str(),
+ format!("{source_label}.radrootsd_execution").as_str(),
)?;
- Ok(ProxyTransportConfig {
+ Ok(RadrootsdExecutionConfig {
url: validate_rpc_url(url.as_str()).map_err(|error| match error {
- RuntimeError::Config(message) => {
- RuntimeError::Config(format!("{source_label}.proxy.url is invalid: {message}"))
- }
+ RuntimeError::Config(message) => RuntimeError::Config(format!(
+ "{source_label}.radrootsd_execution.url is invalid: {message}"
+ )),
other => other,
})?,
token_file,
@@ -1192,7 +1173,7 @@ fn resolve_transport_proxy_file(
})
}
-fn validate_proxy_token_source(
+fn validate_radrootsd_execution_token_source(
token_file: Option<&PathBuf>,
token_secret_id: Option<&str>,
source_label: &str,
@@ -1607,9 +1588,9 @@ fn resolve_mesh_config(
fn parse_mesh_scope(source: &str, value: &str) -> Result<MeshScope, RuntimeError> {
match value.trim().to_ascii_lowercase().as_str() {
"disabled" => Ok(MeshScope::Disabled),
- "local_preview" => Ok(MeshScope::LocalPreview),
+ "local" => Ok(MeshScope::Local),
other => Err(RuntimeError::Config(format!(
- "{source} must be `disabled` or `local_preview`, got `{other}`"
+ "{source} must be `disabled` or `local`, got `{other}`"
))),
}
}
@@ -1955,38 +1936,31 @@ fn parse_transport_profile_kind(
match value.trim().to_ascii_lowercase().as_str() {
"local_only" => Ok(TransportProfileKind::LocalOnly),
"nostr" => Ok(TransportProfileKind::Nostr),
- "reticulum_preview" => Ok(TransportProfileKind::ReticulumPreview),
- "hybrid" => Ok(TransportProfileKind::Hybrid),
- "proxy" => Ok(TransportProfileKind::Proxy),
+ "reticulum" => Ok(TransportProfileKind::Reticulum),
+ "multi_target" => Ok(TransportProfileKind::MultiTarget),
other => Err(RuntimeError::Config(format!(
- "{source} must be `local_only`, `nostr`, `reticulum_preview`, `hybrid`, or `proxy`, got `{other}`"
+ "{source} must be `local_only`, `nostr`, `reticulum`, or `multi_target`, got `{other}`"
))),
}
}
-fn parse_reticulum_preview_behavior(
- source: &str,
- value: &str,
-) -> Result<ReticulumPreviewBehavior, RuntimeError> {
+fn parse_reticulum_behavior(source: &str, value: &str) -> Result<ReticulumBehavior, RuntimeError> {
match value.trim().to_ascii_lowercase().as_str() {
- "reject_delivery_attempts" => Ok(ReticulumPreviewBehavior::RejectDeliveryAttempts),
- "defer_delivery_plans" => Ok(ReticulumPreviewBehavior::DeferDeliveryPlans),
+ "reject_delivery_attempts" => Ok(ReticulumBehavior::RejectDeliveryAttempts),
+ "defer_delivery_plans" => Ok(ReticulumBehavior::DeferDeliveryPlans),
other => Err(RuntimeError::Config(format!(
"{source} must be `reject_delivery_attempts` or `defer_delivery_plans`, got `{other}`"
))),
}
}
-fn parse_reticulum_preview_scope(source: &str, value: &str) -> Result<String, RuntimeError> {
+fn parse_reticulum_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> {
+fn parse_reticulum_agent_endpoint(source: &str, value: &str) -> Result<String, RuntimeError> {
if value.is_empty()
|| value != value.trim()
|| value
@@ -2431,7 +2405,7 @@ mod tests {
"old-identity.json".to_owned(),
),
("RADROOTS_SIGNER".to_owned(), "myc".to_owned()),
- ("RADROOTS_PUBLISH_MODE".to_owned(), "proxy".to_owned()),
+ ("RADROOTS_PUBLISH_MODE".to_owned(), "legacy".to_owned()),
(
"RADROOTS_RELAYS".to_owned(),
"wss://old-relay.example".to_owned(),
@@ -2740,7 +2714,7 @@ path = "identity/from-toml.json"
.expect_err("invalid transport profile");
assert!(error.to_string().contains("RADROOTS_CLI_TRANSPORT_PROFILE"));
assert!(error.to_string().contains("local_only"));
- assert!(error.to_string().contains("reticulum_preview"));
+ assert!(error.to_string().contains("reticulum"));
let args = RuntimeInvocationArgs {
myc_status_timeout_ms: Some(0),
@@ -2953,7 +2927,7 @@ RADROOTS_CLI_LOGGING_STDOUT=true
}
#[test]
- fn hybrid_transport_config_uses_nostr_targets_and_reticulum_preview_behavior() {
+ fn multi_target_transport_config_uses_nostr_targets_and_reticulum_behavior() {
let temp = tempdir().expect("tempdir");
let workspace_root = temp.path().join("workspace");
let repo_local_root = workspace_root.join("infra/local/runtime/radroots");
@@ -2963,16 +2937,19 @@ 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\"\nscope = \"farmers_market\"\nagent_endpoint = \"reticulum-agent:local\"\n",
+ "[transport]\nprofile = \"multi_target\"\n\n[transport.nostr]\nrelay_urls = [\"wss://relay.user\", \"wss://relay.backup\"]\n\n[transport.reticulum]\nbehavior = \"defer_delivery_plans\"\nscope = \"farmers_market\"\nagent_endpoint = \"reticulum-agent:local\"\n",
)
.expect("write user config");
let env = repo_local_env(workspace_root, repo_local_root, user_home, BTreeMap::new());
let resolved =
RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default())
- .expect("resolve hybrid transport profile");
+ .expect("resolve multi-target transport profile");
- assert_eq!(resolved.transport.profile, TransportProfileKind::Hybrid);
+ assert_eq!(
+ resolved.transport.profile,
+ TransportProfileKind::MultiTarget
+ );
assert_eq!(resolved.transport.source, TransportConfigSource::UserConfig);
assert_eq!(
resolved.transport.nostr_relay_urls,
@@ -2982,15 +2959,12 @@ RADROOTS_CLI_LOGGING_STDOUT=true
]
);
assert_eq!(
- resolved.transport.reticulum_preview_behavior,
- super::ReticulumPreviewBehavior::DeferDeliveryPlans
+ resolved.transport.reticulum_behavior,
+ super::ReticulumBehavior::DeferDeliveryPlans
);
- assert_eq!(resolved.transport.reticulum_preview_scope, "farmers_market");
+ assert_eq!(resolved.transport.reticulum_scope, "farmers_market");
assert_eq!(
- resolved
- .transport
- .reticulum_preview_agent_endpoint
- .as_deref(),
+ resolved.transport.reticulum_agent_endpoint.as_deref(),
Some("reticulum-agent:local")
);
}
@@ -3006,7 +2980,7 @@ RADROOTS_CLI_LOGGING_STDOUT=true
fs::create_dir_all(&app_config_dir).expect("app config dir");
fs::write(
repo_local_root.join("config.toml"),
- "[transport]\nprofile = \"proxy\"\n\n[transport.proxy]\nurl = \"http://127.0.0.1:7070\"\n",
+ "[transport]\nprofile = \"reticulum\"\n",
)
.expect("write workspace config");
fs::write(
@@ -3021,13 +2995,13 @@ RADROOTS_CLI_LOGGING_STDOUT=true
user_home.clone(),
BTreeMap::from([(
"RADROOTS_CLI_TRANSPORT_PROFILE".to_owned(),
- "proxy".to_owned(),
+ "reticulum".to_owned(),
)]),
);
let resolved =
RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default())
.expect("resolve environment transport profile");
- assert_eq!(resolved.transport.profile, TransportProfileKind::Proxy);
+ assert_eq!(resolved.transport.profile, TransportProfileKind::Reticulum);
assert_eq!(
resolved.transport.source,
TransportConfigSource::Environment
@@ -3053,7 +3027,7 @@ RADROOTS_CLI_LOGGING_STDOUT=true
let resolved =
RuntimeConfig::resolve_with_env_file(&runtime_args(), &env, &EnvFileValues::default())
.expect("resolve workspace transport profile");
- assert_eq!(resolved.transport.profile, TransportProfileKind::Proxy);
+ assert_eq!(resolved.transport.profile, TransportProfileKind::Reticulum);
assert_eq!(
resolved.transport.source,
TransportConfigSource::WorkspaceConfig
@@ -3269,7 +3243,7 @@ RADROOTS_CLI_LOGGING_STDOUT=true
let message = error.to_string();
assert!(message.contains("workspace config [transport].profile"));
assert!(message.contains("local_only"));
- assert!(message.contains("reticulum_preview"));
+ assert!(message.contains("reticulum"));
}
#[test]
@@ -3359,7 +3333,7 @@ target = "bin/user-hyfd"
r#"
[[capability_binding]]
capability = "write_plane.trade_jsonrpc"
- provider = "legacy_proxy"
+ provider = "legacy_provider"
target_kind = "explicit_endpoint"
target = "https://rpc.workspace.test/jsonrpc"
"#,
diff --git a/src/runtime/farm.rs b/src/runtime/farm.rs
@@ -623,7 +623,7 @@ fn transport_farm_publish_readiness(
) -> FarmPublishReadiness {
if matches!(
config.transport.profile,
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget
) && config.transport.nostr_relay_urls.is_empty()
{
return FarmPublishReadiness {
@@ -977,11 +977,45 @@ fn component_idempotency_key(
args.idempotency_key
.as_deref()
.map(|value| {
- required_text(value, "idempotency_key").map(|key| format!("{key}:{component}"))
+ required_text(value, "idempotency_key")
+ .and_then(|key| derive_component_idempotency_key(key.as_str(), component))
})
.transpose()
}
+fn derive_component_idempotency_key(key: &str, component: &str) -> Result<String, RuntimeError> {
+ if !is_uuid_v7_idempotency_key(key) {
+ return Err(RuntimeError::Config(
+ "idempotency_key must be a UUIDv7".to_owned(),
+ ));
+ }
+ let mut hash = 0xcbf2_9ce4_8422_2325_u64;
+ for byte in key
+ .bytes()
+ .chain(std::iter::once(b':'))
+ .chain(component.bytes())
+ {
+ hash ^= u64::from(byte);
+ hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
+ }
+ Ok(format!("{}{:012x}", &key[..24], hash & 0xffff_ffff_ffff))
+}
+
+fn is_uuid_v7_idempotency_key(value: &str) -> bool {
+ let bytes = value.as_bytes();
+ bytes.len() == 36
+ && bytes[8] == b'-'
+ && bytes[13] == b'-'
+ && bytes[18] == b'-'
+ && bytes[23] == b'-'
+ && bytes[14] == b'7'
+ && matches!(bytes[19], b'8' | b'9' | b'a' | b'b')
+ && bytes
+ .iter()
+ .enumerate()
+ .all(|(index, byte)| matches!(index, 8 | 13 | 18 | 23) || byte.is_ascii_hexdigit())
+}
+
fn preview_component(
rpc_method: &str,
event_kind: u32,
diff --git a/src/runtime/find.rs b/src/runtime/find.rs
@@ -1,5 +1,5 @@
use radroots_replica_store::ReplicaSql;
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use crate::cli::global::FindQueryArgs;
use crate::runtime::RuntimeError;
@@ -62,7 +62,7 @@ pub fn search(config: &RuntimeConfig, args: &FindQueryArgs) -> Result<FindView,
}
refresh_market_if_needed(config)?;
- let db = ReplicaSql::new(SqliteExecutor::open(&config.local.replica_store_path)?);
+ let db = ReplicaSql::new(SqlxSqliteExecutor::open(&config.local.replica_store_path)?);
let freshness =
freshness_for_scope_from_executor(config, db.executor(), RelayIngestScope::MarketRefresh)?;
let applied_query_rewrite = attempt_query_rewrite(config, query.as_str(), &args.query);
@@ -162,7 +162,7 @@ fn refresh_market_if_needed(config: &RuntimeConfig) -> Result<(), RuntimeError>
if config.output.dry_run || config.transport.nostr_relay_urls.is_empty() {
return Ok(());
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
let freshness =
freshness_for_scope_from_executor(config, &executor, RelayIngestScope::MarketRefresh)?;
if freshness_requires_refresh(&freshness) {
diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs
@@ -13,7 +13,7 @@ use radroots_core::{
use radroots_event::contract::RadrootsActorRole;
use radroots_event::farm::RadrootsFarmRef;
use radroots_event::ids::{RadrootsDTag, RadrootsInventoryBinId};
-use radroots_event::kinds::{KIND_LISTING, KIND_LISTING_DRAFT};
+use radroots_event::kinds::KIND_LISTING;
use radroots_event::listing::{
RadrootsListing, RadrootsListingAvailability, RadrootsListingBin,
RadrootsListingDeliveryMethod, RadrootsListingProduct, RadrootsListingPublicLocation,
@@ -30,8 +30,8 @@ use radroots_sdk::{
ListingPublishPlan, PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt,
PushOutboxRequest, PushOutboxTargetOutcomeKind, SdkMutationState,
};
-use radroots_sql_core::SqliteExecutor;
-use radroots_trade::listing::{RadrootsListingDraftDocumentV1, validation::validate_listing_event};
+use radroots_sql_core::SqlxSqliteExecutor;
+use radroots_trade::listing::{RadrootsListingEditDocumentV1, validation::validate_listing_event};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
@@ -237,7 +237,7 @@ struct CanonicalListingDraft {
struct SdkListingPublishInput {
canonical: CanonicalListingDraft,
actor: RadrootsActorContext,
- document: RadrootsListingDraftDocumentV1,
+ document: RadrootsListingEditDocumentV1,
}
#[derive(Debug, Clone)]
@@ -565,7 +565,7 @@ pub fn validate(
match canonicalize_draft(&parsed, &contents, &context) {
Ok(canonical) => {
- let parts = match to_wire_parts_with_kind(&canonical.listing, KIND_LISTING_DRAFT) {
+ let parts = match to_wire_parts_with_kind(&canonical.listing, KIND_LISTING) {
Ok(parts) => parts,
Err(error) => {
return Ok(invalid_validation_view(
@@ -1153,7 +1153,7 @@ fn listing_ready_issues(
canonical: &CanonicalListingDraft,
contents: &str,
) -> Vec<ListingValidationIssueView> {
- let parts = match to_wire_parts_with_kind(&canonical.listing, KIND_LISTING_DRAFT) {
+ let parts = match to_wire_parts_with_kind(&canonical.listing, KIND_LISTING) {
Ok(parts) => parts,
Err(error) => {
return vec![ListingValidationIssueView {
@@ -1615,7 +1615,7 @@ pub fn get(
) -> Result<ListingGetView, RuntimeError> {
refresh_market_listing_if_needed(config)?;
let freshness = if config.local.replica_store_path.exists() {
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
freshness_for_scope_from_executor(config, &executor, RelayIngestScope::MarketRefresh)?
} else {
missing_freshness()
@@ -1648,7 +1648,7 @@ pub fn get(
});
}
- let db = ReplicaSql::new(SqliteExecutor::open(&config.local.replica_store_path)?);
+ let db = ReplicaSql::new(SqlxSqliteExecutor::open(&config.local.replica_store_path)?);
let rows = db.trade_product_lookup(args.key.as_str())?;
let Some(row) = rows.into_iter().next() else {
return Ok(ListingGetView {
@@ -1735,7 +1735,7 @@ fn refresh_market_listing_if_needed(config: &RuntimeConfig) -> Result<(), Runtim
{
return Ok(());
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
let freshness =
freshness_for_scope_from_executor(config, &executor, RelayIngestScope::MarketRefresh)?;
if crate::runtime::sync::freshness_requires_refresh(&freshness) {
@@ -1846,7 +1846,7 @@ fn sdk_listing_publish_input(
[RadrootsActorRole::Seller],
)
.map_err(|error| RuntimeError::Config(format!("invalid listing SDK actor: {error}")))?;
- let document = RadrootsListingDraftDocumentV1::new(canonical.listing.clone());
+ let document = RadrootsListingEditDocumentV1::new(canonical.listing.clone());
Ok(SdkListingPublishInput {
canonical,
actor,
@@ -1973,7 +1973,7 @@ fn listing_validation_event(
id: "0".repeat(64),
author: canonical.seller_pubkey.clone(),
created_at: 0,
- kind: KIND_LISTING_DRAFT,
+ kind: KIND_LISTING,
tags: parts.tags,
content: parts.content,
sig: "0".repeat(128),
@@ -2185,7 +2185,7 @@ fn mutate_via_sdk_from_canonical(
[RadrootsActorRole::Seller],
)
.map_err(|error| RuntimeError::Config(format!("invalid listing SDK actor: {error}")))?;
- let document = RadrootsListingDraftDocumentV1::new(canonical.listing.clone());
+ let document = RadrootsListingEditDocumentV1::new(canonical.listing.clone());
if config.output.dry_run {
let session = CliSdkSession::connect_memory(config)?;
let plan = session
@@ -3428,7 +3428,6 @@ mod tests {
| PushOutboxTargetOutcomeKind::TargetUriRejected
| PushOutboxTargetOutcomeKind::SkippedAlreadyAccepted
| PushOutboxTargetOutcomeKind::DeferredUntilImplemented
- | PushOutboxTargetOutcomeKind::PreviewUnavailable
| PushOutboxTargetOutcomeKind::Unknown => PushOutboxTransportOutcomeKind::Rejected,
_ => PushOutboxTransportOutcomeKind::Rejected,
})
diff --git a/src/runtime/mesh.rs b/src/runtime/mesh.rs
@@ -1,5 +1,5 @@
use radroots_mesh::{
- RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE, RadrootsMeshAdmissionInput, RadrootsMeshPayloadPolicy,
+ RADROOTS_MESH_UNAVAILABLE_MESSAGE, RadrootsMeshAdmissionInput, RadrootsMeshPayloadPolicy,
RadrootsMeshPrivacyClass, RadrootsMeshScope,
};
use serde_json::Value as JsonValue;
@@ -26,7 +26,7 @@ pub fn set_scope(
.and_then(JsonValue::as_str)
.unwrap_or("disabled");
match scope {
- "disabled" | "local_preview" => {}
+ "disabled" | "local" => {}
other => {
return Err(RuntimeError::Config(format!(
"mesh scope `{other}` is not supported"
@@ -47,14 +47,14 @@ pub fn status(config: &RuntimeConfig) -> MeshStatusView {
scope: scope.to_owned(),
transport: "reticulum".to_owned(),
configured: scope != "disabled",
- implementation: "preview_unavailable".to_owned(),
+ implementation: "unavailable".to_owned(),
usable_for_delivery: false,
- message: RADROOTS_MESH_PREVIEW_DENIAL_MESSAGE.to_owned(),
+ message: RADROOTS_MESH_UNAVAILABLE_MESSAGE.to_owned(),
}
}
pub fn policy_check(config: &RuntimeConfig) -> MeshPolicyCheckView {
- let policy = RadrootsMeshPayloadPolicy::preview_unavailable();
+ let policy = RadrootsMeshPayloadPolicy::reticulum_unavailable();
let privacy_class = RadrootsMeshPrivacyClass::PublicEvent;
let input = RadrootsMeshAdmissionInput::new(RadrootsMeshScope::Local, privacy_class, 1, 1);
let decision = policy.evaluate(&input);
@@ -88,8 +88,8 @@ fn scope_view(scope: &str, state: &str) -> MeshScopeView {
state: state.to_owned(),
source: MESH_SOURCE.to_owned(),
scope: scope.to_owned(),
- implementation: "preview_unavailable".to_owned(),
- message: "Mesh delivery is disabled unless a preview scope is explicitly configured"
+ implementation: "unavailable".to_owned(),
+ message: "Mesh delivery is disabled unless a Reticulum scope is explicitly configured"
.to_owned(),
actions: vec!["radroots mesh policy check".to_owned()],
}
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -16,17 +16,16 @@ use radroots_event::RadrootsEventPtr;
use radroots_event::contract::RadrootsActorRole;
use radroots_event::ids::{
RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId,
- RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey,
+ RadrootsOrderQuoteId, RadrootsPublicKey,
};
use radroots_event::kinds::{
KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST,
- KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL,
};
use radroots_event::order::{
RadrootsOrderEconomicActor, RadrootsOrderEconomicEffect, RadrootsOrderEconomicItem,
RadrootsOrderEconomicLine, RadrootsOrderEconomicLineKind, RadrootsOrderEconomics,
RadrootsOrderEventType, RadrootsOrderInventoryCommitment, RadrootsOrderItem,
- RadrootsOrderPricingBasis, RadrootsOrderRequest, RadrootsOrderRevisionOutcome,
+ RadrootsOrderPricingBasis, RadrootsOrderRequest,
};
use radroots_event_codec::d_tag::is_d_tag_base64url;
use radroots_event_codec::order::{order_event_context_from_tags, order_request_from_event};
@@ -54,12 +53,10 @@ use radroots_sdk::{
SdkMutationState, SdkTradeStatusSource, TargetPolicy, TradeAcceptRequest, TradeCancelRequest,
TradeCancellationPlan, TradeCancellationReceipt, TradeDecisionPlan, TradeDecisionReceipt,
TradeDeclineRequest, TradeEvidenceMode, TradeMutationOutcome, TradeProposeRequest,
- TradeRevisionDecisionPlan, TradeRevisionDecisionReceipt, TradeRevisionDecisionRequest,
- TradeRevisionProposalPlan, TradeRevisionProposalReceipt, TradeRevisionProposalRequest,
TradeStatusReceipt, TradeStatusRequest, TradeSubmitPlan, TradeSubmitReceipt,
TradeWorkflowEnqueueReceipt,
};
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use radroots_trade::identity::RadrootsTradeLocator;
use radroots_trade::order::canonicalize_order_request_for_signer;
use radroots_transport_nostr::{
@@ -226,6 +223,7 @@ fn protocol_listing_addr(value: &str, field: &str) -> Result<RadrootsListingAddr
})
}
+#[cfg(any())]
fn protocol_revision_id(value: &str, field: &str) -> Result<RadrootsOrderRevisionId, RuntimeError> {
value.parse().map_err(|error| {
RuntimeError::Config(format!("{field} is not a valid trade revision id: {error}"))
@@ -1241,18 +1239,12 @@ pub fn revision_propose(
if let Some(view) = order_revision_args_preflight_view(config, args) {
return Ok(view);
}
- let seller = match account::resolve_account(config)? {
- Some(account) => account,
- None => {
- let mut view =
- order_revision_base_view(config, args, "unconfigured", config.output.dry_run);
- view.reason =
- Some("trade revision propose requires a selected seller account".to_owned());
- view.actions = vec!["radroots account create".to_owned()];
- return Ok(view);
- }
- };
- propose_revision_via_sdk(config, args, &seller)
+ let mut view = order_revision_base_view(config, args, "unavailable", config.output.dry_run);
+ view.reason = Some(
+ "trade revision proposal is not supported by the current SDK trade runtime".to_owned(),
+ );
+ view.actions = vec![format!("radroots trade status get {}", args.key)];
+ Ok(view)
}
pub fn revision_decide(
@@ -1262,22 +1254,13 @@ pub fn revision_decide(
if let Some(view) = order_revision_decision_args_preflight_view(config, args) {
return Ok(view);
}
- let actor_context = match order_buyer_write_actor_context(config, args.key.as_str())? {
- Some(context) => context,
- None => {
- let mut view = order_revision_decision_base_view(
- config,
- args,
- "unconfigured",
- config.output.dry_run,
- );
- view.reason =
- Some("trade revision decision requires a selected buyer account".to_owned());
- view.actions = vec!["radroots account create".to_owned()];
- return Ok(view);
- }
- };
- decide_revision_via_sdk(config, args, actor_context)
+ let mut view =
+ order_revision_decision_base_view(config, args, "unavailable", config.output.dry_run);
+ view.reason = Some(
+ "trade revision decision is not supported by the current SDK trade runtime".to_owned(),
+ );
+ view.actions = vec![format!("radroots trade status get {}", args.key)];
+ Ok(view)
}
pub fn cancel(
@@ -1372,6 +1355,7 @@ fn decide_trade_via_sdk(
))
}
+#[cfg(any())]
fn propose_revision_via_sdk(
config: &RuntimeConfig,
args: &TradeRevisionProposeArgs,
@@ -1411,6 +1395,7 @@ fn propose_revision_via_sdk(
))
}
+#[cfg(any())]
fn decide_revision_via_sdk(
config: &RuntimeConfig,
args: &TradeRevisionDecisionArgs,
@@ -1547,6 +1532,7 @@ fn inventory_commitments_from_status(
.collect())
}
+#[cfg(any())]
#[derive(Debug, Clone)]
struct SdkRevisionRequestParts {
revision_id: RadrootsOrderRevisionId,
@@ -1554,6 +1540,7 @@ struct SdkRevisionRequestParts {
economics: RadrootsOrderEconomics,
}
+#[cfg(any())]
fn revision_request_parts_from_status(
args: &TradeRevisionProposeArgs,
status: &OrderStatusView,
@@ -1610,6 +1597,7 @@ fn sdk_trade_decision_outcome_view(
}
}
+#[cfg(any())]
fn sdk_trade_revision_outcome_view(
config: &RuntimeConfig,
args: &TradeRevisionProposeArgs,
@@ -1655,6 +1643,7 @@ fn sdk_trade_revision_outcome_view(
}
}
+#[cfg(any())]
fn sdk_trade_revision_decision_outcome_view(
config: &RuntimeConfig,
args: &TradeRevisionDecisionArgs,
@@ -2109,6 +2098,7 @@ fn apply_order_decision_status(view: &mut OrderDecisionView, status: &OrderStatu
view.inventory = status.inventory.clone();
}
+#[cfg(any())]
fn apply_order_revision_status(view: &mut OrderRevisionProposalView, status: &OrderStatusView) {
view.order_id = status.order_id.clone();
view.locator = order_locator_view_from_status(status);
@@ -2130,6 +2120,7 @@ fn apply_order_revision_status(view: &mut OrderRevisionProposalView, status: &Or
view.issues = status.reducer_issues.clone();
}
+#[cfg(any())]
fn apply_order_revision_decision_status(
view: &mut OrderRevisionDecisionView,
status: &OrderStatusView,
@@ -2259,6 +2250,7 @@ fn order_revision_decision_args_preflight_view(
Some(view)
}
+#[cfg(any())]
fn revised_order_economics(
args: &TradeRevisionProposeArgs,
revision_id: &str,
@@ -2325,6 +2317,7 @@ fn revised_order_economics(
Ok(economics)
}
+#[cfg(any())]
fn revision_adjustment_line(
args: &TradeRevisionProposeArgs,
expected_currency: RadrootsCoreCurrency,
@@ -2489,6 +2482,7 @@ fn sdk_order_decision_actions(push_event: Option<&PushOutboxEventReceipt>) -> Ve
Vec::new()
}
+#[cfg(any())]
fn sdk_enqueued_order_revision_view(
config: &RuntimeConfig,
args: &TradeRevisionProposeArgs,
@@ -2539,6 +2533,7 @@ fn sdk_enqueued_order_revision_view(
view
}
+#[cfg(any())]
fn sdk_enqueued_order_revision_decision_view(
config: &RuntimeConfig,
args: &TradeRevisionDecisionArgs,
@@ -2796,7 +2791,7 @@ fn resolve_order_listing(
)));
}
- let db = ReplicaSql::new(SqliteExecutor::open(&config.local.replica_store_path)?);
+ let db = ReplicaSql::new(SqlxSqliteExecutor::open(&config.local.replica_store_path)?);
let rows = db.trade_product_lookup(listing_lookup)?;
match rows.len() {
0 => Err(RuntimeError::Config(format!(
@@ -2864,7 +2859,7 @@ fn resolve_trade_product_by_listing_addr(
return Ok(None);
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
let product_rows = trade_product::find_many(
&executor,
&ITradeProductFindMany {
@@ -2895,7 +2890,7 @@ fn resolve_active_listing_event_id(
return Ok(None);
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
let product_rows = trade_product::find_many(
&executor,
&ITradeProductFindMany {
@@ -4874,33 +4869,14 @@ fn propose_trade_via_sdk(
let actor = sdk_trade_actor(account, RadrootsActorRole::Buyer, "propose")?;
let publish_mode = trade_publish_mode(config);
let ack_policy = trade_satisfaction_policy(publish_mode)?;
- let economics =
- loaded.document.order.economics.clone().ok_or_else(|| {
- RuntimeError::Config("trade draft is missing quote economics".to_owned())
- })?;
- let items = loaded
- .document
- .order
- .items
- .iter()
- .map(|item| {
- Ok(RadrootsOrderItem {
- bin_id: protocol_inventory_bin_id(item.bin_id.as_str(), "order item bin_id")?,
- bin_count: item.bin_count,
- })
- })
- .collect::<Result<Vec<_>, RuntimeError>>()?;
+ let order_request = canonical_order_request_payload_from_loaded(
+ loaded,
+ account.record.public_identity.public_key_hex.as_str(),
+ )?;
let mut request = TradeProposeRequest::new(
actor,
order_submit_listing_event_ptr(loaded)?,
- protocol_order_id(loaded.document.order.order_id.as_str(), "order_id")?,
- protocol_listing_addr(loaded.document.order.listing_addr.as_str(), "listing_addr")?,
- protocol_pubkey(
- loaded.document.order.seller_pubkey.as_str(),
- "seller_pubkey",
- )?,
- items,
- economics,
+ order_request,
trade_target_policy(),
publish_mode,
ack_policy,
@@ -5483,6 +5459,7 @@ fn next_order_id() -> String {
)
}
+#[cfg(any())]
fn next_revision_id() -> String {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
diff --git a/src/runtime/order/sdk_status.rs b/src/runtime/order/sdk_status.rs
@@ -112,7 +112,7 @@ fn sdk_status_evidence_view(
has_decision: evidence.has_decision,
has_agreement: evidence.has_agreement,
has_validation_receipt: evidence.has_validation_receipt,
- has_pending_revision: evidence.has_pending_revision,
+ has_pending_revision: false,
has_cancellation: evidence.has_cancellation,
has_issues: evidence.has_issues,
}
@@ -148,8 +148,8 @@ fn sdk_status_validation_trust_view(
fn sdk_status_eligibility_view(eligibility: &TradeStatusEligibility) -> OrderStatusEligibilityView {
OrderStatusEligibilityView {
can_decide: eligibility.can_decide,
- can_propose_revision: eligibility.can_propose_revision,
- can_decide_revision: eligibility.can_decide_revision,
+ can_propose_revision: false,
+ can_decide_revision: false,
can_cancel: eligibility.can_cancel,
}
}
@@ -159,8 +159,7 @@ fn sdk_status_next_action(kind: TradeStatusNextActionKind) -> &'static str {
TradeStatusNextActionKind::NoLocalOrder => "no_local_order",
TradeStatusNextActionKind::InspectEvidenceIssues => "inspect_evidence_issues",
TradeStatusNextActionKind::AwaitSellerDecision => "await_seller_decision",
- TradeStatusNextActionKind::DecideRevision => "decide_revision",
- TradeStatusNextActionKind::AwaitRhiValidation => "await_rhi_validation",
+ TradeStatusNextActionKind::AwaitValidation => "await_validation",
TradeStatusNextActionKind::Terminal => "terminal",
_ => "unknown",
}
@@ -171,11 +170,11 @@ fn sdk_order_status_state(status: TradeStatusKind) -> &'static str {
TradeStatusKind::Missing => "missing",
TradeStatusKind::Ambiguous => "ambiguous",
TradeStatusKind::Requested => "requested",
- TradeStatusKind::RevisionProposed => "revision_proposed",
- TradeStatusKind::AgreedPendingRhi => "pending_rhi",
+ TradeStatusKind::AgreedPendingValidation => "pending_validation",
TradeStatusKind::Committed => "committed",
TradeStatusKind::Declined => "declined",
TradeStatusKind::Cancelled => "cancelled",
+ TradeStatusKind::ValidationExpired => "validation_expired",
TradeStatusKind::Invalid => "invalid",
_ => "unknown",
}
@@ -226,11 +225,11 @@ fn sdk_order_status_lifecycle_phase(receipt: &TradeStatusReceipt) -> &'static st
TradeStatusKind::Missing => "missing",
TradeStatusKind::Ambiguous => "ambiguous",
TradeStatusKind::Requested => "requested",
- TradeStatusKind::RevisionProposed => "revision_proposed",
- TradeStatusKind::AgreedPendingRhi => "pending_rhi",
+ TradeStatusKind::AgreedPendingValidation => "pending_validation",
TradeStatusKind::Committed => "committed",
TradeStatusKind::Declined => "declined",
TradeStatusKind::Cancelled => "cancelled",
+ TradeStatusKind::ValidationExpired => "validation_expired",
TradeStatusKind::Invalid => "invalid",
_ => "unknown",
}
@@ -294,7 +293,6 @@ mod tests {
has_decision: false,
has_agreement: false,
has_validation_receipt: false,
- has_pending_revision: false,
has_cancellation: false,
has_issues: false,
},
@@ -302,8 +300,6 @@ mod tests {
online_evidence: None,
eligibility: TradeStatusEligibility {
can_decide: false,
- can_propose_revision: false,
- can_decide_revision: false,
can_cancel: false,
},
next_action: TradeStatusNextActionKind::InspectEvidenceIssues,
@@ -312,7 +308,6 @@ mod tests {
decision_event_id: None,
agreement_event_id: None,
rhi_receipt_event_id: None,
- pending_revision_event_id: None,
cancellation_event_id: None,
last_event_id: None,
issues: Vec::new(),
diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs
@@ -97,24 +97,19 @@ pub fn resolve_write_plane_provider(
publish: &PublishRuntimeView,
) -> WritePlaneProviderView {
let (provider_runtime_id, binding_model, detail) = match config.transport.profile {
- TransportProfileKind::Nostr | TransportProfileKind::Hybrid => (
+ TransportProfileKind::Nostr | TransportProfileKind::MultiTarget => (
config.transport.profile.as_str(),
"nostr_transport",
"configured transport profile publishes through Nostr; readiness is reported under publish",
),
- 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",
+ TransportProfileKind::Reticulum => (
+ "reticulum",
+ "reticulum_transport",
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
),
};
diff --git a/src/runtime/runtime_store.rs b/src/runtime/runtime_store.rs
@@ -11,7 +11,7 @@ use radroots_runtime_store::{
PublishOutboxStatus, RuntimeStore, RuntimeStoreRecord, RuntimeStoreRecordFamily,
RuntimeStoreRecordInput, RuntimeStoreRecordStatus, SourceRuntime,
};
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use serde_json::Value;
use crate::runtime::RuntimeError;
@@ -79,7 +79,7 @@ pub fn list_shared_records_latest(
if !database_path.exists() {
return Ok(Vec::new());
}
- let executor = SqliteExecutor::open(database_path)?;
+ let executor = SqlxSqliteExecutor::open(database_path)?;
let store = RuntimeStore::new(executor);
Ok(store.list_records_changed_latest(limit)?)
}
@@ -94,7 +94,7 @@ pub fn list_shared_records_before(
if !database_path.exists() {
return Ok(Vec::new());
}
- let executor = SqliteExecutor::open(database_path)?;
+ let executor = SqlxSqliteExecutor::open(database_path)?;
let store = RuntimeStore::new(executor);
Ok(store.list_records_changed_before(before_change_seq, before_seq, limit)?)
}
@@ -107,15 +107,16 @@ pub fn get_shared_record(
if !database_path.exists() {
return Ok(None);
}
- let executor = SqliteExecutor::open(database_path)?;
+ let executor = SqlxSqliteExecutor::open(database_path)?;
let store = RuntimeStore::new(executor);
Ok(store.get_record(record_id)?)
}
-fn open_store(config: &RuntimeConfig) -> Result<RuntimeStore<SqliteExecutor>, RuntimeError> {
+fn open_store(config: &RuntimeConfig) -> Result<RuntimeStore<SqlxSqliteExecutor>, RuntimeError> {
let root = shared_runtime_store_root_from_paths(&config.paths)?;
fs::create_dir_all(&root)?;
- let executor = SqliteExecutor::open(shared_runtime_store_db_path_from_paths(&config.paths)?)?;
+ let executor =
+ SqlxSqliteExecutor::open(shared_runtime_store_db_path_from_paths(&config.paths)?)?;
let store = RuntimeStore::new(executor);
store.migrate_up()?;
Ok(store)
diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs
@@ -16,13 +16,13 @@ use radroots_nostr_connect::prelude::{
RadrootsNostrConnectClientTarget, RadrootsNostrConnectError, RadrootsNostrConnectUri,
};
use radroots_sdk::{
- HybridProfile, MeshScopeId, NostrProfile, NostrRelayUrlPolicy, ProxyProfile,
+ MeshScopeId, MultiTargetProfile, NostrProfile, NostrRelayUrlPolicy,
PushOutboxTargetOutcomeKind, PushOutboxTransportOutcomeKind, RadrootsClient,
RadrootsClientBuilder, RadrootsSdkError, RadrootsSdkLocalKeySigner,
RadrootsSdkMycNip46RequestPolicy, RadrootsSdkMycNip46Signer, RadrootsSdkNip46Transport,
RadrootsSdkNip46TransportFuture, RadrootsSdkSignerProvider, RadrootsSdkStorageConfig,
- ReticulumPreviewAgentEndpoint, ReticulumPreviewBehavior as SdkReticulumPreviewBehavior,
- ReticulumPreviewProfile, TargetPolicy, TradeValidationReceiptNostrRelayOutcomeKind,
+ RadrootsdExecutionProfile, ReticulumAgentEndpoint, ReticulumBehavior as SdkReticulumBehavior,
+ ReticulumProfile, TargetPolicy, TradeValidationReceiptNostrRelayOutcomeKind,
TradeValidationReceiptNostrRelayTransportOutcomeKind, TransportProfile,
};
use radroots_transport_nostr::{
@@ -37,8 +37,8 @@ use url::Url;
use crate::runtime::RuntimeError;
use crate::runtime::account;
use crate::runtime::config::{
- CapabilityBindingTargetKind, ReticulumPreviewBehavior, RuntimeConfig,
- SIGNER_REMOTE_NIP46_CAPABILITY, SignerBackend, TransportProfileKind,
+ CapabilityBindingTargetKind, ReticulumBehavior, RuntimeConfig, SIGNER_REMOTE_NIP46_CAPABILITY,
+ SignerBackend, TransportProfileKind,
};
const SDK_STORAGE_DIR_NAME: &str = "sdk";
@@ -79,6 +79,7 @@ pub struct CliSdkConfig {
pub geonames_cache_root: PathBuf,
pub nostr_relay_url_policy: NostrRelayUrlPolicy,
pub transport_profile: TransportProfile,
+ pub radrootsd_execution_profile: Option<RadrootsdExecutionProfile>,
}
impl CliSdkConfig {
@@ -88,6 +89,7 @@ impl CliSdkConfig {
geonames_cache_root: config.paths.shared_cache_root.clone(),
nostr_relay_url_policy: sdk_nostr_relay_url_policy(config),
transport_profile: sdk_transport_profile(config)?,
+ radrootsd_execution_profile: sdk_radrootsd_execution_profile(config)?,
})
}
@@ -97,16 +99,22 @@ impl CliSdkConfig {
geonames_cache_root: config.paths.shared_cache_root.clone(),
nostr_relay_url_policy: sdk_nostr_relay_url_policy(config),
transport_profile: TransportProfile::local_only(),
+ radrootsd_execution_profile: None,
}
}
pub fn builder(&self) -> RadrootsClientBuilder {
- RadrootsClient::builder()
+ let builder = RadrootsClient::builder()
.storage(RadrootsSdkStorageConfig::Directory(
self.storage_root.clone(),
))
.geonames_cache_root(self.geonames_cache_root.clone())
- .transport_profile(self.transport_profile.clone())
+ .transport_profile(self.transport_profile.clone());
+ if let Some(profile) = self.radrootsd_execution_profile.clone() {
+ builder.radrootsd_execution_profile(profile)
+ } else {
+ builder
+ }
}
}
@@ -398,7 +406,7 @@ async fn signer_provider(
request_policy,
)
.map_err(|error| RuntimeError::Config(error.to_string()))?;
- Ok(RadrootsSdkSignerProvider::MycNip46(signer))
+ Ok(RadrootsSdkSignerProvider::MycNip46(Box::new(signer)))
}
}
}
@@ -674,93 +682,106 @@ fn sdk_transport_profile(config: &RuntimeConfig) -> Result<TransportProfile, Run
.map_err(|error| RuntimeError::Config(error.to_string()))?;
Ok(TransportProfile::nostr(profile))
}
- TransportProfileKind::ReticulumPreview => Ok(TransportProfile::reticulum_preview(
- sdk_reticulum_preview_profile(config)?,
- )),
- TransportProfileKind::Hybrid => {
+ TransportProfileKind::Reticulum => {
+ Ok(TransportProfile::reticulum(sdk_reticulum_profile(config)?))
+ }
+ TransportProfileKind::MultiTarget => {
let nostr = NostrProfile::new(
config.transport.nostr_relay_urls.iter().map(String::as_str),
sdk_nostr_relay_url_policy(config),
)
.map_err(|error| RuntimeError::Config(error.to_string()))?;
- Ok(TransportProfile::hybrid(HybridProfile::new(
+ Ok(TransportProfile::multi_target(MultiTargetProfile::new(
nostr,
- sdk_reticulum_preview_profile(config)?,
+ sdk_reticulum_profile(config)?,
)))
}
- TransportProfileKind::Proxy => {
- let profile = ProxyProfile::new(config.transport.proxy.url.clone())
- .with_bearer_token(proxy_bearer_token(config)?);
- Ok(TransportProfile::proxy(profile))
- }
}
}
-fn sdk_reticulum_preview_profile(
- config: &RuntimeConfig,
-) -> Result<ReticulumPreviewProfile, RuntimeError> {
- let behavior = match config.transport.reticulum_preview_behavior {
- ReticulumPreviewBehavior::RejectDeliveryAttempts => {
- SdkReticulumPreviewBehavior::RejectDeliveryAttempts
- }
- ReticulumPreviewBehavior::DeferDeliveryPlans => {
- SdkReticulumPreviewBehavior::DeferDeliveryPlans
- }
+fn sdk_reticulum_profile(config: &RuntimeConfig) -> Result<ReticulumProfile, RuntimeError> {
+ let behavior = match config.transport.reticulum_behavior {
+ ReticulumBehavior::RejectDeliveryAttempts => SdkReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::DeferDeliveryPlans => SdkReticulumBehavior::DeferDeliveryPlans,
};
- let scope = MeshScopeId::parse(config.transport.reticulum_preview_scope.as_str())
+ let scope = MeshScopeId::parse(config.transport.reticulum_scope.as_str())
.map_err(|error| RuntimeError::Config(error.to_string()))?;
- let mut profile = ReticulumPreviewProfile::preview_unavailable()
+ let mut profile = ReticulumProfile::deferred_until_implemented()
.with_behavior(behavior)
.with_scope(scope);
- if let Some(agent_endpoint) = config.transport.reticulum_preview_agent_endpoint.as_ref() {
+ if let Some(agent_endpoint) = config.transport.reticulum_agent_endpoint.as_ref() {
profile = profile.with_agent_endpoint(
- ReticulumPreviewAgentEndpoint::parse(agent_endpoint.as_str())
+ ReticulumAgentEndpoint::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> {
- proxy_bearer_token(config).map(|_| ())
+fn sdk_radrootsd_execution_profile(
+ config: &RuntimeConfig,
+) -> Result<Option<RadrootsdExecutionProfile>, RuntimeError> {
+ if config.transport.radrootsd_execution.token_file.is_none()
+ && config
+ .transport
+ .radrootsd_execution
+ .token_secret_id
+ .is_none()
+ {
+ return Ok(None);
+ }
+ Ok(Some(
+ RadrootsdExecutionProfile::new(config.transport.radrootsd_execution.url.clone())
+ .with_bearer_token(radrootsd_execution_bearer_token(config)?),
+ ))
}
-fn proxy_bearer_token(config: &RuntimeConfig) -> Result<String, RuntimeError> {
- if let Some(path) = config.transport.proxy.token_file.as_ref() {
+fn radrootsd_execution_bearer_token(config: &RuntimeConfig) -> Result<String, RuntimeError> {
+ if let Some(path) = config.transport.radrootsd_execution.token_file.as_ref() {
let token = fs::read_to_string(path).map_err(|error| {
RuntimeError::Config(format!(
- "failed to read proxy token file {}: {error}",
+ "failed to read radrootsd execution token file {}: {error}",
path.display()
))
})?;
- return normalize_proxy_bearer_token(
+ return normalize_radrootsd_execution_bearer_token(
token.as_str(),
- format!("proxy token file {}", path.display()).as_str(),
+ format!("radrootsd execution token file {}", path.display()).as_str(),
);
}
- if let Some(secret_id) = config.transport.proxy.token_secret_id.as_ref() {
+ if let Some(secret_id) = config
+ .transport
+ .radrootsd_execution
+ .token_secret_id
+ .as_ref()
+ {
let vault = account::account_secret_vault(config)?;
let token = vault.load_secret(secret_id).map_err(|error| {
RuntimeError::Config(format!(
- "failed to load proxy token secret `{secret_id}`: {error}"
+ "failed to load radrootsd execution token secret `{secret_id}`: {error}"
))
})?;
let token = token.ok_or_else(|| {
- RuntimeError::Config(format!("proxy token secret `{secret_id}` was not found"))
+ RuntimeError::Config(format!(
+ "radrootsd execution token secret `{secret_id}` was not found"
+ ))
})?;
- return normalize_proxy_bearer_token(
+ return normalize_radrootsd_execution_bearer_token(
token.as_str(),
- format!("proxy token secret `{secret_id}`").as_str(),
+ format!("radrootsd execution token secret `{secret_id}`").as_str(),
);
}
Err(RuntimeError::Config(
- "proxy transport profile requires a configured token file or token secret id".to_owned(),
+ "radrootsd execution requires a configured token file or token secret id".to_owned(),
))
}
-fn normalize_proxy_bearer_token(raw: &str, source: &str) -> Result<String, RuntimeError> {
+fn normalize_radrootsd_execution_bearer_token(
+ raw: &str,
+ source: &str,
+) -> Result<String, RuntimeError> {
let token = raw.trim();
if token.is_empty() {
return Err(RuntimeError::Config(format!("{source} is empty")));
@@ -782,8 +803,8 @@ mod tests {
use radroots_authority::RadrootsEventSigner;
use radroots_sdk::{
- ProxyAuth, PushOutboxTargetOutcomeKind, PushOutboxTransportOutcomeKind, SdkStorageKind,
- StorageStatusRequest, TradeValidationReceiptNostrRelayOutcomeKind,
+ PushOutboxTargetOutcomeKind, PushOutboxTransportOutcomeKind, RadrootsdExecutionAuth,
+ SdkStorageKind, StorageStatusRequest, TradeValidationReceiptNostrRelayOutcomeKind,
TradeValidationReceiptNostrRelayTransportOutcomeKind,
};
use radroots_secret_vault::RadrootsSecretBackend;
@@ -793,7 +814,7 @@ mod tests {
use crate::runtime::config::{
AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig,
LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig,
- ReticulumPreviewBehavior, RhiConfig, RpcConfig, SignerBackend, SignerConfig, Verbosity,
+ ReticulumBehavior, RhiConfig, RpcConfig, SignerBackend, SignerConfig, Verbosity,
};
struct DirectRrRsDependency {
@@ -871,9 +892,9 @@ mod tests {
DirectRrRsDependency {
section: "dependencies",
name: "radroots_mesh",
- owner: "cli-mesh-preview-policy",
- reason: "canonical Reticulum preview admission policy and structured delivery denial reporting",
- lifecycle: "retain while CLI exposes mesh preview status and policy inspection",
+ owner: "cli-mesh-reticulum-admission-policy",
+ reason: "canonical Reticulum admission policy and structured delivery denial reporting",
+ lifecycle: "retain while CLI exposes Reticulum availability status and policy inspection",
},
DirectRrRsDependency {
section: "dependencies",
@@ -1160,7 +1181,7 @@ mod tests {
MigratedCliPathGuard {
label: "trade submit",
path: "src/runtime/order.rs",
- start: "fn propose_trade_via_sdk(",
+ start: "fn canonical_order_request_payload_from_loaded(",
end: "fn sdk_trade_submit_outcome_view(",
required_tokens: &[
"propose_trade_via_sdk",
@@ -1325,38 +1346,34 @@ mod tests {
}
#[test]
- fn maps_hybrid_runtime_config_to_sdk_hybrid_profile() {
+ fn maps_multi_target_runtime_config_to_sdk_multi_target_profile() {
let root = tempdir().expect("tempdir");
let mut config = sample_config(
root.path(),
vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()],
);
- 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());
+ config.transport.profile = TransportProfileKind::MultiTarget;
+ config.transport.reticulum_behavior = ReticulumBehavior::DeferDeliveryPlans;
+ config.transport.reticulum_scope = "farmers_market".to_owned();
+ config.transport.reticulum_agent_endpoint = Some("reticulum-agent:local".to_owned());
let sdk_config = CliSdkConfig::from_runtime_config(&config).expect("sdk config");
- let TransportProfile::Hybrid { profile } = sdk_config.transport_profile else {
- panic!("expected Hybrid transport profile");
+ let TransportProfile::MultiTarget { profile } = sdk_config.transport_profile else {
+ panic!("expected multi-target transport profile");
};
assert_eq!(
profile.nostr().relay_urls(),
vec!["wss://relay.one".to_owned(), "wss://relay.two".to_owned()]
);
assert_eq!(
- profile.reticulum_preview().behavior().as_str(),
+ profile.reticulum().behavior().as_str(),
"defer_delivery_plans"
);
- assert_eq!(
- profile.reticulum_preview().scope().as_str(),
- "farmers_market"
- );
+ assert_eq!(profile.reticulum().scope().as_str(), "farmers_market");
assert_eq!(
profile
- .reticulum_preview()
+ .reticulum()
.agent_endpoint()
.expect("agent endpoint")
.as_str(),
@@ -1365,72 +1382,77 @@ mod tests {
}
#[test]
- fn maps_proxy_token_file_to_sdk_profile_auth() {
+ fn maps_radrootsd_execution_token_file_to_sdk_profile_auth() {
let root = tempdir().expect("tempdir");
let mut config = sample_config(root.path(), Vec::new());
- let token_file = root.path().join("proxy.token");
- fs::write(&token_file, "proxy-file-token\n").expect("write token file");
- config.transport.profile = TransportProfileKind::Proxy;
- config.transport.proxy.url = "http://127.0.0.1:7070".to_owned();
- config.transport.proxy.token_file = Some(token_file);
+ let token_file = root.path().join("radrootsd_execution.token");
+ fs::write(&token_file, "radrootsd-execution-file-token\n").expect("write token file");
+ config.transport.radrootsd_execution.url = "http://127.0.0.1:7070".to_owned();
+ config.transport.radrootsd_execution.token_file = Some(token_file);
let sdk_config = CliSdkConfig::from_runtime_config(&config).expect("sdk config");
- let TransportProfile::Proxy { profile } = sdk_config.transport_profile else {
- panic!("expected proxy transport profile");
- };
+ assert!(matches!(
+ sdk_config.transport_profile,
+ TransportProfile::LocalOnly
+ ));
+ let profile = sdk_config
+ .radrootsd_execution_profile
+ .expect("radrootsd execution profile");
assert_eq!(profile.endpoint_url(), "http://127.0.0.1:7070");
assert_eq!(
profile.auth(),
- &ProxyAuth::BearerToken("proxy-file-token".to_owned())
+ &RadrootsdExecutionAuth::BearerToken("radrootsd-execution-file-token".to_owned())
);
}
#[test]
- fn maps_proxy_token_secret_id_to_sdk_profile_auth() {
+ fn maps_radrootsd_execution_token_secret_id_to_sdk_profile_auth() {
let root = tempdir().expect("tempdir");
let mut config = sample_config(root.path(), Vec::new());
- config.transport.profile = TransportProfileKind::Proxy;
- config.transport.proxy.url = "http://127.0.0.1:7070".to_owned();
- config.transport.proxy.token_secret_id = Some("proxy_token".to_owned());
+ config.transport.radrootsd_execution.url = "http://127.0.0.1:7070".to_owned();
+ config.transport.radrootsd_execution.token_secret_id =
+ Some("radrootsd_execution_token".to_owned());
let vault = account::account_secret_vault(&config).expect("account vault");
vault
- .store_secret("proxy_token", "proxy-secret-token")
- .expect("store proxy token");
+ .store_secret(
+ "radrootsd_execution_token",
+ "radrootsd-execution-secret-token",
+ )
+ .expect("store radrootsd execution token");
let sdk_config = CliSdkConfig::from_runtime_config(&config).expect("sdk config");
- let TransportProfile::Proxy { profile } = sdk_config.transport_profile else {
- panic!("expected proxy transport profile");
- };
+ let profile = sdk_config
+ .radrootsd_execution_profile
+ .expect("radrootsd execution profile");
assert_eq!(
profile.auth(),
- &ProxyAuth::BearerToken("proxy-secret-token".to_owned())
+ &RadrootsdExecutionAuth::BearerToken("radrootsd-execution-secret-token".to_owned())
);
}
#[test]
- fn proxy_sdk_profile_requires_materialized_bearer_token() {
+ fn radrootsd_execution_profile_requires_materialized_bearer_token() {
let root = tempdir().expect("tempdir");
let mut config = sample_config(root.path(), Vec::new());
- config.transport.profile = TransportProfileKind::Proxy;
- config.transport.proxy.url = "http://127.0.0.1:7070".to_owned();
+ config.transport.radrootsd_execution.url = "http://127.0.0.1:7070".to_owned();
- assert!(matches!(
- CliSdkConfig::from_runtime_config(&config),
- Err(RuntimeError::Config(message))
- if message.contains("configured token file or token secret id")
- ));
+ let sdk_config = CliSdkConfig::from_runtime_config(&config).expect("sdk config");
+ assert!(sdk_config.radrootsd_execution_profile.is_none());
}
#[test]
- fn proxy_token_resolution_rejects_empty_or_header_unsafe_tokens() {
+ fn radrootsd_execution_token_resolution_rejects_empty_or_header_unsafe_tokens() {
assert!(matches!(
- normalize_proxy_bearer_token(" \n", "proxy token"),
+ normalize_radrootsd_execution_bearer_token(" \n", "radrootsd execution token"),
Err(RuntimeError::Config(message)) if message.contains("empty")
));
assert!(matches!(
- normalize_proxy_bearer_token("proxy\nsecret", "proxy token"),
+ normalize_radrootsd_execution_bearer_token(
+ "radrootsd\nexecution",
+ "radrootsd execution token"
+ ),
Err(RuntimeError::Config(message)) if message.contains("control characters")
));
}
@@ -1597,10 +1619,6 @@ mod tests {
PushOutboxTargetOutcomeKind::DeferredUntilImplemented,
"deferred_until_implemented",
),
- (
- PushOutboxTargetOutcomeKind::PreviewUnavailable,
- "preview_unavailable",
- ),
(PushOutboxTargetOutcomeKind::Unknown, "unknown"),
] {
assert_eq!(sdk_target_outcome_kind_label(kind), label);
diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs
@@ -11,7 +11,7 @@ use crate::view::runtime::{
};
use radroots_event::kinds::{
KIND_FARM, KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST,
- KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, KIND_PROFILE,
+ KIND_PROFILE,
};
use radroots_nostr_accounts::prelude::RadrootsNostrAccountStatus;
use radroots_nostr_connect::prelude::RadrootsNostrConnectPermissions;
@@ -294,7 +294,7 @@ fn disabled_binding_status() -> SignerBindingStatusView {
}
}
-fn cli_write_kinds() -> [CliWriteKind; 12] {
+fn cli_write_kinds() -> [CliWriteKind; 9] {
[
CliWriteKind {
command: "sync.push",
@@ -332,18 +332,6 @@ fn cli_write_kinds() -> [CliWriteKind; 12] {
command: "trade.cancel",
event_kind: KIND_ORDER_CANCELLATION,
},
- CliWriteKind {
- command: "trade.revision.propose",
- event_kind: KIND_ORDER_REVISION_PROPOSAL,
- },
- CliWriteKind {
- command: "trade.revision.accept",
- event_kind: KIND_ORDER_REVISION_DECISION,
- },
- CliWriteKind {
- command: "trade.revision.decline",
- event_kind: KIND_ORDER_REVISION_DECISION,
- },
]
}
@@ -595,8 +583,7 @@ fn sign_event_permission_for_kind(event_kind: u32) -> String {
mod tests {
use super::{
KIND_FARM, KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST,
- KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, KIND_PROFILE, cli_write_kinds,
- myc_managed_account_ref_matches, myc_write_kind_readiness,
+ KIND_PROFILE, cli_write_kinds, myc_managed_account_ref_matches, myc_write_kind_readiness,
myc_write_kind_readiness_for_permissions, sign_event_permission_for_kind,
};
use radroots_nostr_connect::prelude::{
@@ -624,9 +611,6 @@ mod tests {
"trade.accept",
"trade.decline",
"trade.cancel",
- "trade.revision.propose",
- "trade.revision.accept",
- "trade.revision.decline",
]
);
assert!(!commands.contains(&"signer.status.get"));
@@ -657,27 +641,6 @@ mod tests {
}
#[test]
- fn order_revision_readiness_uses_active_revision_kinds() {
- let proposal = cli_write_kinds()
- .into_iter()
- .find(|kind| kind.command == "trade.revision.propose")
- .expect("order revision propose readiness");
-
- assert_eq!(proposal.event_kind, KIND_ORDER_REVISION_PROPOSAL);
- assert_ne!(proposal.event_kind, RESERVED_ORDER_KIND_3431);
-
- for command in ["trade.revision.accept", "trade.revision.decline"] {
- let write_kind = cli_write_kinds()
- .into_iter()
- .find(|kind| kind.command == command)
- .expect("order revision decision readiness");
-
- assert_eq!(write_kind.event_kind, KIND_ORDER_REVISION_DECISION);
- assert_ne!(write_kind.event_kind, RESERVED_ORDER_KIND_3431);
- }
- }
-
- #[test]
fn order_cancel_readiness_uses_order_cancellation_kind() {
let cancel = cli_write_kinds()
.into_iter()
diff --git a/src/runtime/store.rs b/src/runtime/store.rs
@@ -10,7 +10,7 @@ use radroots_sdk::{
SdkOutboxStorageStatus, SdkRestoreState, SdkSqliteStoreStatus, SdkStorageKind,
StorageStatusReceipt, StorageStatusRequest,
};
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use serde::Serialize;
use serde_json::{Value, json};
@@ -30,13 +30,14 @@ const SDK_CANONICAL_SOURCE: &str = "SDK canonical event store and outbox";
const SDK_CANONICAL_STORE: &str = "sdk";
const SDK_BACKUP_KIND: &str = "sdk_canonical";
const SDK_BACKUP_MANIFEST_FILE: &str = "manifest.json";
-const SDK_EVENT_STORE_FILE: &str = "event_store.sqlite";
-const SDK_OUTBOX_FILE: &str = "outbox.sqlite";
+const SDK_RUNTIME_FILE: &str = "runtime.sqlite";
+const SDK_PRIVATE_FILE: &str = "private.sqlite";
+const SDK_STUDIO_FILE: &str = "studio.sqlite";
pub fn init(config: &RuntimeConfig) -> Result<LocalInitView, RuntimeError> {
let existed = config.local.replica_store_path.exists();
ensure_local_roots(config)?;
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
ensure_sync_run_table(&executor)?;
let manifest = export_manifest(&executor)?;
@@ -59,7 +60,7 @@ pub fn init(config: &RuntimeConfig) -> Result<LocalInitView, RuntimeError> {
pub fn init_preflight(config: &RuntimeConfig) -> Result<LocalInitView, RuntimeError> {
validate_local_roots(config)?;
if config.local.replica_store_path.exists() {
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
ensure_sync_run_table(&executor)?;
let manifest = export_manifest(&executor)?;
return Ok(LocalInitView {
@@ -129,7 +130,7 @@ fn derived_projection_status(
});
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
ensure_sync_run_table(&executor)?;
let manifest = export_manifest(&executor)?;
let sync = radroots_replica_sync_status(&executor)?;
@@ -178,8 +179,8 @@ pub fn backup_preflight(
canonical_store: SDK_CANONICAL_STORE.to_owned(),
destination: output.display().to_string(),
file: output.join(SDK_BACKUP_MANIFEST_FILE).display().to_string(),
- event_store_file: Some(output.join(SDK_EVENT_STORE_FILE).display().to_string()),
- outbox_file: Some(output.join(SDK_OUTBOX_FILE).display().to_string()),
+ event_store_file: Some(output.join(SDK_RUNTIME_FILE).display().to_string()),
+ outbox_file: Some(output.join(SDK_RUNTIME_FILE).display().to_string()),
manifest_file: Some(output.join(SDK_BACKUP_MANIFEST_FILE).display().to_string()),
size_bytes: 0,
manifest,
@@ -232,7 +233,7 @@ pub fn export(
ensure_safe_output_path(config, output)?;
create_parent_dir(output)?;
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
let manifest = export_manifest(&executor)?;
let sync = radroots_replica_sync_status(&executor)?;
let records = match format {
@@ -338,7 +339,9 @@ fn validate_directory_target(path: &Path) -> Result<(), RuntimeError> {
}
fn sdk_storage_files_exist(sdk_root: &Path) -> bool {
- sdk_root.join(SDK_EVENT_STORE_FILE).exists() && sdk_root.join(SDK_OUTBOX_FILE).exists()
+ sdk_root.join(SDK_RUNTIME_FILE).exists()
+ && sdk_root.join(SDK_PRIVATE_FILE).exists()
+ && sdk_root.join(SDK_STUDIO_FILE).exists()
}
fn sdk_status_view(
@@ -349,14 +352,10 @@ fn sdk_status_view(
integrity: IntegrityReceipt,
derived_projection: LocalDerivedProjectionStatusView,
) -> LocalStatusView {
- let event_store_path = receipt
+ let runtime_path = receipt
.paths
.as_ref()
- .map(|paths| paths.event_store_path.display().to_string());
- let outbox_path = receipt
- .paths
- .as_ref()
- .map(|paths| paths.outbox_path.display().to_string());
+ .map(|paths| paths.runtime_path.display().to_string());
let state = sdk_status_state(&receipt, &integrity).to_owned();
let reason = sdk_status_reason(&state);
let actions = sdk_status_actions(&state);
@@ -368,8 +367,8 @@ fn sdk_status_view(
sdk_storage: sdk_storage_kind_label(receipt.storage).to_owned(),
sdk_root: sdk_root.display().to_string(),
sdk_existed_before_open,
- event_store: sdk_event_store_status_view(receipt.event_store, event_store_path),
- outbox: sdk_outbox_status_view(receipt.outbox, outbox_path),
+ event_store: sdk_event_store_status_view(receipt.event_store, runtime_path.clone()),
+ outbox: sdk_outbox_status_view(receipt.outbox, runtime_path),
integrity: sdk_integrity_view(integrity),
derived_projection,
reason,
@@ -430,7 +429,6 @@ fn sdk_outbox_status_view(
retryable_events: status.retryable_events,
terminal_events: status.terminal_events,
failed_terminal_events: status.failed_terminal_events,
- preview_unavailable_events: status.preview_unavailable_events,
deferred_until_implemented_events: status.deferred_until_implemented_events,
ready_signed_events: status.ready_signed_events,
publishing_events: status.publishing_events,
@@ -469,13 +467,15 @@ fn ensure_safe_sdk_backup_destination(
output: &Path,
) -> Result<(), RuntimeError> {
let sdk_root = sdk_storage_root(config);
- let sdk_event_store_path = sdk_root.join(SDK_EVENT_STORE_FILE);
- let sdk_outbox_path = sdk_root.join(SDK_OUTBOX_FILE);
+ let sdk_runtime_path = sdk_root.join(SDK_RUNTIME_FILE);
+ let sdk_private_path = sdk_root.join(SDK_PRIVATE_FILE);
+ let sdk_studio_path = sdk_root.join(SDK_STUDIO_FILE);
let forbidden_paths = [
sdk_root.as_path(),
config.local.replica_store_path.as_path(),
- sdk_event_store_path.as_path(),
- sdk_outbox_path.as_path(),
+ sdk_runtime_path.as_path(),
+ sdk_private_path.as_path(),
+ sdk_studio_path.as_path(),
];
if forbidden_paths.iter().any(|forbidden| output == *forbidden) {
return Err(RuntimeError::Config(format!(
@@ -497,13 +497,15 @@ fn ensure_safe_sdk_restore_destination(
destination: &Path,
) -> Result<(), RuntimeError> {
let sdk_root = sdk_storage_root(config);
- let sdk_event_store_path = sdk_root.join(SDK_EVENT_STORE_FILE);
- let sdk_outbox_path = sdk_root.join(SDK_OUTBOX_FILE);
+ let sdk_runtime_path = sdk_root.join(SDK_RUNTIME_FILE);
+ let sdk_private_path = sdk_root.join(SDK_PRIVATE_FILE);
+ let sdk_studio_path = sdk_root.join(SDK_STUDIO_FILE);
let forbidden_paths = [
config.local.root.as_path(),
config.local.replica_store_path.as_path(),
- sdk_event_store_path.as_path(),
- sdk_outbox_path.as_path(),
+ sdk_runtime_path.as_path(),
+ sdk_private_path.as_path(),
+ sdk_studio_path.as_path(),
];
if forbidden_paths
.iter()
@@ -527,11 +529,12 @@ fn ensure_safe_sdk_restore_destination(
}
fn sdk_backup_view(receipt: BackupReceipt) -> Result<LocalBackupView, CliSdkAdapterError> {
- let event_store_file = receipt.event_store_path.as_ref().map(display_path);
- let outbox_file = receipt.outbox_path.as_ref().map(display_path);
+ let event_store_file = receipt.runtime_path.as_ref().map(display_path);
+ let outbox_file = receipt.runtime_path.as_ref().map(display_path);
let manifest_file = receipt.manifest_path.as_ref().map(display_path);
- let size_bytes = path_size(receipt.event_store_path.as_ref())?
- + path_size(receipt.outbox_path.as_ref())?
+ let size_bytes = path_size(receipt.runtime_path.as_ref())?
+ + path_size(receipt.private_path.as_ref())?
+ + path_size(receipt.studio_path.as_ref())?
+ path_size(receipt.manifest_path.as_ref())?;
Ok(LocalBackupView {
state: sdk_backup_state_label(receipt.state).to_owned(),
@@ -570,15 +573,14 @@ fn sdk_restore_view(
.as_ref()
.map(display_path)
.unwrap_or_default(),
- event_store_file: display_path(&receipt.event_store_path),
- outbox_file: display_path(&receipt.outbox_path),
+ event_store_file: display_path(&receipt.runtime_path),
+ outbox_file: display_path(&receipt.runtime_path),
manifest_file: display_path(&receipt.manifest_path),
destination_event_store_file: destination_paths
- .map(|paths| display_path(&paths.event_store_path)),
- destination_outbox_file: destination_paths.map(|paths| display_path(&paths.outbox_path)),
- restored_event_store_file: restored_paths
- .map(|paths| display_path(&paths.event_store_path)),
- restored_outbox_file: restored_paths.map(|paths| display_path(&paths.outbox_path)),
+ .map(|paths| display_path(&paths.runtime_path)),
+ destination_outbox_file: destination_paths.map(|paths| display_path(&paths.runtime_path)),
+ restored_event_store_file: restored_paths.map(|paths| display_path(&paths.runtime_path)),
+ restored_outbox_file: restored_paths.map(|paths| display_path(&paths.runtime_path)),
manifest: json_value(&receipt.manifest)?,
verification: json_value(&receipt.verification)?,
overwrite,
@@ -616,8 +618,9 @@ fn sdk_backup_manifest_preview(
"source_storage": sdk_storage_kind_label(status.storage),
"source_paths": &status.paths,
"backup_paths": {
- "event_store_path": output.join(SDK_EVENT_STORE_FILE).display().to_string(),
- "outbox_path": output.join(SDK_OUTBOX_FILE).display().to_string(),
+ "runtime_path": output.join(SDK_RUNTIME_FILE).display().to_string(),
+ "private_path": output.join(SDK_PRIVATE_FILE).display().to_string(),
+ "studio_path": output.join(SDK_STUDIO_FILE).display().to_string(),
},
"source_status": status,
"backup_verification": {
diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs
@@ -20,9 +20,10 @@ use radroots_sdk::{
PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt, PushOutboxRequest,
PushOutboxTargetOutcomeKind, PushOutboxTargetReceipt, SyncStatusReceipt, SyncStatusRequest,
};
-use radroots_sql_core::{SqlExecutor, SqliteExecutor};
+use radroots_sql_core::{SqlExecutor, SqlxSqliteExecutor};
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RadrootsTransportCapabilityAvailability, RadrootsTransportCapabilityMaturity,
RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportStatus,
RadrootsTransportTarget,
};
@@ -257,7 +258,7 @@ where
let failed_transport_targets = relay_failures(receipt.failed_relays);
let reason = relay_failure_reason(&failed_transport_targets);
let failure_reason = format!("Nostr transport fetch failed: {reason}");
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
record_sync_run(
&executor,
@@ -282,7 +283,7 @@ where
Ok(receipt) => receipt,
Err(error) => {
let failure_reason = error.to_string();
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
record_sync_run(
&executor,
@@ -305,7 +306,7 @@ where
}
};
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
let ingest = ingest_events(&executor, &receipt, scope)?;
record_sync_run(
@@ -612,6 +613,8 @@ fn sdk_transport_status_view(
endpoint_uri: status.endpoint_uri.clone(),
configured: status.configured,
implementation: status.implementation.clone(),
+ maturity: status.maturity.clone(),
+ availability: status.availability.clone(),
usable_for_delivery: status.usable_for_delivery,
capabilities: TransportOperationCapabilitiesView {
deliver: status.capabilities.deliver,
@@ -634,7 +637,7 @@ fn sdk_sync_status_actions(receipt: &SyncStatusReceipt) -> Vec<String> {
fn sdk_sync_push_actions(state: &str, retryable: bool) -> Vec<String> {
match state {
- "published" | "ready" | "preview_unavailable" | "deferred_until_implemented" => {
+ "published" | "ready" | "deferred_until_implemented" => {
vec!["radroots sync status get".to_owned()]
}
"dry_run" | "partial" | "unavailable" if retryable => {
@@ -652,7 +655,6 @@ fn sdk_sync_push_reason_code(state: &str) -> Option<&'static str> {
"dry_run" => Some("dry_run"),
"partial" => Some("sdk_outbox_push_partial"),
"unavailable" => Some("sdk_outbox_push_failed"),
- "preview_unavailable" => Some("sdk_outbox_push_preview_unavailable"),
"deferred_until_implemented" => Some("sdk_outbox_push_deferred_until_implemented"),
_ => None,
}
@@ -660,7 +662,7 @@ fn sdk_sync_push_reason_code(state: &str) -> Option<&'static str> {
fn sdk_push_state(receipt: &PushOutboxReceipt, failed_count: usize) -> &'static str {
if receipt.attempted_events == 0 {
- return sdk_push_reported_preview_state(receipt).unwrap_or("ready");
+ return sdk_push_reported_deferred_state(receipt).unwrap_or("ready");
}
if receipt.published_events > 0 && failed_count > 0 {
"partial"
@@ -673,19 +675,15 @@ fn sdk_push_state(receipt: &PushOutboxReceipt, failed_count: usize) -> &'static
}
}
-fn sdk_push_reported_preview_state(receipt: &PushOutboxReceipt) -> Option<&'static str> {
+fn sdk_push_reported_deferred_state(receipt: &PushOutboxReceipt) -> Option<&'static str> {
let mut deferred = false;
for event in &receipt.events {
match event.final_state {
- PushOutboxEventState::PreviewUnavailable => return Some("preview_unavailable"),
PushOutboxEventState::DeferredUntilImplemented => deferred = true,
_ => {}
}
for target in &event.targets {
match target.outcome_kind {
- PushOutboxTargetOutcomeKind::PreviewUnavailable => {
- return Some("preview_unavailable");
- }
PushOutboxTargetOutcomeKind::DeferredUntilImplemented => deferred = true,
_ => {}
}
@@ -696,15 +694,12 @@ fn sdk_push_reported_preview_state(receipt: &PushOutboxReceipt) -> Option<&'stat
fn sdk_push_reason(receipt: &PushOutboxReceipt, failed_count: usize) -> Option<String> {
if receipt.attempted_events == 0 {
- if let Some(state) = sdk_push_reported_preview_state(receipt) {
+ if let Some(state) = sdk_push_reported_deferred_state(receipt) {
return Some(match state {
- "preview_unavailable" => {
- "SDK outbox push reported Reticulum preview work as preview unavailable without network delivery"
- }
"deferred_until_implemented" => {
- "SDK outbox push reported Reticulum preview work as deferred until implemented without network delivery"
+ "SDK outbox push reported Reticulum work as deferred until implemented without network delivery"
}
- _ => "SDK outbox push reported Reticulum preview work without network delivery",
+ _ => "SDK outbox push reported Reticulum work without network delivery",
}
.to_owned());
}
@@ -738,7 +733,6 @@ fn sdk_sync_queue(receipt: &SyncStatusReceipt) -> SyncQueueView {
retryable_count: Some(usize_from_i64(receipt.outbox.retryable_events)),
terminal_count: Some(usize_from_i64(receipt.outbox.terminal_events)),
failed_terminal_count: Some(usize_from_i64(receipt.outbox.failed_terminal_events)),
- preview_unavailable_count: Some(usize_from_i64(receipt.outbox.preview_unavailable_events)),
deferred_until_implemented_count: Some(usize_from_i64(
receipt.outbox.deferred_until_implemented_events,
)),
@@ -757,7 +751,6 @@ fn derived_projection_sync_queue(expected_count: usize, pending_count: usize) ->
retryable_count: None,
terminal_count: None,
failed_terminal_count: None,
- preview_unavailable_count: None,
deferred_until_implemented_count: None,
ready_signed_count: None,
publishing_count: None,
@@ -887,13 +880,13 @@ fn sync_configured_transport_targets(
)?);
}
}
- TransportProfileKind::ReticulumPreview => {
+ TransportProfileKind::Reticulum => {
targets.push(sync_transport_target_view(
RadrootsTransportKind::Reticulum,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
+ RADROOTS_RETICULUM_ENDPOINT_URI,
)?);
}
- TransportProfileKind::Hybrid => {
+ TransportProfileKind::MultiTarget => {
for relay_url in &config.transport.nostr_relay_urls {
targets.push(sync_transport_target_view(
RadrootsTransportKind::Nostr,
@@ -902,13 +895,7 @@ fn sync_configured_transport_targets(
}
targets.push(sync_transport_target_view(
RadrootsTransportKind::Reticulum,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
- )?);
- }
- TransportProfileKind::Proxy => {
- targets.push(sync_transport_target_view(
- RadrootsTransportKind::Proxy,
- config.transport.proxy.url.as_str(),
+ RADROOTS_RETICULUM_ENDPOINT_URI,
)?);
}
}
@@ -922,17 +909,9 @@ fn sync_transport_target_view(
let target = match kind {
RadrootsTransportKind::Nostr => RadrootsTransportTarget::nostr_relay(endpoint_uri),
RadrootsTransportKind::Reticulum => {
- if endpoint_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
- Err(radroots_transport::RadrootsTransportError::InvalidTargetUri)
- } else {
- RadrootsTransportTarget::reticulum_preview()
- }
+ RadrootsTransportTarget::reticulum_with_metadata(endpoint_uri, None, None)
}
RadrootsTransportKind::Local => RadrootsTransportTarget::local(endpoint_uri),
- RadrootsTransportKind::Proxy => RadrootsTransportTarget::proxy(endpoint_uri),
- RadrootsTransportKind::Mesh | RadrootsTransportKind::Custom(_) => {
- RadrootsTransportTarget::new(kind, endpoint_uri)
- }
}
.map_err(|error| RuntimeError::Config(format!("invalid transport target: {error}")))?;
Ok(SyncTransportTargetView {
@@ -964,37 +943,18 @@ fn sync_transport_statuses(config: &RuntimeConfig) -> Vec<SyncTransportStatusVie
nostr_targets_configured,
))]
}
- TransportProfileKind::ReticulumPreview => {
- vec![sync_transport_status_view(
- reticulum_preview_transport_status(profile_id),
- )]
+ TransportProfileKind::Reticulum => {
+ vec![sync_transport_status_view(reticulum_transport_status(
+ profile_id,
+ ))]
}
- TransportProfileKind::Hybrid => vec![
+ TransportProfileKind::MultiTarget => vec![
sync_transport_status_view(nostr_transport_status(
profile_id,
nostr_targets_configured,
)),
- sync_transport_status_view(reticulum_preview_transport_status(profile_id)),
+ sync_transport_status_view(reticulum_transport_status(profile_id)),
],
- TransportProfileKind::Proxy => {
- let auth_configured = config.transport.proxy.token_file.is_some()
- || config.transport.proxy.token_secret_id.is_some();
- vec![sync_transport_status_view(
- RadrootsTransportStatus::new(
- RadrootsTransportKind::Proxy,
- auth_configured,
- RadrootsTransportImplementationState::Real,
- auth_configured,
- if auth_configured {
- "Proxy transport delegates delivery to the configured endpoint"
- } else {
- "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()),
- )]
- }
}
}
@@ -1013,16 +973,18 @@ fn nostr_transport_status(profile_id: &str, targets_configured: bool) -> Radroot
.with_profile_id(profile_id)
}
-fn reticulum_preview_transport_status(profile_id: &str) -> RadrootsTransportStatus {
+fn reticulum_transport_status(profile_id: &str) -> RadrootsTransportStatus {
RadrootsTransportStatus::new(
RadrootsTransportKind::Reticulum,
true,
- RadrootsTransportImplementationState::PreviewUnavailable,
+ RadrootsTransportImplementationState::Real,
false,
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
)
.with_profile_id(profile_id)
- .with_endpoint_uri(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
+ .with_endpoint_uri(RADROOTS_RETICULUM_ENDPOINT_URI)
+ .with_maturity(RadrootsTransportCapabilityMaturity::Preview)
+ .with_availability(RadrootsTransportCapabilityAvailability::Unavailable)
}
fn sync_transport_status_view(status: RadrootsTransportStatus) -> SyncTransportStatusView {
@@ -1032,6 +994,8 @@ fn sync_transport_status_view(status: RadrootsTransportStatus) -> SyncTransportS
endpoint_uri: status.endpoint_uri,
configured: status.configured,
implementation: transport_implementation_label(status.implementation).to_owned(),
+ maturity: transport_maturity_label(status.maturity).to_owned(),
+ availability: transport_availability_label(status.availability).to_owned(),
usable_for_delivery: status.usable_for_delivery,
capabilities: TransportOperationCapabilitiesView {
deliver: status.capabilities.deliver,
@@ -1045,7 +1009,23 @@ fn transport_implementation_label(state: RadrootsTransportImplementationState) -
match state {
RadrootsTransportImplementationState::Real => "real",
RadrootsTransportImplementationState::Mock => "mock",
- RadrootsTransportImplementationState::PreviewUnavailable => "preview_unavailable",
+ }
+}
+
+fn transport_maturity_label(maturity: RadrootsTransportCapabilityMaturity) -> &'static str {
+ match maturity {
+ RadrootsTransportCapabilityMaturity::Preview => "preview",
+ RadrootsTransportCapabilityMaturity::Stable => "stable",
+ }
+}
+
+fn transport_availability_label(
+ availability: RadrootsTransportCapabilityAvailability,
+) -> &'static str {
+ match availability {
+ RadrootsTransportCapabilityAvailability::Available => "available",
+ RadrootsTransportCapabilityAvailability::Degraded => "degraded",
+ RadrootsTransportCapabilityAvailability::Unavailable => "unavailable",
}
}
@@ -1068,7 +1048,7 @@ fn inspect_sync(config: &RuntimeConfig) -> Result<SyncSnapshot, RuntimeError> {
});
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
let queue = radroots_replica_sync_status(&executor)?;
let freshness =
@@ -1147,7 +1127,7 @@ pub(crate) fn freshness_for_scope(
config: &RuntimeConfig,
scope: RelayIngestScope,
) -> Result<SyncFreshnessView, RuntimeError> {
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
freshness_for_scope_from_executor(config, &executor, scope)
}
@@ -1159,7 +1139,7 @@ pub(crate) fn relay_provenance_relays_for_scope(
if !config.local.replica_store_path.exists() {
return Ok(Vec::new());
}
- let executor = SqliteExecutor::open(&config.local.replica_store_path)?;
+ let executor = SqlxSqliteExecutor::open(&config.local.replica_store_path)?;
migrations::run_all_up(&executor)?;
ensure_sync_run_table(&executor)?;
let current_fingerprint = relay_set_fingerprint(&config.transport.nostr_relay_urls);
@@ -1178,7 +1158,7 @@ pub(crate) fn relay_provenance_relays_for_scope(
pub(crate) fn freshness_for_scope_from_executor(
config: &RuntimeConfig,
- executor: &SqliteExecutor,
+ executor: &SqlxSqliteExecutor,
scope: RelayIngestScope,
) -> Result<SyncFreshnessView, RuntimeError> {
let last_event_at = ReplicaSql::new(executor).nostr_event_last_created_at()?;
@@ -1305,7 +1285,7 @@ fn sync_run_freshness_view(
}
}
-pub(crate) fn ensure_sync_run_table(executor: &SqliteExecutor) -> Result<(), RuntimeError> {
+pub(crate) fn ensure_sync_run_table(executor: &SqlxSqliteExecutor) -> Result<(), RuntimeError> {
executor.exec(
"CREATE TABLE IF NOT EXISTS radroots_cli_sync_run (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1332,7 +1312,7 @@ pub(crate) fn ensure_sync_run_table(executor: &SqliteExecutor) -> Result<(), Run
}
fn latest_sync_run(
- executor: &SqliteExecutor,
+ executor: &SqlxSqliteExecutor,
scope: RelayIngestScope,
) -> Result<Option<SyncRunRecord>, RuntimeError> {
let rows = executor.query_raw(
@@ -1381,7 +1361,10 @@ fn sync_run_record_from_row(row: SyncRunRow) -> SyncRunRecord {
}
}
-fn record_sync_run(executor: &SqliteExecutor, record: &SyncRunRecord) -> Result<(), RuntimeError> {
+fn record_sync_run(
+ executor: &SqlxSqliteExecutor,
+ record: &SyncRunRecord,
+) -> Result<(), RuntimeError> {
ensure_sync_run_table(executor)?;
executor.exec(
&format!(
@@ -1657,7 +1640,7 @@ impl RelayIngestScope {
}
fn ingest_events(
- executor: &SqliteExecutor,
+ executor: &SqlxSqliteExecutor,
receipt: &RadrootsRelayFetchedEventsReceipt,
scope: RelayIngestScope,
) -> Result<RelayIngestCounts, RuntimeError> {
@@ -1801,7 +1784,7 @@ mod tests {
};
use radroots_secret_vault::RadrootsSecretBackend;
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
};
use radroots_transport_nostr::{
RadrootsRelayFetchFailure, RadrootsRelayFetchedEvent, RadrootsRelayFetchedEventsReceipt,
@@ -1818,7 +1801,7 @@ mod tests {
use crate::runtime::config::{
AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig,
LocalConfig, LoggingConfig, MycConfig, OutputConfig, OutputFormat, PathsConfig,
- ReticulumPreviewBehavior, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig,
+ ReticulumBehavior, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig,
TransportProfileKind, Verbosity,
};
@@ -1903,12 +1886,13 @@ mod tests {
}
#[test]
- fn sync_pull_hybrid_reports_profile_targets_and_nostr_fetch_detail() {
+ fn sync_pull_multi_target_reports_profile_targets_and_nostr_fetch_detail() {
let dir = tempdir().expect("tempdir");
- let config = hybrid_config(dir.path(), vec!["wss://relay.example.com".to_owned()]);
+ let config = multi_target_config(dir.path(), vec!["wss://relay.example.com".to_owned()]);
crate::runtime::store::init(&config).expect("store init");
- let view = pull_with_fetcher(&config, fake_fetcher(Vec::new())).expect("sync pull hybrid");
+ let view =
+ pull_with_fetcher(&config, fake_fetcher(Vec::new())).expect("sync pull multi_target");
assert_eq!(view.state, "ready");
assert_eq!(view.configured_transport_target_count, 2);
@@ -1929,10 +1913,9 @@ mod tests {
assert!(!view.transport_statuses[0].capabilities.fetch);
assert_eq!(view.transport_statuses[1].transport, "reticulum");
assert!(view.transport_statuses[1].configured);
- assert_eq!(
- view.transport_statuses[1].implementation,
- "preview_unavailable"
- );
+ assert_eq!(view.transport_statuses[1].implementation, "real");
+ assert_eq!(view.transport_statuses[1].maturity, "preview");
+ assert_eq!(view.transport_statuses[1].availability, "unavailable");
assert!(!view.transport_statuses[1].usable_for_delivery);
assert!(!view.transport_statuses[1].capabilities.deliver);
assert!(!view.transport_statuses[1].capabilities.fetch);
@@ -1947,15 +1930,15 @@ mod tests {
}
#[test]
- fn sync_pull_empty_hybrid_profile_reports_nostr_misconfigured() {
+ fn sync_pull_empty_multi_target_profile_reports_nostr_misconfigured() {
let dir = tempdir().expect("tempdir");
- let config = hybrid_config(dir.path(), Vec::new());
+ let config = multi_target_config(dir.path(), Vec::new());
crate::runtime::store::init(&config).expect("store init");
let view = pull_with_fetcher(&config, |_, _| {
- panic!("empty Hybrid profile sync pull must not fetch")
+ panic!("empty MultiTarget profile sync pull must not fetch")
})
- .expect("sync pull empty Hybrid profile");
+ .expect("sync pull empty MultiTarget profile");
assert_eq!(view.state, "unavailable");
assert_eq!(view.configured_transport_target_count, 1);
@@ -1968,7 +1951,7 @@ mod tests {
assert_eq!(view.transport_statuses[0].transport, "nostr");
assert_eq!(
view.transport_statuses[0].profile_id.as_deref(),
- Some("hybrid")
+ Some("multi_target")
);
assert!(!view.transport_statuses[0].configured);
assert_eq!(view.transport_statuses[0].implementation, "real");
@@ -1977,10 +1960,9 @@ mod tests {
assert!(!view.transport_statuses[0].capabilities.fetch);
assert_eq!(view.transport_statuses[1].transport, "reticulum");
assert!(view.transport_statuses[1].configured);
- assert_eq!(
- view.transport_statuses[1].implementation,
- "preview_unavailable"
- );
+ assert_eq!(view.transport_statuses[1].implementation, "real");
+ assert_eq!(view.transport_statuses[1].maturity, "preview");
+ assert_eq!(view.transport_statuses[1].availability, "unavailable");
assert!(!view.transport_statuses[1].usable_for_delivery);
assert!(!view.transport_statuses[1].capabilities.deliver);
assert!(!view.transport_statuses[1].capabilities.fetch);
@@ -1989,34 +1971,33 @@ mod tests {
}
#[test]
- fn sync_inspect_hybrid_uses_profile_target_count() {
+ fn sync_inspect_multi_target_uses_profile_target_count() {
let dir = tempdir().expect("tempdir");
- let config = hybrid_config(dir.path(), vec!["wss://relay.example.com".to_owned()]);
+ let config = multi_target_config(dir.path(), vec!["wss://relay.example.com".to_owned()]);
crate::runtime::store::init(&config).expect("store init");
- let snapshot = inspect_sync(&config).expect("inspect sync hybrid");
+ let snapshot = inspect_sync(&config).expect("inspect sync multi_target");
assert_eq!(snapshot.state, "ready");
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, "reticulum");
- assert_eq!(
- snapshot.transport_statuses[1].implementation,
- "preview_unavailable"
- );
+ assert_eq!(snapshot.transport_statuses[1].implementation, "real");
+ assert_eq!(snapshot.transport_statuses[1].maturity, "preview");
+ assert_eq!(snapshot.transport_statuses[1].availability, "unavailable");
}
#[test]
- fn sync_pull_reticulum_preview_does_not_report_fetch_success() {
+ fn sync_pull_reticulum_does_not_report_fetch_success() {
let dir = tempdir().expect("tempdir");
- let config = reticulum_preview_config(dir.path());
+ let config = reticulum_config(dir.path());
crate::runtime::store::init(&config).expect("store init");
let view = pull_with_fetcher(&config, |_, _| {
- panic!("reticulum preview sync pull must not run Nostr fetch")
+ panic!("reticulum sync pull must not run Nostr fetch")
})
- .expect("sync pull reticulum preview");
+ .expect("sync pull reticulum");
assert_eq!(view.state, "unavailable");
assert_eq!(view.configured_transport_target_count, 1);
@@ -2025,10 +2006,9 @@ mod tests {
"reticulum"
);
assert!(view.transport_statuses[0].configured);
- assert_eq!(
- view.transport_statuses[0].implementation,
- "preview_unavailable"
- );
+ assert_eq!(view.transport_statuses[0].implementation, "real");
+ assert_eq!(view.transport_statuses[0].maturity, "preview");
+ assert_eq!(view.transport_statuses[0].availability, "unavailable");
assert!(!view.transport_statuses[0].usable_for_delivery);
assert!(!view.transport_statuses[0].capabilities.deliver);
assert!(!view.transport_statuses[0].capabilities.fetch);
@@ -2037,7 +2017,7 @@ mod tests {
assert!(
view.reason
.as_deref()
- .expect("reticulum preview reason")
+ .expect("reticulum unavailable reason")
.contains("does not expose Nostr relay fetch targets")
);
}
@@ -2065,7 +2045,6 @@ mod tests {
0,
0,
0,
- 0,
None,
None,
&["wss://relay-a.example.com", "wss://relay-b.example.com"],
@@ -2080,16 +2059,15 @@ mod tests {
assert_eq!(view.queue.pending_count, 0);
assert_eq!(view.queue.retryable_count, Some(0));
assert_eq!(view.queue.terminal_count, Some(0));
- assert_eq!(view.queue.preview_unavailable_count, Some(0));
assert_eq!(view.queue.deferred_until_implemented_count, Some(0));
assert_eq!(view.queue.ready_signed_count, Some(0));
assert_eq!(view.actions, vec!["radroots sync pull"]);
}
#[test]
- fn sync_status_hybrid_reports_profile_targets_and_statuses() {
+ fn sync_status_multi_target_reports_profile_targets_and_statuses() {
let dir = tempdir().expect("tempdir");
- let config = hybrid_config(dir.path(), vec!["wss://relay.example.com".to_owned()]);
+ let config = multi_target_config(dir.path(), vec!["wss://relay.example.com".to_owned()]);
let mut receipt = sdk_status_receipt(
0,
0,
@@ -2100,13 +2078,12 @@ mod tests {
0,
0,
0,
- 0,
None,
None,
&["wss://relay.example.com"],
);
receipt.transport_profile = SyncTransportProfileSummary {
- transport_profile_id: "hybrid".to_owned(),
+ transport_profile_id: "multi_target".to_owned(),
configured_transport_target_count: 2,
configured_transport_targets: vec![
SyncTransportTargetSummary {
@@ -2118,19 +2095,21 @@ mod tests {
},
SyncTransportTargetSummary {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
endpoint_fingerprint: "1".repeat(64),
- target_scope: Some("local_preview".to_owned()),
+ target_scope: Some("local".to_owned()),
target_label: None,
},
],
transport_statuses: vec![
SyncTransportStatusSummary {
transport: "nostr".to_owned(),
- profile_id: Some("hybrid".to_owned()),
+ profile_id: Some("multi_target".to_owned()),
endpoint_uri: None,
configured: true,
implementation: "real".to_owned(),
+ maturity: "stable".to_owned(),
+ availability: "available".to_owned(),
usable_for_delivery: true,
capabilities: SyncTransportOperationCapabilitiesSummary {
deliver: true,
@@ -2140,10 +2119,12 @@ mod tests {
},
SyncTransportStatusSummary {
transport: "reticulum".to_owned(),
- profile_id: Some("hybrid".to_owned()),
- endpoint_uri: Some(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned()),
+ profile_id: Some("multi_target".to_owned()),
+ endpoint_uri: Some(RADROOTS_RETICULUM_ENDPOINT_URI.to_owned()),
configured: true,
- implementation: "preview_unavailable".to_owned(),
+ implementation: "real".to_owned(),
+ maturity: "preview".to_owned(),
+ availability: "unavailable".to_owned(),
usable_for_delivery: false,
capabilities: SyncTransportOperationCapabilitiesSummary {
deliver: false,
@@ -2164,10 +2145,9 @@ mod tests {
"reticulum"
);
assert_eq!(view.transport_statuses.len(), 2);
- assert_eq!(
- view.transport_statuses[1].implementation,
- "preview_unavailable"
- );
+ assert_eq!(view.transport_statuses[1].implementation, "real");
+ assert_eq!(view.transport_statuses[1].maturity, "preview");
+ assert_eq!(view.transport_statuses[1].availability, "unavailable");
assert!(!view.transport_statuses[1].usable_for_delivery);
assert!(!view.transport_statuses[1].capabilities.deliver);
assert!(!view.transport_statuses[1].capabilities.fetch);
@@ -2188,7 +2168,6 @@ mod tests {
2,
1,
0,
- 0,
1,
0,
Some(1_700_000_010_000),
@@ -2203,7 +2182,6 @@ mod tests {
assert_eq!(view.queue.retryable_count, Some(1));
assert_eq!(view.queue.terminal_count, Some(2));
assert_eq!(view.queue.failed_terminal_count, Some(1));
- assert_eq!(view.queue.preview_unavailable_count, Some(0));
assert_eq!(view.queue.deferred_until_implemented_count, Some(0));
assert_eq!(view.queue.ready_signed_count, Some(1));
assert_eq!(view.queue.last_attempt_at_ms, Some(1_700_000_010_000));
@@ -2230,7 +2208,6 @@ mod tests {
0,
0,
0,
- 0,
1,
0,
None,
@@ -2279,7 +2256,6 @@ mod tests {
0,
0,
0,
- 0,
None,
None,
&["wss://relay.example.com"],
@@ -2298,15 +2274,14 @@ mod tests {
}
#[test]
- fn sync_push_reticulum_preview_reports_non_attempted_preview_work() {
+ fn sync_push_reticulum_reports_non_attempted_deferred_work() {
let cases = [
(
- PushOutboxEventState::PreviewUnavailable,
- PushOutboxTargetOutcomeKind::PreviewUnavailable,
- "preview_unavailable",
- "sdk_outbox_push_preview_unavailable",
- "SDK outbox push reported Reticulum preview work as preview unavailable without network delivery",
- 1,
+ PushOutboxEventState::DeferredUntilImplemented,
+ PushOutboxTargetOutcomeKind::DeferredUntilImplemented,
+ "deferred_until_implemented",
+ "sdk_outbox_push_deferred_until_implemented",
+ "SDK outbox push reported Reticulum work as deferred until implemented without network delivery",
0,
),
(
@@ -2314,8 +2289,7 @@ mod tests {
PushOutboxTargetOutcomeKind::DeferredUntilImplemented,
"deferred_until_implemented",
"sdk_outbox_push_deferred_until_implemented",
- "SDK outbox push reported Reticulum preview work as deferred until implemented without network delivery",
- 0,
+ "SDK outbox push reported Reticulum work as deferred until implemented without network delivery",
1,
),
];
@@ -2326,24 +2300,23 @@ mod tests {
expected_state,
expected_reason_code,
expected_reason,
- preview_count,
deferred_count,
) in cases
{
let dir = tempdir().expect("tempdir");
- let config = reticulum_preview_config(dir.path());
+ let config = reticulum_config(dir.path());
let receipt = PushOutboxReceipt {
attempted_events: 0,
published_events: 0,
retryable_events: 0,
terminal_events: 0,
- events: vec![sdk_reticulum_preview_push_event(final_state, outcome_kind)],
+ events: vec![sdk_reticulum_push_event(final_state, outcome_kind)],
};
let view = sdk_push_view(
&config,
receipt,
- sdk_reticulum_preview_status_receipt(preview_count, deferred_count),
+ sdk_reticulum_status_receipt(deferred_count),
);
assert_eq!(view.state, expected_state);
@@ -2354,7 +2327,7 @@ mod tests {
assert_eq!(view.reason.as_deref(), Some(expected_reason));
assert_eq!(
view.target_transport_endpoints,
- vec![RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned()]
+ vec![RADROOTS_RETICULUM_ENDPOINT_URI.to_owned()]
);
assert!(view.attempted_transport_endpoints.is_empty());
assert!(view.accepted_transport_endpoints.is_empty());
@@ -2362,7 +2335,7 @@ mod tests {
assert_eq!(view.failed_transport_targets[0].transport_kind, "reticulum");
assert_eq!(
view.failed_transport_targets[0].endpoint_uri,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ RADROOTS_RETICULUM_ENDPOINT_URI
);
assert_eq!(
view.failed_transport_targets[0].reason,
@@ -2418,7 +2391,6 @@ mod tests {
0,
0,
0,
- 0,
Some(1_700_000_020_000),
Some("auth-required: login".to_owned()),
&["wss://relay-a.example.com", "wss://relay-b.example.com"],
@@ -2471,7 +2443,6 @@ mod tests {
retryable_events: i64,
terminal_events: i64,
failed_terminal_events: i64,
- preview_unavailable_events: i64,
deferred_until_implemented_events: i64,
ready_signed_events: i64,
publishing_events: i64,
@@ -2499,7 +2470,6 @@ mod tests {
retryable_events,
terminal_events,
failed_terminal_events,
- preview_unavailable_events,
deferred_until_implemented_events,
ready_signed_events,
publishing_events,
@@ -2526,6 +2496,8 @@ mod tests {
endpoint_uri: None,
configured: true,
implementation: "real".to_owned(),
+ maturity: "stable".to_owned(),
+ availability: "available".to_owned(),
usable_for_delivery: true,
capabilities: SyncTransportOperationCapabilitiesSummary {
deliver: true,
@@ -2537,10 +2509,7 @@ mod tests {
}
}
- fn sdk_reticulum_preview_status_receipt(
- preview_unavailable_events: i64,
- deferred_until_implemented_events: i64,
- ) -> SyncStatusReceipt {
+ fn sdk_reticulum_status_receipt(deferred_until_implemented_events: i64) -> SyncStatusReceipt {
SyncStatusReceipt {
source: SyncStatusSource::SdkCanonicalStores,
observed_at_ms: 1_700_000_030_000,
@@ -2557,7 +2526,6 @@ mod tests {
retryable_events: 0,
terminal_events: 0,
failed_terminal_events: 0,
- preview_unavailable_events,
deferred_until_implemented_events,
ready_signed_events: 0,
publishing_events: 0,
@@ -2565,21 +2533,23 @@ mod tests {
last_error: None,
},
transport_profile: SyncTransportProfileSummary {
- transport_profile_id: "reticulum_preview".to_owned(),
+ transport_profile_id: "reticulum".to_owned(),
configured_transport_target_count: 1,
configured_transport_targets: vec![SyncTransportTargetSummary {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
endpoint_fingerprint: "1".repeat(64),
- target_scope: Some("local_preview".to_owned()),
+ target_scope: Some("local".to_owned()),
target_label: None,
}],
transport_statuses: vec![SyncTransportStatusSummary {
transport: "reticulum".to_owned(),
- profile_id: Some("reticulum_preview".to_owned()),
- endpoint_uri: Some(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned()),
+ profile_id: Some("reticulum".to_owned()),
+ endpoint_uri: Some(RADROOTS_RETICULUM_ENDPOINT_URI.to_owned()),
configured: true,
- implementation: "preview_unavailable".to_owned(),
+ implementation: "real".to_owned(),
+ maturity: "preview".to_owned(),
+ availability: "unavailable".to_owned(),
usable_for_delivery: false,
capabilities: SyncTransportOperationCapabilitiesSummary {
deliver: false,
@@ -2644,7 +2614,7 @@ mod tests {
}
}
- fn sdk_reticulum_preview_push_event(
+ fn sdk_reticulum_push_event(
final_state: PushOutboxEventState,
outcome_kind: PushOutboxTargetOutcomeKind,
) -> PushOutboxEventReceipt {
@@ -2660,8 +2630,8 @@ mod tests {
quorum_met: false,
targets: vec![PushOutboxTargetReceipt {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
- target_scope: Some("local_preview".to_owned()),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
+ target_scope: Some("local".to_owned()),
target_label: None,
outcome_kind,
transport_outcome_kind: test_transport_outcome_kind(outcome_kind),
@@ -2686,9 +2656,6 @@ mod tests {
PushOutboxTargetOutcomeKind::ConnectionFailed => {
PushOutboxTransportOutcomeKind::ConnectionFailed
}
- PushOutboxTargetOutcomeKind::PreviewUnavailable => {
- PushOutboxTransportOutcomeKind::TransportUnavailable
- }
PushOutboxTargetOutcomeKind::AuthRequired
| PushOutboxTargetOutcomeKind::Blocked
| PushOutboxTargetOutcomeKind::RateLimited
@@ -3256,10 +3223,10 @@ mod tests {
}
}
- fn hybrid_config(root: &Path, relays: Vec<String>) -> RuntimeConfig {
+ fn multi_target_config(root: &Path, relays: Vec<String>) -> RuntimeConfig {
let mut config = sample_config(root, relays);
- config.transport.profile = TransportProfileKind::Hybrid;
- config.transport.reticulum_preview_behavior = ReticulumPreviewBehavior::DeferDeliveryPlans;
+ config.transport.profile = TransportProfileKind::MultiTarget;
+ config.transport.reticulum_behavior = ReticulumBehavior::DeferDeliveryPlans;
config
}
@@ -3269,10 +3236,10 @@ mod tests {
config
}
- fn reticulum_preview_config(root: &Path) -> RuntimeConfig {
+ fn reticulum_config(root: &Path) -> RuntimeConfig {
let mut config = sample_config(root, Vec::new());
- config.transport.profile = TransportProfileKind::ReticulumPreview;
- config.transport.reticulum_preview_behavior = ReticulumPreviewBehavior::DeferDeliveryPlans;
+ config.transport.profile = TransportProfileKind::Reticulum;
+ config.transport.reticulum_behavior = ReticulumBehavior::DeferDeliveryPlans;
config
}
}
diff --git a/src/runtime/transport.rs b/src/runtime/transport.rs
@@ -1,16 +1,15 @@
-use std::fs;
-use std::path::PathBuf;
-
use radroots_sdk::{
PushOutboxEventState, PushOutboxReceipt, PushOutboxRequest, PushOutboxTargetOutcomeKind,
SyncStatusRequest,
};
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_PREVIEW_SCOPE_ID,
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportImplementationState,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID,
+ RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportCapabilityAvailability,
+ RadrootsTransportCapabilityMaturity, RadrootsTransportImplementationState,
RadrootsTransportKind, RadrootsTransportStatus,
};
use serde_json::Value as JsonValue;
+use std::fs;
use toml::{Value, map::Map};
use crate::ops::OperationData;
@@ -51,80 +50,50 @@ pub fn set_profile(
);
transport.insert("nostr".to_owned(), Value::Table(nostr));
}
- "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(
+ "reticulum" => {
+ let behavior =
+ string_input(input, "reticulum_behavior").unwrap_or("reject_delivery_attempts");
+ let scope =
+ string_input(input, "reticulum_scope").unwrap_or(RADROOTS_RETICULUM_SCOPE_ID);
+ let mut reticulum = Map::new();
+ reticulum.insert("behavior".to_owned(), Value::String(behavior.to_owned()));
+ reticulum.insert("scope".to_owned(), Value::String(scope.to_owned()));
+ if let Some(agent_endpoint) = string_input(input, "reticulum_agent_endpoint") {
+ reticulum.insert(
"agent_endpoint".to_owned(),
Value::String(agent_endpoint.to_owned()),
);
}
- transport.insert("reticulum_preview".to_owned(), Value::Table(preview));
+ transport.insert("reticulum".to_owned(), Value::Table(reticulum));
}
- "hybrid" => {
+ "multi_target" => {
let relays = string_array_input(input, "nostr_relays");
if relays.is_empty() {
return Err(RuntimeError::Config(
- "transport profile `hybrid` requires at least one --nostr-relay".to_owned(),
+ "transport profile `multi_target` requires at least one --nostr-relay"
+ .to_owned(),
));
}
- 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 behavior =
+ string_input(input, "reticulum_behavior").unwrap_or("reject_delivery_attempts");
+ let scope =
+ string_input(input, "reticulum_scope").unwrap_or(RADROOTS_RETICULUM_SCOPE_ID);
let mut nostr = Map::new();
nostr.insert(
"relay_urls".to_owned(),
Value::Array(relays.into_iter().map(Value::String).collect()),
);
- 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(
+ let mut reticulum = Map::new();
+ reticulum.insert("behavior".to_owned(), Value::String(behavior.to_owned()));
+ reticulum.insert("scope".to_owned(), Value::String(scope.to_owned()));
+ if let Some(agent_endpoint) = string_input(input, "reticulum_agent_endpoint") {
+ reticulum.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));
- }
- "proxy" => {
- let Some(url) = string_input(input, "proxy_url") else {
- return Err(RuntimeError::Config(
- "transport profile `proxy` requires --proxy-url".to_owned(),
- ));
- };
- let token_file = string_input(input, "proxy_token_file").map(str::to_owned);
- let token_secret_id = string_input(input, "proxy_token_secret_id").map(str::to_owned);
- validate_proxy_token_source(token_file.as_deref(), token_secret_id.as_deref())?;
- validate_proxy_token_material(
- config,
- url,
- token_file.as_deref(),
- token_secret_id.as_deref(),
- )?;
- let mut proxy = Map::new();
- proxy.insert("url".to_owned(), Value::String(url.to_owned()));
- if let Some(token_file) = token_file.as_ref() {
- proxy.insert(
- "token_file".to_owned(),
- Value::String(token_file.to_owned()),
- );
- }
- if let Some(token_secret_id) = token_secret_id.as_ref() {
- proxy.insert(
- "token_secret_id".to_owned(),
- Value::String(token_secret_id.to_owned()),
- );
- }
- transport.insert("proxy".to_owned(), Value::Table(proxy));
+ transport.insert("reticulum".to_owned(), Value::Table(reticulum));
}
other => {
return Err(RuntimeError::Config(format!(
@@ -136,12 +105,9 @@ pub fn set_profile(
Ok(profile_view_from_parts(
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),
+ string_input(input, "reticulum_behavior").map(str::to_owned),
+ reticulum_scope_for_profile(kind, input),
+ string_input(input, "reticulum_agent_endpoint").map(str::to_owned),
"configured",
))
}
@@ -180,7 +146,6 @@ pub fn outbox_status(
pending_count: receipt.outbox.pending_events,
retryable_count: receipt.outbox.retryable_events,
terminal_count: receipt.outbox.terminal_events,
- preview_unavailable_count: receipt.outbox.preview_unavailable_events,
deferred_until_implemented_count: receipt.outbox.deferred_until_implemented_events,
ready_signed_count: receipt.outbox.ready_signed_events,
publishing_count: receipt.outbox.publishing_events,
@@ -231,7 +196,7 @@ pub fn outbox_push(config: &RuntimeConfig) -> Result<TransportOutboxPushView, Cl
fn transport_outbox_push_state(receipt: &PushOutboxReceipt, failed_count: usize) -> &'static str {
if receipt.attempted_events == 0 {
- return transport_outbox_reported_preview_state(receipt).unwrap_or("ready");
+ return transport_outbox_reported_deferred_state(receipt).unwrap_or("ready");
}
if receipt.published_events > 0 && failed_count > 0 {
"partial"
@@ -244,19 +209,15 @@ fn transport_outbox_push_state(receipt: &PushOutboxReceipt, failed_count: usize)
}
}
-fn transport_outbox_reported_preview_state(receipt: &PushOutboxReceipt) -> Option<&'static str> {
+fn transport_outbox_reported_deferred_state(receipt: &PushOutboxReceipt) -> Option<&'static str> {
let mut deferred = false;
for event in &receipt.events {
match event.final_state {
- PushOutboxEventState::PreviewUnavailable => return Some("preview_unavailable"),
PushOutboxEventState::DeferredUntilImplemented => deferred = true,
_ => {}
}
for target in &event.targets {
match target.outcome_kind {
- PushOutboxTargetOutcomeKind::PreviewUnavailable => {
- return Some("preview_unavailable");
- }
PushOutboxTargetOutcomeKind::DeferredUntilImplemented => deferred = true,
_ => {}
}
@@ -267,15 +228,12 @@ fn transport_outbox_reported_preview_state(receipt: &PushOutboxReceipt) -> Optio
fn transport_outbox_push_reason(receipt: &PushOutboxReceipt) -> Option<String> {
if receipt.attempted_events == 0 {
- if let Some(state) = transport_outbox_reported_preview_state(receipt) {
+ if let Some(state) = transport_outbox_reported_deferred_state(receipt) {
return Some(match state {
- "preview_unavailable" => {
- "SDK outbox push reported Reticulum preview work as preview unavailable without network delivery"
- }
"deferred_until_implemented" => {
- "SDK outbox push reported Reticulum preview work as deferred until implemented without network delivery"
+ "SDK outbox push reported Reticulum work as deferred until implemented without network delivery"
}
- _ => "SDK outbox push reported Reticulum preview work without network delivery",
+ _ => "SDK outbox push reported Reticulum work without network delivery",
}
.to_owned());
}
@@ -292,9 +250,6 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView {
None,
None,
None,
- None,
- None,
- None,
"configured",
),
TransportProfileKind::Nostr => profile_view_from_parts(
@@ -303,47 +258,26 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView {
None,
None,
None,
- None,
- None,
- None,
if config.transport.nostr_relay_urls.is_empty() {
"unconfigured"
} else {
"configured"
},
),
- TransportProfileKind::ReticulumPreview => profile_view_from_parts(
- "reticulum_preview",
+ TransportProfileKind::Reticulum => profile_view_from_parts(
+ "reticulum",
Vec::new(),
- Some(
- config
- .transport
- .reticulum_preview_behavior
- .as_str()
- .to_owned(),
- ),
- Some(config.transport.reticulum_preview_scope.clone()),
- config.transport.reticulum_preview_agent_endpoint.clone(),
- None,
- None,
- None,
- "preview_unavailable",
+ Some(config.transport.reticulum_behavior.as_str().to_owned()),
+ Some(config.transport.reticulum_scope.clone()),
+ config.transport.reticulum_agent_endpoint.clone(),
+ "configured",
),
- TransportProfileKind::Hybrid => profile_view_from_parts(
- "hybrid",
+ TransportProfileKind::MultiTarget => profile_view_from_parts(
+ "multi_target",
config.transport.nostr_relay_urls.clone(),
- Some(
- config
- .transport
- .reticulum_preview_behavior
- .as_str()
- .to_owned(),
- ),
- Some(config.transport.reticulum_preview_scope.clone()),
- config.transport.reticulum_preview_agent_endpoint.clone(),
- None,
- None,
- None,
+ Some(config.transport.reticulum_behavior.as_str().to_owned()),
+ Some(config.transport.reticulum_scope.clone()),
+ config.transport.reticulum_agent_endpoint.clone(),
if config.transport.nostr_relay_urls.is_empty() {
"unconfigured"
} else {
@@ -351,83 +285,34 @@ fn active_profile_view(config: &RuntimeConfig) -> TransportProfileView {
},
)
.with_message(
- "Hybrid transport publishes through configured Nostr relays and reports Reticulum preview status"
+ "Multi-target transport publishes through configured Nostr relays and reports Reticulum availability"
.to_owned(),
),
- TransportProfileKind::Proxy => {
- let proxy_readiness = proxy_token_ready(config);
- profile_view_from_parts(
- "proxy",
- Vec::new(),
- None,
- None,
- None,
- Some(config.transport.proxy.url.clone()),
- config
- .transport
- .proxy
- .token_file
- .as_ref()
- .map(|path| path.display().to_string()),
- config.transport.proxy.token_secret_id.clone(),
- if proxy_readiness.is_ok() {
- "configured"
- } else {
- "unconfigured"
- },
- )
- .with_message(proxy_readiness.err().map_or_else(
- || "Proxy transport delegates delivery to the configured endpoint".to_owned(),
- |error| error.to_string(),
- ))
- }
}
}
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>,
+ reticulum_behavior: Option<String>,
+ reticulum_scope: Option<String>,
+ reticulum_agent_endpoint: Option<String>,
configured_state: &str,
) -> TransportProfileView {
- let transport_statuses = transport_statuses_from_parts(
- profile_id,
- nostr_relays.as_slice(),
- proxy_url.as_deref(),
- configured_state,
- );
+ let transport_statuses = transport_statuses_from_parts(profile_id, nostr_relays.as_slice());
let profile_delivery_usable = transport_statuses
.iter()
.any(|status| status.usable_for_delivery);
let message = match profile_id {
"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 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 profile_delivery_usable => {
- "Proxy transport delegates delivery to the configured endpoint"
+ "reticulum" => RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ "multi_target" if profile_delivery_usable => {
+ "Multi-target transport publishes through configured Nostr relays and reports Reticulum availability"
}
- "proxy" => "Proxy transport requires a configured token file or token secret id",
+ "multi_target" => "Multi-target transport requires configured Nostr relay targets",
_ => "Local-only profile does not deliver to network transports",
};
- let proxy_token_source = match (
- proxy_token_file.as_ref().filter(|value| !value.is_empty()),
- proxy_token_secret_id
- .as_ref()
- .filter(|value| !value.is_empty()),
- ) {
- (Some(_), None) => Some("token_file".to_owned()),
- (None, Some(_)) => Some("token_secret_id".to_owned()),
- _ => None,
- };
TransportProfileView {
state: configured_state.to_owned(),
source: TRANSPORT_SOURCE.to_owned(),
@@ -437,13 +322,9 @@ fn profile_view_from_parts(
profile_delivery_usable,
message: message.to_owned(),
nostr_relays,
- reticulum_preview_behavior,
- reticulum_preview_scope,
- reticulum_preview_agent_endpoint,
- proxy_url,
- proxy_token_source,
- proxy_token_file,
- proxy_token_secret_id,
+ reticulum_behavior,
+ reticulum_scope,
+ reticulum_agent_endpoint,
transport_statuses,
actions: profile_actions(profile_id, profile_delivery_usable),
}
@@ -452,31 +333,24 @@ fn profile_view_from_parts(
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),
- )]
+ "reticulum" => {
+ vec![transport_runtime_status_view(reticulum_transport_status(
+ profile_id,
+ ))]
}
- "hybrid" => vec![
+ "multi_target" => vec![
transport_runtime_status_view(nostr_transport_status(
profile_id,
!nostr_relays.is_empty(),
)),
- transport_runtime_status_view(reticulum_preview_transport_status(profile_id)),
+ transport_runtime_status_view(reticulum_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,
))],
@@ -509,39 +383,18 @@ fn nostr_transport_status(profile_id: &str, targets_configured: bool) -> Radroot
.with_profile_id(profile_id)
}
-fn reticulum_preview_transport_status(profile_id: &str) -> RadrootsTransportStatus {
+fn reticulum_transport_status(profile_id: &str) -> RadrootsTransportStatus {
RadrootsTransportStatus::new(
RadrootsTransportKind::Reticulum,
true,
- RadrootsTransportImplementationState::PreviewUnavailable,
+ RadrootsTransportImplementationState::Real,
false,
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
)
.with_profile_id(profile_id)
- .with_endpoint_uri(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
-}
-
-fn proxy_transport_status(
- profile_id: &str,
- proxy_url: Option<&str>,
- token_ready: bool,
-) -> RadrootsTransportStatus {
- let mut status = RadrootsTransportStatus::new(
- RadrootsTransportKind::Proxy,
- token_ready,
- RadrootsTransportImplementationState::Real,
- token_ready,
- if token_ready {
- "Proxy transport delegates delivery to the configured endpoint"
- } else {
- "Proxy transport requires a configured token file or token secret id"
- },
- )
- .with_profile_id(profile_id);
- if let Some(proxy_url) = proxy_url {
- status = status.with_endpoint_uri(proxy_url);
- }
- status
+ .with_endpoint_uri(RADROOTS_RETICULUM_ENDPOINT_URI)
+ .with_maturity(RadrootsTransportCapabilityMaturity::Preview)
+ .with_availability(RadrootsTransportCapabilityAvailability::Unavailable)
}
fn transport_runtime_status_view(status: RadrootsTransportStatus) -> TransportRuntimeStatusView {
@@ -551,6 +404,8 @@ fn transport_runtime_status_view(status: RadrootsTransportStatus) -> TransportRu
endpoint_uri: status.endpoint_uri,
configured: status.configured,
implementation: transport_implementation_label(status.implementation).to_owned(),
+ maturity: transport_maturity_label(status.maturity).to_owned(),
+ availability: transport_availability_label(status.availability).to_owned(),
usable_for_delivery: status.usable_for_delivery,
capabilities: TransportOperationCapabilitiesView {
deliver: status.capabilities.deliver,
@@ -564,15 +419,31 @@ fn transport_implementation_label(state: RadrootsTransportImplementationState) -
match state {
RadrootsTransportImplementationState::Real => "real",
RadrootsTransportImplementationState::Mock => "mock",
- RadrootsTransportImplementationState::PreviewUnavailable => "preview_unavailable",
}
}
-fn reticulum_preview_scope_for_profile(profile_id: &str, input: &OperationData) -> Option<String> {
+fn transport_maturity_label(maturity: RadrootsTransportCapabilityMaturity) -> &'static str {
+ match maturity {
+ RadrootsTransportCapabilityMaturity::Preview => "preview",
+ RadrootsTransportCapabilityMaturity::Stable => "stable",
+ }
+}
+
+fn transport_availability_label(
+ availability: RadrootsTransportCapabilityAvailability,
+) -> &'static str {
+ match availability {
+ RadrootsTransportCapabilityAvailability::Available => "available",
+ RadrootsTransportCapabilityAvailability::Degraded => "degraded",
+ RadrootsTransportCapabilityAvailability::Unavailable => "unavailable",
+ }
+}
+
+fn reticulum_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)
+ "reticulum" | "multi_target" => Some(
+ string_input(input, "reticulum_scope")
+ .unwrap_or(RADROOTS_RETICULUM_SCOPE_ID)
.to_owned(),
),
_ => None,
@@ -584,7 +455,7 @@ fn profile_actions(profile_id: &str, profile_delivery_usable: bool) -> Vec<Strin
return Vec::new();
}
match profile_id {
- "reticulum_preview" => vec![
+ "reticulum" => vec![
"radroots mesh status".to_owned(),
"radroots transport profile get".to_owned(),
],
@@ -594,61 +465,16 @@ fn profile_actions(profile_id: &str, profile_delivery_usable: bool) -> Vec<Strin
.to_owned(),
]
}
- "hybrid" => {
+ "multi_target" => {
vec![
- "radroots transport profile set --kind hybrid --nostr-relay wss://relay.example.com"
+ "radroots transport profile set --kind multi-target --nostr-relay wss://relay.example.com"
.to_owned(),
]
}
- "proxy" => vec![
- "radroots transport profile set --kind proxy --proxy-url http://127.0.0.1:7070 --proxy-token-file <path>"
- .to_owned(),
- ],
_ => vec!["radroots transport profile get".to_owned()],
}
}
-fn validate_proxy_token_source(
- token_file: Option<&str>,
- token_secret_id: Option<&str>,
-) -> Result<(), RuntimeError> {
- match (token_file, token_secret_id) {
- (None, None) => Err(RuntimeError::Config(
- "transport profile `proxy` requires --proxy-token-file or --proxy-token-secret-id"
- .to_owned(),
- )),
- (Some(file), None) if file.trim().is_empty() => Err(RuntimeError::Config(
- "transport profile `proxy` requires a non-empty --proxy-token-file".to_owned(),
- )),
- (None, Some(secret_id)) if secret_id.trim().is_empty() => Err(RuntimeError::Config(
- "transport profile `proxy` requires a non-empty --proxy-token-secret-id".to_owned(),
- )),
- (Some(_), Some(_)) => Err(RuntimeError::Config(
- "transport profile `proxy` cannot set both --proxy-token-file and --proxy-token-secret-id"
- .to_owned(),
- )),
- _ => Ok(()),
- }
-}
-
-pub fn proxy_token_ready(config: &RuntimeConfig) -> Result<(), RuntimeError> {
- crate::runtime::sdk::validate_proxy_bearer_token(config)
-}
-
-fn validate_proxy_token_material(
- config: &RuntimeConfig,
- url: &str,
- token_file: Option<&str>,
- token_secret_id: Option<&str>,
-) -> Result<(), RuntimeError> {
- let mut validation_config = config.clone();
- validation_config.transport.profile = TransportProfileKind::Proxy;
- validation_config.transport.proxy.url = url.to_owned();
- validation_config.transport.proxy.token_file = token_file.map(PathBuf::from);
- validation_config.transport.proxy.token_secret_id = token_secret_id.map(str::to_owned);
- proxy_token_ready(&validation_config)
-}
-
trait TransportProfileViewMessage {
fn with_message(self, message: String) -> Self;
fn summary(&self) -> TransportProfileSummaryView;
@@ -725,24 +551,16 @@ mod tests {
};
#[test]
- fn transport_outbox_push_reports_reticulum_preview_without_attempts() {
- let cases = [
- (
- PushOutboxEventState::PreviewUnavailable,
- PushOutboxTargetOutcomeKind::PreviewUnavailable,
- "preview_unavailable",
- "SDK outbox push reported Reticulum preview work as preview unavailable without network delivery",
- ),
- (
- PushOutboxEventState::DeferredUntilImplemented,
- PushOutboxTargetOutcomeKind::DeferredUntilImplemented,
- "deferred_until_implemented",
- "SDK outbox push reported Reticulum preview work as deferred until implemented without network delivery",
- ),
- ];
+ fn transport_outbox_push_reports_reticulum_deferred_without_attempts() {
+ let cases = [(
+ PushOutboxEventState::DeferredUntilImplemented,
+ PushOutboxTargetOutcomeKind::DeferredUntilImplemented,
+ "deferred_until_implemented",
+ "SDK outbox push reported Reticulum work as deferred until implemented without network delivery",
+ )];
for (final_state, outcome_kind, expected_state, expected_reason) in cases {
- let receipt = reticulum_preview_receipt(final_state, outcome_kind);
+ let receipt = reticulum_receipt(final_state, outcome_kind);
assert_eq!(transport_outbox_push_state(&receipt, 0), expected_state);
assert_eq!(
@@ -752,7 +570,7 @@ mod tests {
}
}
- fn reticulum_preview_receipt(
+ fn reticulum_receipt(
final_state: PushOutboxEventState,
outcome_kind: PushOutboxTargetOutcomeKind,
) -> PushOutboxReceipt {
@@ -773,17 +591,14 @@ mod tests {
quorum_met: false,
targets: vec![PushOutboxTargetReceipt {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
- target_scope: Some("local_preview".to_owned()),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
+ target_scope: Some(RADROOTS_RETICULUM_SCOPE_ID.to_owned()),
target_label: None,
outcome_kind,
transport_outcome_kind: Some(match outcome_kind {
PushOutboxTargetOutcomeKind::DeferredUntilImplemented => {
PushOutboxTransportOutcomeKind::DeferredUntilImplemented
}
- PushOutboxTargetOutcomeKind::PreviewUnavailable => {
- PushOutboxTransportOutcomeKind::TransportUnavailable
- }
_ => PushOutboxTransportOutcomeKind::TransportUnavailable,
}),
attempted: false,
diff --git a/src/runtime/validation_receipt.rs b/src/runtime/validation_receipt.rs
@@ -4,8 +4,6 @@ use radroots_sdk::{
TradeValidationReceiptListReceipt, TradeValidationReceiptListRequest,
TradeValidationReceiptNostrRelayOutcomeKind, TradeValidationReceiptNostrRelayOutcomeReceipt,
TradeValidationReceiptTags, TradeValidationReceiptVerifyRequest,
- TradeValidationReceiptWorkerEvidence,
- TradeValidationReceiptWorkerEvidenceSelection as SdkWorkerEvidenceSelection,
};
use radroots_trade::validation_receipt::{
RadrootsTradeCommitmentConfidence, RadrootsTradeValidationAuthority,
@@ -240,11 +238,7 @@ pub fn list(config: &RuntimeConfig, args: &ValidationReceiptListArgs) -> Validat
"validation receipt list requires non-empty `order_id`",
);
}
- let request = match TradeValidationReceiptListRequest::parse(order_id).and_then(|request| {
- request.try_with_trusted_worker_pubkeys(
- config.rhi.trusted_worker_pubkeys.iter().map(String::as_str),
- )
- }) {
+ let request = match TradeValidationReceiptListRequest::parse(order_id) {
Ok(request) => request,
Err(error) => {
return list_sdk_error_view(order_id, CliSdkAdapterError::Sdk(error));
@@ -274,16 +268,13 @@ fn inspect_event(
"validation receipt command requires non-empty `receipt_event_id`",
);
}
- let trusted_worker_pubkeys = config.rhi.trusted_worker_pubkeys.iter().map(String::as_str);
let session = match CliSdkSession::connect(config) {
Ok(session) => session,
Err(error) => return inspection_sdk_error_view(receipt_event_id, error),
};
match intent {
ValidationReceiptCommandIntent::Inspect => {
- let request = match TradeValidationReceiptInspectRequest::parse(receipt_event_id)
- .and_then(|request| request.try_with_trusted_worker_pubkeys(trusted_worker_pubkeys))
- {
+ let request = match TradeValidationReceiptInspectRequest::parse(receipt_event_id) {
Ok(request) => request,
Err(error) => {
return inspection_sdk_error_view(
@@ -308,9 +299,7 @@ fn inspect_event(
}
}
ValidationReceiptCommandIntent::Verify => {
- let request = match TradeValidationReceiptVerifyRequest::parse(receipt_event_id)
- .and_then(|request| request.try_with_trusted_worker_pubkeys(trusted_worker_pubkeys))
- {
+ let request = match TradeValidationReceiptVerifyRequest::parse(receipt_event_id) {
Ok(request) => request,
Err(error) => {
return inspection_sdk_error_view(
@@ -398,7 +387,7 @@ fn inspected_event_view(
let order_id = sdk_receipt.tags.order_id.clone();
let proof_verification = proof_verification_view_for_receipt(
&sdk_receipt.receipt,
- sdk_worker_evidence_selection(sdk_receipt.worker_evidence),
+ ValidationReceiptWorkerEvidenceSelection::default(),
);
let accepted = match intent {
ValidationReceiptCommandIntent::Inspect => {
@@ -492,7 +481,7 @@ fn list_from_sdk_receipt(
for sdk_event in sdk_receipt.receipts {
let proof_verification = proof_verification_view_for_receipt(
&sdk_event.receipt,
- sdk_worker_evidence_selection(sdk_event.worker_evidence),
+ ValidationReceiptWorkerEvidenceSelection::default(),
);
if proof_state_is_invalid(proof_verification.state.as_str()) {
invalid_receipts.push(ValidationReceiptInvalidCandidateView {
@@ -639,8 +628,7 @@ fn sdk_error_parts(error: RadrootsSdkError) -> ValidationReceiptSdkErrorParts {
radroots_sdk::RadrootsSdkErrorClass::Transport
| radroots_sdk::RadrootsSdkErrorClass::Storage
| radroots_sdk::RadrootsSdkErrorClass::Clock
- | radroots_sdk::RadrootsSdkErrorClass::Authorization
- | radroots_sdk::RadrootsSdkErrorClass::LocalMutation => "network_unavailable",
+ | radroots_sdk::RadrootsSdkErrorClass::Authorization => "network_unavailable",
_ => "network_unavailable",
},
};
@@ -1197,39 +1185,6 @@ fn invalid_candidate_view(
}
}
-fn sdk_worker_evidence_selection(
- selection: SdkWorkerEvidenceSelection,
-) -> ValidationReceiptWorkerEvidenceSelection {
- ValidationReceiptWorkerEvidenceSelection {
- trusted: selection.trusted.map(worker_evidence_view),
- untrusted: selection.untrusted.map(worker_evidence_view),
- }
-}
-
-fn worker_evidence_view(
- evidence: TradeValidationReceiptWorkerEvidence,
-) -> ValidationReceiptWorkerEvidenceView {
- ValidationReceiptWorkerEvidenceView {
- result_event_id: evidence.result_event_id.as_str().to_owned(),
- author: evidence.author.as_str().to_owned(),
- validation_authority: evidence
- .validation_authority
- .map(|authority| authority.as_str().to_owned()),
- commitment_confidence: evidence
- .commitment_confidence
- .map(|confidence| confidence.as_str().to_owned()),
- status: evidence.status,
- prover_backend: evidence.prover_backend,
- proof_mode: evidence.proof_mode,
- proof_system: evidence.proof_system,
- proof_generated: evidence.proof_generated,
- sp1_execute_checked: evidence.sp1_execute_checked,
- sp1_execute_public_values_hash: evidence.sp1_execute_public_values_hash,
- cryptographic_proof_verified: evidence.cryptographic_proof_verified,
- public_values_hash: evidence.public_values_hash,
- }
-}
-
fn attempted_transport_endpoints(
relays: &[TradeValidationReceiptNostrRelayOutcomeReceipt],
) -> Vec<String> {
diff --git a/src/view/runtime.rs b/src/view/runtime.rs
@@ -634,7 +634,6 @@ pub struct SdkOutboxStatusView {
pub retryable_events: i64,
pub terminal_events: i64,
pub failed_terminal_events: i64,
- pub preview_unavailable_events: i64,
pub deferred_until_implemented_events: i64,
pub ready_signed_events: i64,
pub publishing_events: i64,
@@ -3155,8 +3154,6 @@ pub struct SyncQueueView {
#[serde(skip_serializing_if = "Option::is_none")]
pub failed_terminal_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub preview_unavailable_count: Option<usize>,
- #[serde(skip_serializing_if = "Option::is_none")]
pub deferred_until_implemented_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ready_signed_count: Option<usize>,
@@ -3273,19 +3270,11 @@ pub struct TransportProfileView {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub nostr_relays: Vec<String>,
#[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>,
+ pub reticulum_behavior: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub proxy_token_source: Option<String>,
+ pub reticulum_scope: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub proxy_token_file: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub proxy_token_secret_id: Option<String>,
+ pub reticulum_agent_endpoint: Option<String>,
pub transport_statuses: Vec<TransportRuntimeStatusView>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<String>,
@@ -3317,6 +3306,8 @@ pub struct TransportRuntimeStatusView {
pub endpoint_uri: Option<String>,
pub configured: bool,
pub implementation: String,
+ pub maturity: String,
+ pub availability: String,
pub usable_for_delivery: bool,
pub capabilities: TransportOperationCapabilitiesView,
pub message: String,
@@ -3337,7 +3328,6 @@ pub struct TransportOutboxStatusView {
pub pending_count: i64,
pub retryable_count: i64,
pub terminal_count: i64,
- pub preview_unavailable_count: i64,
pub deferred_until_implemented_count: i64,
pub ready_signed_count: i64,
pub publishing_count: i64,
@@ -3423,9 +3413,9 @@ pub struct RpcStatusView {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub supported_signer_modes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub daemon_proxy_enabled: Option<bool>,
+ pub daemon_execution_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub daemon_proxy_ready: Option<bool>,
+ pub daemon_execution_ready: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relay_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -3575,6 +3565,8 @@ pub struct SyncTransportStatusView {
pub endpoint_uri: Option<String>,
pub configured: bool,
pub implementation: String,
+ pub maturity: String,
+ pub availability: String,
pub usable_for_delivery: bool,
pub capabilities: TransportOperationCapabilitiesView,
pub message: String,
diff --git a/tests/signer_runtime_modes.rs b/tests/signer_runtime_modes.rs
@@ -926,6 +926,8 @@ fn local_listing_publish_fails_without_local_account_authority() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000004",
"listing",
"publish",
listing_file.to_string_lossy().as_ref(),
@@ -1280,6 +1282,8 @@ fn local_farm_publish_fails_without_configured_relay() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000003",
"farm",
"publish",
]);
@@ -1420,6 +1424,7 @@ fn local_farm_publish_reports_sdk_push_failure_without_profile_publish() {
"pickup",
]);
let relay_url = "ws://127.0.0.1:9";
+ let idempotency_key = "01890f3c-7f2a-7b01-8000-000000000001";
sandbox.write_nostr_transport_profile(&[relay_url]);
let (output, value) = sandbox.json_output(&[
@@ -1428,7 +1433,7 @@ fn local_farm_publish_reports_sdk_push_failure_without_profile_publish() {
"--approval-token",
"approve",
"--idempotency-key",
- "farm_partial",
+ idempotency_key,
"farm",
"publish",
]);
@@ -1455,8 +1460,14 @@ fn local_farm_publish_reports_sdk_push_failure_without_profile_publish() {
.len(),
64
);
- assert_eq!(detail["profile"]["idempotency_key"], "farm_partial:profile");
- assert_eq!(detail["farm"]["idempotency_key"], "farm_partial:farm");
+ assert_eq!(
+ detail["profile"]["idempotency_key"],
+ "01890f3c-7f2a-7b01-8000-9db91832364f"
+ );
+ assert_eq!(
+ detail["farm"]["idempotency_key"],
+ "01890f3c-7f2a-7b01-8000-f953bff1d106"
+ );
assert_eq!(detail["actions"][0], "radroots sync push");
assert_eq!(detail["farm"]["target_transport_endpoints"][0], relay_url);
assert_relay_url(
@@ -1508,6 +1519,7 @@ fn local_farm_publish_does_not_persist_publication_until_sdk_push_publishes() {
"pickup",
]);
let relay_url = "ws://127.0.0.1:9";
+ let idempotency_key = "01890f3c-7f2a-7b01-8000-000000000002";
sandbox.write_nostr_transport_profile(&[relay_url]);
let (output, value) = sandbox.json_output(&[
@@ -1516,7 +1528,7 @@ fn local_farm_publish_does_not_persist_publication_until_sdk_push_publishes() {
"--approval-token",
"approve",
"--idempotency-key",
- "farm_success",
+ idempotency_key,
"farm",
"publish",
]);
@@ -1538,8 +1550,14 @@ fn local_farm_publish_does_not_persist_publication_until_sdk_push_publishes() {
.len(),
64
);
- assert_eq!(detail["profile"]["idempotency_key"], "farm_success:profile");
- assert_eq!(detail["farm"]["idempotency_key"], "farm_success:farm");
+ assert_eq!(
+ detail["profile"]["idempotency_key"],
+ "01890f3c-7f2a-7b01-8000-c7f426f40246"
+ );
+ assert_eq!(
+ detail["farm"]["idempotency_key"],
+ "01890f3c-7f2a-7b01-8000-692916b32301"
+ );
assert_no_removed_command_reference(&value, &["farm", "publish"]);
assert_no_daemon_runtime_reference(&value, &["farm", "publish"]);
@@ -2153,6 +2171,8 @@ fn local_seller_publish_commands_attempt_configured_relay() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000003",
"farm",
"publish",
]);
@@ -2189,6 +2209,8 @@ fn local_seller_publish_commands_attempt_configured_relay() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000004",
"listing",
"publish",
listing_file_arg.as_ref(),
@@ -2228,6 +2250,8 @@ fn local_seller_publish_commands_attempt_configured_relay() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000005",
"listing",
"archive",
listing_file_arg.as_ref(),
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
@@ -15,7 +15,7 @@ use radroots_runtime_store::{
RuntimeStoreRecordInput, RuntimeStoreRecordStatus, SourceRuntime,
};
use radroots_secret_vault::RadrootsSecretVault;
-use radroots_sql_core::{SqlExecutor, SqliteExecutor};
+use radroots_sql_core::{SqlExecutor, SqlxSqliteExecutor};
use serde_json::{Value, json};
use tempfile::TempDir;
@@ -140,7 +140,7 @@ impl RadrootsCliSandbox {
if !path.exists() {
return Vec::new();
}
- let executor = SqliteExecutor::open(path).expect("open runtime store db");
+ let executor = SqlxSqliteExecutor::open(path).expect("open runtime store db");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate runtime store db");
store
@@ -324,7 +324,7 @@ pub fn seed_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str)
sig: "f".repeat(128),
})
.expect("seed listing event");
- let executor = SqliteExecutor::open(Path::new(db_path)).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(Path::new(db_path)).expect("open replica db");
assert_eq!(
radroots_replica_ingest_event(&executor, &event).expect("ingest listing"),
RadrootsReplicaIngestOutcome::Applied
@@ -335,7 +335,7 @@ pub fn seed_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str)
#[allow(dead_code)]
pub fn seed_market_refresh_provenance(sandbox: &RadrootsCliSandbox, relay_urls: &[&str]) {
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
executor
.exec(
"CREATE TABLE IF NOT EXISTS radroots_cli_sync_run (
@@ -433,7 +433,7 @@ fn seed_orderable_listing_signed_event(
let database_path = sandbox.runtime_store_db_path();
fs::create_dir_all(database_path.parent().expect("runtime store parent"))
.expect("runtime store parent");
- let executor = SqliteExecutor::open(database_path).expect("open runtime store");
+ let executor = SqlxSqliteExecutor::open(database_path).expect("open runtime store");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate runtime store");
store
@@ -467,7 +467,7 @@ fn seed_orderable_listing_signed_event(
}
pub fn remove_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str) {
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
let params = serde_json::to_string(&vec![listing_addr]).expect("delete listing params");
executor
.exec(
@@ -482,7 +482,7 @@ pub fn update_orderable_listing_available_amount(
listing_addr: &str,
available_amount: i64,
) {
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
let params = serde_json::to_string(&serde_json::json!([available_amount, listing_addr]))
.expect("update listing params");
executor
@@ -498,7 +498,7 @@ pub fn update_orderable_listing_primary_bin_id(
listing_addr: &str,
primary_bin_id: Option<&str>,
) {
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
let params = serde_json::to_string(&serde_json::json!([primary_bin_id, listing_addr]))
.expect("update listing primary bin params");
executor
@@ -510,7 +510,7 @@ pub fn update_orderable_listing_primary_bin_id(
}
pub fn duplicate_orderable_listing_row(sandbox: &RadrootsCliSandbox, listing_addr: &str) {
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
let params = serde_json::to_string(&json!([
"33333333-3333-3333-3333-333333333333",
listing_addr
@@ -531,7 +531,7 @@ pub fn replace_latest_listing_event_id(
) {
let (seller_pubkey, listing_id) = listing_addr_parts(listing_addr);
let key = format!("{KIND_LISTING}:{seller_pubkey}:{listing_id}");
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica db");
let params = serde_json::to_string(&vec![event_id, key.as_str()]).expect("update params");
executor
.exec(
diff --git a/tests/target_cli.rs b/tests/target_cli.rs
@@ -1,7 +1,6 @@
mod support;
use std::fs;
-use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::sync::{
@@ -31,7 +30,7 @@ use radroots_nostr_connect::prelude::{
RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse,
};
use radroots_outbox::{
- RadrootsOutbox, RadrootsOutboxDeliveryPlanInput, RadrootsOutboxReticulumPreviewBehavior,
+ RadrootsOutbox, RadrootsOutboxDeliveryPlanInput, RadrootsOutboxReticulumBehavior,
RadrootsOutboxSignedOperationInput,
};
use radroots_replica_schema::farm::IFarmFields;
@@ -41,10 +40,10 @@ use radroots_runtime_store::{
BUYER_ORDER_REQUEST_LOCAL_WORK_RECORD_KIND, PublishOutboxStatus, RuntimeStore,
RuntimeStoreRecordFamily, RuntimeStoreRecordInput, RuntimeStoreRecordStatus, SourceRuntime,
};
-use radroots_sql_core::SqliteExecutor;
+use radroots_sql_core::SqlxSqliteExecutor;
use radroots_transport::{
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
- RadrootsTransportKind, RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
+ RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportKind,
+ RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
};
use serde_json::Value;
use serde_json::json;
@@ -187,12 +186,6 @@ fn test_pubkey(value: &str) -> RadrootsPublicKey {
value.parse().expect("valid public key")
}
-fn proxy_token_file(sandbox: &RadrootsCliSandbox) -> PathBuf {
- let path = sandbox.root().join("proxy.token");
- fs::write(&path, "proxy_test_token\n").expect("write proxy token file");
- path
-}
-
fn configure_nostr_transport(sandbox: &RadrootsCliSandbox, relay_url: &str) {
sandbox.write_nostr_transport_profile(&[relay_url]);
}
@@ -218,16 +211,10 @@ fn assert_transport_capabilities(status: &Value, deliver: bool, fetch: bool) {
assert_eq!(status["capabilities"]["fetch"], fetch);
}
-fn write_proxy_transport_config(sandbox: &RadrootsCliSandbox, body: &str) -> PathBuf {
- sandbox.write_app_config(
- format!("[transport]\nprofile = \"proxy\"\n\n[transport.proxy]\n{body}").as_str(),
- )
-}
-
fn sdk_outbox_path(sandbox: &RadrootsCliSandbox) -> PathBuf {
sandbox
.root()
- .join("data/apps/cli/replica/sdk/outbox.sqlite")
+ .join("data/apps/cli/replica/sdk/runtime.sqlite")
}
fn post_outbox_draft(public_key_hex: &str, content: &str) -> RadrootsEventDraft {
@@ -257,7 +244,7 @@ fn seed_signed_transport_outbox_event(
content: &str,
transport_profile_id: &str,
target: RadrootsTransportTarget,
- behavior: RadrootsOutboxReticulumPreviewBehavior,
+ behavior: RadrootsOutboxReticulumBehavior,
claim_for_publish: bool,
) {
let outbox_path = sdk_outbox_path(sandbox);
@@ -285,7 +272,7 @@ fn seed_signed_transport_outbox_event(
RadrootsTransportSatisfactionPolicy::all_accepted(),
vec![target],
)
- .with_reticulum_preview_behavior(behavior),
+ .with_reticulum_behavior(behavior),
true,
1_700_000_000_007,
1_700_000_000_000,
@@ -348,52 +335,6 @@ impl RelayFetchServer {
}
}
-struct ProxyJsonRpcServer {
- endpoint: String,
- handle: JoinHandle<Value>,
-}
-
-impl ProxyJsonRpcServer {
- fn once(expected_token: Option<&'static str>) -> Self {
- let listener = TcpListener::bind("127.0.0.1:0").expect("bind proxy");
- listener.set_nonblocking(true).expect("proxy nonblocking");
- let endpoint = format!("http://{}", listener.local_addr().expect("proxy addr"));
- let handle = thread::spawn(move || {
- let deadline = Instant::now() + Duration::from_secs(30);
- loop {
- match listener.accept() {
- Ok((stream, _)) => {
- return handle_proxy_connection(stream, expected_token);
- }
- Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
- assert!(
- Instant::now() < deadline,
- "timed out waiting for proxy request"
- );
- thread::sleep(Duration::from_millis(10));
- }
- Err(error) => panic!("accept proxy connection: {error}"),
- }
- }
- });
- Self { endpoint, handle }
- }
-
- fn endpoint(&self) -> &str {
- self.endpoint.as_str()
- }
-
- fn join(self) -> Value {
- self.handle.join().expect("proxy server join")
- }
-}
-
-#[derive(Clone, Copy)]
-enum Nip46RelayFinish {
- SignResponse,
- ProductPublish,
-}
-
struct Nip46RelayReport {
connection_count: usize,
req_count: usize,
@@ -408,7 +349,6 @@ struct Nip46RelayState {
published_events: Mutex<Vec<RadrootsNostrEvent>>,
pending_responses: Mutex<Vec<RadrootsNostrEvent>>,
done: AtomicBool,
- finish: Nip46RelayFinish,
}
struct Nip46RelayServer {
@@ -418,7 +358,7 @@ struct Nip46RelayServer {
}
impl Nip46RelayServer {
- fn new(remote_keys: Keys, user_keys: Keys, finish: Nip46RelayFinish) -> Self {
+ fn new(remote_keys: Keys, user_keys: Keys) -> Self {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind nip46 relay");
listener.set_nonblocking(true).expect("nip46 nonblocking");
let endpoint = format!("ws://{}", listener.local_addr().expect("nip46 addr"));
@@ -429,7 +369,6 @@ impl Nip46RelayServer {
published_events: Mutex::new(Vec::new()),
pending_responses: Mutex::new(Vec::new()),
done: AtomicBool::new(false),
- finish,
});
let thread_state = Arc::clone(&state);
let remote_keys = Arc::new(remote_keys);
@@ -614,9 +553,6 @@ fn handle_nip46_sign_request(
.sign_request_count
.lock()
.expect("sign request count lock") += 1;
- if matches!(state.finish, Nip46RelayFinish::SignResponse) {
- state.done.store(true, Ordering::SeqCst);
- }
}
fn send_pending_nip46_responses(
@@ -659,9 +595,7 @@ fn handle_nip46_product_publish(
.lock()
.expect("published events lock")
.push(event);
- if matches!(state.finish, Nip46RelayFinish::ProductPublish) {
- state.done.store(true, Ordering::SeqCst);
- }
+ state.done.store(true, Ordering::SeqCst);
}
fn nip46_response_event(
@@ -727,112 +661,6 @@ signer_session_ref = "{}"
)
}
-fn handle_proxy_connection(mut stream: TcpStream, expected_token: Option<&str>) -> Value {
- let mut bytes = Vec::new();
- let mut buffer = [0_u8; 1024];
- let body_start = loop {
- let read = stream.read(&mut buffer).expect("read proxy");
- assert!(read > 0, "proxy request closed before headers");
- bytes.extend_from_slice(&buffer[..read]);
- if let Some(index) = http_body_start(&bytes) {
- break index;
- }
- };
- let headers = String::from_utf8(bytes[..body_start].to_vec()).expect("headers utf8");
- let content_length = http_content_length(headers.as_str());
- while bytes.len() < body_start + content_length {
- let read = stream.read(&mut buffer).expect("read proxy body");
- assert!(read > 0, "proxy request closed before body");
- bytes.extend_from_slice(&buffer[..read]);
- }
- let body = String::from_utf8(bytes[body_start..body_start + content_length].to_vec())
- .expect("body utf8");
- if let Some(expected_token) = expected_token {
- assert!(
- headers
- .to_ascii_lowercase()
- .contains(format!("authorization: bearer {expected_token}").as_str()),
- "proxy request missing expected bearer auth: {headers}"
- );
- }
- let request: Value = serde_json::from_str(body.as_str()).expect("proxy json");
- assert_eq!(request["jsonrpc"], "2.0");
- assert_eq!(request["method"], "transport.publish.event");
- assert!(request["params"].get("event").is_none());
- let event = proxy_request_raw_event(&request);
- let target_result = json!({
- "transport_kind": "nostr",
- "endpoint_uri": "wss://daemon-resolved.radroots.test",
- "source": "daemon_default",
- "attempted": true,
- "outcome_kind": "accepted"
- });
- let response = json!({
- "jsonrpc": "2.0",
- "id": request["id"],
- "result": {
- "deduplicated": false,
- "job": {
- "job_id": "cli-proxy-job-1",
- "status": "delivery_satisfied",
- "terminal": true,
- "delivery_satisfied": true,
- "event_id": event["id"].clone(),
- "pubkey": event["pubkey"].clone(),
- "event_kind": event["kind"].clone(),
- "target_policy": request["params"]["target_policy"],
- "delivery_policy": request["params"]["delivery_policy"],
- "target_count": 1,
- "acknowledged_count": 1,
- "retryable_count": 0,
- "terminal_count": 0,
- "requested_at_ms": 1_700_000_000_000_i64,
- "completed_at_ms": 1_700_000_000_001_i64,
- "targets": [target_result]
- }
- }
- });
- let response_body = response.to_string();
- let raw_response = format!(
- "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{response_body}",
- response_body.len()
- );
- stream
- .write_all(raw_response.as_bytes())
- .expect("write proxy response");
- request
-}
-
-fn proxy_request_raw_event(request: &Value) -> Value {
- serde_json::from_str(
- request["params"]["raw_event_json"]
- .as_str()
- .expect("proxy raw event json"),
- )
- .expect("proxy raw event")
-}
-
-fn http_body_start(bytes: &[u8]) -> Option<usize> {
- bytes
- .windows(4)
- .position(|window| window == b"\r\n\r\n")
- .map(|index| index + 4)
-}
-
-fn http_content_length(headers: &str) -> usize {
- headers
- .lines()
- .find_map(|line| {
- let (name, value) = line.split_once(':')?;
- if name.eq_ignore_ascii_case("content-length") {
- Some(value.trim().parse::<usize>().expect("content length"))
- } else {
- None
- }
- })
- .expect("content-length header")
-}
-
fn handle_relay_fetch_connection(stream: TcpStream, events: Vec<RadrootsNostrEvent>) {
let mut websocket = tungstenite::accept(stream).expect("accept fetch websocket");
let subscription_id = read_relay_req_subscription_id(&mut websocket);
@@ -869,7 +697,7 @@ fn read_relay_req_subscription_id(websocket: &mut tungstenite::WebSocket<TcpStre
}
fn seed_derived_projection_sync_farm(sandbox: &RadrootsCliSandbox, d_tag: &str, pubkey: &str) {
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica");
migrations::run_all_up(&executor).expect("replica migrations");
farm::create(
&executor,
@@ -1151,7 +979,7 @@ fn append_app_local_record(input: RuntimeStoreRecordInput, sandbox: &RadrootsCli
let database_path = sandbox.runtime_store_db_path();
fs::create_dir_all(database_path.parent().expect("runtime store parent"))
.expect("runtime store parent");
- let executor = SqliteExecutor::open(database_path).expect("open runtime store");
+ let executor = SqlxSqliteExecutor::open(database_path).expect("open runtime store");
let store = RuntimeStore::new(executor);
store.migrate_up().expect("migrate runtime store");
store
@@ -1534,16 +1362,16 @@ fn root_help_explains_transport_and_mesh_surfaces() {
let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
assert!(stdout.contains("Manage transport profiles and outbox delivery."));
- assert!(stdout.contains("Inspect mesh scope and Reticulum preview policy."));
+ assert!(stdout.contains("Inspect mesh scope and Reticulum admission policy."));
assert!(stdout.contains("Inspect local readiness and mode-specific recovery steps"));
assert!(stdout.contains("Show effective configuration and publish-plane readiness."));
}
#[test]
-fn transport_profile_reticulum_preview_output_is_transport_specific() {
+fn transport_profile_reticulum_output_is_transport_specific() {
let sandbox = RadrootsCliSandbox::new();
sandbox.write_app_config(
- "[transport]\nprofile = \"reticulum_preview\"\n\n[transport.reticulum_preview]\nbehavior = \"defer_delivery_plans\"\n",
+ "[transport]\nprofile = \"reticulum\"\n\n[transport.reticulum]\nbehavior = \"defer_delivery_plans\"\n",
);
let value = sandbox.json_success(&["--format", "json", "transport", "profile", "get"]);
@@ -1551,15 +1379,17 @@ fn transport_profile_reticulum_preview_output_is_transport_specific() {
let actions = result["actions"].as_array().expect("actions");
assert_eq!(value["operation_id"], "transport.profile.get");
- assert_eq!(result["profile_id"], "reticulum_preview");
- assert_eq!(result["profile_kind"], "reticulum_preview");
+ assert_eq!(result["profile_id"], "reticulum");
+ assert_eq!(result["profile_kind"], "reticulum");
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"], "reticulum");
+ assert_eq!(result["transport_statuses"][0]["implementation"], "real");
+ assert_eq!(result["transport_statuses"][0]["maturity"], "preview");
assert_eq!(
- result["transport_statuses"][0]["implementation"],
- "preview_unavailable"
+ result["transport_statuses"][0]["availability"],
+ "unavailable"
);
assert_eq!(result["transport_statuses"][0]["configured"], true);
assert_eq!(
@@ -1572,8 +1402,8 @@ fn transport_profile_reticulum_preview_output_is_transport_specific() {
RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE
);
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["reticulum_behavior"], "defer_delivery_plans");
+ assert_eq!(result["reticulum_scope"], "local");
assert_eq!(result["message"], RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE);
assert!(
!result["message"]
@@ -1623,7 +1453,7 @@ fn transport_profile_local_only_output_uses_profile_and_local_status_fields() {
}
#[test]
-fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() {
+fn transport_profile_set_multi_target_persists_nostr_and_reticulum_config() {
let sandbox = RadrootsCliSandbox::new();
let value = sandbox.json_success(&[
@@ -1633,20 +1463,20 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() {
"profile",
"set",
"--kind",
- "hybrid",
+ "multi-target",
"--nostr-relay",
"wss://relay.example.com",
- "--reticulum-preview-behavior",
+ "--reticulum-behavior",
"defer-delivery-plans",
- "--reticulum-preview-scope",
+ "--reticulum-scope",
"farmers_market",
- "--reticulum-preview-agent-endpoint",
+ "--reticulum-agent-endpoint",
"reticulum-agent:local",
]);
assert_eq!(value["operation_id"], "transport.profile.set");
- assert_eq!(value["result"]["profile_id"], "hybrid");
- assert_eq!(value["result"]["profile_kind"], "hybrid");
+ assert_eq!(value["result"]["profile_id"], "multi_target");
+ assert_eq!(value["result"]["profile_kind"], "multi_target");
assert!(value["result"].get("transport_kind").is_none());
assert_eq!(value["result"]["state"], "configured");
assert_eq!(
@@ -1680,7 +1510,15 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() {
);
assert_eq!(
value["result"]["transport_statuses"][1]["implementation"],
- "preview_unavailable"
+ "real"
+ );
+ assert_eq!(
+ value["result"]["transport_statuses"][1]["maturity"],
+ "preview"
+ );
+ assert_eq!(
+ value["result"]["transport_statuses"][1]["availability"],
+ "unavailable"
);
assert_eq!(value["result"]["transport_statuses"][1]["configured"], true);
assert_eq!(
@@ -1691,46 +1529,46 @@ fn transport_profile_set_hybrid_persists_nostr_and_reticulum_preview_config() {
assert_no_removed_transport_status_fields(&value["result"]["transport_statuses"][0]);
assert_no_removed_transport_status_fields(&value["result"]["transport_statuses"][1]);
assert_eq!(
- value["result"]["reticulum_preview_behavior"],
+ value["result"]["reticulum_behavior"],
"defer_delivery_plans"
);
- assert_eq!(value["result"]["reticulum_preview_scope"], "farmers_market");
+ assert_eq!(value["result"]["reticulum_scope"], "farmers_market");
assert_eq!(
- value["result"]["reticulum_preview_agent_endpoint"],
+ value["result"]["reticulum_agent_endpoint"],
"reticulum-agent:local"
);
let value = sandbox.json_success(&["--format", "json", "transport", "profile", "get"]);
- assert_eq!(value["result"]["profile_id"], "hybrid");
+ assert_eq!(value["result"]["profile_id"], "multi_target");
assert_eq!(
value["result"]["nostr_relays"][0],
"wss://relay.example.com"
);
assert_eq!(
- value["result"]["reticulum_preview_behavior"],
+ value["result"]["reticulum_behavior"],
"defer_delivery_plans"
);
- assert_eq!(value["result"]["reticulum_preview_scope"], "farmers_market");
+ assert_eq!(value["result"]["reticulum_scope"], "farmers_market");
assert_eq!(
- value["result"]["reticulum_preview_agent_endpoint"],
+ value["result"]["reticulum_agent_endpoint"],
"reticulum-agent:local"
);
let config = fs::read_to_string(sandbox.root().join("config/apps/cli/config.toml"))
.expect("read app config");
- assert!(config.contains("profile = \"hybrid\""));
+ assert!(config.contains("profile = \"multi_target\""));
assert!(config.contains("[transport.nostr]"));
- assert!(config.contains("[transport.reticulum_preview]"));
+ assert!(config.contains("[transport.reticulum]"));
assert!(config.contains("behavior = \"defer_delivery_plans\""));
assert!(config.contains("scope = \"farmers_market\""));
assert!(config.contains("agent_endpoint = \"reticulum-agent:local\""));
}
#[test]
-fn transport_status_hybrid_output_reports_nostr_and_reticulum_rows() {
+fn transport_status_multi_target_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",
+ "[transport]\nprofile = \"multi_target\"\n\n[transport.nostr]\nrelay_urls = [\"wss://relay.example.com\"]\n\n[transport.reticulum]\nbehavior = \"defer_delivery_plans\"\n",
);
let value = sandbox.json_success(&["--format", "json", "transport", "status"]);
@@ -1740,8 +1578,8 @@ fn transport_status_hybrid_output_reports_nostr_and_reticulum_rows() {
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_id"], "multi_target");
+ assert_eq!(result["active_profile"]["profile_kind"], "multi_target");
assert_eq!(result["active_profile"]["profile_delivery_usable"], true);
assert_eq!(transports.len(), 2);
assert_eq!(nostr["transport"], "nostr");
@@ -1750,7 +1588,9 @@ fn transport_status_hybrid_output_reports_nostr_and_reticulum_rows() {
assert_eq!(nostr["usable_for_delivery"], true);
assert_transport_capabilities(nostr, true, false);
assert_eq!(reticulum["transport"], "reticulum");
- assert_eq!(reticulum["implementation"], "preview_unavailable");
+ assert_eq!(reticulum["implementation"], "real");
+ assert_eq!(reticulum["maturity"], "preview");
+ assert_eq!(reticulum["availability"], "unavailable");
assert_eq!(reticulum["configured"], true);
assert_eq!(reticulum["usable_for_delivery"], false);
assert_transport_capabilities(reticulum, false, false);
@@ -1784,10 +1624,10 @@ fn transport_status_nostr_output_uses_canonical_transport_row() {
}
#[test]
-fn transport_status_reticulum_preview_output_reports_unusable_preview_state() {
+fn transport_status_reticulum_output_reports_unusable_unavailable_state() {
let sandbox = RadrootsCliSandbox::new();
sandbox.write_app_config(
- "[transport]\nprofile = \"reticulum_preview\"\n\n[transport.reticulum_preview]\nbehavior = \"defer_delivery_plans\"\n",
+ "[transport]\nprofile = \"reticulum\"\n\n[transport.reticulum]\nbehavior = \"defer_delivery_plans\"\n",
);
let value = sandbox.json_success(&["--format", "json", "transport", "status"]);
@@ -1798,14 +1638,13 @@ 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!(result["active_profile"]["profile_id"], "reticulum_preview");
- assert_eq!(
- result["active_profile"]["profile_kind"],
- "reticulum_preview"
- );
+ assert_eq!(result["active_profile"]["profile_id"], "reticulum");
+ assert_eq!(result["active_profile"]["profile_kind"], "reticulum");
assert_eq!(result["active_profile"]["profile_delivery_usable"], false);
assert_eq!(active["transport"], "reticulum");
- assert_eq!(active["implementation"], "preview_unavailable");
+ assert_eq!(active["implementation"], "real");
+ assert_eq!(active["maturity"], "preview");
+ assert_eq!(active["availability"], "unavailable");
assert_eq!(active["configured"], true);
assert_eq!(active["usable_for_delivery"], false);
assert_transport_capabilities(active, false, false);
@@ -1825,28 +1664,28 @@ fn transport_status_reticulum_preview_output_reports_unusable_preview_state() {
}
#[test]
-fn transport_outbox_status_human_output_reports_preview_deferred_and_publishing_counts() {
+fn transport_outbox_status_human_output_reports_reticulum_deferred_and_publishing_counts() {
let sandbox = RadrootsCliSandbox::new();
seed_signed_transport_outbox_event(
&sandbox,
- "preview unavailable",
- "reticulum_preview",
+ "deferred until implemented",
+ "reticulum",
transport_target(
RadrootsTransportKind::Reticulum,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
+ RADROOTS_RETICULUM_ENDPOINT_URI,
),
- RadrootsOutboxReticulumPreviewBehavior::RejectDeliveryAttempts,
+ RadrootsOutboxReticulumBehavior::RejectDeliveryAttempts,
false,
);
seed_signed_transport_outbox_event(
&sandbox,
"deferred until implemented",
- "reticulum_preview",
+ "reticulum",
transport_target(
RadrootsTransportKind::Reticulum,
- RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
+ RADROOTS_RETICULUM_ENDPOINT_URI,
),
- RadrootsOutboxReticulumPreviewBehavior::DeferDeliveryPlans,
+ RadrootsOutboxReticulumBehavior::DeferDeliveryPlans,
false,
);
seed_signed_transport_outbox_event(
@@ -1854,7 +1693,7 @@ fn transport_outbox_status_human_output_reports_preview_deferred_and_publishing_
"publishing",
"nostr",
transport_target(RadrootsTransportKind::Nostr, ORDERABLE_LISTING_RELAY),
- RadrootsOutboxReticulumPreviewBehavior::RejectDeliveryAttempts,
+ RadrootsOutboxReticulumBehavior::RejectDeliveryAttempts,
true,
);
@@ -1872,8 +1711,7 @@ fn transport_outbox_status_human_output_reports_preview_deferred_and_publishing_
let stdout = String::from_utf8(output.stdout).expect("terminal stdout");
assert!(stdout.contains("Transport Outbox"), "{stdout}");
- assert_terminal_field(&stdout, "Preview unavailable", "1");
- assert_terminal_field(&stdout, "Deferred", "1");
+ assert_terminal_field(&stdout, "Deferred", "2");
assert_terminal_field(&stdout, "Publishing", "1");
assert_terminal_field(&stdout, "Ready signed", "0");
}
@@ -1901,24 +1739,23 @@ fn transport_outbox_status_read_only_succeeds_for_empty_nostr_profile() {
assert_terminal_field(&stdout, "State", "unconfigured");
assert_terminal_field(&stdout, "Profile", "nostr");
assert_terminal_field(&stdout, "Total", "0");
- assert_terminal_field(&stdout, "Preview unavailable", "0");
assert_terminal_field(&stdout, "Deferred", "0");
assert_terminal_field(&stdout, "Publishing", "0");
}
#[test]
-fn config_get_reticulum_preview_transport_exec_readiness_uses_shared_unavailable_copy() {
+fn config_get_reticulum_transport_exec_readiness_uses_shared_unavailable_copy() {
let sandbox = RadrootsCliSandbox::new();
sandbox.write_app_config(
- "[transport]\nprofile = \"reticulum_preview\"\n\n[transport.reticulum_preview]\nbehavior = \"defer_delivery_plans\"\n",
+ "[transport]\nprofile = \"reticulum\"\n\n[transport.reticulum]\nbehavior = \"defer_delivery_plans\"\n",
);
let value = sandbox.json_success(&["--format", "json", "config", "get"]);
let result = &value["result"];
assert_eq!(value["operation_id"], "config.get");
- assert_eq!(result["publish"]["transport"], "reticulum_preview");
- assert_eq!(result["publish"]["state"], "preview_unavailable");
+ assert_eq!(result["publish"]["transport"], "reticulum");
+ assert_eq!(result["publish"]["state"], "unavailable");
assert_eq!(result["publish"]["executable"], false);
assert_eq!(
result["publish"]["reason"],
@@ -1942,7 +1779,7 @@ fn config_get_reticulum_preview_transport_exec_readiness_uses_shared_unavailable
}
#[test]
-fn mesh_status_reports_reticulum_preview_unusable_state_without_fallback() {
+fn mesh_status_reports_reticulum_unusable_state_without_fallback() {
let sandbox = RadrootsCliSandbox::new();
let value = sandbox.json_success(&["--format", "json", "mesh", "status"]);
@@ -1951,10 +1788,13 @@ fn mesh_status_reports_reticulum_preview_unusable_state_without_fallback() {
assert_eq!(value["operation_id"], "mesh.status");
assert_eq!(result["transport"], "reticulum");
assert_eq!(result["configured"], false);
- assert_eq!(result["implementation"], "preview_unavailable");
+ assert_eq!(result["implementation"], "unavailable");
assert_eq!(result["usable_for_delivery"], false);
assert_no_removed_transport_status_fields(result);
- assert_contains(&result["message"], "Reticulum mesh preview is explicit");
+ assert_eq!(
+ result["message"],
+ "Reticulum mesh delivery is unavailable until implementation"
+ );
assert!(
!result["message"]
.as_str()
@@ -1964,7 +1804,7 @@ fn mesh_status_reports_reticulum_preview_unusable_state_without_fallback() {
}
#[test]
-fn mesh_policy_check_reports_structured_preview_denial() {
+fn mesh_policy_check_reports_structured_delivery_denial() {
let sandbox = RadrootsCliSandbox::new();
let value = sandbox.json_success(&["--format", "json", "mesh", "policy", "check"]);
@@ -1972,11 +1812,11 @@ fn mesh_policy_check_reports_structured_preview_denial() {
assert_eq!(value["operation_id"], "mesh.policy.check");
assert_eq!(result["scope"], "disabled");
- assert_eq!(result["policy"], "reticulum_preview_delivery");
+ assert_eq!(result["policy"], "reticulum_mesh_delivery");
assert_eq!(result["transport"], "reticulum");
assert_eq!(result["usable_for_delivery"], false);
assert_eq!(result["decision"], "denied");
- assert_eq!(result["deny_reason"], "preview_unavailable");
+ assert_eq!(result["deny_reason"], "delivery_unavailable");
assert_eq!(result["privacy_class"], "public_event");
assert_eq!(result["payload_bytes"], 1);
assert_eq!(result["frame_bytes"], 1);
@@ -1984,7 +1824,10 @@ fn mesh_policy_check_reports_structured_preview_denial() {
assert_eq!(result["max_frame_bytes"], 0);
assert_eq!(result["compression"], "disabled");
assert_eq!(result["custom_scopes_enabled"], false);
- assert_contains(&result["message"], "Reticulum mesh preview is explicit");
+ assert_eq!(
+ result["message"],
+ "Reticulum mesh delivery is unavailable until implementation"
+ );
assert!(
!result["message"]
.as_str()
@@ -1994,8 +1837,10 @@ fn mesh_policy_check_reports_structured_preview_denial() {
}
#[test]
-fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces() {
+fn transport_source_boundary_rejects_removed_relay_and_daemon_transport_surfaces() {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
+ let removed_daemon_transport_alias = ["radrootsd_", "pro", "xy"].concat();
+ let removed_daemon_publish_alias = ["radrootsd.publish_", "pro", "xy.v1"].concat();
assert!(
!manifest_dir.join("src/cli/relay.rs").exists(),
@@ -2010,8 +1855,8 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces()
] {
let source = fs::read_to_string(manifest_dir.join(relative_path)).expect("read source");
for forbidden in [
- "\"radrootsd_proxy\"",
- "radrootsd.publish_proxy.v1",
+ removed_daemon_transport_alias.as_str(),
+ removed_daemon_publish_alias.as_str(),
"publish.relays.resolve",
"\"publish.event\"",
"transport_kinds",
@@ -2113,9 +1958,8 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces()
"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)",
+ "RadrootsTransportCapabilityMaturity::Preview",
+ "RadrootsTransportCapabilityAvailability::Unavailable",
] {
assert!(
transport_source.contains(required),
@@ -2132,6 +1976,8 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces()
"pub transport: String,",
"pub configured: bool,",
"pub implementation: String,",
+ "pub maturity: String,",
+ "pub availability: String,",
"pub usable_for_delivery: bool,",
"pub capabilities: TransportOperationCapabilitiesView,",
"pub struct TransportOperationCapabilitiesView",
@@ -2235,14 +2081,14 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces()
] {
assert!(
!mesh_policy_view_source.contains(forbidden),
- "mesh policy view must not expose preview-success field `{forbidden}`"
+ "mesh policy view must not expose unavailable-delivery success field `{forbidden}`"
);
}
let mesh_runtime_source =
fs::read_to_string(manifest_dir.join("src/runtime/mesh.rs")).expect("read mesh runtime");
for required in [
- "RadrootsMeshPayloadPolicy::preview_unavailable()",
+ "RadrootsMeshPayloadPolicy::reticulum_unavailable()",
"policy.evaluate(&input)",
"decision.usable_for_delivery()",
"deny_reason",
@@ -2253,7 +2099,7 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces()
] {
assert!(
mesh_runtime_source.contains(required),
- "src/runtime/mesh.rs must derive structured preview denial from mesh policy witness `{required}`"
+ "src/runtime/mesh.rs must derive structured delivery denial from mesh policy witness `{required}`"
);
}
for forbidden in [
@@ -2267,46 +2113,7 @@ fn transport_source_boundary_rejects_removed_relay_and_publish_proxy_surfaces()
] {
assert!(
!mesh_runtime_source.contains(forbidden),
- "src/runtime/mesh.rs must not imply Reticulum preview success or fallback `{forbidden}`"
- );
- }
-
- let sdk_source = fs::read_to_string(manifest_dir.join("src/runtime/sdk.rs")).expect("read sdk");
- for required in [
- "pub(crate) fn validate_proxy_bearer_token",
- ".with_bearer_token(proxy_bearer_token(config)?)",
- "proxy transport profile requires a configured token file or token secret id",
- "contains unsupported control characters",
- ] {
- assert!(
- sdk_source.contains(required),
- "src/runtime/sdk.rs must retain proxy token materialization witness `{required}`"
- );
- }
-
- let main_source = fs::read_to_string(manifest_dir.join("src/main.rs")).expect("read main");
- assert!(
- main_source.contains("runtime::transport::proxy_token_ready(config)"),
- "src/main.rs must retain routed proxy token readiness validation"
- );
-
- let core_source =
- fs::read_to_string(manifest_dir.join("src/ops/exec/core.rs")).expect("read core");
- assert!(
- core_source.contains("crate::runtime::transport::proxy_token_ready(config)"),
- "src/ops/exec/core.rs must retain config and health proxy token readiness validation"
- );
-
- let test_source = fs::read_to_string(file!()).expect("read test source");
- for required in [
- "config_get_proxy_reports_redacted_token_material_failures",
- "transport_profile_set_proxy_rejects_unmaterializable_token_sources",
- "routed_proxy_operations_validate_token_material_before_sdk_runtime",
- "config_get_proxy_with_token_secret_id_reports_ready_transport",
- ] {
- assert!(
- test_source.contains(required),
- "tests/target_cli.rs must retain proxy readiness proof `{required}`"
+ "src/runtime/mesh.rs must not imply Reticulum delivery success or fallback `{forbidden}`"
);
}
}
@@ -2595,606 +2402,6 @@ fn removed_global_flags_are_rejected_publicly() {
}
#[test]
-fn config_get_exposes_proxy_missing_token_state() {
- let sandbox = RadrootsCliSandbox::new();
- write_proxy_transport_config(&sandbox, "");
-
- let value = sandbox.json_success(&["--format", "json", "config", "get"]);
-
- assert_eq!(value["operation_id"], "config.get");
- assert_eq!(value["result"]["publish"]["transport"], "proxy");
- assert_eq!(
- value["result"]["publish"]["source"],
- "user config · transport profile"
- );
- assert_eq!(value["result"]["publish"]["transport_family"], "proxy");
- assert_eq!(value["result"]["publish"]["state"], "unconfigured");
- assert_eq!(value["result"]["publish"]["executable"], false);
- assert_contains(
- &value["result"]["publish"]["reason"],
- "configured token file or token secret id",
- );
- assert_eq!(
- value["result"]["account_resolution"]["status"],
- "unresolved"
- );
- assert_eq!(
- value["result"]["publish"]["provider"]["provider_runtime_id"],
- "proxy"
- );
- assert_eq!(
- value["result"]["write_plane"]["provider_runtime_id"],
- "proxy"
- );
- assert_eq!(
- value["result"]["write_plane"]["binding_model"],
- "proxy_transport"
- );
- 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"]["profile_delivery_usable"],
- false
- );
- assert_eq!(
- value["result"]["transport"]["proxy_url"],
- "http://127.0.0.1:7070"
- );
- assert!(value["result"]["transport"]["proxy_token_source"].is_null());
- assert!(value["result"]["transport"]["proxy_token_file"].is_null());
- assert!(value["result"]["transport"]["proxy_token_secret_id"].is_null());
- assert_eq!(
- value["result"]["actions"][0],
- "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID"
- );
- assert_eq!(
- value["next_actions"][0]["env_var"],
- "RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE"
- );
-}
-
-#[test]
-fn config_get_proxy_with_token_file_reports_ready_transport() {
- let sandbox = RadrootsCliSandbox::new();
- let token_file = proxy_token_file(&sandbox);
- write_proxy_transport_config(
- &sandbox,
- format!(
- "token_file = \"{}\"\n",
- toml_string(token_file.display().to_string().as_str())
- )
- .as_str(),
- );
-
- let value = sandbox.json_success(&["--format", "json", "config", "get"]);
- assert_eq!(value["operation_id"], "config.get");
- assert_eq!(value["result"]["publish"]["transport"], "proxy");
- assert_eq!(value["result"]["publish"]["state"], "ready");
- assert_eq!(value["result"]["publish"]["executable"], true);
- 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"]["profile_delivery_usable"],
- true
- );
- assert_eq!(
- value["result"]["transport"]["proxy_token_source"],
- "token_file"
- );
- assert_eq!(
- value["result"]["transport"]["proxy_token_file"],
- token_file.display().to_string()
- );
- assert!(value["result"]["transport"]["proxy_token_secret_id"].is_null());
- assert!(
- !serde_json::to_string(&value)
- .expect("json")
- .contains("proxy_test_token")
- );
- assert_eq!(
- value["result"]["actions"]
- .as_array()
- .expect("actions")
- .len(),
- 0
- );
-}
-
-#[test]
-fn config_get_proxy_with_token_secret_id_reports_ready_transport() {
- let sandbox = RadrootsCliSandbox::new();
- store_test_session_secret(&sandbox, "proxy.publish.token", "proxy_secret_token");
- write_proxy_transport_config(&sandbox, "token_secret_id = \"proxy.publish.token\"\n");
-
- let value = sandbox.json_success(&["--format", "json", "config", "get"]);
-
- assert_eq!(value["operation_id"], "config.get");
- assert_eq!(value["result"]["publish"]["transport"], "proxy");
- assert_eq!(value["result"]["publish"]["state"], "ready");
- 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"]["profile_delivery_usable"],
- true
- );
- assert_eq!(
- value["result"]["transport"]["proxy_token_source"],
- "token_secret_id"
- );
- assert_eq!(
- value["result"]["transport"]["proxy_token_secret_id"],
- "proxy.publish.token"
- );
- assert!(
- !serde_json::to_string(&value)
- .expect("json")
- .contains("proxy_secret_token")
- );
-}
-
-#[test]
-fn config_get_proxy_reports_redacted_token_material_failures() {
- let missing_file = RadrootsCliSandbox::new();
- let missing_path = missing_file.root().join("missing.proxy.token");
- write_proxy_transport_config(
- &missing_file,
- format!(
- "token_file = \"{}\"\n",
- toml_string(missing_path.display().to_string().as_str())
- )
- .as_str(),
- );
- let missing_file_value = missing_file.json_success(&["--format", "json", "config", "get"]);
- assert_eq!(
- missing_file_value["result"]["publish"]["state"],
- "unconfigured"
- );
- assert_contains(
- &missing_file_value["result"]["publish"]["reason"],
- "failed to read proxy token file",
- );
- assert_contains(
- &missing_file_value["result"]["transport"]["message"],
- missing_path.display().to_string().as_str(),
- );
-
- let missing_secret = RadrootsCliSandbox::new();
- write_proxy_transport_config(
- &missing_secret,
- "token_secret_id = \"proxy.publish.missing\"\n",
- );
- let missing_secret_value = missing_secret.json_success(&["--format", "json", "config", "get"]);
- assert_eq!(
- missing_secret_value["result"]["publish"]["state"],
- "unconfigured"
- );
- assert_contains(
- &missing_secret_value["result"]["publish"]["reason"],
- "proxy token secret `proxy.publish.missing` was not found",
- );
-
- let empty_file = RadrootsCliSandbox::new();
- let empty_path = empty_file.root().join("empty.proxy.token");
- fs::write(&empty_path, "\n").expect("write empty proxy token");
- write_proxy_transport_config(
- &empty_file,
- format!(
- "token_file = \"{}\"\n",
- toml_string(empty_path.display().to_string().as_str())
- )
- .as_str(),
- );
- let empty_value = empty_file.json_success(&["--format", "json", "config", "get"]);
- assert_eq!(empty_value["result"]["publish"]["state"], "unconfigured");
- assert_contains(
- &empty_value["result"]["publish"]["reason"],
- "proxy token file",
- );
- assert_contains(&empty_value["result"]["publish"]["reason"], "is empty");
-
- let control_file = RadrootsCliSandbox::new();
- let control_path = control_file.root().join("control.proxy.token");
- fs::write(&control_path, "proxy_control\tsecret").expect("write control proxy token");
- write_proxy_transport_config(
- &control_file,
- format!(
- "token_file = \"{}\"\n",
- toml_string(control_path.display().to_string().as_str())
- )
- .as_str(),
- );
- let control_value = control_file.json_success(&["--format", "json", "config", "get"]);
- assert_eq!(control_value["result"]["publish"]["state"], "unconfigured");
- assert_contains(
- &control_value["result"]["publish"]["reason"],
- "contains unsupported control characters",
- );
- assert!(
- !serde_json::to_string(&control_value)
- .expect("json")
- .contains("proxy_control")
- );
-}
-
-#[test]
-fn transport_profile_set_proxy_persists_token_file_without_printing_token_material() {
- let sandbox = RadrootsCliSandbox::new();
- let token_file = proxy_token_file(&sandbox);
-
- let value = sandbox.json_success(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-file",
- token_file.to_string_lossy().as_ref(),
- ]);
-
- 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"]["profile_delivery_usable"], true);
- assert_eq!(
- value["result"]["transport_statuses"][0]["transport"],
- "proxy"
- );
- assert_eq!(
- value["result"]["transport_statuses"][0]["implementation"],
- "real"
- );
- assert_eq!(value["result"]["transport_statuses"][0]["configured"], true);
- assert_eq!(
- value["result"]["transport_statuses"][0]["usable_for_delivery"],
- true
- );
- assert_transport_capabilities(&value["result"]["transport_statuses"][0], true, false);
- assert_no_removed_transport_status_fields(&value["result"]["transport_statuses"][0]);
- assert_eq!(value["result"]["proxy_token_source"], "token_file");
- assert_eq!(
- value["result"]["proxy_token_file"],
- token_file.display().to_string()
- );
- assert!(value["result"]["proxy_token_secret_id"].is_null());
- assert!(
- !serde_json::to_string(&value)
- .expect("json")
- .contains("proxy_test_token")
- );
-
- let get = sandbox.json_success(&["--format", "json", "transport", "profile", "get"]);
- assert_eq!(get["result"]["proxy_token_source"], "token_file");
- assert_eq!(
- get["result"]["proxy_token_file"],
- token_file.display().to_string()
- );
- assert!(
- !serde_json::to_string(&get)
- .expect("json")
- .contains("proxy_test_token")
- );
-}
-
-#[test]
-fn transport_profile_set_proxy_persists_token_secret_id_without_printing_token_material() {
- let sandbox = RadrootsCliSandbox::new();
- let token_file = proxy_token_file(&sandbox);
- store_test_session_secret(&sandbox, "proxy.publish.token", "proxy_secret_token");
-
- let value = sandbox.json_success(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-secret-id",
- "proxy.publish.token",
- ]);
-
- 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"]["profile_delivery_usable"], true);
- assert_eq!(
- value["result"]["transport_statuses"][0]["transport"],
- "proxy"
- );
- assert_eq!(
- value["result"]["transport_statuses"][0]["implementation"],
- "real"
- );
- assert_eq!(value["result"]["transport_statuses"][0]["configured"], true);
- assert_eq!(
- value["result"]["transport_statuses"][0]["usable_for_delivery"],
- true
- );
- assert_transport_capabilities(&value["result"]["transport_statuses"][0], true, false);
- assert_no_removed_transport_status_fields(&value["result"]["transport_statuses"][0]);
- assert_eq!(value["result"]["proxy_token_source"], "token_secret_id");
- assert!(value["result"]["proxy_token_file"].is_null());
- assert_eq!(
- value["result"]["proxy_token_secret_id"],
- "proxy.publish.token"
- );
- assert!(
- !serde_json::to_string(&value)
- .expect("json")
- .contains("proxy_secret_token")
- );
-
- let get = sandbox.json_success(&["--format", "json", "transport", "profile", "get"]);
- assert_eq!(get["result"]["proxy_token_source"], "token_secret_id");
- assert_eq!(
- get["result"]["proxy_token_secret_id"],
- "proxy.publish.token"
- );
- assert!(
- !serde_json::to_string(&get)
- .expect("json")
- .contains("proxy_secret_token")
- );
- assert!(token_file.exists());
-}
-
-#[test]
-fn transport_profile_set_proxy_rejects_missing_and_conflicting_token_sources() {
- let sandbox = RadrootsCliSandbox::new();
- let token_file = proxy_token_file(&sandbox);
-
- let (missing_output, missing_value) = sandbox.json_output(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- ]);
- assert!(!missing_output.status.success());
- assert_contains(
- &missing_value["errors"][0]["message"],
- "requires --proxy-token-file or --proxy-token-secret-id",
- );
-
- let (conflict_output, conflict_value) = sandbox.json_output(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-file",
- token_file.to_string_lossy().as_ref(),
- "--proxy-token-secret-id",
- "proxy.publish.token",
- ]);
- assert!(!conflict_output.status.success());
- assert_contains(
- &conflict_value["errors"][0]["message"],
- "cannot set both --proxy-token-file and --proxy-token-secret-id",
- );
- assert!(
- !serde_json::to_string(&conflict_value)
- .expect("json")
- .contains("proxy_test_token")
- );
-}
-
-#[test]
-fn transport_profile_set_proxy_rejects_unmaterializable_token_sources() {
- let missing_file = RadrootsCliSandbox::new();
- let missing_path = missing_file.root().join("missing.profile.token");
- let (missing_file_output, missing_file_value) = missing_file.json_output(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-file",
- missing_path.to_string_lossy().as_ref(),
- ]);
- assert!(!missing_file_output.status.success());
- assert_contains(
- &missing_file_value["errors"][0]["message"],
- "failed to read proxy token file",
- );
-
- let missing_secret = RadrootsCliSandbox::new();
- let (missing_secret_output, missing_secret_value) = missing_secret.json_output(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-secret-id",
- "proxy.profile.missing",
- ]);
- assert!(!missing_secret_output.status.success());
- assert_contains(
- &missing_secret_value["errors"][0]["message"],
- "proxy token secret `proxy.profile.missing` was not found",
- );
-
- let empty_file = RadrootsCliSandbox::new();
- let empty_path = empty_file.root().join("empty.profile.token");
- fs::write(&empty_path, "\n").expect("write empty profile token");
- let (empty_output, empty_value) = empty_file.json_output(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-file",
- empty_path.to_string_lossy().as_ref(),
- ]);
- assert!(!empty_output.status.success());
- assert_contains(&empty_value["errors"][0]["message"], "is empty");
-
- let control_file = RadrootsCliSandbox::new();
- let control_path = control_file.root().join("control.profile.token");
- fs::write(&control_path, "profile_control\tsecret").expect("write control profile token");
- let (control_output, control_value) = control_file.json_output(&[
- "--format",
- "json",
- "transport",
- "profile",
- "set",
- "--kind",
- "proxy",
- "--proxy-url",
- "http://127.0.0.1:7070",
- "--proxy-token-file",
- control_path.to_string_lossy().as_ref(),
- ]);
- assert!(!control_output.status.success());
- assert_contains(
- &control_value["errors"][0]["message"],
- "contains unsupported control characters",
- );
- assert!(
- !serde_json::to_string(&control_value)
- .expect("json")
- .contains("profile_control")
- );
-}
-
-#[test]
-fn routed_proxy_operations_validate_token_material_before_sdk_runtime() {
- let sandbox = RadrootsCliSandbox::new();
- let missing_path = sandbox.root().join("missing.routed.token");
- write_proxy_transport_config(
- &sandbox,
- format!(
- "token_file = \"{}\"\n",
- toml_string(missing_path.display().to_string().as_str())
- )
- .as_str(),
- );
-
- let (online_output, online_value) =
- sandbox.json_output(&["--format", "json", "--online", "sync", "push"]);
- assert!(!online_output.status.success());
- assert_eq!(online_value["errors"][0]["code"], "network_unavailable");
- assert_contains(
- &online_value["errors"][0]["message"],
- "failed to read proxy token file",
- );
-
- let (routed_output, routed_value) = sandbox.json_output(&[
- "--format",
- "json",
- "--approval-token",
- "approve",
- "transport",
- "outbox",
- "push",
- ]);
- assert!(!routed_output.status.success());
- assert_eq!(routed_value["errors"][0]["code"], "network_unavailable");
- assert_contains(
- &routed_value["errors"][0]["message"],
- "failed to read proxy token file",
- );
-}
-
-#[test]
-fn config_get_rejects_proxy_transport_config_with_conflicting_token_sources() {
- let sandbox = RadrootsCliSandbox::new();
- let token_file = proxy_token_file(&sandbox);
- sandbox.write_app_config(
- format!(
- r#"[transport]
-profile = "proxy"
-
-[transport.proxy]
-token_file = "{}"
-token_secret_id = "proxy.publish.token"
-"#,
- toml_string(token_file.display().to_string().as_str())
- )
- .as_str(),
- );
-
- let (output, value) = sandbox.json_output(&["--format", "json", "config", "get"]);
-
- assert!(!output.status.success());
- assert_contains(
- &value["errors"][0]["message"],
- "cannot set both token_file and token_secret_id",
- );
- assert!(
- !serde_json::to_string(&value)
- .expect("json")
- .contains("proxy_test_token")
- );
-}
-
-#[test]
-fn config_get_marks_proxy_unconfigured_with_incomplete_myc_signer() {
- let sandbox = RadrootsCliSandbox::new();
- let token_file = proxy_token_file(&sandbox);
- sandbox.write_app_config(&format!(
- r#"[transport]
-profile = "proxy"
-
-[transport.proxy]
-token_file = "{}"
-
-[signer]
-backend = "myc"
-
-[[capability_binding]]
-capability = "signer.remote_nip46"
-provider = "myc"
-target_kind = "explicit_endpoint"
-target = "http://myc.invalid"
-signer_session_ref = "session_ready"
-"#,
- toml_string(token_file.display().to_string().as_str())
- ));
-
- let value = sandbox.json_success(&["--format", "json", "config", "get"]);
- assert_eq!(value["operation_id"], "config.get");
- assert_eq!(value["result"]["publish"]["transport"], "proxy");
- assert_eq!(value["result"]["publish"]["state"], "unconfigured");
- assert_eq!(value["result"]["publish"]["executable"], false);
- assert_contains(&value["result"]["publish"]["reason"], "signer.remote_nip46");
- assert_eq!(
- value["result"]["publish"]["provider"]["state"],
- "unconfigured"
- );
- assert_eq!(value["result"]["actions"][0], "radroots signer status get");
-}
-
-#[test]
fn config_get_distinguishes_relay_ready_from_missing_signed_write_account() {
let sandbox = RadrootsCliSandbox::new();
configure_nostr_transport(&sandbox, "ws://127.0.0.1:19001");
@@ -3305,51 +2512,6 @@ fn config_get_marks_relay_publish_unconfigured_with_watch_only_account() {
}
#[test]
-fn health_surfaces_publish_state_under_missing_myc_binding() {
- let sandbox = RadrootsCliSandbox::new();
- let missing_myc = sandbox.root().join("bin/missing-myc");
- let token_file = proxy_token_file(&sandbox);
- sandbox.write_app_config(&format!(
- "[transport]\nprofile = \"proxy\"\n\n[transport.proxy]\ntoken_file = \"{}\"\n\n[signer]\nbackend = \"myc\"\n\n[myc]\nexecutable = \"{}\"\n",
- toml_string(token_file.display().to_string().as_str()),
- toml_string(missing_myc.display().to_string().as_str())
- ));
-
- let value = sandbox.json_success(&["--format", "json", "health", "status", "get"]);
-
- assert_eq!(value["operation_id"], "health.status.get");
- assert_eq!(value["result"]["state"], "needs_attention");
- assert_eq!(value["result"]["publish"]["transport"], "proxy");
- assert_eq!(value["result"]["publish"]["executable"], false);
- assert_eq!(
- value["result"]["publish"]["provider"]["state"],
- "unconfigured"
- );
- assert_contains(
- &value["result"]["publish"]["reason"],
- "signer.remote_nip46 binding is missing",
- );
- assert_eq!(value["result"]["store"]["state"], "ready");
- assert_eq!(
- value["result"]["store"]["source"],
- "SDK canonical event store and outbox"
- );
- assert_eq!(value["result"]["store"]["canonical_store"], "sdk");
- assert_eq!(value["result"]["signer"]["state"], "unconfigured");
- assert_eq!(value["result"]["actions"][0], "radroots account create");
- assert_eq!(value["result"]["actions"][1], "radroots signer status get");
- assert_eq!(
- value["next_actions"][0]["command"],
- "radroots account create"
- );
- assert_eq!(
- value["next_actions"][1]["command"],
- "radroots signer status get"
- );
- assert_eq!(value["errors"].as_array().expect("errors").len(), 0);
-}
-
-#[test]
fn health_status_distinguishes_relay_ready_from_missing_signed_write_account() {
let sandbox = RadrootsCliSandbox::new();
configure_nostr_transport(&sandbox, "ws://127.0.0.1:19005");
@@ -3368,35 +2530,11 @@ fn health_status_distinguishes_relay_ready_from_missing_signed_write_account() {
);
assert_eq!(value["result"]["store"]["state"], "ready");
assert_eq!(value["result"]["store"]["canonical_store"], "sdk");
- assert_eq!(value["result"]["signer"]["state"], "unconfigured");
- assert_eq!(value["result"]["actions"][0], "radroots account create");
- assert_eq!(
- value["next_actions"][0]["command"],
- "radroots account create"
- );
-}
-
-#[test]
-fn health_check_rejects_proxy_profile_without_token_material() {
- let sandbox = RadrootsCliSandbox::new();
- write_proxy_transport_config(&sandbox, "");
-
- let (output, value) = sandbox.json_output(&["--format", "json", "health", "check", "run"]);
-
- assert!(!output.status.success());
- assert_eq!(value["operation_id"], "health.check.run");
- assert_eq!(value["result"], Value::Null);
- assert_eq!(value["errors"][0]["code"], "runtime_error");
- assert_contains(
- &value["errors"][0]["message"],
- "proxy transport profile requires a configured token file or token secret id",
- );
+ assert_eq!(value["result"]["signer"]["state"], "unconfigured");
+ assert_eq!(value["result"]["actions"][0], "radroots account create");
assert_eq!(
- value["next_actions"]
- .as_array()
- .expect("next actions")
- .len(),
- 0
+ value["next_actions"][0]["command"],
+ "radroots account create"
);
}
@@ -3469,214 +2607,6 @@ fn farm_readiness_check_reports_mode_specific_publish_gates() {
assert_eq!(relay_detail["transport_profile"], "local_only");
assert_eq!(relay_detail["publish_state"], "ready");
assert_eq!(relay_detail["publish_executable"], true);
-
- let proxy_token_path = proxy_token_file(&sandbox);
- sandbox.write_app_config(&format!(
- r#"[transport]
-profile = "proxy"
-
-[transport.proxy]
-token_file = "{}"
-
-[[capability_binding]]
-capability = "signer.remote_nip46"
-provider = "myc"
-target_kind = "explicit_endpoint"
-target = "http://myc.invalid"
-signer_session_ref = "session_test"
-"#,
- toml_string(proxy_token_path.display().to_string().as_str())
- ));
- let output = sandbox
- .command()
- .args(["--format", "json", "farm", "readiness", "check"])
- .output()
- .expect("run proxy farm readiness");
- let proxy_value: Value = serde_json::from_slice(&output.stdout).expect("json output");
-
- assert!(output.status.success());
- assert_eq!(proxy_value["operation_id"], "farm.readiness.check");
- 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);
- assert_eq!(proxy_value["result"]["actions"][0], "radroots farm publish");
-}
-
-#[test]
-fn proxy_listing_publish_update_and_archive_dry_run_without_direct_publish_relays() {
- for operation in ["publish", "update", "archive"] {
- let sandbox = RadrootsCliSandbox::new();
- sandbox.json_success(&["--format", "json", "account", "create"]);
- let farm = sandbox.json_success(&[
- "--format",
- "json",
- "farm",
- "create",
- "--name",
- "Proxy Farm",
- "--location",
- "farmstand",
- "--city",
- "San Francisco",
- "--country",
- "US",
- "--geohash",
- "9q8yy",
- "--delivery-method",
- "pickup",
- ]);
- let listing_file = create_listing_draft(&sandbox, format!("proxy-{operation}").as_str());
- make_listing_publishable(
- &listing_file,
- farm["result"]["config"]["farm_d_tag"]
- .as_str()
- .expect("farm d tag"),
- );
-
- let token_file = proxy_token_file(&sandbox);
- sandbox.write_app_config(&format!(
- "[transport]\nprofile = \"proxy\"\n\n[transport.proxy]\ntoken_file = \"{}\"\n",
- toml_string(token_file.display().to_string().as_str())
- ));
-
- let output = sandbox
- .command()
- .args([
- "--format",
- "json",
- "--dry-run",
- "listing",
- operation,
- listing_file.to_string_lossy().as_ref(),
- ])
- .output()
- .expect("run proxy listing dry run");
- let value: Value = serde_json::from_slice(&output.stdout).expect("json output");
-
- assert!(output.status.success());
- assert_eq!(value["operation_id"], format!("listing.{operation}"));
- assert_eq!(value["result"]["state"], "dry_run");
- assert_eq!(
- value["result"]["source"],
- "SDK listing publish · configured signer"
- );
- assert_eq!(value["result"]["dry_run"], true);
- assert_eq!(
- value["result"]["target_transport_endpoints"]
- .as_array()
- .expect("relays")
- .len(),
- 0
- );
- assert_contains(
- &value["result"]["reason"],
- "SDK enqueue and transport push skipped",
- );
- }
-}
-
-#[test]
-fn proxy_listing_publish_non_dry_run_uses_local_jsonrpc_server() {
- let sandbox = RadrootsCliSandbox::new();
- let proxy = ProxyJsonRpcServer::once(Some("proxy_test_token"));
- let token_file = proxy_token_file(&sandbox);
- sandbox.write_app_config(
- format!(
- r#"[transport]
-profile = "proxy"
-
-[transport.proxy]
-url = "{}"
-token_file = "{}"
-"#,
- toml_string(proxy.endpoint()),
- toml_string(token_file.display().to_string().as_str())
- )
- .as_str(),
- );
- sandbox.json_success(&["--format", "json", "account", "create"]);
- let farm = sandbox.json_success(&[
- "--format",
- "json",
- "farm",
- "create",
- "--name",
- "Proxy Farm",
- "--location",
- "farmstand",
- "--city",
- "San Francisco",
- "--country",
- "US",
- "--geohash",
- "9q8yy",
- "--delivery-method",
- "pickup",
- ]);
- let listing_file = create_listing_draft(&sandbox, "proxy-live");
- make_listing_publishable(
- &listing_file,
- farm["result"]["config"]["farm_d_tag"]
- .as_str()
- .expect("farm d tag"),
- );
-
- let output = sandbox
- .command()
- .args([
- "--format",
- "json",
- "--approval-token",
- "approve",
- "listing",
- "publish",
- listing_file.to_string_lossy().as_ref(),
- ])
- .output()
- .expect("run proxy listing publish");
- let request = proxy.join();
- let value: Value = serde_json::from_slice(&output.stdout).expect("json output");
-
- assert!(
- output.status.success(),
- "stderr `{}` stdout `{}`",
- String::from_utf8_lossy(&output.stderr),
- String::from_utf8_lossy(&output.stdout)
- );
- assert_eq!(value["operation_id"], "listing.publish");
- assert_eq!(value["result"]["state"], "published");
- assert_eq!(
- value["result"]["source"],
- "SDK listing publish · configured signer"
- );
- assert_eq!(value["result"]["dry_run"], false);
- let event = proxy_request_raw_event(&request);
- assert_eq!(event["id"], value["result"]["event_id"]);
- assert_eq!(
- request["params"]["target_policy"]["relay_urls"]
- .as_array()
- .expect("relay urls")
- .len(),
- 0
- );
- assert!(
- request["params"]["idempotency_key"]
- .as_str()
- .expect("daemon idempotency key")
- .contains("-1-")
- );
-
- let status = sandbox.json_success(&["--format", "json", "sync", "status", "get"]);
- assert_eq!(
- status["result"]["source"],
- "SDK canonical event store and outbox"
- );
- assert_eq!(status["result"]["queue"]["pending_count"], 0);
- assert_eq!(status["result"]["queue"]["ready_signed_count"], 0);
- assert_eq!(status["result"]["queue"]["publishing_count"], 0);
- assert_eq!(status["result"]["queue"]["retryable_count"], 0);
- assert_eq!(status["result"]["queue"]["failed_terminal_count"], 0);
}
#[test]
@@ -3689,11 +2619,7 @@ fn direct_publish_listing_uses_myc_nip46_sdk_signer() {
let user_keys = nostr_keys_from_identity(&user_identity);
let remote_keys = nostr_keys_from_identity(&remote_identity);
let remote_pubkey = remote_keys.public_key().to_hex();
- let relay = Nip46RelayServer::new(
- remote_keys.clone(),
- user_keys,
- Nip46RelayFinish::ProductPublish,
- );
+ let relay = Nip46RelayServer::new(remote_keys.clone(), user_keys);
let relay_endpoint = relay.endpoint().to_owned();
let public_identity_file =
write_public_identity_profile(&sandbox, "myc-direct-user", &user_public);
@@ -3779,6 +2705,8 @@ relay_urls = ["{}"]
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000100",
"listing",
"publish",
listing_file.to_string_lossy().as_ref(),
@@ -3818,127 +2746,6 @@ relay_urls = ["{}"]
}
#[test]
-fn proxy_listing_publish_uses_myc_nip46_sdk_signer() {
- let sandbox = RadrootsCliSandbox::new();
- let user_identity = identity_secret(93);
- let client_identity = identity_secret(94);
- let remote_identity = identity_secret(95);
- let user_public = user_identity.to_public();
- let user_keys = nostr_keys_from_identity(&user_identity);
- let remote_keys = nostr_keys_from_identity(&remote_identity);
- let remote_pubkey = remote_keys.public_key().to_hex();
- let relay = Nip46RelayServer::new(
- remote_keys.clone(),
- user_keys,
- Nip46RelayFinish::SignResponse,
- );
- let proxy = ProxyJsonRpcServer::once(Some("proxy_test_token"));
- let token_file = proxy_token_file(&sandbox);
- let public_identity_file =
- write_public_identity_profile(&sandbox, "myc-proxy-user", &user_public);
- let imported = sandbox.json_success(&[
- "--format",
- "json",
- "--approval-token",
- "approve",
- "account",
- "import",
- "--default",
- public_identity_file.to_string_lossy().as_ref(),
- ]);
- let account_id = imported["result"]["account"]["id"]
- .as_str()
- .expect("account id");
- store_test_session_secret(
- &sandbox,
- "session_ready",
- client_identity.secret_key_hex().as_str(),
- );
- let config = format!(
- r#"[transport]
-profile = "proxy"
-
-[transport.proxy]
-url = "{}"
-token_file = "{}"
-
-{}"#,
- toml_string(proxy.endpoint()),
- toml_string(token_file.display().to_string().as_str()),
- myc_nip46_config(
- remote_pubkey.as_str(),
- relay.endpoint(),
- account_id,
- "session_ready",
- ),
- );
- sandbox.write_app_config(config.as_str());
- let farm = sandbox.json_success(&[
- "--format",
- "json",
- "farm",
- "create",
- "--name",
- "Myc Proxy Farm",
- "--location",
- "farmstand",
- "--city",
- "San Francisco",
- "--country",
- "US",
- "--geohash",
- "9q8yy",
- "--delivery-method",
- "pickup",
- ]);
- let listing_file = create_listing_draft(&sandbox, "myc-proxy-live");
- make_listing_publishable(
- &listing_file,
- farm["result"]["config"]["farm_d_tag"]
- .as_str()
- .expect("farm d tag"),
- );
-
- let output = sandbox
- .command()
- .args([
- "--format",
- "json",
- "--approval-token",
- "approve",
- "listing",
- "publish",
- listing_file.to_string_lossy().as_ref(),
- ])
- .output()
- .expect("run myc proxy listing publish");
- let value: Value = serde_json::from_slice(&output.stdout).expect("json output");
-
- assert!(
- output.status.success(),
- "stderr `{}` stdout `{}`",
- String::from_utf8_lossy(&output.stderr),
- String::from_utf8_lossy(&output.stdout)
- );
- let report = relay.join();
- let request = proxy.join();
- assert_eq!(value["operation_id"], "listing.publish");
- assert_eq!(value["result"]["state"], "published");
- assert_eq!(
- value["result"]["source"],
- "SDK listing publish · configured signer"
- );
- assert!(report.connection_count >= 1);
- assert!(report.req_count >= 1);
- assert_eq!(report.sign_request_count, 1);
- assert_eq!(report.published_events.len(), 0);
- let event = proxy_request_raw_event(&request);
- assert_eq!(event["kind"], KIND_LISTING);
- assert_eq!(event["pubkey"], user_public.public_key_hex);
- assert_eq!(event["id"], value["result"]["event_id"]);
-}
-
-#[test]
fn listing_update_publish_attempts_direct_publish_with_approval() {
let sandbox = RadrootsCliSandbox::new();
sandbox.json_success(&["--format", "json", "account", "create"]);
@@ -3974,6 +2781,8 @@ fn listing_update_publish_attempts_direct_publish_with_approval() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000103",
"listing",
"update",
listing_file.to_string_lossy().as_ref(),
@@ -4407,25 +3216,6 @@ fn next_actions_mirror_result_actions_for_json_and_ndjson() {
terminal["payload"]["next_actions"][0]["command"],
"radroots store init"
);
-
- for args in [&["--format", "ndjson", "config", "get"][..]] {
- let proxy = RadrootsCliSandbox::new();
- write_proxy_transport_config(&proxy, "");
- let output = proxy.command().args(args).output().expect("run ndjson");
- let frames = ndjson_from_stdout(&output);
- let terminal = frames.last().expect("terminal ndjson frame");
-
- assert!(output.status.success(), "{args:?}");
- assert!(
- terminal["payload"]["next_actions"]
- .as_array()
- .expect("next actions")
- .iter()
- .any(|action| action["description"]
- == "configure RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_FILE or RADROOTS_CLI_TRANSPORT_PROXY_TOKEN_SECRET_ID"),
- "{args:?}"
- );
- }
}
#[test]
@@ -5039,6 +3829,8 @@ fn offline_listing_publish_enqueues_sdk_outbox_without_direct_publish_push() {
"--offline",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000104",
"listing",
"publish",
listing_file.to_string_lossy().as_ref(),
@@ -5062,7 +3854,7 @@ fn offline_listing_publish_enqueues_sdk_outbox_without_direct_publish_push() {
assert!(
sandbox
.root()
- .join("data/apps/cli/replica/sdk/outbox.sqlite")
+ .join("data/apps/cli/replica/sdk/runtime.sqlite")
.exists()
);
assert_eq!(
@@ -5099,7 +3891,7 @@ fn listing_publish_idempotency_conflict_maps_sdk_partial_mutation_recovery() {
let listing_file = create_listing_draft(&sandbox, "idem-conflict");
make_listing_publishable(&listing_file, farm_d_tag);
let relay = "ws://127.0.0.1:9";
- let idempotency_key = "listing-idem-conflict";
+ let idempotency_key = "01890f3c-7f2a-7b01-8000-000000000102";
configure_nostr_transport(&sandbox, relay);
sandbox.json_success(&[
@@ -5480,7 +4272,7 @@ fn removed_transport_profile_flag_is_rejected() {
let sandbox = RadrootsCliSandbox::new();
let output = sandbox
.command()
- .args(["--publish-transport", "proxy", "sync", "push"])
+ .args(["--publish-transport", "radrootsd", "sync", "push"])
.output()
.expect("run removed publish transport flag");
let stderr = String::from_utf8(output.stderr).expect("utf8 stderr");
@@ -5580,8 +4372,7 @@ fn store_backup_and_restore_use_sdk_canonical_store() {
status["result"]["derived_projection"]["source"],
"local derived projection cache"
);
- assert!(sdk_root.join("event_store.sqlite").exists());
- assert!(sdk_root.join("outbox.sqlite").exists());
+ assert!(sdk_root.join("runtime.sqlite").exists());
let derived_projection_init = sandbox.json_success(&["--format", "json", "store", "init"]);
assert_eq!(derived_projection_init["operation_id"], "store.init");
@@ -5679,10 +4470,10 @@ fn store_backup_and_restore_use_sdk_canonical_store() {
true
);
assert!(
- backup["result"]["manifest"]["source_paths"]["event_store_path"]
+ backup["result"]["manifest"]["source_paths"]["runtime_path"]
.as_str()
- .expect("source event store path")
- .contains("data/apps/cli/replica/sdk/event_store.sqlite")
+ .expect("source runtime path")
+ .contains("data/apps/cli/replica/sdk/runtime.sqlite")
);
assert!(!event_store_file.ends_with("replica.sqlite"));
@@ -5747,19 +4538,18 @@ fn store_backup_and_restore_use_sdk_canonical_store() {
);
assert_eq!(restored["result"]["restore_kind"], "sdk_canonical");
assert_eq!(restored["result"]["canonical_store"], "sdk");
- assert!(restore_destination.join("event_store.sqlite").exists());
- assert!(restore_destination.join("outbox.sqlite").exists());
+ assert!(restore_destination.join("runtime.sqlite").exists());
assert_eq!(
restored["result"]["restored_event_store_file"],
restore_destination
- .join("event_store.sqlite")
+ .join("runtime.sqlite")
.to_string_lossy()
.to_string()
);
assert_eq!(
restored["result"]["restored_outbox_file"],
restore_destination
- .join("outbox.sqlite")
+ .join("runtime.sqlite")
.to_string_lossy()
.to_string()
);
@@ -7489,6 +6279,8 @@ fn farm_publish_uses_sdk_outbox_without_legacy_signed_event_records() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000101",
"farm",
"publish",
]);
@@ -7561,6 +6353,8 @@ fn listing_publish_failure_uses_sdk_outbox_without_legacy_runtime_store_record()
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000106",
"listing",
"publish",
listing_file.to_string_lossy().as_ref(),
@@ -7634,6 +6428,8 @@ fn sync_push_sdk_outbox_failure_reports_network_unavailable() {
"--offline",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000105",
"listing",
"publish",
listing_file.to_string_lossy().as_ref(),
@@ -7709,7 +6505,7 @@ fn sync_push_ignores_derived_projection_pending_queue_for_sdk_canonical_push() {
DERIVED_PROJECTION_SYNC_PUSH_FARM_D_TAG,
selected_pubkey,
);
- let executor = SqliteExecutor::open(sandbox.replica_store_path()).expect("open replica");
+ let executor = SqlxSqliteExecutor::open(sandbox.replica_store_path()).expect("open replica");
let derived_projection_batch = radroots_replica_pending_publish_batch(&executor)
.expect("derived projection pending batch");
assert!(derived_projection_batch.pending_count > 0);
@@ -9152,39 +7948,21 @@ fn buyer_side_order_writes_reject_conflicting_account_override_for_local_draft()
.expect("drift account id");
configure_nostr_transport(&sandbox, "ws://127.0.0.1:9");
- for (operation_id, command) in [
- (
- "trade.revision.accept",
- vec![
- "--format",
- "json",
- "--dry-run",
- "--account-id",
- drift_account_id,
- "trade",
- "revision",
- "accept",
- order_id.as_str(),
- "--revision-id",
- "rev_pending",
- ],
- ),
- (
- "trade.cancel",
- vec![
- "--format",
- "json",
- "--dry-run",
- "--account-id",
- drift_account_id,
- "trade",
- "cancel",
- order_id.as_str(),
- "--reason",
- "changed plans",
- ],
- ),
- ] {
+ for (operation_id, command) in [(
+ "trade.cancel",
+ vec![
+ "--format",
+ "json",
+ "--dry-run",
+ "--account-id",
+ drift_account_id,
+ "trade",
+ "cancel",
+ order_id.as_str(),
+ "--reason",
+ "changed plans",
+ ],
+ )] {
let (output, value) = sandbox.json_output(command.as_slice());
assert!(!output.status.success(), "{operation_id} should fail");
@@ -9212,6 +7990,8 @@ fn order_submit_non_dry_run_uses_sdk_relay_validation_without_replica_freshness_
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000200",
"trade",
"submit",
order_id.as_str(),
@@ -9258,6 +8038,8 @@ fn order_submit_non_dry_run_uses_sdk_relay_validation_without_listing_state_gate
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000201",
"trade",
"submit",
order_id.as_str(),
@@ -9284,6 +8066,8 @@ fn order_submit_non_dry_run_uses_sdk_relay_validation_without_listing_event_gate
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000202",
"trade",
"submit",
order_id.as_str(),
@@ -9337,6 +8121,8 @@ fn order_submit_non_dry_run_uses_sdk_relay_validation_without_quantity_gate() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000203",
"trade",
"submit",
order_id,
@@ -9364,6 +8150,8 @@ fn order_submit_non_dry_run_uses_sdk_relay_validation_without_bin_gate() {
"json",
"--approval-token",
"approve",
+ "--idempotency-key",
+ "01890f3c-7f2a-7b01-8000-000000000204",
"trade",
"submit",
order_id.as_str(),