simulo-interfaces 0.13.0__tar.gz → 0.14.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.
- {simulo_interfaces-0.13.0/src/simulo_interfaces.egg-info → simulo_interfaces-0.14.0}/PKG-INFO +1 -1
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/pyproject.toml +1 -1
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/__init__.py +116 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/artifacts.py +198 -43
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/enums.py +3 -3
- simulo_interfaces-0.14.0/src/simulo/interfaces/platform/policy_bundle.py +2100 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/runs.py +8 -1
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0/src/simulo_interfaces.egg-info}/PKG-INFO +1 -1
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo_interfaces.egg-info/SOURCES.txt +1 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/MANIFEST.in +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/PYPI.md +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/setup.cfg +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/__init__.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/__init__.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/actuator.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/asset.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/entity.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/light.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/materials.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/physics.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/pose.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/primitives.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/prop.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/record.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/robot.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/scene.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/sensor.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/terrain.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/visual.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/authoring/world.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/exceptions.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/ids.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/app.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/asset.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/asset_catalog.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/callbacks.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/debug.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/domain.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/manifest.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/runtime.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/submit.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/volume.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/py.typed +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/__init__.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/anomaly.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/components.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/env.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/player.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/policy.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/scenario.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/task.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/tensors.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/runtime/trainer.py +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo_interfaces.egg-info/dependency_links.txt +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo_interfaces.egg-info/requires.txt +0 -0
- {simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo_interfaces.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simulo-interfaces"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.14.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"
|
{simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/__init__.py
RENAMED
|
@@ -16,7 +16,10 @@ from simulo.interfaces.platform.artifacts import (
|
|
|
16
16
|
MAX_OUTPUT_FLAG_CODE_CHARS,
|
|
17
17
|
MAX_OUTPUT_FLAG_DETAIL_CHARS,
|
|
18
18
|
MAX_OUTPUTS_BYTES,
|
|
19
|
+
OUTPUT_DROPPED_DETAILS,
|
|
19
20
|
OUTPUT_FLAG_ANOMALY,
|
|
21
|
+
OUTPUT_FLAG_OUTPUT_DROPPED,
|
|
22
|
+
OUTPUTS_UNDELIVERED_CODE,
|
|
20
23
|
WORKER_JOB_ARTIFACTS_PRESIGN_ROUTE_TEMPLATE,
|
|
21
24
|
WORKER_JOB_ARTIFACTS_ROUTE_TEMPLATE,
|
|
22
25
|
Artifact,
|
|
@@ -109,6 +112,60 @@ from simulo.interfaces.platform.manifest import (
|
|
|
109
112
|
PreflightResponse,
|
|
110
113
|
RuntimeManifest,
|
|
111
114
|
)
|
|
115
|
+
from simulo.interfaces.platform.policy_bundle import (
|
|
116
|
+
EXPORT_VALIDATION_JOB_TYPE,
|
|
117
|
+
JOB_EXPORTS_ROUTE_TEMPLATE,
|
|
118
|
+
MAX_POLICY_BUNDLE_UNCOMPRESSED_BYTES,
|
|
119
|
+
MAX_POLICY_NAME_TOKEN_CHARS,
|
|
120
|
+
POLICY_BUNDLE_ACTION_SCHEMA_FILE,
|
|
121
|
+
POLICY_BUNDLE_ARCHIVE_NAME_TEMPLATE,
|
|
122
|
+
POLICY_BUNDLE_ARTIFACT_KIND,
|
|
123
|
+
POLICY_BUNDLE_FILES,
|
|
124
|
+
POLICY_BUNDLE_MANIFEST_FILE,
|
|
125
|
+
POLICY_BUNDLE_MODEL_ONNX_FILE,
|
|
126
|
+
POLICY_BUNDLE_MODEL_PT_FILE,
|
|
127
|
+
POLICY_BUNDLE_NORMALIZATION_FILE,
|
|
128
|
+
POLICY_BUNDLE_OBSERVATION_SCHEMA_FILE,
|
|
129
|
+
POLICY_BUNDLE_OPTIONAL_FILES,
|
|
130
|
+
POLICY_BUNDLE_README_FILE,
|
|
131
|
+
POLICY_BUNDLE_REQUIRED_FILES,
|
|
132
|
+
POLICY_BUNDLE_REQUIREMENTS_FILE,
|
|
133
|
+
POLICY_BUNDLE_ROOT_DIR_TEMPLATE,
|
|
134
|
+
POLICY_BUNDLE_SCHEMA_VERSION,
|
|
135
|
+
POLICY_BUNDLE_TEST_VECTORS_FILE,
|
|
136
|
+
POLICY_BUNDLE_VERIFY_FILE,
|
|
137
|
+
POLICY_EQUIVALENCE_ABS_TOLERANCE,
|
|
138
|
+
POLICY_EQUIVALENCE_REL_TOLERANCE,
|
|
139
|
+
POLICY_GOLDEN_VECTOR_COUNT,
|
|
140
|
+
POLICY_NAME_TOKEN_FALLBACK,
|
|
141
|
+
POLICY_TENSOR_DTYPES,
|
|
142
|
+
POLICY_TEST_VECTOR_EXPECTED_OUTPUTS_KEY,
|
|
143
|
+
POLICY_TEST_VECTOR_INPUTS_KEY,
|
|
144
|
+
SUPPORTED_POLICY_BUNDLE_SCHEMA_MAJORS,
|
|
145
|
+
UNKNOWN,
|
|
146
|
+
PolicyActionComponent,
|
|
147
|
+
PolicyActionSchema,
|
|
148
|
+
PolicyBounds,
|
|
149
|
+
PolicyBundleManifest,
|
|
150
|
+
PolicyEquivalenceSpec,
|
|
151
|
+
PolicyExportFailureReason,
|
|
152
|
+
PolicyExportFormat,
|
|
153
|
+
PolicyExportValidation,
|
|
154
|
+
PolicyExportVerdict,
|
|
155
|
+
PolicyHistorySpec,
|
|
156
|
+
PolicyNormalization,
|
|
157
|
+
PolicyObservationComponent,
|
|
158
|
+
PolicyObservationSchema,
|
|
159
|
+
PolicySourceSpec,
|
|
160
|
+
PolicyTensorSpec,
|
|
161
|
+
Unknown,
|
|
162
|
+
is_supported_policy_bundle_schema,
|
|
163
|
+
policy_bundle_archive_name,
|
|
164
|
+
policy_bundle_root_dir,
|
|
165
|
+
policy_bundle_schema_major,
|
|
166
|
+
policy_name_token,
|
|
167
|
+
validate_policy_bundle,
|
|
168
|
+
)
|
|
112
169
|
from simulo.interfaces.platform.runs import (
|
|
113
170
|
JOB_CANCEL_ROUTE_TEMPLATE,
|
|
114
171
|
JOB_LIST_SCOPES,
|
|
@@ -201,6 +258,9 @@ __all__ = [
|
|
|
201
258
|
"MAX_OUTPUT_FLAG_CODE_CHARS",
|
|
202
259
|
"MAX_OUTPUT_FLAG_DETAIL_CHARS",
|
|
203
260
|
"OUTPUT_FLAG_ANOMALY",
|
|
261
|
+
"OUTPUT_FLAG_OUTPUT_DROPPED",
|
|
262
|
+
"OUTPUT_DROPPED_DETAILS",
|
|
263
|
+
"OUTPUTS_UNDELIVERED_CODE",
|
|
204
264
|
"DECLARED_OUTPUT_MISSING_CODE",
|
|
205
265
|
# job-run contract (run record schema + jobs HTTP API shape)
|
|
206
266
|
"TERMINAL_JOB_STATUSES",
|
|
@@ -322,6 +382,62 @@ __all__ = [
|
|
|
322
382
|
"ConvertedArchive",
|
|
323
383
|
"AssetValidationFinalize",
|
|
324
384
|
"AssetPublishOptions",
|
|
385
|
+
# portable policy export bundle contract (issue #654) — schema version,
|
|
386
|
+
# bundle layout, naming rule, export routes/job type, equivalence
|
|
387
|
+
# defaults, enums, explicit-unknown marker, manifest dataclasses, the
|
|
388
|
+
# export-validation result, and the cross-file coherence check
|
|
389
|
+
"POLICY_BUNDLE_SCHEMA_VERSION",
|
|
390
|
+
"SUPPORTED_POLICY_BUNDLE_SCHEMA_MAJORS",
|
|
391
|
+
"policy_bundle_schema_major",
|
|
392
|
+
"is_supported_policy_bundle_schema",
|
|
393
|
+
"POLICY_BUNDLE_MODEL_ONNX_FILE",
|
|
394
|
+
"POLICY_BUNDLE_MODEL_PT_FILE",
|
|
395
|
+
"POLICY_BUNDLE_MANIFEST_FILE",
|
|
396
|
+
"POLICY_BUNDLE_OBSERVATION_SCHEMA_FILE",
|
|
397
|
+
"POLICY_BUNDLE_ACTION_SCHEMA_FILE",
|
|
398
|
+
"POLICY_BUNDLE_NORMALIZATION_FILE",
|
|
399
|
+
"POLICY_BUNDLE_TEST_VECTORS_FILE",
|
|
400
|
+
"POLICY_BUNDLE_VERIFY_FILE",
|
|
401
|
+
"POLICY_BUNDLE_REQUIREMENTS_FILE",
|
|
402
|
+
"POLICY_BUNDLE_README_FILE",
|
|
403
|
+
"POLICY_BUNDLE_FILES",
|
|
404
|
+
"POLICY_BUNDLE_REQUIRED_FILES",
|
|
405
|
+
"POLICY_BUNDLE_OPTIONAL_FILES",
|
|
406
|
+
"MAX_POLICY_BUNDLE_UNCOMPRESSED_BYTES",
|
|
407
|
+
"POLICY_TEST_VECTOR_INPUTS_KEY",
|
|
408
|
+
"POLICY_TEST_VECTOR_EXPECTED_OUTPUTS_KEY",
|
|
409
|
+
"POLICY_BUNDLE_ARTIFACT_KIND",
|
|
410
|
+
"POLICY_BUNDLE_ROOT_DIR_TEMPLATE",
|
|
411
|
+
"POLICY_BUNDLE_ARCHIVE_NAME_TEMPLATE",
|
|
412
|
+
"MAX_POLICY_NAME_TOKEN_CHARS",
|
|
413
|
+
"POLICY_NAME_TOKEN_FALLBACK",
|
|
414
|
+
"policy_name_token",
|
|
415
|
+
"policy_bundle_root_dir",
|
|
416
|
+
"policy_bundle_archive_name",
|
|
417
|
+
"JOB_EXPORTS_ROUTE_TEMPLATE",
|
|
418
|
+
"EXPORT_VALIDATION_JOB_TYPE",
|
|
419
|
+
"POLICY_EQUIVALENCE_ABS_TOLERANCE",
|
|
420
|
+
"POLICY_EQUIVALENCE_REL_TOLERANCE",
|
|
421
|
+
"POLICY_GOLDEN_VECTOR_COUNT",
|
|
422
|
+
"POLICY_TENSOR_DTYPES",
|
|
423
|
+
"PolicyExportFormat",
|
|
424
|
+
"PolicyExportVerdict",
|
|
425
|
+
"PolicyExportFailureReason",
|
|
426
|
+
"PolicyExportValidation",
|
|
427
|
+
"Unknown",
|
|
428
|
+
"UNKNOWN",
|
|
429
|
+
"PolicyTensorSpec",
|
|
430
|
+
"PolicyBounds",
|
|
431
|
+
"PolicyHistorySpec",
|
|
432
|
+
"PolicySourceSpec",
|
|
433
|
+
"PolicyEquivalenceSpec",
|
|
434
|
+
"PolicyObservationComponent",
|
|
435
|
+
"PolicyActionComponent",
|
|
436
|
+
"PolicyBundleManifest",
|
|
437
|
+
"PolicyObservationSchema",
|
|
438
|
+
"PolicyActionSchema",
|
|
439
|
+
"PolicyNormalization",
|
|
440
|
+
"validate_policy_bundle",
|
|
325
441
|
# protocols
|
|
326
442
|
"RuntimeProtocol",
|
|
327
443
|
"AssetProtocol",
|
{simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/artifacts.py
RENAMED
|
@@ -118,6 +118,29 @@ migration). Instead ``JobRecord.outputs`` carries an optional
|
|
|
118
118
|
even if the transition loses a race) and partial recordings are dropped —
|
|
119
119
|
cancel-uploaded checkpoints are intentionally **unsummarized**. A decision,
|
|
120
120
|
not an oversight.
|
|
121
|
+
* **A registered artifact reaches the general job-artifacts road on the
|
|
122
|
+
``completed`` path only** — never on cancel, timeout, or failure. Also a
|
|
123
|
+
decision, and the reason it is not the worker's to reverse: the plan IS the
|
|
124
|
+
drained registration set, and the registry is drained **after the job body
|
|
125
|
+
returns** (``runner``'s ``--outputs-out`` write is on its success path only,
|
|
126
|
+
mirroring ``--result-out``). A cancelled, timed-out or crashed run never
|
|
127
|
+
reaches that drain, so no plan file exists and there is nothing to upload;
|
|
128
|
+
the worker's ``status == completed`` gate makes that explicit and
|
|
129
|
+
fail-closed rather than reading whatever sidecar happens to be on disk from
|
|
130
|
+
an earlier attempt. The two older roads look asymmetric here because they
|
|
131
|
+
are **sniffed** rather than planned — ``_gather_recordings`` /
|
|
132
|
+
``_gather_models`` scan the run directory, so they still find (and, for
|
|
133
|
+
checkpoints, still upload) files a failed run left behind. This road has no
|
|
134
|
+
sniffer by construction.
|
|
135
|
+
|
|
136
|
+
The user-visible consequence, stated rather than buried: a ``report`` or
|
|
137
|
+
``log`` that a FAILING run registers to explain itself is **not delivered**.
|
|
138
|
+
That is the price of "a partial file is not an honest artifact"; the file
|
|
139
|
+
stays on the worker's disk. The worker deliberately emits no warning for it
|
|
140
|
+
either — with no plan file it cannot distinguish "registered nothing" from
|
|
141
|
+
"registered, never drained", so the only available warning is one that would
|
|
142
|
+
fire on every failing job. A job that must hand back diagnostics on failure
|
|
143
|
+
should write them **before** the work that can fail and return normally.
|
|
121
144
|
|
|
122
145
|
The ``/artifacts`` projection is a read-time union, not a home
|
|
123
146
|
--------------------------------------------------------------
|
|
@@ -138,6 +161,7 @@ contract, including the ``pages >= 1`` invariant that holds even for
|
|
|
138
161
|
``total == 0``.
|
|
139
162
|
"""
|
|
140
163
|
|
|
164
|
+
import re
|
|
141
165
|
from collections.abc import Mapping
|
|
142
166
|
from dataclasses import dataclass, field
|
|
143
167
|
|
|
@@ -284,6 +308,74 @@ MAX_OUTPUT_FLAG_DETAIL_CHARS = 256
|
|
|
284
308
|
#: comes from the persisted-rows projection, never from the flag).
|
|
285
309
|
OUTPUT_FLAG_ANOMALY = "anomaly"
|
|
286
310
|
|
|
311
|
+
#: :attr:`OutputFlag.code` announcing that at least one output the run
|
|
312
|
+
#: REGISTERED never became an artifact — the uploader refused it. ``detail``
|
|
313
|
+
#: carries the refusal reason, one of :data:`OUTPUT_DROPPED_DETAILS`, and the
|
|
314
|
+
#: producer emits ONE flag per DISTINCT reason (the :data:`OUTPUT_FLAG_ANOMALY`
|
|
315
|
+
#: rule: a flag states a property of the run, never an inventory of files).
|
|
316
|
+
#: The worker emits it; the CLI renders it.
|
|
317
|
+
#:
|
|
318
|
+
#: WHY IT EXISTS. A registration the platform refuses is a defect in the JOB —
|
|
319
|
+
#: it named a path the platform will never accept — not a transient condition.
|
|
320
|
+
#: Every other trace of it is unreachable: the refusal is written to the
|
|
321
|
+
#: WORKER's own stderr, while the log the platform uploads is the CHILD's.
|
|
322
|
+
#: Without this flag such a run is reported a plain ``completed`` having
|
|
323
|
+
#: delivered less than it claimed, with nothing anywhere for a user to see.
|
|
324
|
+
#:
|
|
325
|
+
#: SCOPE. This flag rides the ``complete`` body, which the control plane keeps
|
|
326
|
+
#: for a ``completed`` job only. It therefore cannot describe a run that
|
|
327
|
+
#: delivered NOTHING AT ALL — that run is not ``completed``, it is terminal
|
|
328
|
+
#: :data:`OUTPUTS_UNDELIVERED_CODE`. The two signals are mutually exclusive by
|
|
329
|
+
#: construction, and between them every refused output is reported.
|
|
330
|
+
OUTPUT_FLAG_OUTPUT_DROPPED = "output_dropped"
|
|
331
|
+
|
|
332
|
+
#: The closed ``detail`` vocabulary of :data:`OUTPUT_FLAG_OUTPUT_DROPPED` — why
|
|
333
|
+
#: one registered output was refused:
|
|
334
|
+
#:
|
|
335
|
+
#: * ``malformed`` — the registration's ``name``/``path`` was not a string;
|
|
336
|
+
#: * ``unresolvable`` — the path could not be resolved on the worker;
|
|
337
|
+
#: * ``outside_roots`` — the resolved path lies outside every directory the
|
|
338
|
+
#: platform points a job's writes at. Uploading it anyway would let a
|
|
339
|
+
#: registration name any file the worker can read, so the refusal is the
|
|
340
|
+
#: guard working, and the flag is how the run says so;
|
|
341
|
+
#: * ``no_pipeline`` — the declared kind has no artifact home to land in (a
|
|
342
|
+
#: checkpoint whose filename carries no role, or a kind outside
|
|
343
|
+
#: :class:`~simulo.interfaces.platform.enums.ArtifactKind`).
|
|
344
|
+
OUTPUT_DROPPED_DETAILS: tuple[str, ...] = (
|
|
345
|
+
"malformed",
|
|
346
|
+
"unresolvable",
|
|
347
|
+
"outside_roots",
|
|
348
|
+
"no_pipeline",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
#: Terminal ``status_reason`` of a run whose body exited 0 but which delivered
|
|
352
|
+
#: NO artifact at all: every output it registered was refused (see
|
|
353
|
+
#: :data:`OUTPUT_DROPPED_DETAILS`) and it produced no recording and no
|
|
354
|
+
#: checkpoint either. Emitted by the worker, rendered by the CLI.
|
|
355
|
+
#:
|
|
356
|
+
#: Such a run is reported ``failed``, not ``completed``. Green-with-nothing is
|
|
357
|
+
#: the worse of the two outcomes: a job whose whole purpose is one file, shown
|
|
358
|
+
#: successful beside an empty artifact list, gives nobody a reason to look —
|
|
359
|
+
#: whereas a job marked failed still has its log, and the run cost the same
|
|
360
|
+
#: either way.
|
|
361
|
+
#:
|
|
362
|
+
#: The condition is deliberately "delivered nothing", not "every registered
|
|
363
|
+
#: output was refused". A run whose checkpoints or recordings landed DID
|
|
364
|
+
#: deliver something, and stays ``completed`` carrying
|
|
365
|
+
#: :data:`OUTPUT_FLAG_OUTPUT_DROPPED` for what it lost — failing it would throw
|
|
366
|
+
#: away hours of real training over one mis-registered path.
|
|
367
|
+
#:
|
|
368
|
+
#: THE RESULT SURVIVES. ``GET /v1/jobs/{id}/result`` serves the result of a
|
|
369
|
+
#: run reported under this code, exactly as it does for a completed run — the
|
|
370
|
+
#: one status other than ``completed`` for which a result is stored at all.
|
|
371
|
+
#: The same argument that keeps a run with surviving checkpoints ``completed``
|
|
372
|
+
#: applies to the dict the body returned: this run's code RAN, its metrics are
|
|
373
|
+
#: as real as any completed run's, and only its FILES were refused. Discarding
|
|
374
|
+
#: them too would destroy work the refusal never touched. The typed
|
|
375
|
+
#: ``outputs`` summary is NOT preserved the same way — it describes the
|
|
376
|
+
#: artifacts, which is precisely what this code already says.
|
|
377
|
+
OUTPUTS_UNDELIVERED_CODE = "outputs_undelivered"
|
|
378
|
+
|
|
287
379
|
#: Terminal ``status_reason`` code of a job whose declared **non-optional**
|
|
288
380
|
#: output was missing after the run body succeeded (the runner's validation —
|
|
289
381
|
#: see the module docstring). Emitted by the runner, rendered by the CLI.
|
|
@@ -367,6 +459,88 @@ def _validate_artifact_name(owner: str, name: str) -> None:
|
|
|
367
459
|
raise ValueError(f"{owner} must not be a dots-only relative-path token, got {_truncated(name)}.")
|
|
368
460
|
|
|
369
461
|
|
|
462
|
+
#: The SERVER's finalize-time filename rule, mirrored — the second, STRICTER
|
|
463
|
+
#: gate an artifact ``name`` must clear before its bytes may be uploaded.
|
|
464
|
+
#:
|
|
465
|
+
#: Canonical implementation: ``simulo_control_plane.storage.base._KEY_SEGMENT_RE``,
|
|
466
|
+
#: applied to ``ArtifactFinalizeRequest.name`` (and ``ModelFinalizeRequest.name``)
|
|
467
|
+
#: through ``storage.base.is_safe_filename_segment`` — anything else is a 422.
|
|
468
|
+
#: Mirrored here rather than imported for the reason ``jobs/constants.py``
|
|
469
|
+
#: records at length: the control-plane build is self-contained and this
|
|
470
|
+
#: contract package is NOT installed in its deploy image, so the dependency
|
|
471
|
+
#: cannot run in that direction.
|
|
472
|
+
#:
|
|
473
|
+
#: **Why a mirror is worth carrying.** The upload order is presign → PUT →
|
|
474
|
+
#: finalize, so a name that clears :func:`_validate_artifact_name` and then
|
|
475
|
+
#: fails the server's rule has ALREADY written bytes to object storage when the
|
|
476
|
+
#: 422 arrives: the artifact is lost, the object is permanently unreferenced
|
|
477
|
+
#: (no row ever names it), and a job may repeat that up to
|
|
478
|
+
#: ``simulo.outputs.MAX_REGISTERED_OUTPUTS`` times at unbounded size. Measured
|
|
479
|
+
#: divergences — names :func:`_validate_artifact_name` ACCEPTS and this rule
|
|
480
|
+
#: rejects: ``"my report.html"`` (space), ``"résumé.pdf"`` (non-ASCII),
|
|
481
|
+
#: ``"eval (1).json"`` (parentheses), ``".hidden"`` (leading dot).
|
|
482
|
+
#:
|
|
483
|
+
#: Those are ordinary things to call a file, and the reachable path is the
|
|
484
|
+
#: PUBLIC one: ``simulo.save_artifact("my report.html", path, kind="report")``
|
|
485
|
+
#: passes the constructor and then orphans its bytes. A worker that checks
|
|
486
|
+
#: BEFORE the presign turns that into an honest, named refusal on the warning
|
|
487
|
+
#: channel instead of an unreferenced object.
|
|
488
|
+
#:
|
|
489
|
+
#: NOT affected, measured rather than assumed: the policy-export bundle name.
|
|
490
|
+
#: ``policy_bundle.policy_name_token`` reduces a job title to ``[a-z0-9._-]``
|
|
491
|
+
#: with a ``[a-z0-9]`` first character — deliberately, citing this very gate —
|
|
492
|
+
#: so ``résumé-arm`` composes ``r-sum-arm-policy-best.zip``, which this rule
|
|
493
|
+
#: ACCEPTS. That is a second, independent guard on one caller; this one is the
|
|
494
|
+
#: general gate every other registered name needs.
|
|
495
|
+
#:
|
|
496
|
+
#: The drift guard is ``tests/integration/test_contract_mirror.py`` — the one
|
|
497
|
+
#: environment holding both packages. It compares this pattern against the
|
|
498
|
+
#: control plane's real compiled regex AND runs both implementations over a
|
|
499
|
+
#: table of names, so a future divergence goes red there rather than orphaning
|
|
500
|
+
#: objects in production.
|
|
501
|
+
SAFE_FILENAME_SEGMENT_PATTERN = r"^[A-Za-z0-9][A-Za-z0-9._-]*\Z"
|
|
502
|
+
_SAFE_FILENAME_SEGMENT_RE = re.compile(SAFE_FILENAME_SEGMENT_PATTERN)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def is_safe_filename_segment(value: str) -> bool:
|
|
506
|
+
"""``True`` when ``value`` is a name the control plane's finalize accepts.
|
|
507
|
+
|
|
508
|
+
Deliberately the same NAME as the control plane's own helper
|
|
509
|
+
(``storage.base.is_safe_filename_segment``), so the two read as the one
|
|
510
|
+
rule they are. Anchored with ``\\Z`` and not ``$`` for the reason recorded
|
|
511
|
+
on the server's copy: Python's ``$`` also matches just before a trailing
|
|
512
|
+
newline, so ``"rollout.mcap\\n"`` would wrongly pass.
|
|
513
|
+
|
|
514
|
+
NOT a replacement for :func:`_validate_artifact_name`, which stays the
|
|
515
|
+
contract's own construction-time rule (cross-platform separators, control
|
|
516
|
+
characters, dots-only tokens, the UTF-8 byte ceiling, and the type check).
|
|
517
|
+
This is the narrower *server-acceptance* question, asked by an uploader
|
|
518
|
+
before it spends bytes.
|
|
519
|
+
"""
|
|
520
|
+
return bool(value) and bool(_SAFE_FILENAME_SEGMENT_RE.match(value))
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _validate_finalizable_artifact_name(owner: str, name: str) -> None:
|
|
524
|
+
"""Reject any ``name`` the server's finalize would answer 422 for.
|
|
525
|
+
|
|
526
|
+
Both gates, in the order an uploader must apply them: the contract's own
|
|
527
|
+
:func:`_validate_artifact_name` first (it owns the type check and the
|
|
528
|
+
length-before-echo discipline, so an over-length or non-``str`` value is
|
|
529
|
+
reported by its size/type and never reproduced), then
|
|
530
|
+
:func:`is_safe_filename_segment`.
|
|
531
|
+
|
|
532
|
+
Every rejection is a ``ValueError`` whose message names the offending value
|
|
533
|
+
(clamped by :func:`_truncated` — an uploader's message is worker-log
|
|
534
|
+
content and must never amplify its own input) and says which rule it broke.
|
|
535
|
+
"""
|
|
536
|
+
_validate_artifact_name(owner, name)
|
|
537
|
+
if not is_safe_filename_segment(name):
|
|
538
|
+
raise ValueError(
|
|
539
|
+
f"{owner} must be a single safe filename segment the control plane's finalize will accept "
|
|
540
|
+
f"(letters, digits, '.', '_', '-'; must start with a letter or digit), got {_truncated(name)}."
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
370
544
|
def _validate_artifact_path(owner: str, path: str) -> None:
|
|
371
545
|
"""Reject any ``path`` that could not name a real file for any consumer.
|
|
372
546
|
|
|
@@ -518,54 +692,33 @@ class ArtifactDeclaration:
|
|
|
518
692
|
|
|
519
693
|
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
520
694
|
class Artifact:
|
|
521
|
-
"""
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
worker's ``plan ∪ sniff`` union, keyed by ``Path.resolve()`` of
|
|
531
|
-
:attr:`path` — declarations never enter that union. It is an authoring
|
|
532
|
-
value, not a wire row — it validates at construction so mistakes surface
|
|
533
|
-
on the author's machine (the
|
|
534
|
-
:class:`~simulo.interfaces.runtime.anomaly.DebugOnAnomaly` precedent);
|
|
535
|
-
the persisted/served row is :class:`ArtifactRecord`.
|
|
695
|
+
"""A file registration created while a job is running.
|
|
696
|
+
|
|
697
|
+
``simulo.save_artifact(name, path)`` builds this value and validates it
|
|
698
|
+
before adding it to the job's local output registry. It describes a real
|
|
699
|
+
file at a real path, but it is not confirmation that the file was uploaded.
|
|
700
|
+
The current managed platform does not publish general registered files for
|
|
701
|
+
download. Use a job result for small JSON values, a named volume for files
|
|
702
|
+
another job needs, or the supported model and recording workflows for
|
|
703
|
+
downloadable output.
|
|
536
704
|
"""
|
|
537
705
|
|
|
538
706
|
name: str
|
|
539
|
-
"""Logical
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
construction (a traversal-shaped name would otherwise become the
|
|
544
|
-
filename a client writes a download to — see
|
|
545
|
-
:func:`_validate_artifact_name` for the full rule and the download-site
|
|
546
|
-
obligation)."""
|
|
707
|
+
"""Logical filename for the registration. It must be one filename, never
|
|
708
|
+
a path. Separators, control characters, dots-only and whitespace-only
|
|
709
|
+
values, empty values, and names over
|
|
710
|
+
:data:`MAX_ARTIFACT_NAME_BYTES` are rejected."""
|
|
547
711
|
|
|
548
712
|
path: str
|
|
549
|
-
"""Filesystem path of the produced file
|
|
550
|
-
|
|
551
|
-
path
|
|
552
|
-
|
|
553
|
-
Validated at construction for TYPE (a ``str``), LENGTH
|
|
554
|
-
(≤ :data:`MAX_ARTIFACT_PATH_BYTES`), non-emptiness, and absence of a NUL
|
|
555
|
-
byte — the four things that make a value unusable as a path *by any
|
|
556
|
-
consumer*, checked here so the consumer does not discover them mid-upload.
|
|
557
|
-
Deliberately NOT validated for shape or location: a path is legitimately
|
|
558
|
-
absolute and may point anywhere the job can write, so *where* it points is
|
|
559
|
-
contained at the uploader (see the ``simulo.outputs`` module warning), not
|
|
560
|
-
here."""
|
|
713
|
+
"""Filesystem path of the produced file while the job is running.
|
|
714
|
+
Construction checks that it is a non-empty, NUL-free string within
|
|
715
|
+
:data:`MAX_ARTIFACT_PATH_BYTES`. The path may be absolute or relative."""
|
|
561
716
|
|
|
562
717
|
kind: ArtifactKind = ArtifactKind.FILE
|
|
563
|
-
"""What this
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
string is normalized to the enum at construction; an unknown kind string
|
|
568
|
-
is a ``ValueError`` on the author's machine."""
|
|
718
|
+
"""What kind of file this is. The default is
|
|
719
|
+
:attr:`~simulo.interfaces.platform.enums.ArtifactKind.FILE`. You may pass
|
|
720
|
+
an ``ArtifactKind`` value or its string form; unknown values raise
|
|
721
|
+
``ValueError``."""
|
|
569
722
|
|
|
570
723
|
def __post_init__(self) -> None:
|
|
571
724
|
object.__setattr__(self, "kind", ArtifactKind(self.kind))
|
|
@@ -623,8 +776,10 @@ class ArtifactRecord:
|
|
|
623
776
|
(a checkpoint is NOT a trained model — Rule #8, stated in the
|
|
624
777
|
:mod:`~simulo.interfaces.platform.domain` module docstring and on its
|
|
625
778
|
``Checkpoint``/``TrainedModel`` types).
|
|
626
|
-
``trained_model``
|
|
627
|
-
|
|
779
|
+
``trained_model`` DOES have a producer — the worker's general
|
|
780
|
+
job-artifacts road (``worker._upload_job_artifact``), whose rows live in
|
|
781
|
+
``job_artifacts`` — and it is only ever stamped explicitly, by the job
|
|
782
|
+
that registers the bundle, never inferred from a ``models`` row;
|
|
628
783
|
* rows projected from ``job_recordings`` map a legacy NULL kind to
|
|
629
784
|
:attr:`~simulo.interfaces.platform.enums.ArtifactKind.RECORDING` —
|
|
630
785
|
never to ``replay_output`` (see the enum docstrings)."""
|
{simulo_interfaces-0.13.0 → simulo_interfaces-0.14.0}/src/simulo/interfaces/platform/enums.py
RENAMED
|
@@ -172,6 +172,6 @@ class ArtifactKind(StrEnum):
|
|
|
172
172
|
:attr:`ResourceKind.DATASET` classifies an input mount."""
|
|
173
173
|
|
|
174
174
|
FILE = "file"
|
|
175
|
-
"""
|
|
176
|
-
``simulo.save_artifact
|
|
177
|
-
|
|
175
|
+
"""A general produced file. This is the default kind used by
|
|
176
|
+
``simulo.save_artifact``. The current managed platform records the
|
|
177
|
+
registration but does not publish general files for download."""
|