huskies: merge 768
This commit is contained in:
@@ -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}");
|
||||
|
||||
@@ -47,7 +47,6 @@ pub fn build_gateway_route(state_arc: Arc<GatewayState>) -> 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(
|
||||
|
||||
@@ -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<GatewayState>>) -> 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.
|
||||
|
||||
@@ -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<HealthStatus> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -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<AppContext>) -> (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<AppContext>) -> (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 },
|
||||
|
||||
@@ -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<bool, String> {
|
||||
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}")),
|
||||
}
|
||||
|
||||
@@ -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<String, &'static str>) {
|
||||
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.
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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`).
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user