Commit graph

409 commits

Author SHA1 Message Date
5defe7cc01 feat(ha-repl-hardening): add observability log statements (task 5.4)
- config_repl.c: INFO at WAL catch-up start with start/target LSN (Req 7.1)
- config_repl.c: INFO at snapshot transfer start and complete with
  checkpoint_lsn and size (Req 7.3)
- ha.c committing_timeout_fn_for_test: add WARN log counting
  n_converged/n_pending before promotion, matching production path (Req 7.5)
- Reqs 7.2 (standby converged INFO) and 7.4 (majority INFO) were
  already present from tasks 3.1 and 5.2 respectively
2026-06-05 05:36:27 +00:00
0762577bf7 feat(ha-repl-hardening): committing timeout 60s degraded-mode safety net (task 5.3)
- Change HA_DEFAULT_COMMITTING_TIMEOUT_MS from 10000 to 60000
- committing_timeout_fn: count converged/pending standbys from
  standby_converged[], log WARN with counts, then promote (degraded mode)
- Replace hardcoded 10000 in test_ha_evaluate_committing.c with macro
- Three new tests: default=60000, degrade-promote with 1/2 and 0/2 converged
2026-06-05 05:03:40 +00:00
534bfd68fe feat(ha-repl-hardening): majority-convergence → CTRL_PRIMARY transition (task 5.2)
- Add ha_check_majority_convergence() in ha.c: counts standby_converged[],
  promotes CTRL_PRIMARY_COMMITTING → CTRL_PRIMARY when all standbys converge
- Threshold: n_peers (all standbys) per spec test cases; single-node satisfies immediately
- Called from repl_ack_decode_and_dispatch() before replication dispatch
- Logs INFO "majority converged (N/M standbys) → CTRL_PRIMARY" (Req 7.4)
- Three unit tests: 1/2 converged → stays COMMITTING; 2/2 → PRIMARY; single-node → immediate
- Fix ha.h comment: clarify double-promotion prevention via vtask context, not CAS
2026-06-05 04:42:53 +00:00
d13010e4e0 feat(ha-repl-hardening): reduce handle_ctrlhello to connection setup only (task 5.1)
- Strip handle_ctrlhello: remove repl_add_standby, WAL streaming,
  snapshot check, push_config_to_standbys, and CtrlHelloReply
- Keep only: set primary_ctrl_fd, call ha_on_heartbeat_received,
  arm ha_arm_heartbeat_timeout_vtask, log INFO
- All replication decisions now handled by heartbeat decision loop
- Test: call with NULL repl + config_tag mismatch → no crash, no repl
  messages enqueued, primary_ctrl_fd set correctly
2026-06-05 03:54:43 +00:00
5915150c3b feat(ha-repl-hardening): set committed_lsn from vote-derived safe point (task 4.2)
- In raft_cb_become_leader, after persist_truncate_wal_above(safe_lsn),
  store safe_lsn to ha->last_known_committed_lsn via atomic release
- Test: 3-node voters [100,150,200] → safe_lsn=150; asserts
  ha->last_known_committed_lsn == 150 after raft_cb_become_leader_for_test
2026-06-05 03:28:59 +00:00
e7919304c3 feat(ha-repl-hardening): safe_committed_lsn from vote replies (task 4.1)
- Add ha_compute_safe_committed_lsn() in ha.c: collects LSNs from
  granted voters (self + peers), sorts descending, picks index
  floor(n_granted/2) for majority-threshold value
- Wire into raft_cb_become_leader: compute safe_lsn/safe_tag from
  ha->elect_ctx after winning election, call persist_truncate_wal_above
- Four unit tests in test_ha_evaluate_committing.c:
  voters [99,100]+self=100 → 100; voters [50,51]+self=100 → 51;
  single-node → 0; denied voter excluded → self-only
2026-06-05 02:41:22 +00:00
18ad3e9c9f feat(ha-repl-hardening): snapshot transfer integration and dispatch-path tests (task 3.5d)
- Add ha_repl_send_snapshot_hook seam in ha.c/ha.h for XCE_TESTING
- test_dispatch_repl_action_send_snapshot: verifies SEND_SNAPSHOT
  decision triggers hook when standby_lsn < oldest_wal_lsn
- test_snapshot_recv_two_chunks_assembles_and_renames: verifies standby
  assembles snap.part across chunks and renames on isLast=true
- test_snapshot_recv_reconnect_discards_snappart: verifies O_TRUNC
  semantics on chunk_index==0 restart after reconnect
- All tests pass under ASAN
2026-06-05 02:13:11 +00:00
cbd91fca43 feat(ha-repl-hardening): standby snapshot receive handler (task 3.5c)
ha_handle_snapshot_chunk writes chunks to snap.part, on isLast verifies
total_size, atomic renames to snap-{lsn}.bin, loads via new
persist_load_snapshot wrapper, sets applied_lsn=checkpoint_lsn.
3 tests: 2-chunk assembly, size mismatch, null args. Wired into
myreq_handler OPCode_snapshotTransfer dispatch.
2026-06-05 01:43:05 +00:00
318098c727 fix(ha-repl-hardening): wire snapshot production path (task 3.5b)
- SEND_SNAPSHOT uses checkpoint_lsn (not committed_lsn) for file lookup
- ha_send_snapshot_to_standby stats file for actual total_size
- ha_compute_oldest_wal_lsn scans WAL dir for min segment LSN
- Removed hardcoded 131072 and oldest_wal_lsn=0 placeholders
2026-06-05 01:25:00 +00:00
834ab4d5e7 feat(ha-repl-hardening): primary snapshot chunk sender (task 3.5a)
ha_send_snapshot_to_standby reads snapshot file, splits into 64KB
chunks, sends each via OPCode_snapshotTransfer with correct metadata
(snapshotLsn, chunkIndex, chunkOffset, totalSize, isLast). Payload
encoding deferred. Hook seam for unit testing.
2 tests: 128KB/2-chunks, small-file/1-chunk. Tasks 3.5b-3.5d remain.
2026-06-05 01:16:34 +00:00
6b69a47c50 feat(ha-repl-hardening): batch WAL streaming from decision loop (task 3.4)
ha_stream_wal_to_standby() reads WAL from committed_lsn range, accumulates
entries to 256/64KB per batch, enqueues all via xce_request() (non-blocking).
Partial final batch sent immediately. Wired into STREAM_WAL case.
3 tests: 500-entries/2-batches, partial batch, empty range.
Fixed stale task-number comments per review.
2026-06-04 17:57:55 +00:00
d1c7872d73 feat(ha-repl-hardening): wire config push from decision loop (task 3.3)
ha_dispatch_repl_action_for_standby() calls push_config_to_standbys()
when PUSH_CONFIG decision. Hook seam for unit tests. Wired into
repl_ack_decode_and_dispatch after ha_process_standby_state.
oldest_wal_lsn=0 placeholder documented for task 3.4.
2026-06-04 17:23:46 +00:00
a475ef621f feat(ha-repl-hardening): unified replication decision loop (task 3.2)
ha_evaluate_repl_action() pure function: stale tag→PUSH_CONFIG,
lsn<oldest_wal→SEND_SNAPSHOT, lsn<committed→STREAM_WAL, else CONVERGED.
5 tests. Fixed misleading stream_wal_from_zero test name per review.
2026-06-04 16:58:48 +00:00
aab41fa609 feat(ha-repl-hardening): per-standby convergence tracking from heartbeats (task 3.1)
ha_process_standby_state stores (tag, lsn) per standby, evaluates
convergence (tag>=committed && lsn>=committed), sets converged flag,
logs INFO only on transition. Wired into repl_ack_decode_and_dispatch.
5 tests: converged, lsn-behind, tag-behind, regression, multi-peer.
2026-06-04 16:40:44 +00:00
7dc657b1bd feat(ha-repl-hardening): populate stateReport in standby heartbeat reply (task 2.3)
OPCode_nop handler now sends stateReport {applied_lsn, config_tag}
instead of nodata. Function in ha.c for unit-test linkability.
Two tests: warm state and zero-state fresh standby (Req 1.4).
2026-06-04 16:08:11 +00:00
6bbcff62ca feat(ha-repl-hardening): add batch WAL catch-up on standby side (task 2.2)
Extend ha_apply_wal_catchup_entry with batch branch:
- Check n_entries > 0, iterate entries[] list in order
- Apply each via apply_wal_entry(); skip NULL/errors without crash
- Advance applied_lsn once after full batch (not per-entry)
- n_entries == 0 falls through to task 2.1 single-entry path
4 new unit tests covering full batch, partial batch, error skip, fallback
2026-06-04 15:38:20 +00:00
647b88ce57 feat(ha-repl-hardening): wire OPCode_walCatchupEntry standby handler (task 2.1)
Replace stub with ha_apply_wal_catchup_entry() calling apply_wal_entry().
CAS-advance applied_lsn on success; log ERROR and skip on failure.
Standby stays in CTRL_STANDBY throughout. 3 new unit tests.
2026-06-04 15:05:52 +00:00
e3ca775f0d feat(ha-repl-hardening): add per-standby convergence tracking arrays (task 1.4)
Add standby_reported_tag/lsn/converged arrays to ha_state_t, sized
MAX_HA_CANDIDATES. Zero-init via existing memset in ha_init().
Unit test verifies zero-init and writability.
2026-06-04 14:14:23 +00:00
0121bc0683 feat(ha-repl-hardening): add snapshot chunk fields to capnp schema (task 1.3)
Extend SnapshotTransfer struct from 2 to 6 fields:
- snapshotLsn (existing), chunkIndex, chunkOffset, totalSize,
  isLast, payload (existing)
No resumption fields per restart-on-reconnect policy.
2026-06-04 13:55:45 +00:00
51907ebfbc feat(ha-repl-hardening): add batch entries list to WalCatchupEntry schema (task 1.2)
Add self-referential entries @3 :List(WalCatchupEntry) field with
$C.maplistcount("n_entries"). Single-entry path unaffected — n_entries
defaults to 0, entries to NULL when not using batch mode.
2026-06-04 13:46:04 +00:00
9a977b3a43 feat(ha-repl-hardening): add StateReport to RequestReply capnp schema (task 1.1)
Add StateReport struct {appliedLsn, configTag} as stateReportMsg @15
variant in RequestReply.data union. Standby populates this in heartbeat
replies so the primary can track per-standby convergence.

capnpc-c 1.0.1 has a bug with inline :group inside codecgen unions;
workaround uses named outer struct (wire-equivalent). typemap.h
extended with state_report_t and union member for C codec access.
2026-06-04 13:34:11 +00:00
e31673dd88 chore: checkpoint all prior work for clean state
Captures all uncommitted work from ha-config-bootstrap,
ha-heartbeat-liveness, and associated spec/tooling updates:

- src: ha.c, ha.h, cluster.c, config_repl.c, myapp.c, raft.c —
  heartbeat liveness, RDHUP integration, committing phase fixes
- tests: test_ha_role.c, test_config_repl.c, test_ha_rdhup_integration.c,
  test_p1_integration.c, test_p3_hardening.c, scheduler test
- specs: election-iomgr-refactor, ha-heartbeat-liveness,
  ha-peer-connection-reuse, raft-library (new); ha-config-bootstrap
  and election-iomgr-refactor task/research updates
- tooling: .wolf/ state, ha/data cluster fixtures, raft/raft.tla,
  src/build symlink, xce-build skill update
2026-06-04 13:04:16 +00:00
15bd291bdd docs(ha-repl-hardening): add spec with reviewed design and approved tasks
- requirements.md: 7 requirements covering heartbeat state reporting,
  majority convergence tracking, WAL catch-up, snapshot transfer, batch
  WAL, COMMITTING gate, and observability
- design.md: unified heartbeat-driven replication design; reviewed and
  fixed three issues (snapshot resumption→restart-on-reconnect, WAL
  streaming non-blocking contract, applied_lsn durability)
- tasks.md: 21 sub-tasks across 6 groups; sanity reviewed and fixed
  (removed resumption language from tasks 3.5/6.4, added Req 3.4
  coverage, fixed 2.1/2.2 dependency, log coverage trace in 5.4)
- spec.json: design and tasks approved, ready for implementation
2026-06-04 12:58:39 +00:00
374f3bfd0e chore(ha-config-bootstrap): gap analysis audit + fix parent task headers
- Gap analysis: all 10 requirements verified implemented in live codebase
  (ha.c, ha.h, config_repl.c, request.c).
- Fix parent group headers (1-10) to [x] since all leaf subtasks complete.
- Mark task 11.5 complete: /xce-build controller passes 1/1 tests, ASAN clean.
- Remaining: integration tests 11.1-11.4 (manual cluster tests, documented
  in manual-tests.md).
2026-06-03 06:04:58 +00:00
e56b2064de test(ha-heartbeat-liveness): SIGSTOP/SIGCONT tolerance integration test (task 4.5)
Add src/tools/test-heartbeat-tolerance.sh: automated bash script that
deploys a 3-node cluster and runs two SIGSTOP/SIGCONT scenarios:

  Scenario A (1 × heartbeat_interval_ms pause): verifies no RDHUP fires on
  standbys — liveness_poll_fn must not trigger failover for a short silence
  within the tolerance window (Req 2.2).

  Scenario B (heartbeat_timeout_ms + 500ms pause): verifies exactly one RDHUP
  fires on each standby — the activity-based poll fires after sustained silence
  and does not loop (Req 2.3, Req 5.4).

Add .kiro/specs/ha-heartbeat-liveness/manual-tests.md: step-by-step manual
procedure, expected log patterns, and results template for both scenarios.

Mark task 4.5 complete in tasks.md.
2026-06-03 04:18:56 +00:00
529552c127 fix(ha-heartbeat-liveness): start primary heartbeat in COMMITTING phase (task 4.3)
Call ha_start_primary_heartbeat in raft_cb_become_leader when entering
CTRL_PRIMARY_COMMITTING so standbys receive NOPs during the committing
window. Fixes 60s stability: 0 false RDHUP events on standbys.
2026-06-01 15:56:36 +00:00
aceadf0357 chore(ha-heartbeat-liveness): mark unit test tasks 4.1-4.2 complete
All required unit test cases already implemented in prior tasks.
2026-06-01 15:43:29 +00:00
12216f1165 feat(ha-heartbeat-liveness): verify ctrlhello integration and RDHUP cancel (task 3.2)
Verification task: confirmed config_repl.c handle_ctrlhello calls both
ha_on_heartbeat_received and ha_arm_heartbeat_timeout_vtask (delaymgr poll).
Confirmed ha_on_primary_ctrl_fd_rdhup leads with ha_disarm (Req 5.4).
No pthread_create in heartbeat arm path (Req 1.6). Add thread-count test.
2026-06-01 15:37:35 +00:00
d937c1c1bb feat(ha-heartbeat-liveness): role-based liveness reset in opcode dispatch (task 3.1)
Replace fd-based guard in OPCode_nop with role==CTRL_STANDBY check.
Add ha_on_heartbeat_received call to OPCode_hello ctrlhello path when standby.
Satisfies Req 1.3 (any message resets deadline) and Req 4.1 (ctrlhello counts).
3 new dispatch guard tests added to test_ha_liveness.c.
2026-06-01 15:31:15 +00:00
622302a55c feat(ha-heartbeat-liveness): idle-triggered primary NOP per standby (task 2.2)
Extend primary_heartbeat_fn to read last_push_time per standby and send NOP
only when elapsed >= heartbeat_interval_s. on_standby_connected writes
last_push_time before ctrlhello send. Remove workaround xce_nop_req at end
of on_standby_connected. Add ha_nop_req_hook XCE_TESTING seam. 3 new tests.
2026-06-01 15:05:41 +00:00
738859fb4e feat(ha-heartbeat-liveness): replace hb_timeout_thread with liveness_poll_fn (task 2.1)
Remove hb_vtask_t, hb_timeout_thread, s_hb_vtask from ha.c. Replace with
liveness_poll_ctx_t + delaymgr liveness_poll_fn: checks cancelled flag and
role==CTRL_STANDBY before acting; re-arms at heartbeat_interval_ms/2.
ha_on_primary_ctrl_fd_rdhup calls ha_disarm_heartbeat_timeout_vtask first
to prevent duplicate failover (Req 5.4). ha_on_heartbeat_received writes
last_activity via clock_gettime. Add test_ha_liveness.c with 7 unit tests.
2026-06-01 14:34:14 +00:00
9f9a6b3c0f feat(ha-heartbeat-liveness): add validate_heartbeat_config startup check (task 1.2)
Enforce heartbeat_timeout_ms >= 2 * heartbeat_interval_ms at startup.
validate_heartbeat_config() in startup_validation.c returns -EINVAL with
error message on violation; wired in cluster_init_phase1 before ha_init().
4 unit tests added to test_ha_startup.c.
2026-06-01 12:26:29 +00:00
bcb45e59d2 feat(ha-heartbeat-liveness): add last_activity and last_push_time state fields (task 1.1)
Add time_t last_activity to ha_state_t for standby liveness timestamp tracking.
Add time_t last_push_time to standby_ep_ctx_t for primary idle NOP suppression.
Zero-initialize last_activity in ha_init(); last_push_time zeroed by calloc.
Add test_ha_init_last_activity_is_zero to test_ha_startup.c.
2026-06-01 11:59:16 +00:00
e85100d761 test(ha-peer-connection-reuse): complete tasks 7.1, 7.2, 7.3 — all validation done
Task 7.1 (unit tests) was already complete with 8 tests in
test_ha_connection_reuse.c. Added two new tests:

Task 7.2 — test_follower_passive_no_outbound_connect (Req 2.1, 2.4):
  Verifies raft_cb_become_follower sets role=CTRL_STANDBY and nulls all
  votes[i].ep. The old fetch_config_from_primary path is gone; standbys
  never initiate outbound connects after election loss.

Task 7.3 — test_promoted_ep_repl_ack_promotes_to_primary (Req 5.2, 5.4):
  Injects two OPCode_replCatchUpDone packets via peer_ctrl_msg_handler in
  CTRL_PRIMARY_COMMITTING with n_peers=4 (5-node cluster, quorum=2 ACKs).
  Verifies repl_ack_count reaches 2 and role transitions to CTRL_PRIMARY.
  Added vote_ep_ctx_set_cluster_for_test() seam so vctx->c is non-NULL
  when ha_repl_ack_received fires ha_promote_to_primary.

All 62 controller tests pass; ASAN clean.
spec: .kiro/specs/ha-peer-connection-reuse/tasks.md all tasks [x]
2026-06-01 07:53:02 +00:00
78b7942bbe docs(ha): use generated cleanup.sh in clean restart; clarify what it preserves
cleanup.sh is generated by deploy-ha.sh and already does the right thing:
keeps ctrl-1/conf/* and all certs, wipes ctrl-{2..N}/conf, all data/, and logs.
Update skill and CLAUDE.md to use it as the primary clean-restart command
instead of manual rm loops.
2026-06-01 06:03:34 +00:00
484b110009 docs(ha): add xce-deploy-ha skill and update controller CLAUDE.md
- .claude/skills/xce-deploy-ha/SKILL.md: new skill for deploying a
  local 3-node XCE HA controller cluster. Covers build prerequisites,
  deploy via src/tools/deploy-ha.sh, node startup with -vvv raft trace,
  health verification checks, clean restart procedure, and a
  troubleshooting table for common failure modes.
- src/controller/CLAUDE.md: add HA Cluster Deployment section with
  quick-deploy commands, health check one-liners, and reference to the
  xce-deploy-ha skill. Add src/tools/deploy-ha.sh to key files.
2026-06-01 06:01:13 +00:00
3d076ccda6 fix(ha): fix config replication to standbys; add replConfigPush handler
- config_repl.c: add handle_repl_config_push — standby handler for
  OPCode_replConfigPush. Verifies SHA-256, saves blob to disk via
  conf_save_config, reloads via load_primary_config, ACKs primary.
- config_repl.h: declare handle_repl_config_push.
- cluster.c: dispatch OPCode_replConfigPush to handle_repl_config_push;
  populate c->myhost_info.name from myconf->name (ctrl-1/ctrl-2/ctrl-3)
  instead of the static "controller" placeholder.
- ha.c: fix raft_cb_become_leader push_config_to_standbys call to pass
  real config bytes and tag read from disk (was NULL/0 — standbys got
  empty push and never saved config).
- typemap.h: add raw_sha256/raw_bytes/raw_bytes_len fields to
  repl_config_push_req_t (mirrors upgrade_config_req_t pattern).
- request.c: pre-extract List(UInt8) sha256/bytes from OPCode_replConfigPush
  before capn_free() in xce_decode(); free them in xce_free_request().
- helper.c: add all HA/replication opcode names to xce_request_op_name
  so "unknown opcode 30" becomes "replConfigPush" in logs.
- cluster_def.h: add embedded myhost_info field to cluster_t struct.
- ha.h: declare ha_start_primary_heartbeat; remove dead
  ha_find_and_connect_to_primary declaration.
2026-06-01 05:56:16 +00:00
d00d3bb051 fix(ha): wire raft trace to DEBUG3; drop NOP on promoted ep; fix --vcpus in deploy script
- ha.c: add raft_cb_trace callback wired to s_real_cbs.on_trace; emits
  RAFT[pN] term=T event=E at DEBUG3 level, visible with -vvv flag.
- ha.c: repl_ack_decode_and_dispatch now silently drops OPCode_nop and
  OPCode_reply instead of WARNING — heartbeat NOP replies from standbys
  arrive on promoted endpoints and are expected.
- deploy-ha.sh: fix --vcpu -> --vcpus; add \$VERBOSE passthrough so
  start.sh respects VERBOSE=-vvv from the environment.
2026-06-01 05:20:13 +00:00
7b39650229 refactor(ha): passive standby model — primary connects outbound to all peers
Remove fetch_config_from_primary, ha_find_and_connect_to_primary, and the
tier-1/2/3 discovery path. Standbys never initiate outbound connections to
the primary; the primary owns all inter-controller channels.

Key changes:
- ha.c: add primary heartbeat loop (ha_start_primary_heartbeat) that sends
  OPCode_nop to connected standbys every heartbeat_interval_ms; loop
  self-cancels on demotion via role check before re-arm.
- ha.c: add outbound reconnect in primary_heartbeat_fn — for peers absent
  from standby_endpoints, open fresh outbound TLS connection (on_standby_connected
  sends ctrlhello; standby_ep_ctx_t mirrors vote_ep_ctx_t layout).
- ha.c: remove ha_find_and_connect_to_primary + helpers (ha_raw_tcp_connect,
  ha_tcp_connect_hook); remove election_timeout_fn (superseded by
  raft_election_timeout_fn); remove fetch_config_from_primary call from
  ha_elect_evaluate_and_signal STANDBY branch.
- cluster.c: OPCode_requestVote denial no longer reuses inbound connection;
  late joiners are bootstrapped via primary heartbeat outbound connect.
- config_repl.c: remove connect_ssl_raw, do_one_config_fetch,
  fetch_config_from_primary (~250 LOC).
- myapp.c: remove my_tag > 0 fetch branch; all standbys wait passively.
- ha.h: remove ha_find_and_connect_to_primary declaration + macros +
  ha_tcp_connect_hook seam; add ha_start_primary_heartbeat declaration.
- meson.build: remove test_ha_connect.c (tests deleted function).
- controller_test.c: remove test_ha_connect registration.
- CLAUDE.md, src/controller/CLAUDE.md: document passive standby model,
  inbound-vs-outbound connection ownership, async I/O constraint,
  heartbeat model.
- request.h: add xce_request declaration (removes scattered local externs).
2026-06-01 04:17:05 +00:00
842b9a838e fix(election): no-config standbys wait passively for primary push instead of polling 2026-05-31 11:37:52 +00:00
1c1fe462cc fix(election): increase election timeout to 5000ms to handle TLS handshake latency 2026-05-31 11:33:14 +00:00
0f766776ba fix(ha-term): move ha-term.bin to {workdir}/{name}/data/ alongside WAL/snapshot 2026-05-31 11:04:35 +00:00
04b31afb06 fix(election): no-config nodes set CTRL_STANDBY to trigger config fetch; voter-only path 2026-05-31 10:39:33 +00:00
a75fb2581b fix(election): no-config nodes skip own election; delay_ms=0 fires election immediately 2026-05-31 10:27:05 +00:00
937605ec60 feat(raft-library): tasks 6.4 + 7 complete — all ASAN-clean, hooks verified, spec closed 2026-05-31 09:25:20 +00:00
becf6ee765 feat(raft-library): reject even n_peers in raft_init; add test_raft_init_even_peers_returns_einval 2026-05-31 08:48:30 +00:00
fd9933c9d0 feat(raft-library): task 6.3 — delegate election dispatch to raft_* API in ha.c + cluster.c 2026-05-31 08:43:01 +00:00
8f0d2d55c3 feat(raft-library): task 6.2 — real raft callbacks in ha.c; fix double-free + dead stubs 2026-05-31 07:53:00 +00:00
b77525deb0 feat(raft-library): task 6.1 — raft_state_t field in ha_state_t, raft_init with no-op stubs 2026-05-31 07:20:15 +00:00
6bdf7edacf feat(raft-library): task 5.4 — Groups F+G, 6 test cases (RDHUP failover + split-brain) 2026-05-31 07:05:45 +00:00