Skip to main content

meerkat_core/lifecycle/
identifiers.rs

1//! Core lifecycle identifiers
2//!
3//! Only identifiers that core directly operates on during run execution.
4//! Runtime-only identifiers (RuntimeEventId, LogicalRuntimeId, etc.) live in `meerkat-runtime`.
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// Unique identifier for a run (a single execution of the agent loop).
10///
11/// Core emits this in `RunEvent` and tracks it across the run lifecycle.
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
14pub struct RunId(#[cfg_attr(feature = "schema", schemars(with = "String"))] pub Uuid);
15
16impl RunId {
17    /// Create a new run ID using UUID v7 (time-ordered).
18    pub fn new() -> Self {
19        Self(crate::time_compat::new_uuid_v7())
20    }
21
22    /// Create from an existing UUID.
23    pub fn from_uuid(uuid: Uuid) -> Self {
24        Self(uuid)
25    }
26}
27
28impl Default for RunId {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl std::fmt::Display for RunId {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}", self.0)
37    }
38}
39
40/// Opaque identifier for an authority-owned async wait request.
41///
42/// Runtime-owned barrier waits use this to distinguish the wait lifecycle from
43/// the turn `RunId` they eventually feed back into.
44#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
45pub struct WaitRequestId(pub Uuid);
46
47impl WaitRequestId {
48    /// Create a new wait request ID using UUID v7 (time-ordered).
49    pub fn new() -> Self {
50        Self(crate::time_compat::new_uuid_v7())
51    }
52
53    /// Create from an existing UUID.
54    pub fn from_uuid(uuid: Uuid) -> Self {
55        Self(uuid)
56    }
57}
58
59impl Default for WaitRequestId {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl std::fmt::Display for WaitRequestId {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "{}", self.0)
68    }
69}
70
71/// Opaque identifier for an input accepted by the runtime layer.
72///
73/// Core passes this through in `contributing_input_ids` on receipts and events
74/// but NEVER interprets it. The runtime layer creates and manages these.
75#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
76pub struct InputId(pub Uuid);
77
78impl InputId {
79    /// Create a new input ID using UUID v7 (time-ordered).
80    pub fn new() -> Self {
81        Self(crate::time_compat::new_uuid_v7())
82    }
83
84    /// Create from an existing UUID.
85    pub fn from_uuid(uuid: Uuid) -> Self {
86        Self(uuid)
87    }
88}
89
90impl Default for InputId {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl std::fmt::Display for InputId {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        write!(f, "{}", self.0)
99    }
100}
101
102#[cfg(test)]
103#[allow(clippy::unwrap_used)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn run_id_new_is_unique() {
109        let a = RunId::new();
110        let b = RunId::new();
111        assert_ne!(a, b);
112    }
113
114    #[test]
115    fn run_id_from_uuid_roundtrip() {
116        let uuid = Uuid::now_v7();
117        let id = RunId::from_uuid(uuid);
118        assert_eq!(id.0, uuid);
119    }
120
121    #[test]
122    fn run_id_serde_roundtrip() {
123        let id = RunId::new();
124        let json = serde_json::to_string(&id).unwrap();
125        let parsed: RunId = serde_json::from_str(&json).unwrap();
126        assert_eq!(id, parsed);
127    }
128
129    #[test]
130    fn run_id_display() {
131        let uuid = Uuid::nil();
132        let id = RunId::from_uuid(uuid);
133        assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
134    }
135
136    #[test]
137    fn input_id_new_is_unique() {
138        let a = InputId::new();
139        let b = InputId::new();
140        assert_ne!(a, b);
141    }
142
143    #[test]
144    fn wait_request_id_new_is_unique() {
145        let a = WaitRequestId::new();
146        let b = WaitRequestId::new();
147        assert_ne!(a, b);
148    }
149
150    #[test]
151    fn wait_request_id_serde_roundtrip() {
152        let id = WaitRequestId::new();
153        let json = serde_json::to_string(&id).unwrap();
154        let parsed: WaitRequestId = serde_json::from_str(&json).unwrap();
155        assert_eq!(id, parsed);
156    }
157
158    #[test]
159    fn wait_request_id_display() {
160        let uuid = Uuid::nil();
161        let id = WaitRequestId::from_uuid(uuid);
162        assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
163    }
164
165    #[test]
166    fn input_id_serde_roundtrip() {
167        let id = InputId::new();
168        let json = serde_json::to_string(&id).unwrap();
169        let parsed: InputId = serde_json::from_str(&json).unwrap();
170        assert_eq!(id, parsed);
171    }
172
173    #[test]
174    fn input_id_display() {
175        let uuid = Uuid::nil();
176        let id = InputId::from_uuid(uuid);
177        assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
178    }
179
180    #[test]
181    fn run_id_and_input_id_are_distinct_types() {
182        // Compile-time type safety: these are different types
183        let run_id = RunId::new();
184        let wait_request_id = WaitRequestId::new();
185        let input_id = InputId::new();
186        // They cannot be compared directly (different types)
187        let _ = run_id;
188        let _ = wait_request_id;
189        let _ = input_id;
190    }
191}