simulo-interfaces 0.2.0__tar.gz → 0.4.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 (35) hide show
  1. {simulo_interfaces-0.2.0/src/simulo_interfaces.egg-info → simulo_interfaces-0.4.0}/PKG-INFO +1 -1
  2. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/pyproject.toml +1 -1
  3. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/__init__.py +13 -0
  4. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/runs.py +81 -1
  5. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/submit.py +101 -4
  6. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0/src/simulo_interfaces.egg-info}/PKG-INFO +1 -1
  7. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/MANIFEST.in +0 -0
  8. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/PYPI.md +0 -0
  9. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/setup.cfg +0 -0
  10. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/__init__.py +0 -0
  11. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/exceptions.py +0 -0
  12. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/ids.py +0 -0
  13. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/app.py +0 -0
  14. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/asset.py +0 -0
  15. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/callbacks.py +0 -0
  16. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/debug.py +0 -0
  17. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/domain.py +0 -0
  18. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/enums.py +0 -0
  19. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/runtime.py +0 -0
  20. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/platform/volume.py +0 -0
  21. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/py.typed +0 -0
  22. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/__init__.py +0 -0
  23. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/anomaly.py +0 -0
  24. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/components.py +0 -0
  25. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/env.py +0 -0
  26. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/player.py +0 -0
  27. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/policy.py +0 -0
  28. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/scenario.py +0 -0
  29. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/task.py +0 -0
  30. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/tensors.py +0 -0
  31. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo/interfaces/runtime/trainer.py +0 -0
  32. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo_interfaces.egg-info/SOURCES.txt +0 -0
  33. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo_interfaces.egg-info/dependency_links.txt +0 -0
  34. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.0}/src/simulo_interfaces.egg-info/requires.txt +0 -0
  35. {simulo_interfaces-0.2.0 → simulo_interfaces-0.4.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.2.0
3
+ Version: 0.4.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.2.0"
7
+ version = "0.4.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"
@@ -14,12 +14,14 @@ from simulo.interfaces.platform.debug import DebugSessionProtocol, ReplaySession
14
14
  from simulo.interfaces.platform.domain import Checkpoint, Job, Package, Project, Resource, Tag, TrainedModel
15
15
  from simulo.interfaces.platform.enums import ArtifactKind, JobStatus, ResourceKind, ResumePolicy
16
16
  from simulo.interfaces.platform.runs import (
17
+ JOB_CANCEL_ROUTE_TEMPLATE,
17
18
  JOB_LOGS_ROUTE_TEMPLATE,
18
19
  JOB_RESULT_ROUTE_TEMPLATE,
19
20
  JOB_ROUTE_TEMPLATE,
20
21
  JOBS_API_DEFAULT_PORT,
21
22
  JOBS_ROUTE,
22
23
  LOG_CHUNK_MAX_BYTES,
24
+ LOG_TAIL_MAX_BYTES,
23
25
  TERMINAL_JOB_STATUSES,
24
26
  JobRecord,
25
27
  )
@@ -36,11 +38,15 @@ from simulo.interfaces.platform.submit import (
36
38
  MAX_LOG_TOTAL_BYTES,
37
39
  MAX_PACKAGE_BYTES,
38
40
  MAX_RESULT_BYTES,
41
+ MAX_SEED_REF_CHARS,
39
42
  MODEL_KINDS,
40
43
  PACKAGE_ARCHIVE_ROUTE_TEMPLATE,
41
44
  PACKAGES_ROUTE,
42
45
  RESERVED_RUNTIME_ENV_KEYS,
43
46
  RESERVED_RUNTIME_ENV_PREFIXES,
47
+ SEED_JOB_REF_GRAMMAR,
48
+ SEED_SOURCE_CONFLICT_CODE,
49
+ SEED_SOURCE_FIELDS,
44
50
  WORKER_CLAIM_ROUTE,
45
51
  WORKER_JOB_COMPLETE_ROUTE_TEMPLATE,
46
52
  WORKER_JOB_HEARTBEAT_ROUTE_TEMPLATE,
@@ -75,9 +81,11 @@ __all__ = [
75
81
  "JOBS_API_DEFAULT_PORT",
76
82
  "JOBS_ROUTE",
77
83
  "JOB_ROUTE_TEMPLATE",
84
+ "JOB_CANCEL_ROUTE_TEMPLATE",
78
85
  "JOB_LOGS_ROUTE_TEMPLATE",
79
86
  "JOB_RESULT_ROUTE_TEMPLATE",
80
87
  "LOG_CHUNK_MAX_BYTES",
88
+ "LOG_TAIL_MAX_BYTES",
81
89
  # submit / worker / recordings wire contract (PR-0)
82
90
  "PACKAGES_ROUTE",
83
91
  "PACKAGE_ARCHIVE_ROUTE_TEMPLATE",
@@ -106,6 +114,11 @@ __all__ = [
106
114
  "RecordingRecord",
107
115
  "RESERVED_RUNTIME_ENV_KEYS",
108
116
  "RESERVED_RUNTIME_ENV_PREFIXES",
117
+ # explicit-run-intent plan: submit-time seed source (additive)
118
+ "SEED_SOURCE_FIELDS",
119
+ "SEED_SOURCE_CONFLICT_CODE",
120
+ "SEED_JOB_REF_GRAMMAR",
121
+ "MAX_SEED_REF_CHARS",
109
122
  # protocols
110
123
  "RuntimeProtocol",
111
124
  "AssetProtocol",
@@ -37,8 +37,50 @@ HTTP API shape (all responses JSON; all timestamps ISO 8601 UTC, e.g.
37
37
  ``next_offset == offset``; the accompanying ``status`` tells the client
38
38
  whether more output may still arrive (poll while the status is
39
39
  non-terminal, stop once it is in :data:`TERMINAL_JOB_STATUSES`).
40
+
41
+ **Tail semantics (optional, additive):** ``?tail_bytes=K`` (``0 <= K <=``
42
+ :data:`LOG_TAIL_MAX_BYTES`; ``offset`` must be omitted or 0, otherwise the
43
+ request is ambiguous and answers 422 — the control plane's code is
44
+ ``tail_bytes_conflicts_with_offset``; clients need only the status)
45
+ asks the server to start the read near the END of the stream instead of at
46
+ ``offset``: the server computes ``start = max(0, total_len - K)`` and
47
+ returns the chunk from there, adding ``"start_offset": start`` to the
48
+ response. ``start_offset`` is present ONLY when tail was honored — its
49
+ absence tells a tail-requesting client it is talking to an older server
50
+ that ignored the (undeclared) parameter and answered from offset 0, so the
51
+ client can degrade to full history instead of mis-trimming. ``tail_bytes=0``
52
+ is the "seek to end-of-log" primitive: an empty ``chunk`` with
53
+ ``next_offset == start_offset == total_len``. After one tail response the
54
+ client resumes normal offset paging from ``next_offset`` (never sending
55
+ ``tail_bytes`` again), so a tail-following client never drains the full
56
+ history. ``start`` is a byte position and may land mid-line or mid-UTF-8
57
+ sequence; a line-oriented client trims its first partial line client-side
58
+ (``simulo logs --follow`` requests a generous byte window for its line
59
+ count and does exactly that).
40
60
  * ``GET /v1/jobs/{job_id}/result`` — the job's result JSON object. 404 (code
41
61
  ``result_not_available``) until the job has completed successfully.
62
+ * ``POST /v1/jobs/{job_id}/cancel`` — request cancellation of a queued or
63
+ running job (no request body). Semantics by current status:
64
+
65
+ - ``queued`` → the job transitions to terminal ``cancelled`` immediately
66
+ (it can never be claimed afterwards). 200::
67
+
68
+ {"job_id": "...", "status": "cancelled"}
69
+
70
+ - ``running`` → cancellation is REQUESTED; the executor observes it on its
71
+ next heartbeat, stops the job, and reports terminal ``cancelled``
72
+ (existing ``best.pt``/``latest.pt`` checkpoints are uploaded first —
73
+ cancel never vaporizes training progress; partial recordings are
74
+ dropped). Idempotent: repeat requests return the same 202::
75
+
76
+ {"job_id": "...", "status": "running", "cancel_requested": true}
77
+
78
+ A ``running`` job whose executor is provably gone (expired lease, no
79
+ active claim) transitions directly to ``cancelled`` → 200 as above.
80
+ - terminal (``completed``/``failed``/``cancelled``) → 409, code
81
+ ``job_not_running``, with the job's actual status in the human message.
82
+ - unknown/cross-tenant/malformed id → 404 ``job_not_found`` (never 403 —
83
+ anti-enumeration, same as every other per-job route).
42
84
 
43
85
  **Scoping (evolves toward the cloud):** every route above is UNSCOPED locally
44
86
  — there is no project/org filter because the local stand-in has exactly one
@@ -77,10 +119,48 @@ JOB_ROUTE_TEMPLATE = "/v1/jobs/{job_id}"
77
119
  JOB_LOGS_ROUTE_TEMPLATE = "/v1/jobs/{job_id}/logs"
78
120
  #: ``GET`` — the result JSON of a successfully completed job. ``.format(job_id=...)``.
79
121
  JOB_RESULT_ROUTE_TEMPLATE = "/v1/jobs/{job_id}/result"
122
+ #: ``POST`` — cancel a queued/running job (no body). ``.format(job_id=...)``.
123
+ #: 200 terminal ``cancelled`` (queued, or running with a provably-dead
124
+ #: executor); 202 ``{"cancel_requested": true}`` (running — the executor
125
+ #: stops it within one heartbeat interval); 409 ``job_not_running``
126
+ #: (already terminal); 404 ``job_not_found``. Full semantics in the module
127
+ #: docstring above.
128
+ JOB_CANCEL_ROUTE_TEMPLATE = "/v1/jobs/{job_id}/cancel"
80
129
 
81
130
  #: Upper bound on the log bytes a single ``/logs`` response carries. The server
82
131
  #: may return fewer bytes (never more); clients keep reading from ``next_offset``.
83
- LOG_CHUNK_MAX_BYTES = 64 * 1024
132
+ #: This is a server-side response-size POLICY value, not a structural wire
133
+ #: shape — clients are not expected to depend on the exact number (they
134
+ #: read ``chunk``/``next_offset`` generically and loop until caught up), so
135
+ #: it is safe to raise without breaking already-published clients reading
136
+ #: OFFSET responses. **Rollout order caveat (added with tail semantics):**
137
+ #: :data:`LOG_TAIL_MAX_BYTES` is DERIVED from this constant (``4x``), so
138
+ #: raising this ALSO raises what a new client may send as ``?tail_bytes=``.
139
+ #: A server that has not yet picked up the raised bound still enforces its
140
+ #: OLD, smaller ``le=`` cap on that query param and answers 422 — a hard
141
+ #: rejection the client's tail-degradation path does not cover (that path
142
+ #: handles an old server IGNORING the unknown param, not one REJECTING an
143
+ #: in-range-for-the-client-but-out-of-range-for-the-server value). Deploy
144
+ #: every server before shipping a `simulo-interfaces` release that raises
145
+ #: this constant. Bumped
146
+ #: 64 KiB -> 256 KiB (simulo-control-plane PR6, 2026-07-11): a poll-cadence
147
+ #: rate limit sized against the OLD 64 KiB cap could still 429 mid-drain on
148
+ #: a large/fast-growing log because a no-sleep catch-up loop bursts through
149
+ #: it; see that PR's `jobs/routes.py::_LOGS_POLL_RATE` docstring for the
150
+ #: exact math. No PyPI republish accompanies this source change — no
151
+ #: published thin-client version reads this constant at runtime; it takes
152
+ #: effect for whichever `simulo-interfaces` version ships next.
153
+ LOG_CHUNK_MAX_BYTES = 256 * 1024
154
+
155
+ #: Upper bound on ``?tail_bytes=`` (the "start this many bytes before the end"
156
+ #: request parameter — see the module docstring's tail semantics). A tail
157
+ #: window is a REQUEST-side positioning value, not a response-size promise:
158
+ #: the response is still capped at :data:`LOG_CHUNK_MAX_BYTES`, and a window
159
+ #: larger than one response chunk arrives across normal offset pages. 4x the
160
+ #: response cap comfortably covers the CLI's default (50 lines x ~200
161
+ #: bytes/line ≈ 10 KiB) and any plausible ``--tail N`` while keeping the
162
+ #: server's tail scan bounded. Values above this cap answer 422.
163
+ LOG_TAIL_MAX_BYTES = 4 * LOG_CHUNK_MAX_BYTES
84
164
 
85
165
  #: Statuses after which a job's log stream and record no longer change. A log
86
166
  #: follower drains the remaining bytes and stops once the status is terminal.
@@ -83,6 +83,49 @@ platform-standard shape: ``{"error": {"code": ..., "message": ..., "request_id":
83
83
  with the *existing* record rather than creating a second job — a client
84
84
  retry (e.g. after a dropped response) must not double-submit.
85
85
 
86
+ **Seed source (explicit-run-intent plan, additive):** the request body
87
+ optionally carries one of two further top-level fields, both **siblings of
88
+ ``args`` — never nested inside it, and never written into the package
89
+ manifest**::
90
+
91
+ {"package_id": "...", "job_name": "train_cartpole", "args": {...},
92
+ "seed_from_job_id": "<job-id>[:latest]"}
93
+
94
+ This placement is load-bearing, not stylistic: ``args``' canonical JSON is
95
+ what the client hashes (together with the source digest and job name) to
96
+ derive ``package_id`` (``_client/manifest.py``), so a seed reference living
97
+ inside ``args`` would make re-running the exact same package bytes with a
98
+ *different* seed register as a *different* package — collapsing package
99
+ identity (content-addressed, immutable) into job identity (per-submit,
100
+ mutable). ``viewstream`` (the worker's ``_child_env``) already follows this
101
+ same package-≠-job placement rule; these two fields are the same shape.
102
+ Consumers of this contract MUST build the seed fields onto the request
103
+ exactly the way ``create_job`` builds ``viewstream`` today — as extra keys
104
+ set directly on the top-level body dict — and MUST NOT fold them into the
105
+ ``args`` mapping that gets hashed.
106
+
107
+ :data:`SEED_SOURCE_FIELDS` pins the two field names:
108
+
109
+ - ``seed_from_job_id`` (``str``) — continue training from another job's
110
+ checkpoint. A bare job id selects that job's ``best.pt``; suffixing
111
+ ``:latest`` (``"<job-id>:latest"``) selects ``latest.pt`` instead — the
112
+ suffix lives inside this one string; there is no separate "kind" field.
113
+ - ``seed_from_model_id`` (``str``) — continue from one exact
114
+ :class:`ModelRecord` artifact, pinned by id.
115
+
116
+ Both default to (and an old client that predates this field pair always
117
+ sends) absent/``None`` — a **fresh run**, the new default with no
118
+ exceptions: an un-upgraded client that omits both fields degrades to
119
+ exactly the semantics this plan wants, with zero change to the bytes it
120
+ already sends. The two are **mutually exclusive**: a request setting both
121
+ is rejected whole, ``422`` :data:`SEED_SOURCE_CONFLICT_CODE`
122
+ (``"seed_source_conflict"``) — the server never silently prefers one over
123
+ the other. Further validation of the referenced job/model (org membership,
124
+ terminal status, artifact kind) is server-side business logic layered on
125
+ top of this wire shape, not defined by this module — see the
126
+ explicit-run-intent plan's "Server validation" section
127
+ (``context/implementation-plans/explicit-run-intent-plan.md``).
128
+
86
129
  ## Client-facing: recordings
87
130
 
88
131
  * :data:`JOB_RECORDINGS_ROUTE_TEMPLATE` (``GET``) — paginated envelope of
@@ -298,6 +341,59 @@ JOB_MODEL_DOWNLOAD_ROUTE_TEMPLATE = "/v1/jobs/{job_id}/models/{model_id}/downloa
298
341
  #: There is deliberately no separate ``*_SUBMIT_ROUTE`` constant here — see
299
342
  #: the module docstring's "Client-facing: package submit + job create" section.
300
343
 
344
+ #: The two mutually-exclusive, optional ``POST /v1/jobs`` body fields — both
345
+ #: **siblings of ``args``**, never nested inside it — that seed a fresh job's
346
+ #: starting checkpoint from another job's or model's already-uploaded
347
+ #: artifact (explicit-run-intent plan). Neither set = a fresh run, the
348
+ #: default and the only behavior an old client (unaware of this pair) can
349
+ #: produce. Setting both in the same request is rejected whole as
350
+ #: :data:`SEED_SOURCE_CONFLICT_CODE`. Full field-by-field semantics live in
351
+ #: the module docstring's "Client-facing: package submit + job create"
352
+ #: section, above — this tuple exists so PRs building against this contract
353
+ #: (control plane request validation, the thin client's ``--from`` flag —
354
+ #: which resolves a job-ref + artifact name to a concrete ``model_id``, so no
355
+ #: UUID ever reaches a human) import one pinned pair of literal names instead
356
+ #: of re-typing them. There is deliberately no ``--from-model`` CLI flag.
357
+ SEED_SOURCE_FIELDS = ("seed_from_job_id", "seed_from_model_id")
358
+
359
+ #: Grammar of the ``seed_from_job_id`` value (the ``seed_from_model_id`` value
360
+ #: is a bare model id, no suffix). Pinned here because BOTH sides parse it and
361
+ #: a divergence is silent: the client would accept a value the server rejects
362
+ #: (a wasted package build + archive upload for a typo), or reject one the
363
+ #: server accepts.
364
+ #:
365
+ #: ``<job-ref>[:<kind>]`` — ``job-ref`` is a full job UUID or an unambiguous
366
+ #: id prefix; ``kind`` is one of :data:`MODEL_KINDS`, compared **lowercased
367
+ #: after stripping surrounding whitespace**. Rules, in the order both sides
368
+ #: apply them:
369
+ #:
370
+ #: * ``":" not in value`` → the whole value is the ref; ``kind`` defaults to
371
+ #: ``MODEL_KINDS[0]`` (``"best"``). ``":"`` is not in the UUID/prefix
372
+ #: alphabet, so its presence is an unambiguous suffix signal.
373
+ #: * otherwise split on the LAST ``":"`` (``rpartition``) and reject — never
374
+ #: silently fall back — when EITHER side is empty (``":latest"`` is not a
375
+ #: ref named ``latest``; ``"8b52:"`` is not an implicit ``:best``) or when
376
+ #: ``kind`` is outside :data:`MODEL_KINDS`.
377
+ #:
378
+ #: A value violating this grammar is ``422`` ``seed_artifact_invalid``.
379
+ SEED_JOB_REF_GRAMMAR = "<job-ref>[:best|:latest]"
380
+
381
+ #: Server-pinned max length of either :data:`SEED_SOURCE_FIELDS` value on the
382
+ #: wire (the control plane's Pydantic ``max_length``). A client MUST check
383
+ #: this before packaging: a longer value is otherwise packaged and uploaded
384
+ #: only to be rejected by generic schema validation, whose envelope carries
385
+ #: none of the seed error codes a client maps to friendly copy.
386
+ MAX_SEED_REF_CHARS = 80
387
+
388
+ #: Error code (``POST /v1/jobs``, ``422``) when a request sets BOTH
389
+ #: :data:`SEED_SOURCE_FIELDS` in the same body. Pinned as a constant — unlike
390
+ #: this module's other wire error codes (e.g. ``lease_lost``,
391
+ #: ``log_offset_gap``), which are documented as literal strings only —
392
+ #: because both the control plane raising it and the thin client's seed-source
393
+ #: mutual-exclusion check need to switch on this exact string; a bare literal
394
+ #: risks silent drift between the two.
395
+ SEED_SOURCE_CONFLICT_CODE = "seed_source_conflict"
396
+
301
397
  # --------------------------------------------------------------------------
302
398
  # Worker-facing routes (bearer = static WORKER_TOKEN, constant-time compare;
303
399
  # empty server-side config disables every route below, fail-closed)
@@ -344,10 +440,11 @@ MAX_PACKAGE_BYTES = 64 * 1024 * 1024 # 64 MiB
344
440
  #: (``POST`` :data:`WORKER_JOB_LOGS_ROUTE_TEMPLATE` body) — the write-side cap, 1 MiB.
345
441
  #:
346
442
  #: **Not the same constant as** ``simulo.interfaces.platform.runs.LOG_CHUNK_MAX_BYTES``
347
- #: (64 KiB) that is the pre-existing **client** read-side cap on a single
348
- #: ``GET .../logs?offset=N`` response (``runs.py``). Both are now re-exported
349
- #: from ``simulo.interfaces.platform``, so importing the wrong one silently
350
- #: changes behavior by 16x: a worker that POSTs up to ``runs.LOG_CHUNK_MAX_BYTES``
443
+ #: (256 KiB as of 2026-07-11 see that constant's docstring) that is the
444
+ #: pre-existing **client** read-side cap on a single ``GET .../logs?offset=N``
445
+ #: response (``runs.py``). Both are now re-exported from
446
+ #: ``simulo.interfaces.platform``, so importing the wrong one silently
447
+ #: changes behavior by 4x: a worker that POSTs up to ``runs.LOG_CHUNK_MAX_BYTES``
351
448
  #: per call under-uses its allowance; a client GET that budgeted for this
352
449
  #: (larger) constant would be surprised the server never returns that much.
353
450
  MAX_LOG_CHUNK_BYTES = 1 * 1024 * 1024 # 1 MiB
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo-interfaces
3
- Version: 0.2.0
3
+ Version: 0.4.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