radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit d335ed8c66204e144959055905298503fba6071a
parent 8f8eb36aaaea091bd4b7c9aa3bafdc005cfd34a5
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Jul 2026 17:42:02 +0000

transport: emit v3 publish capabilities

- return the v3 transport publish capability DTO from JSON-RPC
- assert capability responses use transport, configured, and implementation
- guard capability rows against old transport_kind and implementation_state fields
- verify daemon formatting, check, and transport publish tests

Diffstat:
Msrc/transport/jsonrpc/methods/mod.rs | 10++++++++--
Msrc/transport/jsonrpc/methods/transport_publish.rs | 2+-
Mtests/source_boundary.rs | 42+++++++++++++++++++++++++++++++++++++++---
3 files changed, 48 insertions(+), 6 deletions(-)

diff --git a/src/transport/jsonrpc/methods/mod.rs b/src/transport/jsonrpc/methods/mod.rs @@ -140,11 +140,17 @@ mod tests { assert!(response.get().contains("\"scoped_bearer_token\"")); assert!(response.get().contains("\"signed_event_ingress\":true")); assert!(response.get().contains("\"transports\":[")); - assert!(response.get().contains("\"transport_kind\":\"reticulum\"")); assert!( response .get() - .contains("\"implementation_state\":\"preview_unavailable\"") + .contains("\"api_version\":\"radrootsd.transport_publish.v3\"") + ); + assert!(response.get().contains("\"transport\":\"reticulum\"")); + assert!(response.get().contains("\"configured\":true")); + assert!( + response + .get() + .contains("\"implementation\":\"preview_unavailable\"") ); assert!(response.get().contains("\"usable_for_delivery\":false")); assert!( diff --git a/src/transport/jsonrpc/methods/transport_publish.rs b/src/transport/jsonrpc/methods/transport_publish.rs @@ -37,7 +37,7 @@ fn register_capabilities( registry.track(METHOD_CAPABILITIES); module.register_async_method(METHOD_CAPABILITIES, |_params, ctx, extensions| async move { require_publish_principal(&extensions)?; - Ok::<TransportPublishCapabilities, RpcError>(TransportPublishCapabilities::v2( + Ok::<TransportPublishCapabilities, RpcError>(TransportPublishCapabilities::v3( ctx.state.transport_publish.config.max_event_bytes, ctx.state.transport_publish.config.max_targets_per_request, )) diff --git a/tests/source_boundary.rs b/tests/source_boundary.rs @@ -212,8 +212,10 @@ fn transport_publish_capabilities_expose_per_transport_readiness() { ); for required in [ "transport.publish.capabilities", - r#"\"transport_kind\":\"reticulum\""#, - r#"\"implementation_state\":\"preview_unavailable\""#, + r#"\"api_version\":\"radrootsd.transport_publish.v3\""#, + r#"\"transport\":\"reticulum\""#, + r#"\"configured\":true"#, + r#"\"implementation\":\"preview_unavailable\""#, r#"\"usable_for_delivery\":false"#, "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE", ] { @@ -229,7 +231,9 @@ fn transport_publish_capabilities_expose_per_transport_readiness() { .as_path(), ); for required in [ - "pub implementation_state: TransportPublishImplementationState,", + "pub transport: String,", + "pub configured: bool,", + "pub implementation: TransportPublishImplementation,", "pub usable_for_delivery: bool,", "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE", ] { @@ -238,6 +242,23 @@ fn transport_publish_capabilities_expose_per_transport_readiness() { "transport publish protocol must retain capability field `{required}`" ); } + let capability_source = source_window( + protocol_source.as_str(), + "pub struct TransportPublishTransportCapability", + "#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(rename_all = \"snake_case\"))]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum TransportPublishDeliveryPolicyName", + ); + for forbidden in [ + "pub transport_kind: String,", + concat!( + "pub implementation", + "_state: TransportPublishImplementationState," + ), + ] { + assert!( + !capability_source.contains(forbidden), + "transport publish capability rows must not retain removed field `{forbidden}`" + ); + } } #[test] @@ -302,6 +323,21 @@ fn relative_path(root: &Path, path: &Path) -> String { .replace('\\', "/") } +fn source_window<'source>( + source: &'source str, + start_marker: &str, + end_marker: &str, +) -> &'source str { + let start = source + .find(start_marker) + .unwrap_or_else(|| panic!("source must contain start marker `{start_marker}`")); + let end = source[start..] + .find(end_marker) + .map(|index| start + index) + .unwrap_or_else(|| panic!("source must contain end marker `{end_marker}`")); + &source[start..end] +} + fn contains_forbidden_concept(source: &str, pattern: &str) -> bool { source.match_indices(pattern).any(|(index, _)| { let before = source[..index].chars().next_back();