Skip to main content

nautilus_event_store/
error.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//! Error types for the event store.
17
18use thiserror::Error;
19
20/// Errors returned by event store backends, the writer, the reader, and the verifier.
21///
22/// The variants form a typed surface for the operational policies described in the SPEC: disk
23/// pressure halts the kernel, hash mismatch quarantines the affected run, and a crashed
24/// predecessor is sealed by the caller before a new run is opened.
25#[derive(Debug, Error)]
26pub enum EventStoreError {
27    /// A backend operation failed for a reason that does not fit any other variant.
28    ///
29    /// Typically wraps a redb error or an in-memory backend invariant violation.
30    #[error("backend error: {0}")]
31    Backend(String),
32    /// A run file is structurally damaged and cannot be opened safely.
33    ///
34    /// Surfaces redb header-region corruption and any backend-detected structural failure.
35    #[error("corrupted run: {0}")]
36    Corrupted(String),
37    /// The backing storage refused the write because of disk pressure.
38    ///
39    /// Maps to redb `Io(FileTooLarge)` and equivalent host-level failures (ENOSPC,
40    /// `RLIMIT_FSIZE`). Triggers the kernel halt path; the writer fail-stops.
41    #[error("disk error: {0}")]
42    Disk(String),
43    /// The canonical entry hash recorded with a row does not match the recomputed hash.
44    ///
45    /// Quarantines the affected run.
46    #[error("entry hash mismatch at seq {seq}")]
47    HashMismatch {
48        /// The sequence number whose stored hash did not match.
49        seq: u64,
50    },
51    /// The row stored at `table_key` embeds an `entry.seq` that disagrees with the key.
52    ///
53    /// The entry hash covers the embedded seq rather than the key, so a moved or
54    /// duplicated row still hashes correctly. Quarantines the affected run.
55    #[error("seq mismatch at table key {table_key}: embedded seq was {embedded_seq}")]
56    SeqMismatch {
57        /// The redb table key (the slot the reader was reading).
58        table_key: u64,
59        /// The seq embedded inside the decoded entry value.
60        embedded_seq: u64,
61    },
62    /// The writer received an entry whose sequence number is not contiguous after the
63    /// current durable high-watermark.
64    #[error(
65        "out-of-order seq: received {seq}, expected contiguous after high-watermark {high_watermark}"
66    )]
67    OutOfOrder {
68        /// The current durable high-watermark.
69        high_watermark: u64,
70        /// The offending sequence number.
71        seq: u64,
72    },
73    /// The run is sealed and cannot accept further writes.
74    #[error("run is closed")]
75    Closed,
76    /// A reader processing `seq=N+1` did not observe `seq=N`.
77    ///
78    /// One of the four idempotency primitives: gap detection on read.
79    #[error("gap detected: missing seq {missing} between {prev} and {next}")]
80    Gap {
81        /// The last seq the reader observed.
82        prev: u64,
83        /// The next seq the reader observed.
84        next: u64,
85        /// The first missing seq.
86        missing: u64,
87    },
88    /// Opening a run found a predecessor whose status is `Running` and that lacks a
89    /// `RunEnded` entry.
90    ///
91    /// The kernel is expected to seal the predecessor (as `CrashedRecovered`, or
92    /// `Quarantined` if hash check fails) and then open a new run that records the
93    /// predecessor's `run_id` as `parent_run_id`.
94    #[error("crashed predecessor run requires sealing")]
95    CrashedPredecessor,
96}