Commit graph

40 commits

Author SHA1 Message Date
ff9de2e004
fix(bug-924): prune stale /tmp test-fixture dirs in xce-build.sh
70+ testsuite/ files across controller/scheduler/lib call mkdtemp()
directly with no shared fixture helper, and none clean up after
themselves. Left unchecked this fills the sandbox disk (recurred three
times now: 2026-06-22, 2026-08-26, 2026-08-30, each larger than the
last -- 696GB at its worst), which then surfaces as unrelated
"Expected Non-NULL"-style cascade failures wherever the next test
happens to call mkdtemp()/persist_init(), easily mistaken for a real
regression.

Retrofitting cleanup into 70+ individual test files would be invasive
and risks breaking tests that inspect a fixture's contents across
multiple TEST_ functions in the same file. Every test run in this
project goes through xce-build.sh, so pruning once there -- age-gated
at 3 hours so a concurrent session's in-progress fixtures are never
touched -- is the lowest-risk single choke point.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QxaSzHRB7pmYFp4cVnwkA
2026-08-30 15:07:49 +00:00
83cb660b44
fix(bug-1732): build xcecd_virtual in xce-build.sh's worker target
The `worker)` case's meson compile target list omitted xcecd_virtual --
the no-root worker binary deploy-ha.sh/the xce-deploy-ha skill needs
whenever no real systemd manager is present (the common sandbox case).
/xce-build worker alone left it unbuilt, so deploy-ha.sh silently
soft-skipped generating start-worker.sh. Verified: xcecd_virtual now
builds with a fresh mtime, full worker suite still 5/5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QxaSzHRB7pmYFp4cVnwkA
2026-08-29 11:35:10 +00:00
faedc6fc49
fix(deploy-ha): guard against double-launching a live HA deploy
start.sh/start-worker.sh/start-scheduler.sh/start-apiserver.sh had no
check for an already-running instance, so re-running start-all.sh (or
a generated per-component script) against a still-live deploy silently
spawned a full duplicate process set under the identical --myname/
--peers/ports. Combined with SO_REUSEPORT on every listening socket
(src/lib/netio.c), the duplicate's bind()/listen() succeeded instead
of failing loudly, so two live controllers per node ended up voting
into the same Raft group with independently diverged term/log state --
a split-brain that surfaced as non-converging elections, a bouncing
redirect chain, and ctrlstatus reporting an unknown primary for 40+
seconds on a freshly deployed cluster.

Each generated launcher now writes a pidfile under $TOP/run/ and
refuses to start if a live process already owns it, verified via
/proc/<pid>/cmdline identity match (not just liveness) to avoid a
PID-reuse false positive. start-all.sh preflights all expected
pidfiles before launching anything, failing fast and loud instead of
partially duplicating some nodes. Each script's final invocation now
uses exec so the pidfile records the daemon's own PID directly, which
also collapses the previous wrapper+child two-process-per-component
layout into one process. cleanup.sh clears stale pidfiles for hygiene.

Verified live in-sandbox: normal start-all.sh still elects a primary
and starts all 6 components; a second start-all.sh run against the
same live deploy now refuses cleanly with all 6 already-running
components identified, process count unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-27 07:59:24 +00:00
f4d2c123fd
test(dirty-content-fingerprint): add Nix production path fingerprint check
Adds src/tools/test-dirty-fingerprint-nix-production-path.sh, reusing task
6.1's isolated-git-worktree technique. Runs nix build ./src#xcecore_debug
(no --impure) twice with different tracked-file edits to flake.nix,
cross-checking nix eval's eval-time myDirtyFingerprint value against the
real build log's mesonConfigurePhase flags: line for each.

This sandbox's nix build dispatches by default to an unreachable-in-
practice remote builder, making a directly-runnable binary impractical to
retrieve -- confirmed genuinely expected sandbox behavior. The eval+build-
log cross-check is not a weaker substitute: self.narHash's value is baked
as a literal string into the derivation at eval time, before any build
phase runs, so eval-time and build-time values cannot diverge by
construction. Forcing --builders '' (local-only) was investigated and
reaches real local compilation with a direct log match, but still can't
produce a fully linked binary here due to a second, unrelated network
dependency -- confirming no stronger approach is currently available.

Verified independently by review with real nix build invocations,
including a live-witnessed remote-builder connection attempt and a
genuine local-only run: two builds with different edits produce different
fingerprints in both eval and build-log evidence; reverting reproduces the
original value exactly; --impure never used; no git dependency in the
build sandbox; worktree list and flake.nix/nixSupport/ confirmed clean
before and after every run.

This is the final task of the dirty-content-fingerprint spec -- all 16
sub-tasks are now complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 11:12:15 +00:00
a47854184a
test(dirty-content-fingerprint): add startup-log-unchanged consistency check
Adds src/tools/test-dirty-fingerprint-startup-log-unchanged.sh, reusing
task 6.1's already-approved isolated-git-worktree technique. Builds
scheduler and apiserver for real on a genuinely dirty tree, starts both
daemons, and proves --version/-version show a real fingerprint while
their startup logs show the unchanged, fingerprint-free original format.

Apiserver's injected fingerprint reuses scheduler's own extracted value
(same dirty tree) rather than an independent recomputation -- verified
independently by review as legitimate, not tautological: apiserver has no
fingerprint-computation code of its own (only ever receives an externally
injected value via -ldflags), and the test still exercises apiserver's
own real composeVersionDisplay()/logger.Info code on a genuinely running
binary, with a round-trip check that would catch a real strip-and-append
bug.

Verified independently by review with a fresh full run plus a separately-
authored 4-case discrimination probe outside the script's own demo block.
No leftover processes, worktrees, or build dirs after either run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 10:17:39 +00:00
a7d64c8225
test(dirty-content-fingerprint): add dev-loop distinguishability regression
Adds src/tools/test-dirty-fingerprint-distinguishability.sh, driving a real
xce-build.sh scheduler cycle through the 4 required --version fingerprint
states (clean/dirty-A/dirty-B-differs/reverted-A-matches). Uses a genuine
isolated git worktree (git worktree add --detach at a unique mktemp -u
path) to sidestep this actively-shared repo's ambient concurrent-editing
noise -- all file edits confined strictly inside the isolated worktree,
cleaned up via a trap registered before creation so it safely no-ops if
creation never happened.

Verified independently by review across two full live runs, including a
deliberately sabotaged copy forcing edit-B to reproduce edit-A's content:
the sabotaged run correctly failed with a clear message and exit 1,
proving genuine discriminating power. git worktree list and the real
repo's working tree confirmed byte-identical to baseline before and after
every run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 09:56:12 +00:00
b949949367
feat(dirty-content-fingerprint): go_build injects fingerprint via ldflags
go_build now reads XCE_DIRTY_FINGERPRINT (empty-safe) and appends
-X main.DirtyFingerprint=... to its existing -ldflags construction,
falling back to plain empty string when unset -- deliberately NOT the
"unknown" fallback GitCommit/BuildTime use, matching dirty_fingerprint's
empty-means-nothing-to-show convention everywhere else in this feature.
Existing GitCommit/BuildTime fallback logic is completely untouched.

Verified independently by review with real scratch Go builds traced via
bash -x: unset -> empty flag value (not fabricated); set -> exact value
carried through; all-unset -> GitCommit/BuildTime still fall back to
"unknown" while DirtyFingerprint stays empty, confirming the two
conventions coexist without interference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 06:06:05 +00:00
c9d40ba180
feat(dirty-content-fingerprint): xce-build.sh computes dev-loop fingerprint
Adds an independent dirty_fingerprint computation to xce-build.sh: sha256
over git diff HEAD's output plus the sorted content of untracked,
non-ignored files (git ls-files --others --exclude-standard), truncated to
8 hex chars. Resolves to empty on a clean tree or if the hash computation
itself fails -- commit_id's own computation and -dirty suffix are left
completely untouched. Zero working-tree/index mutation.

Verified independently by review with real builds (HEAD pinned to control
for concurrent background commits): distinguishes different uncommitted
content, reproduces identical fingerprints for identical content, excludes
.gitignore'd files, degrades gracefully under 3 independently-injected
failure modes without aborting the build, applies to every profile, and
leaves the original feature's regression script passing unmodified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 04:34:00 +00:00
084a5a95c3
test(build-time-versioning): add xceadmin end-to-end build/install/version cycle check
Adds src/tools/test-xceadmin-build-install-version.sh (same house style as
tasks 6.1/6.2), which runs xceadmin's full real meson-driven build cycle
(schema sync, venv install, pytest, wheel build), installs the resulting
wheel into a fresh scratch venv, and separately does an editable install
into a second fresh scratch venv. Asserts the wheel install carries real,
non-"unknown" commit id and build time while the editable install carries
the committed "unknown"/"unknown" fallback stub, and explicitly asserts
the two differ -- proving the generated build-info module genuinely
replaces the fallback rather than both installs coincidentally agreeing.

Verified independently by review with a full from-scratch re-run: 261
passed/3 skipped (matching tasks 5.1/5.2's established baseline), both
install paths correct, and the demonstration block's crux case (identical
build-info values must be rejected by the differ-check) confirmed correct.

This is the final task of the build-time-versioning spec; all 13 sub-tasks
are now complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 17:02:01 +00:00
79941a9f42
test(build-time-versioning): add version/startup-log consistency check
Adds src/tools/test-version-startup-consistency.sh (same house style as
task 6.1's test-build-version-regression.sh), which builds xce_scheduler
and xce_apiserver for real, starts each daemon, and asserts the commit id
and build time reported by --version/-version are identical to the values
in the daemon's own startup log -- proving Requirement 2.2's guarantee
that the two can never disagree for the same artifact.

apiserver's build routes around two pre-existing, unrelated sandbox
blockers (bug-1597's missing CGO env in `make build-server`, and
go_build's hardcoded GOPROXY hanging in this sandbox) via a direct `go
build -mod=readonly -ldflags` invocation that computes commit_id/build_time
identically to go_build and injects them the same way; only the
orchestration layer differs, not what main.go (task 4.4, unmodified)
consumes. Verified independently by review, including live reproduction
of both build-path blockers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 16:38:37 +00:00
0eefc95f0a
test(build-time-versioning): add motivating-incident regression script
Adds src/tools/test-build-version-regression.sh, a standalone script (same
convention as test-commit-durability.sh/test-heartbeat-tolerance.sh) that
runs real xce-build.sh scheduler builds in both asan and debug profiles,
extracts the commit id from xce_scheduler --version, and fails loudly if it
doesn't match a live git rev-parse --short HEAD (+ -dirty) -- the exact
staleness this feature was built to make visible without forensic git
commands.

Tolerates xce-build.sh's own known-unrelated flaky test exit (bug-1560) via
captured exit status rather than a blanket set -e bypass, so a genuine
compile failure still fails the check. A built-in demonstration proves the
comparison logic rejects a wrong hash, an empty commit id, and the literal
"unknown" fallback. Verified independently by review with a full from-
scratch re-run of both profiles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 15:14:21 +00:00
18ae0cc394
feat(build-time-versioning): go_build injects commit/build-time via ldflags
Reads XCE_GIT_COMMIT/XCE_BUILD_TIME from the environment (empty-safe via
[ -z ... ], not just unset-safe -- a classic ${VAR:-default} gotcha
avoided), falls back to "unknown", and appends
-ldflags "-X main.GitCommit=... -X main.BuildTime=..." to the go build
invocation alongside the existing -gcflags.

Confirmed via live testing: -X on a main package var that doesn't exist
yet is silently ignored by the Go linker (not a build error), so this
task has no real ordering dependency on tasks 4.4/4.5 (which add the
consuming vars to apiserver/xcecli).

Task 4.1/13 of build-time-versioning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 12:02:10 +00:00
bca639749d
feat(build-time-versioning): xce-build.sh computes real commit id/build time
The dev-loop build path (/xce-build, the one behind the motivating
incident) never set -Dcommit_id= at all, so every locally-built binary
carried an empty commit id. xce-build.sh now computes commit_id (git
rev-parse --short HEAD, -dirty suffixed on uncommitted changes, unknown
fallback on a genuine git failure) and build_time (UTC date), appends
both to MESON_OPTS for every profile before meson setup, and warns
loudly to stderr if either ever resolves empty anyway.

Task 2.1/13 of build-time-versioning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 08:45:53 +00:00
83e61eecc8
feat(xce-deploy-ha): support real xcecd worker deployment alongside virtual
Adds auto-detection of real vs virtual worker based on systemd
availability (/run/systemd/system), plus an XCE_REAL_WORKER override.
Real xcecd requires root (systemd D-Bus transient units, hardcoded
/var/run paths) and is used opportunistically now that passwordless sudo
is available in the sandbox; defaults to xcecd_virtual when systemd/D-Bus
isn't available.
2026-08-19 09:48:53 +00:00
8f542a384a
feat(resource-value-verification): extend xce-deploy-ha with apiserver step + start-all.sh (task 3.1)
deploy-ha.sh Step 3 gains an opt-in-by-presence apiserver step (gated on a
pre-built /tmp/apiserver binary, not built by this script) that generates
start-apiserver.sh with a debug-level logger.yaml, plus an always-generated
start-all.sh sequencing controller-health-check-then-worker/scheduler
(-vvv)/apiserver. Purely additive - callers without a built apiserver
(e.g. xce-test-ha) see zero behavior change.

Live-confirmed both scenarios end-to-end: no-apiserver correctly skips the
step; with-apiserver reaches full backend registration with debug logging
active and scheduler emitting DEBUG3 output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:53:49 +00:00
0f1df4998e
feat(deploy,xceadmin): standalone xceadmin packaging + deploy-ha usability (bug-1566)
Three related enhancements to make /xce-deploy-ha's output a
self-contained, directly-usable cluster for manual testing:

- xceadmin now builds an installable wheel (meson-build.sh) with
  correct dependencies (click/pycapnp/PyYAML/jsonschema, verified
  against actual imports -- pyproject.toml was missing several).
- deploy-ha.sh resolves the deploy environment's own bash
  ($BASH_BIN, baked into every generated script's shebang -- this
  environment's deploy-ha.sh had drifted to #!/bin/sh while still
  using bash-only syntax) and generates scripts/start-worker.sh /
  scripts/start-scheduler.sh sharing the controllers' --peers, plus
  a standalone xceadmin install (wheel extracted directly, resolved
  $PYTHONPATH baked into a wrapper script -- nix devShell packages
  are $PYTHONPATH entries, not merged into python3's own
  site-packages, so a --system-site-packages venv can't see them).
- bug-1566: xceadmin's --controller option had no relationship to a
  deployment's actual peers (hardcoded localhost:9991 default) --
  every command required an explicit --controller or silently tried
  the wrong port. Fixed via a $XCEADMIN_CONTROLLER envvar fallback
  (click precedence: explicit flag > envvar > bare default),
  exported by deploy-ha.sh's generated scripts/xceadmin wrapper.

Fixed/validated via TDD + live reproduction against a real deployed
cluster in a fully stripped environment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 10:20:52 +00:00
1c943e98fe
feat(build): add --incremental flag to xce-build.sh (bug-1565)
setup_build() unconditionally rm -rf'd the whole profile build
directory on every single-component invocation -- running
/xce-build xceadmin after /xce-build controller silently deleted
xce_controller/xce_test/etc. from the same build dir.

Added an opt-in --incremental flag (meson setup --reconfigure in
place instead of wiping) rather than changing the default: this
project's capnp-generation custom_target has known missing-
dependency-edge races that a full wipe currently papers over as a
loud, retry-fixable failure. Making incremental the default risked
turning that same gap into silent stale-generated-code instead, so
the safer full-wipe behavior is preserved unless explicitly opted
into.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 10:20:36 +00:00
45400c3a28
fix(controller): close 6 data-safety races + apply-pump O(N^2) regression + boot TOCTOU
Bundles the fixes validated this session across ha.c/persist.c/worker.c:

- raft-apply-decoupling Task 7.4: apply-pump O(N^2) throughput regression
  fixed via a segment-read cache.
- bug-1416: boot-time TOCTOU race in persist_verify_apply_correctness()
  closed with a recovery-complete gate that closes at process-lifecycle
  start, not function entry.
- bug-451: HOOK_DETAIL_JOB_RUSAGE_UPDATE now has its own persist hook arm
  instead of silently falling through the default job-state case.
- bug-1426: trim_wal_segments() also caps its trim floor at the local
  apply pump's own progress, not just remote standby-acked positions.
- bug-1427: persist_verify_apply_correctness() extended with a worker-count
  check mirroring the existing job-count check (count_jobmgr_entries and
  count_workermgr_entries now share one count_manager_entries(var) helper).
- bug-1428: persist_register_pending_apply's critical section widened to
  close the actual race instead of patching a symptom.
- bug-1472 (remainder): WORKER_DEAD replay in worker_from_capnp() now
  applies mark_worker_unavail()'s mutations via a shared
  apply_worker_dead_mutation() helper instead of duplicating them.
- bug-1474 (angle-a): on_standby_connected's stale-duplicate detection
  fixed for promoted vote endpoints via a shared votes_idx_for_peer()
  index-scale converter.

Each fix independently verified via TDD (RED before fix, GREEN after).
Full ASAN rebuild + controller_unit_test: 1101 tests, 0 failures. Live
single-node HA deployment validated Task 7.4 and bug-1416 across 5
restart/recovery cycles (45/45 job submissions, zero APPLY_DIVERGENT
fences).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 10:20:02 +00:00
ce9f734a38
feat(component-failover-adoption): unify worker's dual connect paths into single pipeline
Replace worker's separate default/single-host and multi-candidate connect
branches with one path: synthesize-or-accept a candidate list, validate,
convert via xce_cluster_config_to_client_config(), connect once. Register
a flag-only rejoin callback (no I/O, no client-lib reentry) and remove the
liveness probe's bounded give-up so the library owns retry/recovery.
2026-08-11 05:42:48 +00:00
ee8473bd99 feat(admin-config-tool): drop ha/data/ entirely, match real client TLS
deploy-ha.sh no longer takes a <data-dir> argument or --build-config
flag. Every deploy now generates a fresh self-signed TLS cert/key
(openssl, RSA 2048, 10y validity) directly into the deploy dir's
certs/, and builds cluster.bin directly into controller/ctrl-1/conf/
via xceadmin -- no intermediate staging directory, nothing preserved
across runs. run-ha-soak.sh (/xce-test-ha) updated to match: dropped
its own XCE_HA_DATADIR-based precondition entirely. Updated 4 callers
(run-ha-soak.sh, test-heartbeat-tolerance.sh, test-commit-durability.sh,
both xce-deploy-ha/xce-test-ha SKILL.md docs).

Separately: xcecommon.wire.connect() was loading a client-side TLS
cert, diverging from the real reference client -- netio.c's
create_ssl_context() presents no certificate at all when acting as a
client (cert_file/key_file both NULL, bare TLS_client_method()).
Fixed connect() to match, and since nothing else consumed cert_dir
(verify_mode=CERT_NONE throughout, no CA check either), removed it
end-to-end: discovery.follow_redirects(), push.push_config()/
probe_config(), and the --cert-dir CLI flag.

Server-side cert loading (mock TLS test servers) is unaffected --
moved into a shared session-scoped tls_certs fixture (new
tests/conftest.py, openssl-generated) replacing 3 duplicated
REPO_HA_DATA_DIR/skipif blocks. Net result: those tests no longer skip
under a hermetic nix build sandbox (which never had ha/data/) --
verified 165/165 passed, 0 skipped, via both meson compile and a full
nix build --option sandbox false .#xcecore_debug rebuild (previously
128 passed / 37 skipped there).

ha/data/{xce.crt,xce.key} removed from git tracking. Requirements
2.3/2.4 and 8.3/8.4/8.5 marked superseded in requirements.md with
dated correction notes.
2026-07-22 14:50:51 +00:00
d5330fc976 feat(admin-config-tool): wire xceadmin into HA deploy/test skills
deploy-ha.sh's --build-config path (task 4.2) pointed at
src/xceadmin/.venv/bin/xceadmin, broken by today's meson/nix rewiring
(xceadmin no longer builds a venv there). Fixed to search the meson
build dir (xce.build/build.release/build.dbg), a nix-installed
result/bin, then PATH.

/xce-deploy-ha's SKILL.md never mentioned xceadmin or --build-config
(predates task 4.2) — documented both.

/xce-test-ha's run-ha-soak.sh hard-required a pre-existing cluster.bin
under ha/data/, exit 2 if missing, with no xceadmin path at all. Now
always builds cluster.bin fresh from src/configs/*.yaml via xceadmin
before every soak run, same resolution order as deploy-ha.sh.

ha/data/cluster.bin is no longer a checked-in fixture: removed from
git tracking and gitignored. It was stale relative to xceadmin's
output anyway (different hash from a different, older generation
path) -- the xceadmin-built one matches the known-good golden-fixture
hash from admin-config-tool task 5.4.
2026-07-22 12:00:05 +00:00
e4fd9e1d3f feat(admin-config-tool): wire xceadmin into meson + nix build
Reopens the 2026-07-21 design decision to keep xceadmin outside meson.
Standard onboarding consistency (README 3.3 `nix build .#xcecore` and
3.4 `meson compile` working for xceadmin like every other component)
outweighs the earlier concern about meson's single-artifact model not
fitting a multi-file pip install.

- src/xceadmin/meson.build: custom_target() wrapping install+test,
  stamp-file output, build_always_stale (matches this repo's existing
  clean-rebuild-every-invocation convention).
- src/xceadmin/meson-build.sh: out-of-tree pinned install + pytest;
  stages a repo-mirror/ symlink tree so tests' fixed-relative-path
  assumptions (src/configs/, src/schema/, ha/data/) still resolve from
  a copied location, without touching their deliberate fail-loud
  behavior.
- src/meson.build: subdir('xceadmin') under component != 'xcecli'.
- src/tools/xce-build.sh: xceadmin case now delegates to
  `meson compile -C "$BUILD_DIR" xceadmin` instead of its own
  pip/venv implementation, like every other component.

Verified both build paths: meson compile (165/165 passed) and
nix build --option sandbox false .#xcecore_debug (128 passed / 37
skipped — cert-dependent tests correctly skip under nix's
git-tracked-only hermetic source). AppImage bundling (README 3.5)
is explicitly deferred to future work, not part of this change.
2026-07-22 10:46:45 +00:00
ad27147a59 feat(admin-config-tool): task 4.2 - deploy-ha.sh --build-config
Add --build-config (parsed anywhere in the arg list) that rebuilds
<data-dir>/cluster.bin from configs/ via the venv's xceadmin before
deploying; a missing cluster.bin auto-builds the same way even
without the flag. An existing cluster.bin with no flag passed is
left byte-for-byte untouched, matching prior behavior. xceadmin and
the configs source are resolved relative to the script's own
location, not the caller's CWD.

Verified live: missing-file auto-build, untouched-when-present (same
mtime/SHA), and forced rebuild reproducing byte-identical output.
2026-07-22 09:45:11 +00:00
798f19b83a feat(admin-config-tool): task 1.3 - nix and xce-build.sh integration
Add python3 + pycapnp/pyyaml/jsonschema/click/pytest/ruff (+pip) to the
nix dev shell, wire a new `xceadmin` case into xce-build.sh (schema
sync from src/schema/ and src/configs/schema/, pip install by profile,
pytest), package-data config so bundled schemas survive a non-editable
install, xce-build SKILL.md docs, and ruff format/check hooks matching
this repo's stdin-JSON hook convention (not brief.md's env-var sketch,
which would have silently no-op'd).

Verified live end-to-end inside `nix develop ./src` for both the
default (editable) and release (pinned) profiles; found and fixed 3
real bugs along the way (missing pip, PEP 668 externally-managed-
environment, missing package-data for release-profile bundling) —
logged as bug-1458/1459/1460.

Requirements: 5.5, 8.1, 8.2, 8.6
2026-07-22 03:57:43 +00:00
40f510fb14 fix(controller): bug-1433 async-thread fdatasync block + bug-1434 committed_lsn CAS race
bug-1433: raft_cb_become_leader runs synchronously off peer_ctrl_dispatch_to_pool,
which hardcodes VTASK_LIVENESS for every inbound peer-control packet including
vote replies. The bug-1431 round-2 fix (flusher_flush_once force-flush in
persist_promotion_advance_committed) was reachable from that never-blocks task
class -- same bug family as bug-1198. Removed the force-flush; round-3
(durable-capped only) plus the already-shipped bug-1432 immediate-first-heartbeat
fix close the visibility-latency gap non-blockingly. TRACKED RESIDUAL:
persist_apply_pump_run_sync is still reachable on the same VTASK_LIVENESS path
and does bounded pread() -- lower risk (page-cache-hot) but strictly still
blocking I/O; not safe to remove outright (reopens bug-1043-family job loss),
needs its own follow-up (reclassify peer_ctrl_classify's winning-vote-reply case
or dispatch promotion tail onto a fresh VTASK_LIVE_BLOCK vtask).

bug-1434: persist_promotion_advance_committed and ha_standby_advance_committed_and_apply
raced on persist_t.committed_lsn via an unlocked load-then-conditional-store during
the promotion window (before ha->role flips to CTRL_PRIMARY) -- a losing racer could
regress committed_lsn. Added persist_committed_lsn_advance_atomic (CAS retry loop,
lock-free) and routed both call sites through it.

Validated: 19/19 unit test suites, 6-round chaotic soak (0 loss/divergence) + 6-round
sequential+leadership-coverage soak, both PASS. Also folds in raft-apply-decoupling
and raft-ha-core-safety validate-impl phase bumps to "implemented" and clears
config-upgrade-snapshot's now-satisfied entity-bearing-snapshots block.
2026-07-11 12:16:28 +00:00
5e0579dede feat(skills): fix xce-build/xce-test-ha/xce-deploy-ha subagent dispatch
- xce-build.sh: `controller` component now always builds `controller/xce_test`
  alongside `xce_controller`, in every profile — xce-test-ha needs both
  binaries and previously had to build xce_test as a separate step.
- xce-build/SKILL.md: removed `context: fork` — reproducibly broken for this
  skill (confirmed twice): whenever ARGUMENTS is non-empty, the forked
  subagent never receives a usable task and silently does nothing. Documented
  that this skill should instead always be dispatched via the plain Agent
  tool with a fully-resolved, self-contained prompt (not `context: fork`,
  which failed) — proven to work end-to-end.
- xce-test-ha/SKILL.md, xce-deploy-ha/SKILL.md: replaced the blanket "never
  invoke via subagent" warning with the precise finding — `context: fork`
  (Skill-tool isolation) is the confirmed-broken mechanism (its own teardown
  SIGHUPs the whole controller process group mid-soak/mid-deploy), but
  Agent-tool subagent dispatch is a different mechanism and is safe
  (empirically verified: a `setsid nohup ... & disown` process launched from
  inside an Agent subagent survives that subagent returning, reparented to
  init). Documented both the foreground-blocking and fire-and-forget
  subagent patterns for each skill; xce-deploy-ha additionally flagged that
  its `start.sh &` should gain `setsid` to match the soak driver's stronger
  detach pattern before relying on unattended subagent dispatch.
2026-07-09 11:57:11 +00:00
9318f9977e fix: close bug-1401 ctrlstatus leak, bug-1400 jobsub leak; harden bug-1396 harness
bug-1401: xce_free_ctrlstatus(s) was defined as free_CtrlStatus(s), which
only frees the fields inside xce_ctrlstatus_t. The real client reply path
(decode_CtrlStatus_ptr) calloc's the struct itself, so every ctrlstatus call
leaked sizeof(xce_ctrlstatus_t) (120 bytes). Existing unit tests never caught
it because they built/freed the struct via the raw stack-decode + bare
free_CtrlStatus() helpers, never exercising the actual client macro. Fixed
by switching the macro to free_CtrlStatus_ptr(&(s)) and added a regression
test that exercises the real heap-decode path; RED confirmed the exact
120-byte/1-alloc LeakSanitizer signature before the fix.

bug-1400: the originally reported SEGV in cmd_jobsub on malformed positional
input is stale — NULLSTR guards added 2025-11-05 already reject it cleanly.
The real residual defect was submit.start_info leaking on all 4 early-return
validation paths; fixed by routing them through a shared `goto cleanup`
(matches the existing goto-clean idiom in queue_ctrl).

bug-1396 (job-count anomaly after cold-restart-all, not yet root-caused):
instrumented test-commit-durability.sh's assert_job_count() to dump the live
joblist and ACKED_FILE ledger on any count mismatch instead of only the
count, per the bug's own recorded next step.

Verified: controller ASAN build + full controller_unit_test green; lib
xcelib_test 285/285 green, 0 ASAN leaks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDWVNQLKxqTVHh42cH7rV3
2026-07-03 02:40:52 +00:00
02e9275706 feat(xce-build): add non-ASAN debug + release profiles; root-cause bug-1286 with the debug build
/xce-build now takes a build PROFILE: asan (default, debug+ASAN -> xce.build,
backward-compatible), debug (non-ASAN -O0 -g3 -fno-inline -fno-omit-frame-pointer
-> build.dbg, for gdb/cores), release (-O3 -> build.release). Each profile uses
its own default build dir so variants don't clobber each other; arg2 may still be
a legacy build_dir. asan runs the unit suites; debug/release are build-only (the
suites are ASAN-oriented and the meson test harness is overlay-fs fragile, bug-924)
and print how to run a suite directly.

Why: bug-1282/bug-1286 are heisenbugs ASAN perturbs away (no SIGSEGV) and that
gdb-on-release turns into a hang/timeout. The non-ASAN -O0 -g3 build reproduces
them AND yields a symbolized backtrace under gdb.

It immediately paid off: the debug profile ROOT-CAUSED bug-1286 (the round-4
segfault) — myreq_handler (cluster.c:251) does `c = ep->owner` then
`ha_dispatch_gate(&c->ha,…)`; on chaotic restart a walCatchupEntry (op 37) vtask
runs with ep->owner==NULL, so &c->ha = 0x3ff0 -> deref crash at ha.c:3194. Fix is
a NULL-guard in myreq_handler (+ owner-lifecycle). 7.2 now has one remaining
blocker (bug-1286, root-caused); bug-1282 deadlock is fixed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dbTHR9CsEYK9RsqmQpQpC
2026-06-30 09:15:36 +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
6315368b01 test(xce-test-e2e-primitives): convert durability soak to exit-code primitives (task 7.1) 2026-06-19 16:41:30 +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
9821ec4f3c fix(ha): election-safety hardening — vote gates, restored-vote term, freshness source
Election protocol fixes validated against raft.tla invariants:

- ha_init_raft(): one-time raft state init at startup (re-init would lose
  durable election progress); n_peers = full cluster size including self
- handle_request_vote(): higher-term step-down moved BEFORE the role gate
  (split-brain fix); standby vote-deny now requires a live primary control
  channel (primary_ctrl_fd >= 0) AND a fresh heartbeat
- ha_heartbeat_is_fresh(): derive freshness from ha->last_activity (the
  field production actually writes); the orphaned last_heartbeat timespec
  had no production writer, leaving the vote-deny gate inert (bug-337);
  field removed from ha_state_t
- handle_request_vote(): restore same-candidate idempotent re-grant —
  deny only a DIFFERENT candidate in the same term, matching raft.c and
  Raft §5.2 votedFor ∈ {Nil, candidateId} (bug-338)
- raft_init(): restore voted_for_term alongside a persisted vote; leaving
  it 0 allowed a double vote in the same term across a restart (bug-339)
- myapp_run(): leader_known check treats a vote recorded under this node's
  own name as a self-vote — production persists the real candidate name,
  the "self" placeholder only appears in test mode
- raft_on_vote_request(): leader steps down on higher-term vote request
  instead of denying it

Tests: test_proto_H6_restored_vote_blocks_second_vote added; H1 restore
assertion strengthened; test_ha_vote_gate.c driven through last_activity +
primary_ctrl_fd (the real production fields). Full /xce-build all green
(lib + controller + scheduler + Go suites, 0 failures, ASAN clean).

deploy-ha.sh: start.sh now forwards extra args to the controller binary.
2026-06-10 01:25:39 +00:00
fa66d1ba9c finalize configuration replication 2026-06-09 16:40:13 +08:00
e56b2064de test(ha-heartbeat-liveness): SIGSTOP/SIGCONT tolerance integration test (task 4.5)
Add src/tools/test-heartbeat-tolerance.sh: automated bash script that
deploys a 3-node cluster and runs two SIGSTOP/SIGCONT scenarios:

  Scenario A (1 × heartbeat_interval_ms pause): verifies no RDHUP fires on
  standbys — liveness_poll_fn must not trigger failover for a short silence
  within the tolerance window (Req 2.2).

  Scenario B (heartbeat_timeout_ms + 500ms pause): verifies exactly one RDHUP
  fires on each standby — the activity-based poll fires after sustained silence
  and does not loop (Req 2.3, Req 5.4).

Add .kiro/specs/ha-heartbeat-liveness/manual-tests.md: step-by-step manual
procedure, expected log patterns, and results template for both scenarios.

Mark task 4.5 complete in tasks.md.
2026-06-03 04:18:56 +00:00
d00d3bb051 fix(ha): wire raft trace to DEBUG3; drop NOP on promoted ep; fix --vcpus in deploy script
- ha.c: add raft_cb_trace callback wired to s_real_cbs.on_trace; emits
  RAFT[pN] term=T event=E at DEBUG3 level, visible with -vvv flag.
- ha.c: repl_ack_decode_and_dispatch now silently drops OPCode_nop and
  OPCode_reply instead of WARNING — heartbeat NOP replies from standbys
  arrive on promoted endpoints and are expected.
- deploy-ha.sh: fix --vcpu -> --vcpus; add \$VERBOSE passthrough so
  start.sh respects VERBOSE=-vvv from the environment.
2026-06-01 05:20:13 +00:00
c8e0f65a39 enhance xce-build skill 2026-05-30 08:13:59 +08:00
3f74f14687 save works 2026-03-04 18:18:53 +08:00
acfbec875b save works 2026-03-03 16:49:26 +08:00
f4b1ba4cd7 save works 2026-03-02 12:14:30 +08:00
cf1dbdc07a add autoscaler and build for go code 2025-12-08 16:19:56 +08:00