A successful pre-vote predicts a successful election
ProvedOpenSearchPreVote.preVote_then_electionLet csc be the coordination state of a node c, and node n the coordination state in which each node n handles c's start-join request. Let r be a round reachable under the default election strategy in which a response resp can be delivered from n only if (node n).localNode = n and preVoteResponseOf (node n) = resp: the senders' current term and accepted term and version have not changed since they answered. Suppose r.clusterState = csc.lastAcceptedState, r.localNode = csc.localNode, and r.electionStarted = true. Let maxTermSeen be at least the current term of every recorded response (updateMaxTermSeen makes it so) and T = startElectionTerm csc.currentTerm maxTermSeen, that is max(csc.currentTerm, maxTermSeen) + 1. Let csc.handleStartJoin ⟨csc.localNode, T⟩ = .ok (csc₁, j₀), and let js be any list of joins whose terms are all at most T. Then:
- every recorded sender
p.1accepts the request:(node p.1).handleStartJoin ⟨csc.localNode, T⟩succeeds; - if every join the recorded senders produce from that request occurs in
js, then(handleJoins ElectionStrategy.DEFAULT_INSTANCE csc₁ js).electionWon = true.
The senders' joins may arrive in any order, interleaved with any other joins of term at most T, accepted or rejected (a rejected join leaves the state unchanged), and c handles nothing else in between. The bound on the join terms restricts the statement to the joins Coordinator.handleJoin handles without first moving c to a higher term (CO:1269), which would discard the votes.
import Definitions.Def_OpenSearchPreVote open OpenSearchCoordination OpenSearchPreVote
theorem OpenSearchPreVote.preVote_then_election {V : Type} (csc : CoordinationState V)
(node : DiscoveryNode → CoordinationState V) (r : PreVotingRound V)
(hr : RoundReachable ElectionStrategy.DEFAULT_INSTANCE
(fun n resp => (node n).localNode = n ∧ preVoteResponseOf (node n) = resp) r)
(hstate : r.clusterState = csc.lastAcceptedState) (hlocal : r.localNode = csc.localNode)
(hstarted : r.electionStarted = true) (maxTermSeen : Nat)
(hseen : ∀ p ∈ r.preVotesReceived, p.2.currentTerm ≤ maxTermSeen)
(csc₁ : CoordinationState V) (j₀ : Join)
(hsj : csc.handleStartJoin ⟨csc.localNode, startElectionTerm csc.currentTerm maxTermSeen⟩ = .ok (csc₁, j₀))
(js : List Join) (hjs : ∀ j ∈ js, j.term ≤ startElectionTerm csc.currentTerm maxTermSeen) :
(∀ p ∈ r.preVotesReceived, ∃ cs' j,
(node p.1).handleStartJoin ⟨csc.localNode, startElectionTerm csc.currentTerm maxTermSeen⟩ = .ok (cs', j)) ∧
((∀ p ∈ r.preVotesReceived, ∀ cs' j,
(node p.1).handleStartJoin ⟨csc.localNode, startElectionTerm csc.currentTerm maxTermSeen⟩ = .ok (cs', j) →
j ∈ js) →
(handleJoins ElectionStrategy.DEFAULT_INSTANCE csc₁ js).electionWon = true) := by sorryRead-back
What the Lean code literally says, in plain math · claude-opus-5-5 (independent sub-agent, blind: Lean code + auditor instructions only)
Read-back: OpenSearchPreVote.preVote_then_election
Notions used
Discovery nodes, configurations, cluster states.
- A discovery node is a pair: a node id and a Boolean flag ("cluster-manager-eligible"). Equality compares both components.
- A voting configuration is a strictly increasing list of node ids. means that strictly more than half of 's ids occur in the list ; the empty configuration never has a quorum.
- A cluster state has a term , a version and a metadata version in , a last committed configuration , a last accepted configuration , and other fields.
- is the metadata version if , and the version otherwise.
Coordination states. A coordination state has these fields:
- a local node and a current term ;
- a last accepted state ;
- a vote collection (a list of node ids);
- Booleans and ;
- publication fields.
Its pre-vote response is
Start-join. A start-join request has a source node and a term. behaves as follows:
- If , it is an error.
- Otherwise it succeeds with .
- is with current term , startedJoinSinceLastReboot true, electionWon false, empty join votes, empty publish votes, last published version , and last published configuration . The local node and the last accepted state are unchanged.
- is the join , read as (source, target, term, last accepted term, last accepted version).
Join handling with the default strategy. rejects with an error, leaving no state, if any of these holds:
- ;
- startedJoinSinceLastReboot is false;
- ;
- the terms are equal and ;
- is empty.
Otherwise it adds to the join votes, if the source is cluster-manager-eligible and not already present. It then sets
The default strategy's extra test is constantly true. Under certain conditions the last published version also changes. The join's target is never checked.
applies to the joins of from left to right, keeping the state unchanged on each rejection.
Election term. .
Rounds.
- A round has a cluster state , a local node , a list of (sender, response) pairs, and Booleans and .
- is the least predicate containing every , closed under handling (with an arbitrary local response) any with , and closed under closing.
- Handling an open round stores a response only if it is no fresher than the round's cluster state, that is, lexicographically . It replaces any earlier entry from an equal sender.
- It sets once the eligible senders' ids form an election quorum, which under the default strategy means majorities of both and .
1. Declaration
Binders and hypotheses, in order. Write .
- : an implicit type.
- : a coordination state over (the candidate).
- : a function from discovery nodes to coordination states over (every node's coordination state).
- : a round over .
- : , where . A delivery from must be the genuine pre-vote response of 's coordination state, and that state's local node must be itself (same id and flag).
- : .
- : .
- : .
- .
- : .
- : a coordination state; : a join.
- : .
- : a list of joins.
- : .
Conclusion. (A) (B), where:
-
(A) .
By the definition of , this says for every recorded pre-voter: every recorded pre-voter accepts the candidate's start-join request in term .
-
(B) .
- The antecedent: for every recorded pre-voter, the join it produces in answer to is an element of . That join is .
- The consequent: after the candidate's post-start-join state handles the joins of in order, its electionWon flag is true.
- starts with electionWon false, and only an accepted join sets the flag. So the consequent means: some join of was accepted, and the last accepted one left join votes with majorities of both configurations of .
What amounts to. always holds, so always succeeds here. is no restriction on . It only names the result:
- is with current term , startedJoinSinceLastReboot true, electionWon false, empty vote collections, last published version and last published configuration ;
- .
2. What is not constrained
-
Strategy. The election strategy is fixed to
DEFAULT_INSTANCE, both in the round and in . Nothing is claimed for other strategies. -
Frozen states. There is a single function . Each pre-voter's coordination state is the same when it answers the pre-vote and when it receives the start-join.
-
The candidate's own entry. is not tied to . If the candidate's own pre-vote is recorded, it comes from , which may be a different state from . The candidate's own join is not required to be in .
-
Delivery is more permissive than Java. does not involve the pre-vote request handler. Recorded pre-voters need not be healthy, and need not be leaderless or following the candidate. So the theorem covers rounds that Java's request handling would not produce.
-
. Under the default strategy the local node is only passed to the strategy's extra test, which ignores it. So appears not to be needed for the conclusion. This is my reading, not a proof.
-
. It is an assumption standing in for the max-term bookkeeping. Java raises
maxTermSeenwith each response's term (PreVoteCollector.java:231), but no definition here tracks it. It is required only for the recorded entries, not for dropped or fresher responses. -
. The candidate's last accepted state at election time must equal the round's cluster state, that is, unchanged since the round started. The candidate's current term is otherwise free.
-
Java's election guards. Java's
startElectionchecks modeCANDIDATEandlocalNodeMayWinElection(Coordinator.java:587–591). Neither is a hypothesis; the definitionnodeMayWinElectionis not used. Likewise, the actual sending of start-join requests to the discovered nodes is not modelled. The theorem directly assumes is applied. -
The list . Beyond , is arbitrary: extra joins with any source, any target and any term , in any order. The conclusion asserts electionWon regardless. Joins with term are rejected by guard 1, so they have no effect. Joins' targets are never checked.
-
The antecedent of (B). It demands the joins of all recorded pre-voters, including ineligible ones whose joins add no vote. That is stronger than necessary, and harmless.
-
Scope of the conclusion. It is about the candidate's coordination-state flag electionWon. It says nothing about Coordinator mode (becoming leader) or about publishing a first state.
-
Consequences of . With , forces a non-empty recorded list and non-empty configurations, because no vote set is a quorum of the empty configuration.
-
Vacuity. None. I checked a witness by evaluation:
- a one-node configuration in both slots, state term , versions ;
- with local node and current term , and with local node ;
- one delivery from starts the election;
- , , and yields electionWon true.
With the antecedent of (B) fails, because the recorded pre-voter's join is missing, so (B) holds only vacuously there. The antecedent is what makes (B) informative.
3. Plain-language summary
Take a pre-voting round under the default election strategy. Its recorded pre-votes are the genuine responses of the senders' coordination states, and the round has started an election. The candidate's accepted state is the round's state, and it sends a start-join in term , where bounds every recorded pre-voter's term. Then every recorded pre-voter accepts that start-join. And if the candidate then handles a list of joins (all of term at most that term) that contains every recorded pre-voter's resulting join, its coordination state has won the election. This assumes the pre-voters' states do not change between the pre-vote and the start-join.
Confirmed by the mission captain (proposal self-audit).
Confirmed by the moderator at approval.