klieo-ops 3.5.0

Operational layer above klieo-core: supervisor, governor, gates, escalation, worklog, handoff.
Documentation
//! Clock trait + system-time default impl.
//!
//! Time anchoring matters for audit. The trait abstracts wall-clock + drift
//! reporting so a future RFC 3161 trusted-timestamp adapter can drop in
//! without touching call sites.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::sync::Arc;

/// Pluggable wall-clock + drift source.
#[async_trait]
pub trait Clock: Send + Sync {
    /// Current UTC time.
    async fn now(&self) -> DateTime<Utc>;

    /// Drift in milliseconds relative to a configured reference. `None`
    /// means "no reference configured" — verifier records `drift: unknown`.
    async fn drift_ms(&self) -> Option<i64>;
}

/// `Arc<dyn Clock>` alias.
pub type SharedClock = Arc<dyn Clock>;

/// System-time clock. Wraps `chrono::Utc::now`. Optional NTP/PTP reference
/// (not implemented in Phase A — reference name recorded only).
#[derive(Default)]
pub struct SystemClock {
    ntp_reference: Option<String>,
}

impl SystemClock {
    /// Build with an NTP/PTP source name (purely informational in Phase A;
    /// actual drift query lands with a Phase B RFC 3161 adapter).
    #[must_use]
    pub fn with_ntp(reference: impl Into<String>) -> Self {
        Self {
            ntp_reference: Some(reference.into()),
        }
    }

    /// Reference name (for evidence-bundle annotation).
    #[must_use]
    pub fn reference(&self) -> Option<&str> {
        self.ntp_reference.as_deref()
    }
}

#[async_trait]
impl Clock for SystemClock {
    async fn now(&self) -> DateTime<Utc> {
        Utc::now()
    }

    async fn drift_ms(&self) -> Option<i64> {
        None
    }
}

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

    #[tokio::test]
    async fn system_clock_now_returns_recent_time() {
        let c = SystemClock::default();
        let now = c.now().await;
        let recent = chrono::Utc::now().signed_duration_since(now);
        assert!(recent.num_seconds().abs() < 5);
    }

    #[tokio::test]
    async fn system_clock_drift_unknown_without_ntp() {
        let c = SystemClock::default();
        let d = c.drift_ms().await;
        assert!(
            d.is_none(),
            "drift must be unknown when no NTP source configured"
        );
    }
}