Skip to main content

purelms-shared

Pydantic schemas that bridge PureLMS (AGPL-3.0-or-later) and the simulation backend containers (MIT, in purelms-interactive-tasks — formerly purelms-backends).

What's in here

  • purelms_shared.envelopes — SimulationInputEnvelope and SimulationOutputEnvelope, the JSON contract for run input + output. Plus the helper types: InputFile, ResourceFile, OutputArtifact, Message, ExecutionContext.
  • purelms_shared.callbacks — ProgressCallback and CompleteCallback, the request bodies a backend POSTs to the worker during a run.
  • purelms_shared.evidence — EvidenceManifest and EvidenceReference, the immutable record of a completed run that credential issuance reads later.
  • purelms_shared.constants — RunStatus, OutputStatus, MessageLevel, BackendCallbackEvent, InputFileRole enums, plus the purelms.{input,output,evidence}.v1 schema-version literals.

What's not in here

  • No Django.
  • No business logic.
  • No dependencies beyond pydantic.

If you find yourself reaching for any of those while writing code here, you've probably wandered into the LMS's purelms.simulations Django app or a specific backend's runner — not this package.

Why it's MIT

PureLMS is AGPL-3.0-or-later (matching Validibot's Community Edition). Simulation backends are MIT so educator-contributors aren't burdened with copyleft for the per-backend image they ship.

The only artifact that crosses the AGPL/MIT boundary is this schema package — so it's MIT too. Neither side imports the other's code; they share a data contract.

Versioning

Schema versions are dotted-package strings, not integers:

  • purelms.input.v1 — input envelope shape v1
  • purelms.output.v1 — output envelope shape v1
  • purelms.evidence.v1 — evidence manifest shape v1

Additive changes (new optional field with a default) ship in v1. Incompatible changes bump to v2 with a new class living alongside the old one — running backends pin specific versions of this package, and we never break old envelopes.

"Additive within v1" is NOT backward compatible at the wire level. Pydantic's ConfigDict(extra="forbid") on every envelope class means a consumer pinning an older purelms-shared will REJECT messages from a newer producer that include the new field. Bump the package version and update every consumer's floor pin in lockstep — this was the discipline unit_block_id followed when it was added in 0.4.0.

Updating the wire format — lockstep consumer updates

When you add a field to an envelope or a value to a shared enum, update these consumer files in the SAME PR:

Python consumers (each pins a purelms-shared>=X.Y.Z floor):

  • purelms/pyproject.toml (and re-resolve the lockfile)
  • purelms-interactive-tasks/pyproject.toml
  • purelms-interactive-tasks/echo/backend/pyproject.toml
  • purelms-interactive-tasks/energyplus_single_zone/backend/pyproject.toml
  • purelms-interactive-tasks/_template/backend/pyproject.toml
  • Any other per-InteractiveTask backend/pyproject.toml

TypeScript consumers (vendor the wire types — extra="forbid" doesn't apply here, but unions out of sync silently break bundles branching on the missing case):

  • purelms/purelms/static/src/ts/sims/api/types.ts — the LMS-side dispatcher's authoritative TS types
  • purelms-interactive-tasks/echo/frontend/src/echo.ts — echo's vendored inline types
  • purelms-interactive-tasks/energyplus_single_zone/frontend/src/types.ts — EnergyPlus's vendored types
  • Any other per-InteractiveTask frontend/src/types.ts

The wire-format-sync tests in purelms/purelms/simulations/tests/test_wire_format_sync.py parametrize over each enum value and grep the LMS-side TS file for it — they fail loudly if a Python enum gains a value that doesn't appear in the matching TS union. Run them in CI on every push.

Why this matters: Slice 3d's post-shipping review round 2 caught a real drift — the TS RunStatus union was missing failed_simulation, failed_runtime, and timed_out entirely. Bundles branching on status would have silently failed to render any FAILED_* UI. The lockstep procedure above + the sync tests close that failure mode.

On extra="forbid" — why strictness over flexibility

Every envelope class in this package sets ConfigDict(extra="forbid", frozen=True). That's the choice driving the lockstep-update tax above: a consumer pinning purelms-shared<X will reject a message from a producer at >=X that includes a new field, even when the new field is purely additive and the consumer wouldn't have read it anyway.

We considered the looser alternative — extra="ignore" — and rejected it. The reasoning, recorded here so future contributors don't re-litigate:

  • Wire schemas are a credential surface, not just a transport. The output envelope is what evidence manifests and credentials are computed from. A field the producer thinks is meaningful but the consumer silently ignores is the classic shape of evidence drift. extra="forbid" turns "the consumer is one version behind" from a silent mis-credential into a loud rejection at deserialization time.
  • The lockstep cost is bounded and visible. Six pyproject.toml floor-pins plus a handful of TS type files — all enumerated in the section above, all enforced by the test_wire_format_sync.py parametrized tests. A grep-and-bump operation, not an open-ended migration. The cost is real but pays for itself the first time it catches a drift like Slice 3d round 2's RunStatus mismatch.
  • The alternative failure mode is worse. With extra="ignore", a backend author adding provenance_hash to the output envelope and an LMS one version behind would silently strip it on read. The credential issued from that run would lack the field. The bug surfaces months later in audit — if at all. Strict mode surfaces it on the first run.

The escape hatch. If the lockstep tax becomes painful in practice, the relaxation is mechanical: change extra="forbid" to extra="ignore" on the relevant envelope class (or all of them) and bump to v2. Old consumers keep working; new producers can add fields without coordinating updates. The decision is reversible — we'd lose the drift-catching property in exchange for looser coupling. We didn't bake the strictness into the wire format itself; it's a per-class Pydantic setting.

Three triggers that would prompt revisiting:

  1. A third external InteractiveTask author appears (i.e. someone outside the PureLMS core team writing a backend). At that point the shared schema + lockstep-floor-pin workflow becomes a contributor-onboarding tax worth paying for — likely by extracting the TS types into an npm-published @purelms/shared-types package so frontend bundles can npm install instead of vendoring.
  2. Backend authors complain that the lockstep update is blocking them — e.g. "I want to add simulation_seed to my outputs but I can't ship until the LMS bumps its purelms-shared floor." That's the signal the strictness cost has crossed the line from disciplined to obstructive.
  3. A second implementation language wants to write backends (Go, Rust, Node). At that point the right answer probably isn't "port Pydantic" but "generate JSON Schema from these classes and let each language consume it." The strictness invariant survives the language change; the implementation does not.

None of those triggers are tripped today. If they trip in the future, this section is the breadcrumb back to the decision.

Install

uv add purelms-shared
# or
pip install purelms-shared

Quick usage:

from purelms_shared import (
    SimulationInputEnvelope,
    SimulationOutputEnvelope,
    OutputStatus,
    Message,
    MessageLevel,
)

# Backends construct the output envelope at completion:
out = SimulationOutputEnvelope(
    run_id=run_id,
    status=OutputStatus.SUCCESS,
    outputs={"annual_eui_kbtu_ft2": 47.3},
    messages=[
        Message(level=MessageLevel.INFO, code="EPLUS.OK", text="Annual run complete"),
    ],
    runtime_seconds=23.4,
)

Architecture context

See the PureLMS simulation backend contract and simulation runtime protocol for the full surrounding design.

Legacy

purelms_shared.energyplus carries an older EnergyPlus-specific result schema used by the (soon-to-be-retired) Modal runtime in purelms-modal. It will be removed when that runtime is phased out in favour of the pluggable simulation-execution model. New code should use the generic envelope schemas above.

License

MIT — see LICENSE.

Metadata

Release files for purelms-shared 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for purelms-shared 0.5.1
File Size Uploaded Provenance
purelms_shared-0.5.1.tar.gz 34.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for purelms-shared 0.5.1
File Interpreter ABI Platform
purelms_shared-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 62.5 kB

Release files / purelms_shared-0.5.1.tar.gz

Download URL purelms_shared-0.5.1.tar.gz
Size 34.3 kB
Tags Source
SHA-256 checksum
How to use checksums
ca67a4668bfd32ad9392562a53209b4ac84685e37bcc282e5a50469dbfec6a70
BLAKE2b-256 checksum
How to use checksums
49cddf08e84bef3cb3ee5a8eb36f11eadd2c03f0bec9f0c518a514555b72b321
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Release files / purelms_shared-0.5.1-py3-none-any.whl

Download URL purelms_shared-0.5.1-py3-none-any.whl
Size 28.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45b6ad2da1869b17da846f16604f0fea807621cf03c5c1a854283cf31532c1aa
BLAKE2b-256 checksum
How to use checksums
9f52d6d7b011efd42a72b2ed7737db6dc4508657276459964b9dc6b2b1dcfd88
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

Security for purelms-shared 0.5.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for purelms_shared-0.5.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page