tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit a7bf20f220393b2905e4cd4fbb71564de71d1c62
parent d4a5a46bd0209d04787f83447f094a2c42c13dd2
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 08:38:02 -0700

ops: gate readiness on server bind

Diffstat:
Mcrates/tangle/tests/version.rs | 1+
Mcrates/tangle_runtime/src/ops.rs | 26++++++++++++++++++++++++++
Mcrates/tangle_runtime/src/relay/core.rs | 2+-
Mcrates/tangle_runtime/src/runtime.rs | 14+++++++++++++-
Mcrates/tangle_runtime/src/server.rs | 10++++++++--
Mcrates/tangle_runtime/tests/phase2_acceptance_targets.rs | 1+
6 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/crates/tangle/tests/version.rs b/crates/tangle/tests/version.rs @@ -187,6 +187,7 @@ fn tangle_run_starts_server_and_stays_alive_until_shutdown() { assert_eq!(health_value["status"], "ok"); assert_eq!(ready_value["status"], "ready"); + assert_eq!(ready_value["checks"]["server_bind"], "ready"); assert_eq!(ready_value["checks"]["pocket_storage"], "ready"); assert_eq!(nip11_value["name"], "tangle"); assert_eq!(nip11_value["limitation"]["max_message_length"], 1_048_576); diff --git a/crates/tangle_runtime/src/ops.rs b/crates/tangle_runtime/src/ops.rs @@ -27,6 +27,7 @@ impl BaseRelayReadinessCheckStatus { #[derive(Debug, Clone, PartialEq, Eq)] pub struct BaseRelayReadinessState { config: BaseRelayReadinessCheckStatus, + server_bind: BaseRelayReadinessCheckStatus, relay_identity: BaseRelayReadinessCheckStatus, pocket_storage: BaseRelayReadinessCheckStatus, group_projection: BaseRelayReadinessCheckStatus, @@ -36,6 +37,7 @@ pub struct BaseRelayReadinessState { impl BaseRelayReadinessState { pub fn new( config: BaseRelayReadinessCheckStatus, + server_bind: BaseRelayReadinessCheckStatus, relay_identity: BaseRelayReadinessCheckStatus, pocket_storage: BaseRelayReadinessCheckStatus, group_projection: BaseRelayReadinessCheckStatus, @@ -43,6 +45,7 @@ impl BaseRelayReadinessState { ) -> Self { Self { config, + server_bind, relay_identity, pocket_storage, group_projection, @@ -57,12 +60,30 @@ impl BaseRelayReadinessState { BaseRelayReadinessCheckStatus::Ready, BaseRelayReadinessCheckStatus::Ready, BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + ) + } + + pub fn runtime_ready_before_bind() -> Self { + Self::new( + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::NotReady, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, ) } + pub fn with_server_bind(mut self, server_bind: BaseRelayReadinessCheckStatus) -> Self { + self.server_bind = server_bind; + self + } + pub fn is_ready(&self) -> bool { [ self.config, + self.server_bind, self.relay_identity, self.pocket_storage, self.group_projection, @@ -81,6 +102,7 @@ impl BaseRelayReadinessState { }, checks: BaseRelayReadinessChecksDocument { config: self.config.as_str().to_owned(), + server_bind: self.server_bind.as_str().to_owned(), relay_identity: self.relay_identity.as_str().to_owned(), pocket_storage: self.pocket_storage.as_str().to_owned(), group_projection: self.group_projection.as_str().to_owned(), @@ -104,6 +126,7 @@ pub struct BaseRelayReadinessDocument { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BaseRelayReadinessChecksDocument { pub config: String, + pub server_bind: String, pub relay_identity: String, pub pocket_storage: String, pub group_projection: String, @@ -195,6 +218,7 @@ mod tests { let ready_body = to_bytes(ready.into_body(), usize::MAX).await.expect("body"); let ready_value = serde_json::from_slice::<serde_json::Value>(&ready_body).expect("json"); assert_eq!(ready_value["status"], "ready"); + assert_eq!(ready_value["checks"]["server_bind"], "ready"); assert_eq!(ready_value["checks"]["group_outbox_replay"], "ready"); let metrics_response = base_relay_ops_router(BaseRelayReadinessState::ready(), metrics) .oneshot( @@ -222,6 +246,7 @@ mod tests { BaseRelayReadinessCheckStatus::Ready, BaseRelayReadinessCheckStatus::Ready, BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, BaseRelayReadinessCheckStatus::NotReady, BaseRelayReadinessCheckStatus::Ready, ); @@ -242,6 +267,7 @@ mod tests { let rejected_value = serde_json::from_slice::<serde_json::Value>(&rejected_body).expect("json"); assert_eq!(rejected_value["status"], "not_ready"); + assert_eq!(rejected_value["checks"]["server_bind"], "ready"); assert_eq!(rejected_value["checks"]["group_projection"], "not_ready"); } } diff --git a/crates/tangle_runtime/src/relay/core.rs b/crates/tangle_runtime/src/relay/core.rs @@ -361,7 +361,7 @@ impl BaseRelay { let groups = GroupService::from_config(&store, groups)?; let subscriptions = LiveSubscriptionSet::new(limits.max_pending_events(), limits.max_subscriptions())?; - let readiness = BaseRelayReadinessState::ready(); + let readiness = BaseRelayReadinessState::runtime_ready_before_bind(); Ok(Self { store, subscriptions, diff --git a/crates/tangle_runtime/src/runtime.rs b/crates/tangle_runtime/src/runtime.rs @@ -1095,7 +1095,19 @@ mod tests { assert_eq!(runtime.metrics().active_sessions(), 0); assert_eq!(runtime.metrics().stored_event_offsets(), 0); assert!(runtime.relay().groups_enabled()); - assert!(runtime.readiness_state().is_ready()); + assert!(!runtime.readiness_state().is_ready()); + assert_eq!( + runtime.readiness_state().response().checks.server_bind, + "not_ready" + ); + assert_eq!( + runtime + .readiness_state() + .response() + .checks + .group_outbox_replay, + "ready" + ); assert!(!*shutdown.borrow()); assert_eq!(runtime.event_bus().publish(StoreOffset::new(42)), 1); diff --git a/crates/tangle_runtime/src/server.rs b/crates/tangle_runtime/src/server.rs @@ -4,7 +4,7 @@ use crate::{ errors::BaseRelayError, logging, nip11::{BaseRelayInfoConfig, BaseRelayInfoDocument, base_relay_info_response}, - ops::{BaseRelayReadinessState, base_relay_ops_router}, + ops::{BaseRelayReadinessCheckStatus, BaseRelayReadinessState, base_relay_ops_router}, runtime::{ TangleRuntime, TangleRuntimeHandle, TangleRuntimeLimits, TangleRuntimeMetrics, TangleShutdownSignal, @@ -65,7 +65,10 @@ pub async fn serve_listener_until_shutdown( .map_err(|error| BaseRelayError::error(error.to_string()))?; let relay_url = runtime.config().relay_url().to_owned(); let info = BaseRelayInfoConfig::new("tangle", runtime.config())?.build_document()?; - let readiness = runtime.readiness_state().clone(); + let readiness = runtime + .readiness_state() + .clone() + .with_server_bind(BaseRelayReadinessCheckStatus::Ready); let limits = runtime.limits(); let metrics = runtime.metrics().clone(); let shutdown_signal = runtime.shutdown_signal().clone(); @@ -487,6 +490,9 @@ mod tests { assert_eq!(root_without_accept.status(), http::StatusCode::NOT_FOUND); assert_eq!(health.status(), http::StatusCode::OK); assert_eq!(ready.status(), http::StatusCode::OK); + let ready_body = to_bytes(ready.into_body(), usize::MAX).await.expect("body"); + let ready_value = serde_json::from_slice::<serde_json::Value>(&ready_body).expect("json"); + assert_eq!(ready_value["checks"]["server_bind"], "ready"); assert_eq!(metrics.status(), http::StatusCode::OK); let metrics_body = to_bytes(metrics.into_body(), usize::MAX) .await diff --git a/crates/tangle_runtime/tests/phase2_acceptance_targets.rs b/crates/tangle_runtime/tests/phase2_acceptance_targets.rs @@ -52,6 +52,7 @@ async fn tangle_run_serves_until_shutdown() { assert!(health.contains(r#""status":"ok""#)); assert!(ready.contains(r#""status":"ready""#)); + assert!(ready.contains(r#""server_bind":"ready""#)); assert!(metrics.contains(r#""active_sessions":0"#)); assert!(metrics.contains(r#""stored_event_offsets":0"#)); assert!(nip11.contains(r#""name":"tangle""#));