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
//
// ░▀█▀░█▀▀░█▀█░█▀▄░█▀█░█▀▀░█░░░█▀▀
// ░░█░░▀▀█░█░█░█▀▄░█▀█░█░░░█░░░█▀▀
// ░░▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀
//
// tsoracle — Distributed Timestamp Oracle
//
// Copyright (c) 2026 Prisma Risk
// Licensed under the Apache License, Version 2.0
// https://github.com/prisma-risk/tsoracle
//
//! 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.