Prove2Me
Navigate
DiscoverCollectionsFormalpediaGalaxyBlogsUsersMomentumMy Missions+
Prove2Me
Log in
← Formalpedia

Votes keep counting during a stable election: once in the election term, the candidate accepts every voter's join for that term whenever it arrives

Proved
OpenSearchProgress.election_vote_accepted

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

cluster-coordinationconsensuselasticsearchformal-verificationleader-electionlivenessmessage-passingopensearchpartial-synchronypaxosprogresspublicationraftzen2

For every payload type V, cluster parameters P with P.InitCompatible and P.strategy = ElectionStrategy.DEFAULT_INSTANCE, every cluster W with Reachable P W, every node c, term t and list of node ids Q such that

  • c and every member of Q are cluster-manager-eligible: P.eligible c = true and P.eligible q = true for every q ∈ Q;
  • c and every member of Q are in a term below t: (W.node q).currentTerm < t for q = c and for every q ∈ Q;
  • every member of Q has an accepted state no fresher than c's, compared as handleJoin compares a join with the node's state: (W.node q).lastAcceptedTerm < (W.node c).lastAcceptedTerm, or the two terms are equal and (W.node q).lastAcceptedVersionOrMetadataVersion ≤ (W.node c).lastAcceptedVersionOrMetadataVersion;
  • c has a nonempty last accepted configuration (as after a bootstrap or after accepting a publication): (W.node c).lastAcceptedConfiguration.isEmpty = false;

and every cluster W' with ElectionExec P c t Q (W.node c).lastAcceptedTerm (W.node c).lastAcceptedVersion W W' (a stable election period for c, t and Q starting at W) in which c is in term t, (W'.node c).currentTerm = t: for every join j with W'.sent (.join j), j.sourceNode.id ∈ Q, j.targetNode.id = c and j.term = t, there are a state cs' and a flag b with (W'.node c).handleJoin P.strategy j = .ok (cs', b) and j.sourceNode.id ∈ cs'.joinVotes.nodes.

Whatever has happened since the period started (deliveries in any order, duplicates, the steps the period allows the other nodes, commits at c and at the voters), once c is in term t each voter's join for t is accepted by c whenever it is delivered, and the vote is counted. A join delivered while c is still below t is not covered, and is rejected (the terms differ): in the model a voter may handle c's start-join request for t before c does.

Preamble
import Definitions.Def_OpenSearchProgress
open OpenSearchCoordination OpenSearchSafety OpenSearchProgress
Formal statement
theorem OpenSearchProgress.election_vote_accepted {V : Type} (P : Params) (hP : P.InitCompatible)
    (hes : P.strategy = ElectionStrategy.DEFAULT_INSTANCE) (W : Cluster V) (hW : Reachable P W)
    (c : NodeId) (t : Nat) (Q : List NodeId) (hc : P.eligible c = true) (hQ : ∀ q ∈ Q, P.eligible q = true)
    (hterm : ∀ q, q = c ∨ q ∈ Q → (W.node q).currentTerm < t)
    (hfresh : ∀ q ∈ Q, (W.node q).lastAcceptedTerm < (W.node c).lastAcceptedTerm ∨
      ((W.node q).lastAcceptedTerm = (W.node c).lastAcceptedTerm ∧
        (W.node q).lastAcceptedVersionOrMetadataVersion ≤ (W.node c).lastAcceptedVersionOrMetadataVersion))
    (hconf : (W.node c).lastAcceptedConfiguration.isEmpty = false)
    (W' : Cluster V)
    (hE : ElectionExec P c t Q (W.node c).lastAcceptedTerm (W.node c).lastAcceptedVersion W W')
    (hct : (W'.node c).currentTerm = t) (j : Join) (hj : W'.sent (.join j)) (hsrc : j.sourceNode.id ∈ Q)
    (htgt : j.targetNode.id = c) (hjt : j.term = t) :
    ∃ cs' b, (W'.node c).handleJoin P.strategy j = .ok (cs', b) ∧ j.sourceNode.id ∈ cs'.joinVotes.nodes := by sorry
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: OpenSearchProgress.election_vote_accepted

Notions used

The full expansions are in readback_Def_OpenSearchProgress.md. This section repeats what the statement needs.

Node-state notation. For a node state σ\sigmaσ:

  • cur(σ)\mathrm{cur}(\sigma)cur(σ) is the current term (a natural number) and acc(σ)\mathrm{acc}(\sigma)acc(σ) the last accepted cluster state;
  • lat(σ)\mathrm{lat}(\sigma)lat(σ) and lav(σ)\mathrm{lav}(\sigma)lav(σ) are that state's term and (integer) version;
  • lavm(σ)\mathrm{lavm}(\sigma)lavm(σ) is its metadata version if its term is 0, and its version otherwise;
  • lcc(σ)\mathrm{lcc}(\sigma)lcc(σ) and lac(σ)\mathrm{lac}(\sigma)lac(σ) are its last committed and last accepted voting configurations;
  • jv(σ)\mathrm{jv}(\sigma)jv(σ) is the list of ids in the join votes, sj(σ)\mathrm{sj}(\sigma)sj(σ) is startedJoinSinceLastReboot, won(σ)\mathrm{won}(\sigma)won(σ) is electionWon and lpv(σ)\mathrm{lpv}(\sigma)lpv(σ) is lastPublishedVersion.

(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′).

Clusters and reachability. A cluster WWW assigns a node state W.node(n)W.\mathrm{node}(n)W.node(n) to every node id, and has a set W.sentW.\mathrm{sent}W.sent of messages that only ever grows. Reachable(P,W)\mathrm{Reachable}(P, W)Reachable(P,W) means WWW is obtained from an initial cluster by finitely many Steps. In an initial cluster every node nnn is init(P.dn(n),0,s)\mathrm{init}(P.\mathrm{dn}(n), 0, s)init(P.dn(n),0,s) with sss a term-0 state with empty configurations, and no message has been sent. A Step is one successful handler call at one node, with its output sent; the kinds are bootstrap, start-join, join, client value, publish, respond, commit, restart of an eligible node (term kept), and restartAny of a non-eligible node (any term, any state).

The election period. ElectionExec(P,c,t,Q,kt,kv)(W,W′)\mathrm{ElectionExec}(P, c, t, Q, k_t, k_v)(W, W')ElectionExec(P,c,t,Q,kt​,kv​)(W,W′) is a finite, possibly empty, sequence of election steps from WWW to W′W'W′. Each election step is a Step of one of the following kinds, where σ\sigmaσ is the acting node's state:

  • bootstrap of a node n∉{c}∪Qn \notin \{c\} \cup Qn∈/{c}∪Q;
  • start-join at any node nnn, on a request from an eligible srcsrcsrc in term t′t't′ with t′<t∨(src=c∧t′=t)t' < t \lor (src = c \land t' = t)t′<t∨(src=c∧t′=t):
    • it succeeds iff t′>cur(σ)t' > \mathrm{cur}(\sigma)t′>cur(σ);
    • nnn then moves to term t′t't′, with sj:=true\mathrm{sj} := \mathrm{true}sj:=true, won:=false\mathrm{won} := \mathrm{false}won:=false, votes cleared and lpv:=0\mathrm{lpv} := 0lpv:=0;
    • nnn sends the join ⟨ln(σ),P.dn(src),t′,lat(σ),lavm(σ)⟩\langle \mathrm{ln}(\sigma), P.\mathrm{dn}(src), t', \mathrm{lat}(\sigma), \mathrm{lavm}(\sigma)\rangle⟨ln(σ),P.dn(src),t′,lat(σ),lavm(σ)⟩;
  • join: any node handles any sent join whose target id is that node;
  • publish: any node nnn handles any sent publication request (src,s,pt,pv)(src, s, pt, pv)(src,s,pt,pv), provided that (s.term,s.version)⪯(kt,kv)(s.\mathrm{term}, s.\mathrm{version}) \preceq (k_t, k_v)(s.term,s.version)⪯(kt​,kv​) whenever n=cn = cn=c or n∈Qn \in Qn∈Q. The response goes to srcsrcsrc;
  • respond: any node handles any sent publish response addressed to it, and a resulting commit is sent;
  • commit: any node handles any sent commit;
  • restart of an eligible node n∉{c}∪Qn \notin \{c\} \cup Qn∈/{c}∪Q;
  • restartAny of a non-eligible node.

There is no client-value step, so no new publication request is sent during the period. In this theorem, kt=lat(W.node c)k_t = \mathrm{lat}(W.\mathrm{node}\ c)kt​=lat(W.node c) and kv=lav(W.node c)k_v = \mathrm{lav}(W.\mathrm{node}\ c)kv​=lav(W.node c).

handleJoin(es, σ, j) succeeds iff all of the following hold, checked in this order:

  1. j.term=cur(σ)j.\mathrm{term} = \mathrm{cur}(\sigma)j.term=cur(σ);
  2. sj(σ)=true\mathrm{sj}(\sigma) = \mathrm{true}sj(σ)=true;
  3. j.lastAcceptedTerm≤lat(σ)j.\mathrm{lastAcceptedTerm} \le \mathrm{lat}(\sigma)j.lastAcceptedTerm≤lat(σ);
  4. not (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. lac(σ)\mathrm{lac}(\sigma)lac(σ) is non-empty.

On success it returns (σ2,added)(\sigma_2, \mathit{added})(σ2​,added). The new join-vote list is addVote(jv(σ),j.sourceNode).1\mathrm{addVote}(\mathrm{jv}(\sigma), j.\mathrm{sourceNode}).1addVote(jv(σ),j.sourceNode).1: j.sourceNode.idj.\mathrm{sourceNode}.\mathrm{id}j.sourceNode.id is prepended if j.sourceNode.isClusterManagerNode=truej.\mathrm{sourceNode}.\mathrm{isClusterManagerNode} = \mathrm{true}j.sourceNode.isClusterManagerNode=true and the id is not yet present, and the list is unchanged otherwise. σ2\sigma_2σ2​'s won\mathrm{won}won is recomputed, and its lpv\mathrm{lpv}lpv may be reset. The strategy eseses influences only won\mathrm{won}won and lpv\mathrm{lpv}lpv, not success and not the vote list.

1. Declaration

The universally quantified variables and hypotheses, in order:

NameType / statement
V (implicit)Type: the payload type
PParams
hPP.InitCompatibleP.\mathrm{InitCompatible}P.InitCompatible: for bootstrap configurations X,YX, YX,Y and lists q1,q2q_1, q_2q1​,q2​ of eligible ids, Quorum(X,q1)∧Quorum(Y,q2)→q1∩q2≠∅\mathrm{Quorum}(X, q_1) \land \mathrm{Quorum}(Y, q_2) \to q_1 \cap q_2 \ne \emptysetQuorum(X,q1​)∧Quorum(Y,q2​)→q1​∩q2​=∅
hesP.strategy=DEFAULT_INSTANCEP.\mathrm{strategy} = \mathrm{DEFAULT\_INSTANCE}P.strategy=DEFAULT_INSTANCE (no additional quorum constraint)
WCluster V: the cluster at the start of the period
hWReachable(P,W)\mathrm{Reachable}(P, W)Reachable(P,W)
cnode id: the candidate
tN\mathbb{N}N: the election term
Qlist of node ids: the voters
hcP.eligible(c)=trueP.\mathrm{eligible}(c) = \mathrm{true}P.eligible(c)=true
hQ∀q∈Q, P.eligible(q)=true\forall q \in Q,\ P.\mathrm{eligible}(q) = \mathrm{true}∀q∈Q, P.eligible(q)=true
hterm∀q, (q=c∨q∈Q)→cur(W.node q)<t\forall q,\ (q = c \lor q \in Q) \to \mathrm{cur}(W.\mathrm{node}\ q) < t∀q, (q=c∨q∈Q)→cur(W.node q)<t
hfresh∀q∈Q, lat(W.node q)<lat(W.node c)∨(lat(W.node q)=lat(W.node c)∧lavm(W.node q)≤lavm(W.node c))\forall q \in Q,\ \mathrm{lat}(W.\mathrm{node}\ q) < \mathrm{lat}(W.\mathrm{node}\ c) \lor \big(\mathrm{lat}(W.\mathrm{node}\ q) = \mathrm{lat}(W.\mathrm{node}\ c) \land \mathrm{lavm}(W.\mathrm{node}\ q) \le \mathrm{lavm}(W.\mathrm{node}\ c)\big)∀q∈Q, lat(W.node q)<lat(W.node c)∨(lat(W.node q)=lat(W.node c)∧lavm(W.node q)≤lavm(W.node c))
hconflac(W.node c)\mathrm{lac}(W.\mathrm{node}\ c)lac(W.node c) is non-empty (isEmpty = false)
W'Cluster V
hEElectionExec(P,c,t,Q,lat(W.node c),lav(W.node c))(W,W′)\mathrm{ElectionExec}(P, c, t, Q, \mathrm{lat}(W.\mathrm{node}\ c), \mathrm{lav}(W.\mathrm{node}\ c))(W, W')ElectionExec(P,c,t,Q,lat(W.node c),lav(W.node c))(W,W′)
hctcur(W′.node c)=t\mathrm{cur}(W'.\mathrm{node}\ c) = tcur(W′.node c)=t
jJoin
hjjoin(j)∈W′.sent\mathrm{join}(j) \in W'.\mathrm{sent}join(j)∈W′.sent
hsrcj.sourceNode.id∈Qj.\mathrm{sourceNode}.\mathrm{id} \in Qj.sourceNode.id∈Q
htgtj.targetNode.id=cj.\mathrm{targetNode}.\mathrm{id} = cj.targetNode.id=c
hjtj.term=tj.\mathrm{term} = tj.term=t

Conclusion.

∃ cs′ b,handleJoin(P.strategy,W′.node c,j)=ok(cs′,b)  ∧  j.sourceNode.id∈cs′.joinVotes.nodes.\exists\, cs'\ b,\quad \mathrm{handleJoin}(P.\mathrm{strategy}, W'.\mathrm{node}\ c, j) = \mathrm{ok}(cs', b) \;\land\; j.\mathrm{sourceNode}.\mathrm{id} \in cs'.\mathrm{joinVotes}.\mathrm{nodes}.∃cs′ b,handleJoin(P.strategy,W′.node c,j)=ok(cs′,b)∧j.sourceNode.id∈cs′.joinVotes.nodes.

Unfolded. Write σ′=W′.node c\sigma' = W'.\mathrm{node}\ cσ′=W′.node c. The conclusion says that all five guards of handleJoin pass at σ′\sigma'σ′ for jjj, and that the source's id is in the resulting vote list. Guard 1 (j.term=cur(σ′)j.\mathrm{term} = \mathrm{cur}(\sigma')j.term=cur(σ′)) follows directly from hjt and hct. The remaining content is:

  • (ii) sj(σ′)=true\mathrm{sj}(\sigma') = \mathrm{true}sj(σ′)=true;
  • (iii) j.lastAcceptedTerm≤lat(σ′)j.\mathrm{lastAcceptedTerm} \le \mathrm{lat}(\sigma')j.lastAcceptedTerm≤lat(σ′);
  • (iv) j.lastAcceptedTerm=lat(σ′)→j.lastAcceptedVersion≤lavm(σ′)j.\mathrm{lastAcceptedTerm} = \mathrm{lat}(\sigma') \to j.\mathrm{lastAcceptedVersion} \le \mathrm{lavm}(\sigma')j.lastAcceptedTerm=lat(σ′)→j.lastAcceptedVersion≤lavm(σ′);
  • (v) lac(σ′)\mathrm{lac}(\sigma')lac(σ′) is non-empty;
  • (vi) j.sourceNode.isClusterManagerNode=truej.\mathrm{sourceNode}.\mathrm{isClusterManagerNode} = \mathrm{true}j.sourceNode.isClusterManagerNode=true, or j.sourceNode.id∈jv(σ′)j.\mathrm{sourceNode}.\mathrm{id} \in \mathrm{jv}(\sigma')j.sourceNode.id∈jv(σ′) already.

cs′cs'cs′ is then determined as the handler's output, and bbb records whether the vote was new. Nothing is claimed about cs′.electionWoncs'.\mathrm{electionWon}cs′.electionWon, cs′.lastPublishedVersioncs'.\mathrm{lastPublishedVersion}cs′.lastPublishedVersion or bbb.

In words: at the end of any stable election period in which ccc has reached term ttt, every term-ttt join that a voter has addressed to ccc can be handled by ccc without rejection, and handling it records that voter's vote.

2. What is not constrained

  • QQQ.

    • It is not required to be a quorum of anything; this theorem has no quorum hypothesis.
    • It may contain ccc. hfresh is then trivial for q=cq = cq=c, and jjj may be ccc's own join to itself.
    • It may contain duplicates, or nodes outside ccc's configurations.
    • If Q=[ ]Q = [\,]Q=[], hsrc cannot hold and the statement is vacuous. That is the only degenerate case.
  • Other nodes. Nodes outside {c}∪Q\{c\} \cup Q{c}∪Q are unconstrained at WWW: any term (even ≥t\ge t≥t), any accepted state, possibly elected in some term. The period allows them every step except start-joins to terms ≥t\ge t≥t other than ccc's request for ttt, and new publications.

  • The join jjj. Only its source id, target id and term are constrained. Its lastAcceptedTerm\mathrm{lastAcceptedTerm}lastAcceptedTerm and lastAcceptedVersion\mathrm{lastAcceptedVersion}lastAcceptedVersion, and its source node's cluster-manager flag, are not hypotheses. So claims (iii), (iv) and (vi) assert facts about whichever term-ttt joins from voters to ccc can exist in W′W'W′. The target node's flag is not constrained either, only its id.

    The following is derived by me, not stated. In a reachable cluster, the joins a voter has produced by WWW have terms ≤\le≤ its current term <t< t<t. So jjj was produced during the period, by the voter handling ccc's request for ttt; in the period, term ttt can only be entered on ccc's request. Its source node is P.dn(q)P.\mathrm{dn}(q)P.dn(q), which is flagged cluster-manager because qqq is eligible.

  • Whether jjj was already handled. ccc may already have handled jjj during the period. The claim is that handling it (again) at W′W'W′ succeeds and leaves the vote present. The statement is about the pure handler applied to W′.node cW'.\mathrm{node}\ cW′.node c, not about a step. Since the period does not restrict join steps, and jjj is sent with target id ccc, the handler's success is exactly what an election join step from W′W'W′ needs.

  • Freshness. hfresh compares voters with ccc using lavm\mathrm{lavm}lavm, exactly handleJoin's measure, but only at WWW. The period's freshness bound (kt,kv)(k_t, k_v)(kt​,kv​) uses lav\mathrm{lav}lav (version) instead. The two coincide for ccc when lat(W.node c)≥1\mathrm{lat}(W.\mathrm{node}\ c) \ge 1lat(W.node c)≥1. When lat(W.node c)=0\mathrm{lat}(W.\mathrm{node}\ c) = 0lat(W.node c)=0, the period lets ccc and the voters accept no publication at all (no term-0 publication exists in a reachable cluster), so the voters' metadata versions cannot move. The theorem thus implicitly claims that the start-of-period comparison survives every stable period, including voters' acceptances of publications whose key is ⪯(kt,kv)\preceq (k_t, k_v)⪯(kt​,kv​).

  • ccc's configurations. Only hconf (lac\mathrm{lac}lac non-empty at WWW) is assumed. Nothing is assumed about lcc(W.node c)\mathrm{lcc}(W.\mathrm{node}\ c)lcc(W.node c), and nothing links ccc's configurations to QQQ. Claim (v) is about W′W'W′.

  • hes, hP. These are global assumptions on PPP. The conclusion's form does not depend on the strategy, which affects only cs′.woncs'.\mathrm{won}cs′.won and cs′.lpvcs'.\mathrm{lpv}cs′.lpv; neither is mentioned.

  • Empty execution. hterm with hct forces t≥1t \ge 1t≥1 and rules out the empty execution: cur(W.node c)<t=cur(W′.node c)\mathrm{cur}(W.\mathrm{node}\ c) < t = \mathrm{cur}(W'.\mathrm{node}\ c)cur(W.node c)<t=cur(W′.node c), so at least ccc's start-join into ttt occurs, on a request whose requester is ccc.

  • Vacuity. The hypotheses are jointly satisfiable. Example:

    • eligible nodes 1 and 2;
    • P.initConfs(X)  ⟺  X={1,2}P.\mathrm{initConfs}(X) \iff X = \{1, 2\}P.initConfs(X)⟺X={1,2}. InitCompatible holds, because every quorum of {1,2}\{1, 2\}{1,2} contains both nodes;
    • the default strategy;
    • both nodes bootstrapped, from an initial cluster, with the same term-0 state: version 0, metadata version 0, configurations {1,2}\{1, 2\}{1,2};
    • c=1c = 1c=1, Q=[2]Q = [2]Q=[2], t=1t = 1t=1.

    In the period, node 1 start-joins on its own request for term 1, and node 2 start-joins on node 1's request for term 1, sending j=⟨P.dn 2,P.dn 1,1,0,0⟩j = \langle P.\mathrm{dn}\ 2, P.\mathrm{dn}\ 1, 1, 0, 0\ranglej=⟨P.dn 2,P.dn 1,1,0,0⟩. Every hypothesis holds at W′W'W′, and the conclusion is a real claim.

3. Plain-language summary

Start from any reachable cluster in which:

  • the candidate ccc and the voters QQQ are eligible and in terms below ttt;
  • ccc has a non-empty accepted configuration;
  • no voter's last accepted state is fresher than ccc's, in handleJoin's comparison.

Then run any stable election period: no start-join anywhere to a term ≥t\ge t≥t except on ccc's request for ttt, no new publications, no restarts or bootstraps of ccc or of voters, and ccc and the voters accept nothing fresher than ccc's starting state. If ccc has reached term ttt, then ccc can accept any term-ttt join that a voter has sent it, and accepting it records the voter's vote. The theorem does not claim that a voter's join is ever sent, nor that ccc wins.

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