Domain
Purpose
This package is the canonical HPC domain model: the scheduler-neutral vocabulary that every adapter maps into and every downstream capability (analytics, prediction, publication, instance repositories) builds on.
It is the first implemented Forge HPC module. It contains only immutable, self-validating domain objects built from the Python standard library. It has no runtime logic, no I/O, no configuration, no logging, and no knowledge of any concrete scheduler, cluster, or institution.
Why these objects live in the domain layer
An object belongs here only if the concept it names is universally true across HPC systems. Every batch system — whatever its product name — has jobs that are submitted, wait, run, and end; queues that admit jobs under limits; nodes that offer CPU, memory, and accelerator capacity; accounts that work is charged against; allocations that grant capacity over a window; reservations that set capacity aside; storage and software that jobs consume; and observations of all of the above over time.
What varies between systems — command syntax, state names, field formats, site policy — is precisely what is excluded. Adapters translate source-specific observations into these objects; if a property exists in only one scheduler, it belongs in that scheduler’s adapter, not here. This is the vocabulary firewall: the domain never learns where its data came from.
The objects
Jobs (jobs.py)
JobIdentifier— the scheduler-neutral identity of a job. Every batch system names its jobs; only the existence of a stable identifier is universal, so the object holds a single validated string.JobState— the universal lifecycle:pending,running,suspended,completed,failed,cancelled,timed_out,unknown. Real schedulers define many more states; adapters map onto this set and report unmappable states asunknownrather than extending it locally.is_terminalmarks the states a job can never leave.JobResources— what a job requested: nodes, CPUs, GPUs, memory, walltime.Nonemeans “unconstrained”, never “zero”.JobTiming— the observed timestamps: submitted, started, ended. Enforces ordering and derives queue wait and elapsed time.JobExitStatus— how a finished job terminated: exit code and/or terminating signal, recorded exactly as observed.Job— the aggregate. Enforces cross-field truths: a running or suspended job has started; only a terminal job carries an exit status or an end time.
Queues (queues.py)
QueueIdentifier— the identity of a queue (or partition, or class — the neutral concept is a named admission channel).QueueLimits— the per-job ceilings a queue imposes; every limit that exists must admit at least one unit.Queue— identity plus limits.
Nodes (nodes.py)
NodeIdentifier— the identity of a unit of capacity.NodeResources— the totals a node offers (at least one CPU). Deliberately totals, not availability: what is free right now is an observation (telemetry), not a property of the node.Node— identity plus resources.
Accounting (accounts.py, allocations.py, reservations.py)
Account— the entity jobs are charged against, optionally hierarchical. A parent is referenced by identifier value, never embedded, so every account is constructible on its own.Allocation— a grant of a named resource to an account over a strictly positive validity window. The resource unit is named abstractly (for examplecore-seconds); pricing and policy stay outside the domain.Reservation— capacity set aside over a strictly positive window, with optionally known size and beneficiary.
Capacity the jobs consume (storage.py, software.py)
StorageResource— a named storage resource with optionally known total capacity. Filesystem types, mount points, and quota policy are instance concerns.SoftwarePackage— a name–version pair. Module systems, toolchains, and installation paths are instance concerns.
Telemetry (telemetry.py)
Metric— one named measurement at one aware timestamp.TimeSeriesPoint/TimeSeries— a named sequence of observations with strictly increasing timestamps.SchedulerEvent— a neutral record that something happened to a subject at a time, with the kind expressed as a neutral verb phrase (for examplejob-started). Source-specific event names are mapped by adapters before they reach the domain.
Design rules
- Immutability. Every object is a frozen, slotted dataclass. Tuples are
the only collection type; a
TimeSeriesnormalizes its points to a tuple at construction. - Self-validation. Every object checks its own invariants in
__post_init__and fails immediately with a typed error fromerrors.py(InvalidIdentifier,InvalidQuantity,InvalidTimestamp,InvalidInterval,InvalidText,InvalidStructure,InvariantViolation— all subclasses ofDomainValidationError). An instance that exists is valid. - Aware time only. Every timestamp must carry a timezone; naive datetimes are rejected so that ordering and serialization are unambiguous.
- Cross-references by identifier value. A
Jobnames its queue and account as validated strings rather than embedding the objects, so each object is independently constructible and serializable. - Deterministic serialization.
serialization.to_canonicalreduces any domain object to JSON-compatible primitives (enums to values, timestamps normalized to UTC, tuples to lists);canonical_jsonrenders the sorted, compact JSON string that contract tests compare byte-for-byte.
Testing
Unit tests live in tests/, one file per module, covering valid
construction, invalid construction, serialization, equality, immutability,
and boundary validation. From the forge-hpc directory:
python3 -m unittest discover -s domain/tests -t .
Ownership and status
Owner: Forge HPC maintainers. Status: implemented in Capability 2B; the vocabulary is expected to grow only when a concept is shown to be universal across schedulers, never to absorb a single scheduler’s feature.