grit-core 0.2.4

Embedded, bi-temporal property graph for agent memory: one SQLite file, in-process, deterministic
Documentation
//! Injected time. Library code paths never call `SystemTime::now()` directly
//! (Design Invariant 3); everything flows through a [`Clock`] chosen by the
//! caller at open time.

use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering};

/// Milliseconds since the Unix epoch, UTC.
pub type TimestampMs = i64;

/// A source of wall-clock time. Injected into [`crate::Grit`] via
/// [`crate::Options`]; tests inject [`ManualClock`] for full determinism.
///
/// Contract: implementations should return non-negative values (times at or
/// after the Unix epoch). grit clamps negative readings to 0 wherever they
/// would feed an HLC — the sortable HLC encoding is only order-preserving
/// for non-negative wall times.
///
/// # Example
/// ```
/// use grit_core::{Clock, ManualClock};
/// let clock = ManualClock::new(1_000);
/// assert_eq!(clock.now_ms(), 1_000);
/// clock.advance_ms(5);
/// assert_eq!(clock.now_ms(), 1_005);
/// ```
pub trait Clock: Send + Sync {
    /// Current wall time in milliseconds since the Unix epoch.
    fn now_ms(&self) -> TimestampMs;
}

/// The real system clock. This is the *caller's* choice of default — grit
/// itself only ever sees the trait.
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;

impl Clock for SystemClock {
    fn now_ms(&self) -> TimestampMs {
        // A system clock set before 1970 saturates to 0 rather than panicking
        // (see the Clock contract).
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_or(0, |d| d.as_millis() as TimestampMs)
    }
}

/// A deterministic, manually-advanced clock for tests and simulations.
///
/// # Example
/// ```
/// use grit_core::{Clock, ManualClock};
/// let c = ManualClock::new(42);
/// c.set_ms(100);
/// assert_eq!(c.now_ms(), 100);
/// ```
#[derive(Debug, Default)]
pub struct ManualClock {
    now: AtomicI64,
}

impl ManualClock {
    /// Create a clock frozen at `now_ms`.
    pub fn new(now_ms: TimestampMs) -> Self {
        Self {
            now: AtomicI64::new(now_ms),
        }
    }

    /// Move the clock forward by `delta_ms`.
    pub fn advance_ms(&self, delta_ms: i64) {
        self.now.fetch_add(delta_ms, Ordering::SeqCst);
    }

    /// Set the clock to an absolute time.
    pub fn set_ms(&self, now_ms: TimestampMs) {
        self.now.store(now_ms, Ordering::SeqCst);
    }
}

impl Clock for ManualClock {
    fn now_ms(&self) -> TimestampMs {
        self.now.load(Ordering::SeqCst)
    }
}

impl<C: Clock + ?Sized> Clock for Arc<C> {
    fn now_ms(&self) -> TimestampMs {
        (**self).now_ms()
    }
}