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::hash::{Hash, Hasher};
64
65/// A trace in Foata normal form: layers of mutually independent events.
66#[derive(Debug)]
67pub struct FoataTrace {
68    /// Layers of mutually independent events, sorted deterministically.
69    layers: Vec<Vec<TraceEvent>>,
70}
71
72/// A stable, comparable key for a trace event.
73///
74/// This key mirrors the canonical intra-layer ordering used by Foata traces,
75/// making it suitable for golden fixture prefixes and diffing.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77pub struct TraceEventKey {
78    /// Discriminant for the event kind.
79    pub kind: u8,
80    /// Primary sort key.
81    pub primary: u64,
82    /// Secondary sort key.
83    pub secondary: u64,
84    /// Tertiary sort key.
85    pub tertiary: u64,
86}
87
88impl TraceEventKey {
89    /// Create a new [`TraceEventKey`].
90    ///
91    /// This is a small convenience constructor used by canonical ordering and
92    /// fixture generation.
93    #[must_use]
94    pub const fn new(kind: u8, primary: u64, secondary: u64, tertiary: u64) -> Self {
95        Self {
96            kind,
97            primary,
98            secondary,
99            tertiary,
100        }
101    }
102}
103
104impl FoataTrace {
105    /// Returns the number of layers (the critical path length).
106    #[must_use]
107    pub fn depth(&self) -> usize {
108        self.layers.len()
109    }
110
111    /// Returns the total number of events across all layers.
112    #[must_use]
113    pub fn len(&self) -> usize {
114        self.layers.iter().map(Vec::len).sum()
115    }
116
117    /// Returns `true` if the trace contains no events.
118    #[must_use]
119    pub fn is_empty(&self) -> bool {
120        self.layers.is_empty()
121    }
122
123    /// Returns a slice of all layers.
124    #[must_use]
125    pub fn layers(&self) -> &[Vec<TraceEvent>] {
126        &self.layers
127    }
128
129    /// Flatten back into a linear sequence (canonical total order).
130    #[must_use]
131    pub fn flatten(&self) -> Vec<TraceEvent> {
132        self.layers.iter().flat_map(|l| l.iter().cloned()).collect()
133    }
134
135    /// Compute a 64-bit fingerprint of this canonical form.
136    ///
137    /// Two `FoataTrace` values representing the same equivalence class will
138    /// produce identical fingerprints.
139    #[must_use]
140    pub fn fingerprint(&self) -> u64 {
141        let mut hasher = DetHasher::for_lab();
142        for (layer_idx, layer) in self.layers.iter().enumerate() {
143            layer_idx.hash(&mut hasher);
144            layer.len().hash(&mut hasher);
145            for event in layer {
146                event_hash_key(event).hash(&mut hasher);
147            }
148        }
149        hasher.finish()
150    }
151}
152
153/// An element of the trace monoid `M(Σ, I)`.
154///
155/// A `TraceMonoid` value represents an equivalence class of event sequences
156/// under the Mazurkiewicz congruence. It stores the canonical (Foata normal
157/// form) representative and a precomputed fingerprint for O(1) equality checks.
158///
159/// # Monoid Laws
160///
161/// - **Identity**: `TraceMonoid::identity().concat(&t) == t` and
162///   `t.concat(&TraceMonoid::identity()) == t`
163/// - **Associativity**: `a.concat(&b).concat(&c) == a.concat(&b.concat(&c))`
164/// - **Equivalence**: `TraceMonoid::from_events(w1) == TraceMonoid::from_events(w2)`
165///   iff `w1 ≡_I w2`
166#[derive(Debug)]
167pub struct TraceMonoid {
168    /// The canonical representative in Foata normal form.
169    canonical: FoataTrace,
170    /// Precomputed fingerprint for O(1) equality.
171    fingerprint: u64,
172}
173
174impl TraceMonoid {
175    /// The identity element of the trace monoid (empty trace).
176    #[must_use]
177    pub fn identity() -> Self {
178        let canonical = FoataTrace { layers: vec![] };
179        let fingerprint = canonical.fingerprint();
180        Self {
181            canonical,
182            fingerprint,
183        }
184    }
185
186    /// Construct a monoid element from a sequence of trace events.
187    ///
188    /// This is the canonical mapping `φ: Σ* → M(Σ, I)` that sends a word
189    /// (linear trace) to its equivalence class. The returned value contains
190    /// the Foata normal form as the canonical representative.
191    #[must_use]
192    pub fn from_events(events: &[TraceEvent]) -> Self {
193        let canonical = canonicalize(events);
194        let fingerprint = canonical.fingerprint();
195        Self {
196            canonical,
197            fingerprint,
198        }
199    }
200
201    /// Monoid multiplication: concatenation of traces.
202    ///
203    /// Computes `self · other` by concatenating the flattened canonical forms
204    /// and re-canonicalizing. The result is the unique canonical representative
205    /// of the product equivalence class.
206    ///
207    /// # Complexity
208    ///
209    /// O((n+m)²) where n = `self.len()` and m = `other.len()`.
210    #[must_use]
211    pub fn concat(&self, other: &Self) -> Self {
212        if self.is_identity() {
213            return Self {
214                canonical: FoataTrace {
215                    layers: other.canonical.layers.clone(),
216                },
217                fingerprint: other.fingerprint,
218            };
219        }
220        if other.is_identity() {
221            return Self {
222                canonical: FoataTrace {
223                    layers: self.canonical.layers.clone(),
224                },
225                fingerprint: self.fingerprint,
226            };
227        }
228
229        let mut combined = self.canonical.flatten();
230        combined.extend(other.canonical.flatten());
231        Self::from_events(&combined)
232    }
233
234    /// Check whether this is the identity element (empty trace).
235    #[must_use]
236    pub fn is_identity(&self) -> bool {
237        self.canonical.is_empty()
238    }
239
240    /// Returns the Foata normal form (canonical representative).
241    #[must_use]
242    pub fn canonical_form(&self) -> &FoataTrace {
243        &self.canonical
244    }
245
246    /// Returns the precomputed fingerprint for this equivalence class.
247    #[must_use]
248    pub fn class_fingerprint(&self) -> u64 {
249        self.fingerprint
250    }
251
252    /// Number of events in the canonical representative.
253    #[must_use]
254    pub fn len(&self) -> usize {
255        self.canonical.len()
256    }
257
258    /// Returns `true` if the trace is empty (identity element).
259    #[must_use]
260    pub fn is_empty(&self) -> bool {
261        self.canonical.is_empty()
262    }
263
264    /// Critical path length (number of Foata layers).
265    ///
266    /// This is the minimum number of sequential steps needed to execute the
267    /// trace, assuming maximal parallelism of independent events.
268    #[must_use]
269    pub fn critical_path_length(&self) -> usize {
270        self.canonical.depth()
271    }
272
273    /// Maximum parallelism: the width of the widest Foata layer.
274    ///
275    /// Represents the maximum number of independent events that can execute
276    /// concurrently in a single step.
277    #[must_use]
278    pub fn max_parallelism(&self) -> usize {
279        self.canonical
280            .layers
281            .iter()
282            .map(Vec::len)
283            .max()
284            .unwrap_or(0)
285    }
286
287    /// Check trace equivalence: are two monoid elements in the same class?
288    ///
289    /// This is an O(1) comparison using precomputed fingerprints. Note that
290    /// fingerprint collision is theoretically possible (probability ~2⁻⁶⁴),
291    /// so for formal verification use [`Self::equivalent_exact`].
292    #[must_use]
293    pub fn equivalent(&self, other: &Self) -> bool {
294        self.fingerprint == other.fingerprint
295    }
296
297    /// Exact trace equivalence check via structural comparison of Foata layers.
298    ///
299    /// This is more expensive than [`Self::equivalent`] but has no false positives.
300    /// Compares semantic event payloads layer-by-layer while still ignoring
301    /// non-semantic envelope fields such as sequence numbers and timestamps.
302    #[must_use]
303    pub fn equivalent_exact(&self, other: &Self) -> bool {
304        if self.canonical.depth() != other.canonical.depth() {
305            return false;
306        }
307        for (la, lb) in self
308            .canonical
309            .layers
310            .iter()
311            .zip(other.canonical.layers.iter())
312        {
313            if la.len() != lb.len() {
314                return false;
315            }
316            for (ea, eb) in la.iter().zip(lb.iter()) {
317                if !semantically_equal_event(ea, eb) {
318                    return false;
319                }
320            }
321        }
322        true
323    }
324}
325
326impl PartialEq for TraceMonoid {
327    fn eq(&self, other: &Self) -> bool {
328        // Fast path: fingerprints differ => definitely not equivalent.
329        if self.fingerprint != other.fingerprint {
330            return false;
331        }
332        // Guard against theoretical 64-bit hash collisions.
333        self.equivalent_exact(other)
334    }
335}
336
337impl Eq for TraceMonoid {}
338
339/// Compute the Foata normal form of a trace.
340///
341/// Events are grouped into layers based on the happens-before order induced
342/// by the independence relation. Within each layer, events are sorted by a
343/// deterministic comparison key.
344///
345/// # Example
346///
347/// ```ignore
348/// let trace = vec![spawn(1, t1, r1), spawn(2, t2, r2), complete(3, t1, r1)];
349/// let foata = canonicalize(&trace);
350/// // Layer 0: [spawn(t1, r1), spawn(t2, r2)]  — independent, sorted by key
351/// // Layer 1: [complete(t1, r1)]               — depends on spawn(t1)
352/// assert_eq!(foata.depth(), 2);
353/// ```
354#[must_use]
355pub fn canonicalize(events: &[TraceEvent]) -> FoataTrace {
356    let n = events.len();
357    if n == 0 {
358        return FoataTrace { layers: vec![] };
359    }
360
361    // Step 1: Compute layer assignment for each event.
362    // layer[j] = 1 + max(layer[i]) for all i < j where events[i] and events[j]
363    // are dependent.
364    let mut layer_of = vec![0usize; n];
365    let mut max_layer = 0usize;
366
367    for j in 1..n {
368        for i in 0..j {
369            if !independent(&events[i], &events[j]) {
370                layer_of[j] = layer_of[j].max(layer_of[i] + 1);
371            }
372        }
373        max_layer = max_layer.max(layer_of[j]);
374    }
375
376    // Step 2: Group events by layer.
377    let mut layers: Vec<Vec<TraceEvent>> = vec![vec![]; max_layer + 1];
378    for (idx, event) in events.iter().enumerate() {
379        layers[layer_of[idx]].push(event.clone());
380    }
381
382    // Step 3: Sort within each layer deterministically.
383    for layer in &mut layers {
384        layer.sort_by_cached_key(event_total_order_key);
385    }
386
387    FoataTrace { layers }
388}
389
390/// Compute a 64-bit fingerprint for a trace's equivalence class.
391///
392/// Semantically equivalent to `canonicalize(events).fingerprint()` but avoids
393/// cloning events (only hashes in place).
394#[must_use]
395pub fn trace_fingerprint(events: &[TraceEvent]) -> u64 {
396    let n = events.len();
397    if n == 0 {
398        // Must match FoataTrace { layers: vec![] }.fingerprint()
399        return FoataTrace { layers: vec![] }.fingerprint();
400    }
401
402    // Layer assignment (same algorithm as canonicalize).
403    let mut layer_of = vec![0usize; n];
404    let mut max_layer = 0usize;
405
406    for j in 1..n {
407        for i in 0..j {
408            if !independent(&events[i], &events[j]) {
409                layer_of[j] = layer_of[j].max(layer_of[i] + 1);
410            }
411        }
412        max_layer = max_layer.max(layer_of[j]);
413    }
414
415    // Group indices by layer, sort within layer, hash.
416    let mut layer_indices: Vec<Vec<usize>> = vec![vec![]; max_layer + 1];
417    for (idx, &layer) in layer_of.iter().enumerate() {
418        layer_indices[layer].push(idx);
419    }
420
421    let mut hasher = DetHasher::for_lab();
422    for (layer_idx, indices) in layer_indices.iter_mut().enumerate() {
423        indices.sort_by_cached_key(|&idx| event_total_order_key(&events[idx]));
424        layer_idx.hash(&mut hasher);
425        indices.len().hash(&mut hasher);
426        for &idx in indices.iter() {
427            event_hash_key(&events[idx]).hash(&mut hasher);
428        }
429    }
430    hasher.finish()
431}
432
433// === Internal: deterministic event ordering ===
434
435/// Stable discriminant for `TraceEventKind`.
436///
437/// These values are fixed for fingerprint stability across versions.
438fn kind_discriminant(kind: TraceEventKind) -> u8 {
439    match kind {
440        TraceEventKind::Spawn => 0,
441        TraceEventKind::Schedule => 1,
442        TraceEventKind::Yield => 2,
443        TraceEventKind::Wake => 3,
444        TraceEventKind::Poll => 4,
445        TraceEventKind::Complete => 5,
446        TraceEventKind::CancelRequest => 6,
447        TraceEventKind::CancelAck => 7,
448        TraceEventKind::RegionCloseBegin => 8,
449        TraceEventKind::RegionCloseComplete => 9,
450        TraceEventKind::RegionCreated => 10,
451        TraceEventKind::RegionCancelled => 11,
452        TraceEventKind::ObligationReserve => 12,
453        TraceEventKind::ObligationCommit => 13,
454        TraceEventKind::ObligationAbort => 14,
455        TraceEventKind::ObligationLeak => 15,
456        TraceEventKind::TimeAdvance => 16,
457        TraceEventKind::TimerScheduled => 17,
458        TraceEventKind::TimerFired => 18,
459        TraceEventKind::TimerCancelled => 19,
460        TraceEventKind::IoRequested => 20,
461        TraceEventKind::IoReady => 21,
462        TraceEventKind::IoResult => 22,
463        TraceEventKind::IoError => 23,
464        TraceEventKind::RngSeed => 24,
465        TraceEventKind::RngValue => 25,
466        TraceEventKind::Checkpoint => 26,
467        TraceEventKind::FuturelockDetected => 27,
468        TraceEventKind::ChaosInjection => 28,
469        TraceEventKind::UserTrace => 29,
470        TraceEventKind::MonitorCreated => 30,
471        TraceEventKind::MonitorDropped => 31,
472        TraceEventKind::DownDelivered => 32,
473        TraceEventKind::LinkCreated => 33,
474        TraceEventKind::LinkDropped => 34,
475        TraceEventKind::ExitDelivered => 35,
476        // Appended to preserve existing fingerprint assignments for prior kinds.
477        TraceEventKind::WorkerCancelRequested => 36,
478        TraceEventKind::WorkerCancelAcknowledged => 37,
479        TraceEventKind::WorkerDrainStarted => 38,
480        TraceEventKind::WorkerDrainCompleted => 39,
481        TraceEventKind::WorkerFinalizeCompleted => 40,
482        TraceEventKind::TaskSpawnEnqueued => 41,
483        TraceEventKind::TaskAdmitted => 42,
484        // Append-only: never renumber existing discriminants (fingerprint
485        // stability). Next free after TaskAdmitted=42.
486        TraceEventKind::BudgetInstalled => 43,
487        TraceEventKind::BudgetConsumed => 44,
488    }
489}
490
491/// Pack an ArenaIndex into a u64 for deterministic ordering.
492fn pack_arena(idx: crate::util::ArenaIndex) -> u64 {
493    (u64::from(idx.index()) << 32) | u64::from(idx.generation())
494}
495
496/// Compute a sort key for deterministic intra-layer ordering.
497///
498/// The key is a 4-tuple of (kind, primary, secondary, tertiary) where each
499/// component is a fixed-width integer. This ensures total, deterministic
500/// ordering within a Foata layer.
501fn event_sort_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
502    let k = kind_discriminant(event.kind);
503    match &event.data {
504        TraceData::Task { task, region }
505        | TraceData::Cancel { task, region, .. }
506        | TraceData::Budget { task, region, .. } => {
507            (k, pack_arena(task.0), pack_arena(region.0), 0)
508        }
509        TraceData::Futurelock {
510            task,
511            region,
512            idle_steps,
513            held,
514        } => {
515            // Two futurelock observations on the same task+region have a
516            // read-only footprint, so they are INDEPENDENT and share a Foata
517            // layer. The intra-layer sort is stable, so a key of only
518            // (kind, task, region) would let the canonical form preserve input
519            // order — i.e. it would not be a total, input-order-independent
520            // ordering. That breaks the documented monoid-equivalence law
521            // (`from_events(w1) == from_events(w2)` iff `w1 ≡_I w2`) and makes
522            // `equivalent` (fingerprint) disagree with `equivalent_exact`.
523            // Fold the distinguishing fields into the key so distinct
524            // observations get distinct, deterministic keys.
525            let mut hasher = DetHasher::for_lab();
526            idle_steps.hash(&mut hasher);
527            held.hash(&mut hasher);
528            (k, pack_arena(task.0), pack_arena(region.0), hasher.finish())
529        }
530        TraceData::Region { region, parent } => (
531            k,
532            pack_arena(region.0),
533            parent.map_or(0, |p| pack_arena(p.0)),
534            0,
535        ),
536        TraceData::RegionCancel { region, .. } => (k, pack_arena(region.0), 0, 0),
537        TraceData::Obligation {
538            obligation,
539            task,
540            region,
541            ..
542        } => (
543            k,
544            pack_arena(obligation.0),
545            pack_arena(task.0),
546            pack_arena(region.0),
547        ),
548        TraceData::Time { old, new } => (k, old.as_nanos(), new.as_nanos(), 0),
549        TraceData::Timer { timer_id, .. } => (k, *timer_id, 0, 0),
550        TraceData::IoRequested { token, .. } | TraceData::IoReady { token, .. } => {
551            (k, *token, 0, 0)
552        }
553        TraceData::IoResult { token, bytes } => {
554            // Preserve total ordering of i64 in u64 space by flipping the sign bit.
555            let bytes_key = (*bytes).cast_unsigned() ^ (1u64 << 63);
556            (k, *token, bytes_key, 0)
557        }
558        TraceData::IoError { token, kind } => (k, *token, u64::from(*kind), 0),
559        TraceData::RngSeed { seed } => (k, *seed, 0, 0),
560        TraceData::RngValue { value } => (k, *value, 0, 0),
561        TraceData::Checkpoint {
562            sequence,
563            active_tasks,
564            active_regions,
565        } => (
566            k,
567            *sequence,
568            u64::from(*active_tasks),
569            u64::from(*active_regions),
570        ),
571        TraceData::Chaos { task, .. } => {
572            let task_key = task.map_or(0, |t| pack_arena(t.0));
573            (k, task_key, 0, 0)
574        }
575        TraceData::Message(msg) => {
576            let mut h = DetHasher::for_lab();
577            msg.hash(&mut h);
578            (k, h.finish(), 0, 0)
579        }
580        TraceData::Monitor {
581            monitor_ref,
582            watcher,
583            monitored,
584            ..
585        } => (
586            k,
587            *monitor_ref,
588            pack_arena(watcher.0),
589            pack_arena(monitored.0),
590        ),
591        TraceData::Down {
592            monitor_ref,
593            monitored,
594            completion_vt,
595            ..
596        } => (
597            k,
598            completion_vt.as_nanos(),
599            pack_arena(monitored.0),
600            *monitor_ref,
601        ),
602        TraceData::Link {
603            link_ref,
604            task_a,
605            task_b,
606            ..
607        } => (k, *link_ref, pack_arena(task_a.0), pack_arena(task_b.0)),
608        TraceData::Exit {
609            link_ref,
610            from,
611            failure_vt,
612            ..
613        } => (k, failure_vt.as_nanos(), pack_arena(from.0), *link_ref),
614        TraceData::Worker {
615            job_id,
616            task,
617            region,
618            ..
619        } => (k, *job_id, pack_arena(task.0), pack_arena(region.0)),
620        TraceData::None => (k, 0, 0, 0),
621    }
622}
623
624/// Returns the stable key used for deterministic ordering of a trace event.
625#[must_use]
626pub fn trace_event_key(event: &TraceEvent) -> TraceEventKey {
627    let (kind, primary, secondary, tertiary) = event_sort_key(event);
628    TraceEventKey::new(kind, primary, secondary, tertiary)
629}
630
631/// Deterministic total ordering key for trace events.
632fn event_total_order_key(event: &TraceEvent) -> ((u8, u64, u64, u64), Vec<u8>) {
633    (event_sort_key(event), event_total_order_tiebreak(event))
634}
635
636/// Full-value tie-breaker used only when the stable canonical key collides.
637///
638/// The primary key intentionally omits selected payload fields so historical
639/// trace fingerprints remain stable across non-semantic envelope differences.
640/// Rust's slice sorting still requires a comparator that is a total order, so
641/// equal canonical keys are ordered by the complete serialized event.
642fn event_total_order_tiebreak(event: &TraceEvent) -> Vec<u8> {
643    serde_json::to_vec(event).unwrap_or_else(|_| format!("{event:?}").into_bytes())
644}
645
646/// Equality on the semantic content of a trace event.
647fn semantically_equal_event(a: &TraceEvent, b: &TraceEvent) -> bool {
648    a.version == b.version && a.kind == b.kind && a.data == b.data
649}
650
651/// Hash key for fingerprinting.
652///
653/// This deliberately excludes [`event_total_order_tiebreak`] so existing trace
654/// fingerprints keep treating equal canonical keys as the same event class.
655fn event_hash_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
656    event_sort_key(event)
657}
658
659#[cfg(test)]
660mod tests {
661    #![allow(
662        clippy::pedantic,
663        clippy::nursery,
664        clippy::expect_fun_call,
665        clippy::map_unwrap_or,
666        clippy::cast_possible_wrap,
667        clippy::future_not_send
668    )]
669    use super::*;
670    use crate::monitor::DownReason;
671    use crate::record::{ObligationAbortReason, ObligationKind};
672    use crate::types::{CancelReason, ObligationId, RegionId, TaskId, Time};
673    use insta::assert_json_snapshot;
674    use serde::Serialize;
675
676    #[derive(Debug, Serialize)]
677    struct CanonicalTraceSnapshot {
678        depth: usize,
679        len: usize,
680        layers: Vec<Vec<CanonicalTraceEventSnapshot>>,
681    }
682
683    #[derive(Debug, Serialize)]
684    struct CanonicalTraceEventSnapshot {
685        seq: u64,
686        time_ns: u64,
687        kind: &'static str,
688        key: TraceEventKey,
689        data: String,
690    }
691
692    fn tid(n: u32) -> TaskId {
693        TaskId::new_for_test(n, 0)
694    }
695
696    fn rid(n: u32) -> RegionId {
697        RegionId::new_for_test(n, 0)
698    }
699
700    fn oid(n: u32) -> ObligationId {
701        ObligationId::new_for_test(n, 0)
702    }
703
704    fn canonical_trace_snapshot(events: &[TraceEvent]) -> CanonicalTraceSnapshot {
705        let foata = canonicalize(events);
706        CanonicalTraceSnapshot {
707            depth: foata.depth(),
708            len: foata.len(),
709            layers: foata
710                .layers()
711                .iter()
712                .map(|layer| layer.iter().map(snapshot_event).collect())
713                .collect(),
714        }
715    }
716
717    fn snapshot_event(event: &TraceEvent) -> CanonicalTraceEventSnapshot {
718        CanonicalTraceEventSnapshot {
719            seq: event.seq,
720            time_ns: event.time.as_nanos(),
721            kind: event.kind.stable_name(),
722            key: trace_event_key(event),
723            data: snapshot_event_data(&event.data),
724        }
725    }
726
727    fn snapshot_event_data(data: &TraceData) -> String {
728        match data {
729            TraceData::None => "none".to_string(),
730            TraceData::Task { task, region } => {
731                format!("task={} region={}", task.as_u64(), region.as_u64())
732            }
733            TraceData::Region { region, parent } => format!(
734                "region={} parent={}",
735                region.as_u64(),
736                parent.map_or_else(|| "none".to_string(), |region| region.as_u64().to_string())
737            ),
738            TraceData::Obligation {
739                obligation,
740                task,
741                region,
742                kind,
743                state,
744                duration_ns,
745                abort_reason,
746            } => format!(
747                "obligation={} task={} region={} kind={} state={state:?} duration_ns={} abort_reason={}",
748                pack_arena(obligation.arena_index()),
749                task.as_u64(),
750                region.as_u64(),
751                kind.as_str(),
752                duration_ns.map_or_else(|| "none".to_string(), |value| value.to_string()),
753                abort_reason
754                    .map_or_else(|| "none".to_string(), |reason| reason.as_str().to_string())
755            ),
756            TraceData::Cancel {
757                task,
758                region,
759                reason,
760            } => format!(
761                "task={} region={} reason={reason}",
762                task.as_u64(),
763                region.as_u64()
764            ),
765            TraceData::RegionCancel { region, reason } => {
766                format!("region={} reason={reason}", region.as_u64())
767            }
768            TraceData::Time { old, new } => {
769                format!("old={} new={}", old.as_nanos(), new.as_nanos())
770            }
771            TraceData::Timer { timer_id, deadline } => format!(
772                "timer_id={timer_id} deadline={}",
773                deadline.map_or_else(|| "none".to_string(), |time| time.as_nanos().to_string())
774            ),
775            TraceData::Checkpoint {
776                sequence,
777                active_tasks,
778                active_regions,
779            } => format!(
780                "sequence={sequence} active_tasks={active_tasks} active_regions={active_regions}"
781            ),
782            other => format!("{other:?}"),
783        }
784    }
785
786    #[test]
787    fn trace_canonicalize_happy_path_snapshot() {
788        let events = [
789            TraceEvent::region_created(1, Time::ZERO, rid(1), None),
790            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
791            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
792            TraceEvent::poll(4, Time::from_nanos(5), tid(1), rid(1)),
793            TraceEvent::complete(5, Time::from_nanos(9), tid(1), rid(1)),
794            TraceEvent::complete(6, Time::from_nanos(11), tid(2), rid(1)),
795        ];
796
797        assert_json_snapshot!(
798            "trace_canonicalize_happy_path",
799            canonical_trace_snapshot(&events)
800        );
801    }
802
803    #[test]
804    fn trace_canonicalize_empty_snapshot() {
805        assert_json_snapshot!("trace_canonicalize_empty", canonical_trace_snapshot(&[]));
806    }
807
808    #[test]
809    fn trace_canonicalize_max_budget_snapshot() {
810        let events = [
811            TraceEvent::region_created(1, Time::ZERO, rid(9), None),
812            TraceEvent::spawn(2, Time::from_nanos(1), tid(9), rid(9)),
813            TraceEvent::obligation_reserve(
814                3,
815                Time::from_nanos(2),
816                oid(7),
817                tid(9),
818                rid(9),
819                ObligationKind::Lease,
820            ),
821            TraceEvent::time_advance(4, Time::from_nanos(3), Time::ZERO, Time::MAX),
822            TraceEvent::timer_scheduled(5, Time::MAX, u64::MAX, Time::MAX),
823            TraceEvent::obligation_commit(
824                6,
825                Time::MAX,
826                oid(7),
827                tid(9),
828                rid(9),
829                ObligationKind::Lease,
830                u64::MAX,
831            ),
832            TraceEvent::checkpoint(7, Time::MAX, u64::MAX, u32::MAX, u32::MAX),
833        ];
834
835        assert_json_snapshot!(
836            "trace_canonicalize_max_budget",
837            canonical_trace_snapshot(&events)
838        );
839    }
840
841    #[test]
842    fn trace_canonicalize_cancellation_chain_snapshot() {
843        let cancel = CancelReason::timeout();
844        let events = [
845            TraceEvent::region_created(1, Time::ZERO, rid(3), None),
846            TraceEvent::spawn(2, Time::from_nanos(1), tid(4), rid(3)),
847            TraceEvent::obligation_reserve(
848                3,
849                Time::from_nanos(2),
850                oid(4),
851                tid(4),
852                rid(3),
853                ObligationKind::SendPermit,
854            ),
855            TraceEvent::cancel_request(4, Time::from_nanos(3), tid(4), rid(3), cancel.clone()),
856            TraceEvent::new(
857                5,
858                Time::from_nanos(4),
859                TraceEventKind::CancelAck,
860                TraceData::Cancel {
861                    task: tid(4),
862                    region: rid(3),
863                    reason: cancel.clone(),
864                },
865            ),
866            TraceEvent::obligation_abort(
867                6,
868                Time::from_nanos(5),
869                oid(4),
870                tid(4),
871                rid(3),
872                ObligationKind::SendPermit,
873                17,
874                ObligationAbortReason::Cancel,
875            ),
876            TraceEvent::region_cancelled(7, Time::from_nanos(6), rid(3), cancel),
877        ];
878
879        assert_json_snapshot!(
880            "trace_canonicalize_cancellation_chain",
881            canonical_trace_snapshot(&events)
882        );
883    }
884
885    // === Basic structure ===
886
887    #[test]
888    fn empty_trace() {
889        let foata = canonicalize(&[]);
890        assert!(foata.is_empty());
891        assert_eq!(foata.depth(), 0);
892        assert_eq!(foata.len(), 0);
893    }
894
895    #[test]
896    fn single_event() {
897        let events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
898        let foata = canonicalize(&events);
899        assert_eq!(foata.depth(), 1);
900        assert_eq!(foata.len(), 1);
901    }
902
903    #[test]
904    fn all_independent_events_in_one_layer() {
905        // Spawns in different regions with different tasks are independent.
906        let events = [
907            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
908            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
909            TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
910        ];
911        let foata = canonicalize(&events);
912        assert_eq!(foata.depth(), 1);
913        assert_eq!(foata.layers()[0].len(), 3);
914    }
915
916    #[test]
917    fn chain_of_dependent_events() {
918        // Same task: spawn -> poll -> complete forms a chain.
919        let events = [
920            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
921            TraceEvent::poll(2, Time::ZERO, tid(1), rid(1)),
922            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
923        ];
924        let foata = canonicalize(&events);
925        assert_eq!(foata.depth(), 3);
926        assert_eq!(foata.layers()[0].len(), 1);
927        assert_eq!(foata.layers()[1].len(), 1);
928        assert_eq!(foata.layers()[2].len(), 1);
929    }
930
931    #[test]
932    fn diamond_dependency() {
933        // T1 and T2 are independent, but both depend on region creation
934        // and both must complete before region close.
935        //
936        //   region_create(R1)
937        //     /          \
938        //  spawn(T1,R1) spawn(T2,R1)    -- independent (both read R1)
939        //     \          /
940        //  complete(T1) complete(T2)     -- independent (different tasks)
941        //
942        // But region_create writes R1 and spawns read R1 -> dependent.
943        // So: layer 0 = [region_create], layer 1 = [spawn T1, spawn T2],
944        //     layer 2 = [complete T1, complete T2]
945        let events = [
946            TraceEvent::region_created(1, Time::ZERO, rid(1), None),
947            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
948            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
949            TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
950            TraceEvent::complete(5, Time::ZERO, tid(2), rid(1)),
951        ];
952        let foata = canonicalize(&events);
953        assert_eq!(foata.depth(), 3);
954        assert_eq!(foata.layers()[0].len(), 1); // region_create
955        assert_eq!(foata.layers()[1].len(), 2); // spawn T1, spawn T2
956        assert_eq!(foata.layers()[2].len(), 2); // complete T1, complete T2
957    }
958
959    // === Equivalence: swapping independent events produces same canonical form ===
960
961    #[test]
962    fn swapped_independent_events_same_fingerprint() {
963        let trace_a = [
964            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
965            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
966        ];
967        let trace_b = [
968            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
969            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
970        ];
971        let fp_a = trace_fingerprint(&trace_a);
972        let fp_b = trace_fingerprint(&trace_b);
973        assert_eq!(fp_a, fp_b);
974    }
975
976    #[test]
977    fn swapped_independent_events_same_canonical_form() {
978        let trace_a = [
979            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
980            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
981        ];
982        let trace_b = [
983            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
984            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
985        ];
986        let foata_a = canonicalize(&trace_a);
987        let foata_b = canonicalize(&trace_b);
988        assert_eq!(foata_a.depth(), foata_b.depth());
989        assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
990    }
991
992    #[test]
993    fn different_dependent_orders_different_fingerprints() {
994        // Same-task events in different orders are different traces (not equivalent).
995        let trace_a = [
996            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
997            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
998        ];
999        let trace_b = [
1000            TraceEvent::complete(1, Time::ZERO, tid(1), rid(1)),
1001            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1002        ];
1003        let fp_a = trace_fingerprint(&trace_a);
1004        let fp_b = trace_fingerprint(&trace_b);
1005        // These are genuinely different traces (different causal structure).
1006        assert_ne!(fp_a, fp_b);
1007    }
1008
1009    // === Deterministic ordering keys (bd-wwnkh) ===
1010
1011    #[test]
1012    fn down_delivered_canonicalizes_by_completion_vt_monitored_monitor_ref() {
1013        let e0 = TraceEvent::down_delivered(
1014            1,
1015            Time::ZERO,
1016            1,
1017            tid(10),
1018            tid(3),
1019            Time::from_nanos(4),
1020            DownReason::Normal,
1021        );
1022        let e1 = TraceEvent::down_delivered(
1023            2,
1024            Time::ZERO,
1025            10,
1026            tid(11),
1027            tid(1),
1028            Time::from_nanos(5),
1029            DownReason::Normal,
1030        );
1031        let e2 = TraceEvent::down_delivered(
1032            3,
1033            Time::ZERO,
1034            9,
1035            tid(12),
1036            tid(1),
1037            Time::from_nanos(5),
1038            DownReason::Normal,
1039        );
1040        let e3 = TraceEvent::down_delivered(
1041            4,
1042            Time::ZERO,
1043            1,
1044            tid(13),
1045            tid(2),
1046            Time::from_nanos(5),
1047            DownReason::Normal,
1048        );
1049
1050        // All four events are independent (distinct watcher tasks). Canonical
1051        // ordering within the layer must follow:
1052        // (completion_vt, monitored_tid, monitor_ref).
1053        let trace_a = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
1054        let trace_b = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
1055
1056        let foata_a = canonicalize(&trace_a);
1057        let foata_b = canonicalize(&trace_b);
1058        assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
1059        assert_eq!(foata_a.depth(), 1);
1060
1061        let flat = foata_a.flatten();
1062        assert_eq!(flat.len(), 4);
1063        assert_eq!(flat[0], e0);
1064        assert_eq!(flat[1], e2);
1065        assert_eq!(flat[2], e1);
1066        assert_eq!(flat[3], e3);
1067    }
1068
1069    #[test]
1070    fn exit_delivered_canonicalizes_by_failure_vt_from_link_ref() {
1071        let e0 = TraceEvent::exit_delivered(
1072            1,
1073            Time::ZERO,
1074            1,
1075            tid(2),
1076            tid(20),
1077            Time::from_nanos(9),
1078            DownReason::Error("boom".to_string()),
1079        );
1080        let e1 = TraceEvent::exit_delivered(
1081            2,
1082            Time::ZERO,
1083            10,
1084            tid(1),
1085            tid(21),
1086            Time::from_nanos(10),
1087            DownReason::Error("boom".to_string()),
1088        );
1089        let e2 = TraceEvent::exit_delivered(
1090            3,
1091            Time::ZERO,
1092            9,
1093            tid(1),
1094            tid(22),
1095            Time::from_nanos(10),
1096            DownReason::Error("boom".to_string()),
1097        );
1098        let e3 = TraceEvent::exit_delivered(
1099            4,
1100            Time::ZERO,
1101            1,
1102            tid(3),
1103            tid(23),
1104            Time::from_nanos(10),
1105            DownReason::Error("boom".to_string()),
1106        );
1107
1108        // Canonical ordering must follow:
1109        // (failure_vt, from_tid, link_ref).
1110        let trace_a = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
1111        let trace_b = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
1112
1113        let foata_a = canonicalize(&trace_a);
1114        let foata_b = canonicalize(&trace_b);
1115        assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
1116        assert_eq!(foata_a.depth(), 1);
1117
1118        let flat = foata_a.flatten();
1119        assert_eq!(flat.len(), 4);
1120        assert_eq!(flat[0], e0);
1121        assert_eq!(flat[1], e2);
1122        assert_eq!(flat[2], e1);
1123        assert_eq!(flat[3], e3);
1124    }
1125
1126    // === Complex equivalence: three independent events in any of 6 orders ===
1127
1128    #[test]
1129    fn three_independent_events_all_permutations_same_fingerprint() {
1130        let e1 = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
1131        let e2 = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
1132        let e3 = TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3));
1133
1134        let permutations: Vec<Vec<TraceEvent>> = vec![
1135            vec![e1.clone(), e2.clone(), e3.clone()],
1136            vec![e1.clone(), e3.clone(), e2.clone()],
1137            vec![e2.clone(), e1.clone(), e3.clone()],
1138            vec![e2.clone(), e3.clone(), e1.clone()],
1139            vec![e3.clone(), e1.clone(), e2.clone()],
1140            vec![e3, e2, e1],
1141        ];
1142
1143        let fp0 = trace_fingerprint(&permutations[0]);
1144        for (i, perm) in permutations.iter().enumerate() {
1145            let fp = trace_fingerprint(perm);
1146            assert_eq!(fp, fp0, "Permutation {i} has different fingerprint");
1147        }
1148    }
1149
1150    // === Mixed independent and dependent events ===
1151
1152    #[test]
1153    fn mixed_trace_canonical_form() {
1154        // T1 in R1 and T2 in R2 are independent.
1155        // Timer on same timer_id is independent of tasks.
1156        // But time_advance conflicts with timer events.
1157        let events = [
1158            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1159            TraceEvent::time_advance(2, Time::ZERO, Time::ZERO, Time::from_nanos(100)),
1160            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1161            TraceEvent::timer_fired(4, Time::ZERO, 1),
1162        ];
1163        let foata = canonicalize(&events);
1164        // spawn(T1) and spawn(T2) are independent -> same layer
1165        // time_advance is independent of spawns -> same layer as spawns
1166        // timer_fired depends on time_advance -> layer 1
1167        assert_eq!(foata.depth(), 2);
1168        assert_eq!(foata.layers()[0].len(), 3); // spawn T1, time_advance, spawn T2
1169        assert_eq!(foata.layers()[1].len(), 1); // timer_fired
1170    }
1171
1172    // === Deterministic intra-layer ordering ===
1173
1174    #[test]
1175    fn intra_layer_ordering_is_deterministic() {
1176        let events = [
1177            TraceEvent::spawn(1, Time::ZERO, tid(3), rid(3)),
1178            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1179            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1180        ];
1181        let foata = canonicalize(&events);
1182        assert_eq!(foata.depth(), 1);
1183
1184        // Should be sorted by (kind=Spawn, task_id).
1185        // tid(1) < tid(2) < tid(3) by ArenaIndex ordering.
1186        let layer = &foata.layers()[0];
1187        let keys: Vec<_> = layer.iter().map(event_sort_key).collect();
1188        assert!(keys.windows(2).all(|w| w[0] <= w[1]));
1189    }
1190
1191    #[test]
1192    fn equal_canonical_keys_still_have_total_sort_order() {
1193        let events = (0..16)
1194            .map(|idx| {
1195                TraceEvent::new(
1196                    idx,
1197                    Time::from_nanos(idx),
1198                    TraceEventKind::UserTrace,
1199                    TraceData::Message("same canonical message".to_string()),
1200                )
1201            })
1202            .collect::<Vec<_>>();
1203
1204        let foata = canonicalize(&events);
1205        assert_eq!(foata.depth(), 1);
1206        assert_eq!(foata.len(), events.len());
1207
1208        let fingerprint = trace_fingerprint(&events);
1209        assert_ne!(fingerprint, 0);
1210    }
1211
1212    // === Fingerprint consistency ===
1213
1214    #[test]
1215    fn fingerprint_matches_foata_fingerprint() {
1216        let events = [
1217            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1218            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1219            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
1220        ];
1221        let foata = canonicalize(&events);
1222        let direct = trace_fingerprint(&events);
1223        assert_eq!(foata.fingerprint(), direct);
1224    }
1225
1226    #[test]
1227    fn empty_trace_fingerprint_matches_identity() {
1228        let id = TraceMonoid::identity();
1229        let empty = TraceMonoid::from_events(&[]);
1230        assert_eq!(trace_fingerprint(&[]), id.class_fingerprint());
1231        assert_eq!(id.class_fingerprint(), empty.class_fingerprint());
1232        assert!(id.equivalent(&empty));
1233    }
1234
1235    // === Layer depth = critical path ===
1236
1237    #[test]
1238    fn depth_reflects_critical_path() {
1239        // Two parallel chains of length 2:
1240        //   spawn(T1) -> complete(T1)   (depth 2)
1241        //   spawn(T2) -> complete(T2)   (depth 2)
1242        let events = [
1243            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1244            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1245            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
1246            TraceEvent::complete(4, Time::ZERO, tid(2), rid(2)),
1247        ];
1248        let foata = canonicalize(&events);
1249        assert_eq!(foata.depth(), 2);
1250        assert_eq!(foata.layers()[0].len(), 2); // both spawns
1251        assert_eq!(foata.layers()[1].len(), 2); // both completes
1252    }
1253
1254    // === Flatten round-trip ===
1255
1256    #[test]
1257    fn flatten_preserves_event_count() {
1258        let events = [
1259            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1260            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1261            TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
1262        ];
1263        let foata = canonicalize(&events);
1264        assert_eq!(foata.flatten().len(), events.len());
1265    }
1266
1267    // === TraceMonoid: identity law ===
1268
1269    #[test]
1270    fn monoid_identity_left() {
1271        let events = [
1272            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1273            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1274        ];
1275        let t = TraceMonoid::from_events(&events);
1276        let result = TraceMonoid::identity().concat(&t);
1277        assert!(result.equivalent(&t));
1278        assert!(result.equivalent_exact(&t));
1279    }
1280
1281    #[test]
1282    fn monoid_identity_right() {
1283        let events = [
1284            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1285            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1286        ];
1287        let t = TraceMonoid::from_events(&events);
1288        let result = t.concat(&TraceMonoid::identity());
1289        assert!(result.equivalent(&t));
1290        assert!(result.equivalent_exact(&t));
1291    }
1292
1293    #[test]
1294    fn monoid_identity_is_empty() {
1295        let id = TraceMonoid::identity();
1296        assert!(id.is_identity());
1297        assert!(id.is_empty());
1298        assert_eq!(id.len(), 0);
1299        assert_eq!(id.critical_path_length(), 0);
1300        assert_eq!(id.max_parallelism(), 0);
1301    }
1302
1303    // === TraceMonoid: associativity ===
1304
1305    #[test]
1306    fn monoid_associativity() {
1307        let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
1308        let b_events = [TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2))];
1309        let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3))];
1310
1311        let a = TraceMonoid::from_events(&a_events);
1312        let b = TraceMonoid::from_events(&b_events);
1313        let c = TraceMonoid::from_events(&c_events);
1314
1315        let ab_c = a.concat(&b).concat(&c);
1316        let a_bc = a.concat(&b.concat(&c));
1317        assert!(ab_c.equivalent(&a_bc));
1318    }
1319
1320    #[test]
1321    fn monoid_associativity_with_dependencies() {
1322        // a and b are dependent (same task), c is independent
1323        let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
1324        let b_events = [TraceEvent::complete(2, Time::ZERO, tid(1), rid(1))];
1325        let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2))];
1326
1327        let a = TraceMonoid::from_events(&a_events);
1328        let b = TraceMonoid::from_events(&b_events);
1329        let c = TraceMonoid::from_events(&c_events);
1330
1331        let ab_c = a.concat(&b).concat(&c);
1332        let a_bc = a.concat(&b.concat(&c));
1333        assert!(ab_c.equivalent(&a_bc));
1334    }
1335
1336    // === TraceMonoid: equivalence ===
1337
1338    #[test]
1339    fn monoid_equivalent_traces() {
1340        // Two orderings of independent events → same monoid element
1341        let trace_a = [
1342            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1343            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1344        ];
1345        let trace_b = [
1346            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1347            TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1348        ];
1349        let ma = TraceMonoid::from_events(&trace_a);
1350        let mb = TraceMonoid::from_events(&trace_b);
1351        assert_eq!(ma, mb);
1352        assert!(ma.equivalent_exact(&mb));
1353    }
1354
1355    #[test]
1356    fn partial_eq_requires_exact_canonical_match_not_just_fingerprint() {
1357        let trace_a = [
1358            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1359            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1360        ];
1361        let trace_b = [
1362            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1363            TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
1364        ];
1365
1366        let ma = TraceMonoid::from_events(&trace_a);
1367        let mb = TraceMonoid::from_events(&trace_b);
1368        assert!(!ma.equivalent_exact(&mb));
1369
1370        // Construct an impossible-but-valid-internal state to model hash collision:
1371        // same fingerprint, different canonical form.
1372        let TraceMonoid {
1373            canonical: FoataTrace { layers },
1374            ..
1375        } = mb;
1376        let spoof = TraceMonoid {
1377            canonical: FoataTrace { layers },
1378            fingerprint: ma.fingerprint,
1379        };
1380
1381        // Fingerprint-only equivalence says "equal", but PartialEq must remain exact.
1382        assert!(ma.equivalent(&spoof));
1383        assert_ne!(ma, spoof);
1384    }
1385
1386    #[test]
1387    fn exact_equivalence_distinguishes_region_cancel_reason_payload() {
1388        let trace_a = [TraceEvent::region_cancelled(
1389            1,
1390            Time::ZERO,
1391            rid(1),
1392            CancelReason::shutdown(),
1393        )];
1394        let trace_b = [TraceEvent::region_cancelled(
1395            1,
1396            Time::ZERO,
1397            rid(1),
1398            CancelReason::timeout(),
1399        )];
1400
1401        let ma = TraceMonoid::from_events(&trace_a);
1402        let mb = TraceMonoid::from_events(&trace_b);
1403
1404        assert!(ma.equivalent(&mb));
1405        assert!(!ma.equivalent_exact(&mb));
1406        assert_ne!(ma, mb);
1407    }
1408
1409    #[test]
1410    fn monoid_nonequivalent_traces() {
1411        // Different causal structures → different monoid elements
1412        let trace_a = [
1413            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1414            TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1415        ];
1416        let trace_b = [
1417            TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1418            TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
1419        ];
1420        let ma = TraceMonoid::from_events(&trace_a);
1421        let mb = TraceMonoid::from_events(&trace_b);
1422        assert_ne!(ma, mb);
1423    }
1424
1425    // === TraceMonoid: parallelism metrics ===
1426
1427    #[test]
1428    fn monoid_parallelism_metrics() {
1429        let events = [
1430            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1431            TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1432            TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
1433            TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
1434            TraceEvent::complete(5, Time::ZERO, tid(2), rid(2)),
1435        ];
1436        let m = TraceMonoid::from_events(&events);
1437        assert_eq!(m.len(), 5);
1438        assert_eq!(m.critical_path_length(), 2); // spawns then completes
1439        assert_eq!(m.max_parallelism(), 3); // 3 independent spawns
1440    }
1441
1442    #[test]
1443    fn monoid_from_events_mapping() {
1444        // Verify the mapping φ: Σ* → M(Σ, I) preserves trace length
1445        let events = [
1446            TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1447            TraceEvent::region_created(2, Time::ZERO, rid(2), None),
1448            TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1449        ];
1450        let m = TraceMonoid::from_events(&events);
1451        assert_eq!(m.len(), events.len());
1452        assert_eq!(m.class_fingerprint(), trace_fingerprint(&events));
1453    }
1454
1455    // === Obligation events ===
1456
1457    #[test]
1458    fn independent_obligations_same_layer() {
1459        let events = [
1460            TraceEvent::obligation_reserve(
1461                1,
1462                Time::ZERO,
1463                oid(1),
1464                tid(1),
1465                rid(1),
1466                ObligationKind::SendPermit,
1467            ),
1468            TraceEvent::obligation_reserve(
1469                2,
1470                Time::ZERO,
1471                oid(2),
1472                tid(2),
1473                rid(2),
1474                ObligationKind::Ack,
1475            ),
1476        ];
1477        let foata = canonicalize(&events);
1478        assert_eq!(foata.depth(), 1);
1479    }
1480
1481    #[test]
1482    fn same_obligation_events_form_chain() {
1483        let events = [
1484            TraceEvent::obligation_reserve(
1485                1,
1486                Time::ZERO,
1487                oid(1),
1488                tid(1),
1489                rid(1),
1490                ObligationKind::Lease,
1491            ),
1492            TraceEvent::obligation_commit(
1493                2,
1494                Time::ZERO,
1495                oid(1),
1496                tid(1),
1497                rid(1),
1498                ObligationKind::Lease,
1499                5000,
1500            ),
1501        ];
1502        let foata = canonicalize(&events);
1503        assert_eq!(foata.depth(), 2);
1504    }
1505
1506    // --- wave 77 trait coverage ---
1507
1508    #[test]
1509    fn trace_event_key_debug_clone_copy_eq_hash() {
1510        use std::collections::HashSet;
1511        let k = TraceEventKey::new(1, 2, 3, 4);
1512        let k2 = k; // Copy
1513        let k3 = k;
1514        assert_eq!(k, k2);
1515        assert_eq!(k, k3);
1516        assert_ne!(k, TraceEventKey::new(1, 2, 3, 5));
1517        let dbg = format!("{k:?}");
1518        assert!(dbg.contains("TraceEventKey"));
1519        let mut set = HashSet::new();
1520        set.insert(k);
1521        assert!(set.contains(&k2));
1522    }
1523
1524    /// Metamorphic tests for the Foata normal form: commutation invariance,
1525    /// dependency preservation, the `trace_fingerprint`/`canonicalize`
1526    /// equivalence, and the `TraceMonoid` algebraic laws.
1527    mod metamorphic {
1528        use super::*;
1529
1530        /// SplitMix64 — deterministic shuffles so failures reproduce by seed.
1531        struct Rng(u64);
1532        impl Rng {
1533            fn new(seed: u64) -> Self {
1534                Self(seed.wrapping_add(0x9E37_79B9_7F4A_7C15))
1535            }
1536            fn next_u64(&mut self) -> u64 {
1537                self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
1538                let mut z = self.0;
1539                z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1540                z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1541                z ^ (z >> 31)
1542            }
1543            fn below(&mut self, n: usize) -> usize {
1544                (self.next_u64() % n as u64) as usize
1545            }
1546        }
1547
1548        fn task(n: u32) -> TaskId {
1549            TaskId::new_for_test(n, 0)
1550        }
1551        fn region(n: u32) -> RegionId {
1552            RegionId::new_for_test(n, 0)
1553        }
1554
1555        /// `n` pairwise-independent events: spawns on distinct task+region, so
1556        /// every footprint is disjoint and the whole set commutes freely.
1557        fn independent_set(n: u32) -> Vec<TraceEvent> {
1558            (1..=n)
1559                .map(|k| TraceEvent::spawn(u64::from(k), Time::ZERO, task(k), region(k)))
1560                .collect()
1561        }
1562
1563        /// A chain of mutually dependent events — every event writes Task(1).
1564        fn dependent_chain(n: u64) -> Vec<TraceEvent> {
1565            (0..n)
1566                .map(|k| {
1567                    let t = Time::from_nanos(k);
1568                    match k % 4 {
1569                        0 => TraceEvent::spawn(k, t, task(1), region(1)),
1570                        1 => TraceEvent::schedule(k, t, task(1), region(1)),
1571                        2 => TraceEvent::poll(k, t, task(1), region(1)),
1572                        _ => TraceEvent::complete(k, t, task(1), region(1)),
1573                    }
1574                })
1575                .collect()
1576        }
1577
1578        fn shuffled(items: &[TraceEvent], rng: &mut Rng) -> Vec<TraceEvent> {
1579            let mut v = items.to_vec();
1580            for i in (1..v.len()).rev() {
1581                let j = rng.below(i + 1);
1582                v.swap(i, j);
1583            }
1584            v
1585        }
1586
1587        fn canonical_seqs(events: &[TraceEvent]) -> Vec<u64> {
1588            canonicalize(events)
1589                .flatten()
1590                .iter()
1591                .map(|e| e.seq)
1592                .collect()
1593        }
1594
1595        #[test]
1596        fn canonicalize_and_fingerprint_are_deterministic() {
1597            for n in [0u64, 1, 2, 5, 9, 16] {
1598                let trace = dependent_chain(n);
1599                assert_eq!(
1600                    trace_fingerprint(&trace),
1601                    trace_fingerprint(&trace),
1602                    "trace_fingerprint non-deterministic (n={n})"
1603                );
1604                assert_eq!(
1605                    canonical_seqs(&trace),
1606                    canonical_seqs(&trace),
1607                    "canonicalize non-deterministic (n={n})"
1608                );
1609            }
1610        }
1611
1612        #[test]
1613        fn trace_fingerprint_equals_canonicalize_fingerprint() {
1614            // Documented equivalence: trace_fingerprint is the allocation-free
1615            // form of canonicalize(events).fingerprint().
1616            let mixed = {
1617                let z = Time::ZERO;
1618                vec![
1619                    TraceEvent::spawn(1, z, task(1), region(1)),
1620                    TraceEvent::spawn(2, z, task(2), region(2)),
1621                    TraceEvent::complete(3, z, task(1), region(1)),
1622                    TraceEvent::complete(4, z, task(2), region(2)),
1623                ]
1624            };
1625            let cases = [
1626                Vec::new(),
1627                independent_set(1),
1628                independent_set(6),
1629                dependent_chain(7),
1630                mixed,
1631            ];
1632            for trace in cases {
1633                assert_eq!(
1634                    trace_fingerprint(&trace),
1635                    canonicalize(&trace).fingerprint(),
1636                    "trace_fingerprint diverged from canonicalize().fingerprint()"
1637                );
1638            }
1639        }
1640
1641        #[test]
1642        fn permuting_fully_independent_events_yields_one_identical_layer() {
1643            // Pairwise-independent events commute freely: every permutation
1644            // canonicalizes to the same single layer with the same fingerprint.
1645            for n in [2u32, 3, 5, 8, 13] {
1646                let base = independent_set(n);
1647                let baseline = canonicalize(&base);
1648                assert_eq!(
1649                    baseline.depth(),
1650                    1,
1651                    "independent events must collapse to one layer"
1652                );
1653                let baseline_fp = baseline.fingerprint();
1654
1655                for seed in 0..16u64 {
1656                    let mut rng = Rng::new(seed ^ u64::from(n) << 8);
1657                    let permuted = shuffled(&base, &mut rng);
1658                    let canon = canonicalize(&permuted);
1659                    assert_eq!(
1660                        canon.fingerprint(),
1661                        baseline_fp,
1662                        "independent permutation changed the fingerprint (n={n}, seed={seed})"
1663                    );
1664                    assert_eq!(canon.depth(), 1);
1665                    assert_eq!(trace_fingerprint(&permuted), baseline_fp);
1666                }
1667            }
1668        }
1669
1670        #[test]
1671        fn swapping_an_adjacent_independent_pair_preserves_the_canonical_form() {
1672            // Indices 1 and 2 are spawns on disjoint task+region: independent,
1673            // so swapping them must not change the equivalence class.
1674            let z = Time::ZERO;
1675            let original = vec![
1676                TraceEvent::spawn(1, z, task(1), region(1)),
1677                TraceEvent::spawn(2, z, task(2), region(2)),
1678                TraceEvent::spawn(3, z, task(3), region(3)),
1679                TraceEvent::complete(4, z, task(1), region(1)),
1680            ];
1681            let mut swapped = original.clone();
1682            swapped.swap(1, 2);
1683            assert_eq!(
1684                trace_fingerprint(&original),
1685                trace_fingerprint(&swapped),
1686                "swapping an adjacent independent pair changed the class"
1687            );
1688            assert_eq!(
1689                canonicalize(&original).fingerprint(),
1690                canonicalize(&swapped).fingerprint()
1691            );
1692        }
1693
1694        #[test]
1695        fn a_dependent_chain_keeps_one_event_per_layer() {
1696            // Every event writes Task(1): the canonical form is fully
1697            // sequential — depth == length, every layer width 1.
1698            for n in 1u64..=12 {
1699                let canon = canonicalize(&dependent_chain(n));
1700                assert_eq!(canon.depth(), n as usize, "chain of {n} needs {n} layers");
1701                for layer in canon.layers() {
1702                    assert_eq!(layer.len(), 1, "dependent-chain layer holds one event");
1703                }
1704            }
1705        }
1706
1707        #[test]
1708        fn flatten_preserves_the_event_count() {
1709            for n in [0u64, 1, 4, 10] {
1710                let dep = dependent_chain(n);
1711                assert_eq!(canonicalize(&dep).flatten().len(), dep.len());
1712            }
1713            for n in [1u32, 5, 11] {
1714                let ind = independent_set(n);
1715                assert_eq!(canonicalize(&ind).flatten().len(), ind.len());
1716            }
1717        }
1718
1719        #[test]
1720        fn monoid_identity_is_empty_and_neutral() {
1721            let identity = TraceMonoid::identity();
1722            assert!(identity.is_identity());
1723            assert!(identity.is_empty());
1724            assert_eq!(identity.len(), 0);
1725
1726            let m = TraceMonoid::from_events(&dependent_chain(5));
1727            assert_eq!(identity.concat(&m), m, "left identity law");
1728            assert_eq!(m.concat(&identity), m, "right identity law");
1729        }
1730
1731        #[test]
1732        fn monoid_concat_is_associative() {
1733            let a = TraceMonoid::from_events(&independent_set(3));
1734            let b = TraceMonoid::from_events(&dependent_chain(4));
1735            let c = TraceMonoid::from_events(&independent_set(2));
1736            assert_eq!(
1737                a.concat(&b).concat(&c),
1738                a.concat(&b.concat(&c)),
1739                "concat must be associative"
1740            );
1741        }
1742
1743        #[test]
1744        fn monoid_groups_independent_reorderings_into_one_class() {
1745            let base = independent_set(7);
1746            let mut rng = Rng::new(0xC0FFEE);
1747            let permuted = shuffled(&base, &mut rng);
1748            let m1 = TraceMonoid::from_events(&base);
1749            let m2 = TraceMonoid::from_events(&permuted);
1750            assert!(m1.equivalent(&m2), "independent reorderings are one class");
1751            assert_eq!(m1, m2, "and structurally equal");
1752        }
1753
1754        #[test]
1755        fn monoid_equivalence_is_reflexive() {
1756            for trace in [independent_set(6), dependent_chain(8)] {
1757                let m = TraceMonoid::from_events(&trace);
1758                assert!(m.equivalent(&m), "equivalence must be reflexive");
1759                assert!(
1760                    m.equivalent_exact(&m),
1761                    "exact equivalence must be reflexive"
1762                );
1763                let again = TraceMonoid::from_events(&trace);
1764                assert_eq!(m, again, "PartialEq must be reflexive");
1765                assert_eq!(m.class_fingerprint(), canonicalize(&trace).fingerprint());
1766            }
1767        }
1768
1769        #[test]
1770        fn critical_path_and_parallelism_track_the_canonical_shape() {
1771            let ind = TraceMonoid::from_events(&independent_set(9));
1772            assert_eq!(ind.critical_path_length(), 1);
1773            assert_eq!(ind.max_parallelism(), 9);
1774
1775            let dep = TraceMonoid::from_events(&dependent_chain(6));
1776            assert_eq!(dep.critical_path_length(), 6);
1777            assert_eq!(dep.max_parallelism(), 1);
1778
1779            let empty = TraceMonoid::identity();
1780            assert_eq!(empty.critical_path_length(), 0);
1781            assert_eq!(empty.max_parallelism(), 0);
1782        }
1783
1784        #[test]
1785        fn empty_trace_canonicalizes_to_zero_layers() {
1786            let canon = canonicalize(&[]);
1787            assert_eq!(canon.depth(), 0);
1788            assert!(canon.is_empty());
1789            assert!(canon.flatten().is_empty());
1790            assert_eq!(canon.layers().len(), 0);
1791            assert_eq!(trace_fingerprint(&[]), canon.fingerprint());
1792        }
1793    }
1794}
1795
1796#[cfg(test)]
1797#[path = "canonicalize_metamorphic_tests.rs"]
1798mod canonicalize_metamorphic_tests;