March Docs

Membership

Membership: SWIM-style cluster membership with a CRDT member view (P1 L3).

Each node is tracked as { status, incarnation }. A node refutes a false suspicion/death by incrementing its own incarnation and re-announcing Alive, so the merge rule is: higher incarnation wins; at the same incarnation the more-severe status wins (Dead > Suspect > Alive). That order makes merge a lexicographic max over (incarnation, severity) — associative, commutative, and idempotent — so gossiped member views converge regardless of order (the anti-entropy CRDT join for the small member set; no Merkle needed).

This is the pure membership state + merge + transitions; the SWIM probe loop (direct/indirect ping, suspicion timers) drives it from the runtime layer.

Types

typeMemberStatusMemberStatus = Alive | Suspect | Dead#
typeMemberMember = { node_id : String, status : MemberStatus, incarnation : Int }#
typeMembersMembers = Members(Map(String, Member))#

Functions

fnalivealive(node_id : String, incarnation : Int) : Member#
fnalive_membersalive_members(ms : Members) : List(Member)#
fndeaddead(node_id : String, incarnation : Int) : Member#
fnemptyempty() : Members do Members(Map.empty()) end#
fngetget(ms : Members, node_id : String) : Option(Member)#
fnmembersmembers(ms : Members) : List(Member)#
fnmergemerge(a : Members, b : Members) : Members#
fnobserveobserve(ms : Members, obs : Member) : Members#
fnsizesize(ms : Members) : Int do match ms do Members(m) -> Map.size(m) end end#
fnsuspectsuspect(node_id : String, incarnation : Int) : Member#