Commit graph

586 commits

Author SHA1 Message Date
f4327ce4f6 feat(ha-heartbeat-committed-propagation): migrate last_known_committed_config_tag to _Atomic (task 1.1)
Mirror the sibling last_known_committed_lsn: all writes release, reads acquire.
Prereq for a heartbeat-receive writer + the ctrlstatus reader to interact
safely (Req 2.3). Pure access migration, no behavior change; controller suite
0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 11:27:46 +00:00
1aa0761c12 fix(libxce): xce_strerror_r global-buffer-overflow on HA error codes (utils.c)
The E_XCE_* enum (errnox.h) gained NOMASTER/REDIRECT/NOT_READY/STANDBY
(10011-10014) but xce_err_str[] still had only 10 strings + NULL, while the
bounds check used E_XCE_MAX. Formatting any of those codes ran off the array:
NOMASTER -> strncpy(NULL) deref; NOT_READY (10013, returned by ctrlstatus on a
failover-exhausted cluster) -> read 8 bytes past the 88-byte table = the ASAN
global-buffer-overflow at utils.c:335.

Fix: fill the table for all 14 codes; bound xce_strerror_r against the actual
array size + NULL (degrade to 'unknown error', never OOB); add a _Static_assert
tying the table length to E_XCE_MAX so any future enum drift fails the build.
Pre-existing foundation bug surfaced (not caused) by controller-status 6.4.

RED: test_strerror_all_xce_codes SEGV'd at utils.c:335 under ASAN; GREEN: lib
suite Ok:1 Fail:0. New tests in test_error.c cover all in-range + out-of-range codes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 10:13:21 +00:00
ee30765d31 feat(controller-status): render responderTime in cmd_ctrlstatus, log-comparable format (task 6.3)
Add ctrlstatus_fmt.h (ctrlstatus_format_time_ms): renders responder_time_ms as
local YYYY-MM-DD HH:MM:SS,mmm, byte-equivalent to the controller log prefix
(tlog/my_tlog_format) so a captured ctrlstatus lines up against ctrl-N.log.
cmd_ctrlstatus prints a responderTime line; TZ-pinned exact-string unit test.
Req 11.2, 11.3. ctrlstatus group passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 09:28:49 +00:00
3799bd6af4 feat(controller-status): off-primary learned committed + responder timestamp in ctrlstatus_fill (task 6.2)
Role-branch the committed sourcing: PRIMARY reports its own authoritative
persist.committed_lsn / committed_config_tag; off-primary reports the LEARNED
ha->last_known_committed_lsn / last_known_committed_config_tag instead of the
primary-only raw markers that read 0 on a standby (fixes the applied>committed
inversion). Stamp responder_time_ms from CLOCK_REALTIME for log correlation.
Read-only (Req 10.1-10.4, 11.1). 4 new TDD tests; controller suite 110/0/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 09:17:07 +00:00
65ddafeb66 feat(controller-status): add responderTimeMs @13 to CtrlStatus schema + typemap (task 6.1)
Append-only UInt64 epoch-millis field for the responder snapshot timestamp
(Req 11.1, wire-format setup). Hand-authored xce_ctrlstatus_t typemap gains a
matching responder_time_ms member; codec round-trips it. Controller suite 1/1 OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:56:51 +00:00
eda1f4c9d3 fix(xce-test-e2e-primitives): shell-style -e/+e errexit with mode-aware default — interactive REPL no longer exits on a bad command 2026-06-20 06:16:43 +00:00
ade0081609 fix(controller-status): primaryName from role (primary self-reports; standby=unknown) — ha->primary_host is vestigial (E2E finding, task 5.1) 2026-06-20 06:03:00 +00:00
6747304184 feat(controller-status): wait-primary uses authoritative ctrlstatus_primary probe (task 4.2) 2026-06-20 05:46:24 +00:00
9e87d2c92e feat(controller-status): ctrlstatus client command (task 4.1) 2026-06-20 05:40:15 +00:00
03813bf560 feat(controller-status): xcecli_ctrlstatus_primary authoritative reach-the-primary read (task 3.2) 2026-06-20 05:32:18 +00:00
691cd9199e feat(controller-status): libxce xcecli_ctrlstatus any-node read + decode + free (task 3.1) 2026-06-20 05:18:04 +00:00
e56409cbd4 feat(controller-status): allow ctrlstatus through ha_dispatch_gate in all roles incl no-quorum (task 2.2) 2026-06-20 05:04:03 +00:00
a6e03d87ab feat(controller-status): handle_ctrlstatus read-only state snapshot + dispatch + configChecksum as Text hex (task 2.1) 2026-06-20 04:53:27 +00:00
97a501e96e feat(controller-status): add ctrlstatus opcode + CtrlStatus/CtrlPeer schema + typemap typedefs (task 1.1) 2026-06-20 04:32:42 +00:00
2988682bd6 feat(xce-test-e2e-primitives): quiet wait-primary poll — suppress per-attempt joblist perror + libxce failover INFO spam
The wait-primary probe loop EXPECTS repeated failure until a primary serves, so it
no longer spams the console: joblist_query gains a quiet flag (the poll passes
quiet=1) and the client log threshold is raised to warn for the loop duration,
restored to the user's level on every exit path. Interactive joblist + the
assertions keep their diagnostics (quiet=0).
2026-06-20 03:18:40 +00:00
d82cc78284 test(xce-test-e2e-primitives): harden durability soak — separate liveness from durability, no false bug-661 cascades
Conflation fix: gate every kill + durability assertion on cluster health (3 nodes
up + serving + rejoin settle); assert_no_loss probes serveability first and reports
INCONCLUSIVE (not DATA LOSS) when unserveable; confirm-settle re-check distinguishes
catch-up lag from persistent loss; post-kill election SLA configurable (60s default).
DATA LOSS now reported ONLY for a job persistently absent from a SERVING cluster.
2026-06-20 01:27:38 +00:00
3ad646ad0e spec(ha-commit-durability): REOPEN for bug-795 total-outage cold-restart-all committed-job loss
Phase 6 added (6.1 reproduce-on-real-fs gates 6.2 root-cause / 6.3 fix / 6.4 revalidate).
Surfaced by the xce-test-e2e-primitives cold-restart-all soak step; Phase 5 only
validated rolling single-node failover. CLAUDE.md durability claim qualified pending 6.x.
2026-06-20 00:33:18 +00:00
860997634a fix(xce-test-e2e-primitives): jobsub clean-ack requires echoed JobId == submitted id (bug-786, surfaced by live soak 7.2) 2026-06-19 16:57:47 +00:00
6315368b01 test(xce-test-e2e-primitives): convert durability soak to exit-code primitives (task 7.1) 2026-06-19 16:41:30 +00:00
9466b71691 feat(xce-test-e2e-primitives): wait-primary [timeout_ms] (task 6.1) 2026-06-19 16:29:26 +00:00
f3334c0fb6 feat(xce-test-e2e-primitives): expect-job <jobid> assertion (task 5.3) 2026-06-19 16:22:22 +00:00
b0734c87c9 feat(xce-test-e2e-primitives): expect-jobs N assertion (task 5.2) 2026-06-19 16:16:43 +00:00
171c88fcec feat(xce-test-e2e-primitives): joblist_query helper + $jobcount capture (task 5.1) 2026-06-19 16:10:44 +00:00
fae0bc0ff4 fix(xce-test-e2e-primitives): jobsub clean-ack gating + $jobid capture, closes bug-786 (task 4.1) 2026-06-19 16:04:13 +00:00
5331832565 feat(xce-test-e2e-primitives): auto-var substitution $?/$jobid/$jobcount (task 3.1) 2026-06-19 15:54:34 +00:00
0584a6229a feat(xce-test-e2e-primitives): !cmd shell-escape via system(), feeds $? (task 2.1) 2026-06-19 15:45:09 +00:00
7ad63e7403 feat(xce-test-e2e-primitives): add echo command (task 1.2) 2026-06-19 15:38:38 +00:00
481e197928 feat(xce-test-e2e-primitives): session verdict + exit code + abort/continue + comment handling (task 1.1) 2026-06-19 15:32:11 +00:00
64ab210cbf fix(ha-wal-replication): apply_wal_entry must not own the WAL CRC chain (bug-778)
On the standby replicated-apply path, ha_apply_single_entry wrote the entry via
persist_wal_write_replicated (which correctly set the self-computed CRC chain
link p->last_crc), then called apply_wal_entry with a SYNTHETIC header whose
crc=0. apply_wal_entry's `p->last_crc = hdr->crc` clobbered the chain link with
0, so every subsequent replicated write stamped prev_crc=0 on disk. The on-disk
CRC chain broke at the second replicated entry — invisible while live (standbys
serve in-memory state) but fatal on COLD RESTART: replay hit the break, returned
RECOVER_TRUNCATED, recovered only the config prefix, and lost all committed jobs
(joblist=0). The sticky truncated verdict then drove a promotion-fence
availability wedge (bug-777) when every survivor was truncated.

Fix — single ownership of the WAL header + CRC chain:
- apply_wal_entry is now LOGICAL entity-restore only: signature
  (p, type, lsn, payload, payload_len); no header. It advances the current_lsn
  high-water (monotonic) but NEVER touches p->last_crc.
- The physical layer is the sole owner of the CRC chain: persist_wal_write_-
  replicated on the live path, replay_wal/persist_recover on recovery.
- ha_apply_single_entry drops the fabricated header and passes entry fields
  directly. The persist_recover replay caller is updated to the new signature.

Verified: new RED->GREEN test test_replicated_apply_chain_survives_cold_restart
(drives the real ha_apply_wal_catchup_entry path, cold-replays -> RECOVER_CLEAN,
all N recovered); controller suite 0 failures; 3-node rolling-SIGKILL soak on a
real ext2/3 fs now passes 5/5 (was wedge+loss at round 3); full cold restart
recovers all 6 committed jobs (was 0) with CLEAN verdicts and a normal election.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:12:53 +00:00
276e26c37b test(scheduler-controller-msg-gap): real handler/applier unit tests (bug-765 G-6)
The Task 9 tests were marked [x] but were stub/absent. Convert the
correctness-critical ones to real white-box tests, registered as meson tests
under --suite scheduler (leaks-off per convention):

- test_handlers.c (new) — #include ../src/handler.c; covers 9.2/9.3: each
  notify/dataset handler enqueues the correct op / skips NULL-empty / -EINVAL.
  Asserts the 5.6 fix (update_job -> UPDATE_JOB_FIELDS, not bulk UPDATE_JOB).
- test_job_update.c — add 9.5 (real sched_update_existing_queue, replacing the
  former local stub); already covers 9.4 (job) and 9.7 (rusage).
- meson.build — build + register all three scheduler test executables.

Suite GREEN: sched_test 15/15, sched_job_update_test 10/10,
sched_handlers_test 6/6; ASAN-clean.

Honest remaining: 9.6 dequeue worker-resvals APPLY is PARTIAL (enqueue tested;
apply is a dormant path, R-3); 9.8 is a documented compile-time absence check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 04:14:20 +00:00
93124c5601 feat(scheduler-controller-msg-gap): store job rusage on the job + free leaked op payloads (tasks 5.5, 5.8; bug-765 G-5/G-3)
5.5 (Req 6 / G-5): the JOB_RUSAGE notify path called the plugin but never
stored the usage data — xce_sched_job_t had no rusage field (UB-6.1 unmet).
- Add xce_job_rusages_t* rusages to xce_sched_job_t.
- New sched_apply_job_rusage() stores the report ON the job (ownership
  transferred), replacing any prior via free_JobRusages_ptr; -ENOENT unknown,
  -EINVAL NULL. Dequeue NULLs the request pointer on success; kill_sched_job
  releases job->rusages.

5.8 (G-3): sched_pend_update_killer left five new-op payloads on empty
break cases — a per-message leak since ownership is transferred on enqueue.
- Free each with the exact generated deep-freer: free_QueueConf_ptr,
  free_Resource_ptr[], free_ResourceVal_ptr[], free_ResValList_ptr,
  free_JobRusages_ptr. Safe: handlers nullify on enqueue, makeResource copies
  via makeInstance, dequeue copies values (no aliasing).

Tests: sched_job_update_test 7/7 (3 update + 4 rusage), regression
sched_test 15/15, ASAN clean (no bad-free/double-free).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 03:33:28 +00:00
5c1f715ccf feat(scheduler-controller-msg-gap): route notify job-update to in-place sched_update_existing_job (task 5.6, bug-765 G-8+G-4)
The notify schedUpdateJob path shared XCE_SCHED_OP_UPDATE_JOB with the
dataset bulk-load, so the dequeue routed both to sched_update_jobs()
(create/replace) — a notify update to an existing job corrupted/duplicated
live job state and never returned -ENOENT (Req 3 UB-3.1/UB-3.2 unmet;
sched_update_existing_job was dead code copying only state).

- Add distinct op XCE_SCHED_OP_UPDATE_JOB_FIELDS (+ jobfieldsupdate union),
  enqueued only by the notify handler, dequeued to sched_update_existing_job.
- Move sched_update_existing_job to schedobj.c; update all mutable fields
  (state, priority, queue, resreq) in place; -ENOENT unknown, -EINVAL NULL.
- Dataset bulk-create path unchanged. Killer frees the new payload.
- New white-box test sched_job_update_test (3/3); regression sched_test 15/15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 03:17:40 +00:00
f139e1c08e docs(ha-commit-durability): flip bug-661 KNOWN GAP to GUARANTEED + re-verify protocol checklist (task 5.3) 2026-06-18 11:15:13 +00:00
59d9df246c fix(ha-commit-durability)!: controller never self-terminates — loud non-promotable fence replaces recovery FATAL/abort
Per design invariant (a controller exits only on crash or admin kill), replace the
self-abort recovery terminal (tasks 3.3/4.3/4.4) with a loud, permanent, non-promotable
fence that stays alive and keeps retrying catch-up (self-heals on peer return; no manual
admin recovery). Records the never-self-terminate invariant as project-wide guidance in
src/controller/CLAUDE.md + .kiro/steering/ha-async-io.md.
2026-06-18 11:10:19 +00:00
addf53e3b4 feat(ha-commit-durability): bounded-window catch-up before FATAL — fence no longer FATALs synchronously (task 4.4) 2026-06-18 10:35:13 +00:00
e14bd62115 test(ha-commit-durability): end-to-end durable-majority commit + liveness integration tests (task 5.1) 2026-06-18 09:56:26 +00:00
e9e2a6d703 feat(ha-commit-durability): pre-promotion eligibility gate + catch-up fence/release (task 4.3) 2026-06-18 09:40:32 +00:00
3049b620f3 feat(ha-commit-durability): durable-backed vote/PreVote freshness floor + bare-durable candidate advertise (task 4.2) 2026-06-18 09:02:11 +00:00
c1c35ab1b3 feat(ha-commit-durability): publish durable floor from recovered WAL tip (task 3.4) 2026-06-18 08:24:46 +00:00
e3fc42c11e feat(ha-commit-durability): loud bounded FATAL on unrecoverable recovery (task 3.3) 2026-06-18 08:15:22 +00:00
6483ee8afd docs(ha-commit-durability): pin boot-policy decision table + pending skeleton tests (task 3.2) 2026-06-18 08:00:52 +00:00
dd4d70863b feat(ha-commit-durability): WAL recovery integrity verdict (CLEAN/TRUNCATED) + recovery_truncated flag (task 3.1) 2026-06-18 07:43:01 +00:00
526bca9679 feat(ha-commit-durability): gate legacy sync-append commit store on durable_lsn + commit<=durable invariant (task 2.4) 2026-06-18 07:09:47 +00:00
e4c4968331 feat(ha-commit-durability): gate standby ACK on fdatasync success + persist-before-ACK regression test (task 2.3) 2026-06-18 06:45:21 +00:00
c41edc515e feat(ha-commit-durability): cap commit self-contribution at durable_lsn + durable-advance commit wake (task 2.2) 2026-06-18 06:29:47 +00:00
cc83caf5a0 feat(ha-commit-durability): demand-driven group-commit flusher + durable_lsn watermark (task 2.1) 2026-06-18 06:06:55 +00:00
7e6f09f804 test(ha-commit-durability): deterministic strace-based ack-before-durable gap repro (task 1.3) 2026-06-18 05:39:06 +00:00
f90395282c feat(ha-commit-durability): add XCE_TESTING durable-LSN-on-disk verification seam (task 1.2) 2026-06-18 04:56:50 +00:00
1ca2d640cb feat(ha-commit-durability): add --wal-sync-ms flush-cadence override (task 1.1) 2026-06-18 04:46:01 +00:00
80bb1d766e fix(libxce-failover): classify and propagate netio I/O errors on failover
Add io_classify_io_error and propagate endpoint/epoll errors through
endpoint_update_epoll and cbuf_get instead of swallowing them, so client
failover surfaces the real error rather than hanging. Adds netio arm-failure,
close-propagation, and request send-propagation tests.

Pre-existing uncommitted work, committed to establish a clean baseline before
ha-commit-durability implementation.
2026-06-18 04:17:07 +00:00