simulo-interfaces 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,195 @@
1
+ """Anomaly-capture contracts (``DebugOnAnomaly``) — the *local* half of Rule #10.
2
+
3
+ Training/inference anomaly detection + capture is a **runtime** concern: the
4
+ backend trainer/env detect an anomalous training signal (non-finite loss,
5
+ reward collapse, physics/observation explosion) and capture a bounded MCAP
6
+ debug session locally, via the same recording seam — and therefore the same
7
+ storage/retrieval path — as policy rollout recordings.
8
+
9
+ The *remote* debug/replay sessions (attach to a live cloud job, replay a
10
+ recorded run, clone-to-debug) remain the FORWARD Protocols in
11
+ :mod:`simulo.interfaces.platform.debug` (``DebugSessionProtocol`` et al.);
12
+ their semantics depend on the cloud debug-session design, which does not exist
13
+ yet. :class:`AnomalyDebugSessionProtocol` here is deliberately narrower — a
14
+ handle to a completed local capture artifact — and is NOT a replacement for
15
+ the platform ``DebugSessionProtocol``.
16
+
17
+ Torch-free by construction (Rule #11): thresholds and signal values are plain
18
+ floats; the tensor-facing detection lives in the implementer package
19
+ (``simulo.recording.anomaly`` in ``simulo-backend``).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import math
25
+ from dataclasses import dataclass, field
26
+ from enum import StrEnum
27
+ from typing import Any, Mapping, Protocol, Sequence, runtime_checkable
28
+
29
+
30
+ class AnomalyKind(StrEnum):
31
+ """Kind of a detected training/inference anomaly.
32
+
33
+ Values are a stable wire ABI (they appear in MCAP debug-session payloads
34
+ and file names): append-only, never rename or renumber.
35
+
36
+ The 4-kind set is intentional: a finite-gradient check (``NON_FINITE_GRAD``)
37
+ is a deliberate deferral, to be appended when a detection site exists.
38
+ """
39
+
40
+ NON_FINITE_LOSS = "non_finite_loss"
41
+ NON_FINITE_REWARD = "non_finite_reward"
42
+ REWARD_COLLAPSE = "reward_collapse"
43
+ OBSERVATION_EXPLOSION = "observation_explosion"
44
+
45
+
46
+ @dataclass(frozen=True, kw_only=True)
47
+ class AnomalySignal:
48
+ """A single detected anomaly, as handed to the capture path.
49
+
50
+ ``value``/``baseline`` are plain floats and MAY be non-finite (a NaN loss
51
+ signal carries the NaN) — serializers must JSON-stringify them rather than
52
+ emitting raw NaN/Inf tokens.
53
+ """
54
+
55
+ kind: AnomalyKind
56
+ message: str
57
+ step_index: int | None = None
58
+ iteration: int | None = None
59
+ value: float | None = None
60
+ baseline: float | None = None
61
+ env_indices: tuple[int, ...] = ()
62
+ detected_at: str = "" # ISO 8601 UTC, stamped by the detector
63
+ metadata: Mapping[str, Any] = field(default_factory=dict)
64
+
65
+
66
+ @dataclass(frozen=True, kw_only=True)
67
+ class DebugOnAnomaly:
68
+ """Configuration for anomaly detection + local MCAP debug-session capture.
69
+
70
+ The feature is **off by default** at the integration point: a trainer
71
+ constructed without a ``DebugOnAnomaly`` (or with ``enabled=False``) runs
72
+ the exact pre-feature code path — no monitor object exists, no buffering
73
+ happens, and no file is ever written.
74
+
75
+ Signals (deliberately not a bare threshold hack — each maps to a distinct
76
+ failure mode with its own detection site):
77
+
78
+ * ``NON_FINITE_LOSS`` — NaN/Inf policy or value loss mean at a trainer
79
+ chunk boundary (``check_losses``).
80
+ * ``NON_FINITE_REWARD`` — NaN/Inf reward at an env step, or a non-finite
81
+ mean episode reward at a chunk boundary.
82
+ * ``REWARD_COLLAPSE`` — the chunk's mean episode reward drops by at least
83
+ ``collapse_drop_fraction`` of the running best baseline (the same
84
+ best-checkpoint signal the trainer already tracks), sustained for
85
+ ``collapse_patience`` consecutive chunk boundaries
86
+ (``check_reward_collapse``).
87
+ * ``OBSERVATION_EXPLOSION`` — non-finite observations, or observations
88
+ whose magnitude exceeds ``observation_limit``, at an env step
89
+ (``check_observations``).
90
+
91
+ Video evidence (``video``): naming a camera sensor (e.g.
92
+ ``video="side_cam"``) additionally buffers that camera's frames for the
93
+ FIRST watched env (``env_indices[0]``) alongside the numeric window, and
94
+ a capture writes them into the same debug MCAP (``/debug/window/video``
95
+ plus a ``foxglove.CompressedVideo`` sibling) so a viewer shows what the
96
+ sim looked like in the window ending at the anomalous step.
97
+
98
+ Honest costs of ``video`` (both opt-in, like the monitor itself):
99
+
100
+ * attaching a camera to a training env forces the render pipeline ON
101
+ during training (the env must be built with cameras enabled), which
102
+ slows every step whether or not an anomaly ever fires;
103
+ * the frame ring buffer holds up to ``window_steps`` raw RGB frames in
104
+ host memory — ``window_steps x H x W x 3`` bytes (64 frames of
105
+ 640x480 is ~56 MiB). Size ``window_steps`` and the camera resolution
106
+ together.
107
+
108
+ ``video=None`` (the default) buffers no frames, resolves no camera, and
109
+ changes nothing about the numeric-only behavior.
110
+ """
111
+
112
+ enabled: bool = True
113
+ output_dir: str = "runs/debug"
114
+ # Bounded pre-anomaly evidence window (in checked env steps) captured
115
+ # into the debug MCAP.
116
+ window_steps: int = 256
117
+ # Env indices whose full observation/reward rows are buffered (aggregate
118
+ # stats always cover all envs).
119
+ env_indices: Sequence[int] = (0,)
120
+ # --- env-step signals -------------------------------------------------
121
+ check_observations: bool = True
122
+ # Out-of-envelope magnitude threshold; None disables the envelope check
123
+ # (non-finite detection stays on while check_observations is True).
124
+ observation_limit: float | None = 1e6
125
+ # Check (and buffer) every Nth env step; 1 = every step.
126
+ check_every_n_steps: int = 1
127
+ # Name of ONE camera sensor whose frames are buffered as video evidence
128
+ # for env_indices[0] (see the class docstring for the honest costs).
129
+ # None (the default) disables video evidence entirely.
130
+ video: str | None = None
131
+ # --- chunk-boundary signals -------------------------------------------
132
+ check_losses: bool = True
133
+ check_reward_collapse: bool = True
134
+ # Relative drop vs the running best baseline that counts as a collapse:
135
+ # (best - current) / max(|best|, collapse_baseline_floor).
136
+ collapse_drop_fraction: float = 0.5
137
+ # Consecutive collapsed chunk boundaries required before capturing.
138
+ collapse_patience: int = 2
139
+ # Denominator floor so a near-zero best baseline cannot divide away.
140
+ collapse_baseline_floor: float = 1e-6
141
+ # Total capture budget; the monitor disarms after this many captures.
142
+ max_captures: int = 1
143
+
144
+ def __post_init__(self) -> None:
145
+ object.__setattr__(self, "env_indices", tuple(int(i) for i in self.env_indices))
146
+ for i in self.env_indices:
147
+ if i < 0:
148
+ raise ValueError(f"env_indices must be non-negative, got {i}.")
149
+ if self.window_steps < 1:
150
+ raise ValueError(f"window_steps must be >= 1, got {self.window_steps}.")
151
+ if self.check_every_n_steps < 1:
152
+ raise ValueError(f"check_every_n_steps must be >= 1, got {self.check_every_n_steps}.")
153
+ if self.max_captures < 1:
154
+ raise ValueError(f"max_captures must be >= 1, got {self.max_captures}.")
155
+ if self.collapse_patience < 1:
156
+ raise ValueError(f"collapse_patience must be >= 1, got {self.collapse_patience}.")
157
+ if not (0.0 < self.collapse_drop_fraction <= 1.0):
158
+ raise ValueError(f"collapse_drop_fraction must be in (0, 1], got {self.collapse_drop_fraction}.")
159
+ if not (math.isfinite(self.collapse_baseline_floor) and self.collapse_baseline_floor > 0.0):
160
+ raise ValueError(
161
+ f"collapse_baseline_floor must be a finite positive float, got {self.collapse_baseline_floor}."
162
+ )
163
+ if self.observation_limit is not None and not (
164
+ math.isfinite(self.observation_limit) and self.observation_limit > 0.0
165
+ ):
166
+ raise ValueError(
167
+ f"observation_limit must be a finite positive float or None, got {self.observation_limit}."
168
+ )
169
+ if self.video is not None and (not isinstance(self.video, str) or not self.video.strip()):
170
+ raise ValueError(f"video must be a non-empty camera sensor name or None, got {self.video!r}.")
171
+
172
+
173
+ @runtime_checkable
174
+ class AnomalyDebugSessionProtocol(Protocol):
175
+ """Handle to a completed local anomaly capture (an MCAP debug session).
176
+
177
+ Minimal by design: the artifact is a plain MCAP file a user opens in
178
+ Lichtblick / Foxglove or reads with the ``mcap`` package. Interactive
179
+ remote debug sessions are the FORWARD ``DebugSessionProtocol`` in
180
+ :mod:`simulo.interfaces.platform.debug`.
181
+ """
182
+
183
+ @property
184
+ def signal(self) -> AnomalySignal:
185
+ """The anomaly that triggered this capture."""
186
+ ...
187
+
188
+ @property
189
+ def mcap_path(self) -> str:
190
+ """Path of the written MCAP debug-session file."""
191
+ ...
192
+
193
+ def close(self) -> None:
194
+ """Release any resources held by the session (idempotent)."""
195
+ ...
@@ -0,0 +1,35 @@
1
+ """Observation / reward / termination component Protocols (Part A).
2
+
3
+ Structural mirrors of ``simulo.core.observations.ObservationComponent``,
4
+ ``simulo.core.rewards.RewardComponent``, and
5
+ ``simulo.core.terminations.TerminationCondition``.
6
+ """
7
+
8
+ from typing import Any, Optional, Protocol, runtime_checkable
9
+
10
+ from simulo.interfaces.runtime.tensors import TensorLike
11
+
12
+
13
+ @runtime_checkable
14
+ class ObservationComponentProtocol(Protocol):
15
+ """Mirror of ``simulo.core.observations.ObservationComponent``."""
16
+
17
+ def initialize(self, robot: Any, device: str, num_envs: int) -> None: ...
18
+ def dim(self, robot: Any) -> int: ...
19
+ def compute(self, prev_actions: TensorLike) -> TensorLike: ...
20
+
21
+
22
+ @runtime_checkable
23
+ class RewardComponentProtocol(Protocol):
24
+ """Mirror of ``simulo.core.rewards.RewardComponent``."""
25
+
26
+ def initialize(self, robot: Any, device: str, num_envs: int) -> None: ...
27
+ def compute(self, prev_actions: TensorLike, terminated: Optional[TensorLike]) -> TensorLike: ...
28
+
29
+
30
+ @runtime_checkable
31
+ class TerminationConditionProtocol(Protocol):
32
+ """Mirror of ``simulo.core.terminations.TerminationCondition``."""
33
+
34
+ def initialize(self, robot: Any, device: str, num_envs: int) -> None: ...
35
+ def check(self) -> TensorLike: ...
@@ -0,0 +1,27 @@
1
+ """``LearningEnvProtocol`` — structural mirror of ``simulo.core.env.LearningEnv`` (Part A)."""
2
+
3
+ from typing import Any, Dict, Optional, Protocol, Tuple, runtime_checkable
4
+
5
+ from simulo.interfaces.runtime.tensors import TensorLike
6
+
7
+
8
+ @runtime_checkable
9
+ class LearningEnvProtocol(Protocol):
10
+ """Gym-like vectorized env. Mirror of ``simulo.core.env.LearningEnv``."""
11
+
12
+ # Plain data member (not a read-only property): the implementation,
13
+ # ``simulo.core.env.LearningEnv``, exposes ``num_envs`` as an instance
14
+ # attribute assigned in ``__init__``. Mirror fidelity matters under nominal
15
+ # conformance — a read-only property here would make the implementer's
16
+ # ``self.num_envs = num_envs`` an illegal assignment.
17
+ num_envs: int
18
+
19
+ @property
20
+ def device(self) -> str: ...
21
+ @property
22
+ def dt(self) -> float: ...
23
+ def reset(
24
+ self, seed: Optional[int] = ..., options: Optional[Dict[str, Any]] = ...
25
+ ) -> Tuple[TensorLike, Dict[str, Any]]: ...
26
+ def step(self, actions: TensorLike) -> Tuple[TensorLike, TensorLike, TensorLike, TensorLike, Dict[str, Any]]: ...
27
+ def close(self) -> None: ...
@@ -0,0 +1,19 @@
1
+ """``PlayerProtocol`` — structural mirror of ``simulo.core.player.Player`` (Part A)."""
2
+
3
+ from typing import Any, Dict, Optional, Protocol, runtime_checkable
4
+
5
+
6
+ @runtime_checkable
7
+ class PlayerProtocol(Protocol):
8
+ """Trained-policy playback. Mirror of ``simulo.core.player.Player``."""
9
+
10
+ def play(
11
+ self,
12
+ num_episodes: Optional[int] = ...,
13
+ num_steps: Optional[int] = ...,
14
+ real_time: bool = ...,
15
+ video_path: Optional[str] = ...,
16
+ video_length: int = ...,
17
+ deterministic: bool = ...,
18
+ ) -> Dict[str, Any]: ...
19
+ def load(self, path: str) -> None: ...
@@ -0,0 +1,16 @@
1
+ """``PolicyProtocol`` — structural mirror of ``simulo.core.policy.Policy`` (Part A)."""
2
+
3
+ from typing import Optional, Protocol, runtime_checkable
4
+
5
+ from simulo.interfaces.runtime.tensors import TensorLike
6
+
7
+
8
+ @runtime_checkable
9
+ class PolicyProtocol(Protocol):
10
+ """obs -> action callable. Mirror of ``simulo.core.policy.Policy``."""
11
+
12
+ @property
13
+ def input_dim(self) -> Optional[int]: ...
14
+ @property
15
+ def output_dim(self) -> Optional[int]: ...
16
+ def __call__(self, observation: TensorLike) -> TensorLike: ...
@@ -0,0 +1,13 @@
1
+ """``ScenarioProtocol`` — structural mirror of ``simulo.scenario.Scenario`` (Part A)."""
2
+
3
+ from typing import Any, Protocol, runtime_checkable
4
+
5
+
6
+ @runtime_checkable
7
+ class ScenarioProtocol(Protocol):
8
+ """Interactive sim lifecycle. Mirror of ``simulo.scenario.Scenario``."""
9
+
10
+ def build(self, scene: Any) -> None: ...
11
+ def on_start(self) -> None: ...
12
+ def on_step(self) -> None: ...
13
+ def on_shutdown(self) -> None: ...
@@ -0,0 +1,21 @@
1
+ """``TaskProtocol`` — structural mirror of ``simulo.core.task.Task`` (Part A)."""
2
+
3
+ from typing import Any, Protocol, Tuple, runtime_checkable
4
+
5
+ from simulo.interfaces.runtime.tensors import TensorLike
6
+
7
+
8
+ @runtime_checkable
9
+ class TaskProtocol(Protocol):
10
+ """RL task contract — structural mirror of ``simulo.core.task.Task``."""
11
+
12
+ observation_dim: int
13
+ action_dim: int
14
+
15
+ def build(self, scene: Any) -> None: ...
16
+ def on_start(self, env: Any) -> None: ...
17
+ def get_observations(self) -> TensorLike: ...
18
+ def get_rewards(self) -> TensorLike: ...
19
+ def get_dones(self) -> Tuple[TensorLike, TensorLike]: ...
20
+ def apply_actions(self, actions: TensorLike) -> None: ...
21
+ def reset_idx(self, env_ids: TensorLike) -> None: ...
@@ -0,0 +1,12 @@
1
+ """Tensor-library-agnostic alias for the runtime contract (Part A).
2
+
3
+ The runtime Protocols describe tensor in/out method shapes without binding to
4
+ any concrete tensor library. We do NOT import ``torch`` — not even under
5
+ ``TYPE_CHECKING`` — keeping the package importable with no torch installed AND
6
+ mypy free of torch stubs. A concrete ``torch.Tensor`` is assignable to/from
7
+ ``Any``, so structural conformance of any real implementation still holds.
8
+ """
9
+
10
+ from typing import Any
11
+
12
+ TensorLike = Any
@@ -0,0 +1,37 @@
1
+ """``TrainerProtocol`` — structural mirror of ``simulo.core.trainer.Trainer`` (Part A).
2
+
3
+ Also home of the narrower ``PolicyExporterProtocol``: policy export is an
4
+ *optional capability*, deliberately NOT a ``TrainerProtocol`` member. A Protocol
5
+ binds ALL implementations, and the Trainer contract must not promise what only
6
+ some trainers deliver — ``export_policy`` requires a deterministic, standalone
7
+ exportable action head, which the base ``simulo.core.trainer.Trainer`` (a
8
+ user-extensible ABC) does not guarantee. Implementations that CAN export (today:
9
+ ``simulo.core.trainer.RLTrainer``) declare the capability protocol separately;
10
+ consumers feature-detect with ``isinstance(trainer, PolicyExporterProtocol)``.
11
+ """
12
+
13
+ from typing import Any, Dict, Optional, Protocol, runtime_checkable
14
+
15
+
16
+ @runtime_checkable
17
+ class TrainerProtocol(Protocol):
18
+ """Training orchestration. Mirror of ``simulo.core.trainer.Trainer``."""
19
+
20
+ def train(self, max_iterations: int) -> Dict[str, Any]: ...
21
+ def evaluate(self, checkpoint: Optional[str] = ..., num_episodes: int = ...) -> Dict[str, Any]: ...
22
+ def save(self, path: str) -> None: ...
23
+ def load(self, path: str) -> None: ...
24
+
25
+
26
+ @runtime_checkable
27
+ class PolicyExporterProtocol(Protocol):
28
+ """Optional capability: export the trained policy as a standalone artifact.
29
+
30
+ ``export_policy`` writes the policy's deterministic inference head to
31
+ ``path`` and returns the path actually written. Mirror of
32
+ ``simulo.core.trainer.RLTrainer.export_policy`` — see the module docstring
33
+ for why this is a separate protocol rather than a ``TrainerProtocol``
34
+ member.
35
+ """
36
+
37
+ def export_policy(self, path: str) -> str: ...
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: simulo-interfaces
3
+ Version: 0.1.0
4
+ Summary: Implementation-free contract layer for the Simulo platform — torch-free runtime/sim Protocols and platform/workspace domain contracts. Importable with no torch/numpy/gymnasium/Isaac installed.
5
+ Author-email: Simulo Team <team@simulo.ai>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://simulo.ai
8
+ Project-URL: Source, https://github.com/simulo-org/simulo-platform
9
+ Keywords: robotics,contracts,protocols,interfaces,simulo
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Provides-Extra: dev
20
+ Requires-Dist: black>=24.0; extra == "dev"
21
+ Requires-Dist: isort>=5.13; extra == "dev"
22
+ Requires-Dist: ruff>=0.5; extra == "dev"
23
+ Requires-Dist: mypy>=1.10; extra == "dev"
24
+ Requires-Dist: pytest>=8.2; extra == "dev"
25
+ Requires-Dist: build>=1.2; extra == "dev"
26
+ Requires-Dist: twine>=5.1; extra == "dev"
27
+ Provides-Extra: release
28
+ Requires-Dist: commitizen>=3.27; extra == "release"
29
+ Requires-Dist: build>=1.2; extra == "release"
30
+ Requires-Dist: twine>=5.1; extra == "release"
31
+
32
+ # simulo-interfaces
33
+
34
+ The **implementation-free contract layer** for the Simulo platform. A single,
35
+ stable, torch-free, **zero-runtime-dependency** package that both Simulo SDK
36
+ distributions depend on:
37
+
38
+ - `simulo-backend` — the cloud GPU SDK (torch + IsaacLab + Isaac Sim).
39
+ - `simulo` — the future thin local client (no heavy deps).
40
+
41
+ Because both distributions share these contracts, `simulo-interfaces` is the one
42
+ place the runtime Protocols and the platform/workspace domain model are defined.
43
+
44
+ ## What it is
45
+
46
+ A pure-stdlib set of `typing.Protocol` contracts, frozen dataclasses, enums,
47
+ `NewType` identifiers, and an exception hierarchy. Nothing here does any work —
48
+ it only describes shapes.
49
+
50
+ ## What it is NOT
51
+
52
+ It imports **none** of: `torch`, `numpy`, `gymnasium`, IsaacLab / Isaac Sim, AWS
53
+ SDKs, HTTP clients, or database drivers. It performs **no** I/O, persistence, or
54
+ computation. It is **not** a dumping ground (Contract Rule #12) — only contracts
55
+ shared by more than one distribution belong here.
56
+
57
+ The central invariant (Contract Rule #11): this package must import on a laptop
58
+ with no torch/numpy/gymnasium/Isaac installed. Two tests enforce it:
59
+ `tests/test_import_without_torch.py` (no heavy module enters `sys.modules`) and
60
+ `tests/test_no_heavy_deps.py` (zero unconditional `Requires-Dist`).
61
+
62
+ ## Two parts
63
+
64
+ ### Part A — `simulo.interfaces.runtime`
65
+
66
+ Tensor-library-agnostic structural mirrors of the `simulo.core` runtime surface.
67
+ Tensors are typed as `TensorLike` (an alias for `Any`) so the contracts never
68
+ bind to a concrete tensor library.
69
+
70
+ - `TaskProtocol`, `PolicyProtocol`, `TrainerProtocol`, `PlayerProtocol`
71
+ - `LearningEnvProtocol`, `ScenarioProtocol`
72
+ - `ObservationComponentProtocol`, `RewardComponentProtocol`,
73
+ `TerminationConditionProtocol`
74
+ - `PolicyExporterProtocol` — the narrow *optional* export capability
75
+ (`export_policy(path) -> str`). Deliberately not a `TrainerProtocol` member:
76
+ a Protocol binds all implementations, and only some trainers (today
77
+ `simulo.core.trainer.RLTrainer`) can deliver a deterministic standalone
78
+ policy export. Consumers feature-detect with `isinstance(trainer,
79
+ PolicyExporterProtocol)`.
80
+
81
+ ### Part B — `simulo.interfaces.platform`
82
+
83
+ The platform/workspace domain contracts:
84
+
85
+ - **Enums** — `JobStatus`, `ResumePolicy`, `ResourceKind`, `ArtifactKind`.
86
+ - **Domain dataclasses** (frozen, slotted, keyword-only) — `Tag`, `Project`,
87
+ `Package`, `Resource`, `Job`, `Checkpoint`, `TrainedModel`.
88
+ - **Cloud-execution Protocols** — `AppProtocol`, `JobFunctionProtocol`,
89
+ `RuntimeProtocol`, `AssetProtocol`, `VolumeProtocol`, `JobCallbackProtocol`,
90
+ `DebugSessionProtocol`, `ReplaySessionProtocol`, `VisualizationSessionProtocol`.
91
+
92
+ ## Public import surface
93
+
94
+ ```python
95
+ import simulo.interfaces as si
96
+
97
+ # exceptions + ids re-exported at the top level
98
+ si.SimuloError, si.ContractViolationError, si.JobFailedError
99
+ si.ProjectId, si.JobId, si.ResourceUri, si.Digest
100
+
101
+ # Part A
102
+ from simulo.interfaces.runtime import TaskProtocol, PolicyProtocol, LearningEnvProtocol
103
+
104
+ # Part B
105
+ from simulo.interfaces.platform import (
106
+ JobStatus, ResumePolicy, ResourceKind, ArtifactKind,
107
+ Project, Package, Job, Resource, Checkpoint, TrainedModel, Tag,
108
+ AppProtocol, RuntimeProtocol, AssetProtocol, VolumeProtocol,
109
+ )
110
+ ```
111
+
112
+ ## Conformance model
113
+
114
+ The contracts are `typing.Protocol`s — implementers conform **structurally**, no
115
+ inheritance required.
116
+
117
+ - **Static** — implementers (e.g. `simulo-backend`) are type-checked against
118
+ these Protocols by mypy. That static assertion is the primary conformance
119
+ gate and lives in the **implementer** package, not here.
120
+ - **Runtime** — every Protocol is `@runtime_checkable`, so `isinstance(obj,
121
+ SomeProtocol)` provides a lightweight smoke check (presence of members only —
122
+ not signatures). This package's tests use that smoke form.
123
+ - **Nominal (optional, opt-in per implementer)** — `simulo-backend`'s
124
+ `simulo.core` classes additionally *declare* the Part A Protocols as base
125
+ classes, so mypy drift-checks each implementation against its mirror at the
126
+ class-definition site. Nominal declaration never replaces the structural
127
+ contract — a third-party implementation that merely matches the signatures
128
+ still conforms.
129
+
130
+ ## Domain-model invariants
131
+
132
+ - **Package ≠ Job** (Rule #5) — a `Package` is an immutable, content-addressed
133
+ source archive; a `Job` is one execution of a package and references it by id.
134
+ - **Resource ≠ Package** (Rule #6) — a `Resource` is a read-only, versioned,
135
+ registry-resolved mount (robot / environment / dataset / …).
136
+ - **Checkpoint ≠ TrainedModel** (Rule #8) — an intermediate training artifact is
137
+ a distinct type from the final, deployable model. Never collapse them.
138
+ - **Project is the anchor** (Rule #7) — every other domain object hangs off a
139
+ `Project`.
140
+ - **No packaged-skill type** (Rule #11) — there is deliberately no `Skill` /
141
+ `PackagedSkill` type here.
142
+
143
+ ## Stability policy
144
+
145
+ - Enum **values** are a stable wire ABI: append-only, never renumber or rename.
146
+ - Dataclass fields and Protocol method signatures follow the same
147
+ breaking-change discipline as the rest of the contract layer.
148
+ - The package follows SemVer; additive changes (new optional field, new
149
+ Protocol, new enum member at the end) are minor/patch, removals/renames major.
150
+
151
+ ## Requirements
152
+
153
+ - Python **>= 3.11** (uses `enum.StrEnum`). Tested on 3.11 and 3.12.
154
+ - Standard library only — **zero** runtime dependencies.
155
+
156
+ ## Development
157
+
158
+ ```bash
159
+ pip install -e ".[dev]"
160
+ ruff check src
161
+ black --check src tests
162
+ isort --check-only src tests
163
+ mypy src # strict
164
+ python -m build && twine check dist/*
165
+ pytest
166
+ ```
@@ -0,0 +1,29 @@
1
+ simulo/interfaces/__init__.py,sha256=4AcE1wwcD89GMuMjgbCERUjpRLdveJGd869qlZMthy8,1877
2
+ simulo/interfaces/exceptions.py,sha256=UDK0k6SFf2uKqBGNCtOgdwGKqrR0EGX8JlT8k1-s7qM,1006
3
+ simulo/interfaces/ids.py,sha256=du-ODsdz7twbv9q1xL_KPFPOegh2yeZUig1bZFm-tWs,946
4
+ simulo/interfaces/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ simulo/interfaces/platform/__init__.py,sha256=a5JoCCmvetwIo7xLO6XQ-80zdOuTbTScEqY88oHQZtY,3970
6
+ simulo/interfaces/platform/app.py,sha256=1xO3ooZThI3H3mToFazCZyWZ2xMAzMPrxuLxnAz_wBk,1810
7
+ simulo/interfaces/platform/asset.py,sha256=udUdC-p46XhDOmu939YLno95zozLCHPpsTQXI9Z4CMg,467
8
+ simulo/interfaces/platform/callbacks.py,sha256=_RbzIvzEa2ZVn6KjYzOQ4z5EVV1gQorqSFcD-Hx_04Q,660
9
+ simulo/interfaces/platform/debug.py,sha256=b-_4NylxAO4CkgAriv0dt2RBgwkEBxv1MibT8YmlYqY,804
10
+ simulo/interfaces/platform/domain.py,sha256=VJaCf9bsoDAE9e0a0aXa1PHvaeNi3XNMplmNkzbF5GY,3286
11
+ simulo/interfaces/platform/enums.py,sha256=cUPa-nVOHKpYco9QTeuzIYH_KChiUNWlWbtIN4t39PM,1239
12
+ simulo/interfaces/platform/runs.py,sha256=gzzS-_p0WO3Odk2sgg2tbfbYmx4rahUVDBERBJbPJBk,5542
13
+ simulo/interfaces/platform/runtime.py,sha256=eyVQVTVHa0y_l85FMWkPJuPMLfD34_tSjxEbSrHVydg,751
14
+ simulo/interfaces/platform/submit.py,sha256=CFf4QTKa9Mp1JdzDt2wJsAHWsiqD7DMFEgekpEGe5sc,29298
15
+ simulo/interfaces/platform/volume.py,sha256=rCi0C3-VKFiBfxmgNfNtZ9rLQ7CGtYmyC2r6jenHpM4,388
16
+ simulo/interfaces/runtime/__init__.py,sha256=tv79QeafWvJ4EF-ew5QuzHLmYZaDyDjS2QiLqti8Et8,1952
17
+ simulo/interfaces/runtime/anomaly.py,sha256=SFaZs83WVCsr5upIDHo4iGFXC0kaqJrFxGbRrrVFI-Y,9012
18
+ simulo/interfaces/runtime/components.py,sha256=0XCZDk5em3K0TcrgxhqYvODUpVTtNeUOUpdC5uV8uts,1248
19
+ simulo/interfaces/runtime/env.py,sha256=q_LnPwJr6DM7i0ifkzYFnqlYfcyrEfINN_SdXgti58Q,1136
20
+ simulo/interfaces/runtime/player.py,sha256=1SldSTpbIV-l0c7nNg5BxKes5xV2NvLdOVuLy6BASEU,612
21
+ simulo/interfaces/runtime/policy.py,sha256=DY1vNV3FrI0FLqJQwosvVStsGrJIjRPX-0lkIl9vJiI,523
22
+ simulo/interfaces/runtime/scenario.py,sha256=Du0HHlmsg62wlCoVTSbTMbPRpvxeDNWZkPNx3ngKcZw,432
23
+ simulo/interfaces/runtime/task.py,sha256=Jts29KtMJNvcvf4ciNSux-XVkVNP6DiDzJ77LW38giM,747
24
+ simulo/interfaces/runtime/tensors.py,sha256=oA7wUpfIOXwT64rrF6W-TmRdJ800o_Y-auPn_VvL6E8,500
25
+ simulo/interfaces/runtime/trainer.py,sha256=x0D6eKz3NPHTBrdMOmbbUvxvwGs9fRuzfYKQq736v2U,1683
26
+ simulo_interfaces-0.1.0.dist-info/METADATA,sha256=lPwPsp_oBCGCg03i5QbN1DECQ4xy7a-6xLwKtaGe568,7028
27
+ simulo_interfaces-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
28
+ simulo_interfaces-0.1.0.dist-info/top_level.txt,sha256=qud9mL24_iIiqb0ZMUSRitgwjDv_3zxgVqlWY45swJI,7
29
+ simulo_interfaces-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ simulo