commit bebee95eb806dd4b9c9a566e23c2a6668c89faf7
parent ed50478f8f2afefdf6eaeda96591267e6e87f77f
Author: triesap <tyson@radroots.org>
Date: Mon, 27 Apr 2026 09:38:43 +0000
cli: remove deferred myc runtime path
- delete the inactive MYC status command runner
- fail closed for MYC signer authority without probing executables
- quarantine inactive provider status helpers behind tests
- keep local signer guardrail suites green
Diffstat:
7 files changed, 75 insertions(+), 856 deletions(-)
diff --git a/src/runtime/farm.rs b/src/runtime/farm.rs
@@ -767,11 +767,6 @@ fn binding_error_publish_view(
reason,
vec!["run radroots signer status get".to_owned()],
),
- ActorWriteBindingError::Unavailable(reason) => (
- "unavailable".to_owned(),
- reason,
- vec!["run radroots signer status get".to_owned()],
- ),
};
base_publish_view(
state.as_str(),
diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs
@@ -1510,11 +1510,6 @@ fn binding_error_view(
reason,
vec!["run radroots signer status get".to_owned()],
),
- ActorWriteBindingError::Unavailable(reason) => (
- "unavailable".to_owned(),
- reason,
- vec!["run radroots signer status get".to_owned()],
- ),
};
ListingMutationView {
diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs
@@ -2,7 +2,6 @@ pub mod accounts;
pub mod config;
pub mod daemon;
pub mod farm;
-#[allow(dead_code)]
pub mod farm_config;
pub mod find;
pub mod hyf;
@@ -10,7 +9,6 @@ pub mod listing;
pub mod local;
pub mod logging;
pub mod management;
-pub mod myc;
pub mod network;
pub mod order;
pub mod paths;
diff --git a/src/runtime/myc.rs b/src/runtime/myc.rs
@@ -1,421 +0,0 @@
-use std::io::Read;
-use std::process::{Child, Command, ExitStatus, Output, Stdio};
-use std::thread;
-use std::time::{Duration, Instant};
-
-use radroots_nostr_signer::prelude::{
- RadrootsNostrLocalSignerCapability, RadrootsNostrRemoteSessionSignerCapability,
-};
-use serde::Deserialize;
-
-use crate::domain::runtime::{
- IdentityPublicView, LocalSignerStatusView, MycCustodyIdentityView, MycCustodyView,
- MycRemoteSessionView, MycStatusView,
-};
-use crate::runtime::config::MycConfig;
-
-const MYC_SIGNER_STATUS_CONTRACT_VERSION: u32 = 1;
-const MYC_STATUS_VIEW: &str = "signer";
-const MYC_STATUS_POLL_INTERVAL: Duration = Duration::from_millis(10);
-
-pub fn resolve_status(config: &MycConfig) -> MycStatusView {
- let executable = config.executable.display().to_string();
- if config.executable.as_os_str().is_empty() {
- return unavailable_status(
- executable,
- "unconfigured",
- "myc executable path is not configured".to_owned(),
- );
- }
-
- let output = match run_status_command(config) {
- Ok(output) => output,
- Err(MycCommandError::NotFound) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!(
- "myc executable was not found at {}",
- config.executable.display()
- ),
- );
- }
- Err(MycCommandError::Start(error)) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!(
- "failed to start myc status command at {}: {error}",
- config.executable.display()
- ),
- );
- }
- Err(MycCommandError::Timeout) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!(
- "myc status command timed out after {}ms",
- config.status_timeout_ms
- ),
- );
- }
- Err(MycCommandError::Wait(error)) | Err(MycCommandError::Read(error)) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!(
- "failed to capture myc status command output at {}: {error}",
- config.executable.display()
- ),
- );
- }
- };
-
- if !output.status.success() {
- let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned();
- let reason = match output.status.code() {
- Some(code) if stderr.is_empty() => {
- format!("myc status command exited with status code {code}")
- }
- Some(code) => format!("myc status command exited with status code {code}: {stderr}"),
- None if stderr.is_empty() => "myc status command terminated by signal".to_owned(),
- None => format!("myc status command terminated by signal: {stderr}"),
- };
- return unavailable_status(executable, "unavailable", reason);
- }
-
- let stdout = match String::from_utf8(output.stdout) {
- Ok(stdout) => stdout,
- Err(error) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!("myc status output was not valid UTF-8: {error}"),
- );
- }
- };
-
- let payload_value = match serde_json::from_str::<serde_json::Value>(stdout.as_str()) {
- Ok(payload) => payload,
- Err(error) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!("myc signer status output was not valid JSON: {error}"),
- );
- }
- };
- let payload = match serde_json::from_value::<MycStatusPayload>(payload_value) {
- Ok(payload) => payload,
- Err(error) => {
- return unavailable_status(
- executable,
- "unavailable",
- format!(
- "myc signer status output did not match contract version {MYC_SIGNER_STATUS_CONTRACT_VERSION}: {error}"
- ),
- );
- }
- };
-
- let MycStatusPayload {
- status_contract_version,
- status,
- ready,
- reasons,
- signer_backend,
- custody,
- } = payload;
- if status_contract_version != MYC_SIGNER_STATUS_CONTRACT_VERSION {
- return unavailable_status(
- executable,
- "unavailable",
- format!(
- "myc signer status contract version {status_contract_version} is incompatible with cli expected {MYC_SIGNER_STATUS_CONTRACT_VERSION}"
- ),
- );
- }
- let MycSignerBackendPayload {
- local_signer,
- remote_session_count,
- remote_sessions,
- } = signer_backend;
-
- let remote_sessions = remote_sessions
- .iter()
- .map(remote_session_status_view)
- .collect::<Vec<_>>();
- let local_signer = local_signer.map(local_signer_status_view);
- let remote_session_count = remote_session_count.max(remote_sessions.len());
- let custody = custody.into_view();
- let state = if ready {
- "ready"
- } else {
- match status.as_str() {
- "degraded" => "degraded",
- _ => "unavailable",
- }
- };
- let reason = primary_reason(ready, status.as_str(), reasons.as_slice());
-
- MycStatusView {
- executable,
- state: state.to_owned(),
- source: "myc status command · local first".to_owned(),
- service_status: Some(status),
- ready,
- reason,
- reasons,
- remote_session_count,
- local_signer,
- remote_sessions,
- custody,
- }
-}
-
-fn run_status_command(config: &MycConfig) -> Result<Output, MycCommandError> {
- let mut child = Command::new(&config.executable)
- .args(["status", "--view", MYC_STATUS_VIEW])
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .spawn()
- .map_err(|error| match error.kind() {
- std::io::ErrorKind::NotFound => MycCommandError::NotFound,
- _ => MycCommandError::Start(error),
- })?;
-
- let started_at = Instant::now();
- let status_timeout = Duration::from_millis(config.status_timeout_ms);
- loop {
- match child.try_wait() {
- Ok(Some(status)) => return collect_output(child, status),
- Ok(None) => {
- if started_at.elapsed() >= status_timeout {
- let _ = child.kill();
- let _ = child.wait();
- return Err(MycCommandError::Timeout);
- }
- thread::sleep(MYC_STATUS_POLL_INTERVAL);
- }
- Err(error) => {
- let _ = child.kill();
- let _ = child.wait();
- return Err(MycCommandError::Wait(error));
- }
- }
- }
-}
-
-fn collect_output(mut child: Child, status: ExitStatus) -> Result<Output, MycCommandError> {
- let mut stdout = Vec::new();
- let mut stderr = Vec::new();
-
- if let Some(mut pipe) = child.stdout.take() {
- pipe.read_to_end(&mut stdout)
- .map_err(MycCommandError::Read)?;
- }
- if let Some(mut pipe) = child.stderr.take() {
- pipe.read_to_end(&mut stderr)
- .map_err(MycCommandError::Read)?;
- }
-
- Ok(Output {
- status,
- stdout,
- stderr,
- })
-}
-
-fn local_signer_status_view(
- capability: RadrootsNostrLocalSignerCapability,
-) -> LocalSignerStatusView {
- LocalSignerStatusView {
- account_id: capability.account_id.to_string(),
- public_identity: IdentityPublicView::from_public_identity(&capability.public_identity),
- availability: match capability.availability {
- radroots_nostr_signer::prelude::RadrootsNostrLocalSignerAvailability::PublicOnly => {
- "public_only".to_owned()
- }
- radroots_nostr_signer::prelude::RadrootsNostrLocalSignerAvailability::SecretBacked => {
- "secret_backed".to_owned()
- }
- },
- secret_backed: capability.is_secret_backed(),
- backend: "myc".to_owned(),
- used_fallback: false,
- }
-}
-
-fn remote_session_status_view(
- capability: &RadrootsNostrRemoteSessionSignerCapability,
-) -> MycRemoteSessionView {
- MycRemoteSessionView {
- connection_id: capability.connection_id.to_string(),
- signer_identity: IdentityPublicView::from_public_identity(&capability.signer_identity),
- user_identity: IdentityPublicView::from_public_identity(&capability.user_identity),
- relay_count: capability.relays.len(),
- permissions: capability
- .permissions
- .as_slice()
- .iter()
- .map(ToString::to_string)
- .collect(),
- }
-}
-
-fn primary_reason(ready: bool, service_status: &str, reasons: &[String]) -> Option<String> {
- if ready {
- return None;
- }
-
- reasons
- .first()
- .cloned()
- .or_else(|| Some(format!("myc reported service status `{service_status}`")))
-}
-
-fn unavailable_status(executable: String, state: &str, reason: String) -> MycStatusView {
- MycStatusView {
- executable,
- state: state.to_owned(),
- source: "myc status command · local first".to_owned(),
- service_status: None,
- ready: false,
- reason: Some(reason),
- reasons: Vec::new(),
- remote_session_count: 0,
- local_signer: None,
- remote_sessions: Vec::new(),
- custody: None,
- }
-}
-
-enum MycCommandError {
- NotFound,
- Start(std::io::Error),
- Wait(std::io::Error),
- Read(std::io::Error),
- Timeout,
-}
-
-#[derive(Debug, Deserialize)]
-struct MycStatusPayload {
- status_contract_version: u32,
- status: String,
- ready: bool,
- #[serde(default)]
- reasons: Vec<String>,
- #[serde(default)]
- signer_backend: MycSignerBackendPayload,
- #[serde(default)]
- custody: MycCustodyPayload,
-}
-
-#[derive(Debug, Default, Deserialize)]
-struct MycSignerBackendPayload {
- #[serde(default)]
- local_signer: Option<RadrootsNostrLocalSignerCapability>,
- #[serde(default)]
- remote_session_count: usize,
- #[serde(default)]
- remote_sessions: Vec<RadrootsNostrRemoteSessionSignerCapability>,
-}
-
-#[derive(Debug, Default, Deserialize)]
-struct MycCustodyPayload {
- #[serde(default)]
- signer: MycCustodyIdentityPayload,
- #[serde(default)]
- user: MycCustodyIdentityPayload,
- #[serde(default)]
- discovery_app: Option<MycCustodyIdentityPayload>,
-}
-
-impl MycCustodyPayload {
- fn into_view(self) -> Option<MycCustodyView> {
- if !self.signer.has_data()
- && !self.user.has_data()
- && self
- .discovery_app
- .as_ref()
- .is_none_or(|identity| !identity.has_data())
- {
- return None;
- }
-
- Some(MycCustodyView {
- signer: self.signer.into_view(),
- user: self.user.into_view(),
- discovery_app: self.discovery_app.and_then(|identity| {
- if identity.has_data() {
- Some(identity.into_view())
- } else {
- None
- }
- }),
- })
- }
-}
-
-#[derive(Debug, Default, Deserialize)]
-struct MycCustodyIdentityPayload {
- #[serde(default)]
- resolved: bool,
- #[serde(default)]
- selected_account_id: Option<String>,
- #[serde(default)]
- selected_account_state: Option<String>,
- #[serde(default)]
- identity_id: Option<String>,
- #[serde(default)]
- public_key_hex: Option<String>,
- #[serde(default)]
- error: Option<String>,
-}
-
-impl MycCustodyIdentityPayload {
- fn has_data(&self) -> bool {
- self.resolved
- || self.selected_account_id.is_some()
- || self.selected_account_state.is_some()
- || self.identity_id.is_some()
- || self.public_key_hex.is_some()
- || self.error.is_some()
- }
-
- fn into_view(self) -> MycCustodyIdentityView {
- MycCustodyIdentityView {
- resolved: self.resolved,
- selected_account_id: self.selected_account_id,
- selected_account_state: self.selected_account_state,
- identity_id: self.identity_id,
- public_key_hex: self.public_key_hex,
- error: self.error,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::path::PathBuf;
-
- use super::resolve_status;
- use crate::runtime::config::MycConfig;
-
- #[test]
- fn empty_executable_path_reports_unconfigured_status() {
- let view = resolve_status(&MycConfig {
- executable: PathBuf::new(),
- status_timeout_ms: 2_000,
- });
-
- assert_eq!(view.state, "unconfigured");
- assert_eq!(view.ready, false);
- assert!(
- view.reason
- .as_deref()
- .is_some_and(|value| value.contains("not configured"))
- );
- }
-}
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -1408,11 +1408,6 @@ fn order_binding_error_view(
reason,
vec!["run radroots signer status get".to_owned()],
),
- ActorWriteBindingError::Unavailable(reason) => (
- "unavailable".to_owned(),
- reason,
- vec!["run radroots signer status get".to_owned()],
- ),
};
let mut actions = actions;
diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs
@@ -1,28 +1,43 @@
-use std::path::{Path, PathBuf};
+#[cfg(test)]
+use std::path::Path;
+use std::path::PathBuf;
use radroots_runtime_manager::{ManagedRuntimeInstallState, load_registry, read_secret_file};
+#[cfg(test)]
use radroots_runtime_paths::{
RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace,
};
+#[cfg(test)]
use radroots_sdk::RadrootsSdkConfig;
use url::Url;
+#[cfg(test)]
use crate::runtime::config::{
CapabilityBindingInspection, CapabilityBindingInspectionState, CapabilityBindingTargetKind,
INFERENCE_HYF_STDIO_CAPABILITY, RuntimeConfig, WORKFLOW_TRADE_CAPABILITY,
WRITE_PLANE_TRADE_JSONRPC_CAPABILITY,
};
+#[cfg(not(test))]
+use crate::runtime::config::{
+ CapabilityBindingTargetKind, RuntimeConfig, WRITE_PLANE_TRADE_JSONRPC_CAPABILITY,
+};
+#[cfg(test)]
use crate::runtime::hyf;
+#[cfg(test)]
const WORKFLOW_PROVIDER_RUNTIME_ID: &str = "rhi";
+#[cfg(test)]
const WORKFLOW_TARGET: &str = "workflow-default";
+#[cfg(test)]
const WORKFLOW_IDENTITY_FILE_NAME: &str = "identity.secret.json";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderProvenance {
ExplicitBinding,
ManagedDefault,
+ #[cfg(test)]
DirectConfig,
+ #[cfg(test)]
Disabled,
Unavailable,
}
@@ -32,13 +47,16 @@ impl ProviderProvenance {
match self {
Self::ExplicitBinding => "explicit_binding",
Self::ManagedDefault => "managed_default",
+ #[cfg(test)]
Self::DirectConfig => "direct_config",
+ #[cfg(test)]
Self::Disabled => "disabled",
Self::Unavailable => "unavailable",
}
}
}
+#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedProviderView {
pub capability_id: String,
@@ -70,6 +88,7 @@ pub struct ResolvedWritePlaneTarget {
pub bridge_bearer_token: String,
}
+#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkflowProviderView {
pub provider_runtime_id: String,
@@ -84,6 +103,7 @@ pub struct WorkflowProviderView {
pub reason: Option<String>,
}
+#[cfg(test)]
impl WorkflowProviderView {
pub fn detail(&self) -> String {
match self.state.as_str() {
@@ -104,6 +124,7 @@ impl WorkflowProviderView {
}
}
+#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HyfProviderView {
pub provider_runtime_id: String,
@@ -128,6 +149,7 @@ enum WritePlaneResolution {
Unconfigured(WritePlaneProviderView),
}
+#[cfg(test)]
pub fn resolve_write_plane_provider(config: &RuntimeConfig) -> WritePlaneProviderView {
match resolve_write_plane_resolution(config) {
WritePlaneResolution::Ready { view, .. } | WritePlaneResolution::Unconfigured(view) => view,
@@ -143,6 +165,7 @@ pub fn resolve_actor_write_plane_target(
}
}
+#[cfg(test)]
pub fn resolve_workflow_provider(config: &RuntimeConfig) -> WorkflowProviderView {
let binding = inspect_binding(config, WORKFLOW_TRADE_CAPABILITY);
let (state, provenance, reason) = match binding.state {
@@ -182,6 +205,7 @@ pub fn resolve_workflow_provider(config: &RuntimeConfig) -> WorkflowProviderView
}
}
+#[cfg(test)]
pub fn resolve_hyf_provider(config: &RuntimeConfig) -> HyfProviderView {
let binding = inspect_binding(config, INFERENCE_HYF_STDIO_CAPABILITY);
let status = hyf::resolve_runtime_status(config);
@@ -219,6 +243,7 @@ pub fn resolve_hyf_provider(config: &RuntimeConfig) -> HyfProviderView {
}
}
+#[cfg(test)]
pub fn resolve_capability_providers(config: &RuntimeConfig) -> Vec<ResolvedProviderView> {
let write = resolve_write_plane_provider(config);
let workflow = resolve_workflow_provider(config);
@@ -490,6 +515,7 @@ fn validate_write_plane_url(value: &str) -> Result<String, String> {
Ok(trimmed.to_owned())
}
+#[cfg(test)]
fn resolve_workflow_execution_state(
config: &RuntimeConfig,
binding: &CapabilityBindingInspection,
@@ -587,6 +613,7 @@ fn resolve_workflow_execution_state(
)
}
+#[cfg(test)]
fn canonical_local_relay_url() -> Result<String, String> {
let config = RadrootsSdkConfig::local();
let relays = config
@@ -598,12 +625,14 @@ fn canonical_local_relay_url() -> Result<String, String> {
.ok_or_else(|| "canonical localhost relay config did not define any relay urls".to_owned())
}
+#[cfg(test)]
fn canonical_local_radrootsd_url() -> Result<String, String> {
RadrootsSdkConfig::local()
.resolved_radrootsd_endpoint()
.map_err(|error| format!("resolve canonical localhost radrootsd endpoint: {error}"))
}
+#[cfg(test)]
fn workflow_identity_path(repo_local_root: &Path) -> Result<PathBuf, String> {
let base_paths = RadrootsPathResolver::current()
.resolve(
@@ -626,6 +655,7 @@ fn workflow_identity_path(repo_local_root: &Path) -> Result<PathBuf, String> {
.join(WORKFLOW_IDENTITY_FILE_NAME))
}
+#[cfg(test)]
fn loopback_endpoint_matches(configured: &str, canonical: &str) -> bool {
let Ok(configured_url) = Url::parse(configured) else {
return false;
@@ -638,6 +668,7 @@ fn loopback_endpoint_matches(configured: &str, canonical: &str) -> bool {
&& loopback_host_matches(configured_url.host_str(), canonical_url.host_str())
}
+#[cfg(test)]
fn loopback_host_matches(left: Option<&str>, right: Option<&str>) -> bool {
match (left, right) {
(Some(left), Some(right)) => {
@@ -647,6 +678,7 @@ fn loopback_host_matches(left: Option<&str>, right: Option<&str>) -> bool {
}
}
+#[cfg(test)]
fn normalize_loopback_host(host: &str) -> &str {
if host.eq_ignore_ascii_case("localhost") {
"127.0.0.1"
@@ -655,6 +687,7 @@ fn normalize_loopback_host(host: &str) -> &str {
}
}
+#[cfg(test)]
fn inspect_binding(config: &RuntimeConfig, capability_id: &str) -> CapabilityBindingInspection {
config
.inspect_capability_bindings()
@@ -663,6 +696,7 @@ fn inspect_binding(config: &RuntimeConfig, capability_id: &str) -> CapabilityBin
.expect("provider capability binding inspection must exist")
}
+#[cfg(test)]
fn binding_provenance(binding: &CapabilityBindingInspection) -> ProviderProvenance {
match binding.state {
CapabilityBindingInspectionState::Configured => match binding.target_kind.as_deref() {
@@ -674,6 +708,7 @@ fn binding_provenance(binding: &CapabilityBindingInspection) -> ProviderProvenan
}
}
+#[cfg(test)]
fn hyf_target_kind(
config: &RuntimeConfig,
binding: &CapabilityBindingInspection,
@@ -687,6 +722,7 @@ fn hyf_target_kind(
None
}
+#[cfg(test)]
fn hyf_target(config: &RuntimeConfig, binding: &CapabilityBindingInspection) -> Option<String> {
if binding.state == CapabilityBindingInspectionState::Configured {
return binding.target.clone();
@@ -697,6 +733,7 @@ fn hyf_target(config: &RuntimeConfig, binding: &CapabilityBindingInspection) ->
None
}
+#[cfg(test)]
fn hyf_executable(
config: &RuntimeConfig,
binding: &CapabilityBindingInspection,
diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs
@@ -1,12 +1,9 @@
use crate::domain::runtime::{
- IdentityPublicView, LocalSignerStatusView, MycRemoteSessionView, MycStatusView,
- SignerBindingStatusView, SignerStatusView, SignerWriteKindReadinessView,
+ IdentityPublicView, LocalSignerStatusView, SignerBindingStatusView, SignerStatusView,
+ SignerWriteKindReadinessView,
};
use crate::runtime::accounts::{SHARED_ACCOUNT_STORE_SOURCE, empty_account_resolution_view};
-use crate::runtime::config::{
- CapabilityBindingConfig, CapabilityBindingTargetKind, RuntimeConfig,
- SIGNER_REMOTE_NIP46_CAPABILITY, SignerBackend,
-};
+use crate::runtime::config::{RuntimeConfig, SIGNER_REMOTE_NIP46_CAPABILITY, SignerBackend};
use radroots_events::kinds::{KIND_FARM, KIND_LISTING, KIND_PROFILE};
use radroots_events::trade::RadrootsTradeMessageType;
use radroots_nostr_accounts::prelude::RadrootsNostrAccountStatus;
@@ -18,6 +15,7 @@ use serde::{Deserialize, Serialize};
const SIGNER_BINDING_PROVIDER_RUNTIME_ID: &str = "myc";
const SIGNER_BINDING_MODEL: &str = "session_authorized_remote_signer";
+const MYC_DEFERRED_REASON: &str = "signer mode `myc` is deferred; use signer mode `local`";
#[derive(Debug, Clone, Copy)]
struct CliWriteKind {
@@ -26,16 +24,8 @@ struct CliWriteKind {
}
#[derive(Debug, Clone)]
-struct MycBindingResolution {
- view: SignerBindingStatusView,
- resolved_account_id: Option<String>,
- resolved_account_public_key_hex: Option<String>,
-}
-
-#[derive(Debug, Clone)]
pub enum ActorWriteBindingError {
Unconfigured(String),
- Unavailable(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -55,56 +45,16 @@ pub fn resolve_signer_status(config: &RuntimeConfig) -> SignerStatusView {
pub fn resolve_actor_write_authority(
config: &RuntimeConfig,
- actor_role: &str,
- actor_pubkey: &str,
+ _actor_role: &str,
+ _actor_pubkey: &str,
) -> Result<Option<ActorWriteSignerAuthority>, ActorWriteBindingError> {
if !matches!(config.signer.backend, SignerBackend::Myc) {
return Ok(None);
}
- let myc = crate::runtime::myc::resolve_status(&config.myc);
- let resolution = resolve_myc_binding(config, &myc);
- match resolution.view.state.as_str() {
- "ready" => {}
- "unavailable" => {
- return Err(ActorWriteBindingError::Unavailable(
- resolution.view.reason.unwrap_or_else(|| {
- "myc signer binding is unavailable for actor-authored writes".to_owned()
- }),
- ));
- }
- _ => {
- return Err(ActorWriteBindingError::Unconfigured(
- resolution.view.reason.unwrap_or_else(|| {
- "myc signer binding is not ready for actor-authored writes".to_owned()
- }),
- ));
- }
- }
-
- let Some(resolved_account_public_key_hex) = resolution.resolved_account_public_key_hex else {
- return Err(ActorWriteBindingError::Unconfigured(
- "myc signer binding reported ready without a resolved user identity".to_owned(),
- ));
- };
-
- if !resolved_account_public_key_hex.eq_ignore_ascii_case(actor_pubkey) {
- return Err(ActorWriteBindingError::Unconfigured(format!(
- "configured myc signer binding resolves user pubkey `{resolved_account_public_key_hex}` instead of {actor_role} pubkey `{actor_pubkey}`"
- )));
- }
-
- let Some(resolved_account_id) = resolution.resolved_account_id else {
- return Err(ActorWriteBindingError::Unconfigured(
- "myc signer binding reported ready without a resolved account identity".to_owned(),
- ));
- };
-
- Ok(Some(ActorWriteSignerAuthority {
- provider_runtime_id: SIGNER_BINDING_PROVIDER_RUNTIME_ID.to_owned(),
- account_identity_id: resolved_account_id,
- provider_signer_session_id: resolution.view.resolved_signer_session_id.clone(),
- }))
+ Err(ActorWriteBindingError::Unconfigured(
+ MYC_DEFERRED_REASON.to_owned(),
+ ))
}
fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
@@ -273,42 +223,17 @@ fn resolve_myc_signer_status(config: &RuntimeConfig) -> SignerStatusView {
Ok(resolution) => crate::runtime::accounts::account_resolution_view(&resolution),
Err(_) => empty_account_resolution_view(),
};
- let myc = crate::runtime::myc::resolve_status(&config.myc);
- let resolution = resolve_myc_binding(config, &myc);
- let binding = resolution.view;
- let state = myc_signer_state(&myc, &binding).to_owned();
- let resolved_session = binding
- .resolved_signer_session_id
- .as_deref()
- .and_then(|session_id| {
- myc.remote_sessions
- .iter()
- .find(|session| session.connection_id == session_id)
- });
- let write_kinds = myc_write_kind_readiness(
- resolved_session,
- binding.state.as_str(),
- binding.reason.as_deref(),
- );
SignerStatusView {
mode: config.signer.backend.as_str().to_owned(),
- state,
- source: if myc.state == "ready" {
- binding.source.clone()
- } else {
- myc.source.clone()
- },
- signer_account_id: resolution.resolved_account_id,
+ state: "unconfigured".to_owned(),
+ source: "target cli signer mode contract".to_owned(),
+ signer_account_id: None,
account_resolution,
- reason: if myc.state == "ready" {
- binding.reason.clone()
- } else {
- myc.reason.clone().or_else(|| binding.reason.clone())
- },
- binding,
- write_kinds,
+ reason: Some(MYC_DEFERRED_REASON.to_owned()),
+ binding: deferred_myc_binding_status(),
+ write_kinds: deferred_write_kind_readiness(),
local: None,
- myc: Some(myc),
+ myc: None,
}
}
@@ -331,293 +256,23 @@ fn disabled_binding_status() -> SignerBindingStatusView {
}
}
-fn resolve_myc_binding(config: &RuntimeConfig, myc: &MycStatusView) -> MycBindingResolution {
- let Some(binding) = config.capability_binding(SIGNER_REMOTE_NIP46_CAPABILITY) else {
- return MycBindingResolution {
- view: SignerBindingStatusView {
- capability_id: SIGNER_REMOTE_NIP46_CAPABILITY.to_owned(),
- provider_runtime_id: SIGNER_BINDING_PROVIDER_RUNTIME_ID.to_owned(),
- binding_model: SIGNER_BINDING_MODEL.to_owned(),
- state: "unconfigured".to_owned(),
- source: "no explicit capability binding".to_owned(),
- target_kind: None,
- target: None,
- managed_account_ref: None,
- signer_session_ref: None,
- resolved_signer_session_id: None,
- matched_session_count: None,
- reason: Some(
- "configure [[capability_binding]] for `signer.remote_nip46` before using myc signer mode"
- .to_owned(),
- ),
- },
- resolved_account_id: None,
- resolved_account_public_key_hex: None,
- };
- };
-
- if !matches!(
- binding.target_kind,
- CapabilityBindingTargetKind::ManagedInstance
- ) {
- return binding_status(
- binding,
- "unsupported",
- None,
- None,
- None,
- format!(
- "signer.remote_nip46 only supports target_kind `managed_instance`; got `{}`",
- binding.target_kind.as_str()
- ),
- );
- }
-
- if binding.target != "default" {
- return binding_status(
- binding,
- "unsupported",
- None,
- None,
- None,
- format!(
- "managed myc target `{}` is not supported yet; use target `default`",
- binding.target
- ),
- );
- }
-
- match myc.state.as_str() {
- "ready" => {}
- "unconfigured" => {
- return binding_status(
- binding,
- "unconfigured",
- None,
- None,
- None,
- myc.reason.clone().unwrap_or_else(|| {
- "myc is not configured for composed signer bindings".to_owned()
- }),
- );
- }
- _ => {
- return binding_status(
- binding,
- "unavailable",
- None,
- None,
- None,
- myc.reason
- .clone()
- .unwrap_or_else(|| "myc is not ready for remote signer bindings".to_owned()),
- );
- }
- }
-
- let signing_sessions = myc
- .remote_sessions
- .iter()
- .filter(|session| session_supports_signing(session))
- .collect::<Vec<_>>();
-
- if let Some(session_ref) = binding.signer_session_ref.as_deref() {
- let Some(session) = myc
- .remote_sessions
- .iter()
- .find(|session| session.connection_id == session_ref)
- else {
- return binding_status(
- binding,
- "unavailable",
- None,
- Some(0),
- None,
- format!("configured signer session `{session_ref}` is not currently available"),
- );
- };
-
- if !session_supports_signing(session) {
- return binding_status(
- binding,
- "unauthorized",
- None,
- Some(1),
- None,
- format!(
- "configured signer session `{session_ref}` is not approved for any cli write event kind"
- ),
- );
- }
-
- if let Some(account_ref) = binding.managed_account_ref.as_deref() {
- if session.user_identity.id != account_ref {
- return binding_status(
- binding,
- "unauthorized",
- None,
- Some(1),
- None,
- format!(
- "configured signer session `{session_ref}` resolves user `{}` instead of managed account `{account_ref}`",
- session.user_identity.id
- ),
- );
- }
- }
-
- return binding_status(
- binding,
- "ready",
- Some(session.connection_id.clone()),
- Some(1),
- Some(session),
- String::new(),
- );
- }
-
- if let Some(account_ref) = binding.managed_account_ref.as_deref() {
- let matching_sessions = signing_sessions
- .into_iter()
- .filter(|session| session.user_identity.id == account_ref)
- .collect::<Vec<_>>();
- return resolve_matching_sessions(binding, account_ref, matching_sessions);
- }
-
- if signing_sessions.is_empty() {
- return binding_status(
- binding,
- "unavailable",
- None,
- Some(0),
- None,
- "no authorized remote signer session currently approves a cli write event kind"
- .to_owned(),
- );
- }
-
- if signing_sessions.len() > 1 {
- return binding_status(
- binding,
- "ambiguous",
- None,
- Some(signing_sessions.len()),
- None,
- "multiple authorized remote signer sessions approve cli write event kinds; set managed_account_ref or signer_session_ref".to_owned(),
- );
- }
-
- let session = signing_sessions
- .into_iter()
- .next()
- .expect("single matching signer session");
- binding_status(
- binding,
- "ready",
- Some(session.connection_id.clone()),
- Some(1),
- Some(session),
- String::new(),
- )
-}
-
-fn resolve_matching_sessions(
- binding: &CapabilityBindingConfig,
- account_ref: &str,
- matching_sessions: Vec<&MycRemoteSessionView>,
-) -> MycBindingResolution {
- if matching_sessions.is_empty() {
- return binding_status(
- binding,
- "unavailable",
- None,
- Some(0),
- None,
- format!(
- "no authorized remote signer session currently matches managed account `{account_ref}`"
- ),
- );
- }
-
- if matching_sessions.len() > 1 {
- return binding_status(
- binding,
- "ambiguous",
- None,
- Some(matching_sessions.len()),
- None,
- format!(
- "multiple authorized remote signer sessions currently match managed account `{account_ref}`; set signer_session_ref"
- ),
- );
- }
-
- let session = matching_sessions
- .into_iter()
- .next()
- .expect("single matching signer session");
- binding_status(
- binding,
- "ready",
- Some(session.connection_id.clone()),
- Some(1),
- Some(session),
- String::new(),
- )
-}
-
-fn binding_status(
- binding: &CapabilityBindingConfig,
- state: &str,
- resolved_signer_session_id: Option<String>,
- matched_session_count: Option<usize>,
- resolved_session: Option<&MycRemoteSessionView>,
- reason: String,
-) -> MycBindingResolution {
- MycBindingResolution {
- view: SignerBindingStatusView {
- capability_id: binding.capability_id.clone(),
- provider_runtime_id: binding.provider_runtime_id.clone(),
- binding_model: binding.binding_model.clone(),
- state: state.to_owned(),
- source: binding.source.as_str().to_owned(),
- target_kind: Some(binding.target_kind.as_str().to_owned()),
- target: Some(binding.target.clone()),
- managed_account_ref: binding.managed_account_ref.clone(),
- signer_session_ref: binding.signer_session_ref.clone(),
- resolved_signer_session_id,
- matched_session_count,
- reason: if reason.is_empty() {
- None
- } else {
- Some(reason)
- },
- },
- resolved_account_id: resolved_session.map(|session| session.user_identity.id.clone()),
- resolved_account_public_key_hex: resolved_session
- .map(|session| session.user_identity.public_key_hex.clone()),
- }
-}
-
-fn myc_signer_state(myc: &MycStatusView, binding: &SignerBindingStatusView) -> &'static str {
- match myc.state.as_str() {
- "degraded" => "degraded",
- "unavailable" => "unavailable",
- "unconfigured" => "unconfigured",
- _ => match binding.state.as_str() {
- "ready" => "ready",
- "unavailable" => "unavailable",
- _ => "unconfigured",
- },
+fn deferred_myc_binding_status() -> SignerBindingStatusView {
+ SignerBindingStatusView {
+ capability_id: SIGNER_REMOTE_NIP46_CAPABILITY.to_owned(),
+ provider_runtime_id: SIGNER_BINDING_PROVIDER_RUNTIME_ID.to_owned(),
+ binding_model: SIGNER_BINDING_MODEL.to_owned(),
+ state: "deferred".to_owned(),
+ source: "target cli signer mode contract".to_owned(),
+ target_kind: None,
+ target: None,
+ managed_account_ref: None,
+ signer_session_ref: None,
+ resolved_signer_session_id: None,
+ matched_session_count: None,
+ reason: Some(MYC_DEFERRED_REASON.to_owned()),
}
}
-fn session_supports_signing(session: &MycRemoteSessionView) -> bool {
- cli_write_kinds()
- .iter()
- .any(|kind| session_allows_event_kind(session, kind.event_kind))
-}
-
fn cli_write_kinds() -> [CliWriteKind; 4] {
[
CliWriteKind {
@@ -655,54 +310,19 @@ fn local_write_kind_readiness(
.collect()
}
-fn myc_write_kind_readiness(
- session: Option<&MycRemoteSessionView>,
- binding_state: &str,
- binding_reason: Option<&str>,
-) -> Vec<SignerWriteKindReadinessView> {
+fn deferred_write_kind_readiness() -> Vec<SignerWriteKindReadinessView> {
cli_write_kinds()
.iter()
- .map(|kind| {
- let permission = format!("sign_event:{}", kind.event_kind);
- match session {
- Some(session) => {
- let ready = session_allows_event_kind(session, kind.event_kind);
- SignerWriteKindReadinessView {
- command: kind.command.to_owned(),
- event_kind: kind.event_kind,
- permission,
- ready,
- reason: if ready {
- None
- } else {
- Some(format!(
- "resolved signer session `{}` is not approved for sign_event:{}",
- session.connection_id, kind.event_kind
- ))
- },
- }
- }
- None => SignerWriteKindReadinessView {
- command: kind.command.to_owned(),
- event_kind: kind.event_kind,
- permission,
- ready: false,
- reason: binding_reason
- .map(str::to_owned)
- .or_else(|| Some(format!("signer binding is {binding_state}"))),
- },
- }
+ .map(|kind| SignerWriteKindReadinessView {
+ command: kind.command.to_owned(),
+ event_kind: kind.event_kind,
+ permission: "signer_mode_local_required".to_owned(),
+ ready: false,
+ reason: Some(MYC_DEFERRED_REASON.to_owned()),
})
.collect()
}
-fn session_allows_event_kind(session: &MycRemoteSessionView, kind: u32) -> bool {
- session
- .permissions
- .iter()
- .any(|permission| permission == "sign_event" || permission == &format!("sign_event:{kind}"))
-}
-
fn local_availability(value: RadrootsNostrLocalSignerAvailability) -> &'static str {
match value {
RadrootsNostrLocalSignerAvailability::PublicOnly => "public_only",