Value objects#
Everything that crosses a boundary in MatMelt is a frozen dataclass built from standard-library types. Not because immutability is fashionable, but because these values are written to a database, read back by a different process, and compared across restarts — and a mutable object that half the system has a reference to cannot be reasoned about across a restart at all.
core/jobspec.py imports nothing but the standard library. Full signatures are
in Value objects.
The job vocabulary#
JobSpec — what to run. An argv command, input_artifacts (name → local
path), output_artifacts (names the job promises), env, cwd, resources,
and scratch_inputs marking which inputs are implementation rather than data.
It names no machine (What vs. where).
scratch_inputs exists for one concrete reason: a stage’s runner script is an
input, but it is not the scientist’s data. Marking it scratch keeps
screen_runner.py out of the material directory, so a workspace contains
results rather than a copy of the tool.
Handle — the durable identity of a submitted job: backend_name,
job_id, and an extra mapping of JSON-safe values. The invariant is stated in
its docstring and enforced by test: job_id is a process id or scheduler job
id, never a socket, a Popen, or an ORM object. Everything poll, fetch
and release need later must survive a round trip through JSON, because that is
exactly what happens to it in the ledger.
JobResult — what happened: exit_code, stdout, stderr, and the
resolved paths of collected outputs. exit_code is int | None, and None
means “ran, exit code unrecoverable” — never defaulted to 0. Conflating
“unknown” with “success” is how a fabricated result gets into a database.
JobStatus — a string enum with exactly four members and an is_terminal
property. String-valued so it round-trips through JSON and reads plainly in a
log.
Resources — typed cores, gpu, walltime_s. Typed rather than a
free-form dict so a backend can map them onto --cpus-per-task and --time
without guessing key names.
Capabilities / HealthStatus — what a backend advertises, and whether
it is usable now. HealthStatus.detail is a place where secrets could leak, so
the contract is explicit: it never contains one, and health_check never raises.
BackendError — infrastructure only. A non-zero exit is not one of these.
The stage vocabulary#
WorkItem — one material: an id and a directory.
StageInputSpec / StageOutputSpec — a declared file plus a human
description. The description is not decoration; it is what makes a refusal
readable:
missing or empty required input file(s) ['structure.cif'] in
materials-work/Cl1Na1_3D-1 (one candidate crystal from the structure stage)
StageVerdict — ok, reason, terminal. Three fields carrying the
verdict-vs-fault doctrine: terminal=True means a deterministic science
rejection that must not be retried
(The stage contract).
Requirement — module, pip_name, purpose. Two separate name fields
because they differ often enough that guessing would hand users a broken
command: mace is installed by mace-torch.
The state vocabulary#
MaterialState — nine string-valued states; see Material states.
MaterialRecord — a material as stored: state, lineage, attempts, error
fields, timestamps, and its completion timeline. The timeline is what lets
retry derive a resume state per material without consulting config.
InFlightJob — a ledger row: the stage, the backend, the handle, when it was
submitted.
MonitorSnapshot and friends — one value describing everything the dashboard
shows, produced by a pure function and consumed by renderers
(State and storage).
Conventions worth keeping#
Frozen, with independent default containers. A shared mutable default is a bug that appears only on the second instance.
Standard-library types only. These values are persisted; a third-party type in one of them would make the on-disk format depend on that library.
Optional means unknown.
Nonein these objects means “not known”, never “zero” and never “false” — inexit_code, in a CPU sample, in a missing metric.String enums. They survive JSON and read correctly in a message.