simulo-interfaces 0.11.0__tar.gz → 0.13.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 (55) hide show
  1. {simulo_interfaces-0.11.0/src/simulo_interfaces.egg-info → simulo_interfaces-0.13.0}/PKG-INFO +1 -1
  2. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/pyproject.toml +6 -3
  3. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/__init__.py +6 -4
  4. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/pose.py +2 -3
  5. simulo_interfaces-0.13.0/src/simulo/interfaces/authoring/prop.py +210 -0
  6. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/record.py +3 -3
  7. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/__init__.py +1 -14
  8. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/artifacts.py +3 -1
  9. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/asset_catalog.py +57 -49
  10. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/runs.py +39 -5
  11. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/submit.py +37 -4
  12. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/__init__.py +1 -2
  13. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0/src/simulo_interfaces.egg-info}/PKG-INFO +1 -1
  14. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo_interfaces.egg-info/SOURCES.txt +1 -0
  15. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/MANIFEST.in +0 -0
  16. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/PYPI.md +0 -0
  17. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/setup.cfg +0 -0
  18. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/__init__.py +0 -0
  19. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/actuator.py +0 -0
  20. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/asset.py +0 -0
  21. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/entity.py +0 -0
  22. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/light.py +0 -0
  23. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/materials.py +0 -0
  24. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/physics.py +0 -0
  25. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/primitives.py +0 -0
  26. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/robot.py +0 -0
  27. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/scene.py +0 -0
  28. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/sensor.py +0 -0
  29. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/terrain.py +0 -0
  30. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/visual.py +0 -0
  31. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/authoring/world.py +0 -0
  32. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/exceptions.py +0 -0
  33. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/ids.py +0 -0
  34. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/app.py +0 -0
  35. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/asset.py +0 -0
  36. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/callbacks.py +0 -0
  37. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/debug.py +0 -0
  38. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/domain.py +0 -0
  39. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/enums.py +0 -0
  40. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/manifest.py +0 -0
  41. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/runtime.py +0 -0
  42. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/platform/volume.py +0 -0
  43. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/py.typed +0 -0
  44. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/anomaly.py +0 -0
  45. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/components.py +0 -0
  46. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/env.py +0 -0
  47. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/player.py +0 -0
  48. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/policy.py +0 -0
  49. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/scenario.py +0 -0
  50. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/task.py +0 -0
  51. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/tensors.py +0 -0
  52. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo/interfaces/runtime/trainer.py +0 -0
  53. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo_interfaces.egg-info/dependency_links.txt +0 -0
  54. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.0}/src/simulo_interfaces.egg-info/requires.txt +0 -0
  55. {simulo_interfaces-0.11.0 → simulo_interfaces-0.13.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.11.0
3
+ Version: 0.13.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.11.0"
7
+ version = "0.13.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"
@@ -26,8 +26,11 @@ classifiers = [
26
26
  dependencies = []
27
27
 
28
28
  [project.optional-dependencies]
29
- # black/isort/ruff/mypy pinned EXACT, not >=: see simulo-backend/pyproject.toml's
30
- # `dev` extra for the full rationale (2026-08-01 ruff 0.15.21->0.16.1 CI incident).
29
+ # black/isort/ruff/mypy pinned EXACT, not >=: these are rule-set tools whose
30
+ # defaults change between releases, so an unbounded lower bound lets CI silently
31
+ # resolve a newer release than a developer's environment has — "clean locally" and
32
+ # "hundreds of errors in CI" both true of one commit (2026-08-01: ruff 0.15.21 ->
33
+ # 0.16.1 flagged 1088 findings from new default rules, zero of them real).
31
34
  # Keep in lockstep with `.github/workflows/simulo-interfaces-ci.yml`'s
32
35
  # `ruff`/`black`/`isort`/`mypy` steps, which resolve their versions from THIS extra
33
36
  # (`pip install -e ".[dev]"`).
@@ -106,10 +106,11 @@ from simulo.interfaces.authoring.primitives import (
106
106
  Sphere,
107
107
  Transform,
108
108
  )
109
+ from simulo.interfaces.authoring.prop import PropProtocol, PropStateProtocol
109
110
  from simulo.interfaces.authoring.record import RecordConfig
110
111
  from simulo.interfaces.authoring.robot import RobotProtocol, RobotStateProtocol
111
112
  from simulo.interfaces.authoring.scene import SceneGroupProtocol, SceneProtocol
112
- from simulo.interfaces.authoring.sensor import CameraProtocol, SensorProtocol
113
+ from simulo.interfaces.authoring.sensor import CameraProtocol
113
114
  from simulo.interfaces.authoring.terrain import (
114
115
  GroundPlane,
115
116
  RoughTerrain,
@@ -180,11 +181,12 @@ __all__ = [
180
181
  # scene (MIRROR — the concrete classes live in the backend package)
181
182
  "SceneProtocol",
182
183
  "SceneGroupProtocol",
183
- # robot / sensor / actuator (MIRROR — the concrete classes live in the
184
- # backend package and explicitly inherit these Protocols)
184
+ # robot / prop / sensor / actuator (MIRROR — the concrete classes live in
185
+ # the backend package and explicitly inherit these Protocols)
185
186
  "RobotProtocol",
186
187
  "RobotStateProtocol",
187
- "SensorProtocol",
188
+ "PropProtocol",
189
+ "PropStateProtocol",
188
190
  "CameraProtocol",
189
191
  "ActuatorProtocol",
190
192
  ]
@@ -17,8 +17,8 @@ class Pose:
17
17
  xyz: Position as (x, y, z)
18
18
  quat_wxyz: Orientation as quaternion (w, x, y, z)
19
19
 
20
- Example:
21
- ```python
20
+ Example::
21
+
22
22
  # Using named arguments
23
23
  pose = Pose(position=[0.5, 0.3, 0.7], orientation=[1, 0, 0, 0])
24
24
 
@@ -27,7 +27,6 @@ class Pose:
27
27
 
28
28
  # Position only (identity rotation)
29
29
  pose = Pose.from_xyz(0.5, 0.3, 0.7)
30
- ```
31
30
  """
32
31
 
33
32
  xyz: Tuple[float, float, float] = (0.0, 0.0, 0.0)
@@ -0,0 +1,210 @@
1
+ """``PropProtocol`` — the declared contract for a movable scene prop.
2
+
3
+ A MIRROR, not a relocation (the same mechanism as the Robot and Scene
4
+ contracts in this package): the concrete ``Prop`` implementation stays in the
5
+ backend package, because reading and writing a prop's pose reaches through a
6
+ live engine handle and cannot exist in a zero-dependency package. What CAN
7
+ live here is the declared public surface — what a prop is constructed from,
8
+ how its live state is read, and how its pose is written.
9
+
10
+ Why this contract exists at all
11
+ -------------------------------
12
+
13
+ A scene holds two kinds of movable thing: robots, which the simulation drives
14
+ through joints, and *props* — the block on the table, the crate to push, the
15
+ ball to catch. Robots have carried a full pose surface since the beginning
16
+ (``robot.state.pose``, ``set_root_pose``, ``reset``). Props carried none: a
17
+ shape handed to ``scene.add(...)`` is pure authoring data, so the only way to
18
+ reach the spawned body's pose was the engine-private scene index.
19
+
20
+ That gap is what kept manipulation demos *scripted*. An episodic task must
21
+ re-randomise its object at the start of every episode and read the object's
22
+ height back to score the lift — both of which are prop pose access. This
23
+ contract closes it.
24
+
25
+ ``PrimitiveShape``, not every entity
26
+ ------------------------------------
27
+
28
+ A prop is built from a :class:`~simulo.interfaces.authoring.primitives.PrimitiveShape`
29
+ carrying **rigid** physics. That restriction is the honest one rather than a
30
+ convenience: a pose that can be read and written is the simulation's own
31
+ rigid-body state, and only a rigid body has one. A visual-only shape (no
32
+ physics) and a deformable one have no rigid-body pose to write, so the
33
+ implementation refuses them at construction, naming the prop — rather than
34
+ accepting them and silently doing nothing at reset time, which would surface
35
+ much later as "my object never moves".
36
+
37
+ A shape that only needs to be *visible* does not need a prop at all: hand it
38
+ straight to ``scene.add(shape, at=...)`` exactly as before. ``Prop`` is
39
+ strictly the opt-in for "I also need to read or write where this thing is".
40
+
41
+ Tensors, not ``Pose``
42
+ ---------------------
43
+
44
+ Every batched member here is ``TensorLike`` with the environment count as its
45
+ leading dimension, and the write methods take the same. ``Pose`` remains the
46
+ *authoring* type — where the prop spawns, written once as
47
+ ``Cuboid(pose=Pose.from_xyz(...))``. It is deliberately NOT the runtime type:
48
+ a ``Pose`` is one pose, and the entire purpose of this surface is giving each
49
+ of thousands of environments a *different* pose on the same call. See
50
+ ``PropProtocol.set_pose`` for the full reasoning.
51
+
52
+ Enforcement is the standard four layers for a mirror — the type checker alone
53
+ is NOT sufficient (measured on the Scene mirror): mypy catches signature
54
+ *shape* divergence at the backend definition site but is silent on parameter
55
+ renames and on a property degrading to a plain attribute. Those are caught by
56
+ the signature-conformance test in the backend package's suite (per-member,
57
+ both directions, plus member-set equality with a pinned omissions allowlist),
58
+ and the constructor is pinned separately by a ``__call__``-Protocol in the
59
+ backend's static conformance checks (type checkers exempt ``__init__`` from
60
+ override checks). The Protocol stays structural for any other implementer.
61
+ """
62
+
63
+ from typing import Optional, Protocol, runtime_checkable
64
+
65
+ from simulo.interfaces.authoring.primitives import PrimitiveShape
66
+ from simulo.interfaces.runtime.tensors import TensorLike
67
+
68
+
69
+ @runtime_checkable
70
+ class PropStateProtocol(Protocol):
71
+ """The live prop-state contract — batched, read-only, zero-copy.
72
+
73
+ Obtained from a prop as ``prop.state``; never constructed directly. The
74
+ three members are exactly the rigid-body half of
75
+ :class:`~simulo.interfaces.authoring.robot.RobotStateProtocol` and carry
76
+ the same names for the same quantities — a prop has no joints, so the
77
+ three joint members simply do not appear. Code that reads
78
+ ``robot.state.pose`` reads ``prop.state.pose`` identically.
79
+
80
+ Each member is a live view of the simulation's own state buffers: reading
81
+ one returns the current values without copying, at the same cost inside a
82
+ training loop's hot path as anywhere else. All members carry the
83
+ environment batch as their leading dimension (the scene's ``num_envs``).
84
+
85
+ **Read a member each time you need it — do not hold the returned tensor
86
+ across steps, and do not write into it.** The tensor IS the simulation's
87
+ own memory: the engine refreshes these buffers by rebinding them to new
88
+ tensors, so a tensor held across a step can silently serve stale values,
89
+ and writing into one writes into the simulation. The accessor
90
+ (``prop.state`` itself) may be held freely.
91
+
92
+ Members raise ``RuntimeError`` until the simulation runtime has attached
93
+ the prop — read them from ``on_start`` onward. The ``state`` accessor
94
+ itself is always available and is the same object on every access.
95
+ """
96
+
97
+ @property
98
+ def pose(self) -> TensorLike:
99
+ """Pose in the world frame, shape ``(num_envs, 7)``:
100
+ ``[x, y, z, qw, qx, qy, qz]`` (position + w-first quaternion)."""
101
+ ...
102
+
103
+ @property
104
+ def linear_velocity(self) -> TensorLike:
105
+ """Linear velocity in the world frame, shape ``(num_envs, 3)``:
106
+ ``[vx, vy, vz]``."""
107
+ ...
108
+
109
+ @property
110
+ def angular_velocity(self) -> TensorLike:
111
+ """Angular velocity in the world frame, shape ``(num_envs, 3)``:
112
+ ``[wx, wy, wz]``."""
113
+ ...
114
+
115
+
116
+ @runtime_checkable
117
+ class PropProtocol(Protocol):
118
+ """The prop authoring contract — a rigid body whose pose you can read and write.
119
+
120
+ This is the surface authoring code may rely on when it creates a prop,
121
+ adds it to a scene, and moves it: the shape it was built from, its
122
+ identity in the scene, its live state, its spawn pose, and the pose /
123
+ velocity writes an episodic reset needs.
124
+ """
125
+
126
+ # -- Construction-time configuration ----------------------------------
127
+
128
+ shape: PrimitiveShape
129
+ """The primitive shape this prop spawns, carrying its size, material, and
130
+ rigid physics. The shape's own ``pose`` is where the prop spawns."""
131
+
132
+ # -- Identity and attachment ------------------------------------------
133
+
134
+ @property
135
+ def name(self) -> Optional[str]:
136
+ """Prop name — taken from the shape, or set by the scene when added."""
137
+ ...
138
+
139
+ @property
140
+ def path(self) -> Optional[str]:
141
+ """Scene path — set by the scene when the prop is added."""
142
+ ...
143
+
144
+ @property
145
+ def state(self) -> PropStateProtocol:
146
+ """The grouped live prop state — the supported, typed state surface.
147
+
148
+ Always available (the accessor is the same object on every access);
149
+ its members read straight through to the simulation's state buffers
150
+ and raise ``RuntimeError`` until the simulation runtime has attached
151
+ the prop — read them from ``on_start`` onward.
152
+ """
153
+ ...
154
+
155
+ @property
156
+ def default_pose(self) -> TensorLike:
157
+ """The prop's spawn pose in the **world** frame, ``(num_envs, 7)``.
158
+
159
+ The pose the prop was authored at, already offset by each
160
+ environment's own origin — so it is directly comparable with
161
+ ``state.pose`` and directly usable as the base for a randomised
162
+ reset. Index it with the environments being reset
163
+ (``prop.default_pose[env_ids]``), perturb, and write it back with
164
+ :meth:`set_pose`.
165
+
166
+ Raises ``RuntimeError`` until the simulation runtime has attached the
167
+ prop.
168
+ """
169
+ ...
170
+
171
+ # -- Pose writes ------------------------------------------------------
172
+
173
+ def set_pose(self, pose: TensorLike, env_ids: Optional[TensorLike] = ...) -> None:
174
+ """Write the prop's pose directly (teleport, not control), world frame.
175
+
176
+ Args:
177
+ pose: ``(num_envs, 7)`` — or ``(len(env_ids), 7)`` when ``env_ids``
178
+ is given — as ``[x, y, z, qw, qx, qy, qz]``.
179
+ env_ids: Environment indices to write; ``None`` writes every
180
+ environment. Passing the subset being reset is what makes a
181
+ partial episodic reset possible: the environments that did not
182
+ finish keep the pose they had.
183
+ """
184
+ ...
185
+
186
+ def set_velocity(self, velocity: TensorLike, env_ids: Optional[TensorLike] = ...) -> None:
187
+ """Write the prop's velocity directly (teleport, not control), world frame.
188
+
189
+ Args:
190
+ velocity: ``(num_envs, 6)`` — or ``(len(env_ids), 6)`` when
191
+ ``env_ids`` is given — as ``[vx, vy, vz, wx, wy, wz]``.
192
+ env_ids: Environment indices to write; ``None`` writes every
193
+ environment.
194
+
195
+ A reset that moves a prop should zero its velocity too, or the body
196
+ arrives at its new pose still carrying the momentum it had at the end
197
+ of the previous episode.
198
+ """
199
+ ...
200
+
201
+ # -- Lifecycle --------------------------------------------------------
202
+
203
+ def reset(self, env_ids: Optional[TensorLike] = ...) -> None:
204
+ """Return the prop to its spawn pose with zero velocity.
205
+
206
+ Args:
207
+ env_ids: Environment indices to reset; ``None`` resets every
208
+ environment.
209
+ """
210
+ ...
@@ -88,10 +88,10 @@ ProfileName = Literal["core", "standard", "full"]
88
88
  VALID_PROFILES: Tuple[str, ...] = ("core", "standard", "full")
89
89
 
90
90
  # Profile defaults table. Each profile lists ONLY the fields it turns on; any
91
- # field absent from a profile's dict defaults to False. SRD §10.1 mandates
91
+ # field absent from a profile's dict defaults to False. MCAP recording mandates
92
92
  # Phase 1 robot/contact/sensor/video stay off; the `full` row below holds the
93
93
  # line by not enabling those streams in Phase 1. Wave 2 may flip them on
94
- # (plan §6 Wave 2 task F) — that edit is intentionally scoped to this table.
94
+ # when enabled — that edit is intentionally scoped to this table.
95
95
  #
96
96
  # Wave Policy-Eval-Completeness (2026-06-21) — three changes to defaults:
97
97
  #
@@ -161,7 +161,7 @@ _PROFILE_DEFAULTS: Dict[str, Dict[str, bool]] = {
161
161
  "include_terminations": True,
162
162
  "include_events": True,
163
163
  # Wave 2 — Phase 2 streams enabled in the `full` profile per plan
164
- # §6 Wave 2 task F + SRD §7 line 409. Phase 3 streams (video, depth,
164
+ # The full profile includes Phase 2 streams. Phase 3 streams (video, depth,
165
165
  # lidar) and `include_collisions` stay opt-in even under `full` to
166
166
  # avoid surprising users with high-volume captures.
167
167
  "include_robot_state": True,
@@ -7,7 +7,6 @@ implementation-free — concrete behaviour lives in the future
7
7
  ``simulo`` client and the ``simulo-backend`` SDK.
8
8
  """
9
9
 
10
- from simulo.interfaces.platform.app import AppProtocol, JobFunctionProtocol, Mount
11
10
  from simulo.interfaces.platform.artifacts import (
12
11
  DECLARED_OUTPUT_MISSING_CODE,
13
12
  JOB_ARTIFACT_DOWNLOAD_ROUTE_TEMPLATE,
@@ -97,7 +96,7 @@ from simulo.interfaces.platform.asset_catalog import (
97
96
  from simulo.interfaces.platform.callbacks import JobCallbackProtocol
98
97
  from simulo.interfaces.platform.debug import DebugSessionProtocol, ReplaySessionProtocol, VisualizationSessionProtocol
99
98
  from simulo.interfaces.platform.domain import Checkpoint, Job, Package, Project, Resource, Tag, TrainedModel
100
- from simulo.interfaces.platform.enums import ArtifactKind, JobStatus, ResourceKind, ResumePolicy
99
+ from simulo.interfaces.platform.enums import ArtifactKind
101
100
  from simulo.interfaces.platform.manifest import (
102
101
  FINDING_CLASSIFICATIONS,
103
102
  MAX_PREFLIGHT_BODY_BYTES,
@@ -124,7 +123,6 @@ from simulo.interfaces.platform.runs import (
124
123
  LOG_CHUNK_MAX_BYTES,
125
124
  LOG_TAIL_MAX_BYTES,
126
125
  TERMINAL_JOB_STATUSES,
127
- JobRecord,
128
126
  )
129
127
  from simulo.interfaces.platform.runtime import RuntimeProtocol
130
128
  from simulo.interfaces.platform.submit import (
@@ -158,8 +156,6 @@ from simulo.interfaces.platform.submit import (
158
156
  WORKER_JOB_RECORDINGS_PRESIGN_ROUTE_TEMPLATE,
159
157
  WORKER_JOB_RECORDINGS_ROUTE_TEMPLATE,
160
158
  WORKER_LEASE_HEADER,
161
- ModelRecord,
162
- RecordingRecord,
163
159
  canonical_pypi_name,
164
160
  is_platform_pinned,
165
161
  )
@@ -167,9 +163,6 @@ from simulo.interfaces.platform.volume import VolumeProtocol
167
163
 
168
164
  __all__ = [
169
165
  # enums
170
- "JobStatus",
171
- "ResumePolicy",
172
- "ResourceKind",
173
166
  "ArtifactKind",
174
167
  # domain dataclasses
175
168
  "Tag",
@@ -210,7 +203,6 @@ __all__ = [
210
203
  "OUTPUT_FLAG_ANOMALY",
211
204
  "DECLARED_OUTPUT_MISSING_CODE",
212
205
  # job-run contract (run record schema + jobs HTTP API shape)
213
- "JobRecord",
214
206
  "TERMINAL_JOB_STATUSES",
215
207
  "JOBS_API_DEFAULT_PORT",
216
208
  "JOBS_ROUTE",
@@ -249,8 +241,6 @@ __all__ = [
249
241
  "MAX_COMPLETE_MESSAGE_CHARS",
250
242
  "MAX_COMPLETE_REASON_CODE_CHARS",
251
243
  "MODEL_KINDS",
252
- "ModelRecord",
253
- "RecordingRecord",
254
244
  "RESERVED_RUNTIME_ENV_KEYS",
255
245
  "RESERVED_RUNTIME_ENV_PREFIXES",
256
246
  "PLATFORM_PINNED_PACKAGES",
@@ -337,9 +327,6 @@ __all__ = [
337
327
  "AssetProtocol",
338
328
  "VolumeProtocol",
339
329
  "JobCallbackProtocol",
340
- "JobFunctionProtocol",
341
- "AppProtocol",
342
- "Mount",
343
330
  "DebugSessionProtocol",
344
331
  "ReplaySessionProtocol",
345
332
  "VisualizationSessionProtocol",
@@ -133,7 +133,9 @@ and the narrow ``job_artifacts`` table (everything registered via
133
133
  appears in the union **exactly once**. The projection reuses the
134
134
  platform-standard paginated envelope (``items``/``total``/``page``/``limit``/
135
135
  ``pages``; default ``limit=20``, max 100, out-of-range → 422) and the standard
136
- error shape.
136
+ error shape — see :mod:`simulo.interfaces.platform.runs` for the full
137
+ contract, including the ``pages >= 1`` invariant that holds even for
138
+ ``total == 0``.
137
139
  """
138
140
 
139
141
  from collections.abc import Mapping
@@ -1,8 +1,8 @@
1
- """USD Asset Catalogs wire contract (USD Asset Catalogs Wave, PR-1).
1
+ """USD asset-catalog wire contract.
2
2
 
3
3
  This is the **canonical, single source of truth** for the reference grammar, the
4
4
  route constants, the size caps, the wire dataclasses, and the reserved-word /
5
- role / verdict enumerations that carry a USD *asset* (a versioned robot or world
5
+ role / verdict enumerations that carry a USD *asset* (a versioned robot, world, or prop
6
6
  package) from the laptop client to the cloud catalog and back into a job. It
7
7
  lands **before any consumer** — the control plane (customer, ``/admin/*`` and
8
8
  ``/internal/worker/*`` routes), the thin ``simulo`` client (``simulo asset ...``
@@ -28,8 +28,8 @@ stay decoupled (no import cycle). When you want the ref grammar, routes, or a
28
28
  wire record, reach here; when you want the mounted-handle protocol, reach
29
29
  ``simulo.interfaces.platform.asset``.
30
30
 
31
- The design intent behind every shape below follows the USD Asset Catalogs UX
32
- proposal; the frozen wire specifics are transcribed from the API contract-freeze doc.
31
+ The constants, parsers, dataclasses, and tests in this module own the exact
32
+ cross-package wire shape.
33
33
 
34
34
  ## Two catalogs, one grammar
35
35
 
@@ -37,15 +37,15 @@ proposal; the frozen wire specifics are transcribed from the API contract-freeze
37
37
 
38
38
  * **publisher** — omitted means *the caller's own organization catalog*.
39
39
  ``simulo`` is the reserved publisher for the read-only **global** catalog.
40
- Publishers are organization slugs, so a future org-to-org share is a
41
- *permissions* change, not a grammar change — this module therefore accepts
40
+ Publishers are organization slugs. Sharing is an authorization concern, not
41
+ a grammar concern, so this module accepts
42
42
  any syntactically valid org slug in the publisher position (server-side
43
43
  permission checks decide what is actually *usable*), and rejects only the
44
44
  reserved segments that can never be org slugs (``builtin``/``nucleus``/
45
45
  ``local``/``platform``; ``simulo`` is allowed because it names the global
46
46
  catalog). See :data:`RESERVED_ASSET_PUBLISHERS` / :data:`GLOBAL_ASSET_PUBLISHER`.
47
47
  * **kind** — :data:`ASSET_KINDS` = ``("robot", "world", "prop")``. ``prop``
48
- joined the grammar in W7 — furniture/mount/fixture assets that are neither
48
+ covers furniture/mount/fixture assets that are neither
49
49
  a robot nor a full environment (e.g. ``prop/table:v1``).
50
50
  * **name** — an org/project **slug** (:data:`ASSET_SLUG_PATTERN`), unique per
51
51
  ``(publisher, kind)``. A reserved-publisher word (:data:`RESERVED_ASSET_PUBLISHERS`)
@@ -121,7 +121,10 @@ opaque asset-version id (:data:`ASSET_VERSION_ROUTE_TEMPLATE` subtree, whose
121
121
  latest is hidden by default, and additionally surfaced (with its
122
122
  ``deprecated`` :attr:`AssetRecord.latest_version_status`) when
123
123
  ``include_deprecated=true`` (``simulo asset list --deprecated``). Global rows
124
- are visible to any authenticated user.
124
+ are visible to any authenticated user. Returns the platform-standard
125
+ paginated envelope (``items``/``total``/``page``/``limit``/``pages``) — see
126
+ :mod:`simulo.interfaces.platform.runs` for the full contract, including the
127
+ ``pages >= 1`` invariant that holds even for ``total == 0``.
125
128
  * :data:`ASSET_ROUTE_TEMPLATE` (``GET`` ``?publisher=``) — one asset with
126
129
  description/tags + version history; (``PATCH``) — description/tags, the only
127
130
  mutable metadata.
@@ -215,17 +218,17 @@ from simulo.interfaces.ids import AssetId, AssetVersionId, Digest, ResourceUri
215
218
  # Reference grammar — kinds, reserved words, slug rule
216
219
  # --------------------------------------------------------------------------
217
220
 
218
- #: The asset kinds valid in the ref grammar today (§2). ``robot`` is an
221
+ #: The asset kinds valid in the ref grammar today. ``robot`` is an
219
222
  #: articulated system instantiated through the Simulo robot APIs; ``world`` is
220
223
  #: an environment / facility / terrain / scene robots operate in ("world", not
221
224
  #: "environment", because "environment" is overloaded — ``LearningEnv``, Python
222
225
  #: envs, deploy envs); ``prop`` (W7) is a furniture/mount/fixture asset that is
223
226
  #: neither a robot nor a full environment (e.g. a lab table or camera stand) —
224
- #: the additive kind §13 anticipated. Adding a further kind later is an
227
+ #: the additive kind added most recently. Adding a further kind later is an
225
228
  #: additive change to this constant.
226
229
  ASSET_KINDS = ("robot", "world", "prop")
227
230
 
228
- #: Publisher segments that can never be organization slugs (§2). All are rejected
231
+ #: Publisher segments that can never be organization slugs. All are rejected
229
232
  #: in the publisher position **except** :data:`GLOBAL_ASSET_PUBLISHER`
230
233
  #: (``"simulo"``), which names the read-only global catalog. They are also
231
234
  #: rejected as asset *names*, so no ref segment reads ambiguously as a catalog
@@ -234,8 +237,8 @@ ASSET_KINDS = ("robot", "world", "prop")
234
237
  #: catalog-grammar reservation.)
235
238
  RESERVED_ASSET_PUBLISHERS = frozenset({"simulo", "builtin", "nucleus", "local", "platform"})
236
239
 
237
- #: The one reserved publisher that IS a valid publisher: the global catalog
238
- #: (§2, §11). Omitting the publisher means the caller's own org; ``simulo`` means
240
+ #: The one reserved publisher that IS a valid publisher: the global catalog.
241
+ #: Omitting the publisher means the caller's own org; ``simulo`` means
239
242
  #: the global catalog; nothing else is a reserved-but-valid publisher.
240
243
  GLOBAL_ASSET_PUBLISHER = "simulo"
241
244
 
@@ -252,8 +255,8 @@ GLOBAL_ASSET_PUBLISHER = "simulo"
252
255
  #: inner-group bound verbatim, which has a length-2 hole: 1 char matches via the
253
256
  #: optional group being absent, 3+ chars matches via the group, but exactly 2
254
257
  #: chars can match neither — incoherent for users. Fixed here to ``{0,61}``,
255
- #: which accepts every length 1–63 contiguously; see the PR-1 resolutions in
256
- #: ``context/implementation-plans/archive/usd-asset-catalogs/api-contract-freeze.md``.)
258
+ #: which accepts every length 1–63 contiguously; see
259
+ #: ``context/reference/asset-catalog-contract.md`` "Reference parsing".)
257
260
  #: Pinned here (rather than imported) because ``simulo-interfaces`` has zero
258
261
  #: dependencies on the control plane — the value is copied deliberately and this
259
262
  #: docstring is the drift anchor.
@@ -452,7 +455,7 @@ def format_asset_ref(
452
455
 
453
456
  #: ``GET`` — list/search assets (``?scope=&kind=&q=&tag=``); labeled scopes.
454
457
  ASSETS_ROUTE = "/v1/assets"
455
- #: ``POST`` — batch submit-time pin resolver (``{refs, runtime}``); §6.
458
+ #: ``POST`` — batch submit-time pin resolver (``{refs, runtime}``).
456
459
  ASSETS_RESOLVE_ROUTE = "/v1/assets/resolve"
457
460
  #: ``GET`` — the org storage meter (per-asset bytes, org total, quota).
458
461
  ASSETS_USAGE_ROUTE = "/v1/assets/usage"
@@ -462,7 +465,7 @@ ASSETS_USAGE_ROUTE = "/v1/assets/usage"
462
465
  ASSET_ROUTE_TEMPLATE = "/v1/assets/{kind}/{name}"
463
466
  #: ``POST`` — initiate a publish (create a new version). ``.format(kind=..., name=...)``.
464
467
  ASSET_VERSIONS_ROUTE_TEMPLATE = "/v1/assets/{kind}/{name}/versions"
465
- #: ``POST`` — new version from stored source with the current converter (§9.5).
468
+ #: ``POST`` — new version from stored source with the current converter.
466
469
  #: ``.format(kind=..., name=...)``.
467
470
  ASSET_RECONVERT_ROUTE_TEMPLATE = "/v1/assets/{kind}/{name}/reconvert"
468
471
  #: ``POST`` — deprecate one version by its ``vN`` number.
@@ -558,18 +561,18 @@ WORKER_JOB_ASSET_VALIDATION_ROUTE_TEMPLATE = "/internal/worker/jobs/{job_id}/ass
558
561
  #: into the ``args`` mapping that gets hashed. An old client that omits it submits
559
562
  #: a job with no pinned assets — the additive default. The control plane
560
563
  #: re-verifies every pin in the submit transaction and records the ``job_assets``
561
- #: rows (§6).
564
+ #: rows.
562
565
  ASSETS_FIELD = "assets"
563
566
 
564
567
  # --------------------------------------------------------------------------
565
568
  # Authorization roles, status / verdict / format enumerations
566
569
  # --------------------------------------------------------------------------
567
570
 
568
- #: Roles allowed to publish / reconvert an org asset (§3). Missing role → one line
571
+ #: Roles allowed to publish / reconvert an org asset. Missing role → one line
569
572
  #: naming the role and who can grant it.
570
573
  ASSET_PUBLISH_ROLES = frozenset({"owner", "admin", "member"})
571
- #: Roles allowed to deprecate / delete / edit metadata (§3). Identical value to
572
- #: :data:`ASSET_PUBLISH_ROLES` today — proposal §3 groups publish/deprecate/delete
574
+ #: Roles allowed to deprecate / delete / edit metadata. Identical value to
575
+ #: :data:`ASSET_PUBLISH_ROLES` today: publish/deprecate/delete
573
576
  #: under one publish capability (plan critic review, 2026-07-17 realignment) — kept
574
577
  #: as a separate constant so a future, narrower publish role can diverge from
575
578
  #: lifecycle without an API change.
@@ -592,22 +595,22 @@ ASSET_LIFECYCLE_ROLES = frozenset({"owner", "admin", "member"})
592
595
  ASSET_VERSION_STATUSES = ("uploading", "validating", "published", "failed", "deprecated", "deleted")
593
596
  #: The ``asset_validations.verdict`` domain — ``passed`` / ``failed`` are asset
594
597
  #: verdicts; ``error`` is an *infrastructure* failure (validator crash), rendered
595
- #: distinctly so it never reads as "your USD is broken" (§5.1, §8.2).
598
+ #: distinctly so it never reads as "your USD is broken".
596
599
  ASSET_VALIDATION_VERDICTS = ("passed", "failed", "error")
597
600
  #: The ``asset_versions.source_format`` domain — the two ingest formats of the
598
- #: first release (§9; MJCF and others extend the same machinery later).
601
+ #: first release; MJCF and others can extend the same machinery later.
599
602
  ASSET_SOURCE_FORMATS = ("usd", "urdf")
600
603
  #: The base-mode domain — whether a robot is bolted down (``fixed``, manipulators)
601
604
  #: or free (``floating``, mobile/legged). Inexpressible in URDF, so declared at
602
- #: publish (§9.1); ``None`` for worlds and where unset.
605
+ #: publish; ``None`` for worlds and where unset.
603
606
  ASSET_BASE_MODES = ("fixed", "floating")
604
- #: The ``asset_versions.origin_type`` domain (§11.2) — **admin-surface only**.
607
+ #: The ``asset_versions.origin_type`` domain — **admin-surface only**.
605
608
  #: ``customer_upload`` is recorded for org publishes (schema symmetry);
606
609
  #: ``simulo_authored`` / ``third_party_import`` are the global-catalog origins.
607
610
  #: These values NEVER appear in a customer-facing serializer (see
608
611
  #: :class:`AssetRecord` / :class:`AssetVersionRecord`, which carry no origin field).
609
612
  ASSET_ORIGIN_TYPES = ("customer_upload", "simulo_authored", "third_party_import")
610
- #: The ``?scope=`` list/search filter values (§5.3). ``org`` = the caller's org
613
+ #: The ``?scope=`` list/search filter values. ``org`` = the caller's org
611
614
  #: catalog, ``global`` = the ``simulo`` catalog, ``all`` = both (labeled rows).
612
615
  ASSET_LIST_SCOPES = ("org", "global", "all")
613
616
 
@@ -615,13 +618,13 @@ ASSET_LIST_SCOPES = ("org", "global", "all")
615
618
  # Size caps (security NFR — request-size limits; see nfr SKILL)
616
619
  # --------------------------------------------------------------------------
617
620
 
618
- #: Maximum total archive size of one published asset version (§10, §14). 413/422
621
+ #: Maximum total archive size of one published asset version. 413/422
619
622
  #: over this. Settings key: ``max_asset_version_bytes``.
620
623
  MAX_ASSET_VERSION_BYTES = 10 * 1024 * 1024 * 1024 # 10 GiB
621
- #: Maximum number of files in one asset package (§10, §14). Settings key:
624
+ #: Maximum number of files in one asset package. Settings key:
622
625
  #: ``max_asset_files``.
623
626
  MAX_ASSET_FILES = 2000
624
- #: Multipart upload part size (§10, §14). Settings key: ``asset_upload_part_bytes``.
627
+ #: Multipart upload part size. Settings key: ``asset_upload_part_bytes``.
625
628
  ASSET_UPLOAD_PART_BYTES = 64 * 1024 * 1024 # 64 MiB
626
629
 
627
630
  #: Maximum character length of an asset reference (security NFR — bounds an
@@ -637,22 +640,21 @@ MAX_ASSET_REF_CHARS = 256
637
640
 
638
641
  #: Maximum number of refs in one :data:`ASSETS_RESOLVE_ROUTE` batch (security NFR
639
642
  #: — bounds an otherwise-unbounded request array; over this ⇒ 422). Pinned as the
640
- #: frozen contract value (freeze doc "## PR-6 amendments": ``{refs: [<=64 strings],
641
- #: runtime}``). The client de-duplicates before counting, so this bounds the number
643
+ #: request-schema value: ``{refs: [<=64 strings], runtime}``. The client
644
+ #: de-duplicates before counting, so this bounds the number
642
645
  #: of *distinct* refs, not the raw authored occurrences.
643
646
  MAX_ASSETS_RESOLVE_BATCH = 64
644
647
 
645
648
  #: The runtime alias :data:`ASSETS_RESOLVE_ROUTE` / :class:`AssetsResolveRequest.runtime`
646
- #: uses when a caller has no other runtime to name (§6, §14 — one runtime today, no
647
- #: runtime registry yet; a future registry replaces this constant, proposal §11.3
648
- #: task-shaped aliases). Hoisted here (rather than left as a client-only constant) because
649
+ #: uses when a caller has no other runtime to name. Hoisted here rather than
650
+ #: left as a client-only constant because
649
651
  #: the ``validated_on_runtime`` match requires the CLIENT's resolve request and the
650
652
  #: VALIDATOR's finalize call to send the IDENTICAL string — one shared constant is the
651
- #: only way that can never drift (freeze doc "Runtime-string seam", 2026-07-17).
653
+ #: only way to prevent string drift.
652
654
  DEFAULT_ASSET_RUNTIME = "isaac"
653
655
 
654
656
  # --------------------------------------------------------------------------
655
- # Publish options envelope (PR-12 — freeze doc "## PR-7 amendments": --scale)
657
+ # Publish options envelope
656
658
  # --------------------------------------------------------------------------
657
659
 
658
660
  #: Bounds on :attr:`AssetPublishOptions.scale` (security NFR — a
@@ -668,20 +670,20 @@ MAX_ASSET_SCALE = 1e6
668
670
  # Facts envelope + wire error codes
669
671
  # --------------------------------------------------------------------------
670
672
 
671
- #: The schema version stamped on the asset-facts document (§8.2). **Additive-only**
673
+ #: The schema version stamped on the asset-facts document. **Additive-only**
672
674
  #: — new facts fields may be added under this version; a breaking change would mint
673
675
  #: ``simulo.asset_facts.v2``. Carried opaquely as ``Mapping[str, Any]`` on the wire
674
- #: (the exact field list is settling, §15.4).
676
+ #: (the exact field list is settling).
675
677
  ASSET_FACTS_SCHEMA_VERSION = "simulo.asset_facts.v1"
676
678
 
677
679
  #: Error code (``GET`` :data:`ASSET_VERSION_DOWNLOAD_ROUTE_TEMPLATE`, ``403``) when
678
680
  #: a **global** asset download is refused — global assets are referenceable and
679
- #: usable in jobs, never exportable (§5.4). Pinned as a constant (like
681
+ #: usable in jobs, never exportable. Pinned as a constant (like
680
682
  #: ``submit.SEED_SOURCE_CONFLICT_CODE``) because both the control plane raising it
681
683
  #: and the CLI mapping it to friendly copy switch on this exact string.
682
684
  ASSET_NOT_EXPORTABLE_CODE = "not_exportable"
683
685
  #: Error/verdict code the worker uses to **fail a job in place** when a claimed
684
- #: asset's archive is missing at mount time (§14 security posture; the worker
686
+ #: asset's archive is missing at mount time (the security posture; the worker
685
687
  #: verifies digests at mount). Pinned for the same reason as above.
686
688
  ASSET_MISSING_CODE = "asset_missing"
687
689
  #: The ``jobs.job_type`` value of a platform-internal asset-validation job
@@ -716,7 +718,7 @@ ASSET_CORRUPT_CODE = "asset_corrupt"
716
718
  #: The uniform per-ref miss code the batch resolver (:data:`ASSETS_RESOLVE_ROUTE`)
717
719
  #: and the job-submit pin re-verify both answer for a ref that does not resolve —
718
720
  #: unknown, cross-org, deleted, or malformed, **all reported identically**
719
- #: (anti-enumeration; freeze doc "## PR-6 amendments"). Carried on
721
+ #: (anti-enumeration; batch-resolve schema and tests). Carried on
720
722
  #: :class:`ResolveMiss.code`; the submit-pin path raises the same code as its
721
723
  #: ``404`` error ``code``. Pinned as a constant because the control plane raising
722
724
  #: it and the CLI mapping it to friendly copy switch on this exact string.
@@ -728,21 +730,21 @@ ASSET_NOT_FOUND_CODE = "asset_not_found"
728
730
  #
729
731
  # Hoisted here per the freeze's "## PR-7 amendments" ("CP friendly-error
730
732
  # codes used by the CLI hoist into interfaces/platform/asset_catalog.py —
731
- # single source; a CP rename can no longer silently degrade §5.1 copy").
733
+ # single source; a control-plane rename can no longer silently degrade CLI copy).
732
734
  # These mirror the control plane's OWN local constants
733
735
  # (`simulo_control_plane.assets.constants` / `assets/quota.py`) by VALUE —
734
736
  # the same "pinned here, not imported" discipline :data:`ASSET_SLUG_PATTERN`
735
737
  # documents above (simulo-interfaces has zero dependency on the control
736
738
  # plane); this docstring is the drift anchor for a future reconciliation
737
739
  # where the control plane imports these directly instead of re-declaring
738
- # them. The thin client's `simulo asset publish`/`validate` §5.1 error
740
+ # them. The thin client's `simulo asset publish`/`validate` error
739
741
  # rendering (`_client/cli.py::_friendly_asset_error`) matches these exact
740
742
  # strings, never a hand-typed literal.
741
743
  # --------------------------------------------------------------------------
742
744
 
743
745
  #: 403 — publishing this archive would exceed the org's storage quota
744
- #: (`assets/quota.py::check_asset_storage_quota`). The CLI appends the §5.1
745
- #: "two ways out" (delete or upgrade) to the server's own message.
746
+ #: (`assets/quota.py::check_asset_storage_quota`). The CLI appends two remedies
747
+ #: (delete assets or upgrade storage) to the server's own message.
746
748
  ASSET_STORAGE_QUOTA_EXCEEDED_CODE = "asset_storage_quota_exceeded"
747
749
  #: 422 — the archive exceeds :data:`MAX_ASSET_VERSION_BYTES`.
748
750
  ASSET_ARCHIVE_TOO_LARGE_CODE = "asset_archive_too_large"
@@ -767,14 +769,14 @@ ASSET_UPLOAD_INCOMPLETE_CODE = "asset_upload_incomplete"
767
769
  # this package (self-contained build discipline, see this module's own
768
770
  # "mirrored, not imported" precedent above), so this is a value-only
769
771
  # duplication, drift-anchored by this docstring. The thin client's
770
- # `simulo asset get`/`deprecate`/`delete` §5.4/§5.5 error rendering
772
+ # `simulo asset get`/`deprecate`/`delete` error rendering
771
773
  # (`_client/cli.py::_friendly_asset_error`) matches these exact strings so
772
774
  # the server's already user-language message renders verbatim, never a
773
775
  # code-prefixed wire dump.
774
776
  # --------------------------------------------------------------------------
775
777
 
776
778
  #: 409 — ``simulo asset delete`` refused because at least one non-validation
777
- #: job still references the version (§5.5's delete guard). The server's
779
+ #: job still references the version (the job-reference delete guard). The server's
778
780
  #: message already names the job count and the first job id, proposal-
779
781
  #: verbatim, and now also points to the ``--force`` override; the CLI renders
780
782
  #: it as-is.
@@ -943,6 +945,13 @@ class AssetVersionRecord:
943
945
  validation_attempts: int = 0
944
946
  """Number of validation attempts made for this version."""
945
947
 
948
+ validation_job_id: Optional[str] = None
949
+ """Exact id of the newest system validation attempt for an organization-owned
950
+ version, or ``None`` when unavailable. Additive and diagnostic-only: callers
951
+ may surface it as a support/correlation reference, but system validation jobs
952
+ are not part of the ordinary Jobs catalog, their runtime logs and free-form
953
+ results are not public surfaces, and global asset reads never expose the id."""
954
+
946
955
  published_by: Optional[str] = None
947
956
  """Display identity of the publishing user, or ``None`` while unpublished."""
948
957
 
@@ -1134,8 +1143,7 @@ class WorkerAssetInfo:
1134
1143
 
1135
1144
  @dataclass(**_DC)
1136
1145
  class AssetPublishOptions:
1137
- """The optional ``options`` envelope of a publish initiate (PR-12 —
1138
- freeze doc "## PR-7 amendments" executed at Wave 3).
1146
+ """The optional ``options`` envelope of a publish-initiate request.
1139
1147
 
1140
1148
  Carried on the initiate body (customer AND admin) as ``options:
1141
1149
  {scale?: float}``; persisted with the version; threaded verbatim into the
@@ -10,7 +10,7 @@ schema, enum reuse, endpoint constants, and semantics docstrings only.
10
10
  Field names and lifecycle align with the platform's Job use case: a job is one
11
11
  managed execution of exactly one package, with lifecycle states drawn from
12
12
  :class:`~simulo.interfaces.platform.enums.JobStatus` (``queued``/``running``/
13
- ``completed``/``failed``/``cancelled`` — Job.md's wording; the local executor
13
+ ``completed``/``failed``/``cancelled`` — job API's wording; the local executor
14
14
  uses the ``running`` → ``completed``|``failed`` subset, the cloud adds
15
15
  ``queued``/``cancelled``).
16
16
 
@@ -23,6 +23,23 @@ HTTP API shape (all responses JSON; all timestamps ISO 8601 UTC, e.g.
23
23
  {"items": [<JobRecord>, ...], "total": 3, "page": 1, "limit": 20, "pages": 1}
24
24
 
25
25
  ``limit`` defaults to 20, maximum 100; out-of-range ``page``/``limit`` → 422.
26
+
27
+ **``pages`` is always >= 1**, including for an empty list (``total: 0`` →
28
+ ``pages: 1``, not ``0``). Clients treat ``page > pages`` as "this page is out
29
+ of range" and recover by clamping to ``pages``; a ``pages: 0`` would make
30
+ ``page: 1`` permanently out of range and the clamp non-terminating. Both
31
+ producers of this envelope (the control plane and the local
32
+ ``simulo-backend serve`` stand-in) enforce the floor. This platform-standard
33
+ paginated envelope shape (``items``/``total``/``page``/``limit``/``pages``)
34
+ is reused verbatim by every other list endpoint in this package — recordings
35
+ and models in :mod:`simulo.interfaces.platform.submit`, the artifacts
36
+ projection in :mod:`simulo.interfaces.platform.artifacts`, and the asset
37
+ list/search route (``ASSETS_ROUTE``) in
38
+ :mod:`simulo.interfaces.platform.asset_catalog` — and the ``pages >= 1``
39
+ floor applies identically to all of them. That list is the complete set:
40
+ adding a paginated route means adding it here too, so a reader learning
41
+ which endpoints this invariant governs is never told a smaller number than
42
+ the truth.
26
43
  * ``GET /v1/jobs/{job_id}`` — one job record (the :class:`JobRecord` fields).
27
44
  * ``GET /v1/jobs/{job_id}/logs?offset=N`` — one log chunk::
28
45
 
@@ -105,8 +122,7 @@ routes documented in ``submit.py``) additionally accept an optional
105
122
  never the default, and available to any active member (no additional role
106
123
  beyond org membership — the platform's existing flat-permission model for
107
124
  jobs, ``JOB_CREATE_ROLES``/``JOB_CANCEL_ROLES``).
108
- * any other value → 422 (Job.md "List All Jobs Using CLI" AF-3, invalid
109
- filter option).
125
+ * any other value → 422 as an invalid filter option.
110
126
 
111
127
  ``POST /v1/jobs/{job_id}/cancel`` is a deliberate exception: it is NOT scope-
112
128
  gated and keeps resolving org-wide regardless of ``?scope=`` — any active
@@ -160,8 +176,7 @@ JOB_SCOPE_QUERY_PARAM = "scope"
160
176
  #: Default / explicit-opt-in-only values for :data:`JOB_SCOPE_QUERY_PARAM`.
161
177
  JOB_SCOPE_MINE = "mine"
162
178
  JOB_SCOPE_ORG = "org"
163
- #: The closed domain of ``?scope=`` — any other value is a 422 (Job.md "List
164
- #: All Jobs Using CLI" AF-3).
179
+ #: The closed domain of ``?scope=`` — any other value is a 422.
165
180
  JOB_LIST_SCOPES = (JOB_SCOPE_MINE, JOB_SCOPE_ORG)
166
181
 
167
182
  #: Upper bound on the log bytes a single ``/logs`` response carries. The server
@@ -245,6 +260,19 @@ class JobRecord:
245
260
  attempts: int = 0
246
261
  """Number of execution attempts started so far (retries make this > 1)."""
247
262
 
263
+ viewstream: bool = False
264
+ """Whether this run requested an interactive live view. Additive and
265
+ tolerant: older producers omit it and consumers see ``False``."""
266
+
267
+ seed: Optional[Mapping[str, Any]] = None
268
+ """Resolved seed provenance (``job_id``, ``model_id``, ``kind``, and
269
+ ``name``), or ``None`` for a fresh run. Kept mapping-shaped so a real JSON
270
+ response can be passed directly to ``JobRecord(**payload)``."""
271
+
272
+ job_type: str = "other"
273
+ """Producer-defined run category. ``"other"`` is an ordinary user job;
274
+ additive producer categories remain readable by older consumers."""
275
+
248
276
  assets: tuple[str, ...] = ()
249
277
  """Human-readable ref of every asset this run pinned, oldest first (the
250
278
  ``job_assets`` denormalized canonical ref, e.g. ``myorg/robot/tiny-arm:v1``).
@@ -295,6 +323,12 @@ class JobRecord:
295
323
  scoping" section): a shared view of everyone's jobs must say whose job
296
324
  each row is, not just that it exists."""
297
325
 
326
+ submitted_by_display: Optional[str] = None
327
+ """Human-readable identity for :attr:`submitted_by_user_id`, currently the
328
+ submitter's email. ``None`` covers system-owned runs, deleted accounts,
329
+ and producers predating the additive field; clients must never fall back
330
+ to displaying the raw user id."""
331
+
298
332
  outputs: Optional[JobOutputsSummary] = None
299
333
  """Typed outputs summary of the run — a **rendering input,
300
334
  never a status**: ``completed ⚠ 1 anomaly`` is drawn from here while
@@ -105,6 +105,16 @@ platform-standard shape: ``{"error": {"code": ..., "message": ..., "request_id":
105
105
  set directly on the top-level body dict — and MUST NOT fold them into the
106
106
  ``args`` mapping that gets hashed.
107
107
 
108
+ **App name (live-view wave, additive):** the request body
109
+ optionally carries ``app_name`` (``str``) — the submitting ``App``'s
110
+ declared name, display context the platform's live viewer shows next to
111
+ ``job_name``. The SAME top-level sibling-of-``args`` placement rule as
112
+ ``viewstream`` and the seed fields (a display property must never perturb
113
+ the content-addressed ``package_id``). Optional end to end: every client
114
+ published before the field omits it, an older control plane ignores the
115
+ extra key, and a job without one renders by ``job_name`` alone — never an
116
+ error in either direction.
117
+
108
118
  :data:`SEED_SOURCE_FIELDS` pins the two field names:
109
119
 
110
120
  - ``seed_from_job_id`` (``str``) — continue training from another job's
@@ -129,7 +139,10 @@ platform-standard shape: ``{"error": {"code": ..., "message": ..., "request_id":
129
139
  ## Client-facing: recordings
130
140
 
131
141
  * :data:`JOB_RECORDINGS_ROUTE_TEMPLATE` (``GET``) — paginated envelope of
132
- :class:`RecordingRecord`, same pagination shape as ``GET /v1/jobs``::
142
+ :class:`RecordingRecord`, same pagination shape as ``GET /v1/jobs``
143
+ (see :mod:`simulo.interfaces.platform.runs` for the full contract,
144
+ including the ``pages >= 1`` invariant that holds even for
145
+ ``total == 0``)::
133
146
 
134
147
  {"items": [<RecordingRecord>, ...], "total": 2, "page": 1, "limit": 20, "pages": 1}
135
148
 
@@ -218,7 +231,14 @@ platform-standard shape: ``{"error": {"code": ..., "message": ..., "request_id":
218
231
  exists yet).
219
232
 
220
233
  * :data:`WORKER_JOB_STREAM_READY_ROUTE_TEMPLATE` (``POST``) — request
221
- ``{"stream_host": "10.0.1.7", "stream_port": 49100}``. Registers the
234
+ ``{"stream_host": "10.0.1.7", "stream_port": 49100}``, optionally plus
235
+ ``"num_envs"`` (``int`` ≥ 1 — live-view wave): the run's actual
236
+ parallel-environment count, read by the worker from the SDK-written
237
+ runtime-facts sidecar and served to the viewer as display context only.
238
+ Optional end to end — an older worker omits it, an older control plane
239
+ ignores the extra key, and the worker DROPS (never sends) a value it
240
+ cannot validate as a sane positive int, so a garbage sidecar can never
241
+ fail the endpoint registration itself. Registers the
222
242
  claiming worker's Kit signaling endpoint ONCE, the moment Kit is actually
223
243
  listening — NOT at claim (Tier-1 1.1). Reporting at claim registered the
224
244
  endpoint minutes before Kit's signaling port opened during a cold Isaac
@@ -280,12 +300,15 @@ checkpoints a training job leaves in its result's ``checkpoint_dir``
280
300
  models; the list is empty, never an error.
281
301
 
282
302
  * :data:`JOB_MODELS_ROUTE_TEMPLATE` (``GET``) — paginated envelope of
283
- :class:`ModelRecord`, same pagination shape as ``GET /v1/jobs``::
303
+ :class:`ModelRecord`, same pagination shape as ``GET /v1/jobs``
304
+ (see :mod:`simulo.interfaces.platform.runs` for the full contract,
305
+ including the ``pages >= 1`` invariant that holds even for
306
+ ``total == 0``)::
284
307
 
285
308
  {"items": [<ModelRecord>, ...], "total": 2, "page": 1, "limit": 20, "pages": 1}
286
309
 
287
310
  * :data:`JOB_MODEL_ROUTE_TEMPLATE` (``GET``) — one :class:`ModelRecord`
288
- (Job.md "Retrieve Model Information Using CLI"). ``404 model_not_found``
311
+ for model metadata. ``404 model_not_found``
289
312
  for an unknown/foreign/malformed id — never 403 (anti-enumeration).
290
313
 
291
314
  * :data:`JOB_MODEL_DOWNLOAD_ROUTE_TEMPLATE` (``GET``) — resolves one model
@@ -671,6 +694,15 @@ class ModelRecord:
671
694
  # `--viewstream` CLI flag at submit time, never from a package manifest) —
672
695
  # so a manifest-borne override could otherwise force streaming on/off
673
696
  # against the job's actual (server-recorded) submission intent.
697
+ # ``SIMULO_RUNTIME_FACTS_PATH`` (live-view wave) is its sidecar sibling:
698
+ # the worker mints a per-attempt runtime-facts path (``worker.py``,
699
+ # ``tempfile.mkstemp``) and exports it via ``_child_env`` so the SDK
700
+ # inside the job subprocess can record display facts (e.g. the
701
+ # parallel-environment count) the worker reads back and reports on
702
+ # ``stream-ready``. It is a resolved filesystem path the platform then
703
+ # TRUSTS AND READS — the same runner-owned class as
704
+ # ``SIMULO_CHECKPOINT_DIR`` / ``SIMULO_ANOMALY_DEBUG_DIR`` — so nothing
705
+ # user-authored may ever aim it elsewhere.
674
706
  #
675
707
  # * **User-tunable** (deliberately NOT reserved): ``_setup_checkpointing``
676
708
  # exports ``SIMULO_CHECKPOINT_EVERY`` / ``SIMULO_CHECKPOINT_KEEP_LAST`` /
@@ -728,6 +760,7 @@ RESERVED_RUNTIME_ENV_KEYS = frozenset(
728
760
  "SIMULO_SHUTDOWN_GRACE_S",
729
761
  "SIMULO_FAST_EXIT_AFTER_RESULT",
730
762
  "SIMULO_VIEWSTREAM_ENABLED",
763
+ "SIMULO_RUNTIME_FACTS_PATH",
731
764
  "MAX_JOB_TIMEOUT_S",
732
765
  }
733
766
  )
@@ -28,14 +28,13 @@ from simulo.interfaces.runtime.policy import PolicyProtocol
28
28
  from simulo.interfaces.runtime.scenario import ScenarioProtocol
29
29
  from simulo.interfaces.runtime.task import TaskProtocol
30
30
  from simulo.interfaces.runtime.tensors import TensorLike
31
- from simulo.interfaces.runtime.trainer import PolicyExporterProtocol, TrainerProtocol
31
+ from simulo.interfaces.runtime.trainer import TrainerProtocol
32
32
 
33
33
  __all__ = [
34
34
  "TensorLike",
35
35
  "TaskProtocol",
36
36
  "PolicyProtocol",
37
37
  "TrainerProtocol",
38
- "PolicyExporterProtocol",
39
38
  "PlayerProtocol",
40
39
  "LearningEnvProtocol",
41
40
  "ScenarioProtocol",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo-interfaces
3
- Version: 0.11.0
3
+ Version: 0.13.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
@@ -14,6 +14,7 @@ src/simulo/interfaces/authoring/materials.py
14
14
  src/simulo/interfaces/authoring/physics.py
15
15
  src/simulo/interfaces/authoring/pose.py
16
16
  src/simulo/interfaces/authoring/primitives.py
17
+ src/simulo/interfaces/authoring/prop.py
17
18
  src/simulo/interfaces/authoring/record.py
18
19
  src/simulo/interfaces/authoring/robot.py
19
20
  src/simulo/interfaces/authoring/scene.py