Commit graph

1462 commits

Author SHA1 Message Date
7c229284e2
feat(scheduler-core): extract xce_sched_core_t from monolithic scheduler struct (task 4.1)
Splits xce_scheduler_t along the controller-I/O seam: xce_sched_core_t
(new core.h/core.c) now holds all scheduling state (jobs/queues/workers/
users/projects/resource managers/plugins/three-thread-cbuf pipeline),
while xce_scheduler_t shrinks to {core; apictx; cluster_cfg}. Proven via
an nm-based link-time isolation test (zero netio/xcecli/idgen/delaymgr
symbols reachable from core.c) plus a reproducible wall-clock-call guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dw5WnN3DXStaEaPYfACU1
2026-08-03 10:55:58 +00:00
604a564904
test(scheduler-core): audit data-sharing invariants across the picker rework (task 2.6)
Closes out group 2 (Core: Builtin FCFS Picker). Confirms builtin_fcfs's
ordering matches legacy fcfs_elect_job (byte-identical comparators, same
sched_queue_is_ready/sched_job_is_ready gates, provably equivalent jobid
sequence), confirms tasks 1.1-2.5 left every pre-existing test file
unmodified, and confirms resreq immutability, pend-reason factory-only
allocation, and select_eval_ctx_t memoization all hold across every file
this spec has touched so far. Adds a durable regression lock for the
pend-reason factory invariant (no direct calloc(pendreason_t) outside
make_pendreason()).
2026-08-03 09:35:21 +00:00
9d91d470b4
test(scheduler-core): guard core scheduling loop against ordering-logic regressions (task 2.5)
Task 2.4 already made sched_job_picker() delegate entirely to the picker
interface with zero direct queue/job traversal, satisfying Req 1 AC 1/AC 7
as a byproduct. This adds a source-text regression test locking that
property in place: asserts sched_job_picker()'s body contains none of the
forbidden direct-traversal symbols and does contain the expected
picker-interface call sites.
2026-08-03 09:11:55 +00:00
7ee9a16f53
feat(scheduler-core): wire builtin_fcfs as registered default picker (task 2.4)
Registers builtin_fcfs at scheduler startup and makes it the default
job_pick_plugin, replacing the hardcoded "xce_sched_fcfs" literal in
sched_job_picker() with a fail-loud lookup through sched_resolve_job_picker().
The picker thread now branches legacy job_elect_fn plugins (delegated
unchanged, preserving their self-contained wbuf-driven loop) from native
xce_job_picker_iface_t pickers (driven by a new per-session loop), and the
allocator now calls on_dispatch/on_pend for every picker as Req 1 AC 4
requires. Closes task 2.3's flagged picker/allocator concurrency race with a
mutex on fcfs_plugin_data_t, and adds cancel points to next_job().
2026-08-03 08:51:45 +00:00
fcb32a8b30 feat(scheduler-core): implement builtin_fcfs session and post-allocation hooks (task 2.3)
Adds begin_session/next_job/end_session/on_dispatch/on_pend, driving a
dual-cursor walk (queue then job) over builtin_fcfs's private ordq_t
state. next_job() returns candidates in (queue priority desc, job
priority desc, queue_time asc) order and NULL once exhausted;
on_dispatch removes permanently, on_pend is a true no-op.

Review flagged an unsynchronized cross-thread access risk between
next_job() (picker thread) and on_dispatch/on_pend (allocator thread)
once wired live in task 2.4 — recorded in tasks.md Implementation Notes
for that task to close before registering builtin_fcfs as the default.

Requirements: 3.2, 3.4, 3.6
2026-08-03 07:18:39 +00:00
b7214ebe88 feat(scheduler-core): implement builtin_fcfs state-change hooks (task 2.2)
Adds on_queue_update, on_job_push, on_job_state_change, and on_job_update
to builtin_fcfs, driving the picker's private ordq_t candidate sets
directly (no core/sched queries), matching fcfs.c's existing ordering
semantics on the new finer-grained hook interface.

Requirements: 3.2
2026-08-03 06:55:33 +00:00
lei.chen
07ab05671d fix(scheduler): put_resvals treats resource idx 0 as invalid → SEGV
put_resvals (resource.c) used `idxs[i] <= 0` to skip "invalid" resource
indexes, but index 0 IS valid — it's the first resource in the table
(e.g. "slots"). For idx 0 the val was never allocated (xvals[i] stayed
NULL) and the write loop then SEGV'd. bug-1495.

Fix: skip only negative indexes (`idxs[i] < 0`), and NULL-guard the
write loop defensively.

Verified end-to-end: a locally-built scheduler now connects to the t1
controller and dispatches all 5 test jobs to node t1 (exec_hosts
populated, jobs run and exit). This was the final blocker for job
scheduling (bug-1494: all jobs PEND).

Also: add the t1 host definition to hosts.yaml (worker join was rejected
with -EINVAL because no matching hostconfig existed for hostname "t1").

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-03 06:53:14 +00:00
f7921b961a feat(scheduler-core): implement builtin_fcfs lifecycle and private state (task 2.1)
New src/scheduler/src/plugins/builtin_fcfs.c implements
builtin_fcfs_init()/fini() and the private fcfs_plugin_data_t (ordq_t
queues, per-queue ordq_t pendJobs) that all later builtin_fcfs hooks
build on. fcfs.c (today's live job_elect_fn-based picker) is untouched
-- still production, until task 2.4 flips the registration.

picker == xce_sched_plugin_t* calling convention (private state stored
in plugin->data) confirmed consistent with task 1.2's legacy wrapper.

Requirements: 3.1 (context), 3.3
2026-08-03 06:03:49 +00:00
0d63a789a1 feat(scheduler-core): add picker configuration fields (task 1.3)
Adds xce_scheduler_t.job_pick_plugin (global picker name), and on
xce_sched_queue_t: project_name + job_pick_plugin (per-queue override,
NULL = inherit global). Adds xce_sched_project_t.priority (scaffolding
only, no quota logic). sched_set_default_job_pick_plugin() is added but
not yet wired into sched_init() -- task 2.4 owns that, alongside
replacing the hardcoded "xce_sched_fcfs" literal.

Requirements: 2.1, 2.2, 2.3, 2.4
2026-08-03 05:45:53 +00:00
4d8d72142e feat(scheduler-core): register picker plugin category + job_elect_fn compat wrapper (task 1.2)
plugin_register() now adapts an old-style job_elect_fn-only plugin to
xce_job_picker_iface_t via legacy_picker_wrapper_iface (next_job()
forwards to job_elect_fn; other hooks are safe no-ops covered elsewhere
in the existing lifecycle), logging a one-time deprecation warning. A
plugin that already sets picker_iface directly skips the wrapper and
the warning. Reuses the existing plugin_register()/key_find() mechanism
-- no parallel registration system.

Requirements: 1.6, 2.5
2026-08-03 05:23:53 +00:00
065025a3a6 feat(scheduler-core): define job picker plugin contract (task 1.1)
Add xce_job_picker_iface_t to plugin.h with all 13 hooks across
lifecycle/state-change/session/post-allocation/optional categories,
matching design.md's literal struct. Documents per-category thread
assignment, next_job() cancel-safety, and begin_session() session-reset
requirements. Forward-declares xce_sched_core_t (built in a later task).

Requirements: 1.2, 1.5, 2.6
2026-08-03 04:49:55 +00:00
lei.chen
62dee0edc1 fix(configs): add worker-builtin load indices missing from resources.yaml
worker/src/resource_sync.c init_builtin_load_indices registers 11 load
indices (r15s/r1m/r15m/ut/pg/ls/it/swp/mem/tmp/io) but resources.yaml only
defined r1m/r15s/mem/tmp. The 7 missing ones (r15m/ut/pg/ls/it/swp/io) caused
controller "update_resval: Success" ERRORs and scheduler "unknown resource in
per-worker resvals update" warnings, leaving the worker resource view
incomplete. Add the 7 definitions matching the existing r1m/r15s pattern.

Docs: log bug-1494 (all jobs PEND, scheduler never dispatches; after this
config fix jobs still PEND — remaining suspect is the scheduler→controller
join path after restart), update design doc + verification manual known-issue
tables and root CLAUDE.md cluster-verification status.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 07:36:03 +00:00
lei.chen
226259a52b fix(schedulerclient): parse apiserver OrderMap columnar rows (bug-1491)
The apiserver returns list rows in OrderMap columnar form —
{"Fields":["col",...],"Data":{"col":val,...}} — but the read clients
unmarshalled each row as a flat object, so every field came back empty
(counts were right, values were all zero/"").

Fix: decode rows as apiRow{Fields, Data map[string]json.RawMessage} and
pull named fields out of Data via rowStr/rowInt/rowInt64/rowFloat helpers.
ListNodes, ListQueues, and getJobs (ListJobs/GetJob) all use apiRow now.

TDD: 3 new ordermap_test.go tests built from live-captured apiserver
response shapes (red: fields empty → green: parsed). Live-verified against
t1: node name=t1 cores=8, 4 queues active, 3 jobs with real fields.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 05:01:16 +00:00
4153b35b22 chore(wolf): update buglog and session state
buglog.json: corrected bug-1480/1481 (stale "not yet fixed" entries, code
landed same day the entries were written); bug-1483/1484 fixed; bug-1485
investigated, downgraded to low-priority/environment-flake.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXkenK8rqE8mdS1YKjsq78
2026-08-02 04:37:36 +00:00
d683f5fc44 docs: add Jul27-Aug01 2026 project status report
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXkenK8rqE8mdS1YKjsq78
2026-08-02 04:37:28 +00:00
a5560256ea test(xceadmin): capture live HA E2E stderr instead of DEVNULL (bug-1485 investigation)
bug-1485 (flaky test_live_config_push_against_primary_reaches_success_path,
~24/25 fail rate under pytest, primary process going <defunct> after
promotion) could never be diagnosed because controller stderr was
DEVNULL'd -- no ASAN report or signal was ever captured from a real
failure.

Redirects each node's stderr to deploy_dir/logs/<name>.stderr.log and adds
an XCE_KEEP_DEPLOY_DIR=1 debug toggle to preserve the deploy dir across
fixture cleanup for inspection. Ran the live test 40 times in this
environment (25 idle + 15 under concurrent build contention): 40/40
passed, 0/120 captured stderr logs non-empty -- could not reproduce.
Downgraded to low-priority/environment-flake, left open; the capture
infrastructure stays in place for the next real failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXkenK8rqE8mdS1YKjsq78
2026-08-02 04:36:57 +00:00
8e75b0c10c test(ha): add term-adoption regression tests for heartbeat/WAL-NACK vectors (bug-1484)
bug-1421 added two term-discovery fallback vectors so a stale-term primary
steps down instead of retrying AppendEntries forever: heartbeat-reply
state-report term adoption and WAL-NACK reply_term adoption, both inside
repl_ack_decode_and_dispatch. Neither had a test driving the real
production dispatch path and asserting raft_on_higher_term_observed
actually fires.

Adds two tests mirroring test_config_repl.c's existing config-push term
test, driving peer_ctrl_msg_handler -> repl_ack_decode_and_dispatch via
wire-shape capnp packets. Both pass immediately -- the underlying bug-1421
code was already correct, just unverified end-to-end -- closing
raft-adapter-conformance's Requirement 7.5 unverified claim.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXkenK8rqE8mdS1YKjsq78
2026-08-02 04:36:46 +00:00
f16733ee61 fix(ha): free liveness poll ctx on failed/skipped delaymgr schedule (bug-1483)
ha_arm_heartbeat_timeout_vtask published a calloc'd liveness_poll_ctx_t
into s_liveness_ctx unconditionally but only ever freed it via
liveness_poll_fn's cancelled-check path, which never runs if
delay_task_ms was never called (c==NULL, delaymgr not running) or itself
failed. Both no-schedule cases now free the ctx immediately, establishing
the invariant that s_liveness_ctx is non-NULL only when a delaymgr task
actually holds a live reference to it.

Regression test drives a real LOST PreVote round without stubbing
ha_prevote_rearm_liveness_hook (every other LOST-path test in the file
stubs it specifically to avoid this code path) and asserts the ctx isn't
left dangling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXkenK8rqE8mdS1YKjsq78
2026-08-02 04:36:37 +00:00
lei.chen
4a4ae16725 docs(scheduler-agent): add Claude Code integration guide to verification manual
New §5 in src/docs/chatbot-verification.md: how to connect Claude Code to
the scheduler-agent MCP server. Covers building the mcp-server binary,
`claude mcp add` CLI registration (verified against installed CLI
v2.1.220), the .mcp.json project config schema, the three scopes
(local/user/project), the first-run approval gate, verification commands,
and stdio/env gotchas. Original §5-§7 renumbered to §6-§8.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-02 04:26:23 +00:00
lei.chen
56da673b22 docs(scheduler-agent): add cluster deployment + chatbot verification manual
New src/docs/chatbot-verification.md: reproducible steps to deploy the
scheduler-agent stack on the t1/t2/t3 test cluster (AppImage build, store
sync, controller/apiserver/scheduler/worker startup) and verify Phase 0-1
(login, mcp_operator API key, read ops, submit). Documents the vcpus quirk,
setsid launch pattern, conf/current pointer mechanism, and known bugs
(job-control HTTP 500 = bug-1490, HA election deadlock).

Also add the manual to the design doc's related-documents index.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 09:57:44 +00:00
lei.chen
9fe14c1512 docs(scheduler-agent): update design doc with cluster verification results
- Design doc v1.2: Phase 0-1 cluster verification status (read + submit OK,
  control HTTP 500 open bug-1490), bug table for whitelist/job_status fixes,
  mcp_operator queue:control note.
- Root + frontend CLAUDE.md: reflect verified state and apiserver bugs found.
- wolf memory/anatomy/buglog: log deployment session, bug-1489/bug-1490.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 09:54:49 +00:00
lei.chen
e36ed63ca0 fix(apiserver): apikeys whitelist + job_status type + mcp_operator queue:control
Three fixes found during scheduler-agent Phase 1 cluster verification:

- whitelist.go: add /xce/v1/apikeys to WhitelistPOSTURL (Phase 0 added the
  route but never whitelisted it → 403 "URL is not allowed"). bug-1489.
- jobs.go: fix job_status int64 assertion in job control. store.Jobs returns
  status as int/float64; the `.(int64)` assertion broke control with
  "job_status is not an int64". Add normalizeJobStatus() accepting int/int32/
  int64/float64/json.Number.
- rbac.yaml: mcp_operator role gains queue:control (the job-control handler
  also checks queue:control on the job's queue); add rbac_mcp_operator_test.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 09:54:44 +00:00
lei.chen
35416d5cf4 feat(scheduler-agent): Phase 1 write operations — submit/cancel/hold/release + templates
Add write operations to the scheduler-agent MCP server (Phase 1):

- schedulerclient: SubmitJob/CancelJob/HoldJob/ReleaseJob + JobSpec/
  SubmitJobResult/JobControlRequest/ControlResult types + doPost helper
  (HTTP 200 + envelope-code dual check). TDD: 12 new tests.
- mcp-server: 4 write tools (scheduler_submit/cancel/hold/release) with
  destructive annotations; scheduler_submit has dry_run review mode +
  general_serial (通用串行脚本) job template registry. Fix the 6 read-only
  tools' misleading destructive annotations. TDD: 14 new tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 09:54:38 +00:00
Lei Chen
2e299f4a76 docs: add Unified Scheduler Agent Framework design document
Complete design spec for the natural-language scheduler agent:
- Layered architecture (UI/LLM/Agent core/Adapter/Scheduler)
- Unified Resource Model (URM) types and principles
- SchedulerAdapter interface + per-backend strategy
- Language selection strategy (Go primary)
- MCP protocol tool set (read/write/advanced)
- Security model (write-protected by default, API Key auth)
- Event stream design with current-state research
- Implementation roadmap Phase 0-4
- Testing strategy with golden tests
- Design principles + ADR decision records
- Current progress status and blockers

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 12:18:39 +08:00
lei.chen
7e17c5e403 Merge branch 'working' into chatbot
# Conflicts:
#	.wolf/memory.md
2026-08-01 03:43:59 +00:00
lei.chen
b20f2abb27 chore(wolf): record rule — never push to Rongsong's branch without permission
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 03:41:47 +00:00
lei.chen
d6ae04f47f chore(wolf): log bug-1488, record merge session
- buglog: trim/mtime test failures (pre-existing, fixed in f2993d1)
- memory: merge of origin/working + capnpc override dedup

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 03:34:58 +00:00
lei.chen
f2993d1efc fix(controller): trim config versions by ns mtime; unique test tmpdir
Two pre-existing failures (2026-05-13, commit 491c255) surfaced by the
controller unit-test suite (1070 Tests 2 Failures), both in the config
version storage path:

1. conf_trim_old_versions sorted cluster.bin.* versions by
   second-granularity st_mtime. Versions saved within the same second
   (rapid replication, or the trim test's 15ms-spaced saves) tie, glibc's
   stable mergesort keeps creation order, and the newest (current-pointer)
   entry lands at the protected keep_n index — so nothing is pruned and
   keep_n+1 files remain. Store st_mtim (struct timespec) and compare
   tv_sec then tv_nsec.

2. cr_setup() built its tmpdir path from (pid, time(NULL)) at second
   granularity; a Unity assertion failure longjmps past cr_teardown(),
   leaking the dir, and the next test (same pid+second) silently inherited
   the leftover conf/current — cascading a second failure
   (test_conf_get_saved_tag_no_config read a real tag instead of 0). Add a
   per-invocation counter to the path so no two tests share a dir.

Verified: nix develop ./src --command bash src/tools/xce-build.sh all →
meson 9/9 Ok 0 Fail, controller 1070 Tests 0 Failures, xceadmin 172
passed, frontend Go all ok.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 03:34:55 +00:00
lei.chen
84fc46b5fe chore(nix): drop redundant capnpc override from flake.nix overlay
The merged origin/working commit e0c5e8b pins capnpc's source to an
immutable commit directly in src/nixSupport/xce.nix (bug-918 canonical
fix, verified with a full controller_unit_test build: 978 Tests 0
Failures). The flake.nix overlay from 98066db solved the same bug with a
ghfast.top-proxied fetchurl tarball; keeping both is redundant and the
proxied tarball hash differs from GitHub's git-archive. Keep the
fetchFromGitHub pin in xce.nix (idiomatic, no third-party mirror,
scoped to the build), drop the global pkgs overlay.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 03:04:10 +00:00
lei.chen
93ebea572f Merge remote-tracking branch 'origin/working' into working 2026-08-01 03:02:49 +00:00
b17d272f1d add wolf state 2026-07-31 17:42:34 +08:00
5e8a20178b add scheduler related projects 2026-07-31 17:42:13 +08:00
986474eb65 docs(config-upgrade-snapshot): add host admin-state frames, capnp migration, clear bug-1418 blocker
- Host admin-state (open/closed/locked) now in scope for the upgrade
  snapshot; host and queue admin-state frames migrate to independently-
  extensible Cap'n Proto structs (one format-version bump covers both)
- bug-1418 blocker cleared: fixed 2026-07-09 via raft-apply-decoupling,
  independently re-verified 2026-07-23 with a clean 10-round chaotic soak
- New Design Invariants section (6 standing rules) and Requirement 7
  (regression coverage for host-placeholder/rejoin, overlapping upgrades,
  format-mismatch rejection, WAL-trim-under-chaos)
2026-07-31 09:32:24 +00:00
d968bbd722 chore(wolf): update session memory, anatomy, and token ledger 2026-07-31 09:29:48 +00:00
d4b80010b1 docs(raft-adapter-conformance): commit final design.md/research.md and requirements/spec.json state
Spec reached GO with 15/15 tasks complete (see prior task-confirmation
commits); these doc artifacts were generated during the design/research
phases but never separately staged.
2026-07-31 09:29:35 +00:00
a86de77f91 fix(waldump): decode WAL_CONFIG_MARKER, distinguish trimmed gaps from real loss
wal_type_name() never had a case for WAL_CONFIG_MARKER (type 50) -- every
config-change marker entry printed as UNKNOWN instead of a real, defined
type. Added the case plus payload decode (tag/sha256) matching config_repl.c's
config_marker_encode/_decode byte layout, hand-decoded here rather than
linking config_repl.c into xce_test's link graph.

Gap detection also had no way to tell "committed prefix missing!" (real
loss) apart from a normal checkpoint-triggered WAL trim (persist.c's
trim_wal_segments, floor-guarded so it never removes a segment a standby
still needs) -- every long-running node's WAL, once checkpointed, would
false-alarm on every inspection. Cross-checks the sibling data/snap/
directory (snapshot LSN is literally in the filename, snap-<hex>.capnp)
and downgrades the message when a snapshot covers the gap.

Adds waldump_stats_t.leading_gap_explained and 3 new tests plus one
assertion on the existing gap test.
2026-07-31 09:05:22 +00:00
0ca96359f5 docs(deployment): note WAL/snapshot retention is automatic
trim_wal_segments + rotate_snapshots run on every checkpoint --
operators don't need to manually purge data/. Documents the
snap_keep_n=2 default and that it's a cluster-config field, not a CLI
flag.
2026-07-31 08:48:41 +00:00
9ed80c4a7c docs(deployment): explain why the built xceadmin venv needs nix develop
--system-site-packages means pip skips vendoring click/pycapnp/pyyaml/
jsonschema since nix already provides them -- deliberate, to keep
pycapnp's ABI-sensitive build pinned to the exact capnproto C library
nix built it against. Activating the venv outside nix develop (or the
AppImage) hits ModuleNotFoundError immediately; not a packaging bug.
2026-07-31 08:40:28 +00:00
34bd934bc7 wip(admin-config-tool): task 6.3 HA E2E test (blocked, flaky primary-push)
Standby-redirect half is done and correct: OP_upgradeConfig has no
server-side redirect wiring today, matches design.md's own documented
"Out of Boundary" gap, and this test locks in the real current
rejection behavior.

Primary-push success-path half is unreliable (~24/25 live fails in one
investigation round, see bug-1485) -- the elected primary was observed
as a <defunct> zombie shortly after promotion, but a dedicated debug
pass could not reproduce it in 26+ clean runs on an idle host and
captured no crash evidence either way. tasks.md marks 6.3 _Blocked:_
pending re-verification on an idle host with real stderr capture.
2026-07-31 08:09:36 +00:00
0206b9e166 fix(xce-test-ha): retry chaotic-mode verify_classify before counting job loss (bug-1487)
30-round chaotic soak reported committed_job_loss=1 at round 25, but the
flagged job was confirmed present on every later verify and the final
sweep -- a real loss never comes back. Root-caused from ctrl-1/ctrl-2/ctrl-3
logs, no controller change needed: a newly-elected primary cannot fold a
prior-term entry into its own committed_lsn merely because it holds it
durably (Raft Figure-8 safety via ha_arm_own_term_gate) -- it needs a fresh
majority ack for its own-term WAL_NOOP first. That normally closes in
<200ms, but chaotic mode's overlapping-restart design let it stretch to
6.232s this run (the peer whose ack was needed was itself mid-catch-up from
its own recent restart). verify_classify ran its expect-job check
immediately on primary detection with no settle/retry, unlike verify_no_loss
(bug-1422) or wait_for_full_convergence, so it landed inside that window.

Added a bounded retry (5 attempts, 2s settle) around the LOSS classification
only, mirroring verify_no_loss's precedent -- a job still missing after that
still counts as a genuine loss. Verified with a fresh 30-round chaotic
re-soak: committed_divergence=0 committed_job_loss=0 result=PASS.
2026-07-31 05:16:14 +00:00
4835cf2b30 fix(security): replace exposed admin credential with placeholder
rshen/Letmein was a live, checked-in cluster_admin credential in
auth.yaml. Renamed to a non-identifying username with a clearly-marked
placeholder password.

docs: add full ops deployment guide (src/docs/deployment.md)

Covers building and running all four daemons plus xceadmin, based on
direct source verification (CLI flags, config file shapes, TLS posture)
and a live single-node deploy. Documents real production gaps (no
process supervision, plaintext non-controller traffic, exposed dev
creds) rather than treating the repo as a finished runbook.
2026-07-31 03:31:23 +00:00
9232d8fa6e docs(admin-config-tool): mark task 6.3 blocked pending flake reproduction
Standby-redirect coverage is done and correct (redirect wiring for
OP_upgradeConfig genuinely doesn't exist server-side yet, matches
design.md's own documented gap). Primary-push success-path test is
flaky (bug-1485) -- a dedicated debug round could not reproduce the
crash in 26+ clean runs, so root cause is unconfirmed. Test files left
uncommitted in the worktree pending re-verification on an idle host.
2026-07-30 14:57:32 +00:00
1075f4529a test(admin-config-tool): add live single-node E2E test for config push
Deploys a real xce_controller (no HA peers) and runs real xceadmin
config build/push over actual TLS+capnp -- no mock server -- regression
locking task 6.1's envelope fix. Skips cleanly when no controller binary
is available. Surfaced a real, out-of-scope finding: single-node pushes
always report ackedCount==0/exit 1 even on full server-side success,
since there are no standbys to ACK.
2026-07-30 10:21:04 +00:00
915e6fdf64 fix(admin-config-tool): unwrap Request reply envelope before decoding as RequestReply
config push crashed on every real controller with a capnp KjException --
xce_reply() wraps every reply in an outer Request{body.replymsg} envelope,
but discovery.py/push.py decoded raw wire bytes directly as a bare
RequestReply. Adds a shared decode_reply() unwrap point used by both call
sites, and fixes the same wrong-shape assumption baked into task 5.6's
and test_cli.py's mock-server fixtures.
2026-07-30 09:43:56 +00:00
46bbdaa9a6 docs(raft-adapter-conformance): confirm task 7.4 live soak (vote/PreVote post-connect failure)
Found a clean, reusable fault-injection technique: vote/PreVote
endpoints are TLS, and endpoint_connect() succeeds at the TCP level
independent of the handshake, so an accept-then-close impostor
listener on a peer's port deterministically causes an async
SSL_do_handshake failure post-connect -- landing in
vote_ep_err_handler/pre_vote_ep_err_handler (task 3.1's fix target),
never the already-fixed bug-1423 connect-refused path.

Independently reproduced by the reviewer from scratch: both a
RequestVote round and a PreVote round resolved via the fast
all-replied-no-quorum path in single-digit milliseconds, vs the
5000ms configured election timeout (confirmed firing later as a
no-op). Requirements 4.2, 4.3, 4.5 proven live, both vote kinds.

All 15 raft-adapter-conformance sub-tasks now complete. 1067 tests,
0 failures, 8 commits' worth of production fixes (config-push reply
integrity, vote/PreVote failure routing, dormant-sender
documentation), plus 6 regression-lock/confirmation/live-soak tasks.
Two residual gaps logged as new bugs during the sweep (bug-1483,
bug-1484), both pre-existing and out of this spec's scope.
2026-07-30 08:01:35 +00:00
a48ed5c018 docs(raft-adapter-conformance): confirm task 7.3 live soak (config-push rejection)
Live 3-node cluster soak confirmed handle_repl_config_push's SHA-256
mismatch reject branch fires in production: 3/3 injected mismatched
pushes against a live standby produced a decoded
ReplConfigPushReply{accepted=false, term=<live raft term>, status=0},
seq-correlated, corroborated by the standby's local WARN log. Cluster
stayed healthy throughout (no crash/step-down/fence).

One rejection round: the real elected primary's own push has no
TOCTOU window to inject a mismatch without new relay code (confirmed
by reading config_commit_change/push_config_to_standbys), so the soak
plays the sender/primary role directly against a standby instead --
remediated from log-inference-only to actual decoded wire-reply
verification per reviewer's cheap-fix path.

No production/test code changed -- pure live observation.
2026-07-30 05:48:28 +00:00
8b820055ee docs(raft-adapter-conformance): confirm task 7.2 regression coverage, log bug-1484
Verified 190 tests across 8 files cover Requirements 2.4, 3.1-3.4,
4.1, 4.4, 7.1-7.3, 7.5, 8.1-8.4. 1067 tests, 0 failures -- unchanged,
zero-diff verification task.

Task 7.2's sweep surfaced a real, pre-existing gap (bug-1421, not
this feature's fault): no test anywhere drives the real
repl_ack_decode_and_dispatch path and asserts term adoption fires for
the heartbeat-reply or WAL-NACK term-discovery vectors -- only the
config-push vector this feature added (task 2.2) is actually tested.
Logged as bug-1484; outside this spec's task list to fix.
2026-07-30 04:26:25 +00:00
e51fa7b20f docs(raft-adapter-conformance): confirm task 7.1 regression coverage (no code change)
Verified all 17 cited tests in test_commit_barrier_rollout.c,
test_commit_barrier_reply.c, and test_term_safety_adapter.c exist and
pass, covering Requirements 5.1, 5.2, 5.4, 6.1-6.4. 1067 tests, 0
failures -- unchanged, as expected for a zero-diff verification task.
2026-07-30 03:58:13 +00:00
52ae598dfe docs(raft-adapter-conformance): extend controller conformance record with this feature's findings
CLAUDE.md's TLA+ Action-to-Code Mapping table gains corrected/new rows:
production vs. dormant AppendEntries send mechanisms, WAL catch-up and
config-push reply integrity, and vote/PreVote failure routing across
all failure timings -- each citation independently re-verified against
live source. The old AppendEntries row's citations had already drifted
onto unrelated code before this task; corrected, not just appended to.
Protocol Verification Checklist gains 3 new items for the guarantees
this feature closes.

ha-term-safety/spec.json's known_limitations entry for the dormant
send-path drift gets an additive RESOLVED pointer back to this spec's
disposition, so a future reader lands on the answer instead of
re-discovering the drift.

Requirement 1 (systematic conformance audit deliverable) complete.
2026-07-30 03:39:56 +00:00
396d90a85e test(raft-adapter-conformance): batch payload persist-failure propagation regression lock
Adds test_batch_payload_persist_failure_propagates_to_whole_batch,
proving persist_submit_job_payloads_async resolves every payload in a
3-entry batch with the same first-failure rc when the first payload's
append fails, rather than letting later payloads independently
succeed (Req 5.3).

Fault injection: chmod the WAL leaf directory to 0500 to force
open_new_segment's EACCES (surfaced as -EIO), restored the instant the
first failure is observed -- a transient fault, not a permanent one,
so the test genuinely distinguishes propagation from independent
per-payload failure. No existing fault hook was reachable from this
code path; this is a real, previously-uncovered gap.

1066 -> 1067 tests, 0 failures. Task 5 (regression-lock tests for
already-shipped/already-conformant behavior) now complete -- Core
phase of raft-adapter-conformance finished (tasks 1-5).
2026-07-30 03:20:22 +00:00