spec
tit.jobs.spec ¶
Job data model: JobSpec/JobStatus and the small value types they're built from.
Shapes mirror contracts/openapi.yaml (JobKind, JobState, JobProgress,
JobError, Artifact, WaitingOn, JobStatus, JobSpec, JobDetail,
LockConflict). A few fields exist only on the Python side (persisted to
spec.json/status.json for the scheduler's own bookkeeping โ locks, cost,
pid, create_time, budget_wait) and are dropped by
:meth:JobStatus.to_api / :meth:JobSpec.to_api before a response leaves the server, so the wire
shape stays exactly what the contract describes plus one harmless additive field
(budget_wait, not additionalProperties: false in the schema).
JobKind's frozen v1 contract enum (CONTRACT_JOB_KINDS) includes tools alongside
project_init (contracts/CHANGES.md, 2026-08-27 entry, item 1); project_init maps to
-m tit.project_init in :mod:tit.jobs.kinds (ra_14 finding #2). report left the enum on
2026-09-28 with the combined preprocessing report it ran (contracts/CHANGES.md).
Cost
dataclass
¶
Estimated resource cost of one job, for scheduler admission (ยง2.4).
JobSpec
dataclass
¶
JobSpec(id: str, kind: str, config: dict[str, Any], subject_ids: list[str], after: list[str] = list(), tags: list[str] = list(), env: dict[str, str] = dict(), locks: list[str] = list(), cost: Cost = (lambda: Cost(cpus=1.0, mem_gb=1.0))(), created_by: str = 'api', created_at: str = utcnow_iso(), group_id: str | None = None, overwrite: bool = False)
The full, persisted job record (jobs/<id>/spec.json).
kind/config/subject_ids/after/tags/overwrite are the fields a caller
may set (the wire JobSpec in the contract is exactly this subset, minus id which the
server assigns); locks/cost/env/created_by/created_at/group_id are
computed or defaulted by :mod:tit.jobs.manager at submission time and never come from the
client directly. A group_cap key in an older spec.json (the retired "Subjects in
parallel" setting) is ignored: :func:tit.jobs.scheduler.evaluate runs one job per product.
to_api ¶
The contract-shaped JobSpec (request/response body โ no server internals).
Source code in tit/jobs/spec.py
PlannedJob
dataclass
¶
PlannedJob(label: str, kind: str, config: dict[str, Any], subject_ids: list[str], after_labels: list[str] = list(), tags: list[str] = list(), overwrite: bool = False)
One node of a not-yet-submitted job DAG (tit.jobs.plans.plan_preprocessing).
label is a plan-scoped identifier (e.g. "charm", "dicom") used only to express
edges before real job ids exist; :meth:tit.jobs.manager.JobManager.submit_plan resolves
after_labels to real job ids in topological order and assigns each a real id.
JobStatus
dataclass
¶
JobStatus(id: str, kind: str, state: str, subject_ids: list[str], created_at: str, group_id: str | None = None, started_at: str | None = None, finished_at: str | None = None, progress: JobProgress | None = None, liveness: str | None = None, waiting_on: list[WaitingOn] = list(), exit_code: int | None = None, error: JobError | None = None, artifacts: list[Artifact] = list(), cpu_percent: float | None = None, rss: int | None = None, cpu_percent_peak: float | None = None, cpu_percent_avg: float | None = None, rss_peak: int | None = None, rss_avg: int | None = None, pid: int | None = None, create_time: float | None = None, budget_wait: str | None = None)
to_dict ¶
Full persisted shape (status.json), internal fields included.
to_api ¶
Contract-shaped JobStatus for API responses.
log_path (ra_13 finding #6) is derived from project_dir on the fly via the same
convention :mod:tit.jobs.registry uses for the file itself, rather than persisted --
it's the same deterministic string every time for a given (project_dir, id) pair, so
there is nothing to store. None when project_dir isn't supplied (e.g. a bare
JobStatus built in a test with no registry behind it).