1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use super::*;
impl Store<Open> {
/// LIFECYCLE
///
/// # Errors
/// Returns `StoreError::Io` if flushing the active segment to disk fails.
pub fn sync(&self) -> Result<(), StoreError> {
lifecycle::sync(self)
}
/// Block until the durable frontier reaches `point` or `timeout` elapses.
///
/// # Errors
/// Returns [`StoreError::WaitTimeout`] if `durable_hlc` does not reach
/// `point` before `timeout`. Returns [`StoreError::WriterCrashed`] if the
/// writer panicked while the caller was waiting.
pub fn wait_for_durable(
&self,
point: HlcPoint,
timeout: std::time::Duration,
) -> Result<(), StoreError> {
self.watermark_handle.wait_for_durable(point, timeout)
}
/// Block until the applied frontier reaches `point` or `timeout` elapses.
///
/// `applied_hlc` is the minimum applied HLC across registered projections,
/// so a single lagging projection can keep this wait blocked.
///
/// # Errors
/// Returns [`StoreError::WaitTimeout`] if `applied_hlc` does not reach
/// `point` before `timeout`. Returns [`StoreError::WriterCrashed`] if the
/// writer panicked while the caller was waiting.
pub fn wait_for_applied(
&self,
point: HlcPoint,
timeout: std::time::Duration,
) -> Result<(), StoreError> {
self.watermark_handle.wait_for_applied(point, timeout)
}
/// Block until the visible frontier reaches `point` or `timeout` elapses.
///
/// # Errors
/// Returns [`StoreError::WaitTimeout`] if `visible_hlc` does not reach
/// `point` before `timeout`. Returns [`StoreError::WriterCrashed`] if the
/// writer panicked while the caller was waiting.
pub fn wait_for_visible(
&self,
point: HlcPoint,
timeout: std::time::Duration,
) -> Result<(), StoreError> {
self.watermark_handle.wait_for_visible(point, timeout)
}
/// Snapshot the current index to a destination directory and return
/// deterministic snapshot evidence.
///
/// # Errors
/// Returns `StoreError::Io` if creating the destination directory or copying segment files fails.
pub fn snapshot_with_evidence(
&self,
dest: &std::path::Path,
) -> Result<SnapshotEvidenceReport, StoreError> {
lifecycle::snapshot(self, dest)
}
/// Deprecated snapshot wrapper that drops [`SnapshotEvidenceReport`].
///
/// # Errors
/// Returns `StoreError::Io` if creating the destination directory or copying segment files fails.
#[deprecated(note = "use snapshot_with_evidence; snapshot evidence is now first-class")]
pub fn snapshot(&self, dest: &std::path::Path) -> Result<(), StoreError> {
self.snapshot_with_evidence(dest).map(|_| ())
}
/// Compact: merge sealed segments, optionally filtering events.
/// The active (currently-written) segment is never touched.
///
/// # F6 / FREEZE-4 swap contract
///
/// The in-memory index is rebuilt off-side from the post-merge segment
/// layout and then published as a single atomic swap under an exclusive
/// lock (see `StoreIndex::replace_contents_from_fresh`). Reader-facing
/// methods (`query`, `stream`, `cursor_guaranteed` polls, etc.) take a
/// read guard on the same lock, so a concurrent reader observes either
/// the pre-compact index or the post-compact index — never a cleared or
/// partially rebuilt view.
///
/// Failure modes are surfaced through the returned
/// [`segment::CompactionResult`]. The accompanying
/// [`CompactionReportBody`] is always returned as deterministic evidence
/// for the compaction decision and observed outcome.
///
/// * [`segment::CompactionOutcome::Performed`] — the segment merge
/// happened and the live index has been swapped for the fresh one.
/// * [`segment::CompactionOutcome::Skipped`] — the sealed-segment count
/// was below `min_segments`; no disk or index work was done.
/// * [`segment::CompactionOutcome::Failed`] — the off-side rebuild
/// aborted before the swap point; the live index has not been
/// mutated, and the pending-compaction marker preserves a coherent
/// reopen path until cleanup completes.
///
/// Appends that arrive during compaction are safe (they go to the active
/// segment which is not compacted). `sync()` is called before and after
/// the segment merge so the off-side rebuild sees a quiescent on-disk
/// state; for maximum safety, avoid high-throughput appends during
/// compaction.
///
/// # Errors
/// Returns `StoreError::Io` if reading, writing, or removing segment
/// files fails. A rebuild failure is NOT an error — it is reported via
/// `CompactionOutcome::Failed`.
pub fn compact(
&self,
config: &CompactionConfig,
) -> Result<
(
crate::store::segment::CompactionResult,
CompactionReportBody,
),
StoreError,
> {
lifecycle::compact(self, config)
}
/// LIFECYCLE: flush pending writes and shut down the writer thread cleanly.
///
/// # Errors
/// Returns `StoreError::WriterCrashed` if the writer thread has already exited unexpectedly.
pub fn close(self) -> Result<Closed, StoreError> {
lifecycle::close(self)
}
}