Skip to main content

nautilus_event_store/
manifest.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//! The per-run manifest stored alongside captured entries.
17
18use indexmap::IndexMap;
19use nautilus_core::UnixNanos;
20pub use nautilus_system::{RegisteredComponents, event_store::RunId};
21use serde::{Deserialize, Serialize};
22
23use crate::wire;
24
25/// Lifecycle state of a captured run.
26#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub enum RunStatus {
28    /// The run is open and accepting writes.
29    Running,
30    /// The run was sealed by graceful shutdown after a `RunEnded` entry.
31    Ended,
32    /// The run was sealed on boot after the writer found no `RunEnded` entry.
33    CrashedRecovered,
34    /// The run failed an integrity check and is unsafe to replay.
35    Quarantined,
36}
37
38/// Per-run manifest persisted alongside captured entries.
39///
40/// Every field is recorded at run start; `end_ts_init`, `high_watermark`, and `status` are
41/// updated when the run is sealed.
42#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub struct RunManifest {
44    /// The id of this run.
45    pub run_id: RunId,
46    /// The id of the predecessor run that this run resumes from, if any.
47    pub parent_run_id: Option<RunId>,
48    /// The id of the trading instance owning this run.
49    pub instance_id: String,
50    /// A hex-encoded hash of the trader binary.
51    pub binary_hash: String,
52    /// Bumps when the entry payload schema changes.
53    pub schema_version: u32,
54    /// A hex-encoded hash of `Cargo.lock` or an equivalent crate version manifest.
55    pub crate_versions: String,
56    /// The active Cargo features for the trader binary.
57    pub feature_flags: Vec<String>,
58    /// Per-adapter version stamp keyed by adapter name.
59    pub adapter_versions: IndexMap<String, String>,
60    /// A hex-encoded hash of the kernel configuration.
61    pub config_hash: String,
62    /// Registered actor, strategy, and algorithm identities along with subscription bindings.
63    pub registered_components: RegisteredComponents,
64    /// The deterministic seed, populated when the run executes under a seeded mode.
65    pub seed: Option<u64>,
66    /// The first `ts_init` observed by the writer for this run.
67    #[serde(with = "wire::nanos_as_u64")]
68    pub start_ts_init: UnixNanos,
69    /// The last `ts_init` observed by the writer for this run, populated on seal.
70    #[serde(with = "wire::opt_nanos_as_u64")]
71    pub end_ts_init: Option<UnixNanos>,
72    /// The largest `seq` durably acknowledged by the backend at end of run.
73    pub high_watermark: u64,
74    /// The lifecycle state of this run.
75    pub status: RunStatus,
76}
77
78impl RunManifest {
79    /// Returns `true` once `status` is anything other than [`RunStatus::Running`].
80    #[must_use]
81    pub const fn is_sealed(&self) -> bool {
82        !matches!(self.status, RunStatus::Running)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use rstest::rstest;
89
90    use super::*;
91
92    fn manifest_with(status: RunStatus) -> RunManifest {
93        RunManifest {
94            run_id: "1700000000-abcd1234".to_string(),
95            parent_run_id: None,
96            instance_id: "trader-001".to_string(),
97            binary_hash: "deadbeef".to_string(),
98            schema_version: 1,
99            crate_versions: "feedface".to_string(),
100            feature_flags: vec!["live".to_string()],
101            adapter_versions: IndexMap::new(),
102            config_hash: "cafebabe".to_string(),
103            registered_components: RegisteredComponents::default(),
104            seed: None,
105            start_ts_init: UnixNanos::from(0),
106            end_ts_init: None,
107            high_watermark: 0,
108            status,
109        }
110    }
111
112    #[rstest]
113    #[case(RunStatus::Running, false)]
114    #[case(RunStatus::Ended, true)]
115    #[case(RunStatus::CrashedRecovered, true)]
116    #[case(RunStatus::Quarantined, true)]
117    fn is_sealed_matches_status(#[case] status: RunStatus, #[case] expected: bool) {
118        assert_eq!(manifest_with(status).is_sealed(), expected);
119    }
120}