raftNode
Creates and starts a RaftNode whose lifetime is tied to this CoroutineScope.
This is the single construction entry point for kuilt-raft. Using a scope extension means the node participates in structured concurrency — it is cancelled automatically when the scope completes or is cancelled, and any exception in the node propagates to the scope's supervisor.
Test-dispatcher guard. If the scope contains a kotlinx.coroutines.test.TestDispatcher, a diagnostic is emitted because real RaftNode uses real-clock kotlinx.coroutines.delay for elections — under virtual time those delays never advance automatically and the test will deadlock silently for the full runTest timeout. Use FakeRaftNode from :kuilt-raft-test for unit tests instead. Set RaftConfig.strictTestGuard to true to throw rather than warn.
Return
A running RaftNode ready to receive proposals and emit committed entries.
Parameters
The cluster membership (voters + optional learners).
The messaging layer connecting this node to its peers.
Durable state for this node's term, vote, and log.
Timing parameters. Defaults are suitable for LAN; adjust for high-latency or test environments.
How this node obtains its Raft §8 dedup ClientId. ClientIdentity.Auto (default) mints ClientId.auto(thisNodeId, raftConfig.random) — a per-incarnation id giving at-least-once forwarding without cross-crash dedup, re-minted on collision. Pass ClientIdentity.Durable with a stable ClientId the caller persists itself for exactly-once across process restarts (replay the same requestId on retry). See ClientSessionTable.
Optional callback invoked on the engine's coroutine at each RaftMetric transition. Use to route metrics to Prometheus, StatsD, OpenTelemetry, or a test assertion. Must not block — the callback runs synchronously on the engine actor; blocking stalls replication for the entire cluster. null (default) disables the hook.
Throws
if clusterConfig has no voters and its learners do not include transport.selfId. A bootstrap must seat at least one voter, or be a learner seed: no voters, with this node among the learners, as in ClusterConfig(voters = emptySet(), learners = setOf(self)). That is how a joiner starts until the leader's config seats it: Raft §4.4's empty start for a new server, with the intent to join spelled out. The refused shapes, (voters = ∅, learners = ∅) and (voters = ∅, learners = {someone else}), would join the same way and carry the same exposure as the seed: the §5.2 leader-authority gate stays unarmed until a config seats voters, so until then any peer's AppendEntries can move this node's term (#2676). They are refused so that the seed is the one voterless bootstrap, and because the likelier way to reach them is an accident, such as a roster computed as empty or the wrong node id. A deliberate joiner migrates by adding itself to learners.