🤖 This issue was generated by Claude on behalf of @keddie.
Found by the review of PR #1240 (#1231 fix). That PR narrowed the false-success bug (transfer now completes only when the higher-term step-down's sender from == target), a strict improvement — but the residual remains because "sender of the first higher-term message" identifies neither the winner nor even a campaigner.
Severity: 🟠 contract (false success/failure — only reachable on degraded networks; healthy clusters are protected by §4.2.3 stickiness).
Residual false-SUCCESS (echo route). Transfer A→B in flight; unrelated D wins term T+1 on a partition majority; B adopts T+1 from D's heartbeat; B's next AppendEntriesResponse(term=T+1) to A → stepDown(from=B) → from == target → SUCCESS reported while the real leader is D. Also: success can complete at the target's own RequestVote receipt (the isTransferCandidate step-down path) before the vote is even granted — a not-caught-up target that campaigns mid-transfer reports success then loses on LogNotUpToDate.
Residual false-FAILURE (non-target-first). Target B genuinely wins with {B,C} (A never saw B's RequestVote, slow A↔B link); C's AppendEntriesResponse(T+1) outraces B's first leader message → A steps down from=C → transfer reports FAILURE while B is leader.
Root cause. A higher-term message's sender is not the win signal. The §3.10-faithful signal is a leader-authored message from the target — an AppendEntries/InstallSnapshot with leaderId == target at the new term (proof the target is actually leader).
Fix. Keep the transfer pending across the old leader's step-down; complete SUCCESS only when _leader resolves to the target at the higher term (a leader-authored message with leaderId == target), else fail on the auto-timeout. This is the deferred-completion design PR #1240 explicitly deferred as "invasive."
Interim (done in #1240 via KDoc softening): the transferLeadership / onLeadershipRelinquished KDocs are softened to "best-effort confirmation" rather than a guarantee. This issue tracks the real gating fix.
Found by the review of PR #1240 (#1231 fix). That PR narrowed the false-success bug (transfer now completes only when the higher-term step-down's sender
from == target), a strict improvement — but the residual remains because "sender of the first higher-term message" identifies neither the winner nor even a campaigner.Severity: 🟠 contract (false success/failure — only reachable on degraded networks; healthy clusters are protected by §4.2.3 stickiness).
Residual false-SUCCESS (echo route). Transfer A→B in flight; unrelated D wins term T+1 on a partition majority; B adopts T+1 from D's heartbeat; B's next
AppendEntriesResponse(term=T+1)to A →stepDown(from=B)→from == target→ SUCCESS reported while the real leader is D. Also: success can complete at the target's ownRequestVotereceipt (theisTransferCandidatestep-down path) before the vote is even granted — a not-caught-up target that campaigns mid-transfer reports success then loses onLogNotUpToDate.Residual false-FAILURE (non-target-first). Target B genuinely wins with {B,C} (A never saw B's RequestVote, slow A↔B link); C's
AppendEntriesResponse(T+1)outraces B's first leader message → A steps downfrom=C→ transfer reports FAILURE while B is leader.Root cause. A higher-term message's sender is not the win signal. The §3.10-faithful signal is a leader-authored message from the target — an
AppendEntries/InstallSnapshotwithleaderId == targetat the new term (proof the target is actually leader).Fix. Keep the transfer pending across the old leader's step-down; complete SUCCESS only when
_leaderresolves to the target at the higher term (a leader-authored message withleaderId == target), else fail on the auto-timeout. This is the deferred-completion design PR #1240 explicitly deferred as "invasive."Interim (done in #1240 via KDoc softening): the
transferLeadership/onLeadershipRelinquishedKDocs are softened to "best-effort confirmation" rather than a guarantee. This issue tracks the real gating fix.