Skip to main content

nautilus_event_store/
headers.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! First-class correlation headers propagated end-to-end across the system.
17
18use nautilus_core::UUID4;
19use serde::{Deserialize, Serialize};
20
21/// First-class metadata propagated end-to-end across captured messages.
22///
23/// Fields are ordered from most abstract (workflow grouping) to most concrete (one-hop
24/// lineage), matching the CQRS / event-sourcing convention (`EventStore`, Axon, Marten
25/// all list correlation, then causation, then message id). All fields default to `None` so
26/// capture works before propagation discipline lands across the command, event, and
27/// reconciliation report types. Once a field is populated, the bus capture adapter writes
28/// it through; replay never invents values.
29///
30/// Agent-level intent is not a separate field at this layer: when an agent decision is
31/// lowered into a bus message, the agent's `intent_id` is written to the message's
32/// `correlation_id`, so forensics queries that need "find by agent intent" scan the
33/// captured stream by `correlation_id`.
34#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub struct Headers {
36    /// The correlation chain id that ties commands, events, and reports to one logical action.
37    pub correlation_id: Option<UUID4>,
38    /// The id of the message that directly caused this one, if any.
39    pub causation_id: Option<UUID4>,
40}
41
42impl Headers {
43    /// Creates a new [`Headers`] with all fields unset.
44    #[must_use]
45    pub const fn empty() -> Self {
46        Self {
47            correlation_id: None,
48            causation_id: None,
49        }
50    }
51
52    /// Returns `true` if every header field is unset.
53    #[must_use]
54    pub const fn is_empty(&self) -> bool {
55        self.correlation_id.is_none() && self.causation_id.is_none()
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use rstest::rstest;
62
63    use super::*;
64
65    #[rstest]
66    fn default_is_empty() {
67        let headers = Headers::default();
68        assert!(headers.is_empty());
69        assert_eq!(headers, Headers::empty());
70    }
71
72    #[rstest]
73    fn populated_headers_are_not_empty() {
74        let headers = Headers {
75            correlation_id: Some(UUID4::new()),
76            causation_id: None,
77        };
78        assert!(!headers.is_empty());
79    }
80}