title: Implement test generators (test/generators.ml)

description:
Create QCheck generators for property-based testing.

## Generators to implement

### Basic types
- `gen_node_id : node_id QCheck.Gen.t`
- `gen_incarnation : incarnation QCheck.Gen.t`
- `gen_member_state : member_state QCheck.Gen.t`

### Node types
- `gen_node_info : node_info QCheck.Gen.t`
  - Generate valid addresses
  - Random metadata strings

### Protocol messages
- `gen_ping : protocol_msg QCheck.Gen.t`
- `gen_ping_req : protocol_msg QCheck.Gen.t`
- `gen_ack : protocol_msg QCheck.Gen.t`
- `gen_alive : protocol_msg QCheck.Gen.t`
- `gen_suspect : protocol_msg QCheck.Gen.t`
- `gen_dead : protocol_msg QCheck.Gen.t`
- `gen_user_msg : protocol_msg QCheck.Gen.t`
- `gen_protocol_msg : protocol_msg QCheck.Gen.t` (uniform choice)

### Packets
- `gen_packet : packet QCheck.Gen.t`
  - Valid cluster names
  - Primary + piggyback messages

### Binary data
- `gen_cstruct : Cstruct.t QCheck.Gen.t`
  - Various sizes

### Arbitrary instances
- `arb_*` wrappers with shrinkers where useful

## Design constraints
- Use QCheck.Gen combinators
- Generate valid data by construction
- Include edge cases (empty strings, max values)

acceptance_criteria:
- All message types have generators
- Generators produce valid data
- Good distribution of test cases

status: closed

issue_type: task

created_by: gdiazlo

close_reason: Implemented all QCheck generators for SWIM types

labels: qcheck test

type: blocks

depends_on_id: swim-wdc

type: parent-child


title: Implement crypto tests (test/test_crypto.ml)

description:
Property-based and unit tests for crypto module.

## Property tests

### Roundtrip
- `test_crypto_roundtrip` - encrypt then decrypt equals original
- Test with various data sizes

### Key validation
- `test_invalid_key_length_rejected`
- Test 31, 32, 33 byte keys

## Unit tests

### Encryption
- Test output size = input size + overhead (28 bytes)
- Test nonce is prepended
- Test different plaintexts produce different ciphertexts

### Decryption
- Test successful decryption
- Test tampered ciphertext fails
- Test truncated data fails
- Test wrong key fails

### Key initialization
- Test valid 32-byte key
- Test invalid lengths rejected

## Security tests
- Verify nonces are unique (probabilistic)
- Verify ciphertext differs from plaintext

## Design constraints
- Use QCheck for property tests
- Test all error paths
- Don't expose key material in errors

acceptance_criteria:
- All property tests pass
- All unit tests pass
- Security properties verified
- Error handling tested

close_reason: Implemented crypto property and unit tests - all 13 tests passing

labels: crypto security test


title: Refactor codec to use Cstruct/Bigstringaf instead of string

description: Current codec uses string for protocol buffers which causes unnecessary memory copies. Should use Cstruct or Bigstringaf buffers directly for zero-copy encoding/decoding. Key areas: encode_internal_msg, decode_internal_msg, Wire type conversions.

close_reason: Refactored codec to use Cstruct for zero-copy operations. All tests pass.


title: Make wire protocol compatible with HashiCorp memberlist

notes:
Session progress:
- Fixed Ping message: node field now correctly set to target (not sender)
- Added conditional encryption based on config.encryption_enabled
- Fixed cluster name check (memberlist uses empty string)
- Verified unencrypted UDP ping/ack works between OCaml and Go
- All 78 tests pass

Remaining work:
- Encryption format needs investigation (pkcs7decode error from Go)
- May need TCP support for full join handshake
- Buffer refactoring tracked in swim-6ea

issue_type: feature


title: Implement transport.ml - Eio UDP/TCP networking

description:
Implement network transport layer using Eio.

## UDP Transport

### Functions
- `create_udp_socket : Eio.Net.t -> addr:string -> port:int -> Eio.Net.datagram_socket`
- `send_udp : Eio.Net.datagram_socket -> Eio.Net.Sockaddr.datagram -> Cstruct.t -> unit`
- `recv_udp : Eio.Net.datagram_socket -> Cstruct.t -> (int * Eio.Net.Sockaddr.datagram)`

## TCP Transport (for large payloads)

### Functions
- `create_tcp_listener : Eio.Net.t -> addr:string -> port:int -> Eio.Net.listening_socket`
- `connect_tcp : Eio.Net.t -> addr:Eio.Net.Sockaddr.stream -> timeout:float -> clock:Eio.Time.clock -> (Eio.Net.stream_socket, send_error) result`
- `send_tcp : Eio.Net.stream_socket -> Cstruct.t -> (unit, send_error) result`
- `recv_tcp : Eio.Net.stream_socket -> Cstruct.t -> (int, [`Connection_reset]) result`

## Address parsing
- `parse_addr : string -> (Eio.Net.Sockaddr.datagram, [`Invalid_addr]) result`
  - Parse "host:port" format

## Design constraints
- Use Eio.Net for all I/O
- No blocking except Eio primitives
- Proper error handling via Result
- Support for IPv4 and IPv6

acceptance_criteria:
- UDP send/recv works
- TCP connect/send/recv works
- Proper error handling
- Address parsing robust

close_reason: Implemented UDP and TCP transport with Eio.Net, plus address parsing (mli skipped due to complex Eio row types)

labels: core eio transport

depends_on_id: swim-oun


title: Implement benchmarks (bench/)

description:
Performance benchmarks for critical paths.

## bench/bench_codec.ml
- `bench_encode_ping` - encoding a Ping message
- `bench_encode_packet` - full packet with piggyback
- `bench_decode_packet` - decoding a packet
- `bench_encoded_size` - size calculation

## bench/bench_crypto.ml
- `bench_encrypt` - encryption throughput
- `bench_decrypt` - decryption throughput
- `bench_key_init` - key initialization

## bench/bench_throughput.ml
- `bench_broadcast_throughput` - messages/second
- `bench_probe_cycle` - probe cycle latency
- `bench_concurrent_probes` - parallel probe handling

## bench/bench_allocations.ml
- `bench_probe_cycle_allocations` - count allocations per probe
- `bench_buffer_reuse_rate` - % of buffers reused
- `bench_message_handling_allocations` - allocations per message

## Performance targets to verify
- < 5 allocations per probe cycle
- > 95% buffer reuse rate
- < 3 seconds failure detection
- > 10,000 broadcast/sec
- < 1% CPU idle, < 5% under load

## Design constraints
- Use core_bench or similar
- Warm up before measuring
- Multiple iterations for stability
- Report with confidence intervals

acceptance_criteria:
- All benchmarks run
- Performance targets documented
- Regression detection possible
- Results reproducible

status: open

labels: bench performance

issue_id: swim-don

depends_on_id: swim-zsi


title: Implement pending_acks.ml - Ack tracking with promises

description:
Implement pending ack tracking for probe responses.

## Pending_acks module
```ocaml
type waiter = {
  promise : string option Eio.Promise.t;
  resolver : string option Eio.Promise.u;
}

type t = {
  table : (int, waiter) Kcas_data.Hashtbl.t;
}
```

### Functions
- `create : unit -> t`

- `register : t -> seq:int -> waiter`
  - Create promise/resolver pair
  - Store in hashtable keyed by sequence number
  - Return waiter handle

- `complete : t -> seq:int -> payload:string option -> bool`
  - Find waiter by seq
  - Resolve promise with payload
  - Remove from table
  - Return true if found

- `wait : waiter -> timeout:float -> clock:Eio.Time.clock -> string option option`
  - Wait for promise with timeout
  - Return Some payload on success
  - Return None on timeout

- `cancel : t -> seq:int -> unit`
  - Remove waiter from table
  - Called on timeout to cleanup

## Design constraints
- Use Eio.Promise for async waiting
- Use Eio.Time.with_timeout for timeouts
- Lock-free via Kcas_data.Hashtbl
- Cleanup on timeout to prevent leaks

acceptance_criteria:
- Acks properly matched to probes
- Timeouts work correctly
- No memory leaks on timeout
- Concurrent completion safe

close_reason: Implemented pending_acks with Eio.Promise for async waiting and Kcas_data.Hashtbl for lock-free storage

labels: core kcas protocol

issue_id: swim-etm


title: Implement protocol_pure.ml - Pure SWIM state transitions

description:
Implement pure (no effects) SWIM protocol logic for state transitions.

## Core abstraction
```ocaml
type 'a transition = {
  new_state : 'a;
  broadcasts : protocol_msg list;
  events : node_event list;
}
```

## State transition functions
- `handle_alive : member_state -> alive_msg -> now:float -> member_state transition`
- `handle_suspect : member_state -> suspect_msg -> now:float -> member_state transition`
- `handle_dead : member_state -> dead_msg -> now:float -> member_state transition`
- `handle_ack : probe_state -> ack_msg -> probe_state transition`

## Timeout calculations
- `suspicion_timeout : config -> node_count:int -> float`
  - Based on suspicion_mult and log(node_count)
  - Capped by suspicion_max_timeout

## Probe target selection
- `next_probe_target : probe_index:int -> members:node list -> (node * int) option`
  - Round-robin with wraparound
  - Skip self

## Message invalidation (for queue pruning)
- `invalidates : protocol_msg -> protocol_msg -> bool`
  - Alive invalidates Suspect for same node with >= incarnation
  - Dead invalidates everything for same node
  - Suspect invalidates older Suspect

## State merging
- `merge_member_state : local:member_state -> remote:member_state -> member_state`
  - CRDT-style merge based on incarnation
  - Dead is final (tombstone)
  - Higher incarnation wins

## Retransmit calculation
- `retransmit_limit : config -> node_count:int -> int`
  - Based on retransmit_mult * ceil(log(node_count + 1))

## Design constraints
- PURE functions only - no I/O, no time, no randomness
- All inputs explicit
- Exhaustive pattern matching
- Fully testable with property-based tests

acceptance_criteria:
- All functions are pure (no effects)
- Property-based tests for SWIM invariants
- Incarnation ordering correct
- Suspicion timeout formula matches SWIM paper

close_reason: Implemented all pure SWIM state transitions: handle_alive, handle_suspect, handle_dead, suspicion_timeout, retransmit_limit, next_probe_target, invalidates, merge_member_state, select_indirect_targets

labels: core protocol pure

issue_id: swim-fac


title: Implement crypto.ml - AES-256-GCM encryption

description:
Implement encryption layer using mirage-crypto for AES-256-GCM.

## Constants
- `nonce_size = 12`
- `tag_size = 16`
- `overhead = nonce_size + tag_size` (28 bytes)

## Functions

### Key initialization
- `init_key : string -> (key, [`Invalid_key_length]) result`
- Must be exactly 32 bytes for AES-256

### Encryption
- `encrypt : key -> Cstruct.t -> Cstruct.t`
- Generate random nonce via mirage-crypto-rng
- Prepend nonce to ciphertext
- Result: nonce (12) + ciphertext + tag (16)

### Decryption
- `decrypt : key -> Cstruct.t -> (Cstruct.t, [`Too_short | `Decryption_failed]) result`
- Extract nonce from first 12 bytes
- Verify and decrypt remaining data
- Return plaintext or error

## Design constraints
- Use mirage-crypto.Cipher_block.AES.GCM
- Use mirage-crypto-rng for nonce generation
- Return Result types, no exceptions
- Consider in-place decryption where possible

acceptance_criteria:
- Property-based roundtrip tests pass
- Invalid data properly rejected
- Key validation works
- Nonces are unique (use RNG)

close_reason: Implemented crypto.ml with AES-256-GCM using mirage-crypto. Uses Eio.Flow for secure random nonce generation.

labels: core crypto security


title: Implement dissemination.ml - Broadcast queue with invalidation

description:
Implement the broadcast queue for SWIM protocol message dissemination.

## Broadcast_queue module
```ocaml
type item = {
  msg : protocol_msg;
  transmits : int Kcas.Loc.t;
  created : Mtime.span;
}

type t = {
  queue : item Kcas_data.Queue.t;
  depth : int Kcas.Loc.t;
}
```

### Functions
- `create : unit -> t`

- `enqueue : t -> protocol_msg -> transmits:int -> created:Mtime.span -> unit`
  - Add message with initial transmit count
  - Increment depth

- `drain : t -> max_bytes:int -> encode_size:(protocol_msg -> int) -> protocol_msg list`
  - Pop messages up to max_bytes
  - Decrement transmit count
  - Re-enqueue if transmits > 0
  - Return list of messages to piggyback

- `depth : t -> int`

- `invalidate : t -> invalidates:(protocol_msg -> protocol_msg -> bool) -> protocol_msg -> unit`
  - Remove messages invalidated by newer message
  - Uses Protocol_pure.invalidates

## Design constraints
- Lock-free via Kcas_data.Queue
- Transmit counting for reliable dissemination
- Size-aware draining for UDP packet limits
- Message invalidation to prune stale updates

acceptance_criteria:
- Messages properly disseminated
- Transmit counts respected
- Invalidation works correctly
- No message loss during concurrent access

close_reason: Implemented broadcast queue with enqueue, drain (size-aware), and invalidate functions using Kcas_data.Queue

labels: core dissemination kcas

issue_id: swim-iwg

depends_on_id: swim-fac


title: Implement codec tests (test/test_codec.ml)

description:
Property-based and unit tests for codec module.

## Property tests

### Roundtrip
- `test_codec_roundtrip` - encode then decode equals original
- `test_encoder_decoder_roundtrip` - for primitive types

### Size calculation
- `test_encoded_size_accurate` - encoded_size matches actual encoding

### Error handling
- `test_invalid_magic_rejected`
- `test_unsupported_version_rejected`
- `test_truncated_message_rejected`
- `test_invalid_tag_rejected`

## Unit tests

### Encoder
- Test write_byte, write_int16_be, etc.
- Test write_string with various lengths
- Test buffer overflow detection

### Decoder
- Test read operations
- Test remaining/is_empty
- Test boundary conditions

### Message encoding
- Test each message type individually
- Test packet with piggyback messages
- Test empty piggyback list

## Design constraints
- Use QCheck for property tests
- Use Alcotest or similar for unit tests
- Cover all message types
- Test error paths

acceptance_criteria:
- All property tests pass
- All unit tests pass
- Edge cases covered
- Error handling tested

close_reason: Implemented codec property and unit tests - all 19 tests passing

labels: codec test


title: Implement codec.ml - Zero-copy binary encoding/decoding

description:
Implement binary encoding/decoding with zero-copy semantics using Cstruct.

## Components

### Encoder module
- `type t` with buf and mutable pos
- `create : buf:Cstruct.t -> t`
- `write_byte`, `write_int16_be`, `write_int32_be`, `write_int64_be`
- `write_string` (length-prefixed)
- `write_bytes`
- `to_cstruct` - returns view, no copy
- `reset`, `remaining`

### Decoder module
- `type t` with buf and mutable pos
- `create : Cstruct.t -> t`
- `read_byte`, `read_int16_be`, `read_int32_be`, `read_int64_be`
- `read_string` - returns string (must copy for safety)
- `read_bytes` - returns Cstruct view
- `remaining`, `is_empty`

### Codec module
- Magic bytes: "SWIM"
- Version: 1
- Message tags: 0x01-0x07 for each message type
- `encode_packet : packet -> buf:Cstruct.t -> (int, [`Buffer_too_small]) result`
- `decode_packet : Cstruct.t -> packet decode_result`
- `encoded_size : protocol_msg -> int` for queue draining

### Helper encoders
- `encode_node`, `encode_node_id`
- `encode_option`
- `decode_msg`

## Design constraints
- No allocations in hot path except unavoidable string creation
- Return Result types, no exceptions
- Use Cstruct sub-views where possible

acceptance_criteria:
- Property-based roundtrip tests pass
- No unnecessary allocations
- All message types encode/decode correctly
- Error handling for truncated/invalid data

close_reason: Implemented codec.ml with Encoder/Decoder modules, zero-copy encoding/decoding for all protocol messages, IP address parsing, and encoded_size calculation

labels: codec core zero-copy


title: Implement membership.ml - Kcas-based member table

description:
Implement lock-free membership state management using kcas and kcas_data.

## Member module
```ocaml
type t = {
  node : node_info;  (* Immutable *)
  state : member_state Kcas.Loc.t;
  incarnation : incarnation Kcas.Loc.t;
  state_change_time : Mtime.span Kcas.Loc.t;
  last_ack_time : Mtime.span Kcas.Loc.t;
}
```

### Functions
- `create : node_info -> t`
- `node : t -> node_info` (pure accessor)
- `get_state`, `get_incarnation`, `get_last_ack` (kcas reads)
- `set_alive`, `set_suspect`, `set_dead` with `~xt:Kcas.Xt.t`
- `record_ack : t -> now:Mtime.span -> xt:Kcas.Xt.t -> unit`
- `snapshot : t -> xt:Kcas.Xt.t -> member_snapshot`

## Membership module
```ocaml
type t = {
  table : (string, Member.t) Kcas_data.Hashtbl.t;
  count : int Kcas.Loc.t;
}
```

### Functions
- `create : unit -> t`
- `add : t -> Member.t -> unit`
- `remove : t -> node_id -> unit` (returns bool for success)
- `find : t -> node_id -> Member.t option`
- `mem : t -> node_id -> bool`
- `to_list : t -> Member.t list` (snapshot)
- `count : t -> int`
- `update_member : t -> node_id -> (Member.t -> xt:Kcas.Xt.t -> unit) -> bool`

## Design constraints
- All state via kcas locations
- Use Kcas_data.Hashtbl for lock-free hashtable
- Transactional updates via Kcas.Xt.commit
- No I/O inside transactions
- Short transactions only

acceptance_criteria:
- Lock-free operations work correctly
- Concurrent access safe
- Atomic state transitions
- Snapshot consistency

close_reason: Implemented Member module with kcas locations and Membership table with Kcas_data.Hashtbl

labels: core kcas membership

issue_id: swim-oll


title: Project setup: dune-project, opam, dependencies

description:
Set up the project structure and dependencies for the SWIM library.

## Tasks
1. Update dune-project with proper metadata and dependencies
2. Configure swim.opam with all required dependencies:
   - eio (>= 1.0)
   - kcas (>= 0.7)
   - kcas_data (>= 0.7)
   - mirage-crypto
   - mirage-crypto-rng
   - cstruct
   - qcheck (for testing)
3. Create lib/dune with proper library configuration
4. Create test/dune for test configuration
5. Create bench/dune for benchmarks (optional initially)
6. Verify project builds with `dune build`

acceptance_criteria:
- dune build succeeds
- opam install . --deps-only works
- All dependencies available

close_reason: Project setup complete: dune-project, lib/dune, test/dune configured. Build and tests pass.

labels: infrastructure setup

issue_id: swim-oun


title: Implement kcas data structure tests (test/test_kcas.ml)

description:
Concurrent correctness tests for kcas-based data structures.

## Buffer_pool tests
- `test_buffer_pool_no_leaks` - all acquired buffers released
- `test_buffer_pool_concurrent` - multiple fibers acquiring/releasing
- `test_with_buffer_exception_safe` - buffer released on exception

## Membership tests
- `test_membership_concurrent_add_remove` - no lost updates
- `test_membership_snapshot_consistency` - to_list is consistent
- `test_membership_count_accurate` - count matches actual

## Broadcast_queue tests
- `test_broadcast_queue_fifo` - messages dequeued in order
- `test_broadcast_queue_transmit_counting` - transmits decremented correctly
- `test_broadcast_queue_invalidation` - old messages pruned
- `test_broadcast_queue_concurrent` - concurrent enqueue/drain safe

## Pending_acks tests
- `test_pending_acks_complete` - ack resolves waiter
- `test_pending_acks_timeout` - timeout returns None
- `test_pending_acks_cancel` - cancel removes waiter
- `test_pending_acks_concurrent` - multiple pending acks

## Transactional tests
- `test_atomic_member_update` - multi-location update is atomic
- `test_transaction_retry` - conflicting transactions retry

## Design constraints
- Use Eio for concurrency
- Test with multiple domains if possible
- Verify linearizability properties

acceptance_criteria:
- All concurrent tests pass
- No race conditions
- Atomicity verified
- Stress tests pass

close_reason: Implemented kcas data structure tests - 15 tests for buffer_pool, membership, pending_acks, and member transitions

labels: concurrency kcas test

issue_id: swim-szx

depends_on_id: swim-xoo

depends_on_id: swim-oll

depends_on_id: swim-iwg

depends_on_id: swim-etm


title: Implement protocol.ml - Main protocol loop and handlers

description:
Implement the effectful protocol runner that applies pure transitions.

## Main Cluster Type
```ocaml
type t = {
  config : config;
  env : env;
  self : node;
  members : Membership.t;
  incarnation : int Kcas.Loc.t;
  sequence : int Kcas.Loc.t;
  broadcast_queue : Broadcast_queue.t;
  pending_acks : Pending_acks.t;
  probe_index : int Kcas.Loc.t;
  send_pool : Buffer_pool.t;
  recv_pool : Buffer_pool.t;
  udp_sock : Eio.Net.datagram_socket;
  tcp_listener : Eio.Net.listening_socket;
  event_stream : node_event Eio.Stream.t;
  handlers : (node -> string -> string -> unit) list Kcas.Loc.t;
  cipher_key : Mirage_crypto.Cipher_block.AES.GCM.key;
  stats : stats Kcas.Loc.t;
  shutdown : bool Kcas.Loc.t;
}
```

## Protocol Loop
- `run_protocol : t -> unit`
  - Main loop: probe cycle, timing, check shutdown
  - Use Protocol_pure for state transitions

- `probe_cycle : t -> Member.t -> unit`
  - Get sequence number
  - Drain piggyback messages
  - Send ping
  - Wait for ack with timeout
  - On timeout: indirect probe

- `indirect_probe : t -> Member.t -> seq:int -> now:Mtime.span -> unit`
  - Select k random members
  - Send ping_req through them
  - Wait for any ack

## Receive Loop
- `run_udp_receiver : t -> unit`
  - Acquire buffer from pool
  - Receive packet
  - Fork fiber for processing
  - Release buffer after processing

- `handle_udp_packet : t -> buf:Cstruct.t -> addr:Eio.Net.Sockaddr.datagram -> unit`
  - Decrypt
  - Decode
  - Dispatch to handler

## Message Handlers
- `handle_packet : t -> addr:Eio.Net.Sockaddr.datagram -> packet -> unit`
- `handle_ping : t -> Ping.t -> unit`
- `handle_ping_req : t -> Ping_req.t -> unit`
- `handle_ack : t -> Ack.t -> unit`
- `handle_alive : t -> Alive.t -> unit`
- `handle_suspect : t -> Suspect.t -> unit`
- `handle_dead : t -> Dead.t -> unit`
- `handle_user_msg : t -> User_msg.t -> unit`

## Design constraints
- Thin effectful wrapper over Protocol_pure
- Use kcas for all state
- Buffer pool for zero-copy I/O
- Fork fibers for concurrent handling

acceptance_criteria:
- Protocol loop runs correctly
- Probe cycles at configured interval
- All message types handled
- Stats updated accurately

close_reason: Implemented main protocol loop with all message handlers, probe cycles, indirect probing, UDP receiver, and cluster state management

labels: core eio protocol


title: Implement types.ml - Immutable message and node types

description:
Create the core immutable types for the SWIM protocol.

## Types to implement

### Node identification
- `node_id = Node_id of string [@@unboxed]`
- `incarnation = Incarnation of int [@@unboxed]`

### Node information
- `node_info` record with id, addr (Eio.Net.Sockaddr.datagram), meta

### Member state
- `member_state = Alive | Suspect | Dead`
- `member_snapshot` record for pure operations

### Protocol messages (pattern-matchable variants)
- `Ping of { seq; sender }`
- `Ping_req of { seq; target; sender }`
- `Ack of { seq; responder; payload }`
- `Alive of { node; incarnation }`
- `Suspect of { node; incarnation; suspector }`
- `Dead of { node; incarnation; declarator }`
- `User_msg of { topic; payload; origin }`

### Packet structure
- `packet = { cluster; primary; piggyback }`

### Error types
- `decode_error` variants
- `send_error` variants

### Configuration
- `config` record with all SWIM parameters
- `default_config` value

### Environment
- `env` record with Eio dependencies (net, clock, mono_clock, random, sw)

## Design constraints
- All types immutable
- Use [@@unboxed] where appropriate for performance
- Pattern-matchable variants for protocol messages

acceptance_criteria:
- All types defined with proper signatures in types.mli
- Types compile with dune build
- No mutable fields except where kcas will manage them

close_reason: Implemented types.ml and types.mli with all core types: node_id, incarnation, node_info, member_state, protocol_msg, packet, decode_error, send_error, node_event, config, env, stats

labels: core types


title: Implement protocol_pure tests (test/test_pure.ml)

description:
Property-based tests for pure SWIM logic.

## State transition properties

### Incarnation ordering
- `test_alive_dominates_suspect` - Alive with >= incarnation beats Suspect
- `test_higher_incarnation_wins` - Higher incarnation always dominates
- `test_dead_is_final` - Dead state cannot be overridden

### Message invalidation
- `test_invalidation_transitive` - if A invalidates B and B invalidates C, A invalidates C
- `test_alive_invalidates_suspect` - for same node with >= incarnation
- `test_dead_invalidates_all` - Dead invalidates Alive and Suspect for same node

### Merge properties
- `test_merge_commutative` - merge(a, b) = merge(b, a)
- `test_merge_idempotent` - merge(a, a) = a
- `test_merge_respects_incarnation` - higher incarnation wins

### Timeout calculation
- `test_suspicion_timeout_increases_with_nodes` - more nodes = longer timeout
- `test_suspicion_timeout_bounded` - never exceeds max

### Probe target selection
- `test_probe_wraps_around` - index wraps at list end
- `test_probe_skips_self` - self is never selected

## Unit tests
- Test specific transition scenarios
- Test edge cases (empty member list, incarnation 0, etc.)

## Design constraints
- All tests on pure functions
- No I/O or effects in tests
- Comprehensive property coverage

acceptance_criteria:
- All SWIM invariants tested
- Properties match SWIM paper
- Edge cases covered
- All tests pass

close_reason: Implemented protocol_pure property and unit tests - all 32 tests passing

labels: protocol pure test


title: SWIM Protocol Library Implementation

description:
Production-ready SWIM (Scalable Weakly-consistent Infection-style Process Group Membership) protocol library in OCaml 5 for cluster membership, failure detection, and lightweight pub/sub messaging.

## Core Design Principles
- Pure functions by default, separate pure logic from effectful operations
- Immutable data structures, mutations only through kcas
- Zero-copy buffer management with buffer pools
- Lock-free coordination via kcas/kcas_data
- AES-256-GCM encryption

## Dependencies (allowed)
- eio (>= 1.0), kcas (>= 0.7), kcas_data (>= 0.7)
- mirage-crypto, mirage-crypto-rng, cstruct

## Target Scale
- Up to 100 nodes
- Sub-second failure detection
- Optimized for datacenter/cloud environments

acceptance_criteria:
- All 11 core modules implemented
- Property-based tests for pure functions
- Integration tests passing
- Build and opam package working
- Performance targets met (< 5 allocations/probe, > 95% buffer reuse)

issue_type: epic

labels: epic ocaml5 swim


title: Implement integration tests (test/test_integration.ml)

description:
End-to-end integration tests for the SWIM library.

## Two-node tests
- `test_two_node_join` - node2 joins node1, both see each other
- `test_two_node_leave` - graceful leave propagates
- `test_two_node_broadcast` - broadcast message received
- `test_two_node_direct_send` - direct TCP message delivered

## Three-node tests
- `test_gossip_propagation` - message reaches all nodes
- `test_indirect_probe` - indirect probe detects alive node
- `test_failure_detection` - dead node detected and removed

## Failure scenarios
- `test_network_partition` - nodes handle partition
- `test_node_crash` - crashed node detected as dead
- `test_rejoin_after_crash` - node can rejoin after restart

## Metadata tests
- `test_metadata_propagation` - metadata updates reach all nodes
- `test_metadata_update` - updated metadata replaces old

## Event stream tests
- `test_join_event_fired` - Join event on new member
- `test_leave_event_fired` - Leave event on departure
- `test_suspect_event_fired` - Suspect event on probe timeout

## Performance tests
- `test_convergence_time` - cluster converges within expected time
- `test_message_throughput` - broadcast rate meets target

## Design constraints
- Use Eio_main.run for all tests
- Proper cleanup with shutdown
- Realistic timing (but accelerated)
- Isolated network per test

acceptance_criteria:
- All integration tests pass
- Failure scenarios handled
- Performance targets met
- Clean teardown

close_reason: Integration and kcas tests complete, 89 tests total passing

labels: integration test

issue_id: swim-wwr


title: Implement buffer_pool.ml - Buffer management with kcas_data

description:
Implement buffer pool for zero-copy network I/O.

## Buffer_pool module

### Type
```ocaml
type t = {
  buffers : Cstruct.t Kcas_data.Queue.t;
  size : int;
  total : int;
  semaphore : Eio.Semaphore.t;
}
```

### Functions
- `create : size:int -> count:int -> t`
  - Pre-allocate `count` buffers of `size` bytes
  - Use Kcas_data.Queue for lock-free storage
  - Eio.Semaphore for blocking acquire

- `acquire : t -> Cstruct.t`
  - Block on semaphore if no buffers
  - Pop from queue
  - Reset buffer (memset 0) before returning

- `try_acquire : t -> Cstruct.t option`
  - Non-blocking acquire
  - Return None if no buffers available

- `release : t -> Cstruct.t -> unit`
  - Push buffer back to queue
  - Release semaphore

- `with_buffer : t -> (Cstruct.t -> 'a) -> 'a`
  - RAII-style acquire/release
  - Use Fun.protect for exception safety

- `available : t -> int` - current available count
- `total : t -> int` - total pool size

## Design constraints
- Lock-free queue via kcas_data
- Semaphore for blocking (only blocking allowed per spec)
- Clear buffer ownership semantics
- No memory leaks on exceptions

acceptance_criteria:
- Buffers properly recycled
- No leaks under concurrent use
- with_buffer is exception-safe
- Stats accurate

close_reason: Implemented buffer_pool.ml with Kcas_data.Queue and Eio.Semaphore

labels: buffer core zero-copy

issue_id: swim-xoo


title: Implement swim.ml/swim.mli - Public API assembly

description:
Implement the public API as specified in swim.mli.

## Cluster module (public interface)

### Lifecycle
- `create : env -> config -> (t, [> `Invalid_key | `Bind_failed of string]) result`
  - Initialize crypto
  - Create buffer pools
  - Bind sockets
  - Start protocol and receiver fibers

- `join : t -> seed_nodes:string list -> (unit, [> `No_seeds_reachable]) result`
  - Parse seed addresses
  - Send join requests
  - Wait for acks

- `leave : t -> ?timeout:float -> unit -> unit`
  - Broadcast leave
  - Wait for propagation
  - Graceful shutdown

- `shutdown : t -> unit`
  - Set shutdown flag
  - Close sockets
  - Release resources

### Membership queries (pure)
- `local_node : t -> node`
- `nodes : t -> node list`
- `node_count : t -> int`
- `is_alive : t -> node_id -> bool`
- `find_node : t -> node_id -> node option`

### Events
- `events : t -> node_event Eio.Stream.t`

### Metadata
- `set_meta : t -> string -> (unit, [> `Too_large]) result`

### Msg submodule
- `broadcast : t -> topic:string -> payload:string -> (unit, [> `Too_large]) result`
- `send : t -> node -> payload:string -> (unit, [> `Unreachable | `Timeout]) result`
- `on_message : t -> handler -> unit`
- `on_topic : t -> string -> (node -> string -> unit) -> unit`

### Health submodule
- `stats : t -> stats`
- `is_healthy : t -> bool`

## Design constraints
- All operations fiber-safe
- No blocking except Eio primitives
- Result types for fallible operations
- Clean module signature in .mli

acceptance_criteria:
- Public API matches spec
- All operations work correctly
- Clean .mli signature
- Documentation comments

close_reason: Implemented Cluster module as public API with create, start, shutdown, join, broadcast, member queries, and event streaming

labels: api core

issue_id: swim-zsi