The Commit Interlock
Replicated is not committed, and the gap between them is where Raft's safety actually lives.
What you're wiring up
An entry is committed once the leader has seen it stored on a majority. The promise attached to that word is enormous: a committed entry will be present in the log of every future leader, forever. Two rules are what make the promise true, and both get added on this sheet.
The first is the election restriction. A voter refuses any candidate whose log is less up-to-date than its own, comparing the last entry's term first and using length only as a tiebreak. Since a committed entry sits on a majority, and any winner needs a majority, the winner always already holds it.
The second is subtler and is the paper's Figure 8: a leader may only count replicas of entries from its own term toward commitment. Older entries ride along and commit as a side effect. Drop that guard and there is a specific crash-and-election sequence where the cluster acknowledges a write and then loses it. The harness replays exactly that sequence, a hundred times, with random seeds. This is the sheet where your Raft becomes correct rather than merely functional.
# voter asks: is your log at least as up-to-date as mine? mine: lastLogTerm 4, lastLogIndex 9 candidate: lastLogTerm 5, lastLogIndex 2 → grant (higher term wins) candidate: lastLogTerm 4, lastLogIndex 9 → grant (tie, equal length) candidate: lastLogTerm 4, lastLogIndex 8 → refuse (same term, shorter)
Assembly steps
hint
A condition variable signalled on "commitIndex changed", driving a loop over lastApplied+1 through commitIndex, is the clean shape.
hint
Never send on applyCh while holding the main mutex. That deadlock will find you.
hint
That term guard is the Figure 8 defense. When this sheet's nastiest test reports a lost committed write, go re-read section 5.4.2.
hint
That minimum is not a formality. A follower must never mark entries committed that it has not received yet.
hint
Compare the last entry's term first, length only as a tiebreak. Comparing lengths first is the wrong-but-plausible reading the harness specifically traps.
Go deeper (after it passes)
This is the capstone practice for DDIA's Consistency and Consensus module — read it now that you have had to implement the guarantees instead of just naming them. The applier loop is also a textbook condition-variable pattern, worth revisiting the concurrency module's CV section for.