feat(controller-status): add election-time schema fields + ha_state_t (task 7.1)

Adds CtrlStatus.electedAtMs@14 and HeartbeatMsg.electedAtMs@4 to the
capnp schema, their hand-authored typemap.h counterparts, and a new
ha_state_t.last_known_elected_at_ms field alongside last_known_committed_lsn.
Schema/state plumbing only -- nothing stamps or consumes these fields yet
(tasks 7.2-7.5). Codec round-trip tests confirm both new fields survive
encode/decode unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
Rongsong Shen 2026-09-16 04:21:02 +00:00
parent 7556856e5c
commit cae1039ed7
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
6 changed files with 184 additions and 1 deletions

View file

@ -24,6 +24,9 @@
#include "xce/ctrlstatus_fmt.h" /* ctrlstatus_format_time_ms (task 6.3) */
#include "controller.capnp.h" /* generated codec: CtrlStatus/HeartbeatMsg
* encode/decode (task 7.1 round-trip tests) */
/* ── helpers ────────────────────────────────────────────────── */
static ha_config_t cs_valid_cfg(void)
@ -511,6 +514,125 @@ static void test_ctrlstatus_format_time_ms(void)
printf("test_ctrlstatus_format_time_ms: PASS\n");
}
/* ── (k) codec round-trip: CtrlStatus.electedAtMs @14 (task 7.1, Req 12.1) ──
* Design delta "Design Delta — Requirement 12", Schema delta subsection: a new
* append-only UInt64 epoch-millis field on CtrlStatus, next free ordinal after
* responderTimeMs @13. This is schema/codec-level ONLY: ctrlstatus_fill does
* not populate elected_at_ms yet (that wiring is task 7.4, out of boundary for
* 7.1) -- the struct is built directly to prove the new wire field survives a
* genuine encode -> bytes -> decode cycle unchanged, mirroring the pattern in
* src/lib/testsuite/test_ctrlstatus.c's test_ctrlstatus_roundtrip_all_fields.
*/
static void test_ctrlstatus_electedatms_roundtrip(void)
{
xce_ctrlstatus_t in;
memset(&in, 0, sizeof(in));
in.clusterName = "lab-cluster";
in.responderName = "ctrl-a";
in.elected_at_ms = UINT64_C(1781939830911); /* known non-zero value */
struct capn c;
struct capn_segment* cs;
struct CtrlStatus cap_in;
CtrlStatus_ptr sp;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&cap_in, 0, sizeof(cap_in));
encode_CtrlStatus(cs, &cap_in, &in);
sp = new_CtrlStatus(cs);
write_CtrlStatus(&cap_in, sp);
capn_setp(capn_root(&c), 0, sp.p);
size_t sz = capn_size(&c);
uint8_t* buf = (uint8_t*)malloc(sz);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL_size_t(sz, capn_write_mem(&c, buf, sz, 0));
capn_free(&c);
struct capn dc;
struct CtrlStatus cap_out;
CtrlStatus_ptr dp;
xce_ctrlstatus_t out;
capn_init_mem(&dc, buf, sz, 0);
dp.p = capn_getp(capn_root(&dc), 0, 1);
read_CtrlStatus(&cap_out, dp);
memset(&out, 0, sizeof(out));
decode_CtrlStatus(&out, &cap_out);
TEST_ASSERT_EQUAL_UINT64(UINT64_C(1781939830911), out.elected_at_ms);
free_CtrlStatus(&out);
capn_free(&dc);
free(buf);
printf("test_ctrlstatus_electedatms_roundtrip: PASS\n");
}
/* ── (l) codec round-trip: HeartbeatMsg.electedAtMs @4 (task 7.1, Req 12.1) ──
* Same design-delta schema addition on the heartbeat message the primary
* already sends to standbys, next free ordinal after committedConfigTag @3.
* Direct struct construction + encode/decode -- no heartbeat send/receive
* logic is exercised (that is task 7.3, out of boundary for 7.1). Also proves
* the pre-existing sibling fields are undisturbed by the new ordinal. */
static void test_heartbeatmsg_electedatms_roundtrip(void)
{
heartbeat_msg_t in;
memset(&in, 0, sizeof(in));
in.sender_priority = 1;
in.term = 7;
in.committed_lsn = 100;
in.committed_config_tag = 200;
in.elected_at_ms = UINT64_C(1781939830911); /* known non-zero value */
struct capn c;
struct capn_segment* cs;
struct HeartbeatMsg cap_in;
HeartbeatMsg_ptr sp;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&cap_in, 0, sizeof(cap_in));
encode_HeartbeatMsg(cs, &cap_in, &in);
sp = new_HeartbeatMsg(cs);
write_HeartbeatMsg(&cap_in, sp);
capn_setp(capn_root(&c), 0, sp.p);
size_t sz = capn_size(&c);
uint8_t* buf = (uint8_t*)malloc(sz);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL_size_t(sz, capn_write_mem(&c, buf, sz, 0));
capn_free(&c);
struct capn dc;
struct HeartbeatMsg cap_out;
HeartbeatMsg_ptr dp;
heartbeat_msg_t out;
capn_init_mem(&dc, buf, sz, 0);
dp.p = capn_getp(capn_root(&dc), 0, 1);
read_HeartbeatMsg(&cap_out, dp);
memset(&out, 0, sizeof(out));
decode_HeartbeatMsg(&out, &cap_out);
TEST_ASSERT_EQUAL_UINT32(1, out.sender_priority);
TEST_ASSERT_EQUAL_UINT64(7, out.term);
TEST_ASSERT_EQUAL_UINT64(100, out.committed_lsn);
TEST_ASSERT_EQUAL_UINT64(200, out.committed_config_tag);
TEST_ASSERT_EQUAL_UINT64(UINT64_C(1781939830911), out.elected_at_ms);
/* heartbeat_msg_t has no heap-owned fields (all scalars) -- no free_* call */
capn_free(&dc);
free(buf);
printf("test_heartbeatmsg_electedatms_roundtrip: PASS\n");
}
/* ── suite entry point ─────────────────────────────────────── */
void test_ctrlstatus(void)
@ -528,4 +650,6 @@ void test_ctrlstatus(void)
RUN_TEST(test_ctrlstatus_standby_prelearn_committed_zero);
RUN_TEST(test_ctrlstatus_primary_reports_own_committed);
RUN_TEST(test_ctrlstatus_responder_time_ms);
RUN_TEST(test_ctrlstatus_electedatms_roundtrip);
RUN_TEST(test_heartbeatmsg_electedatms_roundtrip);
}