diff --git a/.kiro/specs/controller-status/tasks.md b/.kiro/specs/controller-status/tasks.md index dbfc22fe..2cd58bff 100644 --- a/.kiro/specs/controller-status/tasks.md +++ b/.kiro/specs/controller-status/tasks.md @@ -124,7 +124,7 @@ - _Boundary: ctrlstatus_fill_ - _Depends: 7.1_ -- [ ] 7.5 Render the election term and time in the `ctrlstatus` client command +- [x] 7.5 Render the election term and time in the `ctrlstatus` client command - Print the primary's election term and election time next to the primary's identity in the client command's output, rendering the election time on the same clock/format already used for the responder snapshot timestamp; render both as "unknown" whenever the primary identity itself is reported as unknown. - Observable: piping `ctrlstatus` through the client prints the election term and a log-comparable election-time string alongside the existing fields when a primary is known, and prints "unknown" for both when it is not. - _Requirements: 12.1, 12.3, 12.4, 12.6_ diff --git a/src/harness/test.c b/src/harness/test.c index 92c36271..f9108539 100644 --- a/src/harness/test.c +++ b/src/harness/test.c @@ -3097,6 +3097,30 @@ int cmd_ctrlstatus(int argc, char* argv[]) printf("Primary: %s\n", 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)) { + printf("electedTerm: unknown\n"); + printf("electedAt: unknown\n"); + } else { + char etbuf[64]; + + printf("electedTerm: %llu\n", (unsigned long long)status->term); + if (ctrlstatus_format_time_ms(status->elected_at_ms, etbuf, + sizeof(etbuf)) != NULL) { + printf("electedAt: %s\n", etbuf); + } else { + printf("electedAt: unknown\n"); + } + } + /* Answering node + role, framed as a property of the responder and kept * DISTINCT from the authoritative Primary line above (Req 3.1/3.2/3.3): a * transient responder role (electing/initializing) must not be misread as