Commit graph

181 commits

Author SHA1 Message Date
3457d892b6
feat(job-array-one-based-index): query handlers evaluate element selections (task 7.3)
GetJobsInfo validates every filter entry in request order before any
store access (including the exec_hosts lookup) and GetJobByIDs every key
before querying; the first rejection returns 400 with the parser's
message. Bracketed job_id/job_name entries are evaluated as row
predicates before sorting and pagination, so job_id:eq:X[2] lists only
element 2, GET /jobs/X[1-3] lists elements 1-3 and <ordinary-id>[0]
lists nothing. The unevaluated compound conditions and the old index
parsing are gone from both handlers. Fixes bug-1898 on the query paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 12:21:11 +00:00
675d7d7f6c
feat(job-array-one-based-index): selection predicate builder (task 7.2)
BuildSelectionPredicates converts each job_id/job_name filter entry
containing a bracketed value into one row predicate (values OR'ed,
entries AND'ed with each other and the plain conditions), removing only
that entry. Operators are honoured instead of dropped (bug-1898):
eq/in/between select, notin excludes, name pattern operators reuse
today's condition semantics on the name part, and ne / comparison /
null operators keep today's rejection. Element selections match only
array elements in range; bare identities match the whole array and any
ordinary job; an empty name under exact operators matches nothing.
Bare written keys are normalised to decimal on every path. Not yet
wired into the handlers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 12:09:47 +00:00
2a8490bc92
feat(job-array-one-based-index): REST identity and name selection parsers (task 7.1)
ParseIdentitySelection (strict written form or base-10 decimal key,
optional [k] or [a-b]) and ParseNameSelection (name part may be empty,
no whitespace or brackets) apply the shared selection grammar and check
order (5.16 syntax, 5.14 width, 5.17 marked decimal without [k], 5.8
alignment only with [...], 5.7 element bound, 5.5 reversed), reporting
only the first failure with a rule tag and a message quoting the
selection. Runs the shared selection-cases table plus REST-only rows.
Not yet wired into the handlers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 12:01:22 +00:00
c30e5f1d82
feat(job-array-one-based-index): REST submit creates arrays numbered from 1 (task 6.2)
preCheckJobRequest returns (n, isArray, err): a bracketed job name is
parsed by ParseSubmitIndexExpr first, so name[1-N] and name[1] create
arrays (one-element arrays included) allocated with
generateArrayJobID, stored under the bare name; expression errors are
answered 400 with their specific message and create nothing; names
without brackets take the unchanged createArrayName path. The response
names the array (jobid = array ID, new array_size, "(n elements)"),
submit-time %I/%J use the first element, and the submit and dispatch
logs identify arrays by array ID. Fixes bug-1896 for REST and xcecli.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:55:39 +00:00
0318ac5f44
feat(job-array-one-based-index): parse submit index expressions (task 6.1)
ParseSubmitIndexExpr accepts only name[1-N] and name[1] (leading zeros
allowed) and otherwise reports the lowest-numbered broken rule with its
specific message: interactive arrays (2.1), invalid name or malformed
expression (2.2), lists/steps (2.3), %N (2.4), start 0 (2.5), reversed
(2.6), start above 1 (2.7), above the limit (2.8). Oversized numbers
saturate for the bound checks while reversal compares the digit
strings, so the rule order holds for any input. Not yet wired into
CreateJob (task 6.2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:47:23 +00:00
3a7e3890e2
test(job-array-one-based-index): apiserver handler test seams and fixture (task 5.4)
CreateJob's ID generation and submission and both control dispatch
sites now go through package variables (generateJobID,
generateArrayJobID, submitJob, controlJob) that default to the real
backend/services functions, so production behaviour is unchanged. A new
non-cgo handler test file provides a reusable fixture: it saves and
restores every global it touches, sets up auth/RBAC, seeds jobs and
their queues, and records each dispatched job.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:41:45 +00:00
46f57095c1
feat(job-array-one-based-index): row predicates in the apiserver query pipeline (task 5.3)
QueryOptions gains Predicates; store.ExecuteQuery keeps a row only if
every predicate returns true, after the plain conditions and before
sort, pagination and field selection, so page boundaries and tokens
count only matching rows. Nil/empty predicates leave every existing
query unchanged. Foundation for evaluating element selections
(bug-1898).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:35:05 +00:00
7f417262c5
feat(job-array-one-based-index): clamp the apiserver array size limit (task 5.2)
job.max_array_size is resolved by a pure resolveMaxArraySize: unset or 0
gives 1000, 1..32767 is used as is, values above 32767 are clamped to
32767 with a startup warning naming the configured and effective value,
and a negative value refuses startup. The old >2147483646 rejection is
gone; the shipped config documents the bounds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:32:25 +00:00
b88e352616
feat(job-array-one-based-index): apiserver backend ID helpers and array ID wrapper (task 5.1)
GenerateArrayJobID wraps libxce idgen_array (first element, index 1;
one-element arrays included) with a non-cgo stub; pure helpers
ParseHumanJobIDStrict (written identity with exact field-width checks,
uint64 overflow reported as a width error), IsArrayCapableKey and
MakeElementID (bit-identical to libxce jobsel_element_id).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:29:11 +00:00
141ac22c54
fix(apiserver): an accepted job is in the job store at once (bug-1894)
The apiserver's job store learned of a new job only from the
controller's asynchronous OnJobCreated / array push, so a 'jobs kill'
(or query) straight after 'jobs submit' could answer 'job not found'
for the job just accepted (2 of 4 immediate kills).

services.SubmitJob now records the accepted job right after the
controller's reply (which comes only after the job is committed and
applied): one record per element, base+i for arrays, stored with the new
DataStore.PutIfAbsent and marked JobInfo.Provisional (json:"-", not a
column). An OnJobCreated that already arrived is never overwritten; the
push that arrives later replaces the provisional record -- including in
dispatchJobArray, whose duplicate check no longer treats a provisional
element as already known.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 04:23:10 +00:00
0f4d23e3cd
fix(frontend-test): regression suite accepts dashed job IDs and no longer hangs on stdin (bug-1765)
xcecli prints a submitted job's ID in the dashed
{cluster}-{apiserver}-{timestamp}-{sequence} form (format.FormatJobID,
since d6008ead), but run.sh still matched 'Job <[0-9]+>' -- so 'jobs
submit' always failed and 'jobs kill' was skipped (28/1/1). It also
extracted the ID with [0-9]{10,}, which would have picked out only the
timestamp. Match the dashed form and take the whole ID from between the
angle brackets; 'jobs kill' accepts it back.

'jobs submit' reads a job script from stdin whenever stdin is a pipe,
so the suite hung forever when run from a script or CI with an open
pipe; the submit now reads /dev/null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 04:13:26 +00:00
44173f74ae
fix: spurious errors/warnings from a pc13v HA deploy (bug-1872..1877)
- bug-1872 (worker): "Failed to find any disk statistics in
  /proc/diskstats" ERROR every 15 s on an NVMe-only host. The whole-disk
  filter guessed from the name and skipped anything ending in a digit or
  containing 'p' -- every nvmeXnY, mmcblkN, even sdp -- so io was never
  reported. diskstats.h now counts a device only when
  /sys/block/<name>/device exists (real disks; not partitions, loop, dm,
  md, zram), and a host with no disk warns once.
- bug-1873 (worker): "Failed to get worker pid" WARN on job start. MainPID
  reads 0 until systemd has forked the new transient unit; xcecd.c already
  retries, so the miss is DEBUG and the final failure names the unit.
- bug-1874 (scheduler): deprecation WARN for the legacy xce_sched_fcfs on
  every start, though builtin_fcfs is the picker. Registration still wraps
  a job_elect_fn plugin but no longer warns; selecting one warns once.
- bug-1875 (apiserver): "Not running as root -- PAM authentication will not
  work" with the default provider config_file; now only for provider pam.
- bug-1876 (controller): standbys logged "cluster_init_phase2 ... failed
  (rc=-22)" WARN every 3 s while waiting for the primary's config push,
  a state the function itself calls normal. It returns -EAGAIN for that
  wait; callers log it at INFO, other failures still WARN.
- bug-1877 (controller): "WAL/snapshot stored unencrypted" warned without
  looking. persist_path_encrypted() checks fscrypt (statx) and dm-crypt
  (sysfs dm/uuid CRYPT-, through LVM slaves); the warning stays when the
  storage is not shown to be encrypted (e.g. a plain /tmp).

Tests: worker diskstats_test (2 of 4 RED on the old filter with pc13v's
/proc/diskstats), controller test_phase2_cold_boot and
test_persist_encryption, scheduler picker tests, apiserver
TestRootWarning. Full ASAN build: all 113 suites pass (controller 319/0,
harness 141/0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 04:35:19 +00:00
b970ab75b1
fix(apiserver): reject a malformed rusage[] at job submit (bug-1744)
Gitea #47: `xcecli jobs submit -R "rusage[mem=100000000a0]"` was accepted
with a normal "Job <...> is submitted" reply. The apiserver only
length-checked resreq; the real PEG grammar ran later, asynchronously, in
the scheduler's core_build_job(), which logged a server-side WARN and
dropped the job -- it then sat PEND forever with no sign of why.

- libxce: xce_check_rusage_syntax(resreq, &errmsg) runs parse_rusage() on
  every rusage[...] clause (extracted the way section.peg does: up to the
  first ']'), so primary, compound and alternative requests are all
  covered. No resource manager needed, so select[]/order[] resource names
  are still only checked by the scheduler (the documented scope).
- apiserver/backend: ValidateResreqSyntax() cgo wrapper (resreq_check.go);
  the !cgo stub returns nil (no job can be submitted without CGO anyway).
- controllers/jobs.go preCheckJobRequest(): calls it after the length
  check via a package var, so a malformed request is refused up front.

Tests: lib test_check_rusage_syntax (RED before: the reported input was
accepted) and backend TestValidateResreqSyntax (RED before) through the
real grammar; controllers TestPreCheckJobRequest_RejectsMalformedResreq
checks the call site with an injected validator (written with the call
site, so no separate RED). libxce, apiserver backend (cgo) and controllers
tests pass. apiserver/middleware TestLoadJobs fails here and on the
baseline alike: it needs a live apiserver on :8080.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 03:27:32 +00:00
099dbdabdc
fix(apiserver): fix job control union discriminant, add human-readable job ID support (gitea#45)
ControlJob() set xce_jobctrl_t.action to the job_ctrl_type_t action code
(kill/stop/resume/checkpoint), but that field is actually the capnp
JobControl_u_which union discriminant -- a different enum entirely. This
made kill/resume/checkpoint hit request.c's -EINVAL default case, and made
stop collide with the checkpoint discriminant and read through the wrong
union member. Live-verified against a real deployed cluster: kill/stop/
resume/checkpoint now behave correctly with no apiserver crash.

Also additively accept a human-readable job ID ("{cluster}-{apiserver}-
{timestamp}-{seq}") on the job control endpoint, alongside the existing
numeric form xcecli already translates to -- lets a caller hit the REST
API directly without reimplementing xcecli's ID packing. Live-verified via
a direct curl call with the human-readable ID.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hb6Edtj4KbS8CrgEWYdA2E
2026-09-07 08:31:54 +00:00
5ab344e1ae
fix(apiserver): propagate submit user as job execuser (bug-1780)
modelJobToSubmit() never set job_startinfo_t.execuser when converting
a submitted job to the C struct sent to the controller/worker, so
every job ran as the worker's 'nobody' default and failed to exec
(exit 126). Default execuser to the submitting user via a new
resolveExecUser() pure helper, and free the newly allocated C string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hb6Edtj4KbS8CrgEWYdA2E
2026-09-07 08:10:10 +00:00
e395eaa3ad
fix(frontend): count host job-state columns and EXEC_HOST by real slots
NJOBS/RUN/SSUSP/USUSP (xcecli hosts list -l) and EXEC_HOST (xcecli jobs
list) always showed a hardcoded 1 slot per host regardless of a job's
actual per-host allocation -- rawJobToModel()'s ExecHosts JSONB builder
never read the real per-host "slots" resource from the C job allocation,
so the already-correct downstream slot-counting logic (computeHostSlotUsage,
bug-1743) and EXEC_HOST's "N*host" formatter never had real data to work
with. convert.go now extracts each host's real slots allocation via a new
CGO indexing helper, mirroring the scheduler's own get_nslots_in_alloc()
convention.

Also wires hosts list -l's "CURRENT LOAD USED FOR SCHEDULING" table (tmp/
swap/mem/slots and friends) to the host's real live resource values --
previously entirely hardcoded placeholder strings, never connected to any
backend data.
2026-09-07 07:32:00 +00:00
6fabdba961
feat(eliminate-frontend-makefile): task 4.2 - delete src/frontend/Makefile
The terminal step: src/frontend/Makefile (194 lines, the second,
independently-maintained build/test path this whole spec exists to
eliminate) is deleted. Re-ran the full task 4.1 validation suite with
the file genuinely absent (build, meson test --suite apiserver, and
-- going beyond citing task 3.5 -- a real live run-resource-verify.sh
end-to-end run: VERDICT result=PASS, closing design.md's Testing
Strategy "Discovery-path regression" check specifically). Full-repo
sweep: 181 hits, zero unaccounted, byte-identical to task 4.1's
non-Makefile hits (the only delta is the Makefile's own 8
self-referential lines, now gone since the file doesn't exist).
Independently re-verified by the task reviewer.

Meson is now the single build/test system for apiserver and xcecli.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-03 15:52:13 +00:00
12c6a59c03
feat(eliminate-frontend-makefile): task 3.4 - update source code comments
Comment-only edits in 4 files: test_ha_bootstrap.c/test_ha_ctrlhello.c
rename their "make test-integration" pointers to reference the
regression suite (tests/regression/run.sh) directly; apiserver/xcecli
cmd/main.go drop the Makefile clause from their ldflags-injection
comments, keeping only src/tools/go_build. Zero production/test
logic changed -- verified via full controller unit-test suite rerun
(1261 tests, 0 failures) and xcecli build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-03 14:34:48 +00:00
f5fbc6bcac
feat(eliminate-frontend-makefile): task 3.3 - update live documentation (11 files)
Removes every live current-fact reference to src/frontend/Makefile
targets or Makefile-specific fixed paths across 11 docs (steering,
frontend/CLAUDE.md, root CLAUDE.md, deployment docs, and two skill
docs), replacing with the Meson-based equivalents established by
tasks 1-3.2: /xce-build apiserver, meson test --suite apiserver,
direct tests/regression/run.sh invocation.

Round-1 review rejected the first pass: xce-deploy-ha/SKILL.md had
over-corrected by deleting all mention of deploy-ha.sh's still-real
/tmp/apiserver legacy fallback (that script's discovery logic is
unchanged, unlike run-resource-verify.sh's, which genuinely lost its
fallback in task 3.1 -- xce-test-resources/SKILL.md's identical-
looking edit was correct for that reason). Fixed by restoring an
accurate, Makefile-free description of the real fallback at all four
touched sites. Re-reviewed and approved.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-03 14:22:54 +00:00
lei.chen
6f757872c4 merge-fix(apiserver/hosts): adopt theirs computeHostSlotUsage, drop our superseded job-count helper
hosts.go conflict-free auto-merge left both host counter implementations:
ours countRunningJobsPerHost() (bug-1490, per-RUN-job count) and theirs
computeHostSlotUsage() (bug-1743, per-host occupied-SLOT count bucketed
USUSP>SSUSP>RUN) wired into processHostData(), which runs after GetHostsInfo
and overwrote our num_run, leaving our helper dead code.

Resolution: keep theirs (slot-bucketed). Rationale: it matches the codebase's
canonical JobStateStr mapping (a RUN|USUSP job is USUSP, not RUN -- our
RUN-bit-only check mislabeled suspended jobs as running and left
num_ssusp/num_ususp permanently 0), sums num_slots so values are comparable to
max_slot, and is applied uniformly at the processHostData choke point for all
host endpoints. Deleted countRunningJobsPerHost + GetHostsInfo fill-loop and
rewrote hosts_numrun_test.go to cover computeHostSlotUsage's bucket/multi-slot
semantics plus our retained robustness cases (PEND/DONE and empty/bad
exec_hosts ignored). Theirs' host_control_test.go
TestProcessHostData_ComputesSlotUsageFromJobs provides the processHostData
integration coverage.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-03 14:13:02 +00:00
lei.chen
5e2985542f merge: sync Rongsong's scheduler-framework (88 commits, f01ae353..c5b172c7)
Two-sided divergence from f01ae353 (2026-08-30): theirs +88 (worker
rejoin-resync reconciliation, terminal-state bug-1737/1738/1739/1764,
scheduler-sim verbosity, hermetic offline build, go mod vendor of Go deps
in apiserver -- 3375 pure-adds), ours +89 (job-finish robustness bug-1509
B1/B3, PEND-reason, worker reconnect resend, apiserver job control).

Conflict resolutions (kept both sides' fixes):
- controller_connector.c checking_connection(): combine theirs
  xcecd_resync_is_pending() rejoin deferral gate with ours
  resend_job_status() actual-state re-report on rejoin success.
- job.c: keep both unknown-finish registry (ours, bug-1509) and
  record_job_finish_fact_at (theirs, bug-1747); keep both
  mark_jobs_unknown_core family (ours) and record_job_reconcile_*
  (theirs). No double-notify: on_job_finish unchanged, theirs is
  resync-path-only.
- scheduler handler.c/test_handlers.c/meson: theirs schedExitJob
  exit_code branch (bug-1737) + ours jobDispatchFail success-ack no-op
  (bug-1512) + configpush enq_pend_update (bug-1513) both present; both
  test suites and REPL/sim targets wired.
- apiserver jobs.go: keep ours normalizeJobInt64/jobArrayIdx superset +
  JobIDHuman; drop theirs strict IntOrInt64 early-reject (superseded --
  ours tolerates float64/json.Number, no panic path).
- jobs_test.go: keep both regression suites (ours bug-1490, theirs
  bug-1742).
- .kiro/specs scheduler-sim-*/worker-rejoin-resync + .wolf/*: keep our
  deletions (established convention -- drop Rongsong bookkeeping on merge).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-03 14:11:43 +00:00
6b10512502
feat(eliminate-frontend-makefile): task 3.2 - fix deploy-ha.sh/run.sh messages
Rewords deploy-ha.sh's block comment and two fallback echo lines, and
run.sh's XCE-not-found error, to stop naming the now-deleted
Makefile's build-server/build-cli targets, pointing at /xce-build
apiserver instead. Discovery/control-flow logic in both files is
unchanged (verified line-by-line: no if/elif/else lines touched);
run.sh's XCE default placeholder is untouched by design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-03 14:00:22 +00:00
6c3ff2012b
feat(eliminate-frontend-makefile): task 1.1 - Meson backend CGO test target
Adds a new `test('apiserver backend', ...)` in apiserver/meson.build
reusing the existing apiserver_cgo_deps-derived CGO flags (resolving
their @CURRENT_SOURCE_DIR@/@BUILD_ROOT@ placeholder tokens via
Meson's own .replace(), since this target invokes go_compiler
directly rather than through tools/go_build). Adds timeout: 300
(matching the existing controller test's convention) since a
cold-cache CGO+ASAN build exceeds Meson's 30s default.

Verified cold and warm cache; confirmed CGO_ENABLED=1 selects the
real backend/ CGO path, not the //go:build !cgo stub.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-03 13:17:06 +00:00
68bc86bc59
build: make Go and Python builds fully offline (hermetic Nix build)
The Nix build previously fetched Go modules from goproxy.cn and Python
build deps from PyPI during the build phase, relying on network leaking
into the sandbox. This is non-reproducible and breaks whenever the
sandbox resolver is unreachable (e.g. pc13v's loopback dnsmasq).

Go:
- Vendor all three modules (frontend/apiserver, frontend/xcecli,
  autoscaler/providers/baidu/src/bcctool) and commit vendor/ trees.
- tools/go_build now builds with GOFLAGS=-mod=vendor and GOPROXY=off
  instead of GOPROXY=https://goproxy.cn.

Python (xceadmin):
- Add setuptools + wheel to xce.nix nativeBuildInputs.
- meson-build.sh installs with pip --no-build-isolation --no-index
  --no-deps so no PyPI access happens at build time.

xceadmin install script:
- Invoke meson-install.sh via find_program('bash') instead of relying
  on its #!/usr/bin/env bash shebang, which fails in the Nix install
  sandbox (no /usr/bin/env -> 'could not be run').

Cleanup:
- Drop now-unneeded cacert and impureEnvVars=[HOME] from xce.nix and
  xcecli.nix (they only existed to enable the impure network fetch).

Docs:
- README build guide: drop '--option sandbox false' from nix build /
  nix bundle commands, note the build is now hermetic, and document the
  go mod vendor + git add vendor/ maintenance workflow.

Verified: nix build .#xcecore, .#xcecore_debug, .#xcecli, and .#xce all
succeed with the sandbox enabled.
2026-09-03 12:38:49 +08:00
lei.chen
070e9992f2 fix(apiserver): restore pending_reason in JOB_DEFAULT_COLUMN (merge regression)
The scheduler-framework merge (3b6f53bb) dropped ac6131d's "pending_reason"
from models.JobInfo JOB_DEFAULT_COLUMN during conflict resolution (took
theirs). Default job queries returned pending_reason:null even though the
scheduler attaches reasons, the controller stores them on job->pend_reasons,
and the apiserver's in-memory store has them.

Found during the 2026-09-01 full job cluster verification: scheduler P1
TIME_WINDOW/HOST_UNAVAIL reasons confirmed end-to-end via controller
set_preason/apiserver hook debug logs and apiserver dataset-push debug logs;
only the default query column selection was missing. Verified after the fix:
night queue (window closed) -> main_reason:8 time_window; all workers stopped
-> main_reason:9 host_unavail with per-host details.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 15:26:33 +00:00
728496bea0
fix(apiserver): populate hosts-list slot usage counts
xcecli hosts list showed NUM_RUN/NUM_SSUSP/NUM_USUSP as always 0 --
the fields existed on HostInfo and were displayed, but nothing ever
assigned them. processHostData now derives per-host slot usage from
the live job store (exec_hosts + job_status), counting occupied slots
so the numbers are comparable to MAX_SLOT.

Partial fix for gitea#46 / bug-1743: the separate "dispatch stops at
8/16" question needs the reporter's live limits.yaml/pend-reason to
confirm a usergroup/queue slot limit, not addressed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
2026-09-01 06:37:37 +00:00
343639fc7e
fix(apiserver): stop job-control panic on int/int64 type mismatch
xcecli jobs kill/stop/resume/checkpoint failed for every job with an
empty error body. models.JobInfo.JobStatus/.ArrayIdx are plain Go
`int`, but validateJobControl/foundJob asserted them as `int64` via a
bare type assertion, which always panicked; Gin's Recovery()
middleware swallowed the panic and returned an empty 500.

Fixes gitea#45 / bug-1742.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
2026-09-01 06:37:04 +00:00
lei.chen
c3acd62a17 Merge remote-tracking branch 'myxce/chatbot' into chatbot 2026-09-01 00:44:36 +00:00
lei.chen
3b6f53bb65 merge: sync Rongsong's scheduler-framework (122 commits, 070c7869..f01ae353)
Introduces WAL replay field-completeness fixes (bug-1687..1697, 1731), scheduler snapshot job-allocation restore, heap UAF fixes (vtask_enq, xcealloc, builtin_fcfs, limit), HA fixes (bug-1694/1695/1696), xceadmin retry, worker signal-exit severity, and their test suites.

Conflicts resolved keeping both sides (drop-audit passed):
- worker.c: mark_worker_jobs_exit (bug-1509 B1) + fetchfile_signal_worker_disconnect (bug-1693)
- handler.go: DefaultBroker.Publish (EventJobFinished) + bug-1731 logging
- config_repl.c / test_config_repl.c: our ns-resolution comment / unique-tmpdir
- test_dispatch_commit.c: both B3-UNKWN and bug-1697 start_time assertions

Excluded Rongsong's bookkeeping per policy: .wolf/, .kiro/, CHANGES.md, reports/;
.claude/ + CLAUDE.md kept ours (his settings.json has machine-absolute paths).
2026-08-31 14:24:17 +00:00
lei.chen
6020015fa7 feat(mcp-server): add scheduler_checkpoint + scheduler_restart tools
Expose two backend capabilities MCP previously omitted:
- scheduler_checkpoint: control action "checkpoint" (CRIU)
- scheduler_restart: POST /xce/v1/jobs/:id/restart (re-queues as a new
  job, resuming from the last checkpoint if one exists; returns new jobid)

schedulerclient gains CheckpointJob/RestartJob (RestartJobResult,
JobRestartRequest); Client interface updated. Both tools marked
destructive (confirm required). tools/list now reports 17 tools.

Unit tests added/updated in both modules — all pass. Backend endpoints
are existing apiserver routes; no backend changes in this commit.

Also syncs left_scheduler_work.md §八.4 (checkpoint/restart done; the
only remaining MCP tool gap is scheduler_modify, blocked on a controller
job-field-update opcode that does not exist yet).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-08-30 10:36:54 +00:00
ebe691c3ed
fix(bug-1731): fire HOOK_DETAIL_JOB_START/JOB_EXIT on WAL apply
apply_committed_entry() (persist.c), the single seam that applies every
committed WAL entry, only ever fired the generic HOOK_DETAIL_JOB_UPDATE
for job-state changes -- HOOK_DETAIL_JOB_START/JOB_EXIT were
permanently dead code, since job.c only ever fires them at
HOOK_BEFORE_EVENT (for WAL-append purposes). This left sched_job_start_
fun/sched_job_exit_fun (sched.c) and apiserver_job_start_fun/
apiserver_job_exit_fun (apiserver.c) unreachable, and the Go apiserver's
already-wired OnJobFinished handler permanently starved of data -- a
finished job's state could stay stuck (e.g. showing RUN) in the
scheduler's own view and the apiserver never learned a job finished.

The wire format was already enriched by bug-1697/bug-1689 with the
pidinfo/exit_info data these hooks need, so this closes the gap rather
than requiring new wire work. WAL_JOB_FINISH now fires
HOOK_DETAIL_JOB_EXIT; WAL_JOB_START now fires HOOK_DETAIL_JOB_START,
gated on a call-scoped decode flag rather than the transition result
(WAL_JOB_START applies as a no-op duplicate for every real job in
production per bug-1697, so gating on the transition result would make
it dead code again). WAL_JOB_FAIL -> HOOK_DETAIL_JOB_FAIL has the same
gap and is left as a follow-up.

Also includes this investigation's supporting changes: a DEBUG3 log-
retention bump (main.c, both controller and scheduler) so race-condition
evidence survives a live burst without rotating out, and DEBUG3-gated
diagnostic trace logging at the two points a job-update notification
can fire or silently drop (job.c, sched.c).

Live-verified end to end on a real deployed cluster: scheduler-side
sched_job_state_change now fires for RUN and EXIT states, and the
apiserver's OnJobFinished -- previously never invoked in production --
now correctly updates job status on completion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QxaSzHRB7pmYFp4cVnwkA
2026-08-29 11:29:10 +00:00
lei.chen
8858afed3a fix(merge): restore bug-1490 job control + p2-hang flusher fix + B3 test reconciliation
Merge follow-up fixes found during comprehensive unit-test + live-cluster
verification of the scheduler-framework merge:

- apiserver/backend/clib.go (ControlJob): Rongsong's clib.go had reverted
  our bug-1490 fix — ctrl.action must be the capnp JobControl union
  DISCRIMINANT (0=siginfo,1=ckptoption), not the models.JOB_* constant
  (1/2/3/8), or every kill/stop/resume fails -EINVAL. Restored
  ctrl.action = C.int(jobCtrlDiscriminant(action)); verified live (job
  kill RUN->EXIT).

- controller/persist.c (fsync_worker): the merged bug-1563 no-progress
  backoff used the PRE-FLUSH `pending` snapshot, so a no-op flush (another
  path advanced durable_lsn to tip mid-flush) also slept wal_sync_ms and
  raced commit_engine_fini in teardown, hanging the p2_integration suite
  ~60% (bug-1515). Re-evaluate the level AFTER the flush (pending_after)
  so only a genuine sustained fdatasync failure backoffs. 20/20 clean p2
  runs (was 3/5 hang). Also carries the bug-1509 B3 RUN->UNKWN marking in
  persist_recover.

- scheduler/worker testsuite meson.build: add unity_dep parent includedir
  (unity.pc Cflags only add -I${includedir}/unity, so <unity/unity.h> was
  unresolvable) — same fix as src/lib/meson.build.

- test_handlers.c: re-point notify.data.dispatchfail after the first
  sched_handle_notify NULLs it on success (code=0 branch was -EINVAL).

- test_controller_connector.c: give the rejoin-success fixtures a valid
  worker_list pointer (my bug-1509 resend_job_status deref was NULL-
  crashing).

- test_wal_job_dispatch_fact.c / test_dispatch_commit.c: Rongsong's
  WAL_JOB_DISPATCH replay tests asserted JOB_STAT_RUN after persist_recover,
  conflicting with bug-1509 B3 (RUN->UNKWN on restart). Assert JOB_IS_RUN
  (base RUN recovered, incl. bug-1678 allocs) + UNKWN flag instead.

All suites green: controller 1145/0, scheduler 57/57, worker 15/15,
lib 303/0, apiserver CGO build ok.
2026-08-26 10:52:35 +00:00
lei.chen
7ea117dafa merge-fix: port our scheduler/worker/apiserver fixes onto scheduler-framework merge
After merging origin/scheduler-framework (scheduler-core), re-apply and
reconcile our fixes that Rongsong's branch lacked or implemented differently:

- handler.c: keep the code<0 dispatch-fail check (bug-1512: a code-0
  success ack must NOT re-PEND, or dispatched jobs spawn duplicates).
  Use sched_job_state_change (Rongsong's enqueue mechanism) instead of
  sched_requeue_pend_job so the merged test framework (pop_jstate_change)
  stays valid. Drop Rongsong's duplicate unconditional re-PEND case.
- scheduler.c: port sched_requeue_pend_job (bug-1494 class) adapted to
  sched_get_job(&sched->core, ...) signature.
- types.h: re-add xce_sched_queue_t.dispatchwindow (our time-window PEND
  reason field) dropped when taking Rongsong's types.h.
- models/jobs.go: re-add JobSubmitResponse.JobIDHuman (human job id).
- worker controller_connector.c: our bug-1509 resend_job_status on rejoin
  success was already ported; add test stub.
- tests: adapt dispatch-fail test (code=0 → no re-PEND); fix sched_get_job
  signature in white-box stubs.

Build passes (nix build .#xce). Unit + live-cluster verification pending.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 12:53:20 +00:00
lei.chen
4e894d2184 merge: sync Rongsong's scheduler-framework into chatbot
Full merge of origin/scheduler-framework (scheduler-core restructure,
failover-adoption, WAL_JOB_DISPATCH, apiserver backend health/heartbeat,
build-time versioning, bug-1673/1676/1678/1679/1680).

Conflict resolution:
- scheduler: adopt Rongsong's scheduler-core architecture (driver loop,
  queue project/picker fields, resource bug-1569 fix)
- worker/controller_connector.c: keep Rongsong's reconnect/rejoin
  structure, PORT our bug-1509 resend_job_status onto the rejoin success
  path (his reconnect didn't re-report job state)
- apiserver backend: take Rongsong's health/heartbeat (supersedes our
  e505066 circuit breaker)
- group.c: keep our bug-1513 comment
- misc: take theirs for additive fixes (go_build versioning,
  test_group.c, persist.c RUSAGE_UPDATE, FilterRows bug-1680)

NOTE: our scheduler fixes (bug-1494 family dispatch-fail requeue,
PEND-reason classification) are NOT in Rongsong's branch — needs
verification/porting onto scheduler-core (separate follow-up).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 12:33:16 +00:00
9a230f0897
fix(apiserver): FilterRows must not route non-bitmask operators through bitmask matching (bug-1680)
FilterRows routed ANY condition on job_status/host_status through
MatchesBitmaskCondition based purely on the field name, regardless of
the condition's actual operator. MatchesBitmaskCondition only
implements OpEQ/OpNE (the only two shapes buildIntCondition's in/notin
bitmask branch ever produces) and silently returns false for anything
else -- so xcecli's `-a` flag, which builds `{job_status:notnull:}`
(Operator: OpNOTNULL), matched zero rows unconditionally. `xcecli jobs
list -a` showed "no job found" even when jobs genuinely existed and
were correctly queryable through every other view.

FilterRows now requires the operator to actually be OpEQ/OpNE before
taking the bitmask path; any other operator on these fields correctly
falls through to the generic MatchesCondition evaluator, which already
implements OpNOTNULL/OpISNULL correctly -- no changes needed to either
evaluator itself, only the dispatch condition.

New query_test.go (no test file existed for this package before):
TestFilterRows_JobStatusNotNull_MatchesRealRows reproduces the bug
(independently confirmed RED: 0 of 3 rows matched) and
TestFilterRows_JobStatusBitmaskEQ_StillWorks locks in that the actual
bitmask shape used by every other status flag is unaffected. Live-
verified: `xcecli jobs list -a` now correctly shows jobs across every
status, immediately, no restart needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-25 04:04:08 +00:00
1a61b04ab6
fix(apiserver): thread from_host through the CGO conversion layer (bug-1676, gitea #44)
modelJobToSubmit/freeJobSubmit (submit direction) and
cJobToModel/cJobArrayToModels (receive direction, both single-job and
array-job paths) now carry from_host across the CGO boundary.
rawSubmitData/rawJobToModel (the pure-Go, CGO-free mapping layer) gain
the field too, completing the round-trip so JobInfo.FromHost reflects
whatever the controller actually has -- which, as of the prior two
commits, is now always the client-asserted hostname. backend/handler.go
needs no change: its existing blind overwrite in OnJobUpdated is
correct by construction once every controller push genuinely carries
the right value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-24 15:14:49 +00:00
fe8818b19b
fix(apiserver): pass client-asserted FromHost through unchanged (bug-1676, gitea #44)
CreateJob now does FromHost: req.FromHost, a straight passthrough of
whatever xcecli (or any other client) sends in JobSubmitRequest.FromHost,
instead of deriving it server-side via ctx.ClientIP().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-24 15:14:33 +00:00
4fad788430
fix(xcecli): assert own hostname as FROM_HOST at submission time (bug-1676, gitea #44)
JobSubmitRequest gains a FromHost field, populated in BuildJobRequest()
via os.Hostname(). Matches how traditional batch schedulers report the
submitting host, and sidesteps having the server derive it from the
connection (which apiserver was doing via ctx.ClientIP() -- an IP not
a hostname, and spoofable via X-Forwarded-For since this apiserver
never configures gin's TrustedProxies).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-24 15:14:28 +00:00
lei.chen
b29886a000 feat(apiserver): populate hosts num_run from the job store
num_run was declared in the host model but never set anywhere — the
controller's worker push carries no running-job count, so hosts always
showed 0 regardless of running jobs. Compute it live in the hosts list
handler by counting RUN jobs per exec host from store.Jobs — the same
source the jobs list uses, so the two stay consistent.

Verified: t1 with 5 RUN jobs shows num_run 5, matching the jobs list.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 14:27:11 +00:00
fe87dafc85
fix(apiserver): fall back to default HostConfig for unmatched workers (bug-1673, gitea #40)
OnHostConfigUpdated only applied config-sourced fields (MaxSlot,
HostModel, HostType, DisptWindow) onto a host whose name exact-matched
an entry already in the store. hosts.yaml commonly defines only a
wildcard "default" entry, so a dynamically-joined worker never
received its own config push and MaxSlot stayed at Go's zero-value
forever -- decoupled from the C-side scheduling path, which already
resolves the real value via worker.c's wildcard fallback.

The "default" pseudo-host's config is now cached and retroactively
backfilled onto any registered host with no explicit config of its
own, in either arrival order (config-before-worker or
worker-before-config). The -1 HOSTCONFIG_MXJ_AUTO_DETECT sentinel
(bug-1556) is resolved to the host's real NumCPUs when applied via
fallback, mirroring worker.c's own resolution, so the display shows
usable capacity rather than a bare -1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-24 14:23:45 +00:00
88c7c05daf
fix(apiserver): jobs list -l/-o validates against the wrong struct (bug-1674, gitea #41)
GetJobsInfo/GetJobByIDs validated the -o/-l field list against
models.JobSubmitRequest{} -- the job-submission payload -- instead of
models.JobInfo{}, the query/response model (the already-correct
pattern used elsewhere in this file at line 1131). Response-only
fields like pending_reason don't exist on JobSubmitRequest, so any
such field made the whole listing fail outright with "unsupported
query field", not just omit that column.

core_limit/mem_limit exist on JobSubmitRequest but not on JobInfo --
switching wholesale would have newly broken those two previously-valid
fields. Added CoreLimit/MemLimit to JobAdditionalFields, the existing
merge-in bucket already used for pids/pgids/interactive_job_url.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-24 14:23:38 +00:00
a58c2a19e6
fix(xcecli): friendly message for connection-refused instead of raw OS error (bug-1675, gitea #42)
ExtractErrorMessage()'s connection-refused branch returned the bare
OS error string verbatim. A CLI call right after apiserver startup
(before its listener is up) surfaced a raw "get the request failed:
[connection refused]" with no indication it's a transient startup
race, not a real failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-24 14:23:25 +00:00
lei.chen
06887985b4 fix(schedulerclient): node memory_bytes_total no longer ×1e6 (bug-1514)
The apiserver's host max_mem is already in bytes (the worker reads
sysinfo().totalram × mem_unit → bytes and passes it through unchanged via
controller.capnp), but ListNodes treated it as MB and multiplied by
mbToBytes, inflating each node's reported memory to ~8.2e15 (~7.3 PiB).
Now reports the raw bytes (~7.7 GiB per node).

- httpclient.go: drop ×mbToBytes for node max_mem; clarify mbToBytes is
  job-submit-path only (job max_mem is never populated → stays 0)
- ordermap_test.go: correct assertion that had encoded the buggy value
- httpclient_test.go: regression test TestListNodeMemoryBytesKeepsByteUnit
- buglog.json: bug-1514 + close-tags for the bug-1494 family (prior session)

Also add MCP demo deliverables:
- docs/claude-code-demo-guide.md: live Claude Code demo guide (7 scenes)
- docs/mcp-demo-design.md: leadership demo design
- docs/mcp-demo-run.py: add --slow/--color flags for screen recording

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-22 14:58:56 +00:00
d65d18ad5f
feat(dirty-content-fingerprint): xcecli shows fingerprint in --version
Adds DirtyFingerprint var and composeVersionDisplay() helper to
xcecli/cmd/main.go, a logical duplicate of apiserver's just-approved
implementation (task 4.4, same anchored strings.TrimSuffix strip-and-
append rule). Applied at the sole consumption point: the version string
fed into router.Execute(cm, version), which sets Cobra's rootCmd.Version.
router.go is untouched -- xcecli has no startup log to protect, already
established out of scope by the original build-time-versioning feature.

New main_test.go covers the same 6 cases as apiserver's tests. Verified
independently by review (including a second, independent mutation test)
with real builds across all 3 required scenarios and a full xcecli module
test suite run showing no regressions.

This completes all Go binary work (tasks 4.1-4.5) for this spec.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 08:21:18 +00:00
a4c1b5adee
feat(dirty-content-fingerprint): apiserver shows fingerprint in -version
Adds DirtyFingerprint package var (default "") and a composeVersionDisplay()
helper implementing the same strip-and-append rule as the C daemons: strips
a trailing -dirty from GitCommit and appends -<fingerprint> when available,
via a genuine anchored strings.TrimSuffix, not a blind substring replace.
GitCommit's declaration and the logger.Info startup-log call site (with its
zap fields) are completely untouched.

New main_test.go covers 6 cases via table-driven tests. Verified independently
by review, including a self-constructed mutation test (distinct from the
implementer's own) proving the anchored-suffix regression test is genuinely
meaningful, a real build across all 5 ldflags scenarios, and a live daemon
startup-log capture confirming no leak into the log even on the same binary
whose -version output shows the fingerprint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 08:04:40 +00:00
aa3391a93e
feat(dirty-content-fingerprint): Makefile computes dev-loop fingerprint
Adds an independent DIRTY_FINGERPRINT computation to src/frontend/Makefile,
mirroring xce-build.sh's task 2.1 algorithm exactly, wired into
VERSION_LDFLAGS as -X main.DirtyFingerprint=. GIT_COMMIT/GIT_STATUS/
BUILD_TIME (including GIT_COMMIT's own -dirty suffix) are completely
untouched.

This took two rejected review rounds to get the failure-detection right:
round 1 found a `{ git diff HEAD; ...; } >file` grouped command whose exit
status only reflected the last command, silently swallowing a git diff
failure and fabricating a hash from incomplete data. Round 2 found the
identical bug class one step further down (git ls-files | sort | xargs
cat's pipe swallowing an ls-files failure). Fixed by pulling git ls-files
out of the pipe into its own directly-redirected, individually-checked
step, deliberately chosen over `set -o pipefail;` (which also worked)
because this repo's own build-deployment.md documents a past incident of
code silently relying on /bin/sh happening to be bash with no SHELL:=bash
pin.

One narrow, explicitly-disclosed residual gap remains (a sort-only
failure, not cat, in the trailing pipe) and was assessed as acceptable
low risk after independent verification across three review rounds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 07:34:08 +00:00
502dd0230f
feat(dirty-content-fingerprint): apiserver/xcecli meson pass fingerprint to go_build
Both apiserver/meson.build and xcecli/meson.build add
XCE_DIRTY_FINGERPRINT=get_option('dirty_fingerprint'), unquoted,
identically positioned to the existing XCE_GIT_COMMIT/XCE_BUILD_TIME
entries.

Verified independently by review with a distinct test value: fresh
configure + real ninja builds of both binaries confirm the value reaches
go build's actual invocation (visible in go version -m's recorded -ldflags
build setting), matches the C-side -DVERSION_DIRTY_FINGERPRINT= from the
same configure, and degrades to empty identically to the two pre-existing
vars when omitted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 06:21:21 +00:00
fb09afe6be
feat(build-time-versioning): xcecli --version via Cobra rootCmd.Version
Adds GitCommit/BuildTime vars to xcecli's main.go. Cobra's rootCmd is
declared unexported in router.go (a different file than the task's
original main.go-only boundary), so main.go can't reach it directly --
resolved by extending router.go's sole exported entry point,
Execute(cm, version), to accept the composed version string and set
rootCmd.Version before rootCmd.Execute() runs. Cobra then auto-registers
--version/-v natively once rootCmd.Version is non-empty (confirmed via
reading Cobra v1.9.1 source directly) -- zero additional flag-parsing
code, as designed.

Live-verified via both build paths (meson xce-build.sh and make
build-cli): real commit id (matching git rev-parse --short HEAD plus
dirty suffix) and build time, exit 0.

xcecli's startup log deliberately not extended -- Requirement 3 scopes
that to the four daemons only; xcecli is a one-shot CLI. Corrects a
stale design.md line that had assumed otherwise.

Task 4.5/13 -- closes out the Go binary version display phase (tasks
4.1-4.5).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 14:07:38 +00:00
17e837b196
feat(build-time-versioning): apiserver -version flag and startup log
Declares GitCommit/BuildTime package vars (default "unknown", target of
task 4.1's -ldflags -X injection), a -version flag checked before
utils.PromptForTopDir()/InitLogger() (zero filesystem side effects), and
extends the existing "Initialize Logger done!" startup log line with
zap.String structured fields carrying both values, per design.md's
explicit mandate.

Live-verified end to end: -version output and the startup log's first
line are byte-identical, with a real commit id (matching
git rev-parse --short HEAD plus dirty suffix) and UTC timestamp -- the
first full round-trip proof in this spec (earlier Go tasks could only
verify via linked build-info metadata, since no consuming vars existed
yet).

Task 4.4/13 of build-time-versioning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 13:47:03 +00:00
45c9b026f3
feat(build-time-versioning): Makefile ldflags for make build-server/build-cli
Dev-loop Go build path (no meson involved) now computes commit id (git,
-dirty suffixed, "unknown" on git failure) and build time (UTC date) via
Make := (computed once per invocation), passing the identical -ldflags
shape go_build (task 4.1) already establishes.

Live-verified: make build-cli embeds real, git-matching values (go
version -m). make build-server hits a pre-existing, unrelated CGO
failure (bug-1597, missing CGO_CFLAGS/CGO_LDFLAGS) -- confirmed via
stash A/B that this reproduces identically on the unmodified Makefile,
not a regression from this change. Reproduced apiserver's build
invocation manually with bug-1597's own documented workaround to prove
the ldflags mechanism itself works correctly.

Task 4.3/13 of build-time-versioning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 13:06:20 +00:00