faction
A no_std, 0-unsafe Mealy machine for cluster bootstrapping.
faction is a protocol-agnostic state machine primitive that answers one question:
"When is the cluster ready to proceed?"
It tracks participation and readiness signals across a known set of peers and emits a deterministic exit decision — either Bootstrapped or TimedOut. No network I/O. No consensus algorithm. No opinion on what "ready" means. Just pure, testable state transitions.
Why faction?
Most distributed systems bootstrap with ad-hoc coordination — timeouts, magic numbers, and implicit assumptions that are never tested in isolation. faction replaces that with a Mealy machine that is:
- Deterministic — same inputs always produce the same outputs. Replay any sequence.
- Verifiable — every
(state, command)pair is explicitly tested. No untested paths. - Embeddable —
no_std+alloc, zerounsafe. Runs on bare metal, WASM, and cloud. - Observable — every transition reaches a trait-based
Observer. No instrumentation surprises. - Slim by construction — each state carries only its active data. Terminal states carry counts, not collections.
How it works
The machine progresses through five states:
Initial → Pinging → Collecting → Bootstrapped
↘ TimedOut
| State | Carries |
|---|---|
Initial |
Nothing — unit struct |
Pinging |
pinging_peers: Vec<PeerId>, collecting_peers: Vec<PeerId> |
Collecting |
collecting_peers: Vec<PeerId>, pinged_peers: Vec<PeerId> |
Bootstrapped |
pinged_peers: Vec<PeerId>, collected_peers: Vec<PeerId> |
TimedOut |
pinging_peers: Vec<PeerId>, collecting_peers: Vec<PeerId> |
Each state implements the State trait with step(), cluster_view(), and accept().
Decision logic is split into focused step structs — PingingStep, ReadyStep, and
LocalCompletionStep — each handling exactly one kind of observation without branching
on a kind enum.
Five commands drive the machine: ParticipationObserved, ReadyObserved,
LocalParticipationCompleted, DeadlineExpired, and Probe. Outcomes cover acceptance,
duplication, non-member rejection, local participation completion, broadcast, quorum,
and exit.
Full specification in phase-0-specification.md.
Project status
| Metric | Value |
|---|---|
| Productive LOC (core) | 1,165 |
| Tests (entire codebase) | 264 |
| Crappy functions | 0 |
| Code coverage (productive) | 100% |
| Unsafe code | 0 (enforced by #![deny(unsafe_code)]) |
no_std |
Verified |
Roadmap
The project is building toward full dynamic membership across six phases. See ROADMAP.md for the detailed plan.
Workspace
| Crate | Description |
|---|---|
core/ |
State machine — 13 source files, 145 tests |
core-validation/ |
Deterministic multi-node scenario harness — 23 tests |
protocol/ |
Message translator and protocol runtime — 33 tests |
protocol-validation/ |
In-process protocol cluster — 9 tests |
system-tests/ |
Multi-process convergence + timer + transport + observer tests — 54 tests |
Quality Gates
powershell -File scripts\run_stage_1.ps1 # format, clippy, no_std checks, tests
powershell -File scripts\run_stage_2.ps1 # CRAP and file risk analysis
Design principles
- Pure Mealy —
output = F(state, input). No side effects inside the machine. - Explicit state ownership — states carry only what they mutate.
- No dead code — terminal states return
falsefromaccept(), makingstep()unreachable by construction. - Observer, not logger — the
Observertrait receives every transition. Wire it to telemetry, audit, or testing assertions. - Protocol-agnostic — faction does not know what a "peer" is or how the network works. The caller owns network I/O.
- One struct per file — each step, state, and policy is its own file.
- No
&mutparameters — prefer return values over in-place mutation.
License
Licensed under the MIT License. See LICENSE.
Links
- CHANGELOG — project history
- CODE_OF_CONDUCT — community guidelines
- DONATE — support the project
- ROADMAP — future plans
- PHASE 0 SPECIFICATION — detailed state machine description