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.
24 KiB
Roadmap
Overview
XCE is in the persistence + HA transformation (branch persistent_ha). Governing model: Raft consensus + CP (see product.md key decisions). Active spec work is tracked per-spec in .kiro/specs/; this file is the single consolidated future-work document — the forward-looking queue, long-deferred items, and hardening backlog. It absorbs the former src/docs/FUTURE_WORK.md (2026-05-12, config-replication-design review), which now redirects here.
In Flight
- ha-consistency-invariants — UMBRELLA CONTRACT (requirements + design + research approved/complete). Implementation decomposed 2026-06-11 into the four sub-specs below; the umbrella generates NO tasks itself. Each sub-spec brief references the umbrella docs as authoritative.
- ha-wal-replication — implemented except task 5.4 (live 3-node integration test; needs
/xce-deploy-hacluster); sub-spec 4's E2E extends it - libxce-failover — CLIENT-SIDE transparent controller failover (requirements generated 2026-06-16). Wires all libxce production request paths through the (already-built-but-orphaned)
xce_request_with_retry()primitive + adds multi-master rotation; classifies on the real controller status taxonomyE_XCE_REDIRECT/E_XCE_NOMASTER/E_XCE_NOT_READY/E_XCE_STANDBY(errnox.h:22-25); bounded retry → distinct cluster-not-ready (no more opaque EINVAL); includes one small controller change so redirect replies carry the primary host/port (schema fields already exist,controller.capnp:568-569, just unpopulated injoin.c). Verified E2E viaxce_test(the original reproduction). Closes the integration gap left byha-config-bootstrapReq 7 (tasks 10/10.1/10.2 shipped only the standalone primitive). See bug-608. Dependencies: ha-config-bootstrap (primitive + error codes), ha-commit-barrier-rollout (write idempotency makes write-retry safe).
Raft TLA+ Conformance — raft-library enhancement series (NEW 2026-06-21)
Goal: evolve the election-only src/lib/raft/raft.{c,h} into a STRICT implementation of raft/raft.tla (Ongaro canonical) covering the log + commit half, keeping the pure callback-driven design so it stays independently verifiable via the deterministic simulator (raft_sim.c) against the TLA+ invariants (Election Safety, Log Matching, Leader Completeness, State Machine Safety). Motivation: bug-851 — the log protocol is currently hand-rolled across persist.c/repl.c/ha.c/ha_convergence.c and violates the TLA+ log invariants. Gap analysis (six gaps G1–G6): .kiro/specs/ha-term-safety/TLA-gap-analysis.md. Implement in dependency order; each spec lands with simulator coverage. The library core stays pure/synchronous/deterministic — all I/O (async vtask, fan-out replication, the sync commit barrier, snapshot transport) lives in controller-side callback adapters. Preserve as policy (do NOT canonicalize away): PreVote, priority leadership, synchronous commit-barrier + persist-before-ACK durability, "controller never self-terminates".
- raft-log-replication — (specs generated + auto-approved 2026-06-21: 8 reqs, 24 sub-tasks, cross-spec review PASSED. Ready for /kiro-impl.) Extend the library to OWN the replicated log. TLA+ actions:
ClientRequest,AppendEntries,HandleAppendEntriesRequest,UpdateTerm-on-AppendEntries. Per-entry term in the library log model (closes G1);AppendEntrieslogOk=prevLogTermmatch (G2); conflict branch deletes the divergent(term,lsn)suffix (G3); ANY higher-term RPC incl. AppendEntries/heartbeat advancescurrentTermand steps down (G5). New pure callbacks:on_persist_entry,on_get_entry_term(lsn),on_send_append_entries(peer, prevLogIdx, prevLogTerm, entries, commitIdx),on_truncate_from(lsn); controller adapters implement them over the existing WAL/repl transport. Simulator: drive AppendEntries across partitions/restarts, assert Log Matching. Dependencies: raft-library (election core). Closes bug-851 G1/G2/G3/G5. - raft-commit-safety — (specs generated + auto-approved 2026-06-21: 6 reqs, 18 sub-tasks, cross-spec review PASSED.)
AdvanceCommitIndexwith the Figure-8 current-term rule (commitMax(agreeIndexes)only iflog[Max].term == currentTerm) (G4);RequestVote/PreVote freshness becomes(lastLogTerm, lastLogIndex)instead of LSN-only (G6). New callbackon_commit_advanced(lsn)feeds the existing synchronous commit barrier / durability (persist-before-ACK preserved as policy). Simulator: assert Leader Completeness. Dependencies: raft-log-replication. Closes bug-851 G4/G6. - raft-sim-conformance — (specs generated + auto-approved 2026-06-21: 8 reqs, 23 sub-tasks, cross-spec review PASSED.) Extend
raft_sim.cinto a conformance/verification harness driving the library throughraft.tla-action-equivalent transitions (randomized + targeted) and asserting the four Raft safety invariants; optionally trace-check against TLC output ofraft.tla. The "use the verified spec to help" payoff: a regression oracle for the consensus core. Dependencies: raft-log-replication, raft-commit-safety.
ha-term-safety (requirements + design generated 2026-06-21;
.kiro/specs/ha-term-safety/) is the XCE-side INTEGRATION + REGRESSION umbrella for this family: it wires the controller WAL / commit-barrier into the new raft callbacks (thin adapter — protocol delegated to the library), owns the config-change marker (R7 — config changes as(term,lsn)log markers; bytes via the existing out-of-band config-push transport, hash-verified against the marker), and holds the live bug-851 chaotic-failover regression. Its protocol invariants R1–R6 are realized by the library specs above (not re-implemented in the controller). Scope: R1–R7 only. The former R8 (config-upgrade full-snapshot consistency, bug-857) was descoped 2026-06-21 to the independentconfig-upgrade-snapshotspec (below), because it requires entity-bearing snapshots that do not exist yet. Next:/kiro-spec-tasks ha-term-safety.
Specs (dependency order)
[x]= spec generated (requirements/design/tasks approved, ready for implementation). Implementation tracked via each spec's tasks.md; implement in roadmap order (cross-spec review 2026-06-11: four specs touch ha_dispatch_gate / raft_cb_become_follower in disjoint-but-adjacent regions).
- ha-commit-engine — commit.c per-LSN majority engine + quorum accounting fixes, wired behind job submit. Dependencies: none. (specs done 2026-06-11; IMPLEMENTED 2026-06-12: 12/12 tasks, validate-impl GO, 467 tests green; deferred follow-ups in its tasks.md §Implementation Notes)
- ha-commit-barrier-rollout — classification table, commit-first-then-reply sweep, RETAIN facts, config barrier, idempotency guards. Dependencies: ha-commit-engine. (specs done 2026-06-11; IMPLEMENTED 2026-06-13: 16/16 tasks, validate gate GO, 508 controller tests green under ASAN. Fixed bug-447 (key_find found-rc leak — queue/host control was silently unpersisted+unreplicated in production) and bug-396 (worker fact loss on quorum-loss gate rejection); task 5.2 needed one remediation round (wired the missing production config-ACK ingestion — would have bricked multi-member config upgrades — + kept the barrier off the iomgr thread). Logged not-fixed: bug-448 (job_ctrl ref leak), bug-451 (RUSAGE va_list OOB read). Barrier-before-state-removal precondition for ha-single-leader-role satisfied)
- ha-single-leader-role — promotion no-op + own-term gate, transitional-state (the former fourth role) removal. Dependencies: ha-commit-engine, ha-commit-barrier-rollout (HARD: barrier before state removal). (specs done 2026-06-11: 2 reqs / 7 components / 11 sub-tasks)
- ha-convergence-recovery — demotion truncate+self-restart, rejoin reconciliation, egress watermark filter, ACK-driven catch-up, quorum logging. Dependencies: ha-commit-engine (E2E additionally needs ha-single-leader-role). (specs done 2026-06-11: 9 reqs / 7 components / 13 sub-tasks)
- ha-commit-durability — closes bug-661 (acknowledgment did not imply durability; committed jobs lost across failover). Makes acknowledged ⇒ durable on a majority: the primary counts its own copy toward the commit majority only at its durable LSN (
majority_candidate_lockedcaps self atpersist_durable_lsn, not the appended tip);fsync_workerrepurposed timer→demand-driven group-commit flusher (onefdatasyncover pending appends, advancedurable_lsn, wake commit-eval) — absorbs the former "WAL group-commit batching" backlog item (see below); recovery integrity verdict (clean vs corruption-truncated WAL) + durable-backed freshness floor (candidate advertises barepersist_durable_floor, voter floor =max(durable_floor, learned_committed)) + pre-promotion eligibility fence. Establishes the controllers-never-self-terminate invariant: a recovery-truncated/behind member that cannot catch up is a LOUD PERMANENT FENCE (non-promotable, stays alive, keeps retrying — neverexit/abort), self-heals on peer return (seeha-async-io.mdprocess-lifecycle invariant +src/controller/CLAUDE.md). Dependencies: ha-commit-engine (consumes commit-position semantics), ha-wal-replication (standby ACK/catch-up), ha-commit-barrier-rollout (reply gate, unchanged). (specs done 2026-06-18; IMPLEMENTED 2026-06-18: 17/17 tasks (1.1–5.3), validate-impl GO, controller suite 0 failures under ASAN; deterministic ack-before-fdatasync E2E RED→GREEN + rolling-SIGKILL soak zero acked-job loss. Verified by tests + Protocol Verification Checklist, NOT by raft.tla (which assumes a durable log). Fixed bug-760 (adjacenttest_fact_hook_appendraced the async flusher — test made durable-deterministic viapersist_flush). raft.tla durability-model extension deferred unless explicitly opted in.)
Future Specs / Backlog (dependency order)
- config-upgrade-snapshot — descoped from
ha-term-safetyR8 on 2026-06-21 (requirements generated;.kiro/specs/config-upgrade-snapshot/). Closes bug-857: a configuration upgrade can leave members with DIVERGENT materialized state because today it does config-push + restart + per-node WAL replay under the new config (config_repl.cdo_upgrade_config_with_bytes~1001-1163), with no snapshot pinning an identical post-upgrade state (replay is config-dependent —lost_and_foundre-homes orphaned entities). Approach: an upgrade materializes a full entity-bearing state snapshot at the config-change marker's(term,lsn)boundary, embeds the new config, and installs it on every member via thesnapshot_transfer_stransport — no per-node re-derivation; supersedesha-term-safetyR7's interim config-push byte delivery for upgrades. HARD dependency:entity-bearing-snapshots(today snapshots are a 16-byte LSN sentinel; the entity payload does not exist) ANDha-term-safety(the config-change marker it anchors on). Verify by reproducing bug-857 (upgrade during chaotic multi-kill, diff materialized state). Promote to design onceentity-bearing-snapshotslands. - component-failover-adoption — recorded 2026-06-16; depends on libxce-failover (HARD). Make the C daemons and the Go apiserver actually exercise libxce's transparent failover. The mechanism reaches them for free (worker/scheduler/controller link
libxce_dep; apiserver uses it via cgo,src/frontend/apiserver/backend/clib.go), but two per-component integration items remain: (1) each component must hand libxce the full master list instead of a single endpoint (today even the CLI uses onlyconf->masters[0],client.c:122); (2) bounded retry makes the already-blockingxce_requestblock longer (seconds of backoff) — for the async C daemons (workercontroller_connector.c, schedulerhandler.c) this must be verified liveness-safe, i.e. it must not stall a thread that also drives heartbeats (the "never block the iomgr thread" rule). Scope: per-component master-list config + retry policy + liveness-safe blocking + per-component E2E failover for scheduler, worker, apiserver. (xce_testis NOT here — it is the in-spec verification client of libxce-failover.) Promote via/kiro-spec-init component-failover-adoptionafter libxce-failover lands. - worker-rejoin-resync — planned long ago, no effort yet (recorded 2026-06-10). When a worker rejoins after disconnect or controller failover, it shall re-report its running/finished job states so the controller recovers facts whose notifications were lost. Evidence of the gap (ha-consistency-invariants research deep-dive §A):
xce_join_reqcarries onlyhostattrs_t(src/lib/request.c:1154-1181) — no job state; worker notify is fire-and-forget with zero redelivery (bug-396), so any job start/finish/fail sent during a disconnect or failover window is permanently lost today. Scope sketch: extend the join/hello exchange (or a post-join message) with a running+recent job-state report; controller reconciles against its job table through the commit barrier (entries go through majority commit like any state change); requires themark_job_*idempotency guards (shared need with ha-consistency-invariants' pending-facts queue). Complements — does not replace — the primary-side pending-facts queue: the queue covers quorum-loss windows while connected; resync covers disconnect/failover windows. Elevated 2026-06-11: designated as the official backstop in ha-consistency-invariants Req 2.8 / boundary context — facts the controller received are retained-and-retried until committed; facts that never arrived (or died with the controller) are recovered only by this resync. Dependencies: ha-consistency-invariants (commit barrier + idempotency guards). Extends: worker-join-refactor (implemented — covers attribute/endpoint update on rejoin only). - admin-config-tool — drives
OP_upgradeConfig(prerequisite for config-upgrade Phase 2). Encode ClusterConfig YAML/JSON → Cap'n Proto binary, SHA-256 over the bytes, connect to primary (SSL, same CA), sendOP_upgradeConfig, display reply (version tag, acked count, errors), pollOP_probeafter restart. Suggested CLI:xce-config push --controller <host:port> --config cluster.yaml. MUST validate that user configs don't use the reserved name"lost_and_found"for any queue/host (defence-in-depth: controller enforces at runtime viaload_primary_config()FATAL, tool prevents at authoring time; migration: pre-existing clusters must rename before upgrade — see config-replication-design.md §6.5/§7.1). (From FUTURE_WORK.md §1 + §8, 2026-05-12.) - lightweight rejoin re-attach (failover optimization) — recorded 2026-06-16; deferred (YAGNI). Today, on controller failover a reconnecting daemon does a full re-join and apiserver/scheduler receive full state (e.g. the whole job list), not a delta; for a large cluster this is sizable but rare. Decision (libxce-failover design review): accept full-state rejoin — it is simple, correct, and still cheaper than a full-history replace with no snapshot (cf. IBM LSF). Future optimization: a session-token "re-attach" hello that lets the new primary skip re-pushing already-replicated state, or a delta-only state transfer keyed on the consumer's last-known committed LSN. Delta reconciliation is non-trivial (the primary must know what each consumer already holds). Promote only if failover-rejoin latency/bandwidth is measured as a problem at scale. Dependencies: libxce-failover (rejoin hook), component-failover-adoption (daemon rejoin content), worker-rejoin-resync (worker job state).
- job-array-single-wal-record — recorded 2026-06-16. Enhance controller & scheduler so a job-array submission persists, replicates, and pushes to the scheduler as a single WAL record rather than one record per array element. Today a job array fans out to N per-job state changes, so a 10k-element array means 10k WAL appends + 10k replicated commits + 10k scheduler pushes — amplifying commit-engine load, replication bandwidth, and failover replay time linearly in array size. Scope sketch: (a) controller writes one WAL entry describing the array (base spec + index range/count + per-index overrides) committed atomically through the commit barrier; (b) replication carries the single record so standbys reconstruct the whole array from one committed LSN; (c) scheduler receives the array as one push and expands locally. Must preserve per-element identity/state addressing after expansion (status, cancel, completion still operate per task id). Dependencies: ha-commit-engine + ha-commit-barrier-rollout (single atomic commit), ha-consistency-invariants (replicated scheduling state). Spec advised before implementation; promote via
/kiro-spec-init "job-array-single-wal-record"when ready. WAL group-commit batching— recorded 2026-06-17 as deferred; ABSORBED INTO ha-commit-durability 2026-06-18 (design-review reversal). The/kiro-validate-designreview surfaced that the highest-volume WAL traffic isWAL_MODE_RETAINfacts (job start/finish/fail/rusage) — not array-collapsible and not covered byjob-array-single-wal-record— so a pure per-entry synchronous design would penalize the dominant write path. Decision (with user): do group-commit now — repurposefsync_workerfrom a timer into a demand-driven flusher (batch onefdatasyncacross pending appends, advancedurable_lsn, wake commit-eval) as part of ha-commit-durability rather than deferring. No longer a separate backlog item.- primary step-down-on-quorum-loss — optional CP follow-on noted in ha-consistency-invariants brief (writes already blocked on quorum loss; step-down is an availability/clarity optimization).
- read consistency / leader leases — Raft §8 read-only optimization; status queries may currently observe stale or provisional state (documented deviation in ha-consistency-invariants).
- entity-bearing snapshots ("Task 11" format) — snapshot files are currently a 16-byte LSN sentinel (persist.c:435-437); real entity serialization unblocks snapshot-based state transfer and faster recovery. Cheaper than it looks (verified 2026-06-11): the dual-zarray COW machinery it needs is already built and unit-tested but dormant —
xzarray_freeze/xzarray_thawin the vtable (zarray.c:695-757, O(1) freeze, thaw withshould_retainGC filter, test_zarray_cow.c) with zero production callers (kvmgr.c:297vfreeze = NULL /* wired in P0-2 */). Note: single-generation COW — the snapshot consumer owns freeze/thaw; do not add second concurrent consumers. Slot-level only (snapshots array structure, not object contents — serialize item state under per-object locks during the freeze window). Once landed, also strengthens ha-consistency-invariants §C eviction option (iii) and faster rebuild-on-demotion. Downstream (recorded 2026-06-21):config-upgrade-snapshotHARD-depends on this — it needs an entity-bearing snapshot to deliver an identical materialized post-upgrade state to every member (bug-857).
Hardening / Direct-Implementation Items
Smaller items that likely don't warrant a full spec; pick up opportunistically or bundle into adjacent spec work.
- OP_heartbeat peers cross-validation — include a hash of the local
--peersstring in heartbeat requests; log ERROR on mismatch between candidates. Detects misconfigured nodes at election time instead of silently risking dual-primary split-brain. (FUTURE_WORK.md §3.) - validate_host() real authorization — currently a stub returning success for all peers; MUST be implemented before
CONF_ENABLE_UPGRADE_CONFIG=1. Minimum: verify peer X.509 CN against a configured allowlist (--admin-cnflag oradmin_cn_listconfig), reject non-allowlistedOP_upgradeConfigwith clear error, WARN-log rejected attempts with CN+IP. Future: IP allowlist, role-based access, admin-specific CA. (FUTURE_WORK.md §6; security-critical.) - OP_replConfig access control hardening — currently serves the full config blob to any CA-signed SSL peer (trusted-LAN model). Consider: verify requester is a known
--peerscontroller, rate-limit per peer,minimum_rolecheck (controllers only). (FUTURE_WORK.md §7.) - lost-and-found drain command —
xcecli lost-and-found drain: reassign or cancel sidelined jobs, resetlost_and_found_job_count. Automates operator cleanup after config upgrade. (FUTURE_WORK.md §4.) - OP_upgradeConfig standby redirect verification — test that a standby returns the primary's address to an admin tool and the tool retries against the primary. Note: redirect semantics change under ha-consistency-invariants Req 1 (no transitional-state redirect); verify against the new behavior. (FUTURE_WORK.md §5.)
- steering sync —
ha-async-io.mdsynchronized to the three-state model + own-term gate (transitional-state row/role removed; heartbeat re-arm PRIMARY-only; PRIMARY no-quorum fact allowlist noted). Done as part of ha-single-leader-role task 6.1. - xce_test WAL/snapshot inspection tooling — extend the offline inspector family in
xce_test(sibling of the shippedwaldump,src/controller/testsuite/waldump.{c,h}+cmd_waldump). Two remaining commands: (a)snapdump <snap_dir|snap_file>— decode theXCESNAP\0header (8-byte LSN;persist.c:1266load_snapshot) and report whether the snapshot carries job/queue entities or is an LSN-only stub. This directly settles Open Fork #3 of the failover job-loss investigation (.kiro/specs/ha-commit-durability/INVESTIGATION-failover-job-loss.md): if snapshots are entity-less, any empty-rejoin/WAL-prune below committed is unrecoverable BY DESIGN. (b)walcompare <dirA> <dirB>— diff two nodes' WALs bylsn → crc/jobidto flag divergence/prefix-gaps between members instantly. Reuse thewaldump.creader/decoder; same zero-alloc capnp jobid decode +c_capnproto_dep/zlib_depwiring already added toxce_test_deps. Nonzero exit on entity-less snapshot / divergence so both double as E2E assertions. (recorded 2026-06-20;waldumpalready done.) - xce_test shell command support — add
!<command>prefix toxce_testtest scripts to execute arbitrary shell commands inline during test runs. Enables test scripts to drive environment setup/teardown (e.g., kill a node, write a file, run a helper) without calling out to a separate harness layer. Scope: parser change in the xce_test runner to detect!-prefixed lines, fork/exec the remainder as a shell command, fail the test on non-zero exit, capture stdout/stderr to the test log. - scheduler framework enhancement — strengthen the scheduler subsystem: (a) improve job scheduling policy extensibility (pluggable queue disciplines beyond FIFO), (b) add priority-aware dispatch, (c) improve resource-aware scheduling (CPU/memory/GPU slot tracking per host), (d) wire scheduler decisions through the commit barrier so scheduling state is replicated and survives failover. Dependencies: ha-consistency-invariants (commit barrier). Spec advised before implementation; promote via
/kiro-spec-init "scheduler-framework"when ready.
Superseded / Absorbed Items
Quorum-ACK gate before config restart(FUTURE_WORK.md §2) — largely superseded by ha-consistency-invariants Requirement 5: configuration changes now require majority acknowledgment before being confirmed/committed, and fail to the requester on timeout (no silent rollback when standbys are offline). Residual to verify in that spec's design: restart sequencing after a committed config change (don't restart the primary before the commit barrier passes).Lost-and-found keyword validation(FUTURE_WORK.md §8) — folded into the admin-config-tool backlog item above.
Notes
- Backlog items here are seeds for
/kiro-discovery//kiro-spec-init; promote an item by creating its spec under.kiro/specs/. - Provenance: items marked (FUTURE_WORK.md §N) originate from the 2026-05-12 config-replication-design.md review, merged here 2026-06-10.