myxce/.kiro/specs/raft-sim-conformance/tasks.md
Rongsong Shen 20e59d0b42 checkpoint: bug-851 root cause + raft TLA+ conformance spec family
Investigation & fixes (failover committed-job loss):
- root-cause bug-851 on an owned 3-node cluster: chaotic failover causes
  silent committed-prefix divergence (WAL hdr.term always 0; two leaders
  per term; no (term,lsn) reconcile). Evidence in repro-851/ + INVESTIGATION.
- live-validated fixes 848/849/850 (recovery-on-promotion, advertise real
  applied_lsn, persist-layer contiguity guard); add waldump WAL inspector.

Specs (strict raft.tla conformance — library owns the log):
- TLA-gap-analysis.md: impl vs raft/raft.tla, gaps G1-G6.
- raft-log-replication, raft-commit-safety, raft-sim-conformance: extend
  src/lib/raft to own log/commit/verification via callbacks (specs generated,
  cross-spec review passed).
- ha-term-safety: controller-side adapter + config marker (R1-R7); design done.
- config-upgrade-snapshot: R8 descoped here (deps entity-bearing-snapshots, bug-857).
- roadmap + buglog (851/854/857) + .wolf knowledge updated.
2026-06-21 14:02:15 +00:00

210 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementation Plan
> **TDD-mandatory** (`testing.md`): every checker, scheduler action, and scenario lands as
> a RED test in `src/lib/testsuite/test_raft.c` (with the `raft_sim.c` backing) that fails
> before the harness logic exists and passes after. Build/run library tests via
> `/xce-build lib`. All harness code is `XCE_TESTING`-gated.
>
> **Spec boundary** (applies to ALL tasks): this spec owns `src/lib/raft/raft_sim.c`, the
> `XCE_TESTING` sim section of `src/lib/raft/raft.h`, and the conformance tests in
> `src/lib/testsuite/test_raft.c` ONLY. It does **NOT** modify the protocol
> implementation: no task changes `raft.c` protocol logic or any protocol declaration in
> `raft.h`, and no task edits a controller file. The harness drives the published library
> entry points and consumes the `raft-log-replication` + `raft-commit-safety` callback
> contract **verbatim**.
>
> **Depends** (whole spec): `raft-log-replication` (log model + AppendEntries entry points
> + the four callbacks), `raft-commit-safety` (`raft_advance_commit`, `on_commit_advanced`,
> `(lastLogTerm, lastLogIndex)` freshness). If those signatures change, re-validate per the
> design's Revalidation Triggers.
## 1. Harness types and in-memory adapter backing
- [ ] 1.1 Extend the `XCE_TESTING` sim section of `raft.h` with the harness data types
- Add `raft_sim_msg_kind_t`, `raft_sim_msg_t` (vote/AE req+rsp union fields), `raft_sim_log_entry_t {term, value}`, `raft_sim_action_t {kind, actor, msg_index, term, index}`, and the `RAFT_SIM_MAX_LOG/MSGS/STEPS` bounds
- Extend `raft_sim_node_t` with `log[]`, `log_len`, `commit_index`, `applied[]`, and a persisted-`(term, votedFor)` shadow for Restart; extend `raft_sim_cluster_t` with `bag[]`+`bag_count`, PRNG state, `seed`, and `trace[]`+`trace_len`
- Observable completion: `raft.h` compiles under `XCE_TESTING`; no protocol declaration changed; `sizeof(raft_sim_cluster_t)` grows by the new fixed arrays
- _Requirements: 1.1, 1.3, 2.5_
- _Boundary: raft.h (XCE_TESTING section only)_
- [ ] 1.2 Implement the seeded PRNG and the in-memory adapter callbacks
- Add a small inlined deterministic generator (e.g. SplitMix64) seeded by `cluster.seed`; add `on_persist_entry` → `node.log[]`, `on_get_entry_term` → term|0, `on_truncate_from` → shrink `log_len`, `on_commit_advanced` → set `commit_index` + apply into `applied[]`; route `on_send_vote_request`/`on_send_append_entries` to a bag push
- Add `raft_sim_conformance_init(c, n, timeout, seed)` wiring all six callbacks + the bag
- Observable completion: a test initializes a cluster, drives one `raft_log_append` on a forced leader, and reads the entry back via the in-memory log and `on_get_entry_term`
- _Requirements: 1.1, 1.2, 2.1_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
## 2. Message bag (mirrors raft.tla `messages`)
- [ ] 2.1 RED+GREEN: bag push/deliver routes vote and AppendEntries RPCs through the library handlers
- Test: pushing a vote-req and an AE-req then delivering them invokes `raft_on_vote_request` / `raft_on_append_entries_request`, and the handler reply is pushed back as a `*_RSP` message
- Implement `raft_sim_bag_push` and `raft_sim_bag_deliver(i)` (route by `kind`, honor `node.partition`, push replies)
- Observable completion: delivered requests produce matching responses in the bag; partitioned endpoints are not delivered
- _Requirements: 2.2, 2.4_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.2_
- [ ] 2.2 RED+GREEN: Drop / Duplicate / Reorder mutate only the bag
- Test: `raft_sim_bag_drop`/`_duplicate` change bag multiplicity without mutating any node's term/role/log; reorder delivers in a PRNG-chosen order
- Implement the three bag operations (mirroring `raft.tla:443`/`448` `UNCHANGED` of non-message vars)
- Observable completion: node snapshots are identical before/after a drop or duplicate of an undelivered message
- _Requirements: 2.5_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 2.1_
## 3. The four invariant checkers (TDD against hand-built clusters)
- [ ] 3.1 RED+GREEN: Election Safety checker (P)
- Test: a cluster with two `LEADER` nodes at the same term fails; a single-leader-per-term cluster passes
- Implement `raft_sim_check_election_safety` over roles+terms read from `raft_snapshot`
- Observable completion: both directions assert correctly
- _Requirements: 3.1, 3.6_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
- [ ] 3.2 RED+GREEN: Log Matching checker (P)
- Test: two logs sharing `(idx, term)` but differing earlier fail; identical-prefix logs pass
- Implement `raft_sim_check_log_matching` (pairwise; shared `(idx, term)` ⇒ identical `1..idx`)
- Observable completion: both directions assert correctly
- _Requirements: 3.2, 3.6_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
- [ ] 3.3 RED+GREEN: Leader Completeness checker (P)
- Test: a committed `(index, term)` absent from a higher-term leader's log fails; present passes
- Implement `raft_sim_check_leader_completeness` using `commit_index` + per-node logs, tracking the highest-term leader to date
- Observable completion: both directions assert correctly
- _Requirements: 3.3, 3.6_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
- [ ] 3.4 RED+GREEN: State Machine Safety checker (P)
- Test: divergent `applied[k]` across two nodes fails; identical applied prefixes pass
- Implement `raft_sim_check_state_machine_safety` over `applied[]`
- Observable completion: both directions assert correctly
- _Requirements: 3.4, 3.6_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
- [ ] 3.5 RED+GREEN: `raft_sim_check_all` returns the first failing invariant and halts
- Test: a cluster violating two invariants reports the first; a clean cluster returns OK
- Implement `raft_sim_check_all` composing 3.1–3.4 in a fixed order
- Observable completion: first-failure semantics verified
- _Requirements: 3.5_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 3.1, 3.2, 3.3, 3.4_
## 4. Action scheduler and trace recorder
- [ ] 4.1 RED+GREEN: trace recorder records every action before it is applied
- Test: after a fixed step sequence, `cluster.trace[]` holds the actions in order; `raft_sim_dump_trace` prints seed, n, and the ordered actions
- Implement `raft_sim_action_t` recording (R1.4) + `raft_sim_dump_trace` (R5.1, R5.2); quiet on success unless `RAFT_SIM_VERBOSE` (R5.4)
- Observable completion: a recorded trace round-trips to a readable dump
- _Requirements: 1.4, 5.1, 5.2, 5.4_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
- [ ] 4.2 RED+GREEN: `raft_sim_step` drives one Next-equivalent action through the library and checks invariants
- Test: stepping reaches each enabled action kind over a run; every step calls `raft_sim_check_all`; a forced violation halts with a trace dump and a `<0` return
- Implement the seeded scheduler dispatch (Restart, Timeout, RequestVote/BecomeLeader via vote delivery, ClientRequest, AppendEntries via `raft_send_append_entries`, AdvanceCommitIndex via `raft_advance_commit`, Receive via bag deliver, Duplicate/Drop) selecting only **enabled** actions, driving **only** library entry points
- Observable completion: a multi-step run touches log append, AE replication, commit advance, and message faults; invariants checked each step
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.6, 3.5_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 2.2, 3.5, 4.1_
- [ ] 4.3 RED+GREEN: Restart preserves persisted `term`/`votedFor`/log
- Test: a Restart action re-inits a node while keeping `node.log[]`, recovered `log_len`, and last-persisted `(term, votedFor)`; volatile state resets per `raft.tla` `Restart`
- Implement the Restart branch (feed `initial_log_len` + restored `(term, votedFor)` into `raft_init`)
- Observable completion: a restarted node retains its log and term; no invariant violation results from the restart alone
- _Requirements: 2.1_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
- [ ] 4.4 RED+GREEN: deterministic replay from `(n, seed, max_steps)`
- Test: two runs with the same `(n, seed, max_steps)` reach identical final per-node + bag state and an identical recorded trace
- Implement `raft_sim_run(c, max_steps)` and confirm no wall-clock/address/uninitialized input leaks into choices
- Observable completion: byte-identical replay verified across two runs
- _Requirements: 1.5, 1.6, 5.3_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
## 5. Targeted adversarial scenarios
- [ ] 5.1 Partition/heal scenario (P)
- Implement `raft_sim_scenario_partition_heal`: isolate a minority during appends, heal, drain the bag; assert all four invariants throughout and after convergence
- Observable completion: scenario passes with zero violations on the correct build
- _Requirements: 4.1_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
- [ ] 5.2 Restart-churn scenario (P)
- Implement `raft_sim_scenario_restart_churn`: restart nodes mid-replication preserving persisted state; assert re-convergence with no violation
- Observable completion: scenario passes; restarted nodes converge to the leader's prefix
- _Requirements: 4.2_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.3_
- [ ] 5.3 Drop/duplicate/reorder scenario (P)
- Implement `raft_sim_scenario_drop_dup_reorder`: perturb bag delivery order + multiplicity; assert idempotent convergence with all four invariants held
- Observable completion: scenario passes; duplicated AE is a no-op (already-have branch)
- _Requirements: 4.3_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
- [ ] 5.4 Figure-8 scenario
- Implement `raft_sim_scenario_figure8`: a previous-term entry on a quorum, a leader change; assert the entry is NOT reported committed until a current-term entry commits (Leader Completeness / State Machine Safety preserved)
- Observable completion: the previous-term entry's `commit_index` does not cover it until a current-term commit
- _Requirements: 4.4_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
- [ ] 5.5 Stale-rejoin scenario (bug-851 G5)
- Implement `raft_sim_scenario_stale_rejoin`: a term-lagging node rejoins under a higher-term leader; assert it adopts the higher term from the AE/heartbeat path and does not re-elect at a reused term (Election Safety preserved)
- Observable completion: the rejoining node's term advances via AE; no duplicate-term leader appears
- _Requirements: 4.5_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
## 6. CI batch and randomized seed sweep
- [ ] 6.1 RED+GREEN: randomized M-seed sweep with zero violations
- Test: run `raft_sim_run` for a fixed `max_steps` across M seeds (M ≥ configured minimum) plus all targeted scenarios; fail if any seed/scenario violates any invariant
- Implement the sweep harness in `test_raft.c`; bound `max_steps`, M, and array sizes so the target fits the library test-suite budget
- Observable completion: the full batch runs clean and within budget
- _Requirements: 6.1, 6.2, 6.4_
- _Boundary: test_raft.c_
- _Depends: 5.1, 5.2, 5.3, 5.4, 5.5_
- [ ] 6.2 Verify the conformance batch is ASAN/UBSAN-clean
- Run the conformance batch under the library test sanitizer build; fix any leak/overflow/UB in the harness (fixed-size arrays, no allocation)
- Observable completion: `/xce-build lib` sanitizer run reports no memory errors, leaks, or UB for the conformance target
- _Requirements: 6.3_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 6.1_
## 7. Deliberately-broken-build negative test (the oracle's teeth)
- [ ] 7.1 RED+GREEN: compile-time fault switch emulates the bug-851 defects in the adapter
- Implement `RAFT_SIM_BREAK_TERM_STAMP` (adapter `on_persist_entry` writes term 0) and `RAFT_SIM_BREAK_TRUNCATE` (adapter `on_truncate_from` no-op), confined to the test/harness build configuration; default off
- Observable completion: with a macro off, scenarios are clean; the macro is not set in the default CI run and does not touch `raft.c`
- _Requirements: 7.1, 7.3_
- _Boundary: raft_sim.c_
- _Depends: 5.4, 5.5_
- [ ] 7.2 RED+GREEN: negative test asserts both directions
- Test: under the broken configuration the Figure-8 / divergent-suffix scenario triggers a Log Matching / Leader Completeness / State Machine Safety violation (with trace dump); under the correct configuration the same scenario passes — both asserted in one test
- Observable completion: the broken build is caught and the correct build passes, proving checker sensitivity
- _Requirements: 7.1, 7.2, 7.4_
- _Boundary: test_raft.c_
- _Depends: 7.1_
## 8. Optional TLC-consumable trace export
- [ ]* 8.1 TLC-consumable trace export (deferred; off by default)
- Implement `raft_sim_export_trace` gated by `RAFT_SIM_EXPORT`: per step, emit the action + tracked `raft.tla` variables (`messages` summary, per-node `currentTerm`/`state`/`log`/`commitIndex`) named per `raft/raft.tla`, in a stable line-oriented format; off by default
- Observable completion: with `RAFT_SIM_EXPORT` set, a run emits a stable, spec-named record stream; default runs emit nothing extra
- _Requirements: 8.1, 8.2, 8.3_
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 4.2_
```