Verify success from result artifacts rather than process startup alone
Prerequisites
Run command -v sbatch and confirm an allocation/account is available — PREREQ-SLURM
Run command -v apptainer and record apptainer --version — PREREQ-APPTAINER
Required resources
One Slurm compute node
Apptainer available on compute nodes
A center-approved writable job-scoped directory
The immutable v0.1.0 workflow bundle
How the baseline works
One allocation contains one coordinator and a bounded worker pool. The launcher prepares private runtime storage, binds the coordinator to loopback, waits for semantic readiness, starts workers, and verifies outputs. A running process is not yet a successful result.
Five-step flow
This is the conceptual sequence, not an executable submission script. The release-pinned implementation and annotated source excerpts appear below before the procedure.
Storage: create private state and output directories for the allocation.
Coordinator: start one service on loopback.
Readiness: require a bounded semantic response before continuing.
Workers: start exclusive worker steps only after readiness.
Verification: confirm result count, uniqueness, schema, and worker completion before recording success.
Open the complete workflow package from the implementation reference above; the annotated excerpts are orientation, not a replacement for the release-pinned source.
Set an approved account and partition, wall time, CPU and memory, runtime module, image path and digest, scratch root, worker count, task count, and readiness timeout.
Run the prerequisite commands listed above.
Submit the baseline script. Keep account names, private hosts, and sensitive paths out of logs.
Wait for the scheduler to finish, then run the workflow verifier.
Expected result
A successful run exits zero, writes a machine-readable success result, contains the expected number of unique task results, and reports that every invariant passed. Worker-start events occur only after readiness.
Diagnose a failure
A rejected or unavailable scheduler command: PREREQ-SLURM
The baseline covers one node, loopback communication, bounded inert inputs, and ephemeral allocation-scoped state. It does not establish multi-node networking, durable service operation, cross-center portability, performance scaling, or production support.
Next step
Record your local values, then compare scheduler, network, storage, security, and runtime policy before adapting the workflow.
Implementation reference
The excerpts below show the commands and control flow that matter for understanding the pattern, with the defensive checks and integrity plumbing elided for readability. Each excerpt links to its release-pinned line range in the real v0.1.0 source on GitHub. Use the complete package when adapting and submitting the workflow.
The coordinator is launched inside the container on loopback. Nothing else starts until the readiness probe confirms it can actually serve — not just that the process exists.
# Launch the coordinator on loopback inside the allocation.
apptainer exec --cleanenv --bind "${RUNTIME_DIR}:/work" "${IMAGE_PATH}" \
python3 "${APP_ROOT}/coordinator.py" \
--input /work/input.json --results /work/results.json &
# Wait for it to publish its loopback endpoint.
while [[ ! -s "${RUNTIME_DIR}/endpoint.json" ]]; do
sleep 0.05
done
# Run the semantic readiness probe (bounded HTTP /health contract).
ENDPOINT=$(cat "${RUNTIME_DIR}/endpoint.json" | python3 -c \
'import json,sys; print(json.load(sys.stdin)["endpoint"])')
python3 "${APP_ROOT}/readiness.py" \
--endpoint "${ENDPOINT}" --task-count "${TASK_COUNT}" \
--timeout "${READINESS_TIMEOUT}"
Workers pull work from the coordinator over loopback. Only after every worker finishes does the verifier check the results — a completed process is not yet a successful result.
# Start one exclusive step per worker.
for ((i=1; i<=WORKER_COUNT; i++)); do
printf -v worker_id 'worker-%02d' "$i"
srun apptainer exec --bind "${RUNTIME_DIR}:/work" "${IMAGE_PATH}" \
python3 "${APP_ROOT}/worker.py" \
--endpoint "${ENDPOINT}" --worker-id "${worker_id}" &
done
wait
# Verify: schema, count, uniqueness, and expected content.
apptainer exec --bind "${RUNTIME_DIR}:/work" "${IMAGE_PATH}" \
python3 "${APP_ROOT}/verify.py" \
--input /work/input.json --results /work/results.json \
--expected /work/expected.json --output /work/result.json
echo "BSSW workflow completed: result is in the runtime directory."
The probe polls the coordinator /health endpoint on loopback with a hard deadline. It passes only when the coordinator reports the expected schema and task count — so workers never start against a half-initialized service.
deadline = time.monotonic() + args.timeout
while time.monotonic() < deadline:
try:
with urllib.request.urlopen(f"{args.endpoint}/health") as response:
health = json.load(response)
if response.status == 200 and health == {
"status": "ready",
"schemaVersion": 1,
"taskCount": args.task_count,
}:
raise SystemExit(0) # ready
except (OSError, ValueError, urllib.error.URLError):
pass
time.sleep(min(args.interval, deadline - time.monotonic()))
raise SystemExit(24) # timed out
The /health response is the contract the readiness probe checks. Reporting taskCount means the coordinator has parsed its input and has work to hand out before any worker asks.
def health(self) -> dict:
return {
"status": "ready",
"schemaVersion": 1,
"taskCount": len(self.tasks),
}
def claim(self) -> dict:
# A worker asks for work; the coordinator hands out one task
# or reports that all tasks are done (or that more may arrive).
if self.pending:
task_id = self.pending.popleft()
return {"status": "task", "task": self.tasks[task_id]}
status = "done" if len(self.results) == len(self.tasks) else "wait"
return {"status": status}
The verifier checks that every task produced exactly one result, that results are unique, and that they match the expected outputs. Only then does it atomically write the success marker — the single artifact a completed run is judged by.
# Load tasks, actual results, and the expected results.
tasks = load_tasks(args.input)
actual = json.load(open(args.results))["results"]
expected = json.load(open(args.expected))["results"]
seen = set()
for result in actual:
if result["taskId"] in seen:
raise ValueError("duplicate task id")
if result["taskId"] not in {e["taskId"] for e in expected}:
raise ValueError("unexpected task id")
seen.add(result["taskId"])
# Every task must be covered exactly once.
if seen != {task["id"] for task in tasks}:
raise ValueError("results do not cover each task exactly once")
# Atomically write the success marker.
success = {
"schemaVersion": 1,
"status": "success",
"taskCount": len(tasks),
"results": sorted(actual, key=lambda r: r["taskId"]),
}
atomic_json(args.output, success)
Expected success resultresult.json · sanitized example
A successful run has a machine-readable success marker. Intermediate results are not a success claim until this verifier output exists.