diff --git a/.kiro/specs/ha-single-leader-role/tasks.md b/.kiro/specs/ha-single-leader-role/tasks.md index bd35ae99..4d32a9f2 100644 --- a/.kiro/specs/ha-single-leader-role/tasks.md +++ b/.kiro/specs/ha-single-leader-role/tasks.md @@ -59,7 +59,7 @@ - Observable completion: full controller suite green; `grep -rln "COMMITTING" src/controller/testsuite/` returns zero files - _Requirements: 1.1, 1.2_ -- [ ] 5.2 Remove the enum value and remaining declarations (compiler-enforced completeness) +- [x] 5.2 Remove the enum value and remaining declarations (compiler-enforced completeness) - Delete `CTRL_PRIMARY_COMMITTING = 4` from `ctrl_role_t` (ha.h:64); delete `HA_DEFAULT_COMMITTING_TIMEOUT_MS` (ha.h:131), `committing_timeout_ms` (ha.h:141), `repl_ack_count` (ha.h:180), `committing_timeout_ctx` (ha.h:187); delete the `.committing_timeout_ms` initializer (cluster.c:899); sweep remaining ha.h comments (214-230, 244-262, 373, 503-519, 607-614, 673-700, 791), the cluster.c ctrlhello comment (1402-1410), and the raft.c comment (src/lib/raft/raft.c:123) - Fix everything the compiler then surfaces — this is the completeness gate for code references - Observable completion: clean build with zero warnings about the removed symbols; full controller testsuite + ASAN green via `/xce-build controller` @@ -87,6 +87,7 @@ - **(3.1) Legacy path routes through BARE helpers, not `ha_promote_to_primary`**: `ha_elect_evaluate_and_signal`'s PRIMARY branch calls `ha_register_persist_hooks` + `ha_start_primary_heartbeat` + `ha_arm_own_term_gate` directly (matching the raft path), NOT `ha_promote_to_primary` — because the latter calls `ha_do_promote` (term increment) and the legacy branch historically has NO term store/increment (research §4 finding 2). This is a deliberate deviation from design §283's loose wording ("route through ha_promote_to_primary / ha_register_persist_hooks"); the bare-helper route preserves term semantics and is the correct reading. - **(4.1) No shims — DELETE, don't stub** (design §Transitional-State Deletions / §Design Synthesis: "rewritten or DELETED, not stubbed"). Machinery-subject test cases (convergence checker, committing watchdog, ack-driven promotion, ack counting) were DELETED, not shimmed: test_ha_role.c (5 majority_convergence + 2 committing_timeout_promotes_degraded), test_ha_rdhup_integration.c (rdhup_committing_to_primary_on_ack, rdhup_3node_one_ack_promotes), test_ha_connection_reuse.c (dispatcher_committing_handles_repl_ack, promoted_ep_repl_ack_promotes_to_primary). Kept state-entry cases had their deleted-fn CALLS excised (not shimmed). `test_ha_connection_reuse.c` was overlooked by the spec's §3 census disposition for machinery cases — confirmed in census (line 97) but its ack cases needed DELETE, not UPDATE. - **(4.2) Residual stale COMMITTING comments in ha.c** (~lines 443, 826, 1240): documentary prose on the legacy raft_cb_become_leader/convergence path — NOT live branches. Scrub in 5.2/6.x (they fail the task-6.2 repo-wide grep). After 4.2, ZERO live code branches on `CTRL_PRIMARY_COMMITTING` in ha.c; the enum value persists in ha.h:64 until 5.2. +- **(5.2) Code is COMMITTING-free; only DOCS remain for task 6.1**: after 5.2 the grep `CTRL_PRIMARY_COMMITTING|HA_DEFAULT_COMMITTING_TIMEOUT_MS|committing_timeout_ms|repl_ack_count|committing_timeout_ctx` is ZERO across `src/controller/` + `src/lib/`. Remaining tree references (task 6.1 docs-sync targets, then 6.2 verifies): `src/docs/cluster-config-split-analysis.md:34` (committing_timeout_ms) AND `src/controller/CLAUDE.md` (TLA+/role-mapping table still names PRIMARY_COMMITTING — 6.1 must scrub ALL its COMMITTING refs, not just the :40 line) plus the steering/skill docs already listed in 6.1. ctrl_role_t now has 4 values: CTRL_PRIMARY=0, CTRL_STANDBY=1, CTRL_INIT=2, CTRL_ELECTING=3. - **(5.1) Meson 300s timeout is now routinely hit** — barrier tests' commit-deadline waits push controller-suite wall-time over the 300s meson cap under load (`Fail: 0, Timeout: 1`). The AUTHORITATIVE green check is the DIRECT binary run (`src/xce.build/controller/controller_unit_test`, EXIT=0, 167 Tests/0 Failures aggregated). For task 6.2's "suite + ASAN green" gate: run the direct binary OR re-run meson until under-cap; a `Timeout: 1` is a wrapper artifact, NOT a test failure. `test_committing_timeout_default_is_60s` was DELETED in 5.1 (not 5.2) because its body used the uppercase production macro `HA_DEFAULT_COMMITTING_TIMEOUT_MS` (caught by the grep gate); the knob it tested dies in 5.2 regardless. - **(4.2) Test-leak / suite-timeout facts**: the controller test target sets `ASAN_OPTIONS=detect_leaks=0` (controller/meson.build:163), so a pre-existing suite-wide LSan leak (~153KB, present at baseline 678b7f8, this spec leaks slightly LESS) is suppressed under the meson gate — LSan exit=1 only on a RAW direct-binary run, not a regression. The controller suite runs ~268s against a 300s meson timeout — borderline; the final task-6.2 full regression may TIMEOUT under machine load — re-run if so (it is not a failure). - **(4.1) Residual for task 6.2 final grep**: 3 DOCUMENTARY PROSE comments in `test_ha_role_lifecycle.c` (~lines 760/815/825) still mention `ha_repl_ack_received`/`committing_timeout_fn` by name (no live code). The 4.1 gate (`src/controller/*.c|*.h`) excludes testsuite so this passed; the task-6.2 repo-wide grep WILL catch them — scrub these comments in 5.x/6.x. Also a non-tracked editor backup `src/controller/.ha.c.~undo-tree~` holds stale refs (irrelevant; not a build input — do not commit). diff --git a/src/controller/cluster.c b/src/controller/cluster.c index f409c637..e2554777 100644 --- a/src/controller/cluster.c +++ b/src/controller/cluster.c @@ -372,7 +372,7 @@ static int myreq_handler(vtask_t* task) * raft_on_vote_request handles: * - stale-term deny (term < current_term) * - term upgrade (term > current_term → clear voted_for) - * - role gate: RAFT_LEADER denies; COMMITTING gate in on_vote_eligible + * - role gate: RAFT_LEADER denies (via on_vote_eligible) * - one-vote-per-term invariant (voted_for != -1 && voted_for != from_idx) * - on_persist before granting (durability guarantee) * @@ -925,7 +925,6 @@ int cluster_init_phase1(cluster_t* c) .max_promotion_retries = HA_DEFAULT_MAX_PROMOTION_RETRIES, .election_jitter_max_ms = HA_DEFAULT_ELECTION_JITTER_MAX_MS, .election_priority_step_ms = HA_DEFAULT_ELECTION_PRIORITY_STEP_MS, - .committing_timeout_ms = HA_DEFAULT_COMMITTING_TIMEOUT_MS, }; int my_priority = 0; @@ -1464,8 +1463,8 @@ int cluster_run(cluster_t* c) * This triggers handle_ctrlhello on the receiving standby, which runs the * existing catch-up replication sequence (WAL streaming, config push). * - * Must be called from the COMMITTING path in ha_elect_evaluate_and_signal - * after ha->role is set to CTRL_PRIMARY_COMMITTING (Req 7.2). + * Must be called from the post-election path in ha_elect_evaluate_and_signal + * after ha->role is set to CTRL_PRIMARY (Req 7.2). * * Thread safety: acquires ha->lock around standby_endpoints iteration. * Best-effort: failures per-endpoint are logged but do not abort the loop. diff --git a/src/controller/ha.c b/src/controller/ha.c index 8bde5326..afe99836 100644 --- a/src/controller/ha.c +++ b/src/controller/ha.c @@ -437,13 +437,12 @@ void ha_compute_safe_committed_lsn(ha_elect_ctx_t *ctx, } /* - * raft_cb_become_leader: node won the election — enter COMMITTING state. + * raft_cb_become_leader: node won the election — enter CTRL_PRIMARY directly. * * Equivalent of ha_elect_evaluate_and_signal CTRL_PRIMARY path: - * - enter CTRL_PRIMARY_COMMITTING + * - enter CTRL_PRIMARY (no transitional state) * - call cluster_init_phase2 on first boot - * - reset repl_ack_count, push config, send ctrlhello - * - arm committing timeout watchdog + * - push config, send ctrlhello * * ha->raft_c must be non-NULL (production path only; test mode uses noop stubs). */ @@ -469,7 +468,7 @@ static void raft_cb_become_leader(void *ctx, uint64_t term) uint64_t safe_lsn = 0; uint64_t safe_tag = 0; ha_compute_safe_committed_lsn(ectx, &safe_lsn, &safe_tag); - /* Store results for use in WAL truncation and COMMITTING phase. + /* Store results for use in WAL truncation and post-election commit. * safe_lsn is the highest LSN on a majority; entries above it * may not have been committed on all nodes and must be truncated. */ if (c) { @@ -486,7 +485,7 @@ static void raft_cb_become_leader(void *ctx, uint64_t term) * Req 2.3 (ha-repl-hardening). */ atomic_store_explicit(&ha->last_known_committed_lsn, safe_lsn, memory_order_release); - /* Update committed config tag baseline for the COMMITTING decision loop */ + /* Update committed config tag baseline for the post-election decision loop */ atomic_store_explicit(&ha->committed_config_tag, safe_tag, memory_order_release); } @@ -520,7 +519,7 @@ static void raft_cb_become_leader(void *ctx, uint64_t term) ", first_boot=%d)", term, is_first_boot); /* Req 2.1 (ha-repl-hardening): re-initialize per-standby convergence - * tracking for THIS committing round. Stale converged flags from a + * tracking for THIS primary term. Stale converged flags from a * previous term (demote → re-elect cycle) must not satisfy the * majority-convergence gate before standbys re-report their state. */ memset(ha->standby_reported_tag, 0, sizeof(ha->standby_reported_tag)); @@ -823,7 +822,7 @@ int ha_init(ha_state_t *ha, const ha_config_t *cfg, int my_priority) /* ha-repl-hardening Task 1.4: per-standby convergence tracking arrays. * memset(ha, 0, sizeof(*ha)) above already zeroes these arrays; the explicit - * note documents the invariant: on entry to CTRL_PRIMARY_COMMITTING every + * note documents the invariant: on promotion to CTRL_PRIMARY every * standby starts as not-converged (tag=0, lsn=0, converged=false). */ /* heartbeat_seen starts at 0: no heartbeat seen yet. @@ -1237,8 +1236,8 @@ ha_repl_action_t ha_evaluate_repl_action(uint64_t standby_tag, /* * ha_process_standby_state — update per-standby convergence tracking. * - * Called on each heartbeat reply from a standby (CTRL_PRIMARY_COMMITTING or - * CTRL_PRIMARY). Stores (reported_tag, reported_lsn) into the per-standby + * Called on each heartbeat reply from a standby (CTRL_PRIMARY). + * Stores (reported_tag, reported_lsn) into the per-standby * arrays, evaluates convergence, and logs INFO on the first * not-converged → converged transition (Req 7.2). Regression resets the flag * (Req 2.5). @@ -2071,14 +2070,14 @@ int (*ha_phase2_hook)(void *c) = NULL; /* * ha_send_ctrlhello_hook: test-only seam for ha_send_ctrlhello_to_all_standbys. - * When non-NULL, the COMMITTING path calls this INSTEAD OF the real function + * When non-NULL, the post-election path calls this INSTEAD OF the real function * (cluster.c is not linked in unit-test binary). Req 7.2. */ int (*ha_send_ctrlhello_hook)(void *c) = NULL; /* * ha_push_config_hook: test-only seam for push_config_transport_only. - * When non-NULL, the COMMITTING path calls this INSTEAD OF the real function + * When non-NULL, the post-election path calls this INSTEAD OF the real function * (config_repl.c's push_config_transport_only needs a real cluster). Req 7.1. */ int (*ha_push_config_hook)(void *c) = NULL; @@ -2976,7 +2975,7 @@ int election_timeout_fn_for_test(ha_state_t *ha, ha_elect_ctx_t *ctx, /* * repl_catchup_done_pkt_for_test: build a minimal io_packet_t encoding * Request{opcode=replCatchUpDone, body_which=nodata}. - * Used by peer_ctrl_msg_handler tests to verify the COMMITTING dispatch path. + * Used by peer_ctrl_msg_handler tests to verify the repl-ack dispatch path. */ io_packet_t *repl_catchup_done_pkt_for_test(void) { @@ -3273,7 +3272,7 @@ void ha_start_election_async(ha_state_t *ha, cluster_t *c, INFO("ha_start_election_async: no local config (tag=0, n_peers=%d) — " "participating as voter only, not starting own election", n_peers); /* Transition to CTRL_STANDBY so myapp_run schedules fetch_config_from_primary. - * The primary will push config via ctrlhello once it enters COMMITTING. */ + * The primary will push config via ctrlhello once it becomes CTRL_PRIMARY. */ atomic_store_explicit(&ha->role, (int32_t)CTRL_STANDBY, memory_order_release); /* Free ctx — no vote sends will happen from this node this round. */ atomic_store_explicit(&ha->elect_ctx, NULL, memory_order_release); @@ -3285,8 +3284,8 @@ void ha_start_election_async(ha_state_t *ha, cluster_t *c, raft_start_election(&ha->raft); /* For single-node, raft_start_election fires on_become_leader synchronously. - * on_become_leader (raft_cb_become_leader) handles role → COMMITTING, - * phase2, and committing timeout. Free the ctx here since no raft callbacks + * on_become_leader (raft_cb_become_leader) handles role -> CTRL_PRIMARY and + * phase2. Free the ctx here since no raft callbacks * will reference it after raft_start_election returns for single-node. */ if (n_peers == 1) { atomic_store_explicit(&ha->elect_ctx, NULL, memory_order_release); diff --git a/src/controller/ha.h b/src/controller/ha.h index 36ffe7fe..4aebe1f0 100644 --- a/src/controller/ha.h +++ b/src/controller/ha.h @@ -60,8 +60,7 @@ typedef enum { CTRL_PRIMARY = 0, CTRL_STANDBY = 1, CTRL_INIT = 2, /* Startup: listener open, awaiting heartbeat or election timeout */ - CTRL_ELECTING = 3, /* Running quorum election via OP_requestVote */ - CTRL_PRIMARY_COMMITTING = 4 /* Primary waiting for quorum ACK on config commit */ + CTRL_ELECTING = 3 /* Running quorum election via OP_requestVote */ } ctrl_role_t; /* ── Election types ────────────────────────────────────────── */ @@ -105,8 +104,8 @@ typedef struct { * watchdog. Set by ha_start_election_async after scheduling the timeout. */ void *timeout_ctx; /* election_timeout_ctx_t*; NULL until watchdog scheduled */ /* Task 4.1: cluster_t* so ha_elect_evaluate_and_signal can call - * cluster_init_phase2(c) and arm the committing timeout watchdog. - * NULL in unit tests (c=NULL bypasses phase2 and watchdog arming). */ + * cluster_init_phase2(c). + * NULL in unit tests (c=NULL bypasses phase2). */ cluster_t *c; /* Task 6.3: candidate's own LSN/tag, stored unconditionally so that * ha_compute_safe_committed_lsn can use them even for single-node elections @@ -128,7 +127,6 @@ typedef struct { #define HA_DEFAULT_MAX_PROMOTION_RETRIES 5 #define HA_DEFAULT_ELECTION_JITTER_MAX_MS 200 #define HA_DEFAULT_ELECTION_PRIORITY_STEP_MS 500 -#define HA_DEFAULT_COMMITTING_TIMEOUT_MS 60000 typedef struct { uint32_t heartbeat_interval_ms; /* Primary heartbeat send interval (min 2000 ms) */ @@ -138,7 +136,6 @@ typedef struct { uint32_t max_promotion_retries; /* Max consecutive failed promotion attempts */ uint32_t election_jitter_max_ms; /* random jitter added to election timeout (default 200) */ uint32_t election_priority_step_ms; /* extra ms per priority level (default 500) */ - uint32_t committing_timeout_ms; /* Quorum-ACK deadline for CTRL_PRIMARY_COMMITTING (default 60000) */ } ha_config_t; /* probe_ctx_t deleted — only used by am_i_primary() (election-iomgr-refactor) */ @@ -177,14 +174,12 @@ typedef struct { /* ── New fields added by election-iomgr-refactor spec (Task 2.1) ── */ void *client_ssl_ctx; /* SSL_CTX* (TLS_client_method); created in myapp_run */ int phase2_done; /* 1 after cluster_init_phase2 runs */ - _Atomic int repl_ack_count; /* count of standbys that ACKed in COMMITTING state */ int n_peers; /* total cluster size (including self), matches raft convention */ /* ── New fields added by raft-library spec (Task 6.1) ──────── */ raft_state_t raft; /* Raft consensus state (wired in task 6.2) */ /* ── New fields added by raft-library spec (Task 6.2) ──────── */ cluster_t *raft_c; /* cluster_t* set by ha_start_election_async; used by raft callbacks */ void *raft_election_timeout_ctx; /* election_timeout_ctx_t* for cancel (task 6.2) */ - void *committing_timeout_ctx; /* committing_timeout_ctx_t* for cancel on step-down */ /* ── New fields added by ha-heartbeat-liveness spec (Task 1.1) ── */ time_t last_activity; /* CLOCK_MONOTONIC seconds of last primary message; 0 = none yet */ /* ── New fields added by ha-repl-hardening spec (Task 1.4) ──────── */ @@ -376,7 +371,7 @@ ha_repl_action_t ha_evaluate_repl_action(uint64_t standby_tag, * ha_process_standby_state: update per-standby convergence tracking. * * Called on each heartbeat reply from a standby while the primary is in - * CTRL_PRIMARY_COMMITTING or CTRL_PRIMARY. Stores the standby's reported + * CTRL_PRIMARY. Stores the standby's reported * (config_tag, applied_lsn) and evaluates whether the standby has converged * with the primary's committed state. * @@ -650,7 +645,7 @@ int ha_dispatch_repl_action_for_standby(ha_state_t *ha, * an OP_hello CtrlHelloRequest to each connected standby. * This triggers the existing catch-up replication path in handle_ctrlhello * (Req 7.2). Defined in cluster.c; called from ha_elect_evaluate_and_signal - * in the COMMITTING path. + * in the post-election path. */ void ha_send_ctrlhello_to_all_standbys(cluster_t *c); @@ -668,7 +663,7 @@ extern int (*ha_phase2_hook)(void *c); /* * ha_send_ctrlhello_hook: test-only seam for ha_send_ctrlhello_to_all_standbys. - * When non-NULL, ha_elect_evaluate_and_signal() calls this in the COMMITTING + * When non-NULL, ha_elect_evaluate_and_signal() calls this in the post-election * path instead of the real function (cluster.c not linked in test binary). * Requirements: 7.2. */ @@ -676,7 +671,7 @@ extern int (*ha_send_ctrlhello_hook)(void *c); /* * ha_push_config_hook: test-only seam for push_config_to_standbys. - * When non-NULL, ha_elect_evaluate_and_signal() calls this in the COMMITTING + * When non-NULL, ha_elect_evaluate_and_signal() calls this in the post-election * path instead of the real function. Requirements: 7.1. */ extern int (*ha_push_config_hook)(void *c); diff --git a/src/controller/testsuite/test_commit_barrier_rollout.c b/src/controller/testsuite/test_commit_barrier_rollout.c index 1105de48..d2a3b341 100644 --- a/src/controller/testsuite/test_commit_barrier_rollout.c +++ b/src/controller/testsuite/test_commit_barrier_rollout.c @@ -820,7 +820,6 @@ static ha_config_t g_fg_cfg = { .max_promotion_retries = 5, .election_jitter_max_ms = 0, .election_priority_step_ms = 500, - .committing_timeout_ms = 10000, }; /* Arrange a CTRL_PRIMARY ha_state_t with n_endpoints connected standby diff --git a/src/controller/testsuite/test_commit_engine.c b/src/controller/testsuite/test_commit_engine.c index c2a6756c..80edd4e5 100644 --- a/src/controller/testsuite/test_commit_engine.c +++ b/src/controller/testsuite/test_commit_engine.c @@ -840,7 +840,6 @@ static ha_config_t g_gate_cfg = { .max_promotion_retries = 5, .election_jitter_max_ms = 0, .election_priority_step_ms = 500, - .committing_timeout_ms = 10000, }; /* Arrange a CTRL_PRIMARY ha_state_t with n_endpoints connected standby diff --git a/src/controller/testsuite/test_ha_connection_reuse.c b/src/controller/testsuite/test_ha_connection_reuse.c index 3af988d0..d3c35b21 100644 --- a/src/controller/testsuite/test_ha_connection_reuse.c +++ b/src/controller/testsuite/test_ha_connection_reuse.c @@ -175,11 +175,11 @@ static void test_dispatcher_electing_calls_vote_reply_hook(void) printf("test_dispatcher_electing_calls_vote_reply_hook: PASS\n"); } -/* ── Test 2 (dispatcher transitional-leader → repl_ack count) DELETED ───────── +/* ── Test 2 (dispatcher transitional-leader ack count) DELETED ───────── * ha-single-leader-role task 4.1. Subject was the deleted ack-counting - * promotion machinery (replCatchUpDone incrementing repl_ack_count to drive a - * transitional leader state to PRIMARY). That path is gone — nothing left to - * verify. No shim (design §"Transitional-State Deletions"). + * promotion machinery (replCatchUpDone incrementing the per-term ACK tally to + * drive a transitional leader state to PRIMARY). That path is gone — nothing + * left to verify. No shim (design §"Transitional-State Deletions"). */ /* ── Test 3: dispatcher drops packet for CTRL_STANDBY ───────────────────── */ @@ -477,10 +477,10 @@ static void test_follower_passive_no_outbound_connect(void) printf("test_follower_passive_no_outbound_connect: PASS\n"); } -/* ── Test 10 (Task 7.3: promoted ep repl_ack promotes to PRIMARY) DELETED ───── +/* ── Test 10 (Task 7.3: promoted ep ack promotes to PRIMARY) DELETED ───── * ha-single-leader-role task 4.1. Subject was the deleted ack-counting - * promotion machinery (replCatchUpDone incrementing repl_ack_count until the - * majority threshold promoted a transitional leader state to CTRL_PRIMARY via + * promotion machinery (replCatchUpDone incrementing the per-term ACK tally until + * the majority threshold promoted a transitional leader state to CTRL_PRIMARY via * ha_promote_to_primary). * The mechanism is gone — promotion is now the heartbeat decision loop + * per-entry majority ack. Nothing left to verify; no shim (design diff --git a/src/controller/testsuite/test_ha_role.c b/src/controller/testsuite/test_ha_role.c index b1f83955..de6895b2 100644 --- a/src/controller/testsuite/test_ha_role.c +++ b/src/controller/testsuite/test_ha_role.c @@ -150,7 +150,7 @@ static void test_demote_sets_standby_role(void) /* * Verify that ha_init zeroes the new fields added for election-iomgr-refactor: - * phase2_done, repl_ack_count, n_peers, client_ssl_ctx + * phase2_done, n_peers, client_ssl_ctx * and that the surviving leader role values are distinct in ctrl_role_t. * * Requirements: 5.2, 8.1 (election-iomgr-refactor spec); 1.2 (ha-single-leader-role: @@ -171,9 +171,6 @@ static void test_ha_init_zeroes_new_fields(void) /* phase2_done must be explicitly zero after ha_init */ TEST_ASSERT_EQUAL_INT(0, ha.phase2_done); - /* repl_ack_count is _Atomic int; must be zero after ha_init */ - TEST_ASSERT_EQUAL_INT(0, atomic_load_explicit(&ha.repl_ack_count, memory_order_relaxed)); - /* n_peers must be zero after ha_init */ TEST_ASSERT_EQUAL_INT(0, ha.n_peers); diff --git a/src/controller/testsuite/test_ha_role_lifecycle.c b/src/controller/testsuite/test_ha_role_lifecycle.c index 86246c34..18063e7a 100644 --- a/src/controller/testsuite/test_ha_role_lifecycle.c +++ b/src/controller/testsuite/test_ha_role_lifecycle.c @@ -755,7 +755,7 @@ static void *lifecycle_wait_thread(void *p) * * SHARED-SEQUENCE CONTRACT PIN (GREEN now, MUST STAY GREEN — not a behavioral * RED carrier). After task 2.2 BOTH roads to PRIMARY (the raft become-leader - * callback and ha_repl_ack_received's quorum path) route through this same + * callback and the legacy quorum-ack path) route through this same * ha_promote_to_primary sequence, which task 2.2 augments with the noop+gate * tail while KEEPING hook registration. This case is therefore a CONTRACT/ * REGRESSION pin proving the shared promotion sequence registers the persist @@ -809,8 +809,8 @@ static void test_promotion_registers_persist_hooks(void) /* ── Case 4: NO WATCHDOG — promotion arms no delaymgr task (Req 1.5, 2.3). * - * The shared promotion sequence must never arm a wall-clock watchdog (the - * deleted committing_timeout_fn). Asserted via the delaymgr task-count seam: + * The shared promotion sequence must never arm a wall-clock promotion watchdog + * (deleted in task 4.x). Asserted via the delaymgr task-count seam: * the count is unchanged across ha_promote_to_primary. * * Status note (RED vs already-GREEN): the force-promotion watchdog is armed @@ -820,8 +820,8 @@ static void test_promotion_registers_persist_hooks(void) * exercises — never armed a watchdog, so this case is ALREADY GREEN today and * stands as a regression-pin guaranteeing task 2.2's shared sequence does not * introduce one. The watchdog REMOVAL from raft_cb_become_leader is covered - * at integration scope (task 4.x deletes committing_timeout_fn and the - * test_ha_evaluate_committing committing-timeout cases). */ + * at integration scope (task 4.x deletes the promotion-watchdog arm and the + * transitional-state evaluation cases). */ static void test_promotion_arms_no_delaymgr_watchdog(void) { #ifdef XCE_TESTING @@ -1140,11 +1140,11 @@ static void test_legacy_and_raft_promotion_are_symmetric(void) "legacy road must arm the own-term gate at noop_lsn (Req 1.4, 2.1) — " "identical to the raft road's shared tail"); - /* Symmetry axis 4: NO committing-timeout watchdog armed (Req 1.5). */ + /* Symmetry axis 4: NO promotion watchdog armed (Req 1.5). */ int after = delaymgr_task_count(&g_lc.delaymgr); TEST_ASSERT_EQUAL_INT_MESSAGE(before, after, "legacy road must arm NO delaymgr watchdog (Req 1.5) — symmetric with " - "the raft road; the committing-timeout arm is deleted from this path"); + "the raft road; the promotion-watchdog arm is deleted from this path"); pthread_mutex_destroy(&ctx->lock); sem_destroy(&ctx->done); diff --git a/src/lib/raft/raft.c b/src/lib/raft/raft.c index e05f9b3f..d909a9b5 100644 --- a/src/lib/raft/raft.c +++ b/src/lib/raft/raft.c @@ -120,8 +120,8 @@ int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term, /* Step 2: term upgrade — update term, clear vote, step down to FOLLOWER. * If we were LEADER, notify the HA FSM so it updates ha->role. - * Without this, the HA FSM stays in CTRL_PRIMARY_COMMITTING and - * eventually promotes to PRIMARY via the committing timeout → split-brain. */ + * Without this, the HA FSM stays in CTRL_PRIMARY after losing the term, + * never stepping down to STANDBY → split-brain. */ if (term > s->term) { int was_leader = (s->role == RAFT_LEADER); s->term = term;