Commit graph

2188 commits

Author SHA1 Message Date
258267fa48
feat(scheduler-sim-decision-trace): capture shortfall and limit rows from the satisfaction check
Task 2.2: each checked row carries name, flags, value type, total,
in-use, reserved, requested and met (same comparison as unsatisfy_());
limit rows resolve through the scope's matched limits and never count as
shortfalls; the cluster-wide check keeps every row, a host only unmet
rows, which set its shortfall/limit verdict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 09:35:00 +00:00
90d417bb1b
docs(buglog): bug-1924 clock test fails when the test PID contains 1999
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 09:31:10 +00:00
54f9139e77
feat(scheduler-sim-decision-trace): capture selection facts, cluster-wide limits and attempt reasons
Task 2.1: policy, queue and priority/queue-time ordering keys, each
matched cluster-wide limit as (name, scope), and per-code attempt
failure reasons with the hosts and resources they name, all copied into
the record arena.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 09:08:23 +00:00
a801aff46f
feat(scheduler-sim-decision-trace): core trace gate and SCHED_TRACE hook macro
Task 1.8: the core gains trace_ctl/trace_rec (NULL unless the sim REPL
sets them) and SCHED_TRACE evaluates its call only while a record is
being built.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:53:40 +00:00
29b6734649
feat(scheduler-sim-decision-trace): trace attachment and trace-only op on decisions
Task 1.7: xce_decision_t gains a trace pointer (NULL from every existing
constructor) and XCE_DECISION_TRACE with make_trace_decision(); the live
driver skips trace-only decisions and frees any attached trace.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:40:37 +00:00
320f88a432
feat(scheduler-sim-decision-trace): compile trace sources into white-box tests of hooked files
Task 1.6: the 27 tests that #include a hooked scheduler source now also
include sched_trace.c and sched_trace_capture.c.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:29:56 +00:00
4ae68998ce
test(scheduler): silence snprintf truncation warning in repl trace baseline cleanup
remove_workdir() now checks snprintf's result and skips a truncated path
instead of unlinking whatever it happens to name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:28:22 +00:00
d277bfb657
feat(scheduler-sim-decision-trace): plan/verdict counters, search-end, summary tallies, rollback merging
Task 1.5: per-requirement plan counts and search-end statements,
decision-wide counters exact past truncation, summary-level position
tallies (per verdict kind and per limit), merged rollback entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:21:57 +00:00
e06bdc290b
docs(buglog): bug-1923 fixed (16616d96), synced to Gitea #348
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:11:37 +00:00
16616d9688
fix(scheduler): load shared resource values from the snapshot (bug-1923)
xce_scheduler --sim started with no shared resource pool when the shared
values came only from the recording's SnapshotEntry (license value=(none)),
so jobs needing a shared resource pended instead of dispatching. Two
defects caused it:

- Load side (snapshot_load.c): a resval was treated as shared only when
  rv->host == NULL. The capnp round trip (write_snapshot_entry ->
  decode_SnapshotEntry) reads a NULL host back as "", so every shared
  resval in a .replay file was skipped. A NULL or empty host now means
  the shared pool; only a named host marks per-worker data.
- Save side (snapshot.c, snapshot_wire.c): core->sharedInst and each
  worker's resvals were walked with i < .count. zarray_t.count is the
  number of filled slots, not the highest idx + 1, and both arrays are
  sparse by resource idx, so higher-index values were dropped from the
  snapshot. Both loops are now bounded by .caps, as repl_info.c already is.

New test test_snapshot_sparse_resvals.c covers the in-memory save/load,
the empty-host load, and the real sched_record_start() -> .replay ->
sched_sim_driver_start() round trip.

Regenerated fixtures/repl_trace_baseline/set_output.replay: its mid-run
snapshot now carries license, limit=cluster_slots and each worker's
limit=host_slots, which the old writer dropped (all other fixtures
unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:10:00 +00:00
4f951f2b52
feat(scheduler-sim-decision-trace): trace entry sequence and line budget
Task 1.4: requirement path push/pop with balanced begin/end entries
carrying the full nesting path, one entry per traced event kind, and a
line budget that stops storing at the bound and counts omitted lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:05:44 +00:00
383e2a90e4
docs(wolf): cerebrum learnings (bug-1923 snapshot resvals, build dir) and memory log
Also carries earlier uncommitted cerebrum entries (gitea milestones filter,
standalone ASAN sim leaks, bug-1883 ctx vectors, gofmt vendor rule) and the
hook-maintained memory.md log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 07:51:14 +00:00
7d3c71fc34
feat(scheduler-sim-decision-trace): trace record model foundation
Task 1.3: trace types and constants, per-record arena, job selection
matching, no-decision record finishing; empty capture module; both
sources scanned by the core isolation test with a no-stdio check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 07:49:09 +00:00
7f40ff27dc
feat(scheduler-sim-decision-trace): move units and value formatting helpers into repl_fmt.c
Task 1.2: translate_units_mode, repl_format_numeric_resource_value,
repl_format_consumable_value_and_used and repl_current_units_mode move
unchanged from repl_info.c and are declared in repl_internal.h so the
trace renderer can reuse them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 07:30:42 +00:00
ce757eaaa1
docs(buglog): record Gitea sync for bug-1923 (#348)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 07:24:40 +00:00
e2d5662d28
feat(scheduler-sim-decision-trace): capture the trace-off REPL transcript baseline
Task 1.1: scripted REPL session over a contention replay (license, cluster
and host limits, parallel and alternative jobs); transcript, recordings and
--sim-verify result committed as fixtures and compared byte for byte.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 07:21:38 +00:00
bf1e4b7245
docs(buglog): bug-1923 sim drops shared resource values from the recording snapshot
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 07:18:37 +00:00
ed201686b6
docs(spec): scheduler-sim-decision-trace design, research and tasks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 06:48:57 +00:00
33437a392e
docs(wolf): STATUS.md -- PR #347 open into develop
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:10:41 +00:00
99f479082a
docs(wolf): STATUS.md -- bug-1736 exposure, bug-1914..1922, soak gates; merge pending
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:08:20 +00:00
145e41d8e0
docs(buglog): record Gitea sync for bug-1917/1919/1920/1922
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:04:11 +00:00
8dab1f2065
docs(buglog): bug-1922 fixed; bug-1917 follow-up; soak coverage for bug-1919/1920
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:03:27 +00:00
5cf923350c
test(soak): WAL segment-layout gate and --truncated-fence scenario
- wal-layout-check.py: every WAL segment must hold one contiguous LSN run
  starting at the LSN in its name (bug-1920 signature); the sequential,
  chaotic and truncated-fence modes fail on a misfiled segment.
- --truncated-fence: breaks ctrl-1's WAL tail CRC so it boots
  RECOVER_TRUNCATED, makes it win an election (promotion REFUSED), then
  requires the fence to be healed and released after catch-up and the
  node to lead with zero job loss (bug-1919 lifecycle, proven from logs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:03:09 +00:00
c06798b2fa
fix(harness): xce_test fake worker answers the controller's job-state query (bug-1922)
The worker-rejoin-resync query sent when a worker with live jobs re-joins
had no handler in xce_test's worker context, so the controller resolved
every such round by its 15 s timeout, withholding dispatch meanwhile.
New fake_resync.{c,h} reassembles the paginated query and builds the
report like xcecd (queried jobs in order, running or unrecognized, then
unqueried running jobs); the handler sends it with a non-blocking request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:03:09 +00:00
f26a2d4586
fix(lib): reconnect from an endpoint's own task neither waits nor leaks old replies (bug-1917)
A reconnect made from inside a task of the endpoint it re-establishes (a
request handler that failed over) waited out the full 2 s quiesce bound:
its own reference cannot drain, and the old connection's queued tasks are
serialised behind it -- and those then ran after the owner was re-armed,
reaching the new session.

Each VTASK_IO task now records the endpoint's reconnect_gen when queued and
vtask_entry drops a task whose generation is stale; the vtask manager
tracks the endpoint whose task the current thread runs
(vtask_current_io_ep), and xce_ctx_quiesce_ep skips the wait in that case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 03:03:09 +00:00
6a42631b2a
docs(buglog): record Gitea sync for bug-1921
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 14:43:22 +00:00
77ca96ab63
docs(buglog): bug-1921 fixed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 14:42:28 +00:00
70d56a5df6
fix(harness): xce_test fake worker defers handler notifications and re-joins after a reconnect (bug-1921)
Worker-context handlers (startjob, runtasks, jobcontrol) run as tasks of
the worker's own connection, so their synchronous notify RPCs waited for
a reply queued behind themselves, timed out (-110) and failed the client
over; the session never joined again, so the controller rejected every
later worker request ("is not a worker") and job usage stayed zero.

Handlers now queue the notification (new worker_notify.{c,h} FIFO) and a
one-shot delay-manager task sends it; housekeeping retries anything left.
A recorded worker join is repeated with the saved attributes once the
connection's reconnect_gen changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 14:42:28 +00:00
12f5eb2a76
docs(buglog): record Gitea issues for bug-1736/1917/1921
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 13:55:45 +00:00
07d81fc5bc
docs(buglog): bug-1736 exposed to apiserver/xcecli; log bug-1921
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 13:54:53 +00:00
1cedd7f791
fix(controller,apiserver,xcecli): expose job CPU peak and efficiency to the apiserver and xcecli (bug-1736)
The controller computed run usage (CPU time, memory, CPU peak and
efficiency, LSF bjobs -l definitions) but the capnp Job struct had no
field for it, so the job pushed to the apiserver never carried it, and
snapshots (the same struct) dropped it: a job restored from a checkpoint
lost its accumulated usage.

- controller.capnp: Job.runUsage @13 (JobRunusage); old entries decode
  with no run usage.
- apiserver: map run_usage into JobInfo; max_mem, avg_mem, used_mem,
  cpu_time, peak_cpu, peak_cpu_time, peak_cpu_duration, mem_efficiency,
  avg_cpu_efficiency, peak_cpu_efficiency become queryable columns.
- xcecli: -o fields MAX_MEM, AVG_MEM, MEM_EFFICIENCY, CPU_PEAK,
  CPU_PEAK_DURATION, CPU_EFFICIENCY, CPU_PEAK_EFFICIENCY (CPU_TIME now
  renders too), and a MEMORY USAGE / CPU USAGE block in jobs list -l.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 13:54:43 +00:00
e2472aa73c
docs(buglog): record Gitea issues for bug-1914/1917/1918
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:00:25 +00:00
8e66d463ea
docs(buglog): bug-1914, bug-1917, bug-1918 fixed; refresh STATUS.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 23:59:29 +00:00
06e99b9081
fix(controller): size the config-push barrier for loaded hosts and admit its wait (bug-1914)
The configAck byte-delivery barrier waited 500 ms, below scheduling
delay on an oversubscribed host, so a push to a healthy majority was
rejected with -110 while its already-appended marker still committed.
CONF_PUSH_TIMEOUT_MS is now 2000 ms, the wait holds a commit-wait
admission slot (new commit_blocking_admit/release; a full budget fails
fast with not-ready), and the timeout reply no longer claims the change
was not committed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 23:59:29 +00:00
431a6d709e
fix(lib): quiesce the old endpoint before xce_ctx_reconnect re-inits it (bug-1917)
Work dispatched for the old connection (the io_worker's epoll batch,
queued VTASK_IO reply tasks) could still run while the reconnect tore
down and re-armed ctx->ep; a queued reply of the old connection then
reached the new session. The reconnect now unmonitors the old fd,
clears ep->owner (stale tasks are dropped) and waits for the endpoint's
references to drain, bounded at 2 s so a caller that is itself one of
those tasks cannot deadlock. vtask_entry reads ep->owner under ep->lock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 23:59:29 +00:00
a74b684c48
fix(lib): keep a refused inbound packet endpoint-owned instead of leaking it (bug-1918)
INVOKE_MSG_HANDLER stored NULL into ep->fsm_in.pkt after every msg
handler. Handlers that take the packet clear the slot themselves
(vtask_handle_msg, peer_ctrl_msg_handler); one that refuses it
(-EINVAL / -ENOMEM) leaves it there, and the NULL store dropped the
only reference. The slot is now left as the handler leaves it: a
refused packet is reused for the next message or freed by endpoint_fini.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 23:59:29 +00:00
e27bc2ed3a
docs(buglog): record Gitea issues for bug-1915/1919/1920
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 13:09:10 +00:00
8e97b11929
docs(buglog): bug-1920 fixed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 12:02:37 +00:00
762c6f1883
fix(controller): refuse replicated WAL writes before this boot's recovery (bug-1920)
A restarted standby accepts the primary's connection during its startup
delay, before persist_recover runs. Catch-up entries written into the
un-recovered persist_t opened seg-...0001 with O_TRUNC and rewrote it,
and after recovery the stale fd kept filing new LSNs under that segment
name. The primary's filename-ordered streamer then skipped them, so a
standby needing that range never caught up and commits stalled.

persist_wal_write_replicated now returns -EBUSY until recovery completes
(the bug-1416 recovery_complete gate). ha_apply_wal_catchup_entry and
ha_handle_snapshot_chunk drop pre-recovery traffic without ACK or NACK.
Catch-up streams from min(NACK-derived join position, standby report),
and the streamer warns when a non-empty range yields no entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 12:02:37 +00:00
f01bb7de34
docs(buglog): bug-1915 and bug-1919 fixed; log bug-1920
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 09:14:58 +00:00
0ab12074f5
fix(controller): release the promotion fence once a truncated-boot node has caught up (bug-1919)
The fence re-check required !persist_recovery_truncated(), a boot-time
verdict nothing cleared, so a node that booted RECOVER_TRUNCATED stayed
fenced (fenced: true, non-promotable) until restart however far it
caught up. And the "progress is possible" branch did not re-arm, so the
fence was never re-evaluated.

persist_t gains a runtime recovery_truncation_healed latch, which
persist_recovery_truncated() honours (the raw boot verdict stays
available). The re-check marks the truncation healed once the durable
floor has advanced past the fence-time baseline (entries replicated from
the live primary) and reached the HWM, and the progress branch re-arms
with the same baseline and HWM.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 09:14:48 +00:00
88a8bf8a47
fix(controller,lib): decide each PreVote round once and promote only with this election's votes (bug-1915)
A late PreVote grant after WON re-ran the WON path and started a second
real election that replaced elect_ctx, so the term-2 leader promoted no
vote endpoints. Rounds now carry an id and endpoints a round/kind; stale
or wrong-kind replies, timeouts and errors are dropped; raft ignores
replies with no round in progress. raft_cb_become_leader promotes only
with its own stamped election record (own term, majority), else steps
down and re-arms. raft_on_leader_lost keeps voted_for (one vote per
term) and the delayed election starts only as a follower in the lost
term. The failover stagger hands the PreVote start to a vtask so the
delaymgr stays trigger-only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 09:14:38 +00:00
acf78f9a94
docs(buglog): bug-1913 and bug-1916 fixed; log bug-1915, bug-1917, bug-1918
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 23:18:53 +00:00
0fb2e14cc7
fix(lib): detach the received packet before handing it to a vtask (bug-1916)
vtask_handle_msg published the VTASK_IO task while ep->fsm_in.pkt still
pointed at the packet; INVOKE_MSG_HANDLER clears the slot only after the
handler returns. A vtask could decode and free the packet (xce_decode)
and wake the client, whose failover reconnect then ran endpoint_fini and
freed the dangling slot again: ASAN heap-use-after-free in free_packet,
seen in the worker and scheduler during failovers under heavy CPU load.

vtask_handle_msg now clears ep->fsm_in.pkt before vtask_enq and marks
the task the packet's sole owner (owns_io_pkt), so never-run tasks free
it at vtaskmgr_fini and an enqueue failure frees it instead of leaking;
xcectx vtask_entry frees it on its early -EINVAL exits. Same take-and-
clear discipline the controller's peer-control path already used.

Tests: test_vtask_pkt_handoff.c (3), RED reproduces the soak's ASAN stack.
libxce 733/0; xce-build all green; loaded /xce-test-ha soak (8 rounds,
40 spinners) PASS with no ASAN reports (it crashed 2/2 before).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 23:18:22 +00:00
2aefa939db
fix(controller): probe a late-joining standby at once so its catch-up starts without waiting a heartbeat (bug-1913)
After an election where the vote connections are not promoted, both
standbys register later via on_standby_connected; the own-term no-op was
appended before any standby existed, and catch-up waited for heartbeat-
driven state reports (config push one 3 s cycle, WAL stream the next), so
the no-op committed ~6 s late and every client write in that window
failed -110.

ha_send_catchup_probe() sends the ordinary heartbeat request (same fields
as primary_heartbeat_fn; encode + enqueue only; primary only) at
registration and once after the standby's first accepted config-push ACK,
so the decision loop's next action follows at ~RTT. The decision loop,
its one-action-per-report pacing, commit/majority rules and the dispatch
gate are unchanged; WAL streaming still runs only on the vtask. The
one-shot arm is cleared on disconnect and on every become_leader /
become_follower.

Tests: test_late_joiner_catchup_kick.c (6). Controller suite 1285/0.
Live: catch-up starts 0-6 ms after a late registration (was up to ~6 s);
/xce-test-ha sequential 20 rounds, chaotic 6 rounds and sequential 8
rounds under 40-spinner load all PASS with zero job loss.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 23:18:10 +00:00
6d9ef3d024
docs(buglog): log bug-1914 config push -110 under heavy CPU load
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 15:08:01 +00:00
4cd5fb7dd8
docs(buglog): bug-1683, bug-1485, bug-1725 fixed with corrected root causes; log bug-1913
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 14:51:27 +00:00
362098b12a
fix(xceadmin tests): root-cause the three flaky tests (bug-1683, bug-1485, bug-1725)
- bug-1683 (test_wire known-good capture): the client closed its TLS
  socket with the server's TLS 1.3 NewSessionTickets unread, so the kernel
  sent RST and the server lost the unread payload record; the server
  swallowed the OSError. The client now waits for the capture to finish,
  handler errors are recorded and asserted, and a 50 ms "tickets-pending"
  variant makes the RST race deterministic.
- bug-1485 (live HA push, versionTag=0): the CLI's 0 was an error reply
  (-110): after a late-joiner election the primary's own-term no-op only
  commits ~6 s later via heartbeat catch-up, so a push in that window
  misses the majority barrier. The fixture now waits until the latest
  own-term no-op has committed (wait_for_primary_write_ready); versionTag
  asserts print the CLI's stdout/stderr. The controller latency itself is
  bug-1913.
- bug-1725 (activation line missing): "Config upgraded" and the
  activation line are logged after the reply; both tests now poll for all
  three commit lines (shared assert_primary_committed).

xceadmin suite 373 passed; live push tests looped (95 runs incl. heavy
CPU load, 0 failures from these mechanisms).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 14:49:46 +00:00
91782e4089
docs: bug-1909..1912 fixed; controller-peers-env-fallback gap notes resolved
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 13:07:37 +00:00
ef6e562cde
fix: close controller-peers-env-fallback validation gaps (bug-1909..1912)
- bug-1912: xce_parse_peers() ignores whitespace around entries and skips
  blank ones, matching xceadmin's resolve_controllers(), so "a=h:1, b=h:2"
  names the second candidate "b" (5 libxce tests + regression case).
- bug-1911: xcecd frees the -l log dir before the -h / --version returns
  (regression case: no LeakSanitizer report, exit 0).
- bug-1910: both version scripts build the apiserver with -mod=vendor
  (as the meson build does) instead of -mod=readonly, so they no longer
  need a Go module cache.
- bug-1909: test-version-startup-consistency.sh accepts exactly the
  dirty-content-fingerprint pairing ('<sha>-<8 hex>' in --version with
  '<sha>-dirty' in the startup log), with self-test cases (e)/(f).

Both version scripts now exit 0 on a dirty tree with XCE_CONTROLLER_PEERS
set; regression script 35/35; xce-build all: C 127/0, scheduler 16/0,
xceadmin 364 passed, no ASAN reports.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 13:06:58 +00:00