feat(ha-heartbeat-committed-propagation): add reconcile-free ha_observe_heartbeat_committed (task 2.1)

Strict-monotonic updater for a standby's learned committed position: advance
last_known_committed_lsn only when strictly greater (zero/lower no-op), set the
config tag together (tag-before-lsn release ordering for reader safety). No
ha_rejoin_reconcile, no truncation, no other mutation (Req 2.1-2.3, 3.1, 3.3).
4 revert-detecting unit tests; controller suite 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-20 11:52:18 +00:00
parent 282a82ff7e
commit 8bfa861be2
6 changed files with 273 additions and 1 deletions

View file

@ -878,6 +878,27 @@ void ha_discover_primary(ha_state_t *ha, cluster_t *c);
/* ha_on_heartbeat_received: reset the heartbeat deadline (Req 6.3). */
void ha_on_heartbeat_received(ha_state_t *ha);
/*
* ha_observe_heartbeat_committed: reconcile-free monotonic update of a standby's
* learned committed position from a primary heartbeat
* (ha-heartbeat-committed-propagation, Req 2.1, 2.2, 2.3, 3.1, 3.3).
*
* Advances ha->last_known_committed_lsn to committed_lsn ONLY when it is
* strictly greater than the current value (monotonic); when it advances, the
* carried committed_config_tag is stored together with it (they move as a pair —
* the tag never regresses independently). No-op when committed_lsn is 0 or
* <= the current learned LSN.
*
* This is the reconcile-free sibling of cluster_apply_ctrlhello_reply: it MUST
* NOT call ha_rejoin_reconcile, truncate the WAL, or modify ANY other state
* (produced/persisted committed, role, term, applied, etc.). The value is
* advisory — for observability and vote-floor freshness only. The caller (the
* OP_nop receive path) applies the term-guard before invoking this; the helper
* itself is term-agnostic and single-receiver-thread safe.
*/
void ha_observe_heartbeat_committed(cluster_t *c, uint64_t committed_lsn,
uint64_t committed_config_tag);
/* ha_heartbeat_is_fresh: 1 if a primary message arrived within
* heartbeat_timeout_ms (leader-stickiness predicate). Exposed for PreVote
* denial-reason logging in cluster.c; pure read, no state change. */