Dummy page. The prose below is placeholder trunk narrative so the tabs have something to link to; the tree and timeline are live from the project’s decision log.
milestoneadoptedrejecteddeferredrevisitednumbers are implementation order · trunk reads top to bottom
click a node · drag to pan · ← → to step
flowchart TB
classDef ms fill:#1e2622,stroke:#1e2622,color:#f5f6f2,font-weight:700
classDef adopted fill:#dcefe3,stroke:#2f6f4e,color:#1e2622
classDef rejected fill:#f5e0d9,stroke:#a3452d,color:#1e2622
classDef deferred fill:#f3ead0,stroke:#8a6f1f,color:#1e2622
classDef revisited fill:#dfe4f5,stroke:#4d5f9b,color:#1e2622,stroke-dasharray:4 3
orig_build["1 · original build"]:::ms
return["10 · return, orientation, and the big 'what is this project' conversation"]:::ms
plan["25 · plan, correctness sweep, architecture pivot"]:::ms
types["49 · Message/Ready types, unstable buffer, testing tension, blog"]:::ms
hex["2 · Hexagonal core with three plug-in …"]:::adopted
sm_port["3 · State machine is application-facing, …"]:::adopted
inmem["4 · In-memory adapters as the test …"]:::adopted
event_loop["5 · Single event-loop goroutine, …"]:::adopted
mutex["6 · Mutex only for external readers"]:::adopted
submit["7 · SubmitFoo methods as the …"]:::adopted
tests_v1["8 · Seven election tests, sleep-based …"]:::adopted
higher_term_ch["9 · Dedicated higherTermCh for fast term …"]:::adopted
home["11 · Home-scale 'killer app' for Raft"]:::rejected
fl["12 · Federated learning testbed on old …"]:::rejected
llm["13 · Distributed LLM inference across …"]:::deferred
lora["14 · LoRa mesh application"]:::deferred
portfolio_piece["15 · Ship lockraft as a portfolio piece; …"]:::adopted
tla_1["16 · TLA+ (1) only — run TLC on Ongaro's …"]:::adopted
trace["17 · Trace conformance"]:::rejected
refinement["18 · Refinement / Verdi-style proof"]:::rejected
pgo["19 · PGo / MPCal spec-driven extraction"]:::rejected
cbmc["20 · Go → C transpile → CBMC"]:::rejected
gomela["21 · Gomela → SPIN"]:::deferred
no_fv["22 · No formal verification of the Go …"]:::adopted
testing_pyramid["23 · Layered pyramid:"]:::adopted
jepsen["24 · Jepsen"]:::deferred
timeout["26 · Re-randomize election timeout at …"]:::adopted
maxterm["27 · Remove unused maxTermSeen from …"]:::adopted
storage_errors["28 · AppendEntries storage errors: reads …"]:::adopted
savestate["29 · SaveState wiring"]:::deferred
step_pattern["30 · Step-pattern refactor: pull …"]:::adopted
channels_go["31 · Most existing channels disappear, …"]:::adopted
runner["32 · New runner/ package owns the …"]:::adopted
ports_widen["33 · Widen ports: Clock, Rand, Emitter"]:::adopted
no_shared_abstraction["34 · No shared abstraction between old …"]:::adopted
bench["35 · Benchmark step-pattern vs. the …"]:::deferred
sm_determinism["36 · Determinism is load-bearing"]:::adopted
dedup["37 · Dedup by (ClientID, RequestID)"]:::adopted
logical_clock["38 · Logical clock inside the state …"]:::adopted
cmd_kind["39 · Command discriminator pattern"]:::adopted
snapshot["40 · Snapshot()/Restore() on the state …"]:::deferred
perf_stance["41 · The real cost of Raft is fsync + …"]:::adopted
reads["42 · Reads through the log first; …"]:::adopted
viz["43 · Emitter is a port"]:::adopted
hosting["44 · Three hosting tiers:"]:::adopted
lock_viz["45 · Lock-manager visualization is the …"]:::adopted
fencing_demo["46 · The Kleppmann fencing scenario is …"]:::adopted
plan_md["47 · PLAN.md created"]:::adopted
etcd["48 · Copy etcd where novelty would be a …"]:::adopted
message_type["50 · raft.Message is a flat struct with a …"]:::adopted
matchindex["51 · AppendEntriesReply grows MatchIndex"]:::adopted
ready_type["52 · raft.Ready carries intents; the …"]:::adopted
hardstate["53 · HardState = Term + VotedFor only"]:::adopted
sync["54 · No Entries on Ready; Node writes the …"]:::revisited
unst["55 · Unstable log buffer (etcd model) — …"]:::adopted
transport_send["56 · Transport.Send(Message) refactor"]:::deferred
fakes_vs_real["57 · Fakes do the heavy lifting; …"]:::adopted
fake_storage["58 · The DST fake Storage must model …"]:::adopted
decisions_log["59 · Design decisions are logged here as …"]:::adopted
blog_views["60 · Blog writeup gets two views over one …"]:::adopted
entry_format["61 · Parseable entry format in this file"]:::adopted
tags["62 · Git tags at narrative milestones"]:::adopted
renderer_v1["63 · Renderer: horizontal mermaid …"]:::revisited
three_tabs["64 · Three tabs: Write-up, Tangent tree, …"]:::adopted
no_dup["65 · The tree pane never duplicates …"]:::adopted
zoom["66 · Click-to-zoom on the tree"]:::adopted
hash_state["67 · UI state is two values in the URL …"]:::adopted
dst_vs_tla["68 · What DST covers vs. what TLA+ on …"]:::adopted
tags_created["69 · Tags created:"]:::adopted
foundation_commits["70 · Committed the step-pattern …"]:::adopted
strict["71 · Strict adherence to the three …"]:::adopted
overlay["72 · The blog component lives in the site …"]:::adopted
parser["73 · One script is the bridge: …"]:::adopted
entry_ids["74 · Every entry now ends in {id}"]:::adopted
mermaid_only["75 · Only one library: mermaid 11.4.1 …"]:::adopted
full_bleed["76 · The tree tab goes full-bleed; the …"]:::adopted
dummy_page["77 · Dummy page shipped"]:::adopted
timeline_tab["78 · Timeline as a third tab"]:::revisited
seq_order["79 · Order is folded into the tree"]:::adopted
label_hygiene["80 · Mermaid label hygiene"]:::adopted
orig_build --> hex
orig_build --> sm_port
orig_build --> inmem
orig_build --> event_loop
event_loop --> mutex
event_loop --> submit
orig_build --> tests_v1
orig_build --> higher_term_ch
return --> home
return --> fl
return --> llm
return --> lora
return --> portfolio_piece
return --> tla_1
tla_1 --> trace
tla_1 --> refinement
tla_1 --> pgo
tla_1 --> cbmc
tla_1 --> gomela
return --> no_fv
return --> testing_pyramid
testing_pyramid --> jepsen
plan --> timeout
plan --> maxterm
plan --> storage_errors
plan --> savestate
plan --> step_pattern
step_pattern --> channels_go
step_pattern --> runner
step_pattern --> ports_widen
step_pattern --> no_shared_abstraction
step_pattern --> bench
plan --> sm_determinism
sm_determinism --> dedup
sm_determinism --> logical_clock
plan --> cmd_kind
plan --> snapshot
plan --> perf_stance
perf_stance --> reads
plan --> viz
viz --> hosting
viz --> lock_viz
lock_viz --> fencing_demo
plan --> plan_md
plan --> etcd
step_pattern --> message_type
message_type --> matchindex
step_pattern --> ready_type
ready_type --> hardstate
ready_type --> sync
ready_type --> unst
message_type --> transport_send
testing_pyramid --> fakes_vs_real
fakes_vs_real --> fake_storage
return --> decisions_log
portfolio_piece --> blog_views
blog_views --> entry_format
blog_views --> tags
blog_views --> renderer_v1
blog_views --> three_tabs
three_tabs --> no_dup
three_tabs --> zoom
three_tabs --> hash_state
testing_pyramid --> dst_vs_tla
blog_views --> tags_created
step_pattern --> foundation_commits
return --> strict
three_tabs --> overlay
overlay --> parser
entry_format --> entry_ids
overlay --> mermaid_only
overlay --> full_bleed
overlay --> dummy_page
three_tabs --> timeline_tab
timeline_tab --> seq_order
seq_order --> label_hygiene
orig_build ~~~ hex
hex ~~~ event_loop
event_loop ~~~ return
return ~~~ portfolio_piece
portfolio_piece ~~~ testing_pyramid
testing_pyramid ~~~ plan
plan ~~~ timeout
timeout ~~~ step_pattern
step_pattern ~~~ types
types ~~~ unst
unst ~~~ three_tabs
Click a node to zoom to it, or step through in implementation order with the arrows. Trunk nodes link into the write-up; side branches show their own entry; milestones show what branches from them.
Ports first
I started with the boundary, not the algorithm. Three interfaces in raft/ports.go: Transport, Storage, StateMachine. Everything that touches a network, a disk, or an application lives behind one of them, and the core never imports anything else.
One goroutine owns the state
The first three real commits after the scaffold were each a concurrency fix: a deadlock, a race on the role field, a ticker race. The lesson landed the way it lands for everyone: serialize state changes through a single event loop and let workers signal it over channels.
It is a portfolio piece, and that is fine
After a three-month gap I spent a day trying to find a use case that demanded Raft at the scale of one person’s hardware. There isn’t one. Raft exists because of scale, adversaries, or geography, and none of those shrink to a single owner.
Testing: fakes do the work, real chaos calibrates them
Deterministic simulation is the primary explorer. Property tests sit alongside it. Real-network chaos comes later and is used to check the fakes, not to find bugs first.
Three bugs, three commits
Before changing any architecture: the election timeout is re-randomized at construction, at each election, and on step-down. A dead field went. AppendEntries handles its storage errors with a rule I want to keep everywhere: a read that fails means the log has a shape we did not expect, so return Success:false; a write that fails would silently violate Log Matching, so panic.
The pivot: a passive core
Pull every goroutine out of raft/. The node becomes a struct with five methods (Tick, Step, Propose, Ready, Advance) and whoever calls them decides when.
Unstable entries, and a reversal
For a few hours the node wrote log entries straight to storage inside Step. Then I reversed it: new entries wait in an in-memory buffer, Ready exposes them, the runner persists them and calls Advance. The node performs zero I/O.
Every decision, adopted or not, is logged to a local DECISIONS.md as it happens. This write-up follows the trunk; the tree tab holds every branch; the timeline is both by date. They are three renderings of one data file.