commit 7e030b0414d2bad0e79a2b07a1b118e681f9231d
parent 5fcb1aa3ff6615a72c134bdd39e832b81a84c93e
Author: triesap <tyson@radroots.org>
Date: Thu, 7 May 2026 05:00:00 +0000
cli: align listing publish readiness
- make radrootsd publish readiness capability-aware
- report missing bridge auth and signer-session binding
- mark configured daemon listing publish as executable
- cover readiness and not-implemented regressions
Diffstat:
2 files changed, 108 insertions(+), 25 deletions(-)
diff --git a/src/operation_core.rs b/src/operation_core.rs
@@ -30,7 +30,9 @@ use crate::runtime::accounts::{
resolve_account_selector, secret_backend_status, select_account, snapshot,
unresolved_account_reason,
};
-use crate::runtime::config::{PublishMode, RuntimeConfig, SignerBackend};
+use crate::runtime::config::{
+ PublishMode, RuntimeConfig, SIGNER_REMOTE_NIP46_CAPABILITY, SignerBackend,
+};
use crate::runtime::logging::LoggingState;
use crate::runtime_args::LocalExportFormatArg;
@@ -722,27 +724,25 @@ fn publish_runtime_view(
},
}
}
- PublishMode::Radrootsd => PublishRuntimeView {
- mode: config.publish.mode.as_str().to_owned(),
- source,
- transport_family: config.publish.mode.transport_family().to_owned(),
- state: "unavailable".to_owned(),
- executable: false,
- reason: Some(
- "radrootsd publish mode is configured but the radrootsd publish transport is not implemented"
- .to_owned(),
- ),
- signed_write_required,
- relay,
- provider: PublishProviderRuntimeView {
- provider_runtime_id: "radrootsd".to_owned(),
- state: "unavailable".to_owned(),
- source: "publish mode · local first".to_owned(),
- reason: Some(
- "radrootsd publish transport is reserved for a future implementation".to_owned(),
- ),
- },
- },
+ PublishMode::Radrootsd => {
+ let (state, executable, reason) = radrootsd_publish_readiness(config);
+ PublishRuntimeView {
+ mode: config.publish.mode.as_str().to_owned(),
+ source,
+ transport_family: config.publish.mode.transport_family().to_owned(),
+ state: state.to_owned(),
+ executable,
+ reason: reason.clone(),
+ signed_write_required,
+ relay,
+ provider: PublishProviderRuntimeView {
+ provider_runtime_id: "radrootsd".to_owned(),
+ state: state.to_owned(),
+ source: "publish mode · local first".to_owned(),
+ reason,
+ },
+ }
+ }
}
}
@@ -802,6 +802,47 @@ fn nostr_relay_publish_readiness(
("ready", true, None)
}
+fn radrootsd_publish_readiness(config: &RuntimeConfig) -> (&'static str, bool, Option<String>) {
+ if config.rpc.bridge_bearer_token.is_none() {
+ return (
+ "unconfigured",
+ false,
+ Some(
+ "radrootsd listing publish requires bridge bearer token configuration from RADROOTS_RPC_BEARER_TOKEN"
+ .to_owned(),
+ ),
+ );
+ }
+
+ if !radrootsd_signer_session_binding_configured(config) {
+ return (
+ "unconfigured",
+ false,
+ Some(
+ "radrootsd listing publish requires a signer.remote_nip46 capability binding with signer_session_ref for config and health readiness"
+ .to_owned(),
+ ),
+ );
+ }
+
+ (
+ "ready",
+ true,
+ Some(
+ "radrootsd bridge endpoint, bridge auth, and signer-session binding are configured; live daemon readiness is verified when listing publish runs"
+ .to_owned(),
+ ),
+ )
+}
+
+fn radrootsd_signer_session_binding_configured(config: &RuntimeConfig) -> bool {
+ config
+ .capability_binding(SIGNER_REMOTE_NIP46_CAPABILITY)
+ .and_then(|binding| binding.signer_session_ref.as_deref())
+ .map(str::trim)
+ .is_some_and(|value| !value.is_empty())
+}
+
fn health_status_state(store_state: &str, publish: &PublishRuntimeView) -> &'static str {
if store_state == "ready" && publish_runtime_ready(publish) {
"ready"
diff --git a/tests/target_cli.rs b/tests/target_cli.rs
@@ -210,8 +210,9 @@ fn config_get_exposes_resolved_publish_state() {
"user config · local first"
);
assert_eq!(value["result"]["publish"]["transport_family"], "radrootsd");
- assert_eq!(value["result"]["publish"]["state"], "unavailable");
+ assert_eq!(value["result"]["publish"]["state"], "unconfigured");
assert_eq!(value["result"]["publish"]["executable"], false);
+ assert_contains(&value["result"]["publish"]["reason"], "bridge bearer token");
assert_eq!(
value["result"]["publish"]["provider"]["provider_runtime_id"],
"radrootsd"
@@ -220,6 +221,43 @@ fn config_get_exposes_resolved_publish_state() {
}
#[test]
+fn config_get_marks_radrootsd_listing_publish_ready_with_bridge_auth_and_session_binding() {
+ let sandbox = RadrootsCliSandbox::new();
+ sandbox.write_app_config(
+ r#"[publish]
+mode = "radrootsd"
+
+[[capability_binding]]
+capability = "signer.remote_nip46"
+provider = "myc"
+target_kind = "explicit_endpoint"
+target = "http://myc.invalid"
+signer_session_ref = "session_ready"
+"#,
+ );
+
+ let mut command = sandbox.command();
+ command
+ .env("RADROOTS_RPC_BEARER_TOKEN", "bridge_test")
+ .args(["--format", "json", "config", "get"]);
+ let output = command.output().expect("run config get");
+ let value: Value = serde_json::from_slice(&output.stdout).expect("json output");
+
+ assert!(output.status.success());
+ assert_eq!(value["operation_id"], "config.get");
+ assert_eq!(value["result"]["publish"]["mode"], "radrootsd");
+ assert_eq!(value["result"]["publish"]["relay"]["ready"], false);
+ assert_eq!(value["result"]["publish"]["state"], "ready");
+ assert_eq!(value["result"]["publish"]["executable"], true);
+ assert_contains(
+ &value["result"]["publish"]["reason"],
+ "live daemon readiness is verified when listing publish runs",
+ );
+ assert_eq!(value["result"]["publish"]["provider"]["state"], "ready");
+ assert_eq!(value["result"]["rpc"]["bridge_auth_configured"], true);
+}
+
+#[test]
fn config_get_distinguishes_relay_ready_from_missing_signed_write_account() {
let sandbox = RadrootsCliSandbox::new();
@@ -348,8 +386,9 @@ fn health_surfaces_publish_state_under_deferred_signer_mode() {
assert_eq!(value["result"]["publish"]["executable"], false);
assert_eq!(
value["result"]["publish"]["provider"]["state"],
- "unavailable"
+ "unconfigured"
);
+ assert_contains(&value["result"]["publish"]["reason"], "bridge bearer token");
assert_eq!(value["errors"].as_array().expect("errors").len(), 0);
}
@@ -389,7 +428,10 @@ fn health_check_exposes_publish_readiness() {
assert_eq!(value["operation_id"], "health.check.run");
assert_eq!(value["result"]["state"], "needs_attention");
assert_eq!(value["result"]["checks"]["publish"]["mode"], "radrootsd");
- assert_eq!(value["result"]["checks"]["publish"]["state"], "unavailable");
+ assert_eq!(
+ value["result"]["checks"]["publish"]["state"],
+ "unconfigured"
+ );
assert_eq!(value["result"]["checks"]["publish"]["executable"], false);
assert_eq!(value["errors"].as_array().expect("errors").len(), 0);
}