chore(ha-prevote): resolve log-reason nit; track elect_ctx leak as follow-up spec

- Mark the PreVote denial log-reason nit RESOLVED in ha-prevote tasks notes (47f7338).
- File ha-elect-ctx-lifecycle spec scaffold (spec.json + brief.md) capturing the
  pre-existing, bounded, UAF-sensitive election-carrier leak and the refcount/
  teardown-ordered-free approach across both election and PreVote paths. Deferred
  by decision — not folded into ha-prevote to avoid a risky lifetime refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-16 07:56:07 +00:00
parent 47f733891b
commit 5503c7a176
5 changed files with 100 additions and 2 deletions

View file

@ -106,6 +106,7 @@
- (2026-06-11, ha-convergence-recovery spec) `repl_complete_catchup` (repl.c:521) has NO production caller — `in_catch_up` is set by broadcast-overflow eviction (repl.c:240) and never cleared; re-inclusion trigger is wired by the ACK-driven catch-up continuation.
- (2026-06-11) Scheduler-bound job egress has TWO seams, not one: full push `xcesched_push_jobs` (sched.c:169, on scheduler join) AND incremental JOB_NEW push `sched_job_new_fun`→`xcesched_push_joblist` (sched.c:236-248). Status reads (apiserver.c:83, opget.c:173) share `collect_jobs` (dataex.c:294) and must stay unfiltered per umbrella deviation.
- (2026-06-11) No saved-argv/execv machinery exists anywhere in src/controller or src/lib — demotion self-restart is net-new; `job_t` is a thin wrapper `{ xce_job_t job; }` at src/include/xce/types.h:483-485 (controller-local fields go on the wrapper, wire type untouched).
- (2026-06-16, ha-prevote cleanup) The HA election carrier `ha_elect_ctx_t` (`ha->elect_ctx`) is deliberately leaked one-per-round on BOTH the real-election and PreVote paths — vote endpoints hold a bare `vctx->ctx` pointer freed async by endpoint_fini, so freeing the carrier risks UAF; there is NO real `ha_election_cleanup()`. Pre-existing + bounded (rare failover events). Deferred by user decision to follow-up spec `.kiro/specs/ha-elect-ctx-lifecycle/` (refcount-the-carrier or teardown-ordered free, both paths). Do NOT attempt a drive-by free — UAF on failover is worse than the bounded leak. The ha-prevote 3.1 log-reason nit is RESOLVED (commit 47f7338): `ha_heartbeat_is_fresh` un-static'd + declared in ha.h; cluster.c denial-reason now mirrors `raft_cb_vote_eligible` order exactly.
- (2026-06-16, ha-prevote impl) The controller `"controller test"` 300s TIMEOUT under `meson test` is a WALL-CLOCK WRAPPER ARTIFACT, not a hang/failure: running the binary directly (`ASAN_OPTIONS=detect_leaks=0 ./xce.build/controller/controller_unit_test`) completes all 564 tests incl. the slow `test_commit_barrier_reply` (61 tests, real 500ms commit barriers) with 0 failures. For a true controller pass/fail, run the binary directly (no cap) or filter with `-t <suite>`; do NOT treat the meson timeout as a regression. ha-prevote feature COMPLETE: 11 tasks, raft suite 123/0, controller 564/0, real-vote path byte-for-byte unchanged (0 raft.c deletions), commits 0d66026..e6b234a.
- (2026-06-15, ha-prevote design review) PRODUCTION vote-reply tally path = `raft_on_vote_reply` in the raft library, NOT `ha_elect_ctx_t`. `vote_reply_dispatch` (ha.c ~2630-2650) calls `raft_on_vote_reply` directly in production; the `ha_elect_ctx_t` / `handle_request_vote_reply` / `ha_elect_evaluate_and_signal` path runs ONLY behind `#ifdef XCE_TESTING` via `xce_vote_reply_hook` (plus the startup/first-boot `ha_elect_ctx_t.done` semaphore). The raft library owns quorum tracking and fires `on_become_leader`/`on_become_follower`. Do-Not-Repeat: when mirroring the election reply path, mirror `raft_on_vote_reply` (raft lib), not `ha_elect_ctx_t`. (An earlier exploration mislabeled ha_elect_ctx_t as the operative tally; caught in design validation.)
- (2026-06-15, user decision, ha-prevote RN-2) Leader-stickiness window for PreVote = reuse `ha_heartbeat_is_fresh()` / `heartbeat_timeout_ms` (10s), gated on role==CTRL_STANDBY. Rationale: the stickiness window MUST equal XCE's failure-detection window or you get either a disruption gap (window too short) or a liveness stall (window too long). XCE splits textbook Raft's overloaded "election timeout" — `HEARTBEAT_TIMEOUT_MS` (10s) is the leader-failure detector; `ELECTION_TIMEOUT_MS` (5s) is only the candidate vote-collection deadline. Raft's "minimum election timeout" disruption rule maps to HEARTBEAT_TIMEOUT_MS, NOT ELECTION_TIMEOUT_MS. Reusing it keeps stickiness == failure-detect == existing real-vote gate (`raft_cb_vote_eligible`), one predicate; role-gate handles RDHUP/connection-loss for free. A shorter window buys no failover speedup (nobody campaigns before failure-detect anyway; real crashes RDHUP immediately) and only opens a disruption gap on flaky links. Rejected a separate `prevote_stickiness_ms` constant (lets the two timeouts drift). Full comparison table in .kiro/specs/ha-prevote/research.md §6.

View file

@ -1762,3 +1762,16 @@ Implemented sub-spec 3/4 of ha-consistency-invariants autonomously (per-task imp
| 02:30 | kiro-impl ha-prevote: ALL 11 tasks done (2.1-6.1) | raft+ha+cluster+tests | every task review-APPROVED + committed; raft suite 123/0, controller 563/0 | ~60k |
| 02:50 | kiro-validate-impl ha-prevote: GO | full suites | raft 123/0, controller 564/0 (direct binary, no real fail), smoke OK, 27/27 reqs, boundary clean | ~5k |
| 00:38 | Session end: 9 writes across 4 files (tasks.md, test_ha_prevote.c, test_raft.c, research.md) | 14 reads | ~53449 tok |
| 00:46 | Edited src/controller/ha.c | inline fix | ~30 |
| 00:47 | Edited src/controller/ha.c | modified ha_heartbeat_is_fresh() | ~12 |
| 00:47 | Edited src/controller/ha.h | 2→7 lines | ~80 |
| 00:47 | Edited src/controller/cluster.c | heartbeat() → ha_heartbeat_is_fresh() | ~419 |
| 00:49 | Session end: 13 writes across 7 files (tasks.md, test_ha_prevote.c, test_raft.c, research.md, ha.c) | 16 reads | ~54029 tok |
| 00:54 | Edited .claude/settings.json | removed 13 lines | ~11 |
| 00:55 | Created .kiro/specs/ha-elect-ctx-lifecycle/spec.json | — | ~130 |
| 00:55 | Edited .claude/settings.json | removed 13 lines | ~10 |
| 00:55 | Edited .claude/settings.local.json | 5→3 lines | ~20 |
| 00:55 | Created .kiro/specs/ha-elect-ctx-lifecycle/brief.md | — | ~826 |
| 00:55 | Edited .kiro/specs/ha-prevote/tasks.md | inline fix | ~60 |
| 00:55 | Edited .kiro/specs/ha-prevote/tasks.md | inline fix | ~68 |
| 03:12 | ha-prevote cleanup: issue-1 log-reason fixed (47f7338); issue-2 leak deferred | .kiro/specs/ha-elect-ctx-lifecycle/, tasks.md, cerebrum.md | follow-up spec scaffolded + tracked | ~3k |