nautalid 0.1.0

Scratch container substrate — TLS 1.3 HTTP/2+3 kernel, LID/AetherDB, optional filter bus (GPL-3.0-or-later).
//! Lightweight Prometheus text exposition (no external metrics crate).

use std::sync::atomic::{AtomicU64, Ordering};

static HTTP_REQUESTS: AtomicU64 = AtomicU64::new(0);
static HTTP_CLIENT_ERRORS: AtomicU64 = AtomicU64::new(0);
static HTTP_SERVER_ERRORS: AtomicU64 = AtomicU64::new(0);
static FILTER_DENIED: AtomicU64 = AtomicU64::new(0);
static KWT_AUTH_FAILURES: AtomicU64 = AtomicU64::new(0);
static KWT_AUTH_SUCCESSES: AtomicU64 = AtomicU64::new(0);

/// Whether **`GET /metrics`** is served (default on; set **`NAUTALID_METRICS=0`** to disable).
#[must_use]
pub fn metrics_enabled(raw: Option<&str>) -> bool {
    !matches!(raw, Some("0") | Some("false") | Some("no"))
}

/// Whether **`GET /metrics`** is served (default on; set **`NAUTALID_METRICS=0`** to disable).
#[must_use]
pub fn enabled_from_env() -> bool {
    metrics_enabled(std::env::var("NAUTALID_METRICS").ok().as_deref())
}

pub fn record_http_status(status: u16) {
    HTTP_REQUESTS.fetch_add(1, Ordering::Relaxed);
    if (400..500).contains(&status) {
        HTTP_CLIENT_ERRORS.fetch_add(1, Ordering::Relaxed);
    } else if status >= 500 {
        HTTP_SERVER_ERRORS.fetch_add(1, Ordering::Relaxed);
    }
}

pub fn record_filter_denied() {
    FILTER_DENIED.fetch_add(1, Ordering::Relaxed);
}

pub fn record_kwt_auth_failure() {
    KWT_AUTH_FAILURES.fetch_add(1, Ordering::Relaxed);
}

pub fn record_kwt_auth_success() {
    KWT_AUTH_SUCCESSES.fetch_add(1, Ordering::Relaxed);
}

fn counter(name: &str, help: &str, value: u64) -> String {
    format!("# HELP {name} {help}\n# TYPE {name} counter\n{name} {value}\n")
}

fn gauge(name: &str, help: &str, value: u64) -> String {
    format!("# HELP {name} {help}\n# TYPE {name} gauge\n{name} {value}\n")
}

/// Render Prometheus text format (0.0.4). When **`detailed`** is false, omits sensitive gauges/labels.
pub fn render_prometheus(
    bus_ready: bool,
    kwt_replay_entries: Option<usize>,
    kwt_replay_backend: Option<&str>,
    detailed: bool,
) -> String {
    let mut out = String::new();
    out.push_str(&counter(
        "nautalid_http_requests_total",
        "Total HTTP responses served",
        HTTP_REQUESTS.load(Ordering::Relaxed),
    ));
    out.push_str(&counter(
        "nautalid_http_client_errors_total",
        "HTTP 4xx responses",
        HTTP_CLIENT_ERRORS.load(Ordering::Relaxed),
    ));
    out.push_str(&counter(
        "nautalid_http_server_errors_total",
        "HTTP 5xx responses",
        HTTP_SERVER_ERRORS.load(Ordering::Relaxed),
    ));
    out.push_str(&counter(
        "nautalid_filter_denied_total",
        "Requests rejected by the filter layer",
        FILTER_DENIED.load(Ordering::Relaxed),
    ));
    out.push_str(&counter(
        "nautalid_kwt_auth_failures_total",
        "Failed KWT validations on protected routes",
        KWT_AUTH_FAILURES.load(Ordering::Relaxed),
    ));
    out.push_str(&counter(
        "nautalid_kwt_auth_successes_total",
        "Successful KWT validations on protected routes",
        KWT_AUTH_SUCCESSES.load(Ordering::Relaxed),
    ));
    out.push_str(&gauge(
        "nautalid_bus_ready",
        "Control-plane bus bound and accepting (1=yes, 0=no)",
        u64::from(bus_ready),
    ));
    if detailed {
        if let Some(entries) = kwt_replay_entries {
            out.push_str(&gauge(
                "nautalid_kwt_replay_entries",
                "In-memory JTI replay cache size",
                entries as u64,
            ));
        }
        out.push_str(&format!(
            "# HELP nautalid_build_info Static build metadata\n\
             # TYPE nautalid_build_info gauge\n\
             nautalid_build_info{{version=\"{}\",kwt_replay_backend=\"{}\"}} 1\n",
            env!("CARGO_PKG_VERSION"),
            kwt_replay_backend.unwrap_or("none")
        ));
    } else {
        out.push_str(&format!(
            "# HELP nautalid_build_info Static build metadata\n\
             # TYPE nautalid_build_info gauge\n\
             nautalid_build_info{{version=\"{}\"}} 1\n",
            env!("CARGO_PKG_VERSION"),
        ));
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn render_includes_version_and_counters() {
        let body = render_prometheus(true, Some(3), Some("memory"), true);
        assert!(body.contains("nautalid_build_info"));
        assert!(body.contains(env!("CARGO_PKG_VERSION")));
        assert!(body.contains("nautalid_kwt_replay_entries 3"));
        assert!(body.contains("nautalid_bus_ready 1"));
    }

    #[test]
    fn render_redacts_sensitive_when_not_detailed() {
        let body = render_prometheus(true, Some(3), Some("memory"), false);
        assert!(!body.contains("kwt_replay_backend"));
        assert!(!body.contains("nautalid_kwt_replay_entries"));
        assert!(body.contains("nautalid_build_info"));
    }

    #[test]
    fn metrics_disabled_by_env_zero() {
        assert!(!metrics_enabled(Some("0")));
        assert!(!metrics_enabled(Some("false")));
        assert!(metrics_enabled(None));
        assert!(metrics_enabled(Some("1")));
    }
}