hennery_kernel/
capabilities.rs1use crate::operator::Operator;
11use axum::routing::get;
12use axum::{Json, Router};
13use hennery_proto::rest::{CapabilitiesResponse, DeploymentMode};
14use std::sync::Arc;
15
16pub fn mode() -> DeploymentMode {
19 DeploymentMode::Full
20}
21
22pub fn features() -> Vec<String> {
28 vec![
29 "mcp_connections".to_owned(),
30 "mcp_stdio".to_owned(),
31 "mcp_oauth".to_owned(),
32 "web_push".to_owned(),
33 "session_defaults".to_owned(),
34 ]
35}
36
37pub fn current() -> CapabilitiesResponse {
39 CapabilitiesResponse {
40 mode: mode(),
41 features: features(),
42 collector_version: env!("CARGO_PKG_VERSION").to_owned(),
43 source_commit: source_commit(option_env!("HENNERY_BUILD_SOURCE_SHA")),
44 }
45}
46
47fn source_commit(recorded: Option<&str>) -> Option<String> {
48 recorded
49 .filter(|sha| matches!(sha.len(), 40 | 64) && sha.bytes().all(|b| b.is_ascii_hexdigit()))
50 .map(str::to_owned)
51}
52
53pub fn router(operator: Arc<Operator>) -> Router {
55 crate::auth::operator_only(
56 Router::new().route("/api/capabilities", get(|| async { Json(current()) })),
57 operator,
58 )
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn the_collector_is_the_whole_cockpit_with_the_gateways_connections() {
67 let mut body = serde_json::to_value(current()).unwrap();
68 let source = body.as_object_mut().unwrap().remove("source_commit").unwrap();
69 assert!(
70 source.is_null()
71 || source.as_str().is_some_and(|sha| {
72 matches!(sha.len(), 40 | 64) && sha.bytes().all(|byte| byte.is_ascii_hexdigit())
73 })
74 );
75 assert_eq!(
76 body,
77 serde_json::json!({"mode": "full", "features": ["mcp_connections", "mcp_stdio", "mcp_oauth", "web_push", "session_defaults"],
78 "collector_version": env!("CARGO_PKG_VERSION")})
79 );
80 }
81
82 #[test]
84 fn the_gateway_mode_is_named_gateway() {
85 assert_eq!(
86 serde_json::to_value(DeploymentMode::Gateway).unwrap(),
87 serde_json::json!("gateway")
88 );
89 }
90
91 #[test]
92 fn source_identity_is_only_a_recorded_commit() {
93 let sha = "abcdef1234567890abcdef1234567890abcdef12";
94 assert_eq!(source_commit(Some(sha)).as_deref(), Some(sha));
95 let sha256 = "a".repeat(64);
96 assert_eq!(source_commit(Some(&sha256)).as_deref(), Some(sha256.as_str()));
97 for value in [
98 None,
99 Some(""),
100 Some("unrecorded (development build)"),
101 Some("unrecorded (dirty source)"),
102 Some("/source/checkout"),
103 Some("not-a-commit00000000000000000000000000000"),
104 ] {
105 assert_eq!(source_commit(value), None);
106 }
107 }
108}