• feat(cluster): join as non-voter, auto-promote/demote by reachability
    Release / version (push) Canceled after 0s
    Release / frontend (push) Canceled after 0s
    Release / linux (amd64) (push) Canceled after 0s
    Release / linux (arm64) (push) Canceled after 0s
    Release / windows (push) Canceled after 0s
    Release / macos (push) Canceled after 0s
    Release / release (push) Canceled after 0s
    Build and Push Docker Image / build-and-push (push) Canceled after 0s

    LastSkywalker released this 2026-06-20 22:57:05 +03:00 | 128 commits to main since this release

    A new node used to be added straight as a Raft voter, so a botched join (e.g.
    an advertise address that's unreachable or later goes down) could drag the
    cluster below quorum and wedge control-plane writes. Nodes now join as
    NON-voters — they replicate the full log/snapshot and serve reads, but don't
    count toward quorum — so a bad joiner can never cost the cluster its write
    availability.

    A leader-side membership manager (internal/cluster/raft/membership.go, ~10s)
    reconciles suffrage by TCP-probing each peer's advertised Raft addr:

    • promotes a non-voter to voter once it has stayed reachable >= 20s
    • demotes a voter unreachable >= 90s back to non-voter to restore quorum, but
      only while a majority is still reachable (the demote config-change must
      commit) — so the genuine 2-node 'lost a voter' case is left to the manual
      'Recover this node' action instead of churning uncommittable changes.

    The decision policy is a pure function (decideMembership) with unit tests; the
    manager reads the swappable Service so one loop survives a wipe-state
    re-activation. Adds AddNonvoter/DemoteVoter across the Service interface, the
    real Node, the disabled/swappable wrappers and the bridge test fake.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01U3LvJu9qZfspPr21TW9ptv

    Downloads