@@ -311,14 +311,15 @@ pub struct Executor {
311311 /// `plugin_settings.effect_log_path` or programmatically. Opt-in.
312312 effect_log : Option < Arc < dyn DurableEffectLog > > ,
313313
314- /// Audit stream identity + counters, fresh per executor lifetime (a new
315- /// identity on config reload). Each emitted record carries its per-stream
316- /// counter (`decision_seq` / `effect_seq`, gap-free → completeness) and the
317- /// shared `emission_seq` (global across both → interleaved order). `Arc`
318- /// so copy-on-write snapshot mutations stay on the same stream.
319- decision_stream_id : Arc < str > ,
314+ /// Audit stream identity + counters. `epoch` is the executor's boot time
315+ /// (Unix nanos), captured once — it scopes the counters so a restart is
316+ /// distinguishable from a loss and orders records across restarts. Each
317+ /// record carries its per-type counter (`decision_seq` / `effect_seq`,
318+ /// gap-free → completeness) and the shared `emission_seq` (global across
319+ /// both → interleaved order). The counters are `Arc` so copy-on-write
320+ /// snapshot mutations stay on the same stream.
321+ epoch : u64 ,
320322 decision_seq : Arc < AtomicU64 > ,
321- effect_stream_id : Arc < str > ,
322323 effect_seq : Arc < AtomicU64 > ,
323324 emission_seq : Arc < AtomicU64 > ,
324325}
@@ -330,9 +331,14 @@ impl Executor {
330331 config,
331332 audit_handlers : Vec :: new ( ) ,
332333 effect_log : None ,
333- decision_stream_id : Arc :: from ( format ! ( "dec-{}" , uuid:: Uuid :: new_v4( ) . simple( ) ) ) ,
334+ // Boot time in Unix nanoseconds — an orderable epoch that needs no
335+ // persistence. A new executor (restart or config reload) gets a
336+ // larger value, so a verifier tells a reset from a loss.
337+ epoch : std:: time:: SystemTime :: now ( )
338+ . duration_since ( std:: time:: UNIX_EPOCH )
339+ . map ( |d| d. as_nanos ( ) as u64 )
340+ . unwrap_or ( 0 ) ,
334341 decision_seq : Arc :: new ( AtomicU64 :: new ( 0 ) ) ,
335- effect_stream_id : Arc :: from ( format ! ( "eff-{}" , uuid:: Uuid :: new_v4( ) . simple( ) ) ) ,
336342 effect_seq : Arc :: new ( AtomicU64 :: new ( 0 ) ) ,
337343 emission_seq : Arc :: new ( AtomicU64 :: new ( 0 ) ) ,
338344 }
@@ -383,7 +389,8 @@ impl Executor {
383389 /// `PipelineResult.decision_log`.
384390 fn stamp_decision_stream ( & self , decisions : & mut DecisionLog ) {
385391 decisions. set_stream (
386- self . decision_stream_id . to_string ( ) ,
392+ self . epoch ,
393+ "decision" . to_string ( ) ,
387394 self . decision_seq . fetch_add ( 1 , Ordering :: Relaxed ) ,
388395 self . emission_seq . fetch_add ( 1 , Ordering :: Relaxed ) ,
389396 ) ;
@@ -692,7 +699,7 @@ impl Executor {
692699 // The configured WAL (opt-in). `None` → ordering-only, not
693700 // fail-closed; `Some` → durable-before-fanout, fail-closed.
694701 durable : self . effect_log . clone ( ) ,
695- stream_id : self . effect_stream_id . clone ( ) ,
702+ epoch : self . epoch ,
696703 stream_seq : self . effect_seq . clone ( ) ,
697704 emission_seq : self . emission_seq . clone ( ) ,
698705 } ) ) ;
@@ -1344,10 +1351,10 @@ struct AuditEffectEmitter {
13441351 /// before fanning out and fails closed if that write fails. `None` until
13451352 /// slice 3b wires a real WAL — then emit is ordering-only.
13461353 durable : Option < Arc < dyn DurableEffectLog > > ,
1347- /// Effect stream identity + counters (shared with the executor). Each
1348- /// emitted record is stamped with `stream_seq` (gap-free within the effect
1349- /// stream) and the global `emission_seq` (interleaved order vs decisions).
1350- stream_id : Arc < str > ,
1354+ /// Boot epoch + counters (shared with the executor). Each emitted record is
1355+ /// stamped with `epoch`, ` stream_seq` (gap-free within the effect stream),
1356+ /// and the global `emission_seq` (interleaved order vs decisions).
1357+ epoch : u64 ,
13511358 stream_seq : Arc < AtomicU64 > ,
13521359 emission_seq : Arc < AtomicU64 > ,
13531360}
@@ -1373,7 +1380,8 @@ impl EffectEmitter for AuditEffectEmitter {
13731380 // counter across decisions and effects (interleaved order).
13741381 let mut stamped = effect. clone ( ) ;
13751382 stamped. plugin_name = Some ( self . plugin_name . clone ( ) ) ;
1376- stamped. stream_id = Some ( self . stream_id . to_string ( ) ) ;
1383+ stamped. epoch = Some ( self . epoch ) ;
1384+ stamped. stream_id = Some ( "effect" . to_string ( ) ) ;
13771385 stamped. stream_seq = Some ( self . stream_seq . fetch_add ( 1 , Ordering :: Relaxed ) ) ;
13781386 stamped. emission_seq = Some ( self . emission_seq . fetch_add ( 1 , Ordering :: Relaxed ) ) ;
13791387
@@ -1597,7 +1605,7 @@ mod tests {
15971605 plugin_name : "delegator" . into ( ) ,
15981606 timeout : Duration :: from_secs ( 5 ) ,
15991607 durable : Some ( Arc :: new ( FailingLog ) ) ,
1600- stream_id : Arc :: from ( "eff-test" ) ,
1608+ epoch : 0 ,
16011609 stream_seq : Arc :: new ( std:: sync:: atomic:: AtomicU64 :: new ( 0 ) ) ,
16021610 emission_seq : Arc :: new ( std:: sync:: atomic:: AtomicU64 :: new ( 0 ) ) ,
16031611 } ;
@@ -1616,7 +1624,7 @@ mod tests {
16161624 plugin_name : "delegator" . into ( ) ,
16171625 timeout : Duration :: from_secs ( 5 ) ,
16181626 durable : Some ( Arc :: new ( OkLog ) ) ,
1619- stream_id : Arc :: from ( "eff-test" ) ,
1627+ epoch : 0 ,
16201628 stream_seq : Arc :: new ( std:: sync:: atomic:: AtomicU64 :: new ( 0 ) ) ,
16211629 emission_seq : Arc :: new ( std:: sync:: atomic:: AtomicU64 :: new ( 0 ) ) ,
16221630 } ;
0 commit comments