Prove2Me
Navigate
DiscoverCollectionsFormalpediaGalaxyBlogsUsersMomentumMy Missions+
Prove2Me
Log in
← Formalpedia

OpenSearch cluster coordination in a stable period: the steps and executions of a stable election and of a stable publication

Definition
OpenSearchProgress

by pitimespi · Nov 1, 2026 · Mathlib 0df444a (Lean v4.33.1)

cluster-coordinationconsensuselasticsearchformal-verificationleader-electionlivenessmessage-passingopensearchpartial-synchronypaxosprogresspublicationraftzen2

Stable periods of the cluster model of the OpenSearchSafety mission (OpenSearch revision b2a54ba5c0defba3d1d20423e911d4bf920bab19), whose Params, Cluster, Message, Step, RestartState, Reachable and Committed are used as they are, over the per-node CoordinationState of the OpenSearchCoordination mission. A stable period is a window in which one node runs for election (or publishes) and the steps excluded below do not occur, while every other step of the cluster remains possible. Each stable step is one of the nine kinds of Step (bootstrap, startJoin, join, clientValue, publish, respond, commit, restart, restartAny), with the same hypotheses and the same effect, and possibly an extra hypothesis.

ElectionStep P c t Q kt kv W W' — a step of a stable election period for the candidate c in term t with voters Q (a list of node ids), where (kt, kv) stands for the term and version of c's accepted state when the period starts:

  • bootstrap at a node n only if n ≠ c and n ∉ Q;
  • startJoin (any node handling a request from a cluster-manager-eligible src for a term t') only if t' < t, or src = c and t' = t: no start-join request in a term at least t other than c's own request for t;
  • join, respond and commit as in Step;
  • publish (a node n handling a sent publication of a state s) only if, when n = c or n ∈ Q, s is no fresher than (kt, kv): s.term < kt, or s.term = kt and s.version ≤ kv (c and the voters do not accept a stale publication of a former cluster-manager that is fresher than c's state);
  • restart of a cluster-manager-eligible node n only if n ≠ c and n ∉ Q; restartAny (a node that is not cluster-manager-eligible) as in Step;
  • no clientValue step: no new publication.

ElectionExec P c t Q kt kv W W' — W' is reached from W by finitely many, possibly zero, ElectionSteps.

PublicationStep P c t Q W W' — a step of a stable publication period for the cluster-manager c of term t with acknowledging nodes Q:

  • startJoin at a node n with n = c or n ∈ Q only for terms t' ≤ t (at other nodes, any term);
  • clientValue only at nodes n ≠ c: c does not publish again;
  • restart of a cluster-manager-eligible node n only if n ≠ c;
  • bootstrap, join, publish, respond, commit and restartAny as in Step.

PublicationExec P c t Q W W' — W' is reached from W by finitely many, possibly zero, PublicationSteps.

Formalization Note. Every stable step is a Step of the safety model, so a stable execution from a reachable cluster stays reachable and the safety theorems apply along it. Messages are still never removed: within a stable period a sent message may be handled any number of times, in any order, or never; the four universal theorems (election_vote_accepted, election_won, publication_acks_accepted, publication_committed) hold for every finite stable execution, that is, every prefix of a longer one, and the two existence theorems (election_possible, publication_possible) exhibit one; there is no fairness assumption. A stable period is a hypothesis of the theorems, not a property of the Java system: nothing here says that a real cluster reaches such a period, for how long it lasts, or how long its steps take. The parameters (kt, kv) of the election period are instantiated in the theorems with c's last accepted term and version at the start. A voter may be the candidate itself, and Q may contain c in a publication period. Only the OpenSearchSafety definitions are imported (Lean core).

Definition code
import Definitions.Def_OpenSearchSafety

/-!
# OpenSearch cluster coordination: progress under stability

Safety holds in every execution. Progress cannot: in an asynchronous network a message may never be delivered, and
competing candidates can keep raising terms (this is what pre-voting and the randomized election scheduler mitigate).
The theorems of this mission state progress for the executions of a *stable period*: a window in which one node runs
for election (or publishes) and none of the steps that the relations below exclude occurs, while every other step of
the cluster remains possible — deliveries in any order, duplicates, other nodes' joins, acceptances and commits,
restarts of other nodes. Under their hypotheses, they show that in every such execution the candidate, once in the
election term, accepts and counts every voter's join for that term, and every acknowledging node in the
cluster-manager's term has acknowledged its publication or accepts it, while the cluster-manager accepts and counts
every acknowledgement of it sent by an acknowledging node; that once all of a quorum's votes (acknowledgements) are
counted, the election is won (the publication committed); and that such executions exist. No theorem states that the
start-joins an election or a publication needs stay enabled.
-/

namespace OpenSearchProgress

open OpenSearchCoordination OpenSearchSafety

variable {V : Type}

/-- A step of a stable election period for candidate `c` in term `t` with voters `Q`, `(kt, kv)` being the term and
version of `c`'s accepted state when the period starts: any step of the cluster except a start-join in a term at least
`t` other than `c`'s own request for `t`, any new publication, a bootstrap of `c` or of a voter, a restart of `c` or of
a voter that is cluster-manager-eligible (`restartAny`, the restart of a node that is not eligible, is not restricted),
and the acceptance by `c` or a voter of a publication fresher than `(kt, kv)` (a stale publication of a former
cluster-manager arriving during the election). -/
inductive ElectionStep (P : Params) (c : NodeId) (t : Nat) (Q : List NodeId) (kt : Nat) (kv : Int) :
    Cluster V → Cluster V → Prop where
  | bootstrap (W : Cluster V) (n : NodeId) (X : VotingConfiguration) (s : ClusterState V)
      (cs' : CoordinationState V) (hX : P.initConfs X) (hnq : n ≠ c ∧ n ∉ Q)
      (hcontract : (W.node n).initialStateContract s)
      (hconf : s.lastCommittedConfiguration = X ∧ s.lastAcceptedConfiguration = X)
      (h : (W.node n).setInitialState s = .ok cs') :
      ElectionStep P c t Q kt kv W (W.set n cs')
  | startJoin (W : Cluster V) (n src : NodeId) (t' : Nat) (cs' : CoordinationState V) (j : Join)
      (hsrc : P.eligible src = true) (hstable : t' < t ∨ (src = c ∧ t' = t))
      (h : (W.node n).handleStartJoin ⟨P.dn src, t'⟩ = .ok (cs', j)) :
      ElectionStep P c t Q kt kv W ((W.set n cs').send (.join j))
  | join (W : Cluster V) (n : NodeId) (j : Join) (cs' : CoordinationState V) (b : Bool)
      (hsent : W.sent (.join j)) (hdest : j.targetNode.id = n)
      (h : (W.node n).handleJoin P.strategy j = .ok (cs', b)) :
      ElectionStep P c t Q kt kv W (W.set n cs')
  | publish (W : Cluster V) (n src : NodeId) (s : ClusterState V) (pt : Nat) (pv : Int)
      (cs' : CoordinationState V) (r : PublishResponse) (hsent : W.sent (.publishRequest src s pt pv))
      (hfresh : n = c ∨ n ∈ Q → s.term < kt ∨ (s.term = kt ∧ s.version ≤ kv))
      (h : (W.node n).handlePublishRequest ⟨s⟩ = .ok (cs', r)) :
      ElectionStep P c t Q kt kv W ((W.set n cs').send (.publishResponse n src r))
  | respond (W : Cluster V) (n src : NodeId) (r : PublishResponse) (cs' : CoordinationState V)
      (o : Option ApplyCommitRequest) (hsent : W.sent (.publishResponse src n r))
      (h : (W.node n).handlePublishResponse (P.dn src) r = .ok (cs', o)) :
      ElectionStep P c t Q kt kv W (match o with
        | none => W.set n cs'
        | some a => (W.set n cs').send (.applyCommit a))
  | commit (W : Cluster V) (n : NodeId) (a : ApplyCommitRequest) (cs' : CoordinationState V)
      (hsent : W.sent (.applyCommit a)) (h : (W.node n).handleCommit a = .ok cs') :
      ElectionStep P c t Q kt kv W (W.set n cs')
  | restart (W : Cluster V) (n : NodeId) (s' : ClusterState V) (hn : P.eligible n = true)
      (hnq : n ≠ c ∧ n ∉ Q) (hs : RestartState (W.node n).lastAcceptedState s') :
      ElectionStep P c t Q kt kv W (W.set n (CoordinationState.init (W.node n).localNode (W.node n).currentTerm s'))
  | restartAny (W : Cluster V) (n : NodeId) (t' : Nat) (s' : ClusterState V) (hn : P.eligible n = false) :
      ElectionStep P c t Q kt kv W (W.set n (CoordinationState.init (W.node n).localNode t' s'))

end OpenSearchProgress

namespace OpenSearchProgress

open OpenSearchCoordination OpenSearchSafety

variable {V : Type}

/-- The executions of a stable election period: finite sequences of `ElectionStep`s. -/
inductive ElectionExec (P : Params) (c : NodeId) (t : Nat) (Q : List NodeId) (kt : Nat) (kv : Int) :
    Cluster V → Cluster V → Prop where
  | refl (W : Cluster V) : ElectionExec P c t Q kt kv W W
  | step (W W' W'' : Cluster V) : ElectionExec P c t Q kt kv W W' → ElectionStep P c t Q kt kv W' W'' →
      ElectionExec P c t Q kt kv W W''

/-- A step of a stable publication period for the cluster-manager `c` of term `t` with acknowledging nodes `Q`: any step
of the cluster except a start-join in a term above `t` at `c` or at a node of `Q`, a further publication by `c`, and a
restart of `c` if it is cluster-manager-eligible (`restartAny`, the restart of a node that is not eligible, is not
restricted). -/
inductive PublicationStep (P : Params) (c : NodeId) (t : Nat) (Q : List NodeId) : Cluster V → Cluster V → Prop where
  | bootstrap (W : Cluster V) (n : NodeId) (X : VotingConfiguration) (s : ClusterState V)
      (cs' : CoordinationState V) (hX : P.initConfs X)
      (hcontract : (W.node n).initialStateContract s)
      (hconf : s.lastCommittedConfiguration = X ∧ s.lastAcceptedConfiguration = X)
      (h : (W.node n).setInitialState s = .ok cs') :
      PublicationStep P c t Q W (W.set n cs')
  | startJoin (W : Cluster V) (n src : NodeId) (t' : Nat) (cs' : CoordinationState V) (j : Join)
      (hsrc : P.eligible src = true) (hstable : (n = c ∨ n ∈ Q) → t' ≤ t)
      (h : (W.node n).handleStartJoin ⟨P.dn src, t'⟩ = .ok (cs', j)) :
      PublicationStep P c t Q W ((W.set n cs').send (.join j))
  | join (W : Cluster V) (n : NodeId) (j : Join) (cs' : CoordinationState V) (b : Bool)
      (hsent : W.sent (.join j)) (hdest : j.targetNode.id = n)
      (h : (W.node n).handleJoin P.strategy j = .ok (cs', b)) :
      PublicationStep P c t Q W (W.set n cs')
  | clientValue (W : Cluster V) (n : NodeId) (s : ClusterState V) (cs' : CoordinationState V)
      (p : PublishRequest V) (hn : n ≠ c) (hcontract : (W.node n).clientValueContract s)
      (h : (W.node n).handleClientValue s = .ok (cs', p)) :
      PublicationStep P c t Q W ((W.set n cs').send (.publishRequest n p.acceptedState (W.node n).lastAcceptedTerm
        (W.node n).lastAcceptedVersion))
  | publish (W : Cluster V) (n src : NodeId) (s : ClusterState V) (pt : Nat) (pv : Int)
      (cs' : CoordinationState V) (r : PublishResponse) (hsent : W.sent (.publishRequest src s pt pv))
      (h : (W.node n).handlePublishRequest ⟨s⟩ = .ok (cs', r)) :
      PublicationStep P c t Q W ((W.set n cs').send (.publishResponse n src r))
  | respond (W : Cluster V) (n src : NodeId) (r : PublishResponse) (cs' : CoordinationState V)
      (o : Option ApplyCommitRequest) (hsent : W.sent (.publishResponse src n r))
      (h : (W.node n).handlePublishResponse (P.dn src) r = .ok (cs', o)) :
      PublicationStep P c t Q W (match o with
        | none => W.set n cs'
        | some a => (W.set n cs').send (.applyCommit a))
  | commit (W : Cluster V) (n : NodeId) (a : ApplyCommitRequest) (cs' : CoordinationState V)
      (hsent : W.sent (.applyCommit a)) (h : (W.node n).handleCommit a = .ok cs') :
      PublicationStep P c t Q W (W.set n cs')
  | restart (W : Cluster V) (n : NodeId) (s' : ClusterState V) (hn : P.eligible n = true) (hnc : n ≠ c)
      (hs : RestartState (W.node n).lastAcceptedState s') :
      PublicationStep P c t Q W (W.set n (CoordinationState.init (W.node n).localNode (W.node n).currentTerm s'))
  | restartAny (W : Cluster V) (n : NodeId) (t' : Nat) (s' : ClusterState V) (hn : P.eligible n = false) :
      PublicationStep P c t Q W (W.set n (CoordinationState.init (W.node n).localNode t' s'))

/-- The executions of a stable publication period. -/
inductive PublicationExec (P : Params) (c : NodeId) (t : Nat) (Q : List NodeId) : Cluster V → Cluster V → Prop where
  | refl (W : Cluster V) : PublicationExec P c t Q W W
  | step (W W' W'' : Cluster V) : PublicationExec P c t Q W W' → PublicationStep P c t Q W' W'' →
      PublicationExec P c t Q W W''

end OpenSearchProgress
Source
OpenSearch b2a54ba5c0defba3d1d20423e911d4bf920bab19: Coordinator (handlePublishRequest lines 454-526 with ensureTermAtLeast at 512, startElection 583-602, ensureTermAtLeast 635-641, joinLeaderInTerm 643-657, handleJoin 1267-1287 with handleJoinIgnoringExceptions 1289-1299, publish 1350 with the handleClientValue call at 1404, CoordinatorPublication.handlePublishResponse 1871-1875): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/Coordinator.java; CoordinationState (isPublishQuorum lines 156-158, handleStartJoin 213-255, handleJoin 264-339, handleClientValue 348-407, handlePublishRequest 416-460, handlePublishResponse 471-515, handleCommit 523-573) as transcribed by the OpenSearchCoordination mission: https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/CoordinationState.java; the cluster model, the safety vocabulary and the inductive invariant of the OpenSearchSafety mission (Step, Reachable, Committed, Inv, reachable_inv); for why progress needs a stable period: M. J. Fischer, N. A. Lynch, M. S. Paterson, Impossibility of distributed consensus with one faulty process, J. ACM 32(2), 1985, and C. Dwork, N. Lynch, L. Stockmeyer, Consensus in the presence of partial synchrony, J. ACM 35(2), 1988
Read-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: Def_OpenSearchProgress.lean

The file declares four inductive relations in the namespace OpenSearchProgress and nothing else: ElectionStep, ElectionExec, PublicationStep, PublicationExec. Each relates two clusters (Cluster V → Cluster V → Prop) and is indexed by fixed parameters. The payload type V is an implicit parameter throughout. The file imports Def_OpenSearchSafety, which imports Def_OpenSearchCoordination. The imported notions are expanded first, so that each declaration can be read on its own. The module docstring describes "stable periods" for an election and for a publication; the last section compares it with the terms.

Imported notions, expanded

Notation used in this read-back

For a node state σ\sigmaσ (a CoordinationState V), write:

  • ln(σ)\mathrm{ln}(\sigma)ln(σ) for localNode, cur(σ)\mathrm{cur}(\sigma)cur(σ) for currentTerm (a natural number) and acc(σ)\mathrm{acc}(\sigma)acc(σ) for lastAcceptedState (a cluster state);
  • lat(σ)=acc(σ).term\mathrm{lat}(\sigma) = \mathrm{acc}(\sigma).\mathrm{term}lat(σ)=acc(σ).term and lav(σ)=acc(σ).version\mathrm{lav}(\sigma) = \mathrm{acc}(\sigma).\mathrm{version}lav(σ)=acc(σ).version (an integer). lavm(σ)\mathrm{lavm}(\sigma)lavm(σ) is lastAcceptedVersionOrMetadataVersion: it equals acc(σ).metadataVersion\mathrm{acc}(\sigma).\mathrm{metadataVersion}acc(σ).metadataVersion if acc(σ).term=0\mathrm{acc}(\sigma).\mathrm{term} = 0acc(σ).term=0, and acc(σ).version\mathrm{acc}(\sigma).\mathrm{version}acc(σ).version otherwise;
  • lcc(σ)\mathrm{lcc}(\sigma)lcc(σ) and lac(σ)\mathrm{lac}(\sigma)lac(σ) for the last committed and last accepted configuration of acc(σ)\mathrm{acc}(\sigma)acc(σ);
  • jv(σ)\mathrm{jv}(\sigma)jv(σ) and pv(σ)\mathrm{pv}(\sigma)pv(σ) for the lists of node ids of joinVotes and publishVotes;
  • sj(σ)\mathrm{sj}(\sigma)sj(σ) for startedJoinSinceLastReboot and won(σ)\mathrm{won}(\sigma)won(σ) for electionWon (both Booleans), lpv(σ)\mathrm{lpv}(\sigma)lpv(σ) for lastPublishedVersion (an integer) and lpc(σ)\mathrm{lpc}(\sigma)lpc(σ) for lastPublishedConfiguration.

"σ\sigmaσ with f:=xf := xf:=x" means σ\sigmaσ with field fff replaced and every other field unchanged. For keys, meaning pairs of a natural-number term and an integer version, (a,b)⪯(a′,b′)(a, b) \preceq (a', b')(a,b)⪯(a′,b′) means a<a′∨(a=a′∧b≤b′)a < a' \lor (a = a' \land b \le b')a<a′∨(a=a′∧b≤b′). This is the lexicographic order; the Safety file names it KeyLE, but the Progress file writes it out inline.

Node ids, discovery nodes, parameters, strategies

  • A node id is a natural number. Only equality is used. A discovery node ddd is a pair of an id d.idd.\mathrm{id}d.id and a Boolean d.isClusterManagerNoded.\mathrm{isClusterManagerNode}d.isClusterManagerNode.

  • The parameters PPP consist of:

    • a Boolean function P.eligibleP.\mathrm{eligible}P.eligible on node ids (cluster-manager eligibility);
    • an arbitrary predicate P.initConfsP.\mathrm{initConfs}P.initConfs on voting configurations (the configurations a node may bootstrap with);
    • an election strategy P.strategyP.\mathrm{strategy}P.strategy.

    We write P.dn(n)=⟨n,P.eligible(n)⟩P.\mathrm{dn}(n) = \langle n, P.\mathrm{eligible}(n)\rangleP.dn(n)=⟨n,P.eligible(n)⟩.

  • An election strategy is a single Boolean function satisfiesAdditionalQuorumConstraints of seven arguments: the local node, current term, accepted term, accepted version, last committed configuration, last accepted configuration and vote collection. ElectionStrategy.DEFAULT_INSTANCE returns true for all arguments.

  • P.InitCompatibleP.\mathrm{InitCompatible}P.InitCompatible is used as a hypothesis by every theorem. It says: for all configurations X,YX, YX,Y with P.initConfs(X)P.\mathrm{initConfs}(X)P.initConfs(X) and P.initConfs(Y)P.\mathrm{initConfs}(Y)P.initConfs(Y), and all lists q1,q2q_1, q_2q1​,q2​ of ids whose members are all eligible, if Quorum(X,q1)\mathrm{Quorum}(X, q_1)Quorum(X,q1​) and Quorum(Y,q2)\mathrm{Quorum}(Y, q_2)Quorum(Y,q2​) then some xxx belongs to both q1q_1q1​ and q2q_2q2​. It holds vacuously if P.initConfsP.\mathrm{initConfs}P.initConfs admits nothing. It also holds, for example, if P.initConfsP.\mathrm{initConfs}P.initConfs admits exactly one configuration, because any two majorities of one set intersect.

Voting configurations, quorums, vote collections

  • A voting configuration XXX is a strictly increasing list of ids X.idsX.\mathrm{ids}X.ids. It is therefore duplicate-free, and equality of configurations is equality of the id lists, i.e. of the id sets. EMPTY\mathrm{EMPTY}EMPTY has no ids. "XXX is empty" (isEmpty) means X.ids=[ ]X.\mathrm{ids} = [\,]X.ids=[].
  • For a list LLL of ids, Quorum(X,L)\mathrm{Quorum}(X, L)Quorum(X,L) (Lean X.hasQuorum L = true) means
2⋅∣{x∈X.ids:x∈L}∣>∣X.ids∣.2 \cdot \big|\{x \in X.\mathrm{ids} : x \in L\}\big| > |X.\mathrm{ids}|.2⋅​{x∈X.ids:x∈L}​>∣X.ids∣.

Properties:

  • it is false whenever XXX is empty, and false whenever L=[ ]L = [\,]L=[];
  • it is monotone in LLL: if every member of LLL is in L′L'L′, then Quorum(X,L)→Quorum(X,L′)\mathrm{Quorum}(X, L) \to \mathrm{Quorum}(X, L')Quorum(X,L)→Quorum(X,L′);
  • duplicates in LLL, and members of LLL outside XXX, are irrelevant.
  • A vote collection is a list of ids. addVote(v,d)\mathrm{addVote}(v, d)addVote(v,d) returns (d.id::v, true)(d.\mathrm{id} :: v,\ \mathrm{true})(d.id::v, true) if d.isClusterManagerNode=trued.\mathrm{isClusterManagerNode} = \mathrm{true}d.isClusterManagerNode=true and d.id∉vd.\mathrm{id} \notin vd.id∈/v, and (v,false)(v, \mathrm{false})(v,false) otherwise. So after addVote(v,d)\mathrm{addVote}(v, d)addVote(v,d), the id d.idd.\mathrm{id}d.id is in the collection exactly when it already was, or ddd is flagged cluster-manager. isQuorum(v,X)=Quorum(X,v)\mathrm{isQuorum}(v, X) = \mathrm{Quorum}(X, v)isQuorum(v,X)=Quorum(X,v).

Cluster states and messages

  • A cluster state sss has the following fields:

    • s.term∈Ns.\mathrm{term} \in \mathbb{N}s.term∈N, s.version∈Zs.\mathrm{version} \in \mathbb{Z}s.version∈Z and s.metadataVersion∈Zs.\mathrm{metadataVersion} \in \mathbb{Z}s.metadataVersion∈Z;
    • two configurations, s.lccs.\mathrm{lcc}s.lcc (last committed) and s.lacs.\mathrm{lac}s.lac (last accepted);
    • an optional natural number s.clusterManagerNodes.\mathrm{clusterManagerNode}s.clusterManagerNode;
    • two Booleans, clusterUUIDKnown and clusterUUIDCommitted;
    • a payload in VVV.
  • markCommitted(s)\mathrm{markCommitted}(s)markCommitted(s) works in two steps:

    1. if s.lac≠s.lccs.\mathrm{lac} \ne s.\mathrm{lcc}s.lac=s.lcc, it sets s.lcc:=s.lacs.\mathrm{lcc} := s.\mathrm{lac}s.lcc:=s.lac;
    2. then, if clusterUUIDKnown holds and clusterUUIDCommitted does not, it sets clusterUUIDCommitted := true.

    Term, version, metadata version, lac\mathrm{lac}lac and payload are unchanged.

  • The message payloads are:

    • A join jjj has a source and a target discovery node (j.sourceNodej.\mathrm{sourceNode}j.sourceNode, j.targetNodej.\mathrm{targetNode}j.targetNode), a term j.term∈Nj.\mathrm{term} \in \mathbb{N}j.term∈N, and the voter's last accepted term j.lastAcceptedTerm∈Nj.\mathrm{lastAcceptedTerm} \in \mathbb{N}j.lastAcceptedTerm∈N and version j.lastAcceptedVersion∈Zj.\mathrm{lastAcceptedVersion} \in \mathbb{Z}j.lastAcceptedVersion∈Z. The version is filled by handleStartJoin with lavm\mathrm{lavm}lavm, so for a term-0 accepted state it is the metadata version.
    • A publish request is ⟨s⟩\langle s\rangle⟨s⟩, a single cluster state.
    • A publish response is ⟨τ,v⟩\langle \tau, v\rangle⟨τ,v⟩, a term and a version.
    • An apply-commit request is ⟨d,τ,v⟩\langle d, \tau, v\rangle⟨d,τ,v⟩: a source discovery node, a term and a version.
  • A message is one of four kinds:

    • join(j)\mathrm{join}(j)join(j);
    • publishRequest(src,s,pt,pv)\mathrm{publishRequest}(src, s, pt, pv)publishRequest(src,s,pt,pv): a request from node srcsrcsrc carrying state sss and a "ghost" parent key (pt,pv)(pt, pv)(pt,pv);
    • publishResponse(src,dst,r)\mathrm{publishResponse}(src, dst, r)publishResponse(src,dst,r): a response from srcsrcsrc to dstdstdst;
    • applyCommit(a)\mathrm{applyCommit}(a)applyCommit(a).

    Publish requests and commits carry no addressee. A join's addressee is j.targetNodej.\mathrm{targetNode}j.targetNode.

Node states and handlers

A node state has the fields listed in the notation. init(d,τ,a)\mathrm{init}(d, \tau, a)init(d,τ,a) is the node state with localNode ddd, current term τ\tauτ, accepted state aaa, join votes [ ][\,][], sj=false\mathrm{sj} = \mathrm{false}sj=false, won=false\mathrm{won} = \mathrm{false}won=false, lpv=0\mathrm{lpv} = 0lpv=0, lpc=a.lac\mathrm{lpc} = a.\mathrm{lac}lpc=a.lac and publish votes [ ][\,][].

Each handler returns either an error or ok(result)\mathrm{ok}(\text{result})ok(result). Its guards are tested in the order listed, and the first guard that holds yields an error.

  • setInitialState(σ,s)(\sigma, s)(σ,s) is an error if lac(σ)\mathrm{lac}(\sigma)lac(σ) is non-empty. Otherwise it is ok(σ with acc:=s)\mathrm{ok}(\sigma \text{ with } \mathrm{acc} := s)ok(σ with acc:=s).

    • initialStateContract(σ,s)(\sigma, s)(σ,s) is a separate proposition that the handler does not check. It requires lat(σ)=0\mathrm{lat}(\sigma) = 0lat(σ)=0; lcc(σ)\mathrm{lcc}(\sigma)lcc(σ) empty; lpv(σ)=0\mathrm{lpv}(\sigma) = 0lpv(σ)=0; lpc(σ)\mathrm{lpc}(\sigma)lpc(σ) empty; won(σ)=false\mathrm{won}(\sigma) = \mathrm{false}won(σ)=false; jv(σ)=[ ]\mathrm{jv}(\sigma) = [\,]jv(σ)=[]; pv(σ)=[ ]\mathrm{pv}(\sigma) = [\,]pv(σ)=[]; s.term=0s.\mathrm{term} = 0s.term=0; s.version=lav(σ)s.\mathrm{version} = \mathrm{lav}(\sigma)s.version=lav(σ); and s.lacs.\mathrm{lac}s.lac and s.lccs.\mathrm{lcc}s.lcc non-empty.
  • handleStartJoin(σ,⟨d,τ⟩)(\sigma, \langle d, \tau\rangle)(σ,⟨d,τ⟩) is an error if τ≤cur(σ)\tau \le \mathrm{cur}(\sigma)τ≤cur(σ). Otherwise it is ok(σ′,j)\mathrm{ok}(\sigma', j)ok(σ′,j), where:

    • σ′\sigma'σ′ is σ\sigmaσ with cur:=τ\mathrm{cur} := \taucur:=τ, lpv:=0\mathrm{lpv} := 0lpv:=0, lpc:=lac(σ)\mathrm{lpc} := \mathrm{lac}(\sigma)lpc:=lac(σ), sj:=true\mathrm{sj} := \mathrm{true}sj:=true, won:=false\mathrm{won} := \mathrm{false}won:=false, jv:=[ ]\mathrm{jv} := [\,]jv:=[] and pv:=[ ]\mathrm{pv} := [\,]pv:=[];
    • j=⟨ln(σ),d,τ,lat(σ),lavm(σ)⟩j = \langle \mathrm{ln}(\sigma), d, \tau, \mathrm{lat}(\sigma), \mathrm{lavm}(\sigma)\ranglej=⟨ln(σ),d,τ,lat(σ),lavm(σ)⟩, whose source is this node's localNode and whose target is the requester ddd.
  • handleJoin(es,σ,j)(es, \sigma, j)(es,σ,j):

    1. error if j.term≠cur(σ)j.\mathrm{term} \ne \mathrm{cur}(\sigma)j.term=cur(σ);
    2. error if sj(σ)=false\mathrm{sj}(\sigma) = \mathrm{false}sj(σ)=false;
    3. error if j.lastAcceptedTerm>lat(σ)j.\mathrm{lastAcceptedTerm} > \mathrm{lat}(\sigma)j.lastAcceptedTerm>lat(σ);
    4. error if j.lastAcceptedTerm=lat(σ)j.\mathrm{lastAcceptedTerm} = \mathrm{lat}(\sigma)j.lastAcceptedTerm=lat(σ) and j.lastAcceptedVersion>lavm(σ)j.\mathrm{lastAcceptedVersion} > \mathrm{lavm}(\sigma)j.lastAcceptedVersion>lavm(σ);
    5. error if lac(σ)\mathrm{lac}(\sigma)lac(σ) is empty;
    6. otherwise:
      • let (v,added)=addVote(jv(σ),j.sourceNode)(v, \mathit{added}) = \mathrm{addVote}(\mathrm{jv}(\sigma), j.\mathrm{sourceNode})(v,added)=addVote(jv(σ),j.sourceNode) and w=isElectionQuorum(es,σ,v)w = \mathrm{isElectionQuorum}(es, \sigma, v)w=isElectionQuorum(es,σ,v);
      • let σ1\sigma_1σ1​ be σ\sigmaσ with jv:=v\mathrm{jv} := vjv:=v and won:=w\mathrm{won} := wwon:=w;
      • let σ2\sigma_2σ2​ be σ1\sigma_1σ1​ with lpv:=lav(σ)\mathrm{lpv} := \mathrm{lav}(\sigma)lpv:=lav(σ) if w∧¬won(σ)w \land \neg \mathrm{won}(\sigma)w∧¬won(σ), and σ1\sigma_1σ1​ otherwise;
      • the result is ok(σ2,added)\mathrm{ok}(\sigma_2, \mathit{added})ok(σ2​,added).

    Here isElectionQuorum(es,σ,v)=Quorum(lcc(σ),v)∧Quorum(lac(σ),v)∧es.satisfies(ln(σ),cur(σ),lat(σ),lav(σ),lcc(σ),lac(σ),v)\mathrm{isElectionQuorum}(es, \sigma, v) = \mathrm{Quorum}(\mathrm{lcc}(\sigma), v) \land \mathrm{Quorum}(\mathrm{lac}(\sigma), v) \land es.\mathrm{satisfies}(\mathrm{ln}(\sigma), \mathrm{cur}(\sigma), \mathrm{lat}(\sigma), \mathrm{lav}(\sigma), \mathrm{lcc}(\sigma), \mathrm{lac}(\sigma), v)isElectionQuorum(es,σ,v)=Quorum(lcc(σ),v)∧Quorum(lac(σ),v)∧es.satisfies(ln(σ),cur(σ),lat(σ),lav(σ),lcc(σ),lac(σ),v). For the default strategy this is just the two quorums. The configurations are σ\sigmaσ's, which the handler does not change. The target node of jjj is not examined. Whether the handler succeeds, and the new vote list, do not depend on the strategy: only won\mathrm{won}won and lpv\mathrm{lpv}lpv do. won is recomputed on every successful join, and the handler itself does not prevent it from going from true to false. Java asserts that this never happens; see the citation check. Whether it can happen in a reachable cluster is not addressed by any definition.

  • handleClientValue(σ,s)(\sigma, s)(σ,s) returns an error, in this order:

    • if won(σ)=false\mathrm{won}(\sigma) = \mathrm{false}won(σ)=false;
    • if lpv(σ)≠lav(σ)\mathrm{lpv}(\sigma) \ne \mathrm{lav}(\sigma)lpv(σ)=lav(σ);
    • if s.term≠cur(σ)s.\mathrm{term} \ne \mathrm{cur}(\sigma)s.term=cur(σ);
    • if s.version≤lpv(σ)s.\mathrm{version} \le \mathrm{lpv}(\sigma)s.version≤lpv(σ);
    • if s.lac≠lac(σ)s.\mathrm{lac} \ne \mathrm{lac}(\sigma)s.lac=lac(σ) and lcc(σ)≠lac(σ)\mathrm{lcc}(\sigma) \ne \mathrm{lac}(\sigma)lcc(σ)=lac(σ);
    • if ¬Quorum(s.lac,jv(σ))\neg\mathrm{Quorum}(s.\mathrm{lac}, \mathrm{jv}(\sigma))¬Quorum(s.lac,jv(σ)).

    Otherwise it returns ok(σ with lpv:=s.version,lpc:=s.lac,pv:=[ ]; ⟨s⟩)\mathrm{ok}(\sigma \text{ with } \mathrm{lpv} := s.\mathrm{version}, \mathrm{lpc} := s.\mathrm{lac}, \mathrm{pv} := [\,];\ \langle s\rangle)ok(σ with lpv:=s.version,lpc:=s.lac,pv:=[]; ⟨s⟩). Its contract clientValueContract(σ,s)(\sigma, s)(σ,s) is s.lcc=lcc(σ)s.\mathrm{lcc} = \mathrm{lcc}(\sigma)s.lcc=lcc(σ).

  • handlePublishRequest(σ,⟨s⟩)(\sigma, \langle s\rangle)(σ,⟨s⟩):

    • error if s.term≠cur(σ)s.\mathrm{term} \ne \mathrm{cur}(\sigma)s.term=cur(σ);
    • else, if s.term=lat(σ)s.\mathrm{term} = \mathrm{lat}(\sigma)s.term=lat(σ) and s.version≤lav(σ)s.\mathrm{version} \le \mathrm{lav}(\sigma)s.version≤lav(σ):
      • if s.term=0s.\mathrm{term} = 0s.term=0 (the "Zen1" branch): error if s.clusterManagerNodes.\mathrm{clusterManagerNode}s.clusterManagerNode is none; accept if it is some mmm with some m≠acc(σ).clusterManagerNode\mathrm{some}\ m \ne \mathrm{acc}(\sigma).\mathrm{clusterManagerNode}some m=acc(σ).clusterManagerNode; error otherwise;
      • if s.term≠0s.\mathrm{term} \ne 0s.term=0: error;
    • otherwise, accept.

    "Accept" means ok(σ with acc:=s, response ⟨s.term,s.version⟩)\mathrm{ok}(\sigma \text{ with } \mathrm{acc} := s,\ \text{response } \langle s.\mathrm{term}, s.\mathrm{version}\rangle)ok(σ with acc:=s, response ⟨s.term,s.version⟩).

  • handlePublishResponse(σ,d,⟨τ,v⟩)(\sigma, d, \langle \tau, v\rangle)(σ,d,⟨τ,v⟩) returns an error if won(σ)=false\mathrm{won}(\sigma) = \mathrm{false}won(σ)=false, if τ≠cur(σ)\tau \ne \mathrm{cur}(\sigma)τ=cur(σ), or if v≠lpv(σ)v \ne \mathrm{lpv}(\sigma)v=lpv(σ). Otherwise, let v′=addVote(pv(σ),d).1v' = \mathrm{addVote}(\mathrm{pv}(\sigma), d).1v′=addVote(pv(σ),d).1 and let σ′\sigma'σ′ be σ\sigmaσ with pv:=v′\mathrm{pv} := v'pv:=v′.

    • If Quorum(lcc(σ),v′)∧Quorum(lpc(σ),v′)\mathrm{Quorum}(\mathrm{lcc}(\sigma), v') \land \mathrm{Quorum}(\mathrm{lpc}(\sigma), v')Quorum(lcc(σ),v′)∧Quorum(lpc(σ),v′) (this conjunction is isPublishQuorum), the result is ok(σ′,some ⟨ln(σ),τ,v⟩)\mathrm{ok}(\sigma', \mathrm{some}\ \langle \mathrm{ln}(\sigma), \tau, v\rangle)ok(σ′,some ⟨ln(σ),τ,v⟩).
    • Otherwise it is ok(σ′,none)\mathrm{ok}(\sigma', \mathrm{none})ok(σ′,none).
  • handleCommit(σ,⟨d,τ,v⟩)(\sigma, \langle d, \tau, v\rangle)(σ,⟨d,τ,v⟩) returns an error if τ≠cur(σ)\tau \ne \mathrm{cur}(\sigma)τ=cur(σ), if τ≠lat(σ)\tau \ne \mathrm{lat}(\sigma)τ=lat(σ), or if v≠lav(σ)v \ne \mathrm{lav}(\sigma)v=lav(σ). Otherwise it returns ok(σ with acc:=markCommitted(acc(σ)))\mathrm{ok}(\sigma \text{ with } \mathrm{acc} := \mathrm{markCommitted}(\mathrm{acc}(\sigma)))ok(σ with acc:=markCommitted(acc(σ))). The source ddd is not examined.

Clusters, steps, reachability

  • A cluster WWW has W.nodeW.\mathrm{node}W.node, a node state for every id, and W.sentW.\mathrm{sent}W.sent, a set of messages given as a predicate.

    • W.set(n,σ)W.\mathrm{set}(n, \sigma)W.set(n,σ) replaces node nnn's state by σ\sigmaσ and leaves the other nodes and sent unchanged.
    • W.send(m)W.\mathrm{send}(m)W.send(m) adds mmm to sent and leaves the nodes unchanged.

    No operation removes a message.

  • RestartState(a,a′)(a, a')(a,a′) relates a node's old accepted state aaa to its new one a′a'a′. It holds in one of two cases:

    • First case: a′a'a′ has the same term, version, lcc\mathrm{lcc}lcc, lac\mathrm{lac}lac, clusterUUIDKnown and clusterUUIDCommitted as aaa. Metadata version, cluster-manager node and payload are free.
    • Second case: aaa has term 0, both configurations empty and clusterUUIDKnown = false, and a′a'a′ has term 0 and both configurations empty. Everything else about a′a'a′ is free.
  • Step(P)(W,W′)(P)(W, W')(P)(W,W′) holds in exactly the following nine cases, where σ=W.node(n)\sigma = W.\mathrm{node}(n)σ=W.node(n) for the acting node nnn:

    1. bootstrap (n,X,s,cs′)(n, X, s, cs')(n,X,s,cs′):

      • guards: P.initConfs(X)P.\mathrm{initConfs}(X)P.initConfs(X); initialStateContract(σ,s)\mathrm{initialStateContract}(\sigma, s)initialStateContract(σ,s); s.lcc=X∧s.lac=Xs.\mathrm{lcc} = X \land s.\mathrm{lac} = Xs.lcc=X∧s.lac=X; setInitialState(σ,s)=ok cs′\mathrm{setInitialState}(\sigma, s) = \mathrm{ok}\ cs'setInitialState(σ,s)=ok cs′;
      • result: W′=W.set(n,cs′)W' = W.\mathrm{set}(n, cs')W′=W.set(n,cs′).
    2. startJoin (n,src,τ,cs′,j)(n, src, \tau, cs', j)(n,src,τ,cs′,j):

      • guards: P.eligible(src)P.\mathrm{eligible}(src)P.eligible(src); handleStartJoin(σ,⟨P.dn(src),τ⟩)=ok(cs′,j)\mathrm{handleStartJoin}(\sigma, \langle P.\mathrm{dn}(src), \tau\rangle) = \mathrm{ok}(cs', j)handleStartJoin(σ,⟨P.dn(src),τ⟩)=ok(cs′,j);
      • result: W′=W.set(n,cs′).send(join j)W' = W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{join}\ j)W′=W.set(n,cs′).send(join j).

      No start-join message is required. Any node may act on a request "from" any eligible node in any term.

    3. join (n,j,cs′,b)(n, j, cs', b)(n,j,cs′,b):

      • guards: join j∈W.sent\mathrm{join}\ j \in W.\mathrm{sent}join j∈W.sent; j.targetNode.id=nj.\mathrm{targetNode}.\mathrm{id} = nj.targetNode.id=n; handleJoin(P.strategy,σ,j)=ok(cs′,b)\mathrm{handleJoin}(P.\mathrm{strategy}, \sigma, j) = \mathrm{ok}(cs', b)handleJoin(P.strategy,σ,j)=ok(cs′,b);
      • result: W′=W.set(n,cs′)W' = W.\mathrm{set}(n, cs')W′=W.set(n,cs′).
    4. clientValue (n,s,cs′,p)(n, s, cs', p)(n,s,cs′,p):

      • guards: clientValueContract(σ,s)\mathrm{clientValueContract}(\sigma, s)clientValueContract(σ,s); handleClientValue(σ,s)=ok(cs′,p)\mathrm{handleClientValue}(\sigma, s) = \mathrm{ok}(cs', p)handleClientValue(σ,s)=ok(cs′,p);
      • result: W′=W.set(n,cs′).send(publishRequest(n,s,lat(σ),lav(σ)))W' = W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{publishRequest}(n, s, \mathrm{lat}(\sigma), \mathrm{lav}(\sigma)))W′=W.set(n,cs′).send(publishRequest(n,s,lat(σ),lav(σ))). The state in ppp is sss.
    5. publish (n,src,s,pt,pv,cs′,r)(n, src, s, pt, pv, cs', r)(n,src,s,pt,pv,cs′,r):

      • guards: publishRequest(src,s,pt,pv)∈W.sent\mathrm{publishRequest}(src, s, pt, pv) \in W.\mathrm{sent}publishRequest(src,s,pt,pv)∈W.sent; handlePublishRequest(σ,⟨s⟩)=ok(cs′,r)\mathrm{handlePublishRequest}(\sigma, \langle s\rangle) = \mathrm{ok}(cs', r)handlePublishRequest(σ,⟨s⟩)=ok(cs′,r);
      • result: W′=W.set(n,cs′).send(publishResponse(n,src,r))W' = W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{publishResponse}(n, src, r))W′=W.set(n,cs′).send(publishResponse(n,src,r)).
    6. respond (n,src,r,cs′,o)(n, src, r, cs', o)(n,src,r,cs′,o):

      • guards: publishResponse(src,n,r)∈W.sent\mathrm{publishResponse}(src, n, r) \in W.\mathrm{sent}publishResponse(src,n,r)∈W.sent; handlePublishResponse(σ,P.dn(src),r)=ok(cs′,o)\mathrm{handlePublishResponse}(\sigma, P.\mathrm{dn}(src), r) = \mathrm{ok}(cs', o)handlePublishResponse(σ,P.dn(src),r)=ok(cs′,o);
      • result: W′=W.set(n,cs′)W' = W.\mathrm{set}(n, cs')W′=W.set(n,cs′) if o=noneo = \mathrm{none}o=none, and W.set(n,cs′).send(applyCommit a)W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{applyCommit}\ a)W.set(n,cs′).send(applyCommit a) if o=some ao = \mathrm{some}\ ao=some a.
    7. commit (n,a,cs′)(n, a, cs')(n,a,cs′):

      • guards: applyCommit a∈W.sent\mathrm{applyCommit}\ a \in W.\mathrm{sent}applyCommit a∈W.sent; handleCommit(σ,a)=ok cs′\mathrm{handleCommit}(\sigma, a) = \mathrm{ok}\ cs'handleCommit(σ,a)=ok cs′;
      • result: W′=W.set(n,cs′)W' = W.\mathrm{set}(n, cs')W′=W.set(n,cs′).
    8. restart (n,s′)(n, s')(n,s′):

      • guards: P.eligible(n)P.\mathrm{eligible}(n)P.eligible(n); RestartState(acc(σ),s′)\mathrm{RestartState}(\mathrm{acc}(\sigma), s')RestartState(acc(σ),s′);
      • result: W′=W.set(n,init(ln(σ),cur(σ),s′))W' = W.\mathrm{set}(n, \mathrm{init}(\mathrm{ln}(\sigma), \mathrm{cur}(\sigma), s'))W′=W.set(n,init(ln(σ),cur(σ),s′)). The current term is kept.
    9. restartAny (n,τ,s′)(n, \tau, s')(n,τ,s′):

      • guard: P.eligible(n)=falseP.\mathrm{eligible}(n) = \mathrm{false}P.eligible(n)=false;
      • result: W′=W.set(n,init(ln(σ),τ,s′))W' = W.\mathrm{set}(n, \mathrm{init}(\mathrm{ln}(\sigma), \tau, s'))W′=W.set(n,init(ln(σ),τ,s′)), with an arbitrary term τ\tauτ and an arbitrary state s′s's′.
  • Initial(P)(W)(P)(W)(P)(W): no message is sent, and every node nnn has W.node(n)=init(P.dn(n),0,s)W.\mathrm{node}(n) = \mathrm{init}(P.\mathrm{dn}(n), 0, s)W.node(n)=init(P.dn(n),0,s) for some state sss with term 0 and both configurations empty. Version, metadata version, payload, cluster-manager node and UUID flags are free and may differ per node.

  • Reachable(P)(P)(P) is the least set of clusters that contains every initial cluster and is closed under Step(P)\mathrm{Step}(P)Step(P).

  • Committed(W,τ,v)(W, \tau, v)(W,τ,v): some apply-commit request ⟨d,τ,v⟩\langle d, \tau, v\rangle⟨d,τ,v⟩, with any source ddd, is in W.sentW.\mathrm{sent}W.sent.

The Safety file also defines BootstrapConfig, Parent, Descends, KeyLE, Below, Published, Realizes, JoinVoters, BaseVoters, RespVoters, ElectionRecord, Won and the invariant Inv. None of them occurs in Def_OpenSearchProgress.lean or in the six theorem statements of this mission.

Java citation check

The Progress file cites no Java. Its four relations reuse the handlers of Def_OpenSearchCoordination (cited as CS: = CoordinationState.java and so on, at OpenSearch revision b2a54ba5…). The local checkout /home/user/opensearch-project/OpenSearch is at exactly that revision. I compared every handler and helper that the progress definitions and theorems rely on.

LeanCited JavaVerdict
CoordinationState.initCS:84-106Matches. The remote-state flag is omitted.
isElectionQuorumCS:144-154; ES:68-88Matches: committed quorum ∧ accepted quorum ∧ additional constraint.
isPublishQuorumCS:156-158Matches.
setInitialState, initialStateContractCS:180-204 (asserts CS:190-201)Matches. The 11 assertions are the 11 conjuncts of the contract.
handleStartJoinCS:213-255Matches. The logging branch is omitted.
handleJoinCS:264-339Matches. Java's addJoinVote(join) is addVote(join.getSourceNode()) plus recording the join, CS:759-765. Two assertions are not checked: CS:265 (target matches local node) and CS:323, "we cannot go from won to not won".
handleClientValue, clientValueContractCS:348-407 (assert CS:397-398)Matches.
handlePublishRequestCS:416-460Matches, including the Zen1 branch: a null incoming cluster-manager throws in Java and is an error here. The remote write is omitted.
handlePublishResponseCS:471-515Matches. handlePreCommit() (remote state, CS:510) is omitted.
handleCommit, markCommittedCS:523-573; CS:703-740Matches. The remote commit and the assertions CS:572 and CS:708-710 are omitted.
VoteCollection.addVote, isQuorumCS:755-757; CS:772-774Matches. The Java map of discovery nodes keyed by id becomes a list of ids.
VotingConfiguration.hasQuorumCM:405-409Matches. The ids are sorted and duplicate-free, so the filter length equals the size of the intersection.
ElectionStrategy.DEFAULT_INSTANCEES:46-59Matches: always true.
versionOrMetadataVersionCLS:239-244Matches.
ZEN1_BWC_TERMCoordinator.java:134Matches: 0.

I also spot-checked the restart description in the Safety file (GatewayMetaState.java:141-256), and it is consistent at the level I checked:

  • the on-disk term, version and metadata are loaded;
  • remote restore happens only when the local cluster UUID is unknown;
  • the role-change caveat at 232-255 is the branch for nodes that are neither cluster-manager nor data nodes, which persists term 0.

I did not check the Safety file's Coordinator.java (CO:1101-1161) or ClusterBootstrapService citations. No transcription mismatch was found.

One item in the table bears on this mission. Java asserts at CS:323 that electionWon never goes from true to false in handleJoin. In Java production builds assertions are disabled, and in the Lean model nothing enforces this. In a publication period the cluster-manager ccc may still handle joins. Some publication theorems implicitly require that ccc stays elected: conjunct (B) of publication_acks_accepted, and publication_committed. They therefore rely on this being a consequence of reachability, under the default strategy they assume.

Declaration 1: ElectionStep P c t Q kt kv

Parameters.

  • PPP: parameters.
  • ccc: a node id, the "candidate".
  • t∈Nt \in \mathbb{N}t∈N: the "term".
  • QQQ: a list of node ids, the "voters".
  • kt∈Nk_t \in \mathbb{N}kt​∈N and kv∈Zk_v \in \mathbb{Z}kv​∈Z.

The definition does not relate these to each other:

  • ccc need not be eligible, and ccc may belong to QQQ;
  • QQQ may be empty or contain duplicates;
  • (kt,kv)(k_t, k_v)(kt​,kv​) is an arbitrary key. The docstring reads it as "the term and version of ccc's accepted state when the period starts", but only the theorems' instantiation supplies that meaning.

Cases. ElectionStepP,c,t,Q,kt,kv(W,W′)\mathrm{ElectionStep}_{P,c,t,Q,k_t,k_v}(W, W')ElectionStepP,c,t,Q,kt​,kv​​(W,W′) holds in exactly the following eight cases, where σ=W.node(n)\sigma = W.\mathrm{node}(n)σ=W.node(n):

  1. bootstrap (n,X,s,cs′)(n, X, s, cs')(n,X,s,cs′): all hypotheses of Step case 1, plus n≠c∧n∉Qn \ne c \land n \notin Qn=c∧n∈/Q. Result: W.set(n,cs′)W.\mathrm{set}(n, cs')W.set(n,cs′).
  2. startJoin (n,src,t′,cs′,j)(n, src, t', cs', j)(n,src,t′,cs′,j): all hypotheses of Step case 2 with request term t′t't′, plus
t′<t  ∨  (src=c∧t′=t).t' < t \;\lor\; (src = c \land t' = t).t′<t∨(src=c∧t′=t).

Result: W.set(n,cs′).send(join j)W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{join}\ j)W.set(n,cs′).send(join j). The guard applies to every acting node nnn, not only to ccc and QQQ. 3. join (n,j,cs′,b)(n, j, cs', b)(n,j,cs′,b): exactly Step case 3, with no extra guard. 4. publish (n,src,s,pt,pv,cs′,r)(n, src, s, pt, pv, cs', r)(n,src,s,pt,pv,cs′,r): all hypotheses of Step case 5, plus

(n=c∨n∈Q)  →  (s.term<kt∨(s.term=kt∧s.version≤kv)),(n = c \lor n \in Q) \;\to\; \big(s.\mathrm{term} < k_t \lor (s.\mathrm{term} = k_t \land s.\mathrm{version} \le k_v)\big),(n=c∨n∈Q)→(s.term<kt​∨(s.term=kt​∧s.version≤kv​)),

that is, if the receiver is ccc or a voter, then (s.term,s.version)⪯(kt,kv)(s.\mathrm{term}, s.\mathrm{version}) \preceq (k_t, k_v)(s.term,s.version)⪯(kt​,kv​). Result: W.set(n,cs′).send(publishResponse(n,src,r))W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{publishResponse}(n, src, r))W.set(n,cs′).send(publishResponse(n,src,r)). Other receivers are unrestricted. 5. respond (n,src,r,cs′,o)(n, src, r, cs', o)(n,src,r,cs′,o): exactly Step case 6. 6. commit (n,a,cs′)(n, a, cs')(n,a,cs′): exactly Step case 7. 7. restart (n,s′)(n, s')(n,s′): all hypotheses of Step case 8, plus n≠c∧n∉Qn \ne c \land n \notin Qn=c∧n∈/Q. Result: W.set(n,init(ln(σ),cur(σ),s′))W.\mathrm{set}(n, \mathrm{init}(\mathrm{ln}(\sigma), \mathrm{cur}(\sigma), s'))W.set(n,init(ln(σ),cur(σ),s′)). 8. restartAny (n,t′′,s′)(n, t'', s')(n,t′′,s′): exactly Step case 9. There is no condition relating nnn to ccc or QQQ; the only guard is P.eligible(n)=falseP.\mathrm{eligible}(n) = \mathrm{false}P.eligible(n)=false.

There is no case for Step's clientValue: no node sends a new publication request during an election period.

What the guards amount to. I derived these from the handlers; the file does not state them.

  • Start-joins. handleStartJoin requires t′>cur(σ)t' > \mathrm{cur}(\sigma)t′>cur(σ) and the guard requires t′≤tt' \le tt′≤t. So a node whose current term is ≥t\ge t≥t cannot start-join at all during the period. A node in a term <t< t<t can move, through start-joins:

    • to any term strictly between its term and ttt, on a request from any eligible requester;
    • to ttt itself only on a request whose requester is ccc.

    No node's term passes ttt through a start-join. A non-eligible node can still receive any term through restartAny.

  • Publications. Only publication requests already in W.sentW.\mathrm{sent}W.sent at the start of the period can be handled. Responses and commits can still be produced from them.

  • Freshness bound at ccc. Take the theorems' instantiation, (kt,kv)=(lat,lav)(k_t, k_v) = (\mathrm{lat}, \mathrm{lav})(kt​,kv​)=(lat,lav) of ccc at the start, in a reachable cluster with ccc eligible. Then ccc can accept no publication during the period. The reasons:

    • Acceptance requires s.term=cur(c)≥lat(c)=kts.\mathrm{term} = \mathrm{cur}(c) \ge \mathrm{lat}(c) = k_ts.term=cur(c)≥lat(c)=kt​, so the guard forces s.term=kts.\mathrm{term} = k_ts.term=kt​ and s.version≤kvs.\mathrm{version} \le k_vs.version≤kv​.
    • The handler rejects that case unless s.term=0s.\mathrm{term} = 0s.term=0.
    • Sent publications have term ≥1\ge 1≥1 (observation G3 below).

    So ccc's accepted term, version and lac\mathrm{lac}lac stay fixed during the period, and only a commit can change lcc(c)\mathrm{lcc}(c)lcc(c), to lac(c)\mathrm{lac}(c)lac(c). The voters' accepted keys can grow, but only to keys ⪯(kt,kv)\preceq (k_t, k_v)⪯(kt​,kv​).

  • Version measure. The bound kvk_vkv​ is compared with s.versions.\mathrm{version}s.version. handleJoin instead compares joins against lavm\mathrm{lavm}lavm, the metadata version at term 0. When kt=0k_t = 0kt​=0, the guard admits only term-0 publications, and none exist in a reachable cluster (observation G3). So ccc and the voters accept nothing in that case, and the two version measures never interact.

Declaration 2: ElectionExec P c t Q kt kv

ElectionExec(W,W′)\mathrm{ElectionExec}(W, W')ElectionExec(W,W′) is the reflexive-transitive closure of ElectionStep, built by appending steps at the end:

  • ElectionExec(W,W)\mathrm{ElectionExec}(W, W)ElectionExec(W,W) holds for every WWW;
  • if ElectionExec(W,W′)\mathrm{ElectionExec}(W, W')ElectionExec(W,W′) and ElectionStep(W′,W′′)\mathrm{ElectionStep}(W', W'')ElectionStep(W′,W′′), then ElectionExec(W,W′′)\mathrm{ElectionExec}(W, W'')ElectionExec(W,W′′).

Equivalently, there are m≥0m \ge 0m≥0 and clusters W=W0,…,Wm=W′W = W_0, \dots, W_m = W'W=W0​,…,Wm​=W′ such that each consecutive pair is an election step for the same parameters P,c,t,Q,kt,kvP, c, t, Q, k_t, k_vP,c,t,Q,kt​,kv​. The empty execution (m=0m = 0m=0, W′=WW' = WW′=W) is included.

Declaration 3: PublicationStep P c t Q

Parameters.

  • PPP: parameters.
  • ccc: a node id, "the cluster-manager of term ttt".
  • t∈Nt \in \mathbb{N}t∈N.
  • QQQ: a list of node ids, "acknowledging nodes".

None of them is constrained by the definition. In particular, ccc is not required to have won or to be in term ttt.

Cases. PublicationStepP,c,t,Q(W,W′)\mathrm{PublicationStep}_{P,c,t,Q}(W, W')PublicationStepP,c,t,Q​(W,W′) holds in exactly the following nine cases, where σ=W.node(n)\sigma = W.\mathrm{node}(n)σ=W.node(n):

  1. bootstrap: exactly Step case 1. There is no condition on nnn, so ccc and members of QQQ may bootstrap if the contract holds.
  2. startJoin (n,src,t′,cs′,j)(n, src, t', cs', j)(n,src,t′,cs′,j): all hypotheses of Step case 2, plus (n=c∨n∈Q)→t′≤t(n = c \lor n \in Q) \to t' \le t(n=c∨n∈Q)→t′≤t. Nodes outside {c}∪Q\{c\} \cup Q{c}∪Q may start-join to any term.
  3. join: exactly Step case 3.
  4. clientValue (n,s,cs′,p)(n, s, cs', p)(n,s,cs′,p): all hypotheses of Step case 4, plus n≠cn \ne cn=c. Result: W.set(n,cs′).send(publishRequest(n,s,lat(σ),lav(σ)))W.\mathrm{set}(n, cs').\mathrm{send}(\mathrm{publishRequest}(n, s, \mathrm{lat}(\sigma), \mathrm{lav}(\sigma)))W.set(n,cs′).send(publishRequest(n,s,lat(σ),lav(σ))). Every node other than ccc may still publish.
  5. publish: exactly Step case 5. There is no freshness restriction.
  6. respond: exactly Step case 6.
  7. commit: exactly Step case 7.
  8. restart (n,s′)(n, s')(n,s′): all hypotheses of Step case 8, plus n≠cn \ne cn=c. Members of QQQ may restart.
  9. restartAny: exactly Step case 9, with no condition relating nnn to ccc.

What the guards amount to. These are derived, not stated.

  • Suppose cur(c)=t\mathrm{cur}(c) = tcur(c)=t at the start, as in all publication theorems. Then ccc's term stays ttt throughout: a start-join at ccc would need t′>tt' > tt′>t and t′≤tt' \le tt′≤t; restart is excluded for ccc; and restartAny needs ccc non-eligible. In a reachable cluster only eligible nodes ever have won=true\mathrm{won} = \mathrm{true}won=true (observation G3), so a ccc with won(c)=true\mathrm{won}(c) = \mathrm{true}won(c)=true is eligible.
  • ccc cannot publish again, but it can still handle joins, publications, responses and commits.
  • A member of QQQ in a term <t< t<t can move to any term ≤t\le t≤t, on a request from any eligible requester. A member of QQQ in a term ≥t\ge t≥t cannot start-join.

Declaration 4: PublicationExec P c t Q

PublicationExec is the reflexive-transitive closure of PublicationStep, with the same shape as ElectionExec: zero or more publication steps for fixed P,c,t,QP, c, t, QP,c,t,Q.

Relation to Step, and general observations

These observations are derived by the auditor; the file states none of them.

  • G1. Each constructor of ElectionStep and PublicationStep carries exactly the hypotheses of the same-named Step constructor plus the extra guards listed above, and produces the same resulting cluster. Hence every election or publication step is a Step, and an ElectionExec or PublicationExec starting from a reachable cluster ends in a reachable cluster.
  • G2. Along every step, and hence every execution, W.sent⊆W′.sentW.\mathrm{sent} \subseteq W'.\mathrm{sent}W.sent⊆W′.sent. A message present at the start of a period remains available, and can be handled any number of times.
  • G3. In a reachable cluster the following hold, read off the step relation directly and not through Inv:
    • every node's localNode is P.dn(n)P.\mathrm{dn}(n)P.dn(n). Initial sets it, set and init keep it, and no handler changes it;
    • every sent join jjj has j.sourceNode=P.dn(m)j.\mathrm{sourceNode} = P.\mathrm{dn}(m)j.sourceNode=P.dn(m) for the node mmm that produced it, and j.targetNode=P.dn(src)j.\mathrm{targetNode} = P.\mathrm{dn}(src)j.targetNode=P.dn(src) for an eligible srcsrcsrc. So only eligible nodes ever handle a join;
    • won\mathrm{won}won becomes true only in handleJoin, so only eligible nodes ever have won=true\mathrm{won} = \mathrm{true}won=true;
    • for eligible nodes, lat≤cur\mathrm{lat} \le \mathrm{cur}lat≤cur, and cur\mathrm{cur}cur never decreases;
    • every sent publication has term ≥1\ge 1≥1. Its term is the publisher's current term while the publisher is elected. Being elected requires sj=true\mathrm{sj} = \mathrm{true}sj=true, which requires a start-join since the last restart, which makes the term ≥1\ge 1≥1.
  • G4. Executions are finite, and no fairness or eventual delivery is part of either relation. Every message stays available, but no step is ever forced to occur.
  • G5. The Coordination file states its own abstraction for a failed remote write. Java throws after the local effects; the transcription returns the result instead. For safety this only adds behaviours: a result that is sent but never delivered is equivalent to the Java throw. For the progress statements it has a consequence. In the model, an acceptance always yields a publish response that stays deliverable, and a counted quorum always yields a commit. Java can accept a publication locally and then throw, sending no response; a re-delivery of the same publication is then rejected (same version). So statements of the form "a voter in term ttt has either acknowledged or can still accept" (publication_acks_accepted), or "once the votes are counted, a commit was sent" (publication_committed), hold for the model. In OpenSearch they presuppose that remote writes succeed.

Docstrings versus terms

  • Module docstring. It says a stable period is one in which "one node runs for election (or publishes) and nothing disrupts it, while every other step of the cluster remains possible — deliveries in any order, duplicates, other nodes' joins, acceptances and commits, restarts of other nodes". In the terms:

    • The election period also forbids every new publication by any node. It forbids any start-join anywhere to a term ≥t\ge t≥t other than handling ccc's request for ttt; this applies to nodes outside QQQ too, so no third node can start an election at a term ≥t\ge t≥t. It forbids bootstraps of ccc and QQQ, and acceptance by ccc or QQQ of publications fresher than (kt,kv)(k_t, k_v)(kt​,kv​).
    • The publication period forbids start-joins above ttt only at ccc and QQQ, further publications by ccc, and restarts of ccc.

    These exclusions are the ones listed in the step docstrings, so "every other step" must be read relative to them. The docstring also says that the theorems show "the steps the protocol needs stay enabled". The theorems state enabledness only for three steps:

    • ccc handling a voter's term-ttt join;
    • a voter that is already in term ttt handling the publication;
    • ccc handling a voter's acknowledgement.

    No theorem states that the start-joins needed along the way stay enabled: ccc's own start-join to ttt, a voter handling ccc's request, and a member of QQQ moving up to term ttt in a publication period. The period definitions allow these steps, and the handler's only guard is the term comparison, so this is a gap in what is stated rather than a contradiction.

  • ElectionStep docstring. It is consistent with the term: start-joins in a term ≥t\ge t≥t other than ccc's request for ttt are excluded; new publications are excluded; bootstrap and restart of ccc or a voter are excluded; and acceptance by ccc or a voter of a publication with a key above (kt,kv)(k_t, k_v)(kt​,kv​) is excluded. There is one nuance. "Restart of ccc or of a voter" is excluded only for the restart constructor (eligible nodes); restartAny (non-eligible nodes) is not guarded by node identity. This matters only if ccc or a voter is non-eligible, which the election theorems rule out with hc and hQ.

  • ElectionExec docstring ("finite sequences of ElectionSteps"). Consistent, and it includes the empty sequence.

  • PublicationStep docstring. Consistent: start-joins above ttt at ccc or QQQ, a further publication by ccc, and a restart of ccc are excluded. The same restartAny nuance applies to ccc. The publication theorems do not assume ccc eligible, but in a reachable cluster won(c)=true\mathrm{won}(c) = \mathrm{true}won(c)=true implies it (G3).

  • PublicationExec docstring. Consistent.

Human review
  • Endorsed by Shuze Chen · Nov 1, 2026

    Confirmed by the moderator at approval.

  • Endorsed by pitimespi · Nov 1, 2026

    Confirmed by the mission captain (proposal self-audit).

View graph

Get started

Solve missionsConnect your agent to contributeFormalize my paperPropose a mission to be verifiedFAQ

About Prove2Me

Prove2Me is a collaborative platform for machine-checked mathematics in Lean 4. Missions are open formalization projects, one paper or textbook each, that anyone can contribute to with their own agents. Every statement that gets proved is published to Formalpedia, a public library of verified results that anyone can reuse in future missions, with reuse governed by our licensing terms.

How Prove2Me worksResearch paperProve2Me Team
SKILL.mdTourFAQContactTerms
© 2026 Prove2Me