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
136
137
138
139
//
// ░▀█▀░█▀▀░█▀█░█▀▄░█▀█░█▀▀░█░░░█▀▀
// ░░█░░▀▀█░█░█░█▀▄░█▀█░█░░░█░░░█▀▀
// ░░▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀
//
// tsoracle — Distributed Timestamp Oracle
// https://www.tsoracle.rs
//
// Copyright (c) 2026 Prisma Risk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Cluster bootstrap helpers.
//!
//! Coverage note: excluded from `make coverage` because exercising this
//! wrapper requires a live raft, which the toolkit's own tests deliberately
//! don't stand up. Downstream consumers' integration tests carry the real
//! coverage; the compile-time signature shim in `tests/lifecycle.rs` is what
//! catches openraft API drift inside this file.
//!
//! A node coming up as a voter needs one of two startup modes:
//!
//! - **Fresh**: first-time start; the caller knows the initial voter set.
//! - **Reopen**: restart against existing on-disk state; do nothing special.
//!
//! [`bootstrap`] folds these into a single async call, mapping openraft's
//! `InitializeError::NotAllowed` (raised when the raft already has log/snapshot
//! state) to success under `Reopen` and to a clear `BootstrapError` under
//! `Fresh`.
//!
//! A node joining an existing cluster as a learner uses neither mode: it does
//! no local initialization, so it calls [`join`] (a documented no-op on the
//! joining side) and waits for the leader to register it via
//! [`add_learner`](super::membership::add_learner).
use BTreeMap;
use ;
use RaftStateMachine;
use ;
use Error;
use info;
/// Startup mode for [`bootstrap`].
///
/// A learner joining an existing cluster is intentionally **not** a variant
/// here: the joining node does no local initialization, so it calls [`join`]
/// and waits for the leader to register it via
/// [`add_learner`](super::membership::add_learner). Keeping it out of this enum
/// means every arm performs a distinct local action — there is no mode whose
/// only difference from another is the log line it emits.
/// Failure modes for [`bootstrap`].
/// Bring a raft instance to a serviceable state without duplicating the
/// fresh/reopen branching in every consumer.
///
/// - `Fresh { initial_members }`: calls `raft.initialize(initial_members)`.
/// `InitializeError::NotAllowed` (the log already has membership) is
/// translated to [`BootstrapError::UnexpectedExistingState`]; other errors
/// surface as [`BootstrapError::Initialize`].
/// - `Reopen`: does not touch the raft. The caller is expected to have opened
/// existing storage; openraft replays the log on its own.
///
/// A node joining as a learner does not call [`bootstrap`] at all — see
/// [`join`].
pub async
/// Record that this node is joining an existing cluster as a learner.
///
/// This is a deliberate no-op on the joining node and never touches the raft:
/// a learner must not initialize its own membership — doing so would fork the
/// cluster into two single-node clusters. The node simply comes up against
/// empty or replayed storage and sits idle until it is promoted.
///
/// The join itself is driven from the **leader**, which registers this node by
/// calling [`add_learner`](super::membership::add_learner). Call [`join`] only
/// to record the startup intent in the logs; use [`bootstrap`] with
/// [`BootstrapMode::Fresh`] or [`BootstrapMode::Reopen`] for the voter paths.