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}