tangle


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

commit 433aa20dc5141f071545d46445e8792f86dc8d8e
parent 4f86034e5fa0f2431329999277a47e0e1e4d46f8
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 01:52:05 -0700

runtime: extract ops readiness module

- move health and readiness documents into the ops module
- move healthz and readyz routing with readiness state
- update runtime startup reporting to use the ops readiness type
- verify formatting and focused tangle_runtime checks stay green

Diffstat:
Mcrates/tangle_runtime/src/base_relay.rs | 204+------------------------------------------------------------------------------
Mcrates/tangle_runtime/src/lib.rs | 3++-
Acrates/tangle_runtime/src/ops.rs | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 208 insertions(+), 203 deletions(-)

diff --git a/crates/tangle_runtime/src/base_relay.rs b/crates/tangle_runtime/src/base_relay.rs @@ -1,7 +1,5 @@ use crate::errors::{BaseRelayError, ok_accepted, ok_rejected}; -use axum::{Json, Router, extract::State, routing::get}; -use http::StatusCode; -use serde::{Deserialize, Serialize}; +use crate::ops::BaseRelayReadinessState; use std::{collections::BTreeMap, collections::BTreeSet, str}; use tangle_crypto::{RelaySigner, verify_event_signature}; use tangle_groups::{ @@ -26,135 +24,6 @@ use tangle_store_pocket::{ TANGLE_GROUP_PROJECTION_TABLE, parse_pocket_event_json, parse_pocket_filter_json, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BaseRelayReadinessCheckStatus { - Ready, - NotReady, -} - -impl BaseRelayReadinessCheckStatus { - pub fn as_str(self) -> &'static str { - match self { - Self::Ready => "ready", - Self::NotReady => "not_ready", - } - } - - pub fn is_ready(self) -> bool { - self == Self::Ready - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BaseRelayReadinessState { - config: BaseRelayReadinessCheckStatus, - relay_identity: BaseRelayReadinessCheckStatus, - pocket_storage: BaseRelayReadinessCheckStatus, - group_projection: BaseRelayReadinessCheckStatus, - group_outbox_replay: BaseRelayReadinessCheckStatus, -} - -impl BaseRelayReadinessState { - pub fn new( - config: BaseRelayReadinessCheckStatus, - relay_identity: BaseRelayReadinessCheckStatus, - pocket_storage: BaseRelayReadinessCheckStatus, - group_projection: BaseRelayReadinessCheckStatus, - group_outbox_replay: BaseRelayReadinessCheckStatus, - ) -> Self { - Self { - config, - relay_identity, - pocket_storage, - group_projection, - group_outbox_replay, - } - } - - pub fn ready() -> Self { - Self::new( - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::Ready, - ) - } - - pub fn is_ready(&self) -> bool { - [ - self.config, - self.relay_identity, - self.pocket_storage, - self.group_projection, - self.group_outbox_replay, - ] - .into_iter() - .all(BaseRelayReadinessCheckStatus::is_ready) - } - - pub fn response(&self) -> BaseRelayReadinessDocument { - BaseRelayReadinessDocument { - status: if self.is_ready() { - "ready".to_owned() - } else { - "not_ready".to_owned() - }, - checks: BaseRelayReadinessChecksDocument { - config: self.config.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(), - group_outbox_replay: self.group_outbox_replay.as_str().to_owned(), - }, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BaseRelayHealthDocument { - pub status: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BaseRelayReadinessDocument { - pub status: String, - pub checks: BaseRelayReadinessChecksDocument, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BaseRelayReadinessChecksDocument { - pub config: String, - pub relay_identity: String, - pub pocket_storage: String, - pub group_projection: String, - pub group_outbox_replay: String, -} - -pub fn base_relay_ops_router(readiness: BaseRelayReadinessState) -> Router { - Router::new() - .route("/healthz", get(base_relay_healthz)) - .route("/readyz", get(base_relay_readyz)) - .with_state(readiness) -} - -async fn base_relay_healthz() -> Json<BaseRelayHealthDocument> { - Json(BaseRelayHealthDocument { - status: "ok".to_owned(), - }) -} - -async fn base_relay_readyz( - State(readiness): State<BaseRelayReadinessState>, -) -> (StatusCode, Json<BaseRelayReadinessDocument>) { - let status = if readiness.is_ready() { - StatusCode::OK - } else { - StatusCode::SERVICE_UNAVAILABLE - }; - (status, Json(readiness.response())) -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct BaseAuthState { relay_url: String, @@ -1244,12 +1113,7 @@ fn pocket_event_id(event_id: &EventId) -> Result<PocketEventId, BaseRelayError> #[cfg(test)] mod tests { - use super::{ - BaseAuthState, BaseRelay, BaseRelayReadinessCheckStatus, BaseRelayReadinessState, - CloseResult, base_relay_ops_router, - }; - use axum::body::to_bytes; - use http::{Request, StatusCode}; + use super::{BaseAuthState, BaseRelay, CloseResult}; use tangle_crypto::RelaySigner; use tangle_groups::{ GroupId, KIND_GROUP_ADMINS, KIND_GROUP_CREATE_GROUP, KIND_GROUP_CREATE_INVITE, @@ -1262,70 +1126,6 @@ mod tests { Tag, UnixTimestamp, UnsignedEvent, filter_from_value, }; use tangle_store_pocket::{PocketStoreConfig, PocketSyncPolicy}; - use tower::ServiceExt; - - #[tokio::test] - async fn base_relay_ops_router_reports_health_and_readiness() { - let health = base_relay_ops_router(BaseRelayReadinessState::ready()) - .oneshot( - Request::builder() - .uri("/healthz") - .body(axum::body::Body::empty()) - .expect("request"), - ) - .await - .expect("health"); - - assert_eq!(health.status(), StatusCode::OK); - let health_body = to_bytes(health.into_body(), usize::MAX) - .await - .expect("body"); - let health_value = serde_json::from_slice::<serde_json::Value>(&health_body).expect("json"); - assert_eq!(health_value["status"], "ok"); - - let ready = base_relay_ops_router(BaseRelayReadinessState::ready()) - .oneshot( - Request::builder() - .uri("/readyz") - .body(axum::body::Body::empty()) - .expect("request"), - ) - .await - .expect("ready"); - - assert_eq!(ready.status(), 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["status"], "ready"); - assert_eq!(ready_value["checks"]["group_outbox_replay"], "ready"); - - let not_ready = BaseRelayReadinessState::new( - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::Ready, - BaseRelayReadinessCheckStatus::NotReady, - BaseRelayReadinessCheckStatus::Ready, - ); - let rejected = base_relay_ops_router(not_ready) - .oneshot( - Request::builder() - .uri("/readyz") - .body(axum::body::Body::empty()) - .expect("request"), - ) - .await - .expect("not ready"); - - assert_eq!(rejected.status(), StatusCode::SERVICE_UNAVAILABLE); - let rejected_body = to_bytes(rejected.into_body(), usize::MAX) - .await - .expect("body"); - 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"]["group_projection"], "not_ready"); - } - #[test] fn auth_state_issues_challenges_and_accepts_multiple_pubkeys() { let mut auth = BaseAuthState::new("wss://relay.radroots.test", 60).expect("auth state"); diff --git a/crates/tangle_runtime/src/lib.rs b/crates/tangle_runtime/src/lib.rs @@ -5,12 +5,13 @@ pub mod chorus_pocket; pub mod config; pub mod errors; pub mod nip11; +pub mod ops; use std::{fmt, fs, path::Path, path::PathBuf}; -use base_relay::BaseRelayReadinessState; use config::{BaseRelayRuntimeConfig, parse_base_relay_runtime_config_json}; use errors::BaseRelayError; +use ops::BaseRelayReadinessState; pub const TANGLE_SUPPORTED_NIPS: [u16; 6] = [1, 11, 29, 42, 45, 70]; pub const TANGLE_RELAY_SOFTWARE: &str = "https://github.com/radrootslabs/tangle"; diff --git a/crates/tangle_runtime/src/ops.rs b/crates/tangle_runtime/src/ops.rs @@ -0,0 +1,204 @@ +#![forbid(unsafe_code)] + +use axum::{Json, Router, extract::State, routing::get}; +use http::StatusCode; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BaseRelayReadinessCheckStatus { + Ready, + NotReady, +} + +impl BaseRelayReadinessCheckStatus { + pub fn as_str(self) -> &'static str { + match self { + Self::Ready => "ready", + Self::NotReady => "not_ready", + } + } + + pub fn is_ready(self) -> bool { + self == Self::Ready + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BaseRelayReadinessState { + config: BaseRelayReadinessCheckStatus, + relay_identity: BaseRelayReadinessCheckStatus, + pocket_storage: BaseRelayReadinessCheckStatus, + group_projection: BaseRelayReadinessCheckStatus, + group_outbox_replay: BaseRelayReadinessCheckStatus, +} + +impl BaseRelayReadinessState { + pub fn new( + config: BaseRelayReadinessCheckStatus, + relay_identity: BaseRelayReadinessCheckStatus, + pocket_storage: BaseRelayReadinessCheckStatus, + group_projection: BaseRelayReadinessCheckStatus, + group_outbox_replay: BaseRelayReadinessCheckStatus, + ) -> Self { + Self { + config, + relay_identity, + pocket_storage, + group_projection, + group_outbox_replay, + } + } + + pub fn ready() -> Self { + Self::new( + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + ) + } + + pub fn is_ready(&self) -> bool { + [ + self.config, + self.relay_identity, + self.pocket_storage, + self.group_projection, + self.group_outbox_replay, + ] + .into_iter() + .all(BaseRelayReadinessCheckStatus::is_ready) + } + + pub fn response(&self) -> BaseRelayReadinessDocument { + BaseRelayReadinessDocument { + status: if self.is_ready() { + "ready".to_owned() + } else { + "not_ready".to_owned() + }, + checks: BaseRelayReadinessChecksDocument { + config: self.config.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(), + group_outbox_replay: self.group_outbox_replay.as_str().to_owned(), + }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BaseRelayHealthDocument { + pub status: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BaseRelayReadinessDocument { + pub status: String, + pub checks: BaseRelayReadinessChecksDocument, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BaseRelayReadinessChecksDocument { + pub config: String, + pub relay_identity: String, + pub pocket_storage: String, + pub group_projection: String, + pub group_outbox_replay: String, +} + +pub fn base_relay_ops_router(readiness: BaseRelayReadinessState) -> Router { + Router::new() + .route("/healthz", get(base_relay_healthz)) + .route("/readyz", get(base_relay_readyz)) + .with_state(readiness) +} + +async fn base_relay_healthz() -> Json<BaseRelayHealthDocument> { + Json(BaseRelayHealthDocument { + status: "ok".to_owned(), + }) +} + +async fn base_relay_readyz( + State(readiness): State<BaseRelayReadinessState>, +) -> (StatusCode, Json<BaseRelayReadinessDocument>) { + let status = if readiness.is_ready() { + StatusCode::OK + } else { + StatusCode::SERVICE_UNAVAILABLE + }; + (status, Json(readiness.response())) +} + +#[cfg(test)] +mod tests { + use super::{BaseRelayReadinessCheckStatus, BaseRelayReadinessState, base_relay_ops_router}; + use axum::body::to_bytes; + use http::{Request, StatusCode}; + use tower::ServiceExt; + + #[tokio::test] + async fn base_relay_ops_router_reports_health_and_readiness() { + let health = base_relay_ops_router(BaseRelayReadinessState::ready()) + .oneshot( + Request::builder() + .uri("/healthz") + .body(axum::body::Body::empty()) + .expect("request"), + ) + .await + .expect("health"); + + assert_eq!(health.status(), StatusCode::OK); + let health_body = to_bytes(health.into_body(), usize::MAX) + .await + .expect("body"); + let health_value = serde_json::from_slice::<serde_json::Value>(&health_body).expect("json"); + assert_eq!(health_value["status"], "ok"); + + let ready = base_relay_ops_router(BaseRelayReadinessState::ready()) + .oneshot( + Request::builder() + .uri("/readyz") + .body(axum::body::Body::empty()) + .expect("request"), + ) + .await + .expect("ready"); + + assert_eq!(ready.status(), 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["status"], "ready"); + assert_eq!(ready_value["checks"]["group_outbox_replay"], "ready"); + + let not_ready = BaseRelayReadinessState::new( + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::Ready, + BaseRelayReadinessCheckStatus::NotReady, + BaseRelayReadinessCheckStatus::Ready, + ); + let rejected = base_relay_ops_router(not_ready) + .oneshot( + Request::builder() + .uri("/readyz") + .body(axum::body::Body::empty()) + .expect("request"), + ) + .await + .expect("not ready"); + + assert_eq!(rejected.status(), StatusCode::SERVICE_UNAVAILABLE); + let rejected_body = to_bytes(rejected.into_body(), usize::MAX) + .await + .expect("body"); + 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"]["group_projection"], "not_ready"); + } +}