Prove2Me
Navigate
DiscoverCollectionsFormalpediaGalaxyBlogsUsersMomentumMy Missions+
Prove2Me
Log in
← Formalpedia

The publish step preserves the invariant

Proved
OpenSearchSafety.inv_publish

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

cluster-coordinationconsensuscrash-recoveryelasticsearchformal-verificationinductive-invariantleader-electionmessage-passingopensearchpaxosraftreconfigurationsafetyzen2

Let P satisfy P.InitCompatible and P.strategy = ElectionStrategy.DEFAULT_INSTANCE, and let W satisfy Inv P W. For all nodes n and src, every cluster state s, term pt and version pv with W.sent (.publishRequest src s pt pv), and every state cs' and response r with (W.node n).handlePublishRequest ⟨s⟩ = .ok (cs', r): Inv P ((W.set n cs').send (.publishResponse n src r)).

Every publication has term at least 1, so the Zen1 compatibility branch of handlePublishRequest never applies; an elected node accepts only its own publication in flight. The hypotheses on P are not used.

Preamble
import Definitions.Def_OpenSearchSafety
open OpenSearchCoordination OpenSearchSafety
Formal statement
theorem OpenSearchSafety.inv_publish {V : Type} (P : Params) (_hP : P.InitCompatible)
    (_hes : P.strategy = ElectionStrategy.DEFAULT_INSTANCE) (W : Cluster V) (hinv : Inv P W)
    (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)) :
    Inv P ((W.set n cs').send (.publishResponse n src r)) := by sorry
Source
OpenSearch b2a54ba5c0defba3d1d20423e911d4bf920bab19: CoordinationState (the per-node handlers as transcribed by the OpenSearchCoordination mission; handleCommit lines 523-573, isPublishQuorum 156-158, invariant 601-611): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/CoordinationState.java; Coordinator (the calls into CoordinationState: handleApplyCommit lines 419-452, handlePublishRequest 454-526, startElection 583-602, abdicateTo 604-620, ensureTermAtLeast 635-641, joinLeaderInTerm 643-657, setInitialConfiguration 1101-1161, handleJoin 1267-1299, publish 1350 with the handleClientValue call at 1404, CoordinatorPublication.handlePublishResponse 1871-1876): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/Coordinator.java; ClusterBootstrapService (requirements 146-157, onFoundPeersUpdated 176-215, startBootstrap 259-273, checkRequirements 306-335): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/ClusterBootstrapService.java#L146-L335; GatewayMetaState.start (persisted state loaded and rebuilt 141-231, remote restore 169-188, synchronous and asynchronous persistence 199-207, nodes without the cluster-manager and data roles 232-255): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/gateway/GatewayMetaState.java#L126-L257; PersistedStateRegistry.addPersistedState (lines 38-41): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/PersistedStateRegistry.java#L38-L41; ElectionStrategy (DEFAULT_INSTANCE lines 46-59): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/main/java/org/opensearch/cluster/coordination/ElectionStrategy.java#L44-L110; the randomized safety test CoordinationStateTestCluster (reboot 156-193, runRandomly 279-329, invariant 331-370): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/test/framework/src/main/java/org/opensearch/cluster/coordination/CoordinationStateTestCluster.java and CoordinationStateTests.testSafety (lines 917-924): https://github.com/opensearch-project/OpenSearch/blob/b2a54ba5c0defba3d1d20423e911d4bf920bab19/server/src/test/java/org/opensearch/cluster/coordination/CoordinationStateTests.java#L917-L924; for comparison, Elastic's TLA+ model ZenWithTerms (HandleCommit 241-250, CommittedValuesDescendantsFromInitialValue 338-347, CommitHasQuorumVsPreviousCommittedConfiguration 349-358): https://github.com/elastic/elasticsearch-formal-models/blob/ca30663506a7e18de9d23fabbc70a369903d7c98/ZenWithTerms/tla/ZenWithTerms.tla
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: OpenSearchSafety.inv_publish

Notions used

Sources and conventions. Rendered from Definitions/Def_OpenSearchCoordination.lean, Definitions/Def_OpenSearchSafety.lean and the stub Theorems/Thm_OpenSearchSafety_inv_publish.lean; Java line numbers refer to OpenSearch b2a54ba5c0defba3d1d20423e911d4bf920bab19 (CS = CoordinationState.java, CM = CoordinationMetadata.java, ES = ElectionStrategy.java, CO = Coordinator.java, CBS = ClusterBootstrapService.java), checked against the local checkout. Throughout, VVV is an arbitrary payload type, PPP a parameter record and WWW a cluster; csncs_ncsn​ abbreviates W.node(n)W.\mathrm{node}(n)W.node(n), the coordination state of node nnn in WWW, and "m∈sentm \in \mathrm{sent}m∈sent" means W.sent(m)W.\mathrm{sent}(m)W.sent(m). Booleans are read as truth values: "won(cs)\mathrm{won}(cs)won(cs)" means electionWon = true, "¬won(cs)\neg\mathrm{won}(cs)¬won(cs)" means electionWon = false.

Nodes, configurations, quorums. Node ids are natural numbers. A discovery node ddd is a pair (d.id,d.cm)(d.\mathrm{id}, d.\mathrm{cm})(d.id,d.cm) of an id and a Boolean (isClusterManagerNode). A voting configuration ccc is a strictly increasing list of node ids, hence duplicate-free; two configurations are equal exactly when they have the same members. ∅\emptyset∅ denotes the empty configuration EMPTY_CONFIG; "ccc is empty" means its list is empty. For a configuration ccc and any list qqq of ids,

hasQuorum(c,q)  ⟺  2⋅∣{x∈c:x∈q}∣>∣c∣,\mathrm{hasQuorum}(c, q) \iff 2\cdot\big|\{x \in c : x \in q\}\big| > |c| ,hasQuorum(c,q)⟺2⋅​{x∈c:x∈q}​>∣c∣,

so the empty configuration has no quorum, and ids of qqq outside ccc as well as repetitions in qqq are irrelevant. Transcription check: matches VotingConfiguration.hasQuorum (CM:405-409).

A vote collection is a list of node ids; the empty collection is [ ][\,][]; isQuorum(v,c):=hasQuorum(c,v)\mathrm{isQuorum}(v, c) := \mathrm{hasQuorum}(c, v)isQuorum(v,c):=hasQuorum(c,v); addVote(v,d)\mathrm{addVote}(v, d)addVote(v,d) is (d.id::v, added)(d.\mathrm{id} :: v,\ \text{added})(d.id::v, added) if d.cmd.\mathrm{cm}d.cm is true and d.id∉vd.\mathrm{id} \notin vd.id∈/v, and (v, not added)(v,\ \text{not added})(v, not added) otherwise. Transcription check: VoteCollection (CS:750-814) keys a map by node id; addVote (CS:755-757) changes the key set exactly in the same cases.

Cluster states. A cluster state sss has a term s.term∈Ns.\mathrm{term} \in \mathbb{N}s.term∈N, a version s.version∈Zs.\mathrm{version} \in \mathbb{Z}s.version∈Z, a metadata version s.mv∈Zs.\mathrm{mv} \in \mathbb{Z}s.mv∈Z, a last committed and a last accepted configuration s.lccs.\mathrm{lcc}s.lcc and s.lacs.\mathrm{lac}s.lac, an optional natural number s.cmNodes.\mathrm{cmNode}s.cmNode (the cluster-manager node), two Booleans s.uuidKnowns.\mathrm{uuidKnown}s.uuidKnown and s.uuidCommitteds.\mathrm{uuidCommitted}s.uuidCommitted, and a payload s.value∈Vs.\mathrm{value} \in Vs.value∈V. Two cluster states are equal when all nine fields are. vomv(s)\mathrm{vomv}(s)vomv(s) (versionOrMetadataVersion) is s.mvs.\mathrm{mv}s.mv if s.term=0s.\mathrm{term} = 0s.term=0 and s.versions.\mathrm{version}s.version otherwise. markCommitted(s)\mathrm{markCommitted}(s)markCommitted(s) is sss with lcc\mathrm{lcc}lcc replaced by s.lacs.\mathrm{lac}s.lac and with uuidCommitted\mathrm{uuidCommitted}uuidCommitted set to true when s.uuidKnowns.\mathrm{uuidKnown}s.uuidKnown is true; nothing else changes (transcribes CS:703-740; the Java assertion at CS:708-710 is not checked).

Message contents. A join jjj has a source and a target discovery node j.srcj.\mathrm{src}j.src, j.tgtj.\mathrm{tgt}j.tgt, a term j.term∈Nj.\mathrm{term} \in \mathbb{N}j.term∈N, a last accepted term j.lat∈Nj.\mathrm{lat} \in \mathbb{N}j.lat∈N and a last accepted version j.lav∈Zj.\mathrm{lav} \in \mathbb{Z}j.lav∈Z; two joins are equal when all five fields are. A publish response is a pair ⟨t,v⟩∈N×Z\langle t, v\rangle \in \mathbb{N}\times\mathbb{Z}⟨t,v⟩∈N×Z. An apply-commit request is a triple ⟨d,t,v⟩\langle d, t, v\rangle⟨d,t,v⟩ of a discovery node, a term and a version. A start-join request is a pair of a discovery node and a term; it is not a cluster message (see the steps).

Coordination states. A coordination state cscscs has a local node cs.localNodecs.\mathrm{localNode}cs.localNode (a discovery node), a current term ct(cs)∈N\mathrm{ct}(cs) \in \mathbb{N}ct(cs)∈N, a last accepted state cs.lascs.\mathrm{las}cs.las (a cluster state), two vote collections joinVotes(cs)\mathrm{joinVotes}(cs)joinVotes(cs) and pubVotes(cs)\mathrm{pubVotes}(cs)pubVotes(cs), Booleans started(cs)\mathrm{started}(cs)started(cs) (startedJoinSinceLastReboot) and won(cs)\mathrm{won}(cs)won(cs) (electionWon), a last published version lpv(cs)∈Z\mathrm{lpv}(cs) \in \mathbb{Z}lpv(cs)∈Z and a last published configuration lpc(cs)\mathrm{lpc}(cs)lpc(cs). lat(cs)\mathrm{lat}(cs)lat(cs), lav(cs)\mathrm{lav}(cs)lav(cs), lcc(cs)\mathrm{lcc}(cs)lcc(cs), lac(cs)\mathrm{lac}(cs)lac(cs) are the term, version, last committed and last accepted configuration of cs.lascs.\mathrm{las}cs.las. init(d,t,s)\mathrm{init}(d, t, s)init(d,t,s) is the coordination state with local node ddd, current term ttt, last accepted state sss, both vote collections empty, started\mathrm{started}started and won\mathrm{won}won false, lpv=0\mathrm{lpv} = 0lpv=0 and lpc=s.lac\mathrm{lpc} = s.\mathrm{lac}lpc=s.lac (the constructor, CS:84-106).

Election strategies. An election strategy is a Boolean function σ(d,t,at,av,c1,c2,v)\sigma(d, t, at, av, c_1, c_2, v)σ(d,t,at,av,c1​,c2​,v) of a discovery node, two naturals, an integer, two configurations and a vote collection; DEFAULT_INSTANCE is the constant-true function. isElectionQuorumσ(cs,v):=isQuorum(v,lcc(cs))∧isQuorum(v,lac(cs))∧σ(cs.localNode,ct(cs),lat(cs),lav(cs),lcc(cs),lac(cs),v)\mathrm{isElectionQuorum}_\sigma(cs, v) := \mathrm{isQuorum}(v, \mathrm{lcc}(cs)) \wedge \mathrm{isQuorum}(v, \mathrm{lac}(cs)) \wedge \sigma(cs.\mathrm{localNode}, \mathrm{ct}(cs), \mathrm{lat}(cs), \mathrm{lav}(cs), \mathrm{lcc}(cs), \mathrm{lac}(cs), v)isElectionQuorumσ​(cs,v):=isQuorum(v,lcc(cs))∧isQuorum(v,lac(cs))∧σ(cs.localNode,ct(cs),lat(cs),lav(cs),lcc(cs),lac(cs),v); for the default strategy the third conjunct is true. isPublishQuorum(cs,v):=isQuorum(v,lcc(cs))∧isQuorum(v,lpc(cs))\mathrm{isPublishQuorum}(cs, v) := \mathrm{isQuorum}(v, \mathrm{lcc}(cs)) \wedge \mathrm{isQuorum}(v, \mathrm{lpc}(cs))isPublishQuorum(cs,v):=isQuorum(v,lcc(cs))∧isQuorum(v,lpc(cs)). Transcription check: ES:68-88 with CS:144-154, and CS:156-158.

Parameters. A parameter record PPP consists of elig:N→Bool\mathrm{elig} : \mathbb{N} \to \mathrm{Bool}elig:N→Bool (eligible: whether a node is cluster-manager-eligible), a predicate initConfs\mathrm{initConfs}initConfs on configurations (an arbitrary set of "bootstrap configurations": possibly empty, possibly containing ∅\emptyset∅, possibly infinite), and an election strategy σP\sigma_PσP​ (strategy). dn(n):=(n,elig(n))\mathrm{dn}(n) := (n, \mathrm{elig}(n))dn(n):=(n,elig(n)); "xxx is eligible" means elig(x)=\mathrm{elig}(x) = elig(x)= true. InitCompatible(P)\mathrm{InitCompatible}(P)InitCompatible(P) holds when for all configurations X,YX, YX,Y with initConfs(X)\mathrm{initConfs}(X)initConfs(X) and initConfs(Y)\mathrm{initConfs}(Y)initConfs(Y) and all lists q1,q2q_1, q_2q1​,q2​ of eligible ids,

hasQuorum(X,q1)∧hasQuorum(Y,q2)  ⟹  ∃x, x∈q1∧x∈q2\mathrm{hasQuorum}(X, q_1) \wedge \mathrm{hasQuorum}(Y, q_2) \implies \exists x,\ x \in q_1 \wedge x \in q_2hasQuorum(X,q1​)∧hasQuorum(Y,q2​)⟹∃x, x∈q1​∧x∈q2​

(for X=YX = YX=Y this is automatic: two majorities of one configuration meet).

Messages and clusters. A cluster message is one of: join(j)\mathsf{join}(j)join(j); pubReq(a,s,pt,pv)\mathsf{pubReq}(a, s, pt, pv)pubReq(a,s,pt,pv), a publish request from node aaa carrying a cluster state sss and a ghost parent (pt,pv)∈N×Z(pt, pv) \in \mathbb{N}\times\mathbb{Z}(pt,pv)∈N×Z; pubResp(a,d,r)\mathsf{pubResp}(a, d, r)pubResp(a,d,r), a publish response rrr from node aaa to node ddd; commit(c)\mathsf{commit}(c)commit(c), an apply-commit request ccc. A cluster WWW is a function W.nodeW.\mathrm{node}W.node from node ids to coordination states together with a predicate W.sentW.\mathrm{sent}W.sent on messages (the messages sent so far). W.set(n,cs)W.\mathrm{set}(n, cs)W.set(n,cs) replaces node nnn's state by cscscs and changes nothing else; W.send(m)W.\mathrm{send}(m)W.send(m) adds mmm to the sent set and changes nothing else. A key is a pair (t,v)∈N×Z(t, v) \in \mathbb{N}\times\mathbb{Z}(t,v)∈N×Z; the key of a publication pubReq(a,s,pt,pv)\mathsf{pubReq}(a, s, pt, pv)pubReq(a,s,pt,pv) is (s.term,s.version)(s.\mathrm{term}, s.\mathrm{version})(s.term,s.version).

The handler. Each handler returns either an error (the corresponding step is then impossible) or a result. Guards are tested in the order listed; the first that applies gives the error.

  • handlePublishRequest(cs,s)\mathrm{handlePublishRequest}(cs, s)handlePublishRequest(cs,s): error if s.term≠ct(cs)s.\mathrm{term} \ne \mathrm{ct}(cs)s.term=ct(cs). Otherwise, if s.term=lat(cs)s.\mathrm{term} = \mathrm{lat}(cs)s.term=lat(cs) and s.version≤lav(cs)s.\mathrm{version} \le \mathrm{lav}(cs)s.version≤lav(cs): when s.term≠0s.\mathrm{term} \ne 0s.term=0, error; when s.term=0s.\mathrm{term} = 0s.term=0 (the "Zen1" branch), error if s.cmNodes.\mathrm{cmNode}s.cmNode is none, accept if s.cmNode=some ms.\mathrm{cmNode} = \mathrm{some}\ ms.cmNode=some m with some m≠cs.las.cmNode\mathrm{some}\ m \ne cs.\mathrm{las}.\mathrm{cmNode}some m=cs.las.cmNode, error otherwise. In all remaining cases, accept. To accept is to return cscscs with cs.las:=scs.\mathrm{las} := scs.las:=s and the response ⟨s.term,s.version⟩\langle s.\mathrm{term}, s.\mathrm{version}\rangle⟨s.term,s.version⟩. Transcription check: CS:416-460 (a null incoming cluster-manager in the Zen1 branch throws in Java).

Commits. Committed(W,t,v)\mathrm{Committed}(W, t, v)Committed(W,t,v) holds when commit(⟨d,t,v⟩)∈sent\mathsf{commit}(\langle d, t, v\rangle) \in \mathrm{sent}commit(⟨d,t,v⟩)∈sent for some discovery node ddd (the source is irrelevant).

Descent. Parent(W,(t,v),(t′,v′))\mathrm{Parent}(W, (t, v), (t', v'))Parent(W,(t,v),(t′,v′)) holds when some pubReq(a,s,t,v)∈sent\mathsf{pubReq}(a, s, t, v) \in \mathrm{sent}pubReq(a,s,t,v)∈sent has s.term=t′s.\mathrm{term} = t's.term=t′ and s.version=v′s.\mathrm{version} = v's.version=v′: a publication of key (t′,v′)(t', v')(t′,v′) was sent with ghost parent (t,v)(t, v)(t,v). Descends(W,k,k′)\mathrm{Descends}(W, k, k')Descends(W,k,k′), for keys k,k′k, k'k,k′, is the least relation that holds when k′=kk' = kk′=k — for every key, whether or not anything was published under it — and that holds of (k,k′′)(k, k'')(k,k′′) whenever it holds of (k,k′)(k, k')(k,k′) and Parent(W,k′,k′′)\mathrm{Parent}(W, k', k'')Parent(W,k′,k′′). Thus Descends(W,k,k′)\mathrm{Descends}(W, k, k')Descends(W,k,k′) means: k=k′k = k'k=k′, or there is a chain k=k0,k1,…,km=k′k = k_0, k_1, \dots, k_m = k'k=k0​,k1​,…,km​=k′ with m≥1m \ge 1m≥1 and Parent(W,ki−1,ki)\mathrm{Parent}(W, k_{i-1}, k_i)Parent(W,ki−1​,ki​) for each iii. Descent relates keys through ghost parents only; it says nothing about the payloads of the states.

Auxiliary notions of the invariant.

  • KeyLE((t,v),(t′,v′))\mathrm{KeyLE}((t, v), (t', v'))KeyLE((t,v),(t′,v′)): t<t′t < t't<t′, or t=t′t = t't=t′ and v≤v′v \le v'v≤v′.
  • Below((lt,lv),(bt,bv))\mathrm{Below}((lt, lv), (bt, bv))Below((lt,lv),(bt,bv)): lt<btlt < btlt<bt, or lt=btlt = btlt=bt and (bt=0bt = 0bt=0 or lv≤bvlv \le bvlv≤bv) — at term 000 versions are not compared.
  • Realizes(P,W,(t,v),x,y)\mathrm{Realizes}(P, W, (t, v), x, y)Realizes(P,W,(t,v),x,y): either t=0t = 0t=0, x=yx = yx=y and initConfs(x)\mathrm{initConfs}(x)initConfs(x) (with vvv unconstrained); or some pubReq(a,s,pt,pv)∈sent\mathsf{pubReq}(a, s, pt, pv) \in \mathrm{sent}pubReq(a,s,pt,pv)∈sent has s.term=ts.\mathrm{term} = ts.term=t, s.version=vs.\mathrm{version} = vs.version=v, y=s.lacy = s.\mathrm{lac}y=s.lac, and either x=s.lccx = s.\mathrm{lcc}x=s.lcc, or x=s.lacx = s.\mathrm{lac}x=s.lac and Committed(W,t,v)\mathrm{Committed}(W, t, v)Committed(W,t,v).
  • JoinVoters(P,W,L,t,J)\mathrm{JoinVoters}(P, W, L, t, J)JoinVoters(P,W,L,t,J): every x∈Jx \in Jx∈J is eligible and some join(j)∈sent\mathsf{join}(j) \in \mathrm{sent}join(j)∈sent has j.src.id=xj.\mathrm{src}.\mathrm{id} = xj.src.id=x, j.tgt.id=Lj.\mathrm{tgt}.\mathrm{id} = Lj.tgt.id=L and j.term=tj.\mathrm{term} = tj.term=t.
  • BaseVoters(P,W,L,t,(bt,bv),J)\mathrm{BaseVoters}(P, W, L, t, (bt, bv), J)BaseVoters(P,W,L,t,(bt,bv),J): the same, the witnessing join satisfying in addition Below((j.lat,j.lav),(bt,bv))\mathrm{Below}((j.\mathrm{lat}, j.\mathrm{lav}), (bt, bv))Below((j.lat,j.lav),(bt,bv)).
  • RespVoters(P,W,(t,v),A)\mathrm{RespVoters}(P, W, (t, v), A)RespVoters(P,W,(t,v),A): every x∈Ax \in Ax∈A is eligible and pubResp(x,d,⟨t,v⟩)∈sent\mathsf{pubResp}(x, d, \langle t, v\rangle) \in \mathrm{sent}pubResp(x,d,⟨t,v⟩)∈sent for some ddd.
  • ElectionRecord(P,W,L,t,(bt,bv),x,y,J)\mathrm{ElectionRecord}(P, W, L, t, (bt, bv), x, y, J)ElectionRecord(P,W,L,t,(bt,bv),x,y,J): bt<tbt < tbt<t, Realizes(P,W,(bt,bv),x,y)\mathrm{Realizes}(P, W, (bt, bv), x, y)Realizes(P,W,(bt,bv),x,y), BaseVoters(P,W,L,t,(bt,bv),J)\mathrm{BaseVoters}(P, W, L, t, (bt, bv), J)BaseVoters(P,W,L,t,(bt,bv),J), hasQuorum(x,J)\mathrm{hasQuorum}(x, J)hasQuorum(x,J) and hasQuorum(y,J)\mathrm{hasQuorum}(y, J)hasQuorum(y,J).
  • Won(P,W,b,t)\mathrm{Won}(P, W, b, t)Won(P,W,b,t): bbb is eligible, and either (won(csb)\mathrm{won}(cs_b)won(csb​) and ct(csb)=t\mathrm{ct}(cs_b) = tct(csb​)=t) or some pubReq(b,s,pt,pv)∈sent\mathsf{pubReq}(b, s, pt, pv) \in \mathrm{sent}pubReq(b,s,pt,pv)∈sent has s.term=ts.\mathrm{term} = ts.term=t.

The invariant. Inv(P,W)\mathrm{Inv}(P, W)Inv(P,W) is the conjunction of the following 30 statements (Lean field names in parentheses); nodes n,m,a,b,dn, m, a, b, dn,m,a,b,d range over all node ids, j,j′j, j'j,j′ over all joins, rrr over all publish responses.

Every node nnn:

  1. (localNode_eq) csn.localNode=dn(n)cs_n.\mathrm{localNode} = \mathrm{dn}(n)csn​.localNode=dn(n).
  2. (idle_of_not_won) ¬won(csn)⇒lpv(csn)=0∧pubVotes(csn)=[ ]\neg\mathrm{won}(cs_n) \Rightarrow \mathrm{lpv}(cs_n) = 0 \wedge \mathrm{pubVotes}(cs_n) = [\,]¬won(csn​)⇒lpv(csn​)=0∧pubVotes(csn​)=[].
  3. (won_started) won(csn)⇒started(csn)\mathrm{won}(cs_n) \Rightarrow \mathrm{started}(cs_n)won(csn​)⇒started(csn​).
  4. (votes_started) joinVotes(csn)≠[ ]⇒started(csn)\mathrm{joinVotes}(cs_n) \ne [\,] \Rightarrow \mathrm{started}(cs_n)joinVotes(csn​)=[]⇒started(csn​).
  5. (started_term_pos) started(csn)⇒ct(csn)≥1\mathrm{started}(cs_n) \Rightarrow \mathrm{ct}(cs_n) \ge 1started(csn​)⇒ct(csn​)≥1.
  6. (won_quorum) won(csn)⇒isQuorum(joinVotes(csn),lcc(csn))∧isQuorum(joinVotes(csn),lac(csn))\mathrm{won}(cs_n) \Rightarrow \mathrm{isQuorum}(\mathrm{joinVotes}(cs_n), \mathrm{lcc}(cs_n)) \wedge \mathrm{isQuorum}(\mathrm{joinVotes}(cs_n), \mathrm{lac}(cs_n))won(csn​)⇒isQuorum(joinVotes(csn​),lcc(csn​))∧isQuorum(joinVotes(csn​),lac(csn​)).
  7. (not_won_no_quorum) ¬won(csn)⇒¬(isQuorum(joinVotes(csn),lcc(csn))∧isQuorum(joinVotes(csn),lac(csn)))\neg\mathrm{won}(cs_n) \Rightarrow \neg\big(\mathrm{isQuorum}(\mathrm{joinVotes}(cs_n), \mathrm{lcc}(cs_n)) \wedge \mathrm{isQuorum}(\mathrm{joinVotes}(cs_n), \mathrm{lac}(cs_n))\big)¬won(csn​)⇒¬(isQuorum(joinVotes(csn​),lcc(csn​))∧isQuorum(joinVotes(csn​),lac(csn​))).
  8. (joinVotes_sent) every m∈joinVotes(csn)m \in \mathrm{joinVotes}(cs_n)m∈joinVotes(csn​) is eligible, and some join(j)∈sent\mathsf{join}(j) \in \mathrm{sent}join(j)∈sent has j.src.id=mj.\mathrm{src}.\mathrm{id} = mj.src.id=m, j.tgt.id=nj.\mathrm{tgt}.\mathrm{id} = nj.tgt.id=n, j.term=ct(csn)j.\mathrm{term} = \mathrm{ct}(cs_n)j.term=ct(csn​) and Below((j.lat,j.lav),(lat(csn),lav(csn)))\mathrm{Below}((j.\mathrm{lat}, j.\mathrm{lav}), (\mathrm{lat}(cs_n), \mathrm{lav}(cs_n)))Below((j.lat,j.lav),(lat(csn​),lav(csn​))).
  9. (publishVotes_sent) every m∈pubVotes(csn)m \in \mathrm{pubVotes}(cs_n)m∈pubVotes(csn​) is eligible and pubResp(m,n,⟨ct(csn),lpv(csn)⟩)∈sent\mathsf{pubResp}(m, n, \langle \mathrm{ct}(cs_n), \mathrm{lpv}(cs_n)\rangle) \in \mathrm{sent}pubResp(m,n,⟨ct(csn​),lpv(csn​)⟩)∈sent.

Every eligible node nnn:

  1. (term_le) lat(csn)≤ct(csn)\mathrm{lat}(cs_n) \le \mathrm{ct}(cs_n)lat(csn​)≤ct(csn​).
  2. (accepted_realizes) (lat(csn)=0∧lcc(csn)=∅∧lac(csn)=∅)∨Realizes(P,W,(lat(csn),lav(csn)),lcc(csn),lac(csn))\big(\mathrm{lat}(cs_n) = 0 \wedge \mathrm{lcc}(cs_n) = \emptyset \wedge \mathrm{lac}(cs_n) = \emptyset\big) \vee \mathrm{Realizes}(P, W, (\mathrm{lat}(cs_n), \mathrm{lav}(cs_n)), \mathrm{lcc}(cs_n), \mathrm{lac}(cs_n))(lat(csn​)=0∧lcc(csn​)=∅∧lac(csn​)=∅)∨Realizes(P,W,(lat(csn​),lav(csn​)),lcc(csn​),lac(csn​)).

Joins and publish responses:

  1. (join_wf) if join(j)∈sent\mathsf{join}(j) \in \mathrm{sent}join(j)∈sent then j.src=dn(j.src.id)j.\mathrm{src} = \mathrm{dn}(j.\mathrm{src}.\mathrm{id})j.src=dn(j.src.id) and j.tgt.idj.\mathrm{tgt}.\mathrm{id}j.tgt.id is eligible.
  2. (join_term_le) if join(j)∈sent\mathsf{join}(j) \in \mathrm{sent}join(j)∈sent and j.src.idj.\mathrm{src}.\mathrm{id}j.src.id is eligible then j.term≤ct(csj.src.id)j.\mathrm{term} \le \mathrm{ct}(cs_{j.\mathrm{src}.\mathrm{id}})j.term≤ct(csj.src.id​).
  3. (join_unique) if join(j),join(j′)∈sent\mathsf{join}(j), \mathsf{join}(j') \in \mathrm{sent}join(j),join(j′)∈sent, j.src.idj.\mathrm{src}.\mathrm{id}j.src.id is eligible, j.src.id=j′.src.idj.\mathrm{src}.\mathrm{id} = j'.\mathrm{src}.\mathrm{id}j.src.id=j′.src.id and j.term=j′.termj.\mathrm{term} = j'.\mathrm{term}j.term=j′.term, then j=j′j = j'j=j′ (all five fields, in particular the same target).
  4. (join_after_response) if join(j)∈sent\mathsf{join}(j) \in \mathrm{sent}join(j)∈sent, pubResp(a,d,r)∈sent\mathsf{pubResp}(a, d, r) \in \mathrm{sent}pubResp(a,d,r)∈sent, j.src.id=aj.\mathrm{src}.\mathrm{id} = aj.src.id=a, aaa is eligible and r.term<j.termr.\mathrm{term} < j.\mathrm{term}r.term<j.term, then KeyLE((r.term,r.version),(j.lat,j.lav))\mathrm{KeyLE}((r.\mathrm{term}, r.\mathrm{version}), (j.\mathrm{lat}, j.\mathrm{lav}))KeyLE((r.term,r.version),(j.lat,j.lav)).
  5. (response_le) if pubResp(a,d,r)∈sent\mathsf{pubResp}(a, d, r) \in \mathrm{sent}pubResp(a,d,r)∈sent and aaa is eligible, then r.term≤ct(csa)r.\mathrm{term} \le \mathrm{ct}(cs_a)r.term≤ct(csa​) and KeyLE((r.term,r.version),(lat(csa),lav(csa)))\mathrm{KeyLE}((r.\mathrm{term}, r.\mathrm{version}), (\mathrm{lat}(cs_a), \mathrm{lav}(cs_a)))KeyLE((r.term,r.version),(lat(csa​),lav(csa​))).
  6. (response_published) if pubResp(a,d,r)∈sent\mathsf{pubResp}(a, d, r) \in \mathrm{sent}pubResp(a,d,r)∈sent then some pubReq(d,s,pt,pv)∈sent\mathsf{pubReq}(d, s, pt, pv) \in \mathrm{sent}pubReq(d,s,pt,pv)∈sent — sent by the addressee ddd — has s.term=r.terms.\mathrm{term} = r.\mathrm{term}s.term=r.term and s.version=r.versions.\mathrm{version} = r.\mathrm{version}s.version=r.version.

Publications: for every pubReq(a,s,pt,pv)∈sent\mathsf{pubReq}(a, s, pt, pv) \in \mathrm{sent}pubReq(a,s,pt,pv)∈sent:

  1. (pub_wf) aaa is eligible, s.term≥1s.\mathrm{term} \ge 1s.term≥1, pt≤s.termpt \le s.\mathrm{term}pt≤s.term, pv<s.versionpv < s.\mathrm{version}pv<s.version, and pt=0⇒initConfs(s.lcc)pt = 0 \Rightarrow \mathrm{initConfs}(s.\mathrm{lcc})pt=0⇒initConfs(s.lcc).
  2. (pub_parent) if pt≥1pt \ge 1pt≥1, some pubReq(a′,s′,pt′,pv′)∈sent\mathsf{pubReq}(a', s', pt', pv') \in \mathrm{sent}pubReq(a′,s′,pt′,pv′)∈sent has s′.term=pts'.\mathrm{term} = pts′.term=pt, s′.version=pvs'.\mathrm{version} = pvs′.version=pv, [s.lcc=s′.lccs.\mathrm{lcc} = s'.\mathrm{lcc}s.lcc=s′.lcc, or s.lcc=s′.lacs.\mathrm{lcc} = s'.\mathrm{lac}s.lcc=s′.lac and Committed(W,pt,pv)\mathrm{Committed}(W, pt, pv)Committed(W,pt,pv)], and [s.lac≠s′.lac⇒s.lcc=s′.lacs.\mathrm{lac} \ne s'.\mathrm{lac} \Rightarrow s.\mathrm{lcc} = s'.\mathrm{lac}s.lac=s′.lac⇒s.lcc=s′.lac].
  3. (pub_unique) every pubReq(a′,s′,pt′,pv′)∈sent\mathsf{pubReq}(a', s', pt', pv') \in \mathrm{sent}pubReq(a′,s′,pt′,pv′)∈sent with s′.term=s.terms'.\mathrm{term} = s.\mathrm{term}s′.term=s.term and s′.version=s.versions'.\mathrm{version} = s.\mathrm{version}s′.version=s.version has a′=aa' = aa′=a, s′=ss' = ss′=s, pt′=ptpt' = ptpt′=pt and pv′=pvpv' = pvpv′=pv.
  4. (pub_rooted) Descends(W,(0,v0),(s.term,s.version))\mathrm{Descends}(W, (0, v_0), (s.\mathrm{term}, s.\mathrm{version}))Descends(W,(0,v0​),(s.term,s.version)) for some v0∈Zv_0 \in \mathbb{Z}v0​∈Z.
  5. (pub_same_term) every pubReq(a′,s′,pt′,pv′)∈sent\mathsf{pubReq}(a', s', pt', pv') \in \mathrm{sent}pubReq(a′,s′,pt′,pv′)∈sent with s′.term=s.terms'.\mathrm{term} = s.\mathrm{term}s′.term=s.term has Descends(W,(s.term,s.version),(s′.term,s′.version))\mathrm{Descends}(W, (s.\mathrm{term}, s.\mathrm{version}), (s'.\mathrm{term}, s'.\mathrm{version}))Descends(W,(s.term,s.version),(s′.term,s′.version)) or the converse.
  6. (pub_voters) some list JJJ has JoinVoters(P,W,a,s.term,J)\mathrm{JoinVoters}(P, W, a, s.\mathrm{term}, J)JoinVoters(P,W,a,s.term,J), hasQuorum(s.lcc,J)\mathrm{hasQuorum}(s.\mathrm{lcc}, J)hasQuorum(s.lcc,J) and hasQuorum(s.lac,J)\mathrm{hasQuorum}(s.\mathrm{lac}, J)hasQuorum(s.lac,J).
  7. (pub_election) if pt<s.termpt < s.\mathrm{term}pt<s.term, some yyy and JJJ have ElectionRecord(P,W,a,s.term,(pt,pv),s.lcc,y,J)\mathrm{ElectionRecord}(P, W, a, s.\mathrm{term}, (pt, pv), s.\mathrm{lcc}, y, J)ElectionRecord(P,W,a,s.term,(pt,pv),s.lcc,y,J).

Commits:

  1. (commit_quorum) for every commit(⟨d,t,v⟩)∈sent\mathsf{commit}(\langle d, t, v\rangle) \in \mathrm{sent}commit(⟨d,t,v⟩)∈sent, some pubReq(a,s,pt,pv)∈sent\mathsf{pubReq}(a, s, pt, pv) \in \mathrm{sent}pubReq(a,s,pt,pv)∈sent has s.term=ts.\mathrm{term} = ts.term=t and s.version=vs.\mathrm{version} = vs.version=v, and some list AAA has RespVoters(P,W,(t,v),A)\mathrm{RespVoters}(P, W, (t, v), A)RespVoters(P,W,(t,v),A), hasQuorum(s.lac,A)\mathrm{hasQuorum}(s.\mathrm{lac}, A)hasQuorum(s.lac,A), and either hasQuorum(s.lcc,A)\mathrm{hasQuorum}(s.\mathrm{lcc}, A)hasQuorum(s.lcc,A), or [Committed(W,pt,pv)\mathrm{Committed}(W, pt, pv)Committed(W,pt,pv) and some pubReq(a′,s′,pt′,pv′)∈sent\mathsf{pubReq}(a', s', pt', pv') \in \mathrm{sent}pubReq(a′,s′,pt′,pv′)∈sent has s′.term=pts'.\mathrm{term} = pts′.term=pt, s′.version=pvs'.\mathrm{version} = pvs′.version=pv, s′.lac=s.lacs'.\mathrm{lac} = s.\mathrm{lac}s′.lac=s.lac].

Leaders:

  1. (won_unique) Won(P,W,a,t)∧Won(P,W,b,t)⇒a=b\mathrm{Won}(P, W, a, t) \wedge \mathrm{Won}(P, W, b, t) \Rightarrow a = bWon(P,W,a,t)∧Won(P,W,b,t)⇒a=b.
  2. (pub_source_term) for every pubReq(a,s,pt,pv)∈sent\mathsf{pubReq}(a, s, pt, pv) \in \mathrm{sent}pubReq(a,s,pt,pv)∈sent: s.term<ct(csa)s.\mathrm{term} < \mathrm{ct}(cs_a)s.term<ct(csa​), or s.term=ct(csa)s.\mathrm{term} = \mathrm{ct}(cs_a)s.term=ct(csa​) and (won(csa)\mathrm{won}(cs_a)won(csa​) or ¬started(csa)\neg\mathrm{started}(cs_a)¬started(csa​)).
  3. (leader_pubs) if won(csn)\mathrm{won}(cs_n)won(csn​), every pubReq(a,s,pt,pv)∈sent\mathsf{pubReq}(a, s, pt, pv) \in \mathrm{sent}pubReq(a,s,pt,pv)∈sent with s.term=ct(csn)s.\mathrm{term} = \mathrm{ct}(cs_n)s.term=ct(csn​), whoever its sender aaa, has s.version≤lpv(csn)s.\mathrm{version} \le \mathrm{lpv}(cs_n)s.version≤lpv(csn​), Descends(W,(s.term,s.version),(ct(csn),lpv(csn)))\mathrm{Descends}(W, (s.\mathrm{term}, s.\mathrm{version}), (\mathrm{ct}(cs_n), \mathrm{lpv}(cs_n)))Descends(W,(s.term,s.version),(ct(csn​),lpv(csn​))), isQuorum(joinVotes(csn),s.lcc)\mathrm{isQuorum}(\mathrm{joinVotes}(cs_n), s.\mathrm{lcc})isQuorum(joinVotes(csn​),s.lcc) and isQuorum(joinVotes(csn),s.lac)\mathrm{isQuorum}(\mathrm{joinVotes}(cs_n), s.\mathrm{lac})isQuorum(joinVotes(csn​),s.lac).
  4. (leader_inflight) if won(csn)\mathrm{won}(cs_n)won(csn​) and lpv(csn)≠lav(csn)\mathrm{lpv}(cs_n) \ne \mathrm{lav}(cs_n)lpv(csn​)=lav(csn​), some sss has pubReq(n,s,lat(csn),lav(csn))∈sent\mathsf{pubReq}(n, s, \mathrm{lat}(cs_n), \mathrm{lav}(cs_n)) \in \mathrm{sent}pubReq(n,s,lat(csn​),lav(csn​))∈sent, s.term=ct(csn)s.\mathrm{term} = \mathrm{ct}(cs_n)s.term=ct(csn​), s.version=lpv(csn)s.\mathrm{version} = \mathrm{lpv}(cs_n)s.version=lpv(csn​), lpc(csn)=s.lac\mathrm{lpc}(cs_n) = s.\mathrm{lac}lpc(csn​)=s.lac, and either lcc(csn)=s.lcc\mathrm{lcc}(cs_n) = s.\mathrm{lcc}lcc(csn​)=s.lcc, or [lcc(csn)=s.lac\mathrm{lcc}(cs_n) = s.\mathrm{lac}lcc(csn​)=s.lac, Committed(W,lat(csn),lav(csn))\mathrm{Committed}(W, \mathrm{lat}(cs_n), \mathrm{lav}(cs_n))Committed(W,lat(csn​),lav(csn​)) and lac(csn)=s.lac\mathrm{lac}(cs_n) = s.\mathrm{lac}lac(csn​)=s.lac].
  5. (leader_settled) if won(csn)\mathrm{won}(cs_n)won(csn​) and lpv(csn)=lav(csn)\mathrm{lpv}(cs_n) = \mathrm{lav}(cs_n)lpv(csn​)=lav(csn​): either lat(csn)=ct(csn)\mathrm{lat}(cs_n) = \mathrm{ct}(cs_n)lat(csn​)=ct(csn​) and lpc(csn)=lac(csn)\mathrm{lpc}(cs_n) = \mathrm{lac}(cs_n)lpc(csn​)=lac(csn​), or lat(csn)<ct(csn)\mathrm{lat}(cs_n) < \mathrm{ct}(cs_n)lat(csn​)<ct(csn​) and no pubReq(a,s,pt,pv)∈sent\mathsf{pubReq}(a, s, pt, pv) \in \mathrm{sent}pubReq(a,s,pt,pv)∈sent has s.term=ct(csn)s.\mathrm{term} = \mathrm{ct}(cs_n)s.term=ct(csn​).

Docstring remarks. The docstring of idle_of_not_won says that a node that has not won "has published nothing in its term"; the term only says lpv=0\mathrm{lpv} = 0lpv=0 and no publish votes, which a node that published in its current term and then restarted also satisfies. The docstring of leader_pubs speaks of "an elected node's publications of its term"; the term covers every publication of that term, whoever sent it (a stronger statement).

1. Declaration

Binders and hypotheses, in order:

  1. an implicit payload type VVV and a parameter record PPP;
  2. _hP : InitCompatible(P)\mathrm{InitCompatible}(P)InitCompatible(P);
  3. _hes : σP=\sigma_P =σP​= DEFAULT_INSTANCE, so that isElectionQuorumσP(cs,v)\mathrm{isElectionQuorum}_{\sigma_P}(cs, v)isElectionQuorumσP​​(cs,v) is just isQuorum(v,lcc(cs))∧isQuorum(v,lac(cs))\mathrm{isQuorum}(v, \mathrm{lcc}(cs)) \wedge \mathrm{isQuorum}(v, \mathrm{lac}(cs))isQuorum(v,lcc(cs))∧isQuorum(v,lac(cs));
  4. a cluster WWW and hinv : Inv(P,W)\mathrm{Inv}(P, W)Inv(P,W) — any cluster satisfying the invariant, reachable or not;
  5. nodes nnn and srcsrcsrc, a cluster state sss, pt∈Npt \in \mathbb{N}pt∈N, pv∈Zpv \in \mathbb{Z}pv∈Z, a coordination state cs′cs'cs′ and a publish response rrr;
  6. hsent : pubReq(src,s,pt,pv)∈sent\mathsf{pubReq}(src, s, pt, pv) \in \mathrm{sent}pubReq(src,s,pt,pv)∈sent;
  7. h : handlePublishRequest(csn,s)=ok (cs′,r)\mathrm{handlePublishRequest}(cs_n, s) = \mathrm{ok}\ (cs', r)handlePublishRequest(csn​,s)=ok (cs′,r), which holds exactly when s.term=ct(csn)s.\mathrm{term} = \mathrm{ct}(cs_n)s.term=ct(csn​) and either (regular branch) s.term≠lat(csn)s.\mathrm{term} \ne \mathrm{lat}(cs_n)s.term=lat(csn​) or s.version>lav(csn)s.\mathrm{version} > \mathrm{lav}(cs_n)s.version>lav(csn​), or (Zen1 branch) s.term=lat(csn)=0s.\mathrm{term} = \mathrm{lat}(cs_n) = 0s.term=lat(csn​)=0, s.version≤lav(csn)s.\mathrm{version} \le \mathrm{lav}(cs_n)s.version≤lav(csn​) and s.cmNode=some ms.\mathrm{cmNode} = \mathrm{some}\ ms.cmNode=some m with some m≠csn.las.cmNode\mathrm{some}\ m \ne cs_n.\mathrm{las}.\mathrm{cmNode}some m=csn​.las.cmNode; in both cases cs′cs'cs′ is csncs_ncsn​ with last accepted state sss, and r=⟨s.term,s.version⟩r = \langle s.\mathrm{term}, s.\mathrm{version}\rangler=⟨s.term,s.version⟩.

Items 5–7 are exactly the data and premises of the publish step.

Conclusion. Inv(P,W.set(n,cs′).send(pubResp(n,src,r)))\mathrm{Inv}(P, W.\mathrm{set}(n, cs').\mathrm{send}(\mathsf{pubResp}(n, src, r)))Inv(P,W.set(n,cs′).send(pubResp(n,src,r))): the invariant holds after node nnn adopts the published state sss as its last accepted state (all nine fields, payload included) and the response ⟨s.term,s.version⟩\langle s.\mathrm{term}, s.\mathrm{version}\rangle⟨s.term,s.version⟩ from nnn to the publisher srcsrcsrc is added to the sent messages.

2. What is not constrained

  • _hP and _hes are genuine hypotheses (the leading underscore only silences Lean's unused-variable linter; it does not remove the hypothesis). The lemma is stated only for parameters whose bootstrap configurations are compatible and whose election strategy is the default one.
  • WWW is not required to be reachable: the lemma asserts that this step preserves Inv\mathrm{Inv}Inv from every cluster satisfying Inv\mathrm{Inv}Inv (the inductive-step form). It is exactly as strong as the claim that Inv\mathrm{Inv}Inv is inductive for this step.
  • Apart from _hP, elig\mathrm{elig}elig and initConfs\mathrm{initConfs}initConfs are arbitrary, and so is the payload type VVV.
  • nnn is any node, eligible or not, possibly srcsrcsrc itself. The ghost parent (pt,pv)(pt, pv)(pt,pv) is not read by the handler.
  • Under hinv, conjunct 18 gives s.term≥1s.\mathrm{term} \ge 1s.term≥1, so the Zen1 branch cannot occur and the lemma's content is the regular branch. For an eligible nnn, conjunct 10 gives lat(csn)≤ct(csn)=s.term\mathrm{lat}(cs_n) \le \mathrm{ct}(cs_n) = s.\mathrm{term}lat(csn​)≤ct(csn​)=s.term, so the regular branch means that sss has a lexicographically larger key than nnn's accepted state; for a non-eligible nnn, whose accepted term may exceed its current term, it only means s.term≠lat(csn)s.\mathrm{term} \ne \mathrm{lat}(cs_n)s.term=lat(csn​) or s.version>lav(csn)s.\mathrm{version} > \mathrm{lav}(cs_n)s.version>lav(csn​).
  • Not vacuous. Take elig(n)  ⟺  n=0\mathrm{elig}(n) \iff n = 0elig(n)⟺n=0, initConfs={{0}}\mathrm{initConfs} = \{\{0\}\}initConfs={{0}} and the default strategy (then InitCompatible(P)\mathrm{InitCompatible}(P)InitCompatible(P) holds), and the initial cluster in which every node is init(dn(n),0,s0)\mathrm{init}(\mathrm{dn}(n), 0, s_0)init(dn(n),0,s0​) with s0s_0s0​ of term 0, version 0 and empty configurations. Node 0 can then, in order: (0) bootstrap with sbs_bsb​ (term 0, version 0, both configurations {0}\{0\}{0}); (1) handle the start-join (dn(0),1)(\mathrm{dn}(0), 1)(dn(0),1), sending j=⟨dn(0),dn(0),1,0,sb.mv⟩j = \langle \mathrm{dn}(0), \mathrm{dn}(0), 1, 0, s_b.\mathrm{mv}\ranglej=⟨dn(0),dn(0),1,0,sb​.mv⟩; (2) handle jjj and win with votes [0][0][0]; (3) publish s1s_1s1​ (term 1, version 1, both configurations {0}\{0\}{0}) with ghost parent (0,0)(0, 0)(0,0); (4) accept s1s_1s1​ and send the response ⟨1,1⟩\langle 1, 1\rangle⟨1,1⟩ to itself; (5) count that response, which is a publish quorum, and send commit(⟨dn(0),1,1⟩)\mathsf{commit}(\langle \mathrm{dn}(0), 1, 1\rangle)commit(⟨dn(0),1,1⟩); (6) apply that commit. By direct inspection, each cluster before one of these steps satisfies Inv\mathrm{Inv}Inv (most conjuncts are vacuous; the others are witnessed by the single join, publication, response and commit). The hypotheses of this lemma hold for step (4).

3. Plain-language summary

A node accepting a sent publication of its current term (newer than its accepted state, in the only branch the invariant leaves possible) and answering the publisher preserves the invariant.

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