fix(controller-status): gate electedTerm/electedAt on elected_at_ms, not primaryName
Live 3-node E2E validation (task 7.6) found that cmd_ctrlstatus's electedTerm/electedAt lines (task 7.5) gated on NULLSTR(primaryName), which is unconditionally empty on every non-primary responder in this build (ctrlstatus_fill only self-reports the primary's own name -- ha->primary_host is vestigial, a pre-existing Req 2 behavior). This made electedTerm/electedAt print "unknown" on every standby, forever, even though elected_at_ms was correctly, non-zero, and convergently populated the whole time (term/committedLsn/appliedLsn, sourced the same unconditional way, DID correctly display and converge on the same query). Fix: gate on status->elected_at_ms == 0 instead -- the field stays exactly 0 until a real promotion stamps it (either road) or a heartbeat carries a learned value from an actual primary, unlike term (which can legitimately bump from mere vote/candidacy activity before any primary is elected). ctrlstatus_fill itself is untouched; this is purely a cmd_ctrlstatus display-layer correction. Also corrects design.md's "Design Delta -- Requirement 12" Locked Decision 3 (and its dependent traceability/service-contract text) to document this live-E2E finding, mirroring the spec's existing delta convention (Req 10/11's own live-E2E correction). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
d982d34eb5
commit
808396618c
2 changed files with 26 additions and 17 deletions
|
|
@ -3098,15 +3098,23 @@ int cmd_ctrlstatus(int argc, char* argv[])
|
|||
NULLSTR(status->primaryName) ? "unknown" : status->primaryName);
|
||||
|
||||
/* Election term + time of the CURRENT primary (Req 12.1/12.3/12.4/12.6):
|
||||
* gated on the SAME primaryName-known/unknown derivation the Primary:
|
||||
* line above already computes (Req 2.2/2.3) -- a stale/learned
|
||||
* elected_at_ms left over from a since-lost primary must never surface
|
||||
* once primaryName itself reports "unknown" (design.md Locked Decision
|
||||
* 3). electedTerm is DERIVED client-side from the existing `term` field
|
||||
* (no new wire field, design.md Locked Decision 1); electedAt renders
|
||||
* `elected_at_ms` on the same clock/format as responderTime below,
|
||||
* reusing ctrlstatus_format_time_ms (design.md Locked Decision 4). */
|
||||
if (NULLSTR(status->primaryName)) {
|
||||
* gated on elected_at_ms itself, NOT on primaryName (design.md Locked
|
||||
* Decision 3, corrected 2026-09-16 / task 7.6 live E2E finding).
|
||||
* primaryName is unconditionally empty on every non-primary responder
|
||||
* (ctrlstatus_fill only self-reports the primary's own name; a
|
||||
* pre-existing Req 2 behavior, unrelated to this delta), so gating on it
|
||||
* made electedTerm/electedAt read "unknown" on every standby forever,
|
||||
* even once a real election had converged. elected_at_ms stays exactly 0
|
||||
* until a real promotion stamps it (ha_do_promote, task 7.2) or a
|
||||
* heartbeat carries a learned value from an actual primary (task 7.3) --
|
||||
* unlike `term`, which can legitimately bump on mere vote/candidacy
|
||||
* activity (raft term adoption) before any primary is actually elected,
|
||||
* so `term == 0` would be the wrong gate. electedTerm is DERIVED
|
||||
* client-side from the existing `term` field (no new wire field,
|
||||
* design.md Locked Decision 1); electedAt renders `elected_at_ms` on the
|
||||
* same clock/format as responderTime below, reusing
|
||||
* ctrlstatus_format_time_ms (design.md Locked Decision 4). */
|
||||
if (status->elected_at_ms == 0) {
|
||||
printf("electedTerm: unknown\n");
|
||||
printf("electedAt: unknown\n");
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue