src/docs/xce.md gains a 'Job arrays' section: name[1-N] and name[1], numbering from 1, the API-server limit (default 1000, at most 32767), <array-id>[k] and <array-id>[a-b] selection (with the peek/restart caveat), XCE_JOBINDEX, the harness jobsub -a N / $jobid[k], and every LSF array feature this release does not support. The superseded statements in job-array-single-wal-record and xce-test-e2e-primitives are annotated (history kept), the controller-job-cleanup and job-dependency briefs describe 1-based numbering, and the roadmap gains an entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
11 KiB
Brief: job-dependency
Problem
XCE users run workflows: preprocess → solve → postprocess, or "run B only if A
failed". Today they must babysit each step and submit the next job by hand when
the previous one finishes, or write external polling scripts. XCE has no job
dependency concept at all. job_submit_data_t has no dependency field, and
nothing in the controller, scheduler, libxce or clients handles one.
controller-job-cleanup also needs a real "does any job depend on this one?"
check; it is a stub today.
Reference: IBM Spectrum LSF 10.1 Administering LSF, section "Job dependency and job priority" (printed pp. 459-465, arrays p. 522, orphans pp. 91-94). The local extracted text is in the discovery scratchpad. User scope decision (2026-09-29): job dependency only. Job priority (user priority, priority escalation, absolute priority scheduling) is a separate, later discovery.
Current State
Codebase exploration, 2026-09-29.
- No dependency fields in
job_submit_data_t(include/xce/types.h:356-370) or capnpJobSubmitData(schema/controller.capnp:579-591, last ordinal @10). - The scheduler gate is a stub. The FCFS picker skips a job unless
sched_job_is_ready()holds (scheduler/src/core.c:493, returns 1 today).job-time-windowuses the same gate for begin and end times.- Sessions run about every 1 s.
- Time and state must come from the scheduler core (
xce_core_now) so that--sim-verifystays deterministic.
- The scheduler sees parent state through
XCE_CORE_EVENT_JOB_STATE_CHANGE{jobid, old_state, new_state, exit_code}(scheduler/include/core.h:550-555).- Gap:
xce_sched_job_tdoes not store the exit code. - Gap: the full-push wire
SchedJob(controller.capnp:347-357) carries no exit info, and neither does the scheduler snapshot (snapshot_wire.c). - So after a scheduler restart or failover,
exit(id, op code)could not be evaluated. This must be closed (save/restore field completeness).
- Gap:
- Job states (
types.h:382-429): DONE, EXIT, PDONE (post-exec done OK), PERR (post-exec error), RUN, USUSP, SSUSP, and so on. LSFpost_done/post_errmap onto PDONE / PERR directly. - Pend reasons. Only resource codes exist (
include/preason.h:7-9). A new non-resource code, and a publish path for skipped jobs, is also needed byjob-time-window. - Controller job table.
c->jobmgrholds jobs. The scheduler'score->jobsalso keeps every job forever today, whichcontroller-job-cleanupchanges. - Array jobs. There is no parent object. Each element is an independent job
with its own jobid. Elements are numbered 1..N per
job-array-one-based-index(2026-10-01) and are referenced as<array-id>[k], k = 1..N; a bare element jobid is rejected by the CLI/REST listing and control commands (use<array-id>[k]; peek and restart still take the element's own jobid). The earlier note here, jobidbase+iwith i from 0, described numbering from 0. - Submit paths: REST (apiserver, fed every job by controller push,
cgo into libxce),
xcecli jobs submit, the harnessxce_test jobsub(harness/test.c:3575), and libxce directly. - The job-modify command (
xce jobs modify, harnessjobmod) is being introduced byjob-time-window.
Desired Outcome
- A user submits a job with a dependency expression, for example
xcecli jobs submit -w 'done(1001) && (started(1002) || exit(1003, != 0))' .... The job is not started until the expression is TRUE. - v1 conditions (user decision), all by job ID:
done(id)ended(id)(DONE or EXIT)exit(id [, op code]), whereopis one of> >= < <= == !=; the default is==, and with no code any exit code matchesstarted(id)(RUN, USUSP, SSUSP, DONE or EXIT)post_done(id)(PDONE) andpost_err(id)(PERR)- a bare
id, which meansdone(id) - operators
&&,||,!, with parentheses
- Validation at submit, rejected with a precise error, following LSF:
- bad syntax;
- a referenced job that does not exist (no forward references);
- a referenced job the submitter may not view (reuse RBAC
job:view); - self-reference or a cycle (impossible with existing parents at submit, but it must be checked on modify).
- While waiting, the job is PEND with the pend reason "dependency not satisfied".
- When the condition can never be satisfied (an orphan, for example
done(P)after P exited), the job stays PEND with the reason "dependency can never be satisfied" until the user kills it or changes the dependency. This is the default (user decision). - Optional orphan termination is owned by
job-dependency-insight. - Changing a pending job's dependency: set or remove it (
-w, and-wnto remove) through the modify path introduced byjob-time-window. It is re-validated like a submit. - Cleanup. A job that any pending job depends on is never removed by
controller-job-cleanup. This spec implements that spec'sjob_has_dependents()stub. - Both client tools, xcecli and the harness (
jobsub -w,jobmod -w/-wn), support the options, and job details show the dependency expression.
Approach
Chosen (user decision, 2026-09-29): the apiserver validates early, the controller is authoritative for validation and owns the dependency graph, and the scheduler evaluates.
-
One shared grammar in libxce. A parser, AST and printer for the expression, used by the controller, the scheduler, the harness and (via cgo) the apiserver. No second copy in Go.
-
apiserver, early validation (like
application-profile's existence check), before anything goes downstream:- parse;
- check each referenced job exists in its job store;
- check the submitter holds
job:viewon it.
It rejects with a clear 4xx.
-
Controller, authoritative.
- On submit and modify it re-validates: the apiserver's store can be stale, a parent may have been cleaned up meanwhile, and the harness and libxce bypass the apiserver.
- It stores the canonical expression in a new additive
JobSubmitDatafield, carried by the WAL submit, update and snapshot paths (field completeness). - It keeps an in-memory reverse index (parent → dependents), rebuilt from the stored expressions on replay and snapshot load. There is no new persisted state.
- The index serves
job_has_dependents()(for cleanup) and the queries injob-dependency-insight.
-
Scheduler, evaluation. Each session,
sched_job_is_ready()evaluates the AST against the scheduler's own job states (deterministic under the core clock). It yields satisfied, not yet, or never, and publishes the corresponding pend reason.- The scheduler must keep each parent's final state and exit code, so add
the exit code to
xce_sched_job_t, toSchedJob, and to the scheduler snapshot. - It must not drop finished parents while pending dependents exist (coordinate
with
controller-job-cleanup).
- The scheduler must keep each parent's final state and exit code, so add
the exit code to
-
Primary work. The primary does no per-session evaluation. Its only dependency work is validation and the index updates at submit, modify and remove.
Rejected alternatives:
- The controller evaluates on each state change (LSF mbatchd style): more primary work.
- Scheduler-only: no validation at submit, and no authoritative reverse index.
- The apiserver owns the graph: libxce and the harness bypass it, its store is non-authoritative and lost on restart, and cleanup runs on the controller.
Scope
- In:
- The shared libxce expression grammar.
- The new submit field and its persistence.
- apiserver and controller validation.
- The controller reverse index and
job_has_dependents(). - Scheduler evaluation plus the two pend reasons.
- The scheduler exit-code field-completeness fix.
- Modifying pending dependencies (set and remove).
- xcecli
-w/modify -w -wn, harnessjobsub -w/jobmod. - Showing the expression in job details.
- Simulator determinism.
- Tests.
- Open for requirements:
- What happens when a dependent's parent is later removed by cleanup. It can't happen while the dependent is pending, but it can after the dependent itself has finished.
- Requeue and rerun semantics. LSF: requeuing a finished parent can orphan its dependents.
- A limit on expression length or complexity.
- Whether whole-array references (all elements) are wanted in v1 without the
num*conditions.
- Out:
- Referring to jobs by name, and wildcards (not selected for v1).
- The array count conditions
num*(not selected for v1). external().- The LSF
JOB_DEP_LAST_SUBandEVALUATE_JOB_DEPENDENCYtuning. - Job priority features.
- A
bjdepinfo-style query, dependency-graph display, and orphan termination (job-dependency-insight). - Cross-cluster dependencies.
Boundary Candidates
- The libxce grammar (parse, validate, print, evaluate on an abstract state-lookup interface).
- apiserver early validation.
- Controller authoritative validation, storage and the reverse index.
- Scheduler evaluation, pend reasons, and keeping parent state and exit code.
- Client surfaces: xcecli, harness, REST.
Out of Boundary
- Showing dependency trees and levels (
job-dependency-insight). - Automatic orphan termination (
job-dependency-insight). - How pend reasons are rendered as text (
job-status-detail-display, which needs the new codes).
Upstream / Downstream
- Upstream:
- The scheduler eligibility gate (shared with
job-time-window; sequence or coordinate the two). - The job-modify path introduced by
job-time-window. - RBAC
job:view. - The WAL submit and update facts.
- The scheduler eligibility gate (shared with
- Downstream:
job-dependency-insight.controller-job-cleanup(the realjob_has_dependents()).job-status-detail-display(text for the new reasons, showing the expression).- Future name-based references, array conditions, and job priority.
Existing Spec Touchpoints
- Extends:
controller-job-cleanup(replaces its stub dependency check). - Adjacent:
job-time-window(the same gate, the modify path, the pend-reason infrastructure)job-status-detail-displayscheduler-alter-pend-reasonsscheduler-sim-discrete-clock
Constraints
-
The primary does no per-session evaluation.
-
Every wire change is an additive Cap'n Proto field. Old WAL records and snapshots decode as "no dependency".
-
Save/restore field completeness:
- the expression round trips through the WAL submit and update facts, the controller snapshot, the scheduler snapshot and the recordings;
- the scheduler job's exit code round trips through the full push and the snapshot;
- the reverse index is rebuilt identically after replay.
Run
/xce-audit-invariants(controller dimension 7, scheduler dimension 4). -
Scheduler evaluation uses only core state and clock (
--sim-verifydeterminism). -
One grammar implementation (libxce), used from Go through cgo.
-
Middleware chain unchanged. RBAC
job:viewgoverns which jobs a user may reference. -
HA/persistence touch: use the full 3-phase workflow.