Skip to main content

asupersync/trace/
canonicalize.rs

1//! Trace monoid and canonicalization for DPOR equivalence tracking.
2//!
3//! # Trace Monoid (Mazurkiewicz Traces)
4//!
5//! A **trace monoid** `M(Σ, I)` is the quotient of the free monoid `Σ*` by the
6//! congruence generated by an independence relation `I ⊆ Σ × Σ`. Two words
7//! (sequences of events) are *trace-equivalent* if one can be transformed into
8//! the other by repeatedly swapping adjacent independent events.
9//!
10//! Formally, given alphabet `Σ` (the set of all possible trace events) and
11//! symmetric, irreflexive independence relation `I`:
12//!
13//! ```text
14//! w₁ ≡_I w₂  ⟺  w₁ can be obtained from w₂ by a finite sequence of
15//!                 transpositions of adjacent independent letters
16//! ```
17//!
18//! The trace monoid is `M(Σ, I) = Σ* / ≡_I` with:
19//! - **Identity**: the empty trace `ε`
20//! - **Multiplication**: concatenation followed by canonicalization
21//! - **Associativity**: inherited from free monoid (concatenation is associative)
22//!
23//! # Canonical Representatives via Foata Normal Form
24//!
25//! Each equivalence class `[w]_I` has a unique **Foata normal form** (canonical
26//! representative): a sequence of layers where:
27//! - Layer 0: events with no dependent predecessors
28//! - Layer k: events whose nearest dependent predecessor is in layer k-1
29//!
30//! Within each layer, events are sorted by a deterministic comparison key
31//! derived from the event kind and data. This ensures that equivalent traces
32//! produce identical canonical forms.
33//!
34//! # Why Foata?
35//!
36//! Foata normal form is a natural choice because:
37//! 1. It is well-studied in combinatorics on words (Cartier–Foata, 1969)
38//! 2. It has a simple O(n²) construction algorithm
39//! 3. The layered structure is useful for parallelism analysis (layer depth
40//!    = critical path length)
41//! 4. It enables efficient fingerprinting for DPOR equivalence tracking
42//! 5. It is the unique canonical representative per equivalence class
43//!
44//! # Complexity
45//!
46//! - Canonicalization: O(n²) time, O(n) space (pairwise independence checks)
47//! - Fingerprint: O(n²) time, O(n) space (same algorithm, hash instead of clone)
48//! - Monoid concatenation: O((n+m)²) time for traces of length n and m
49//! - Equivalence check: O(n²) time (canonicalize + compare fingerprints)
50//!
51//! # References
52//!
53//! - Cartier & Foata, "Problèmes combinatoires de commutation et
54//!   réarrangements" (1969)
55//! - Mazurkiewicz, "Concurrent program schemes and their interpretations" (1977)
56//! - Diekert & Rozenberg, "The Book of Traces" (1995)
57//! - Mazurkiewicz, "Trace theory" (1987)
58
59use crate::trace::event::{TraceData, TraceEvent, TraceEventKind};
60use crate::trace::independence::independent;
61use crate::util::DetHasher;
62use serde::{Deserialize, Serialize};
63use std::cmp::Ordering;
64use std::hash::{Hash, Hasher};
65
66/// A trace in Foata normal form: layers of mutually independent events.
67#[derive(Debug)]
68pub struct FoataTrace {
69    /// Layers of mutually independent events, sorted deterministically.
70    layers: Vec<Vec<TraceEvent>>,
71}
72
73/// A stable, comparable key for a trace event.
74///
75/// This key mirrors the canonical intra-layer ordering used by Foata traces,
76/// making it suitable for golden fixture prefixes and diffing.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
78pub struct TraceEventKey {
79    /// Discriminant for the event kind.
80    pub kind: u8,
81    /// Primary sort key.
82    pub primary: u64,
83    /// Secondary sort key.
84    pub secondary: u64,
85    /// Tertiary sort key.
86    pub tertiary: u64,
87}
88
89impl TraceEventKey {
90    /// Create a new [`TraceEventKey`].
91    ///
92    /// This is a small convenience constructor used by canonical ordering and
93    /// fixture generation.
94    #[must_use]
95    pub const fn new(kind: u8, primary: u64, secondary: u64, tertiary: u64) -> Self {
96        Self {
97            kind,
98            primary,
99            secondary,
100            tertiary,
101        }
102    }
103}
104
105impl FoataTrace {
106    /// Returns the number of layers (the critical path length).
107    #[must_use]
108    pub fn depth(&self) -> usize {
109        self.layers.len()
110    }
111
112    /// Returns the total number of events across all layers.
113    #[must_use]
114    pub fn len(&self) -> usize {
115        self.layers.iter().map(Vec::len).sum()
116    }
117
118    /// Returns `true` if the trace contains no events.
119    #[must_use]
120    pub fn is_empty(&self) -> bool {
121        self.layers.is_empty()
122    }
123
124    /// Returns a slice of all layers.
125    #[must_use]
126    pub fn layers(&self) -> &[Vec<TraceEvent>] {
127        &self.layers
128    }
129
130    /// Flatten back into a linear sequence (canonical total order).
131    #[must_use]
132    pub fn flatten(&self) -> Vec<TraceEvent> {
133        self.layers.iter().flat_map(|l| l.iter().cloned()).collect()
134    }
135
136    /// Compute a 64-bit fingerprint of this canonical form.
137    ///
138    /// Two `FoataTrace` values representing the same equivalence class will
139    /// produce identical fingerprints.
140    #[must_use]
141    pub fn fingerprint(&self) -> u64 {
142        let mut hasher = DetHasher::default();
143        for (layer_idx, layer) in self.layers.iter().enumerate() {
144            layer_idx.hash(&mut hasher);
145            layer.len().hash(&mut hasher);
146            for event in layer {
147                event_hash_key(event).hash(&mut hasher);
148            }
149        }
150        hasher.finish()
151    }
152}
153
154/// An element of the trace monoid `M(Σ, I)`.
155///
156/// A `TraceMonoid` value represents an equivalence class of event sequences
157/// under the Mazurkiewicz congruence. It stores the canonical (Foata normal
158/// form) representative and a precomputed fingerprint for O(1) equality checks.
159///
160/// # Monoid Laws
161///
162/// - **Identity**: `TraceMonoid::identity().concat(&t) == t` and
163///   `t.concat(&TraceMonoid::identity()) == t`
164/// - **Associativity**: `a.concat(&b).concat(&c) == a.concat(&b.concat(&c))`
165/// - **Equivalence**: `TraceMonoid::from_events(w1) == TraceMonoid::from_events(w2)`
166///   iff `w1 ≡_I w2`
167#[derive(Debug)]
168pub struct TraceMonoid {
169    /// The canonical representative in Foata normal form.
170    canonical: FoataTrace,
171    /// Precomputed fingerprint for O(1) equality.
172    fingerprint: u64,
173}
174
175impl TraceMonoid {
176    /// The identity element of the trace monoid (empty trace).
177    #[must_use]
178    pub fn identity() -> Self {
179        let canonical = FoataTrace { layers: vec![] };
180        let fingerprint = canonical.fingerprint();
181        Self {
182            canonical,
183            fingerprint,
184        }
185    }
186
187    /// Construct a monoid element from a sequence of trace events.
188    ///
189    /// This is the canonical mapping `φ: Σ* → M(Σ, I)` that sends a word
190    /// (linear trace) to its equivalence class. The returned value contains
191    /// the Foata normal form as the canonical representative.
192    #[must_use]
193    pub fn from_events(events: &[TraceEvent]) -> Self {
194        let canonical = canonicalize(events);
195        let fingerprint = canonical.fingerprint();
196        Self {
197            canonical,
198            fingerprint,
199        }
200    }
201
202    /// Monoid multiplication: concatenation of traces.
203    ///
204    /// Computes `self · other` by concatenating the flattened canonical forms
205    /// and re-canonicalizing. The result is the unique canonical representative
206    /// of the product equivalence class.
207    ///
208    /// # Complexity
209    ///
210    /// O((n+m)²) where n = `self.len()` and m = `other.len()`.
211    #[must_use]
212    pub fn concat(&self, other: &Self) -> Self {
213        if self.is_identity() {
214            return Self {
215                canonical: FoataTrace {
216                    layers: other.canonical.layers.clone(),
217                },
218                fingerprint: other.fingerprint,
219            };
220        }
221        if other.is_identity() {
222            return Self {
223                canonical: FoataTrace {
224                    layers: self.canonical.layers.clone(),
225                },
226                fingerprint: self.fingerprint,
227            };
228        }
229
230        let mut combined = self.canonical.flatten();
231        combined.extend(other.canonical.flatten());
232        Self::from_events(&combined)
233    }
234
235    /// Check whether this is the identity element (empty trace).
236    #[must_use]
237    pub fn is_identity(&self) -> bool {
238        self.canonical.is_empty()
239    }
240
241    /// Returns the Foata normal form (canonical representative).
242    #[must_use]
243    pub fn canonical_form(&self) -> &FoataTrace {
244        &self.canonical
245    }
246
247    /// Returns the precomputed fingerprint for this equivalence class.
248    #[must_use]
249    pub fn class_fingerprint(&self) -> u64 {
250        self.fingerprint
251    }
252
253    /// Number of events in the canonical representative.
254    #[must_use]
255    pub fn len(&self) -> usize {
256        self.canonical.len()
257    }
258
259    /// Returns `true` if the trace is empty (identity element).
260    #[must_use]
261    pub fn is_empty(&self) -> bool {
262        self.canonical.is_empty()
263    }
264
265    /// Critical path length (number of Foata layers).
266    ///
267    /// This is the minimum number of sequential steps needed to execute the
268    /// trace, assuming maximal parallelism of independent events.
269    #[must_use]
270    pub fn critical_path_length(&self) -> usize {
271        self.canonical.depth()
272    }
273
274    /// Maximum parallelism: the width of the widest Foata layer.
275    ///
276    /// Represents the maximum number of independent events that can execute
277    /// concurrently in a single step.
278    #[must_use]
279    pub fn max_parallelism(&self) -> usize {
280        self.canonical
281            .layers
282            .iter()
283            .map(Vec::len)
284            .max()
285            .unwrap_or(0)
286    }
287
288    /// Check trace equivalence: are two monoid elements in the same class?
289    ///
290    /// This is an O(1) comparison using precomputed fingerprints. Note that
291    /// fingerprint collision is theoretically possible (probability ~2⁻⁶⁴),
292    /// so for formal verification use [`Self::equivalent_exact`].
293    #[must_use]
294    pub fn equivalent(&self, other: &Self) -> bool {
295        self.fingerprint == other.fingerprint
296    }
297
298    /// Exact trace equivalence check via structural comparison of Foata layers.
299    ///
300    /// This is more expensive than [`Self::equivalent`] but has no false positives.
301    /// Compares semantic event payloads layer-by-layer while still ignoring
302    /// non-semantic envelope fields such as sequence numbers and timestamps.
303    #[must_use]
304    pub fn equivalent_exact(&self, other: &Self) -> bool {
305        if self.canonical.depth() != other.canonical.depth() {
306            return false;
307        }
308        for (la, lb) in self
309            .canonical
310            .layers
311            .iter()
312            .zip(other.canonical.layers.iter())
313        {
314            if la.len() != lb.len() {
315                return false;
316            }
317            for (ea, eb) in la.iter().zip(lb.iter()) {
318                if !semantically_equal_event(ea, eb) {
319                    return false;
320                }
321            }
322        }
323        true
324    }
325}
326
327impl PartialEq for TraceMonoid {
328    fn eq(&self, other: &Self) -> bool {
329        // Fast path: fingerprints differ => definitely not equivalent.
330        if self.fingerprint != other.fingerprint {
331            return false;
332        }
333        // Guard against theoretical 64-bit hash collisions.
334        self.equivalent_exact(other)
335    }
336}
337
338impl Eq for TraceMonoid {}
339
340/// Compute the Foata normal form of a trace.
341///
342/// Events are grouped into layers based on the happens-before order induced
343/// by the independence relation. Within each layer, events are sorted by a
344/// deterministic comparison key.
345///
346/// # Example
347///
348/// ```ignore
349/// let trace = vec![spawn(1, t1, r1), spawn(2, t2, r2), complete(3, t1, r1)];
350/// let foata = canonicalize(&trace);
351/// // Layer 0: [spawn(t1, r1), spawn(t2, r2)]  — independent, sorted by key
352/// // Layer 1: [complete(t1, r1)]               — depends on spawn(t1)
353/// assert_eq!(foata.depth(), 2);
354/// ```
355#[must_use]
356pub fn canonicalize(events: &[TraceEvent]) -> FoataTrace {
357    let n = events.len();
358    if n == 0 {
359        return FoataTrace { layers: vec![] };
360    }
361
362    // Step 1: Compute layer assignment for each event.
363    // layer[j] = 1 + max(layer[i]) for all i < j where events[i] and events[j]
364    // are dependent.
365    let mut layer_of = vec![0usize; n];
366    let mut max_layer = 0usize;
367
368    for j in 1..n {
369        for i in 0..j {
370            if !independent(&events[i], &events[j]) {
371                layer_of[j] = layer_of[j].max(layer_of[i] + 1);
372            }
373        }
374        max_layer = max_layer.max(layer_of[j]);
375    }
376
377    // Step 2: Group events by layer.
378    let mut layers: Vec<Vec<TraceEvent>> = vec![vec![]; max_layer + 1];
379    for (idx, event) in events.iter().enumerate() {
380        layers[layer_of[idx]].push(event.clone());
381    }
382
383    // Step 3: Sort within each layer deterministically.
384    for layer in &mut layers {
385        layer.sort_by(event_cmp);
386    }
387
388    FoataTrace { layers }
389}
390
391/// Compute a 64-bit fingerprint for a trace's equivalence class.
392///
393/// Semantically equivalent to `canonicalize(events).fingerprint()` but avoids
394/// cloning events (only hashes in place).
395#[must_use]
396pub fn trace_fingerprint(events: &[TraceEvent]) -> u64 {
397    let n = events.len();
398    if n == 0 {
399        // Must match FoataTrace { layers: vec![] }.fingerprint()
400        return FoataTrace { layers: vec![] }.fingerprint();
401    }
402
403    // Layer assignment (same algorithm as canonicalize).
404    let mut layer_of = vec![0usize; n];
405    let mut max_layer = 0usize;
406
407    for j in 1..n {
408        for i in 0..j {
409            if !independent(&events[i], &events[j]) {
410                layer_of[j] = layer_of[j].max(layer_of[i] + 1);
411            }
412        }
413        max_layer = max_layer.max(layer_of[j]);
414    }
415
416    // Group indices by layer, sort within layer, hash.
417    let mut layer_indices: Vec<Vec<usize>> = vec![vec![]; max_layer + 1];
418    for (idx, &layer) in layer_of.iter().enumerate() {
419        layer_indices[layer].push(idx);
420    }
421
422    let mut hasher = DetHasher::default();
423    for (layer_idx, indices) in layer_indices.iter_mut().enumerate() {
424        indices.sort_by(|&a, &b| event_cmp(&events[a], &events[b]));
425        layer_idx.hash(&mut hasher);
426        indices.len().hash(&mut hasher);
427        for &idx in indices.iter() {
428            event_hash_key(&events[idx]).hash(&mut hasher);
429        }
430    }
431    hasher.finish()
432}
433
434// === Internal: deterministic event ordering ===
435
436/// Stable discriminant for `TraceEventKind`.
437///
438/// These values are fixed for fingerprint stability across versions.
439fn kind_discriminant(kind: TraceEventKind) -> u8 {
440    match kind {
441        TraceEventKind::Spawn => 0,
442        TraceEventKind::Schedule => 1,
443        TraceEventKind::Yield => 2,
444        TraceEventKind::Wake => 3,
445        TraceEventKind::Poll => 4,
446        TraceEventKind::Complete => 5,
447        TraceEventKind::CancelRequest => 6,
448        TraceEventKind::CancelAck => 7,
449        TraceEventKind::RegionCloseBegin => 8,
450        TraceEventKind::RegionCloseComplete => 9,
451        TraceEventKind::RegionCreated => 10,
452        TraceEventKind::RegionCancelled => 11,
453        TraceEventKind::ObligationReserve => 12,
454        TraceEventKind::ObligationCommit => 13,
455        TraceEventKind::ObligationAbort => 14,
456        TraceEventKind::ObligationLeak => 15,
457        TraceEventKind::TimeAdvance => 16,
458        TraceEventKind::TimerScheduled => 17,
459        TraceEventKind::TimerFired => 18,
460        TraceEventKind::TimerCancelled => 19,
461        TraceEventKind::IoRequested => 20,
462        TraceEventKind::IoReady => 21,
463        TraceEventKind::IoResult => 22,
464        TraceEventKind::IoError => 23,
465        TraceEventKind::RngSeed => 24,
466        TraceEventKind::RngValue => 25,
467        TraceEventKind::Checkpoint => 26,
468        TraceEventKind::FuturelockDetected => 27,
469        TraceEventKind::ChaosInjection => 28,
470        TraceEventKind::UserTrace => 29,
471        TraceEventKind::MonitorCreated => 30,
472        TraceEventKind::MonitorDropped => 31,
473        TraceEventKind::DownDelivered => 32,
474        TraceEventKind::LinkCreated => 33,
475        TraceEventKind::LinkDropped => 34,
476        TraceEventKind::ExitDelivered => 35,
477        // Appended to preserve existing fingerprint assignments for prior kinds.
478        TraceEventKind::WorkerCancelRequested => 36,
479        TraceEventKind::WorkerCancelAcknowledged => 37,
480        TraceEventKind::WorkerDrainStarted => 38,
481        TraceEventKind::WorkerDrainCompleted => 39,
482        TraceEventKind::WorkerFinalizeCompleted => 40,
483    }
484}
485
486/// Pack an ArenaIndex into a u64 for deterministic ordering.
487fn pack_arena(idx: crate::util::ArenaIndex) -> u64 {
488    (u64::from(idx.index()) << 32) | u64::from(idx.generation())
489}
490
491/// Compute a sort key for deterministic intra-layer ordering.
492///
493/// The key is a 4-tuple of (kind, primary, secondary, tertiary) where each
494/// component is a fixed-width integer. This ensures total, deterministic
495/// ordering within a Foata layer.
496fn event_sort_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
497    let k = kind_discriminant(event.kind);
498    match &event.data {
499        TraceData::Task { task, region }
500        | TraceData::Cancel { task, region, .. }
501        | TraceData::Futurelock { task, region, .. } => {
502            (k, pack_arena(task.0), pack_arena(region.0), 0)
503        }
504        TraceData::Region { region, parent } => (
505            k,
506            pack_arena(region.0),
507            parent.map_or(0, |p| pack_arena(p.0)),
508            0,
509        ),
510        TraceData::RegionCancel { region, .. } => (k, pack_arena(region.0), 0, 0),
511        TraceData::Obligation {
512            obligation,
513            task,
514            region,
515            ..
516        } => (
517            k,
518            pack_arena(obligation.0),
519            pack_arena(task.0),
520            pack_arena(region.0),
521        ),
522        TraceData::Time { old, new } => (k, old.as_nanos(), new.as_nanos(), 0),
523        TraceData::Timer { timer_id, .. } => (k, *timer_id, 0, 0),
524        TraceData::IoRequested { token, .. } | TraceData::IoReady { token, .. } => {
525            (k, *token, 0, 0)
526        }
527        TraceData::IoResult { token, bytes } => {
528            // Preserve total ordering of i64 in u64 space by flipping the sign bit.
529            let bytes_key = (*bytes).cast_unsigned() ^ (1u64 << 63);
530            (k, *token, bytes_key, 0)
531        }
532        TraceData::IoError { token, kind } => (k, *token, u64::from(*kind), 0),
533        TraceData::RngSeed { seed } => (k, *seed, 0, 0),
534        TraceData::RngValue { value } => (k, *value, 0, 0),
535        TraceData::Checkpoint {
536            sequence,
537            active_tasks,
538            active_regions,
539        } => (
540            k,
541            *sequence,
542            u64::from(*active_tasks),
543            u64::from(*active_regions),
544        ),
545        TraceData::Chaos { task, .. } => {
546            let task_key = task.map_or(0, |t| pack_arena(t.0));
547            (k, task_key, 0, 0)
548        }
549        TraceData::Message(msg) => {
550            let mut h = DetHasher::default();
551            msg.hash(&mut h);
552            (k, h.finish(), 0, 0)
553        }
554        TraceData::Monitor {
555            monitor_ref,
556            watcher,
557            monitored,
558            ..
559        } => (
560            k,
561            *monitor_ref,
562            pack_arena(watcher.0),
563            pack_arena(monitored.0),
564        ),
565        TraceData::Down {
566            monitor_ref,
567            monitored,
568            completion_vt,
569            ..
570        } => (
571            k,
572            completion_vt.as_nanos(),
573            pack_arena(monitored.0),
574            *monitor_ref,
575        ),
576        TraceData::Link {
577            link_ref,
578            task_a,
579            task_b,
580            ..
581        } => (k, *link_ref, pack_arena(task_a.0), pack_arena(task_b.0)),
582        TraceData::Exit {
583            link_ref,
584            from,
585            failure_vt,
586            ..
587        } => (k, failure_vt.as_nanos(), pack_arena(from.0), *link_ref),
588        TraceData::Worker {
589            job_id,
590            task,
591            region,
592            ..
593        } => (k, *job_id, pack_arena(task.0), pack_arena(region.0)),
594        TraceData::None => (k, 0, 0, 0),
595    }
596}
597
598/// Returns the stable key used for deterministic ordering of a trace event.
599#[must_use]
600pub fn trace_event_key(event: &TraceEvent) -> TraceEventKey {
601    let (kind, primary, secondary, tertiary) = event_sort_key(event);
602    TraceEventKey::new(kind, primary, secondary, tertiary)
603}
604
605/// Deterministic comparison of two trace events.
606fn event_cmp(a: &TraceEvent, b: &TraceEvent) -> Ordering {
607    event_sort_key(a).cmp(&event_sort_key(b))
608}
609
610/// Equality on the semantic content of a trace event.
611fn semantically_equal_event(a: &TraceEvent, b: &TraceEvent) -> bool {
612    a.version == b.version && a.kind == b.kind && a.data == b.data
613}
614
615/// Hash key for fingerprinting. Must agree with `event_sort_key` ordering:
616/// events with the same sort key produce the same hash key.
617fn event_hash_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
618    event_sort_key(event)
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624    use crate::monitor::DownReason;
625    use crate::record::ObligationKind;
626    use crate::types::{CancelReason, ObligationId, RegionId, TaskId, Time};
627
628    fn tid(n: u32) -> TaskId {
629        TaskId::new_for_test(n, 0)
630    }
631
632    fn rid(n: u32) -> RegionId {
633        RegionId::new_for_test(n, 0)
634    }
635
636    fn oid(n: u32) -> ObligationId {
637        ObligationId::new_for_test(n, 0)
638    }
639
640    // === Basic structure ===
641
642    #[test]
643    fn empty_trace() {
644        let foata = canonicalize(&[]);
645        assert!(foata.is_empty());
646        assert_eq!(foata.depth(), 0);
647        assert_eq!(foata.len(), 0);
648    }
649
650    #[test]
651    fn single_event() {
652        let events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
653        let foata = canonicalize(&events);
654        assert_eq!(foata.depth(), 1);
655        assert_eq!(foata.len(), 1);
656    }
657
658    #[test]
659    fn all_independent_events_in_one_layer() {
660        // Spawns in different regions with different tasks are independent.
661        let events = [
662            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
663            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
664            TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
665        ];
666        let foata = canonicalize(&events);
667        assert_eq!(foata.depth(), 1);
668        assert_eq!(foata.layers()[0].len(), 3);
669    }
670
671    #[test]
672    fn chain_of_dependent_events() {
673        // Same task: spawn -> poll -> complete forms a chain.
674        let events = [
675            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
676            TraceEvent::poll(2, Time::ZERO, tid(1), rid(1)),
677            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
678        ];
679        let foata = canonicalize(&events);
680        assert_eq!(foata.depth(), 3);
681        assert_eq!(foata.layers()[0].len(), 1);
682        assert_eq!(foata.layers()[1].len(), 1);
683        assert_eq!(foata.layers()[2].len(), 1);
684    }
685
686    #[test]
687    fn diamond_dependency() {
688        // T1 and T2 are independent, but both depend on region creation
689        // and both must complete before region close.
690        //
691        //   region_create(R1)
692        //     /          \
693        //  spawn(T1,R1) spawn(T2,R1)    -- independent (both read R1)
694        //     \          /
695        //  complete(T1) complete(T2)     -- independent (different tasks)
696        //
697        // But region_create writes R1 and spawns read R1 -> dependent.
698        // So: layer 0 = [region_create], layer 1 = [spawn T1, spawn T2],
699        //     layer 2 = [complete T1, complete T2]
700        let events = [
701            TraceEvent::region_created(1, Time::ZERO, rid(1), None),
702            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
703            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
704            TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
705            TraceEvent::complete(5, Time::ZERO, tid(2), rid(1)),
706        ];
707        let foata = canonicalize(&events);
708        assert_eq!(foata.depth(), 3);
709        assert_eq!(foata.layers()[0].len(), 1); // region_create
710        assert_eq!(foata.layers()[1].len(), 2); // spawn T1, spawn T2
711        assert_eq!(foata.layers()[2].len(), 2); // complete T1, complete T2
712    }
713
714    // === Equivalence: swapping independent events produces same canonical form ===
715
716    #[test]
717    fn swapped_independent_events_same_fingerprint() {
718        let trace_a = [
719            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
720            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
721        ];
722        let trace_b = [
723            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
724            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
725        ];
726        let fp_a = trace_fingerprint(&trace_a);
727        let fp_b = trace_fingerprint(&trace_b);
728        assert_eq!(fp_a, fp_b);
729    }
730
731    #[test]
732    fn swapped_independent_events_same_canonical_form() {
733        let trace_a = [
734            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
735            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
736        ];
737        let trace_b = [
738            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
739            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
740        ];
741        let foata_a = canonicalize(&trace_a);
742        let foata_b = canonicalize(&trace_b);
743        assert_eq!(foata_a.depth(), foata_b.depth());
744        assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
745    }
746
747    #[test]
748    fn different_dependent_orders_different_fingerprints() {
749        // Same-task events in different orders are different traces (not equivalent).
750        let trace_a = [
751            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
752            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
753        ];
754        let trace_b = [
755            TraceEvent::complete(1, Time::ZERO, tid(1), rid(1)),
756            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
757        ];
758        let fp_a = trace_fingerprint(&trace_a);
759        let fp_b = trace_fingerprint(&trace_b);
760        // These are genuinely different traces (different causal structure).
761        assert_ne!(fp_a, fp_b);
762    }
763
764    // === Deterministic ordering keys (bd-wwnkh) ===
765
766    #[test]
767    fn down_delivered_canonicalizes_by_completion_vt_monitored_monitor_ref() {
768        let e0 = TraceEvent::down_delivered(
769            1,
770            Time::ZERO,
771            1,
772            tid(10),
773            tid(3),
774            Time::from_nanos(4),
775            DownReason::Normal,
776        );
777        let e1 = TraceEvent::down_delivered(
778            2,
779            Time::ZERO,
780            10,
781            tid(11),
782            tid(1),
783            Time::from_nanos(5),
784            DownReason::Normal,
785        );
786        let e2 = TraceEvent::down_delivered(
787            3,
788            Time::ZERO,
789            9,
790            tid(12),
791            tid(1),
792            Time::from_nanos(5),
793            DownReason::Normal,
794        );
795        let e3 = TraceEvent::down_delivered(
796            4,
797            Time::ZERO,
798            1,
799            tid(13),
800            tid(2),
801            Time::from_nanos(5),
802            DownReason::Normal,
803        );
804
805        // All four events are independent (distinct watcher tasks). Canonical
806        // ordering within the layer must follow:
807        // (completion_vt, monitored_tid, monitor_ref).
808        let trace_a = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
809        let trace_b = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
810
811        let foata_a = canonicalize(&trace_a);
812        let foata_b = canonicalize(&trace_b);
813        assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
814        assert_eq!(foata_a.depth(), 1);
815
816        let flat = foata_a.flatten();
817        assert_eq!(flat.len(), 4);
818        assert_eq!(flat[0], e0);
819        assert_eq!(flat[1], e2);
820        assert_eq!(flat[2], e1);
821        assert_eq!(flat[3], e3);
822    }
823
824    #[test]
825    fn exit_delivered_canonicalizes_by_failure_vt_from_link_ref() {
826        let e0 = TraceEvent::exit_delivered(
827            1,
828            Time::ZERO,
829            1,
830            tid(2),
831            tid(20),
832            Time::from_nanos(9),
833            DownReason::Error("boom".to_string()),
834        );
835        let e1 = TraceEvent::exit_delivered(
836            2,
837            Time::ZERO,
838            10,
839            tid(1),
840            tid(21),
841            Time::from_nanos(10),
842            DownReason::Error("boom".to_string()),
843        );
844        let e2 = TraceEvent::exit_delivered(
845            3,
846            Time::ZERO,
847            9,
848            tid(1),
849            tid(22),
850            Time::from_nanos(10),
851            DownReason::Error("boom".to_string()),
852        );
853        let e3 = TraceEvent::exit_delivered(
854            4,
855            Time::ZERO,
856            1,
857            tid(3),
858            tid(23),
859            Time::from_nanos(10),
860            DownReason::Error("boom".to_string()),
861        );
862
863        // Canonical ordering must follow:
864        // (failure_vt, from_tid, link_ref).
865        let trace_a = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
866        let trace_b = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
867
868        let foata_a = canonicalize(&trace_a);
869        let foata_b = canonicalize(&trace_b);
870        assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
871        assert_eq!(foata_a.depth(), 1);
872
873        let flat = foata_a.flatten();
874        assert_eq!(flat.len(), 4);
875        assert_eq!(flat[0], e0);
876        assert_eq!(flat[1], e2);
877        assert_eq!(flat[2], e1);
878        assert_eq!(flat[3], e3);
879    }
880
881    // === Complex equivalence: three independent events in any of 6 orders ===
882
883    #[test]
884    fn three_independent_events_all_permutations_same_fingerprint() {
885        let e1 = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
886        let e2 = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
887        let e3 = TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3));
888
889        let permutations: Vec<Vec<TraceEvent>> = vec![
890            vec![e1.clone(), e2.clone(), e3.clone()],
891            vec![e1.clone(), e3.clone(), e2.clone()],
892            vec![e2.clone(), e1.clone(), e3.clone()],
893            vec![e2.clone(), e3.clone(), e1.clone()],
894            vec![e3.clone(), e1.clone(), e2.clone()],
895            vec![e3, e2, e1],
896        ];
897
898        let fp0 = trace_fingerprint(&permutations[0]);
899        for (i, perm) in permutations.iter().enumerate() {
900            let fp = trace_fingerprint(perm);
901            assert_eq!(fp, fp0, "Permutation {i} has different fingerprint");
902        }
903    }
904
905    // === Mixed independent and dependent events ===
906
907    #[test]
908    fn mixed_trace_canonical_form() {
909        // T1 in R1 and T2 in R2 are independent.
910        // Timer on same timer_id is independent of tasks.
911        // But time_advance conflicts with timer events.
912        let events = [
913            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
914            TraceEvent::time_advance(2, Time::ZERO, Time::ZERO, Time::from_nanos(100)),
915            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
916            TraceEvent::timer_fired(4, Time::ZERO, 1),
917        ];
918        let foata = canonicalize(&events);
919        // spawn(T1) and spawn(T2) are independent -> same layer
920        // time_advance is independent of spawns -> same layer as spawns
921        // timer_fired depends on time_advance -> layer 1
922        assert_eq!(foata.depth(), 2);
923        assert_eq!(foata.layers()[0].len(), 3); // spawn T1, time_advance, spawn T2
924        assert_eq!(foata.layers()[1].len(), 1); // timer_fired
925    }
926
927    // === Deterministic intra-layer ordering ===
928
929    #[test]
930    fn intra_layer_ordering_is_deterministic() {
931        let events = [
932            TraceEvent::spawn(1, Time::ZERO, tid(3), rid(3)),
933            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
934            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
935        ];
936        let foata = canonicalize(&events);
937        assert_eq!(foata.depth(), 1);
938
939        // Should be sorted by (kind=Spawn, task_id).
940        // tid(1) < tid(2) < tid(3) by ArenaIndex ordering.
941        let layer = &foata.layers()[0];
942        let keys: Vec<_> = layer.iter().map(event_sort_key).collect();
943        assert!(keys.windows(2).all(|w| w[0] <= w[1]));
944    }
945
946    // === Fingerprint consistency ===
947
948    #[test]
949    fn fingerprint_matches_foata_fingerprint() {
950        let events = [
951            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
952            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
953            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
954        ];
955        let foata = canonicalize(&events);
956        let direct = trace_fingerprint(&events);
957        assert_eq!(foata.fingerprint(), direct);
958    }
959
960    #[test]
961    fn empty_trace_fingerprint_matches_identity() {
962        let id = TraceMonoid::identity();
963        let empty = TraceMonoid::from_events(&[]);
964        assert_eq!(trace_fingerprint(&[]), id.class_fingerprint());
965        assert_eq!(id.class_fingerprint(), empty.class_fingerprint());
966        assert!(id.equivalent(&empty));
967    }
968
969    // === Layer depth = critical path ===
970
971    #[test]
972    fn depth_reflects_critical_path() {
973        // Two parallel chains of length 2:
974        //   spawn(T1) -> complete(T1)   (depth 2)
975        //   spawn(T2) -> complete(T2)   (depth 2)
976        let events = [
977            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
978            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
979            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
980            TraceEvent::complete(4, Time::ZERO, tid(2), rid(2)),
981        ];
982        let foata = canonicalize(&events);
983        assert_eq!(foata.depth(), 2);
984        assert_eq!(foata.layers()[0].len(), 2); // both spawns
985        assert_eq!(foata.layers()[1].len(), 2); // both completes
986    }
987
988    // === Flatten round-trip ===
989
990    #[test]
991    fn flatten_preserves_event_count() {
992        let events = [
993            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
994            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
995            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
996        ];
997        let foata = canonicalize(&events);
998        assert_eq!(foata.flatten().len(), events.len());
999    }
1000
1001    // === TraceMonoid: identity law ===
1002
1003    #[test]
1004    fn monoid_identity_left() {
1005        let events = [
1006            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1007            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1008        ];
1009        let t = TraceMonoid::from_events(&events);
1010        let result = TraceMonoid::identity().concat(&t);
1011        assert!(result.equivalent(&t));
1012        assert!(result.equivalent_exact(&t));
1013    }
1014
1015    #[test]
1016    fn monoid_identity_right() {
1017        let events = [
1018            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1019            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1020        ];
1021        let t = TraceMonoid::from_events(&events);
1022        let result = t.concat(&TraceMonoid::identity());
1023        assert!(result.equivalent(&t));
1024        assert!(result.equivalent_exact(&t));
1025    }
1026
1027    #[test]
1028    fn monoid_identity_is_empty() {
1029        let id = TraceMonoid::identity();
1030        assert!(id.is_identity());
1031        assert!(id.is_empty());
1032        assert_eq!(id.len(), 0);
1033        assert_eq!(id.critical_path_length(), 0);
1034        assert_eq!(id.max_parallelism(), 0);
1035    }
1036
1037    // === TraceMonoid: associativity ===
1038
1039    #[test]
1040    fn monoid_associativity() {
1041        let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
1042        let b_events = [TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2))];
1043        let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3))];
1044
1045        let a = TraceMonoid::from_events(&a_events);
1046        let b = TraceMonoid::from_events(&b_events);
1047        let c = TraceMonoid::from_events(&c_events);
1048
1049        let ab_c = a.concat(&b).concat(&c);
1050        let a_bc = a.concat(&b.concat(&c));
1051        assert!(ab_c.equivalent(&a_bc));
1052    }
1053
1054    #[test]
1055    fn monoid_associativity_with_dependencies() {
1056        // a and b are dependent (same task), c is independent
1057        let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
1058        let b_events = [TraceEvent::complete(2, Time::ZERO, tid(1), rid(1))];
1059        let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2))];
1060
1061        let a = TraceMonoid::from_events(&a_events);
1062        let b = TraceMonoid::from_events(&b_events);
1063        let c = TraceMonoid::from_events(&c_events);
1064
1065        let ab_c = a.concat(&b).concat(&c);
1066        let a_bc = a.concat(&b.concat(&c));
1067        assert!(ab_c.equivalent(&a_bc));
1068    }
1069
1070    // === TraceMonoid: equivalence ===
1071
1072    #[test]
1073    fn monoid_equivalent_traces() {
1074        // Two orderings of independent events → same monoid element
1075        let trace_a = [
1076            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1077            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1078        ];
1079        let trace_b = [
1080            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1081            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1082        ];
1083        let ma = TraceMonoid::from_events(&trace_a);
1084        let mb = TraceMonoid::from_events(&trace_b);
1085        assert_eq!(ma, mb);
1086        assert!(ma.equivalent_exact(&mb));
1087    }
1088
1089    #[test]
1090    fn partial_eq_requires_exact_canonical_match_not_just_fingerprint() {
1091        let trace_a = [
1092            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1093            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1094        ];
1095        let trace_b = [
1096            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1097            TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
1098        ];
1099
1100        let ma = TraceMonoid::from_events(&trace_a);
1101        let mb = TraceMonoid::from_events(&trace_b);
1102        assert!(!ma.equivalent_exact(&mb));
1103
1104        // Construct an impossible-but-valid-internal state to model hash collision:
1105        // same fingerprint, different canonical form.
1106        let TraceMonoid {
1107            canonical: FoataTrace { layers },
1108            ..
1109        } = mb;
1110        let spoof = TraceMonoid {
1111            canonical: FoataTrace { layers },
1112            fingerprint: ma.fingerprint,
1113        };
1114
1115        // Fingerprint-only equivalence says "equal", but PartialEq must remain exact.
1116        assert!(ma.equivalent(&spoof));
1117        assert_ne!(ma, spoof);
1118    }
1119
1120    #[test]
1121    fn exact_equivalence_distinguishes_region_cancel_reason_payload() {
1122        let trace_a = [TraceEvent::region_cancelled(
1123            1,
1124            Time::ZERO,
1125            rid(1),
1126            CancelReason::shutdown(),
1127        )];
1128        let trace_b = [TraceEvent::region_cancelled(
1129            1,
1130            Time::ZERO,
1131            rid(1),
1132            CancelReason::timeout(),
1133        )];
1134
1135        let ma = TraceMonoid::from_events(&trace_a);
1136        let mb = TraceMonoid::from_events(&trace_b);
1137
1138        assert!(ma.equivalent(&mb));
1139        assert!(!ma.equivalent_exact(&mb));
1140        assert_ne!(ma, mb);
1141    }
1142
1143    #[test]
1144    fn monoid_nonequivalent_traces() {
1145        // Different causal structures → different monoid elements
1146        let trace_a = [
1147            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1148            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1149        ];
1150        let trace_b = [
1151            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1152            TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
1153        ];
1154        let ma = TraceMonoid::from_events(&trace_a);
1155        let mb = TraceMonoid::from_events(&trace_b);
1156        assert_ne!(ma, mb);
1157    }
1158
1159    // === TraceMonoid: parallelism metrics ===
1160
1161    #[test]
1162    fn monoid_parallelism_metrics() {
1163        let events = [
1164            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1165            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1166            TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
1167            TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
1168            TraceEvent::complete(5, Time::ZERO, tid(2), rid(2)),
1169        ];
1170        let m = TraceMonoid::from_events(&events);
1171        assert_eq!(m.len(), 5);
1172        assert_eq!(m.critical_path_length(), 2); // spawns then completes
1173        assert_eq!(m.max_parallelism(), 3); // 3 independent spawns
1174    }
1175
1176    #[test]
1177    fn monoid_from_events_mapping() {
1178        // Verify the mapping φ: Σ* → M(Σ, I) preserves trace length
1179        let events = [
1180            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1181            TraceEvent::region_created(2, Time::ZERO, rid(2), None),
1182            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1183        ];
1184        let m = TraceMonoid::from_events(&events);
1185        assert_eq!(m.len(), events.len());
1186        assert_eq!(m.class_fingerprint(), trace_fingerprint(&events));
1187    }
1188
1189    // === Obligation events ===
1190
1191    #[test]
1192    fn independent_obligations_same_layer() {
1193        let events = [
1194            TraceEvent::obligation_reserve(
1195                1,
1196                Time::ZERO,
1197                oid(1),
1198                tid(1),
1199                rid(1),
1200                ObligationKind::SendPermit,
1201            ),
1202            TraceEvent::obligation_reserve(
1203                2,
1204                Time::ZERO,
1205                oid(2),
1206                tid(2),
1207                rid(2),
1208                ObligationKind::Ack,
1209            ),
1210        ];
1211        let foata = canonicalize(&events);
1212        assert_eq!(foata.depth(), 1);
1213    }
1214
1215    #[test]
1216    fn same_obligation_events_form_chain() {
1217        let events = [
1218            TraceEvent::obligation_reserve(
1219                1,
1220                Time::ZERO,
1221                oid(1),
1222                tid(1),
1223                rid(1),
1224                ObligationKind::Lease,
1225            ),
1226            TraceEvent::obligation_commit(
1227                2,
1228                Time::ZERO,
1229                oid(1),
1230                tid(1),
1231                rid(1),
1232                ObligationKind::Lease,
1233                5000,
1234            ),
1235        ];
1236        let foata = canonicalize(&events);
1237        assert_eq!(foata.depth(), 2);
1238    }
1239
1240    // --- wave 77 trait coverage ---
1241
1242    #[test]
1243    fn trace_event_key_debug_clone_copy_eq_hash() {
1244        use std::collections::HashSet;
1245        let k = TraceEventKey::new(1, 2, 3, 4);
1246        let k2 = k; // Copy
1247        let k3 = k;
1248        assert_eq!(k, k2);
1249        assert_eq!(k, k3);
1250        assert_ne!(k, TraceEventKey::new(1, 2, 3, 5));
1251        let dbg = format!("{k:?}");
1252        assert!(dbg.contains("TraceEventKey"));
1253        let mut set = HashSet::new();
1254        set.insert(k);
1255        assert!(set.contains(&k2));
1256    }
1257}