Start the cluster state machine — bootstrap, join, or restart. Returns the initialized [`ClusterState`] only. Subsystems are NOT spawned here: they share an `Arc >` with the `RaftLoop`, which only exists after the host calls [`crate::raft_loop::RaftLoop::new`]. The host therefore drives a two-step startup: ```text 1. start_cluster(...) -> ClusterState 2. start_raft(.
(
config: &ClusterConfig,
catalog: &ClusterCatalog,
transport: Arc<NexarTransport>,
lifecycle: &ClusterLifecycleTracker,
)
| 138 | /// transitions it to `Restarting` / `Bootstrapping` / `Joining` as |
| 139 | /// the dispatcher picks a branch, and to `Failed` on terminal error. |
| 140 | pub async fn start_cluster( |
| 141 | config: &ClusterConfig, |
| 142 | catalog: &ClusterCatalog, |
| 143 | transport: Arc<NexarTransport>, |
| 144 | lifecycle: &ClusterLifecycleTracker, |
| 145 | ) -> Result<ClusterState> { |
| 146 | // Authoritative catalog state wins — a previously bootstrapped |
| 147 | // node always takes the restart path on boot. |
| 148 | let cluster_state = if catalog.is_bootstrapped()? { |
| 149 | lifecycle.to_restarting(); |
| 150 | restart(config, catalog, &transport).inspect_err(|e| { |
| 151 | lifecycle.to_failed(format!("restart failed: {e}")); |
| 152 | })? |
| 153 | } else { |
| 154 | // No existing state — decide bootstrap vs join. |
| 155 | let is_seed = config.seed_nodes.contains(&config.listen_addr); |
| 156 | if is_seed && should_bootstrap(config, &transport).await { |
| 157 | lifecycle.to_bootstrapping(); |
| 158 | bootstrap(config, catalog, transport.local_spki_pin()).inspect_err(|e| { |
| 159 | lifecycle.to_failed(format!("bootstrap failed: {e}")); |
| 160 | })? |
| 161 | } else { |
| 162 | join(config, catalog, &transport, lifecycle).await? |
| 163 | } |
| 164 | }; |
| 165 | |
| 166 | Ok(cluster_state) |
| 167 | } |
| 168 | |
| 169 | /// Spawn the default cluster subsystems sharing `raft_multi_raft` with |
| 170 | /// the running [`crate::raft_loop::RaftLoop`]. |