fix(raft-ha-core-safety): (term,lsn,payload-crc)-aware persist guard (task 2.2)

Payload-aware repair: identical payload = idempotent no-op; non-empty
differing payload at the TIP = repair (truncate-to-lsn-1 + re-append,
committed_lsn snapshot/restored under lock); empty-over-stored = keep
(never degrade); BELOW-tip conflict = loud non-promotable fence
(recovery_unrecoverable), never truncate the tail or lower committed_lsn.
read_entry_meta_locked computes payload-crc identically to walcompare.
Turns 2.1 case (a) green; new below-tip regression test pins the
committed-boundary safety. Review round 1 caught + fixed a committed-data-
loss bug (tail-truncate on below-tip conflict). 8/8 target suite, sibling
persist/repl/catchup/durability suites green; persist-before-ACK intact.
This commit is contained in:
Rongsong Shen 2026-06-27 10:57:37 +00:00
parent 3b5eda1429
commit 056675c8b3
3 changed files with 335 additions and 14 deletions

View file

@ -46,7 +46,7 @@
- _Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.4_
- _Boundary: Persist Conflict Guard_
- [ ] 2.2 Make the persistence guard `(term, lsn, payload-crc)`-aware
- [x] 2.2 Make the persistence guard `(term, lsn, payload-crc)`-aware
- Extend the LSN-only idempotency check (persist.c:822): identical `(term, payload-crc)` at an LSN is an idempotent no-op; a differing payload-crc (or term) at an existing LSN is surfaced for repair, never a silent `return 0`; never accept a `PLEN=0` write as the durable form of a known-non-empty entry; preserve persist-before-ACK, the term index, and `WAL_LEGACY_TERM`
- Observable completion: the 2.1 cases pass; an empty-over-real write at an existing committed LSN no longer silently survives; identical re-writes stay idempotent
- _Requirements: 1.1, 1.2, 2.1, 2.2, 2.4, 3.1_
@ -139,3 +139,7 @@
- Observable completion: the soak divergence count is zero with closure justified by the live soak, OR a recorded new finding plus a follow-up task; `raft/raft.tla` invariants (Election Safety, Leader Completeness, State Machine Safety, Commit Ordering) reviewed against the final behavior
- _Requirements: 1.1, 1.4, 7.1, 8.1_
- _Depends: 7.2_
## Implementation Notes
- **Task 2.2 — payload repair must be TIP-ONLY (consensus-safety, caught in review round 1).** The `(term,lsn,payload-crc)`-aware guard repairs an empty-over-real entry by truncate-to-`lsn-1`+re-append, but this is safe ONLY when `lsn == current_lsn-1` (the conflict is at the tip with nothing committed above). For a BELOW-tip conflict (`lsn < current_lsn-1` — the actual live LSN-9 case with 10/11/12 above), truncating the tail would drop committed entries and roll `committed_lsn` backward (State Machine Safety violation). Below-tip is handled as a loud non-promotable fence (reuses `recovery_unrecoverable` sticky flag), never mutating any boundary; the real reconvergence comes from the 2.4 streamer fix + re-stream. The tip path snapshots/restores `committed_lsn` under `p->lock` so the boundary is never observably lowered. Any future in-WAL repair must preserve: never drop a committed entry above the conflict, never lower `committed_lsn`.