diff --git a/server/src/agent_mode.rs b/server/src/agent_mode.rs index b003c4d4..bc6b2d2b 100644 --- a/server/src/agent_mode.rs +++ b/server/src/agent_mode.rs @@ -197,7 +197,6 @@ pub async fn run( // open supplementary mesh connections for resilience. { let sync_handler = poem::get(crate::crdt_sync::crdt_sync_handler); - let health_handler = poem::get(crate::http::health::health); // Build a minimal AppContext for the crdt_sync_handler (the handler // receives it via Data<> but doesn't use it — the underscore prefix @@ -207,7 +206,6 @@ pub async fn run( let app = poem::Route::new() .at("/crdt-sync", sync_handler) - .at("/health", health_handler) .data(agent_ctx_arc); let bind_addr = format!("0.0.0.0:{port}"); diff --git a/server/src/gateway.rs b/server/src/gateway.rs index f92844da..3a67d447 100644 --- a/server/src/gateway.rs +++ b/server/src/gateway.rs @@ -47,7 +47,6 @@ pub fn build_gateway_route(state_arc: Arc) -> impl poem::Endpoint "/mcp", poem::post(gateway_mcp_post_handler).get(gateway_mcp_get_handler), ) - .at("/health", poem::get(gateway_health_handler)) // Agent join endpoints. .at("/gateway/mode", poem::get(gateway_mode_handler)) .at( diff --git a/server/src/http/gateway.rs b/server/src/http/gateway.rs index 85f32743..87d6e2f6 100644 --- a/server/src/http/gateway.rs +++ b/server/src/http/gateway.rs @@ -806,29 +806,6 @@ pub async fn gateway_event_push_handler( .into_response() } -// ── Health handler ────────────────────────────────────────────────────────── - -/// HTTP GET `/health` handler for the gateway. -#[handler] -pub async fn gateway_health_handler(state: Data<&Arc>) -> Response { - let (all_healthy, statuses) = gateway::health_check_all(&state).await; - - let body = json!({ - "status": if all_healthy { "ok" } else { "degraded" }, - "projects": statuses, - }); - - let status = if all_healthy { - StatusCode::OK - } else { - StatusCode::SERVICE_UNAVAILABLE - }; - Response::builder() - .status(status) - .header("Content-Type", "application/json") - .body(Body::from(serde_json::to_vec(&body).unwrap_or_default())) -} - // ── Gateway Web UI ────────────────────────────────────────────────────────── /// `GET /api/gateway` — returns the list of registered projects and the active project. diff --git a/server/src/http/health.rs b/server/src/http/health.rs deleted file mode 100644 index c8800fe3..00000000 --- a/server/src/http/health.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Health check endpoint — thin HTTP adapter over `service::health`. -//! -//! Domain logic (the `HealthStatus` type and check function) lives in -//! `service::health`; this module is a thin adapter: call service → shape -//! response. - -pub use crate::service::health::HealthStatus; - -use poem::handler; -use poem_openapi::{OpenApi, Tags, payload::Json}; - -/// Health check endpoint. -/// -/// Returns a static "ok" response to indicate the server is running. -#[handler] -pub fn health() -> &'static str { - "ok" -} - -#[derive(Tags)] -enum HealthTags { - Health, -} - -pub struct HealthApi; - -#[OpenApi(tag = "HealthTags::Health")] -impl HealthApi { - /// Health check endpoint. - /// - /// Returns a JSON status object to confirm the server is running. - #[oai(path = "/health", method = "get")] - async fn health(&self) -> Json { - Json(crate::service::health::check()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn handler_health_returns_ok() { - let app = poem::Route::new().at("/health", poem::get(health)); - let cli = poem::test::TestClient::new(app); - let resp = cli.get("/health").send().await; - resp.assert_status_is_ok(); - resp.assert_text("ok").await; - } - - #[test] - fn health_status_serializes_to_json() { - let status = HealthStatus { - status: "ok".to_string(), - }; - let json = serde_json::to_value(&status).unwrap(); - assert_eq!(json["status"], "ok"); - } - - #[tokio::test] - async fn api_health_returns_ok_status() { - let api = HealthApi; - let response = api.health().await; - assert_eq!(response.0.status, "ok"); - } -} diff --git a/server/src/http/mod.rs b/server/src/http/mod.rs index 93c14cf3..d2e2ee32 100644 --- a/server/src/http/mod.rs +++ b/server/src/http/mod.rs @@ -8,7 +8,6 @@ pub mod bot_config; pub mod chat; pub mod context; pub mod events; -pub mod health; pub mod identity; pub mod io; pub mod mcp; @@ -30,7 +29,6 @@ use bot_command::BotCommandApi; use bot_config::BotConfigApi; use chat::ChatApi; use context::AppContext; -use health::HealthApi; use io::IoApi; use model::ModelApi; use poem::EndpointExt; @@ -92,7 +90,6 @@ pub fn build_routes( "/mcp", post(mcp::mcp_post_handler).get(mcp::mcp_get_handler), ) - .at("/health", get(health::health)) .at("/identity", get(identity::identity_handler)) .at( "/oauth/authorize", @@ -204,7 +201,6 @@ type ApiTuple = ( ChatApi, AgentsApi, SettingsApi, - HealthApi, BotCommandApi, wizard::WizardApi, BotConfigApi, @@ -222,7 +218,6 @@ pub fn build_openapi_service(ctx: Arc) -> (ApiService, ApiService) { ChatApi { ctx: ctx.clone() }, AgentsApi { ctx: ctx.clone() }, SettingsApi { ctx: ctx.clone() }, - HealthApi, BotCommandApi { ctx: ctx.clone() }, wizard::WizardApi { ctx: ctx.clone() }, BotConfigApi { ctx: ctx.clone() }, @@ -239,7 +234,6 @@ pub fn build_openapi_service(ctx: Arc) -> (ApiService, ApiService) { ChatApi { ctx: ctx.clone() }, AgentsApi { ctx: ctx.clone() }, SettingsApi { ctx: ctx.clone() }, - HealthApi, BotCommandApi { ctx: ctx.clone() }, wizard::WizardApi { ctx: ctx.clone() }, BotConfigApi { ctx }, diff --git a/server/src/service/gateway/io.rs b/server/src/service/gateway/io.rs index 875859af..8eaae0e8 100644 --- a/server/src/service/gateway/io.rs +++ b/server/src/service/gateway/io.rs @@ -221,10 +221,20 @@ pub async fn fetch_pipeline_status_for_project( .map_err(|e| format!("invalid upstream response: {e}")) } -/// Check health of a single project URL. +/// Check health of a single project URL via the read-RPC `health.check` method. +/// +/// Sends an RPC request to the project's `/mcp` endpoint. A successful +/// response (HTTP 2xx) indicates the project container is reachable and +/// serving requests. pub async fn check_project_health(client: &Client, base_url: &str) -> Result { - let health_url = format!("{}/health", base_url.trim_end_matches('/')); - match client.get(&health_url).send().await { + let mcp_url = format!("{}/mcp", base_url.trim_end_matches('/')); + let rpc_body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list", + "params": {} + }); + match client.post(&mcp_url).json(&rpc_body).send().await { Ok(resp) => Ok(resp.status().is_success()), Err(e) => Err(format!("unreachable: {e}")), } diff --git a/server/src/service/gateway/mod.rs b/server/src/service/gateway/mod.rs index cee8da1b..a8982fb1 100644 --- a/server/src/service/gateway/mod.rs +++ b/server/src/service/gateway/mod.rs @@ -404,32 +404,6 @@ pub async fn init_project( Ok(registered_name) } -/// Fetch aggregated health status across all projects. -pub async fn health_check_all(state: &GatewayState) -> (bool, BTreeMap) { - let mut all_healthy = true; - let mut statuses = BTreeMap::new(); - - let project_entries: Vec<(String, String)> = state - .projects - .read() - .await - .iter() - .map(|(n, e)| (n.clone(), e.url.clone())) - .collect(); - - for (name, url) in &project_entries { - let healthy = io::check_project_health(&state.client, url) - .await - .unwrap_or(false); - if !healthy { - all_healthy = false; - } - statuses.insert(name.clone(), if healthy { "ok" } else { "error" }); - } - - (all_healthy, statuses) -} - /// Broadcast a status event received from a project node to all local subscribers. /// /// Returns the number of active receivers that received the event. diff --git a/server/src/service/health/check.rs b/server/src/service/health/check.rs deleted file mode 100644 index 5ea0b602..00000000 --- a/server/src/service/health/check.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Pure health-check logic — no side effects. - -use poem_openapi::Object; -use serde::Serialize; - -/// The JSON payload returned by the health check endpoint. -#[derive(Serialize, Object)] -pub struct HealthStatus { - /// Human-readable status string, always `"ok"` when the server is healthy. - pub status: String, -} - -/// Return a healthy status response. -pub fn ok() -> HealthStatus { - HealthStatus { - status: "ok".to_string(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn ok_returns_status_ok() { - let s = ok(); - assert_eq!(s.status, "ok"); - } - - #[test] - fn health_status_serializes() { - let s = HealthStatus { - status: "ok".to_string(), - }; - let json = serde_json::to_value(&s).unwrap(); - assert_eq!(json["status"], "ok"); - } -} diff --git a/server/src/service/health/io.rs b/server/src/service/health/io.rs deleted file mode 100644 index a525d8c0..00000000 --- a/server/src/service/health/io.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Health I/O wrappers. -//! -//! Health has no side effects; this file exists to satisfy the -//! service-module convention (`docs/architecture/service-modules.md`). diff --git a/server/src/service/health/mod.rs b/server/src/service/health/mod.rs deleted file mode 100644 index 9252509e..00000000 --- a/server/src/service/health/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Health service — public API for the health domain. -//! -//! Exposes a single `check()` function that returns a [`HealthStatus`]. -//! HTTP handlers call this instead of constructing the response inline. -//! -//! Conventions: `docs/architecture/service-modules.md` - -pub mod check; -pub(super) mod io; - -pub use check::HealthStatus; - -// ── Error type ──────────────────────────────────────────────────────────────── - -/// Typed errors returned by `service::health` functions. -/// -/// Health checks are currently infallible; this enum satisfies the module -/// convention and accommodates future error cases (e.g. dependency checks). -#[allow(dead_code)] -#[derive(Debug)] -pub enum Error { - /// An internal error occurred during the health check. - Internal(String), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Internal(msg) => write!(f, "Health error: {msg}"), - } - } -} - -// ── Public API ──────────────────────────────────────────────────────────────── - -/// Perform a health check and return the status. -pub fn check() -> HealthStatus { - check::ok() -} diff --git a/server/src/service/mod.rs b/server/src/service/mod.rs index ed96a18b..86eaba72 100644 --- a/server/src/service/mod.rs +++ b/server/src/service/mod.rs @@ -14,7 +14,6 @@ pub mod events; pub mod file_io; pub mod gateway; pub mod git_ops; -pub mod health; pub mod merge; pub mod notifications; pub mod oauth;