use super::health::{
ensure_health_probe_palace, run_health_round_trip_inner, seed_probe_sentinel_if_absent,
HealthProbeError, HealthQuery, PROBE_SENTINEL_CONTENT,
};
use super::HEALTH_PROBE_PALACE;
use serde_json::Value;
use trusty_common::memory_core::palace::PalaceId;
use trusty_common::memory_core::retrieval::{PalaceHandle, RecallResult};
use trusty_common::memory_core::store::kg::KnowledgeGraph;
use trusty_common::memory_core::store::vector::UsearchStore;
use uuid::Uuid;
#[tokio::test]
#[ignore = "loads the default ONNX embedder; run with --include-ignored"]
async fn health_endpoint_returns_ok() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(v["status"], "ok");
assert_eq!(v["version"], env!("CARGO_PKG_VERSION"));
}
#[tokio::test]
#[ignore = "loads the default ONNX embedder; run with --include-ignored"]
async fn health_endpoint_includes_resource_fields() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
let rss_mb = v["rss_mb"].as_u64().expect("rss_mb is u64");
assert!(rss_mb < 1024 * 1024, "rss_mb unit must be MB");
let cpu = v["cpu_pct"].as_f64().expect("cpu_pct is a number");
assert!(cpu >= 0.0, "cpu_pct must be non-negative");
assert_eq!(v["disk_bytes"].as_u64(), Some(0));
assert!(v["uptime_secs"].is_u64(), "uptime_secs must be present");
}
#[tokio::test]
async fn health_endpoint_cheap_by_default() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(v["status"], "ok", "cheap health must report ok; got {v:?}");
assert_eq!(v["version"], env!("CARGO_PKG_VERSION"));
assert!(
v.get("detail").is_none() || v["detail"].is_null(),
"cheap health must not carry detail; got {v:?}"
);
}
#[tokio::test]
async fn health_reports_idle_worker_pool() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(v["worker"]["in_flight"], 0, "idle pool; got {v:?}");
assert_eq!(v["worker"]["wedged"], false, "idle pool; got {v:?}");
assert_eq!(
v["worker"]["stall_tracking_ok"], true,
"a `wedged: false` is only worth the detector behind it; got {v:?}"
);
assert!(
v["worker"].get("oldest_age_secs").is_none(),
"an idle pool has no age to report; got {v:?}"
);
assert!(
v["worker"].get("wedged_reason").is_none(),
"nothing is wedged, so there is no reason; got {v:?}"
);
}
#[tokio::test]
async fn health_reports_stall_tracking_that_stopped() {
let state = test_state();
let stale = std::time::Instant::now()
.checked_sub(std::time::Duration::from_secs(3600))
.expect("the monotonic clock has an hour of history");
state
.lock_stalls
.beat(stale, std::time::Duration::from_secs(30));
let v: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
assert_eq!(
v["worker"]["stall_tracking_ok"], false,
"a ticker silent for an hour cannot vouch for the locks; got {v:?}"
);
assert_eq!(v["status"], "degraded", "got {v:?}");
assert!(
v["detail"].as_str().unwrap_or_default().contains("ticker"),
"detail must name the dead ticker; got {v:?}"
);
}
#[tokio::test]
async fn health_reports_wedged_worker_pool() {
let mut state = test_state();
state.wedge_threshold = std::time::Duration::ZERO;
let _stuck = state.worker_liveness.track();
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
let body: Value = super::health::health(&state.clone(), HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(
v["worker"]["wedged"], true,
"a stuck operation past the threshold must read as wedged; got {v:?}"
);
assert_eq!(
v["status"], "wedged",
"top-level status must NOT be ok while workers are stuck; got {v:?}"
);
assert_ne!(
v["status"], "ok",
"this is the #3992 false positive; got {v:?}"
);
assert_eq!(
v["worker"]["wedged_reason"], "pool",
"no palace lock is stamped here, so the pool is the reason; got {v:?}"
);
assert_eq!(v["worker"]["in_flight"], 1, "got {v:?}");
assert!(
v["detail"].as_str().unwrap_or_default().contains("wedged")
|| v["detail"]
.as_str()
.unwrap_or_default()
.contains("not making progress"),
"detail must explain the wedge; got {v:?}"
);
}
#[tokio::test]
async fn health_names_the_lock_as_the_wedge_reason() {
let mut state = test_state();
state.wedge_threshold = std::time::Duration::ZERO;
let mutex = std::sync::Arc::new(tokio::sync::Mutex::new(()));
let _held = mutex.clone().lock_owned().await;
let since = std::time::Instant::now()
.checked_sub(std::time::Duration::from_secs(1))
.expect("the monotonic clock has a second of history");
state.lock_stalls.observe(
"wedged-palace",
crate::lock_stall::PalaceLock::Write,
&mutex,
since,
);
let v: Value = super::health::health(&state.clone(), HealthQuery::default())
.await
.expect("health answers");
assert_eq!(v["worker"]["wedged"], true, "got {v:?}");
assert_eq!(
v["worker"]["wedged_reason"], "lock",
"a held palace lock, not the pool, is what wedged; got {v:?}"
);
assert_eq!(v["worker"]["stalled_lock"]["palace"], "wedged-palace");
assert_eq!(v["status"], "wedged", "got {v:?}");
}
#[tokio::test]
async fn health_wedge_signal_clears_when_work_completes() {
let mut state = test_state();
state.wedge_threshold = std::time::Duration::ZERO;
{
let _stuck = state.worker_liveness.track();
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
}
let body: Value = super::health::health(&state.clone(), HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(v["worker"]["wedged"], false, "got {v:?}");
assert_eq!(v["status"], "ok", "got {v:?}");
}
#[tokio::test]
async fn health_endpoint_includes_fd_gauge() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
#[cfg(unix)]
{
let open_fds = v["open_fds"]
.as_u64()
.expect("open_fds must be present on Unix");
assert!(
open_fds > 0,
"open_fds must be > 0 (at least stdin/stdout/stderr)"
);
let limit = v["fd_soft_limit"]
.as_u64()
.expect("fd_soft_limit must be present on Unix");
assert!(limit > 0, "fd_soft_limit must be > 0");
assert!(
open_fds < limit,
"open_fds ({open_fds}) must be below fd_soft_limit ({limit}) in tests"
);
}
}
#[tokio::test]
#[ignore = "loads the default ONNX embedder; run with --include-ignored"]
async fn health_endpoint_round_trip_on_fresh_install_is_ok() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(v["status"], "ok");
assert!(
v.get("detail").is_none() || v["detail"].is_null(),
"fresh-install health must not carry a degraded detail (got {v:?})"
);
}
#[tokio::test]
#[ignore = "loads the default ONNX embedder; run with --include-ignored"]
async fn health_endpoint_round_trip_with_palace_is_ok() {
let state = test_state();
let palace = trusty_common::memory_core::Palace {
id: PalaceId::new("health-probe-palace"),
name: "health-probe-palace".to_string(),
description: None,
created_at: chrono::Utc::now(),
data_dir: state.data_root.join("health-probe-palace"),
};
state
.registry
.create_palace(&state.data_root, palace)
.expect("create_palace");
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert_eq!(
v["status"], "ok",
"round-trip should succeed against a fresh palace; got {v:?}"
);
assert!(
v.get("detail").is_none() || v["detail"].is_null(),
"successful round-trip must not carry a detail field (got {v:?})"
);
}
#[tokio::test]
async fn health_probe_palace_is_invisible() {
let state = test_state();
ensure_health_probe_palace(&state).expect("ensure_health_probe_palace");
assert!(
state.data_root.join(HEALTH_PROBE_PALACE).exists(),
"probe palace directory should be persisted on disk"
);
let service = crate::service::MemoryService::new(state);
let listed = service.list_palaces().await.expect("list_palaces");
assert!(
listed.iter().all(|p| !p.id.starts_with("__")),
"no `__`-prefixed palace may appear in the user-facing list; got {:?}",
listed.iter().map(|p| &p.id).collect::<Vec<_>>()
);
assert!(
!listed.iter().any(|p| p.id == HEALTH_PROBE_PALACE),
"the dedicated `__health_probe__` palace must be invisible; got {:?}",
listed.iter().map(|p| &p.id).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn health_probe_cleans_up_on_success() {
use trusty_common::memory_core::Drawer;
let state = test_state();
ensure_health_probe_palace(&state).expect("ensure_health_probe_palace");
let handle = state
.registry
.open_palace(&state.data_root, &PalaceId::new(HEALTH_PROBE_PALACE))
.expect("open probe palace");
let result = run_health_round_trip_inner(handle.clone(), move |h, _query| async move {
let drawers = h.drawers.read();
let last = drawers
.last()
.cloned()
.unwrap_or_else(|| Drawer::new(Uuid::new_v4(), "stub"));
drop(drawers);
Ok(vec![RecallResult {
drawer: last,
score: 1.0,
layer: 1,
}])
})
.await;
assert!(
result.is_ok(),
"successful round-trip should return Ok; got {result:?}"
);
let drawer_count = handle.drawers.read().len();
assert_eq!(
drawer_count, 0,
"probe palace must have zero drawers after a successful round-trip (got {drawer_count})"
);
}
#[tokio::test]
async fn health_probe_cleans_up_on_recall_miss() {
let state = test_state();
ensure_health_probe_palace(&state).expect("ensure_health_probe_palace");
let handle = state
.registry
.open_palace(&state.data_root, &PalaceId::new(HEALTH_PROBE_PALACE))
.expect("open probe palace");
let result = run_health_round_trip_inner(handle.clone(), |_h, _q| async move {
Ok(Vec::new())
})
.await;
assert!(
matches!(result, Err(HealthProbeError::ProbeMissing(_))),
"recall miss must surface as ProbeMissing; got {result:?}"
);
let drawer_count = handle.drawers.read().len();
assert_eq!(
drawer_count, 0,
"probe palace must be empty after a recall miss (got {drawer_count})"
);
}
#[tokio::test]
async fn health_probe_cleans_up_on_recall_error() {
let state = test_state();
ensure_health_probe_palace(&state).expect("ensure_health_probe_palace");
let handle = state
.registry
.open_palace(&state.data_root, &PalaceId::new(HEALTH_PROBE_PALACE))
.expect("open probe palace");
let result = run_health_round_trip_inner(handle.clone(), |_h, _q| async move {
Err(HealthProbeError::Recall("simulated failure".to_string()))
})
.await;
assert!(
matches!(result, Err(HealthProbeError::Recall(_))),
"recall error must surface as Recall; got {result:?}"
);
let drawer_count = handle.drawers.read().len();
assert_eq!(
drawer_count, 0,
"probe palace must be empty after a recall error (got {drawer_count})"
);
}
#[tokio::test]
async fn health_probe_self_heals_after_migration_wipe() {
let state = test_state();
ensure_health_probe_palace(&state).expect("create probe palace");
let handle = state
.registry
.open_palace(&state.data_root, &PalaceId::new(HEALTH_PROBE_PALACE))
.expect("open probe palace");
assert_eq!(
handle.drawers.read().len(),
0,
"palace must be empty before self-heal"
);
let seeded = seed_probe_sentinel_if_absent(&handle)
.await
.expect("seed_probe_sentinel_if_absent");
assert!(
seeded,
"first call must report that the sentinel was seeded"
);
{
let drawers = handle.drawers.read();
assert_eq!(
drawers.len(),
1,
"sentinel must be seeded when palace is empty (issue #1142)"
);
assert_eq!(
drawers[0].content(),
PROBE_SENTINEL_CONTENT,
"seeded drawer must carry the well-known sentinel content"
);
}
let seeded_again = seed_probe_sentinel_if_absent(&handle)
.await
.expect("seed_probe_sentinel_if_absent idempotent");
assert!(
!seeded_again,
"second call must report sentinel already present"
);
let drawer_count = handle.drawers.read().len();
assert_eq!(
drawer_count, 1,
"seed_probe_sentinel_if_absent must be idempotent (got {drawer_count})"
);
}
#[tokio::test]
async fn health_reports_unopenable_palaces() {
let state = test_state();
state.registry.record_unopenable(
PalaceId::new("stale-format"),
"open vector store for stale-format: incompatible on-disk format".to_string(),
);
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
let listed = v["unopenable_palaces"]
.as_array()
.unwrap_or_else(|| panic!("/health must list unopenable palaces; got {v:?}"));
assert_eq!(listed.len(), 1, "exactly one palace was refused; got {v:?}");
assert_eq!(listed[0]["id"], "stale-format");
assert!(
listed[0]["reason"]
.as_str()
.is_some_and(|r| r.contains("incompatible on-disk format")),
"the reason must reach the operator, not just the id; got {v:?}"
);
}
#[tokio::test]
async fn health_omits_unopenable_palaces_when_none() {
let state = test_state();
let body: Value = super::health::health(&state, HealthQuery::default())
.await
.expect("health answers");
let v = body;
assert!(
v.get("unopenable_palaces").is_none(),
"a healthy daemon's payload must be unchanged; got {v:?}"
);
}
fn degraded_handle(id: &str, degraded: bool) -> std::sync::Arc<PalaceHandle> {
let tmp = tempfile::tempdir().expect("tempdir");
let dir = tmp.path().to_path_buf();
std::mem::forget(tmp);
let vs = UsearchStore::new(dir.join("idx.usearch"), 384).expect("vector store");
let kg = KnowledgeGraph::open(&dir.join("kg.db")).expect("kg");
let mut handle = PalaceHandle::new(PalaceId::new(id), String::new(), vs, kg);
handle.drawer_load_degraded = degraded;
std::sync::Arc::new(handle)
}
async fn health_body(state: crate::AppState) -> Value {
super::health::health(&state, HealthQuery::default())
.await
.expect("the cheap health path never fails")
}
fn test_state() -> crate::AppState {
trusty_common::memory_core::retrieval::seed_shared_embedder_with_mock();
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().to_path_buf();
std::mem::forget(tmp);
unsafe {
std::env::set_var("TRUSTY_SKIP_PALACE_ENFORCEMENT", "1");
}
let state = crate::AppState::new(root);
state.set_ready();
state
}
#[tokio::test]
async fn health_reports_drawer_degraded_palace() {
let state = test_state();
state
.registry
.register_arc(degraded_handle("corrupt", true));
let v = health_body(state).await;
let listed = v["drawer_degraded_palaces"]
.as_array()
.unwrap_or_else(|| panic!("/health must name the degraded palace; got {v:?}"));
assert_eq!(listed.len(), 1, "exactly one palace is degraded; got {v:?}");
assert_eq!(listed[0], "corrupt");
assert_eq!(
v["status"], "ok",
"a partial corpus is reported, not escalated to a failing status; got {v:?}"
);
}
#[tokio::test]
async fn health_omits_drawer_degraded_when_all_healthy() {
let state = test_state();
state
.registry
.register_arc(degraded_handle("intact", false));
let v = health_body(state).await;
assert!(
v.get("drawer_degraded_palaces").is_none(),
"a fully-loaded palace must not be reported as degraded; got {v:?}"
);
}
#[tokio::test]
async fn health_drawer_degraded_names_only_the_degraded_palace() {
let state = test_state();
state
.registry
.register_arc(degraded_handle("intact-a", false));
state.registry.register_arc(degraded_handle("holey", true));
state
.registry
.register_arc(degraded_handle("intact-b", false));
let v = health_body(state).await;
let listed = v["drawer_degraded_palaces"]
.as_array()
.unwrap_or_else(|| panic!("/health must name the degraded palace; got {v:?}"));
assert_eq!(
listed.len(),
1,
"only one of three palaces is degraded; got {v:?}"
);
assert_eq!(listed[0], "holey");
}
#[tokio::test]
async fn health_drawer_degraded_check_opens_no_palace() {
use trusty_common::memory_core::{Palace, PalaceRegistry};
let mut state = test_state();
let registry = PalaceRegistry::with_max_open(2);
let on_disk = PalaceId::new("on-disk-only");
registry
.create_palace(
&state.data_root,
Palace {
id: on_disk.clone(),
name: "on-disk-only".to_string(),
description: None,
created_at: chrono::Utc::now(),
data_dir: state.data_root.join("on-disk-only"),
},
)
.unwrap_or_else(|e| panic!("create_palace(on-disk-only) failed: {e:#}"));
registry.register_arc(degraded_handle("resident-intact", false));
registry.register_arc(degraded_handle("resident-degraded", true));
assert!(
registry.peek(&on_disk).is_none(),
"precondition: the on-disk palace must be evicted before /health runs"
);
state.registry = std::sync::Arc::new(registry);
let registry = std::sync::Arc::clone(&state.registry);
let v = health_body(state).await;
let listed = v["drawer_degraded_palaces"]
.as_array()
.unwrap_or_else(|| panic!("/health must name the resident degraded palace; got {v:?}"));
assert_eq!(listed.len(), 1, "only the resident is degraded; got {v:?}");
assert_eq!(listed[0], "resident-degraded");
assert!(
registry.peek(&PalaceId::new("resident-intact")).is_some(),
"/health must not evict a resident handle to inspect a palace on disk; got {v:?}"
);
assert!(
registry.peek(&PalaceId::new("resident-degraded")).is_some(),
"/health must not evict a resident handle to inspect a palace on disk; got {v:?}"
);
assert!(
registry.peek(&on_disk).is_none(),
"/health must not open a palace that is not already resident; got {v:?}"
);
assert_eq!(registry.len(), 2, "/health must not grow the cache");
}