diff --git a/.kiro/specs/controller-status/design.md b/.kiro/specs/controller-status/design.md index 4612de86..c034208c 100644 --- a/.kiro/specs/controller-status/design.md +++ b/.kiro/specs/controller-status/design.md @@ -358,14 +358,17 @@ No change to `term @4` on `CtrlStatus` — Req 12's term half changes only the C **Modified files:** - `ha.h` — add `_Atomic uint64_t last_known_elected_at_ms;` to `ha_state_t`, next to `last_known_committed_lsn`/`last_known_committed_config_tag`. - `src/schema/controller.capnp` — add `electedAtMs` to `CtrlStatus` (@14) and to `HeartbeatMsg` (@4); regen `controller.capnp.h/.c`. -- `src/controller/ha.c` — `ha_do_promote` and `raft_cb_become_leader`: stamp `last_known_elected_at_ms` from `CLOCK_REALTIME` at each road's final promotion-commit point (Research Items 1, 3); the heartbeat-receive call site: unconditional store alongside the existing `ha_observe_heartbeat_committed` call (Research Item 2); `primary_heartbeat_fn`: populate the outgoing `heartbeat_msg_t.elected_at_ms` from `last_known_elected_at_ms` (Research Item 2). +- `src/controller/ha.c` — `ha_do_promote` and `raft_cb_become_leader`: stamp `last_known_elected_at_ms` from `CLOCK_REALTIME` at each road's final promotion-commit point (Research Items 1, 3); the heartbeat-receive call site (`ha_consume_heartbeat_committed`): unconditional store alongside the existing `ha_observe_heartbeat_committed` call (Research Item 2); `primary_heartbeat_fn`: read `last_known_elected_at_ms` into a local, passed through to the outgoing heartbeat (Research Item 2). - `src/controller/ctrlstatus.c` — `ctrlstatus_fill`: read `last_known_elected_at_ms` unconditionally into `st->elected_at_ms` (no role-branch, Locked Decision 2). -- `src/harness/test.c` — `cmd_ctrlstatus`: two new printed lines, `electedTerm` (derived from `term`+`primaryName`) and `electedAt` (rendered via `ctrlstatus_format_time_ms`), both "unknown" when `primaryName` is unknown. **Path correction (as-built, confirmed 2026-09-15):** `cmd_ctrlstatus`/`cmd_wait_primary` now live in `src/harness/test.c`, not `src/controller/testsuite/test.c` as Reqs 1-11's original design text says — the test-client harness was consolidated into `src/harness/` by a later refactor (see the `xce-harness-component` spec) after those sections were written. Historical Reqs 1-11 text is left as-is (accurate as of when implemented); this delta targets the current, correct location. +- `src/harness/test.c` — `cmd_ctrlstatus`: two new printed lines, `electedTerm` (derived from `term`, gated on `elected_at_ms`) and `electedAt` (rendered via `ctrlstatus_format_time_ms`), both "unknown" when `elected_at_ms == 0` (corrected 2026-09-16 — see Locked Decision 3; originally gated on `primaryName`). **Path correction (as-built, confirmed 2026-09-15):** `cmd_ctrlstatus`/`cmd_wait_primary` now live in `src/harness/test.c`, not `src/controller/testsuite/test.c` as Reqs 1-11's original design text says — the test-client harness was consolidated into `src/harness/` by a later refactor (see the `xce-harness-component` spec) after those sections were written. Historical Reqs 1-11 text is left as-is (accurate as of when implemented); this delta targets the current, correct location. +- `src/include/request.h`, `src/lib/request.c` — **as-built addition (2026-09-16), not anticipated at design time:** `xce_ha_heartbeat_req`, not `primary_heartbeat_fn` directly, owns building the outgoing `heartbeat_msg_t` (it already threaded `term`/`committed_lsn`/`committed_config_tag` as scalar args this same way) — task 7.3 found this only once it read the real send path, and added `elected_at_ms` as a 4th scalar parameter, mechanically mirroring the 3 existing ones. No election/heartbeat *protocol* semantics changed, only one added field in an already-established parameter-threading pattern. - `src/controller/testsuite/test_ctrlstatus.c` — extend/add unit tests (below). -- `src/controller/testsuite/test_ha_heartbeat_committed.c` (or a new sibling test file) — coverage for the heartbeat-carried `electedAtMs` store. -- `src/controller/testsuite/test_ha_role.c` — extend `test_promote_increments_term`-style coverage to also assert `last_known_elected_at_ms` is stamped on promotion. +- `src/controller/testsuite/test_ha_heartbeat_committed.c` — coverage for the heartbeat-carried `electedAtMs` store (the reconcile-free negative-space test, mirroring `test_observe_no_reconcile_no_other_mutation`). +- `src/controller/testsuite/test_ha_role.c` — extend `test_promote_increments_term`-style coverage to also assert `last_known_elected_at_ms` is stamped on promotion (and NOT stamped on a refused promotion attempt). +- `src/controller/testsuite/test_ha_liveness.c`, `src/controller/testsuite/test_ha_partition.c` — **as-built addition (2026-09-16):** signature-conformance updates only (these files mirror `ha_heartbeat_req_hook`'s signature for their own test doubles; the new 4th parameter had to be threaded through, no test behavior changed). +- `src/lib/testsuite/test_request.c` — **as-built addition (2026-09-16):** extends the existing `xce_ha_heartbeat_req` round-trip test to also cover `elected_at_ms`, proving the new field survives a real encode/decode cycle. -No new files (capnp fields are generated; all logic slots into existing TUs) — consistent with this spec's established pattern. +No new files (capnp fields are generated; all logic slots into existing TUs) — consistent with this spec's established pattern. **Known gap (2026-09-16 feature-level validation):** `cmd_ctrlstatus`'s print-gate logic (the exact code bug-1818 regressed in) has no checked-in unit/regression test — this print layer has never had one, for any of its lines, going back to Req 8's original implementation; coverage is live-E2E (task 7.6) plus an ephemeral scratch-program check at fix time, not a permanent regression guard. Accepted as a pre-existing, deliberate scope boundary of this spec (see Requirement 8's own design text), not a new gap introduced by this delta — flagged here for whoever next touches this function. ### Requirements Traceability (delta) | Requirement | Summary | Components | Interfaces |