1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
//! Replay-safe helpers for generating deterministic non-deterministic values.
//!
//! This module provides utilities for generating values that would normally be
//! non-deterministic (like UUIDs and timestamps) in a way that is safe for replay.
//! During replay, these helpers ensure that the same values are generated as in
//! the original execution, maintaining determinism.
//!
//! # Why Replay-Safe Helpers?
//!
//! In durable execution workflows, functions may be replayed multiple times after
//! interruptions. If your code generates random UUIDs or captures the current time,
//! these values will differ between the original execution and replays, potentially
//! causing non-deterministic behavior.
//!
//! These helpers solve this problem by:
//! - **UUIDs**: Generating deterministic UUIDs based on the operation ID and a seed
//! - **Timestamps**: Using the execution start time instead of the current time
//!
//! # Example
//!
//! ```rust,ignore
//! use durable_execution_sdk::replay_safe::{uuid_from_operation, timestamp_from_execution};
//!
//! async fn my_workflow(ctx: DurableContext) -> Result<(), DurableError> {
//! // Generate a deterministic UUID for this operation
//! let operation_id = ctx.next_operation_id();
//! let uuid = uuid_from_operation(&operation_id, 0);
//!
//! // Get the execution start timestamp (replay-safe)
//! if let Some(timestamp) = timestamp_from_execution(ctx.state()) {
//! println!("Execution started at: {}", timestamp);
//! }
//!
//! Ok(())
//! }
//! ```
//!
//! # Requirements
//!
//! - 22.1: THE Replay_Safe_Helpers MAY provide a deterministic UUID generator seeded by operation ID
//! - 22.2: THE Replay_Safe_Helpers MAY provide replay-safe timestamps derived from execution state
//! - 22.3: THE Replay_Safe_Helpers SHALL document how to use these helpers correctly
use ;
use crateExecutionState;
/// Generates a deterministic UUID from an operation ID and seed.
///
/// This function creates a UUID that is deterministic based on the input parameters.
/// The same operation_id and seed will always produce the same UUID, making it safe
/// for use in replay scenarios.
///
/// # Algorithm
///
/// The UUID is generated using blake2b hashing:
/// 1. Hash the operation_id bytes
/// 2. Hash the seed as little-endian bytes
/// 3. Take the first 16 bytes of the hash result
/// 4. Set the UUID version (4) and variant (RFC 4122) bits
///
/// # Arguments
///
/// * `operation_id` - The operation ID to use as the base for UUID generation.
/// This should be unique per operation within an execution.
/// * `seed` - An additional seed value to differentiate multiple UUIDs within
/// the same operation. Use different seed values (0, 1, 2, ...) if you need
/// multiple UUIDs in the same operation.
///
/// # Returns
///
/// A 128-bit UUID as a `[u8; 16]` array. You can convert this to a string
/// using the `uuid_to_string` helper function.
///
/// # Example
///
/// ```rust
/// use durable_execution_sdk::replay_safe::{uuid_from_operation, uuid_to_string};
///
/// let operation_id = "abc123";
/// let uuid_bytes = uuid_from_operation(operation_id, 0);
/// let uuid_string = uuid_to_string(&uuid_bytes);
/// println!("Generated UUID: {}", uuid_string);
///
/// // Same inputs always produce the same UUID
/// let uuid_bytes_2 = uuid_from_operation(operation_id, 0);
/// assert_eq!(uuid_bytes, uuid_bytes_2);
///
/// // Different seeds produce different UUIDs
/// let uuid_bytes_3 = uuid_from_operation(operation_id, 1);
/// assert_ne!(uuid_bytes, uuid_bytes_3);
/// ```
/// Converts a UUID byte array to a standard UUID string format.
///
/// The output format is: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
///
/// # Arguments
///
/// * `uuid_bytes` - A 16-byte array representing the UUID
///
/// # Returns
///
/// A string in standard UUID format (36 characters with hyphens).
///
/// # Example
///
/// ```rust
/// use durable_execution_sdk::replay_safe::{uuid_from_operation, uuid_to_string};
///
/// let uuid_bytes = uuid_from_operation("my-operation", 0);
/// let uuid_string = uuid_to_string(&uuid_bytes);
/// assert_eq!(uuid_string.len(), 36);
/// assert_eq!(uuid_string.chars().filter(|c| *c == '-').count(), 4);
/// ```
/// Generates a deterministic UUID string from an operation ID and seed.
///
/// This is a convenience function that combines `uuid_from_operation` and
/// `uuid_to_string` into a single call.
///
/// # Arguments
///
/// * `operation_id` - The operation ID to use as the base for UUID generation
/// * `seed` - An additional seed value to differentiate multiple UUIDs
///
/// # Returns
///
/// A string in standard UUID format (36 characters with hyphens).
///
/// # Example
///
/// ```rust
/// use durable_execution_sdk::replay_safe::uuid_string_from_operation;
///
/// let uuid = uuid_string_from_operation("my-operation", 0);
/// println!("Generated UUID: {}", uuid);
/// ```
/// Returns the replay-safe timestamp from the execution state.
///
/// This function returns the `StartTimestamp` from the EXECUTION operation,
/// which represents when the durable execution was first started. This timestamp
/// is consistent across all replays of the execution.
///
/// # Arguments
///
/// * `state` - The execution state containing the EXECUTION operation
///
/// # Returns
///
/// `Some(i64)` containing the start timestamp in milliseconds since Unix epoch
/// if the EXECUTION operation exists and has a start timestamp, `None` otherwise.
///
/// # Example
///
/// ```rust,ignore
/// use durable_execution_sdk::replay_safe::timestamp_from_execution;
///
/// async fn my_workflow(ctx: DurableContext) -> Result<(), DurableError> {
/// // Get the execution start timestamp (replay-safe)
/// if let Some(timestamp_ms) = timestamp_from_execution(ctx.state()) {
/// // Convert to seconds if needed
/// let timestamp_secs = timestamp_ms / 1000;
/// println!("Execution started at: {} seconds since epoch", timestamp_secs);
/// }
/// Ok(())
/// }
/// ```
///
/// # Why Use This Instead of `std::time::SystemTime::now()`?
///
/// Using `SystemTime::now()` in a durable execution is non-deterministic because:
/// - The first execution might capture time T1
/// - A replay might capture time T2 (different from T1)
/// - This can cause different behavior between executions
///
/// By using `timestamp_from_execution`, you always get the same timestamp
/// (the execution start time) regardless of when the replay occurs.
/// Returns the replay-safe timestamp as seconds since Unix epoch.
///
/// This is a convenience function that converts the millisecond timestamp
/// from `timestamp_from_execution` to seconds.
///
/// # Arguments
///
/// * `state` - The execution state containing the EXECUTION operation
///
/// # Returns
///
/// `Some(i64)` containing the start timestamp in seconds since Unix epoch
/// if available, `None` otherwise.
///
/// # Example
///
/// ```rust,ignore
/// use durable_execution_sdk::replay_safe::timestamp_seconds_from_execution;
///
/// async fn my_workflow(ctx: DurableContext) -> Result<(), DurableError> {
/// if let Some(timestamp_secs) = timestamp_seconds_from_execution(ctx.state()) {
/// println!("Execution started at: {} seconds since epoch", timestamp_secs);
/// }
/// Ok(())
/// }
/// ```