feat(raft-log-replication): header contract — log model, callbacks, entry points (tasks 1.1-1.3)

Extend src/lib/raft/raft.h for the TLA+ log half (raft.tla logVars/leaderVars):
- raft_state_t: log_len, next_index[], match_index[] (no entry bytes stored —
  terms via on_get_entry_term, bytes in the adapter); raft_config_t.initial_log_len.
- raft_callbacks_t: 4 REQUIRED log callbacks on_persist_entry, on_get_entry_term,
  on_send_append_entries, on_truncate_from.
- Entry points + raft_append_result_t: raft_log_append (ClientRequest),
  raft_send_append_entries (AppendEntries emit), raft_on_append_entries_request
  (Handle + UpdateTerm-first), raft_on_append_entries_reply.

Concretized on_send_append_entries's varargs "entries handle" placeholder to an
explicit n_entries (0/1); design.md updated to match. Header compiles clean with
and without XCE_TESTING (-Wall -Wextra -fsyntax-only).
This commit is contained in:
Rongsong Shen 2026-06-21 14:15:15 +00:00
parent 20e59d0b42
commit 6b5b201a64
3 changed files with 111 additions and 6 deletions

View file

@ -338,10 +338,13 @@ int on_persist_entry(void *ctx, uint64_t index, uint64_t term,
uint64_t on_get_entry_term(void *ctx, uint64_t index);
/* REQUIRED: transmit one AppendEntries to peer_idx with prevLog{Index,Term},
* leaderCommit, and an entries handle. */
* leaderCommit, and n_entries (0 = heartbeat, 1 = one entry). The library holds
* no entry bytes, so when n_entries==1 the adapter sources the single entry at
* prev_log_index+1 from its own log. [Concretizes the original varargs "entries
* handle" placeholder; impl 2026-06-21.] */
int on_send_append_entries(void *ctx, int peer_idx,
uint64_t prev_log_index, uint64_t prev_log_term,
uint64_t leader_commit /*, entries handle */ ...);
uint64_t leader_commit, int n_entries);
/* REQUIRED: delete local entries at index..end. */
int on_truncate_from(void *ctx, uint64_t index);

View file

@ -6,14 +6,14 @@
## 1. Header contract: log model, callbacks, entry points
- [ ] 1.1 Extend `raft.h` with the log model state and config restore field
- [x] 1.1 Extend `raft.h` with the log model state and config restore field
- Add `uint64_t log_len;`, `uint64_t next_index[RAFT_MAX_PEERS];`, `uint64_t match_index[RAFT_MAX_PEERS];` to `raft_state_t`
- Add `uint64_t initial_log_len;` to `raft_config_t` (restored WAL length at boot, default 0; library never reads storage)
- Observable completion: `raft.h` compiles; `sizeof(raft_state_t)` grows by the new fixed fields; no entry-byte storage added
- _Requirements: 1.1, 1.2, 3.1_
- _Boundary: raft.h_
- [ ] 1.2 Add the four new REQUIRED callbacks to `raft_callbacks_t` (verbatim contract)
- [x] 1.2 Add the four new REQUIRED callbacks to `raft_callbacks_t` (verbatim contract)
- Declare `on_persist_entry`, `on_get_entry_term`, `on_send_append_entries`, `on_truncate_from` with the exact signatures from design.md / brief
- Document each as REQUIRED and the `on_get_entry_term` → `0`-means-absent convention
- Observable completion: the four function-pointer members exist in `raft_callbacks_t` with the verbatim signatures; header doc comments match the Authoritative shared contract
@ -21,7 +21,8 @@
- _Boundary: raft.h_
- _Depends: 1.1_
- [ ] 1.3 Declare the three new entry points and `raft_append_result_t`
- [x] 1.3 Declare the three new entry points and `raft_append_result_t`
- NOTE (impl 2026-06-21): `on_send_append_entries`'s design "entries handle `...`" varargs placeholder was concretized to an explicit `int n_entries` (0=heartbeat, 1=one entry at `prev_log_index+1`; adapter sources the bytes since the library holds none). design.md §"Seam — raft_callbacks_t additions" updated to match. Downstream `raft-commit-safety` / `raft-sim-conformance` consume this concrete shape.
- Declare `raft_append_result_t`; declare `raft_log_append`, `raft_send_append_entries`, `raft_on_append_entries_request`, `raft_on_append_entries_reply` with the design.md signatures
- Observable completion: header declares all new entry points; a stub TU including `raft.h` compiles
- _Requirements: 2.1, 3.3, 4.7, 5.1_

View file

@ -44,6 +44,15 @@ typedef struct {
int pre_vote_in_progress; /* 1 while a pre-vote round is active */
int pre_votes_granted; /* grants collected this round (incl. self) */
int pre_vote_replies[RAFT_MAX_PEERS]; /* per-peer replied flag; avoids double-count */
/* ---- Log replication state (raft-log-replication, raft.tla logVars/leaderVars) ----
* The library stores NO entry bytes and NO per-entry term array — entry terms
* are obtained via on_get_entry_term; bytes live in the adapter/simulator. This
* keeps raft_state_t fixed-size and the core pure (same discipline as not
* storing (term, voted_for) bytes). Index is 1-based (raft.tla) and maps 1:1
* to the WAL LSN. */
uint64_t log_len; /* highest local log index (0 = empty) */
uint64_t next_index[RAFT_MAX_PEERS]; /* leader: next index to send each peer */
uint64_t match_index[RAFT_MAX_PEERS]; /* leader: highest index known replicated to each peer */
/* set by raft_init — needed by the state machine to fire callbacks */
const raft_callbacks_t *cbs;
void *ctx;
@ -69,7 +78,8 @@ typedef struct {
* REQUIRED callbacks (must be non-NULL at raft_init):
* on_send_vote_request, on_become_leader, on_become_follower,
* on_persist, on_schedule_timeout, on_cancel_timeout,
* on_start_election_after_delay
* on_start_election_after_delay,
* on_persist_entry, on_get_entry_term, on_send_append_entries, on_truncate_from
*
* OPTIONAL callbacks (may be NULL):
* on_vote_eligible — if NULL, all Raft-eligible candidates are granted
@ -109,6 +119,34 @@ typedef struct raft_callbacks_t {
void (*on_cancel_timeout)(void *ctx);
/* REQUIRED: Request delayed election start after RDHUP (priority-based jitter) */
void (*on_start_election_after_delay)(void *ctx, uint32_t delay_ms);
/* ---- Log replication callbacks (raft-log-replication) ----
* REQUIRED (all four non-NULL at raft_init). The library owns the protocol
* decisions; these perform the I/O. The library stores no entry bytes, so it
* reads entry terms only via on_get_entry_term and writes/truncates only via
* these callbacks. */
/* REQUIRED: durably append/overwrite the log entry at 1-based `index` with
* `term` (persist-before-act: called before an append/accept is reported). */
int (*on_persist_entry)(void *ctx, uint64_t index, uint64_t term,
const void *entry, size_t len);
/* REQUIRED: term of the local entry at 1-based `index` (0 if absent). */
uint64_t (*on_get_entry_term)(void *ctx, uint64_t index);
/* REQUIRED: transmit one AppendEntries to peer_idx with prevLog{Index,Term}
* and leader_commit. `n_entries` is 0 (heartbeat) or 1 (this spec, like
* raft.tla); when 1 the adapter sources the single entry at prev_log_index+1
* from its own log (the library holds no entry bytes). [Concretizes the
* design's varargs "entries handle" placeholder into an explicit count.] */
int (*on_send_append_entries)(void *ctx, int peer_idx,
uint64_t prev_log_index,
uint64_t prev_log_term,
uint64_t leader_commit, int n_entries);
/* REQUIRED: delete local entries at `index`..end. */
int (*on_truncate_from)(void *ctx, uint64_t index);
/* OPTIONAL: Trace callback for observability.
* event: short string literal, e.g. "vote_granted", "became_leader"
* detail: formatted human-readable context string
@ -136,6 +174,7 @@ typedef struct {
int my_priority; /* priority index for RDHUP jitter (0 = highest) */
uint64_t initial_term; /* term restored from disk; 0 on first boot */
int initial_voted_for; /* voted_for restored from disk; -1 if none */
uint64_t initial_log_len; /* WAL length restored from disk; 0 on first boot (library never reads storage) */
const raft_callbacks_t *cbs; /* all callbacks; required callbacks must be non-NULL */
void *ctx; /* opaque caller context passed to every callback */
} raft_config_t;
@ -258,6 +297,68 @@ int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term);
*/
int raft_on_pre_vote_reply(raft_state_t *s, int peer_idx, int granted);
/* ---------- Log replication (raft-log-replication) ----------
*
* The library owns the log protocol decisions (append, AppendEntries logOk +
* conflict truncation, UpdateTerm-on-any-RPC) and drives I/O through the four
* log callbacks. Index is 1-based (raft.tla) == WAL LSN. Commit-index advance
* and (lastLogTerm,lastLogIndex) vote freshness are NOT here — they are
* raft-commit-safety. */
/* Result of handling an AppendEntries request — the adapter replies with this. */
typedef struct {
uint64_t reply_term; /* current term to return to the leader */
int success; /* 1 = accepted, 0 = rejected */
uint64_t match_index; /* on success: prev_log_index + entries_len; else 0 */
} raft_append_result_t;
/*
* raft_log_append: leader appends one client entry (raft.tla ClientRequest, 246).
* Stamps the entry with s->term, assigns index log_len+1, persists it via
* on_persist_entry BEFORE returning success (persist-before-act). Does NOT
* advance any commit index (owned by raft-commit-safety).
* out_index receives the assigned 1-based index (== WAL LSN) on success.
* Returns RAFT_OK; -EPERM if role != RAFT_LEADER; propagates a non-zero
* on_persist_entry return on persist failure (no log mutation on failure).
*/
int raft_log_append(raft_state_t *s, const void *entry, size_t len,
uint64_t *out_index);
/*
* raft_send_append_entries: emit one AppendEntries to peer_idx (raft.tla
* AppendEntries, 204). Computes prev_log_index = next_index[peer]-1 and
* prev_log_term via on_get_entry_term, sends at most one entry (n_entries 0/1),
* via on_send_append_entries. Leader-only (R3.5); leader_commit is opaque.
* Returns RAFT_OK.
*/
int raft_send_append_entries(raft_state_t *s, int peer_idx,
uint64_t leader_commit);
/*
* raft_on_append_entries_request: handle an incoming AppendEntries / heartbeat
* (raft.tla HandleAppendEntriesRequest 327 + UpdateTerm 406). Step order is
* fixed: UpdateTerm first (G5, every RPC incl. heartbeat) → logOk (G2) →
* reject / candidate-step-down / accept (already-have / conflict-truncate (G3) /
* append). entries_len is 0 (heartbeat) or 1. Fills *out. leader_commit is
* accepted but NOT acted on here (raft-commit-safety). Returns RAFT_OK.
*/
int raft_on_append_entries_request(raft_state_t *s, int leader_idx,
uint64_t term, uint64_t prev_log_index,
uint64_t prev_log_term, uint64_t leader_commit,
const void *entry, size_t entry_len,
uint64_t entry_term, int entries_len,
raft_append_result_t *out);
/*
* raft_on_append_entries_reply: process a follower's AppendEntries response
* (raft.tla HandleAppendEntriesResponse 393). Applied only when
* reply_term == s->term (stale-term drop, 415). success: next_index[peer] =
* match_index+1, match_index[peer] = match_index. failure: next_index[peer] =
* Max(next_index[peer]-1, 1). Does NOT advance commit. Returns RAFT_OK.
*/
int raft_on_append_entries_reply(raft_state_t *s, int peer_idx, int success,
uint64_t match_index, uint64_t reply_term);
/* Fills snap with current state. Caller must hold lock. */
void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap);