Skip to main content

wm_core/
time.rs

1//! Time — one generation point for every persisted timestamp.
2//!
3//! Three canonical forms exist in the wild (see
4//! `docs/TIMESTAMP_CONVENTIONS.md` for the full per-surface registry):
5//!
6//! - **Epoch milliseconds** (`now_unix_millis`) — the default for new
7//!   persisted event records (sessions, friction JSONL, the opencode
8//!   corpus). Sub-second ordering matters for parallel sessions, and the
9//!   opencode corpus we correlate against (Phase 4 archaeology) is millis.
10//! - **Epoch seconds** (`now_unix_secs`) — legacy schemas already fixed to
11//!   seconds (karma chain, write-audit journal, sangha locks). Do not
12//!   migrate casually: readers exist.
13//! - **RFC 3339** (`now_rfc3339`) — human-facing coordination surfaces
14//!   (lease ledgers, drill reports) where an agent reads the value with a
15//!   bare eye.
16//!
17//! Rules:
18//! 1. Never hand-roll `SystemTime::now()` or `Utc::now().timestamp*()` at
19//!    a write site — go through this module so the convention has one
20//!    implementation.
21//! 2. A persisted timestamp field's doc comment names its unit.
22//! 3. Cross-surface correlation converts through typed `chrono::DateTime`,
23//!    never by comparing raw integers (the 1000× ambiguity class).
24
25#![forbid(unsafe_code)]
26
27use std::time::{SystemTime, UNIX_EPOCH};
28
29/// Current Unix time in **milliseconds** — the default for new persisted
30/// event records.
31#[must_use]
32pub fn now_unix_millis() -> i64 {
33    SystemTime::now()
34        .duration_since(UNIX_EPOCH)
35        .map_or(0, |d| d.as_millis() as i64)
36}
37
38/// Current Unix time in **seconds** — legacy schemas fixed to seconds
39/// (karma chain, write-audit journal, sangha locks).
40#[must_use]
41pub fn now_unix_secs() -> u64 {
42    SystemTime::now()
43        .duration_since(UNIX_EPOCH)
44        .map_or(0, |d| d.as_secs())
45}
46
47/// Current time as **RFC 3339** with second precision and `Z` offset —
48/// human-facing coordination surfaces (lease ledgers, drill reports).
49#[must_use]
50pub fn now_rfc3339() -> String {
51    chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
52}
53
54/// Generate a monotonic, time-ordered operation ID (UUIDv7 format) for
55/// end-to-end multi-step write sequence correlation and crash barrier detection (U6).
56#[must_use]
57pub fn new_operation_id() -> String {
58    uuid::Uuid::now_v7().to_string()
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn millis_and_secs_agree() {
67        let secs = i64::try_from(now_unix_secs()).expect("unix seconds fit i64");
68        let millis = now_unix_millis();
69        // The two calls straddle the same wall-clock second in practice;
70        // allow one second of drift between them.
71        assert!(
72            (millis - secs * 1000).abs() <= 1500,
73            "millis {millis} vs secs {secs} disagree by more than a second"
74        );
75    }
76
77    #[test]
78    fn millis_is_thirteen_digits_this_era() {
79        let millis = now_unix_millis();
80        assert!(
81            (1_000_000_000_000..=9_999_999_999_999).contains(&millis),
82            "millis outside the 13-digit era: {millis}"
83        );
84    }
85
86    #[test]
87    fn rfc3339_shape_is_z_terminated() {
88        let ts = now_rfc3339();
89        assert!(ts.ends_with('Z'), "got: {ts}");
90        assert_eq!(ts.len(), 20, "second-precision Z format, got: {ts}");
91        assert!(chrono::DateTime::parse_from_rfc3339(&ts).is_ok());
92    }
93
94    #[test]
95    fn operation_id_is_valid_time_ordered_uuid() {
96        let id1 = new_operation_id();
97        let id2 = new_operation_id();
98        assert_ne!(id1, id2);
99        let parsed1 = uuid::Uuid::parse_str(&id1).expect("valid uuid");
100        let parsed2 = uuid::Uuid::parse_str(&id2).expect("valid uuid");
101        assert_eq!(parsed1.get_version(), Some(uuid::Version::SortRand));
102        assert_eq!(parsed2.get_version(), Some(uuid::Version::SortRand));
103        assert!(id1 <= id2, "monotonic time order");
104    }
105}