Skip to content

Prove B1 and B3, and make the TLA+ model terminate#3

Merged
orange-dot merged 2 commits into
mainfrom
b2-evaluate-ballot-proof
Jul 4, 2026
Merged

Prove B1 and B3, and make the TLA+ model terminate#3
orange-dot merged 2 commits into
mainfrom
b2-evaluate-ballot-proof

Conversation

@orange-dot

Copy link
Copy Markdown
Owner

What this adds

Follow-up verification work on top of the merged B2 proof (PR #2). Two commits:

a74b014 — Make the TLA+ check terminate: symmetry + bounded state constraint

The raw 3-node protocol model never terminated (msgs grows monotonically →
100M+ states). Added SYMMETRY Perms == Permutations(Nodes) plus a
CONSTRAINT StateConstraint (MaxBallots = 1 — the decisive lever, since every
invariant is per-ballot — MaxInflight = 6, MaxInhibit = 2). TLC now terminates
green: 776,646 distinct states, depth 18, ~1:48 on the 8 GiB host.
Mutation-tested for teeth: removing the on_vote inhibition guard makes it fail
in ~4 s.

8929ba5 — Prove B1 and B3 (heartbeat init totality + health refinement)

Discharges the two remaining Track-B targets in EkkHeartbeat.thy:

  • B1 — ekk_heartbeat_init_verif_ok (total correctness, ⦃ ⦄!): the lifted
    init function returns EKK_OK on valid arguments; the 64-iteration
    neighbor-zeroing loop (six heap writes/iteration) is proved terminating and
    guard-safe. The invariant carries only the per-slot validity the guards
    consume; measure 0x40 - i gives termination. Annotating with
    whileLoop_add_inv is what keeps wp from unfolding the fixpoint.
  • B3 — health-transition refinement: health_transition_valid_correct proves
    the C validator computes exactly the abstract valid_health_edge relation;
    health_step_accepted shows every abstract health_step transition is
    accepted; set_neighbor_health_verif_sets_health proves the setter writes
    exactly the health field. This re-proves the TLA+ health-machine safety
    property at the C level.

VERIFICATION_PLAN.md phases B1 and B3 marked DONE with the discharged lemma
names, matching the existing B2 status block.

Verification state

  • EkkVerification session builds green — B1 + B2 + B3 all machine-checked.
  • No sorry, no quick_and_dirty (gated by scripts/check-no-sorry.sh).
  • TLA+ gate green (scripts/check-tla.sh).

Test / gate status

Check Result
isabelle build … EkkVerification ✅ green (~22 s incremental)
scripts/check-no-sorry.sh ✅ OK
scripts/check-tla.sh (TLC bounded) ✅ 776,646 states, no violation

🤖 Generated with Claude Code

orange-dot and others added 2 commits July 3, 2026 22:36
The raw 3-node model never terminated (msgs grows monotonically): a
12-min run hit 100M+ states and kept going. Two mechanisms fix it:

- SYMMETRY Perms == Permutations(Nodes): nodes are interchangeable and
  every invariant is quantified over all nodes, so permuting them is a
  sound symmetry for the safety invariants we check.
- CONSTRAINT StateConstraint: MaxBallots=1, MaxInflight=6, MaxInhibit=2.
  One ballot suffices because every invariant here is per-ballot,
  including the inhibition/approval race.

Result: the check completes green — 776,646 distinct states, depth 18,
~1min 48s. The decisive lever was MaxBallots=1, not symmetry alone.

The bound has teeth: removing the on_vote fix from the model (the
p \notin inhibited[p] guard on RecvVote) makes TLC report
NoApproveWhileInhibited violated in ~4s at exactly these bounds, so a
passing run is meaningful rather than vacuous.

check-tla.sh now terminates green in ~2 min and is a usable gate;
docs/tla/README.md, README.md and DEVELOPMENT.md updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ment

Discharges the two remaining Track-B proof targets in EkkHeartbeat.thy,
so EkkVerification now machine-checks B1+B2+B3 (green in ~22 s, no sorry,
no quick_and_dirty).

B1 (ekk_heartbeat_init_verif_ok, total correctness): the lifted init
function returns EKK_OK on valid arguments. The 64-iteration neighbor-
zeroing loop (six heap writes per iteration) is proved terminating and
guard-safe. The postcondition is heap-independent, so the loop invariant
carries only the validity facts the per-iteration guards consume; the
measure 0x40 - i gives termination. Annotating with whileLoop_add_inv is
what keeps wp from unfolding the fixpoint and blowing the stack.
is_valid survives the value-heap writes because is_valid_AnonStructN' and
heap_AnonStructN'_update are distinct lifted_globals record fields
(lifted_globals.simps), leaving only word-arithmetic residues (unat_arith).

B3 (health-transition refinement): health_transition_valid_correct proves
the C validator computes exactly the abstract valid_health_edge relation,
health_step_accepted then shows every abstract health_step transition is
accepted, and set_neighbor_health_verif_sets_health proves the setter
writes exactly the health field. This re-proves the TLA+ health-machine
safety property at the C level.

VERIFICATION_PLAN.md: mark phases B1 and B3 DONE with the discharged
lemma names, matching the B2 status block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@orange-dot orange-dot merged commit 5c726d8 into main Jul 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant