simulo-interfaces 0.18.0__tar.gz → 0.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {simulo_interfaces-0.18.0/src/simulo_interfaces.egg-info → simulo_interfaces-0.20.0}/PKG-INFO +1 -1
  2. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/pyproject.toml +1 -1
  3. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/__init__.py +8 -1
  4. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/asset_catalog.py +1 -1
  5. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/enums.py +42 -2
  6. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/manifest.py +3 -3
  7. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/outputs.py +3 -2
  8. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/runs.py +18 -2
  9. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/submit.py +68 -8
  10. simulo_interfaces-0.20.0/src/simulo/interfaces/platform/systems.py +106 -0
  11. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0/src/simulo_interfaces.egg-info}/PKG-INFO +1 -1
  12. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo_interfaces.egg-info/SOURCES.txt +1 -0
  13. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/MANIFEST.in +0 -0
  14. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/PYPI.md +0 -0
  15. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/setup.cfg +0 -0
  16. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/__init__.py +0 -0
  17. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/__init__.py +0 -0
  18. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/actuator.py +0 -0
  19. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/asset.py +0 -0
  20. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/entity.py +0 -0
  21. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/light.py +0 -0
  22. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/materials.py +0 -0
  23. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/physics.py +0 -0
  24. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/pose.py +0 -0
  25. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/primitives.py +0 -0
  26. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/prop.py +0 -0
  27. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/record.py +0 -0
  28. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/robot.py +0 -0
  29. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/scene.py +0 -0
  30. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/sensor.py +0 -0
  31. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/terrain.py +0 -0
  32. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/visual.py +0 -0
  33. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/authoring/world.py +0 -0
  34. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/exceptions.py +0 -0
  35. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/ids.py +0 -0
  36. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/_payload.py +0 -0
  37. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/app.py +0 -0
  38. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/asset.py +0 -0
  39. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/callbacks.py +0 -0
  40. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/debug.py +0 -0
  41. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/domain.py +0 -0
  42. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/job_events.py +0 -0
  43. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/policy_bundle.py +0 -0
  44. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/runtime.py +0 -0
  45. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/platform/volume.py +0 -0
  46. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/py.typed +0 -0
  47. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/__init__.py +0 -0
  48. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/anomaly.py +0 -0
  49. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/components.py +0 -0
  50. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/env.py +0 -0
  51. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/player.py +0 -0
  52. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/policy.py +0 -0
  53. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/scenario.py +0 -0
  54. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/task.py +0 -0
  55. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/tensors.py +0 -0
  56. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo/interfaces/runtime/trainer.py +0 -0
  57. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo_interfaces.egg-info/dependency_links.txt +0 -0
  58. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo_interfaces.egg-info/requires.txt +0 -0
  59. {simulo_interfaces-0.18.0 → simulo_interfaces-0.20.0}/src/simulo_interfaces.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo-interfaces
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: Type contracts for the Simulo SDK. Installed automatically as a dependency of the simulo package.
5
5
  Author-email: Simulo Team <team@simulo.ai>
6
6
  License: BSD-3-Clause
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simulo-interfaces"
7
- version = "0.18.0"
7
+ version = "0.20.0"
8
8
  description = "Type contracts for the Simulo SDK. Installed automatically as a dependency of the simulo package."
9
9
  readme = "PYPI.md"
10
10
  requires-python = ">=3.11"
@@ -70,7 +70,7 @@ from simulo.interfaces.platform.asset_catalog import (
70
70
  from simulo.interfaces.platform.callbacks import JobCallbackProtocol
71
71
  from simulo.interfaces.platform.debug import DebugSessionProtocol, ReplaySessionProtocol, VisualizationSessionProtocol
72
72
  from simulo.interfaces.platform.domain import Checkpoint, Job, Package, Project, Resource, Tag, TrainedModel
73
- from simulo.interfaces.platform.enums import OutputKind
73
+ from simulo.interfaces.platform.enums import OutputKind, SystemType
74
74
  from simulo.interfaces.platform.job_events import (
75
75
  MAX_PUBLIC_ASSET_FINDING_COUNT,
76
76
  MAX_PUBLIC_JOB_EVENT_ATTEMPT,
@@ -228,6 +228,7 @@ from simulo.interfaces.platform.submit import (
228
228
  SEED_JOB_REF_GRAMMAR,
229
229
  SEED_SOURCE_CONFLICT_CODE,
230
230
  SEED_SOURCE_FIELDS,
231
+ WORKER_BOOTSTRAP_ROUTE,
231
232
  WORKER_CLAIM_ROUTE,
232
233
  WORKER_JOB_COMPLETE_ROUTE_TEMPLATE,
233
234
  WORKER_JOB_HEARTBEAT_ROUTE_TEMPLATE,
@@ -240,11 +241,16 @@ from simulo.interfaces.platform.submit import (
240
241
  canonical_pypi_name,
241
242
  is_platform_pinned,
242
243
  )
244
+ from simulo.interfaces.platform.systems import SYSTEM_SPECS, SystemSpec
243
245
  from simulo.interfaces.platform.volume import VolumeProtocol
244
246
 
245
247
  __all__ = [
246
248
  # enums
247
249
  "OutputKind",
250
+ "SystemType",
251
+ # systems (GPU system-tier catalog, issue #1362)
252
+ "SystemSpec",
253
+ "SYSTEM_SPECS",
248
254
  # closed public job-event contract (#656)
249
255
  "PUBLIC_JOB_EVENT_SCHEMA_VERSION",
250
256
  "WORKER_JOB_EVENTS_ROUTE_TEMPLATE",
@@ -346,6 +352,7 @@ __all__ = [
346
352
  "JOB_MODELS_ROUTE_TEMPLATE",
347
353
  "JOB_MODEL_ROUTE_TEMPLATE",
348
354
  "JOB_MODEL_DOWNLOAD_ROUTE_TEMPLATE",
355
+ "WORKER_BOOTSTRAP_ROUTE",
349
356
  "WORKER_CLAIM_ROUTE",
350
357
  "WORKER_JOB_LOGS_ROUTE_TEMPLATE",
351
358
  "WORKER_JOB_HEARTBEAT_ROUTE_TEMPLATE",
@@ -493,7 +493,7 @@ ASSET_VERSION_REPORT_ROUTE_TEMPLATE = "/v1/asset-versions/{version_id}/report"
493
493
  ASSET_VERSION_DOWNLOAD_ROUTE_TEMPLATE = "/v1/asset-versions/{version_id}/download"
494
494
 
495
495
  # --------------------------------------------------------------------------
496
- # Worker routes (bearer = static WORKER_TOKEN; + lease header, see submit.py)
496
+ # Worker routes (bearer = per-worker identity JWT; + lease header, see submit.py)
497
497
  # --------------------------------------------------------------------------
498
498
  #
499
499
  # The claim response (submit.WORKER_CLAIM_ROUTE) additionally carries an
@@ -11,8 +11,9 @@ class JobStatus(StrEnum):
11
11
  """Lifecycle state of a submitted job.
12
12
 
13
13
  A job moves ``QUEUED`` → ``RUNNING`` → one terminal state
14
- (``COMPLETED`` / ``FAILED`` / ``CANCELLED``). Terminal states never change
15
- again — logs and results are final once a job reaches one.
14
+ (``COMPLETED`` / ``FAILED`` / ``CANCELLED`` / ``TIMED_OUT``). Terminal
15
+ states never change again — logs and results are final once a job
16
+ reaches one.
16
17
  """
17
18
 
18
19
  QUEUED = "queued"
@@ -30,6 +31,23 @@ class JobStatus(StrEnum):
30
31
  CANCELLED = "cancelled"
31
32
  """Stopped on request (``simulo cancel``) before completing."""
32
33
 
34
+ TIMED_OUT = "timed_out"
35
+ """Stopped for exceeding its organization's configured runtime limit —
36
+ distinct from :attr:`FAILED`: the job did not crash, it ran past its
37
+ organization's configured runtime limit and was stopped by the platform
38
+ (the supervisor's own kill, or a backstop reaper after the deadline plus
39
+ a grace period). Any checkpoint the run had produced by then is salvaged
40
+ the same way a cancelled run's is.
41
+
42
+ Appended, never inserted: this member was added after the first five and
43
+ keeps its place at the end of the wire enum per this module's own
44
+ append-only rule. A client should treat this value as
45
+ terminal-and-distinct wherever it reaches one directly (e.g. a
46
+ worker-authored `complete` body); the control plane's own public read
47
+ routes may still project it as `FAILED` for compatibility with clients
48
+ published before this member existed — see that rollout's own record for
49
+ when the projection lifts."""
50
+
33
51
 
34
52
  class ResumePolicy(StrEnum):
35
53
  """How a job resumes from a discovered checkpoint."""
@@ -44,6 +62,28 @@ class ResumePolicy(StrEnum):
44
62
  """Resume only on explicit request. Reserved: currently behaves like ``NEVER``."""
45
63
 
46
64
 
65
+ class SystemType(StrEnum):
66
+ """An abstract GPU system tier.
67
+
68
+ Each tier's hardware profile and fleet-provisioning state lives in
69
+ :data:`simulo.interfaces.platform.systems.SYSTEM_SPECS`, keyed by these
70
+ members. The AWS instance type behind a tier is never part of this name —
71
+ see :class:`simulo.interfaces.platform.systems.SystemSpec`'s own docstring.
72
+ """
73
+
74
+ TIER_1 = "tier1"
75
+ """Entry tier — always available."""
76
+
77
+ TIER_2 = "tier2"
78
+ """Mid tier — always available."""
79
+
80
+ TIER_3 = "tier3"
81
+ """High tier — not yet available on the deployable fleet."""
82
+
83
+ TIER_4 = "tier4"
84
+ """Top tier — not yet available on the deployable fleet."""
85
+
86
+
47
87
  class ResourceKind(StrEnum):
48
88
  """Kind of a registry-resolved, read-only resource mount."""
49
89
 
@@ -164,9 +164,9 @@ class Manifest:
164
164
  #: this endpoint must not break a newer client.
165
165
  META_ROUTE = "/v1/meta"
166
166
 
167
- #: ``POST`` — the worker's runtime-manifest self-report (bearer = static
168
- #: ``WORKER_TOKEN``, same trust zone as every ``/internal/worker/*`` route —
169
- #: see :mod:`simulo.interfaces.platform.submit`). The worker introspects its
167
+ #: ``POST`` — the worker's runtime-manifest self-report (bearer = the
168
+ #: per-worker identity JWT, same trust zone as every ``/internal/worker/*``
169
+ #: route — see :mod:`simulo.interfaces.platform.submit`). The worker introspects its
170
170
  #: own interpreter (``importlib.metadata.distributions()``) on startup and
171
171
  #: POSTs a :class:`RuntimeManifest` — the reporter *is* the runtime.
172
172
  #: **Best-effort by contract:** a failed report must never stop a worker from
@@ -196,8 +196,9 @@ JOB_OUTPUTS_ROUTE_TEMPLATE = "/v1/jobs/{job_id}/outputs"
196
196
  JOB_OUTPUT_DOWNLOAD_ROUTE_TEMPLATE = "/v1/jobs/{job_id}/outputs/{output_id}/download"
197
197
 
198
198
  # --------------------------------------------------------------------------
199
- # Worker-facing routes (bearer = static WORKER_TOKEN, constant-time compare;
200
- # same auth surface as the recordings/models worker routes in submit.py)
199
+ # Worker-facing routes (bearer = the per-worker identity JWT
200
+ # `WORKER_BOOTSTRAP_ROUTE` mints, verified server-side per call; same auth
201
+ # surface as the recordings/models worker routes in submit.py)
201
202
  # --------------------------------------------------------------------------
202
203
 
203
204
  #: ``POST`` — obtain a presigned upload URL for a new general output (a
@@ -302,7 +302,16 @@ LOG_ARCHIVE_READ_FAILED_CODE = "log_archive_read_failed"
302
302
 
303
303
  #: Statuses after which a job's log stream and record no longer change. A log
304
304
  #: follower drains the remaining bytes and stops once the status is terminal.
305
- TERMINAL_JOB_STATUSES = frozenset({JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELLED})
305
+ #:
306
+ #: ``TIMED_OUT`` joined this set in `M55-org-runtime-policy` (issue #1537).
307
+ #: The control plane's public read routes do not emit it yet (they project a
308
+ #: `timed_out` job as `FAILED` with `status_reason="timeout"` until a
309
+ #: tolerant-client floor is recorded — see :class:`~simulo.interfaces.
310
+ #: platform.enums.JobStatus`'s own docstring), so this set already carrying
311
+ #: it is what makes a client that widens itself ahead of that projection
312
+ #: lift (per this contract's own unknown-status-is-terminal guidance) treat
313
+ #: the value correctly the moment it does appear on the wire.
314
+ TERMINAL_JOB_STATUSES = frozenset({JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELLED, JobStatus.TIMED_OUT})
306
315
 
307
316
 
308
317
  @dataclass(frozen=True, slots=True, kw_only=True)
@@ -334,7 +343,14 @@ class JobRecord:
334
343
  """When the job was created (ISO 8601 UTC, e.g. ``2026-07-03T12:00:00Z``)."""
335
344
 
336
345
  args: Mapping[str, Any] = field(default_factory=dict)
337
- """The keyword arguments the job body was invoked with."""
346
+ """The keyword arguments the job body was invoked with, AS SERVED ON
347
+ THIS RECORD — not necessarily what the job actually ran with. The
348
+ control plane redacts any credential-shaped key or value (replacing it
349
+ with the literal string ``"<redacted>"``) when building this public
350
+ record; the job itself executed with the real, unredacted values. A
351
+ client reading this field to display a job's history sees the redacted
352
+ view; nothing about a redacted entry here implies the run itself saw
353
+ ``"<redacted>"``."""
338
354
 
339
355
  started_at: Optional[str] = None
340
356
  """When execution began (ISO 8601 UTC). ``None`` while queued."""
@@ -25,10 +25,16 @@ architecture section for the full picture):
25
25
  * **Client-facing `/v1/...`** — bearer = the user's Cognito JWT;
26
26
  ``require_active_org`` is enforced server-side. These are the routes a
27
27
  human's ``simulo`` CLI/SDK calls.
28
- * **Worker-facing `/internal/worker/...`** — bearer = a single static
29
- ``WORKER_TOKEN`` (constant-time compare on the server, never logged). Empty
30
- server-side config disables every ``/internal/worker/*`` route
31
- fail-closed there is no "auth disabled" state for these routes.
28
+ * **Worker-facing `/internal/worker/...`** — bearer = a short-lived,
29
+ per-worker identity JWT (see :data:`WORKER_BOOTSTRAP_ROUTE` below). The worker obtains it from an
30
+ IAM-signed bootstrap call, unique per EC2 instance identity; the server
31
+ verifies it (audience, issuer, expiry, and live, on every call — the
32
+ issuing worker row's revocation status) rather than comparing against a
33
+ single shared secret. Empty server-side signing-secret config disables
34
+ every ``/internal/worker/*`` route fail-closed — there is no "auth
35
+ disabled" state for these routes. (Before this cutover the bearer was a
36
+ single static ``WORKER_TOKEN`` shared by the whole fleet; that scheme is
37
+ retired.)
32
38
  * **Presigned URLs** (`download_url` / `upload_url` fields below) carry their
33
39
  own auth (S3 SigV4 query params, or a control-plane-served one-time token
34
40
  when running on the local storage backend) — a caller must send the
@@ -159,8 +165,28 @@ platform-standard shape: ``{"error": {"code": ..., "message": ..., "request_id":
159
165
 
160
166
  ## Worker-facing (`/internal/worker/*`)
161
167
 
162
- * :data:`WORKER_CLAIM_ROUTE` (``POST``) — request ``{"worker_id": "..."}``.
163
- Response ``200``::
168
+ * :data:`WORKER_BOOTSTRAP_ROUTE` (``POST``) — request
169
+ ``{"signature_b64": "..."}``, the caller's own IAM-signed EC2 instance
170
+ identity document exactly as ``dynamic/instance-identity/rsa2048``
171
+ returns it (base64, no armor). Carries NO shared secret of its own — the
172
+ signed document IS the credential. Response ``200``::
173
+
174
+ {"worker_id": "wkr_...", "tier": "tier1", "pinned_organization_id": null,
175
+ "status": "active", "token": "<jwt>", "token_expires_at": "2026-07-03T13:00:00Z"}
176
+
177
+ Idempotent per EC2 instance identity: a repeat call from an instance that
178
+ already holds a worker identity returns that SAME identity (still pinned,
179
+ if pinned) with a freshly-issued ``token`` — the recommended renewal
180
+ cadence is half of the deployment's configured token TTL. ``token`` is
181
+ the bearer every OTHER ``/internal/worker/*`` route now requires (below);
182
+ nothing on the wire ever carries the ``WORKER_TOKEN`` shared bearer this
183
+ route's own token retired.
184
+
185
+ * :data:`WORKER_CLAIM_ROUTE` (``POST``) — request body carries no worker
186
+ identity field: the claiming worker is whoever the ``Authorization``
187
+ bearer (above) verifies to, never a caller-asserted value — a
188
+ pre-cutover claim body's ``{"worker_id": "..."}`` was trusted outright,
189
+ the actual gap this contract change closed. Response ``200``::
164
190
 
165
191
  {"job": <JobRecord>,
166
192
  "package": {"download_url": "...", "tar_sha256": "sha256:...", "size_bytes": 123456},
@@ -495,10 +521,19 @@ MAX_SEED_REF_CHARS = 80
495
521
  SEED_SOURCE_CONFLICT_CODE = "seed_source_conflict"
496
522
 
497
523
  # --------------------------------------------------------------------------
498
- # Worker-facing routes (bearer = static WORKER_TOKEN, constant-time compare;
499
- # empty server-side config disables every route below, fail-closed)
524
+ # Worker-facing routes (bearer = a per-worker identity JWT this bootstrap
525
+ # route mints; empty server-side signing-secret config disables every
526
+ # OTHER route below, fail-closed — see the module docstring's trust zones)
500
527
  # --------------------------------------------------------------------------
501
528
 
529
+ #: ``POST`` — bind (or re-bind) a worker identity to the calling EC2 instance
530
+ #: via an IAM-signed instance identity document. Body
531
+ #: ``{"signature_b64": "..."}``. Carries NO shared secret of its own — the
532
+ #: signed document IS this route's authentication, which is the whole point
533
+ #: of it existing: it is the ONE route that MINTS the token every OTHER
534
+ #: ``/internal/worker/*`` route now requires, so it cannot itself require
535
+ #: that token without a chicken-and-egg cycle.
536
+ WORKER_BOOTSTRAP_ROUTE = "/internal/worker/bootstrap"
502
537
  #: ``POST`` — atomically claim the next queued job (``FOR UPDATE SKIP LOCKED``).
503
538
  WORKER_CLAIM_ROUTE = "/internal/worker/claim"
504
539
  #: ``POST`` — append ordinary-job raw log bytes at a byte offset
@@ -850,6 +885,31 @@ RESERVED_RUNTIME_ENV_KEYS = frozenset(
850
885
  "SIMULO_VIEWSTREAM_ENABLED",
851
886
  "SIMULO_RUNTIME_FACTS_PATH",
852
887
  "MAX_JOB_TIMEOUT_S",
888
+ # M40-job-sandbox (issue #527): the container launcher sets this to
889
+ # advertise the job container's publicly reachable stream endpoint
890
+ # (`--/app/livestream/publicEndpointAddress=<instance IP>`) to Kit —
891
+ # a bridge-networked job container needs this because its own
892
+ # address is NAT'd, unlike the pre-M40 host-networking deployment
893
+ # where Kit's default self-detected address was already correct.
894
+ # `simulo/backend/isaaclab/stream_adapter.py`'s `get_launch_args`
895
+ # merges this env var in LAST, so an unreserved key would let a
896
+ # manifest's own `runtime_env` override the launcher's own address
897
+ # and redirect where the job's live-view media is advertised
898
+ # (ADR-0016's threat table: "Job redirects the stream endpoint via a
899
+ # job-controllable env key"). Reserving it here is the
900
+ # denylist-enforcement half of the fix; `worker._child_env` is the
901
+ # other half, unconditionally scrubbing any ambient value before the
902
+ # launcher re-sets it from the resolved instance address.
903
+ "SIMULO_KIT_EXTRA_ARGS",
904
+ # M40-job-sandbox (issue #527): the same "worker mount wins" seam as
905
+ # SIMULO_CHECKPOINT_DIR / SIMULO_ANOMALY_DEBUG_DIR — the worker
906
+ # pre-resolves and bind-mounts the validation run-dir output
907
+ # contract (verdict/report/facts/runtime-findings) at this path
908
+ # before an asset-validation job's container starts, because that
909
+ # container's own `~` is ephemeral tmpfs the worker could never
910
+ # read back. A manifest-borne override would point the worker's own
911
+ # later read at a job-chosen path instead.
912
+ "SIMULO_VALIDATION_OUTPUT_DIR",
853
913
  }
854
914
  )
855
915
 
@@ -0,0 +1,106 @@
1
+ """GPU system-tier catalog — the closed hardware contract behind `SystemType`.
2
+
3
+ `SYSTEM_SPECS` is the single published source of truth for what each tier
4
+ means: the marketing GPU name, its VRAM, the host's vCPU/memory, whether the
5
+ tier is provisionable on the deployable fleet today, and (internal-only) the
6
+ AWS instance type behind it. Nothing here schedules a job against real
7
+ hardware — selection stays record-only until a later milestone adds live
8
+ enforcement.
9
+
10
+ Stdlib-only (`dataclasses` plus the sibling `enums` module): this file must
11
+ import nothing else, so the package's zero-runtime-dependency guarantee
12
+ (`test_no_heavy_deps.py`) holds.
13
+ """
14
+
15
+ from dataclasses import dataclass
16
+
17
+ from simulo.interfaces.platform.enums import SystemType
18
+
19
+ # Shared dataclass options: immutable value objects (frozen), memory-lean
20
+ # (slots), and keyword-only construction (kw_only) — see `platform/domain.py`'s
21
+ # identical `_DC` for the full rationale.
22
+ _DC = {"frozen": True, "slots": True, "kw_only": True}
23
+
24
+
25
+ @dataclass(**_DC)
26
+ class SystemSpec:
27
+ """One GPU tier's hardware profile and fleet-provisioning state.
28
+
29
+ `gpu_model` (e.g. `"T4"`) is the user-facing marketing name — the only
30
+ hardware identity a CLI table, `--json` output, or documentation page may
31
+ ever show. `instance_type` is the AWS EC2 instance type behind the tier,
32
+ kept here ONLY for an offline fleet crosscheck against the deployable
33
+ CloudFormation allowlist and the control plane's own bookkeeping — it
34
+ must never reach a user-facing surface. Hiding it is deliberate: a future
35
+ author copying an AWS SKU into an example is exactly how `gpu="L4"`
36
+ (actually Tier 3, not provisionable) ended up in 40 tracked files before
37
+ this catalog existed.
38
+ """
39
+
40
+ system: SystemType
41
+ gpu_model: str
42
+ vram_gb: int
43
+ vcpu: int
44
+ memory_gb: int
45
+ max_parallel_envs: int | None
46
+ available: bool
47
+ instance_type: str
48
+
49
+
50
+ # vcpu/memory_gb: AWS's published instance-type specifications
51
+ # (https://docs.aws.amazon.com/ec2/latest/instancetypes/ac.html, confirmed at
52
+ # M20-system-catalog implementation time, 2026-09-07) for
53
+ # g4dn.xlarge/g5.xlarge/g6.xlarge/g6e.xlarge. vram_gb: each GPU model's
54
+ # published VRAM capacity (NVIDIA product specifications) — deliberately NOT
55
+ # the ~22/44 GiB AWS's own instance-type page reports as *usable* GPU memory
56
+ # on g5/g6/g6e after ECC/hypervisor reservation, since `vram_gb` documents the
57
+ # hardware users are told they are getting (`gpu_model`), not a fleet
58
+ # provisioning detail.
59
+ SYSTEM_SPECS: dict[SystemType, SystemSpec] = {
60
+ SystemType.TIER_1: SystemSpec(
61
+ system=SystemType.TIER_1,
62
+ gpu_model="T4",
63
+ vram_gb=16,
64
+ vcpu=4,
65
+ memory_gb=16,
66
+ # Measured ceiling for g4dn.xlarge's default workload (`.claude/skills/isaaclab/SKILL.md`,
67
+ # `.claude/skills/aws-infra/SKILL.md`, `cloud-infra/gpu-worker/gpu-worker.yaml`): 16
68
+ # parallel environments; a 4096-environment run exhausted GPU memory and made the
69
+ # instance unrecoverable. `aws-infra/SKILL.md` is explicit that this is
70
+ # workload/instance evidence for the template's default workload, not a universal
71
+ # constant — re-benchmark before relying on it for a different workload shape.
72
+ max_parallel_envs=16,
73
+ available=True,
74
+ instance_type="g4dn.xlarge",
75
+ ),
76
+ SystemType.TIER_2: SystemSpec(
77
+ system=SystemType.TIER_2,
78
+ gpu_model="A10G",
79
+ vram_gb=24,
80
+ vcpu=4,
81
+ memory_gb=16,
82
+ max_parallel_envs=None,
83
+ available=True,
84
+ instance_type="g5.xlarge",
85
+ ),
86
+ SystemType.TIER_3: SystemSpec(
87
+ system=SystemType.TIER_3,
88
+ gpu_model="L4",
89
+ vram_gb=24,
90
+ vcpu=4,
91
+ memory_gb=16,
92
+ max_parallel_envs=None,
93
+ available=False,
94
+ instance_type="g6.xlarge",
95
+ ),
96
+ SystemType.TIER_4: SystemSpec(
97
+ system=SystemType.TIER_4,
98
+ gpu_model="L40S",
99
+ vram_gb=48,
100
+ vcpu=4,
101
+ memory_gb=32,
102
+ max_parallel_envs=None,
103
+ available=False,
104
+ instance_type="g6e.xlarge",
105
+ ),
106
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo-interfaces
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: Type contracts for the Simulo SDK. Installed automatically as a dependency of the simulo package.
5
5
  Author-email: Simulo Team <team@simulo.ai>
6
6
  License: BSD-3-Clause
@@ -38,6 +38,7 @@ src/simulo/interfaces/platform/policy_bundle.py
38
38
  src/simulo/interfaces/platform/runs.py
39
39
  src/simulo/interfaces/platform/runtime.py
40
40
  src/simulo/interfaces/platform/submit.py
41
+ src/simulo/interfaces/platform/systems.py
41
42
  src/simulo/interfaces/platform/volume.py
42
43
  src/simulo/interfaces/runtime/__init__.py
43
44
  src/simulo/interfaces/runtime/anomaly.py