scientific-workflow 0.4.4__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,39 @@
1
+ """Official verified Python reader for Scientific Workflow recordings."""
2
+
3
+ from .errors import (
4
+ DecoderError,
5
+ IntegrityError,
6
+ MetadataError,
7
+ RecordError,
8
+ RecordingError,
9
+ RecordingNotCompleteError,
10
+ UnknownStreamError,
11
+ )
12
+ from .state import StateField, StateRecord, StateSeries
13
+ from .reader import (
14
+ FORMAT_NAME,
15
+ FORMAT_VERSION,
16
+ Decoder,
17
+ RecordingReader,
18
+ open_completed_recording,
19
+ )
20
+
21
+ __all__ = [
22
+ "Decoder",
23
+ "DecoderError",
24
+ "FORMAT_NAME",
25
+ "FORMAT_VERSION",
26
+ "IntegrityError",
27
+ "MetadataError",
28
+ "RecordError",
29
+ "RecordingError",
30
+ "RecordingNotCompleteError",
31
+ "RecordingReader",
32
+ "StateField",
33
+ "StateRecord",
34
+ "StateSeries",
35
+ "UnknownStreamError",
36
+ "open_completed_recording",
37
+ ]
38
+
39
+ __version__ = "0.4.4"
@@ -0,0 +1,59 @@
1
+ """Private cooperative control for the coordinated standard converter."""
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+ import time
6
+
7
+ _TOKEN = "parent"
8
+ _LAST_CHECK = 0.0
9
+ _PAUSE_STARTED = None
10
+ _PAUSE_TOTAL = 0.0
11
+
12
+
13
+ def state() -> tuple[Path | None, bool, bool]:
14
+ value = os.environ.get("WORKFLOW_CONTROL_PATH")
15
+ if not value:
16
+ return None, False, False
17
+ path = Path(value)
18
+ document = json.loads(path.read_text(encoding="utf-8"))
19
+ global _PAUSE_STARTED, _PAUSE_TOTAL
20
+ now = time.monotonic()
21
+ if document["paused"] and _PAUSE_STARTED is None:
22
+ _PAUSE_STARTED = now
23
+ elif not document["paused"] and _PAUSE_STARTED is not None:
24
+ _PAUSE_TOTAL += now - _PAUSE_STARTED
25
+ _PAUSE_STARTED = None
26
+ return path, document["paused"], document["cancelled"]
27
+
28
+
29
+ def acknowledgement(path: Path, token: str) -> Path:
30
+ return path.with_name(path.name + f".{token}.paused")
31
+
32
+
33
+ def checkpoint(*, force: bool = False) -> None:
34
+ global _LAST_CHECK
35
+ now = time.monotonic()
36
+ if not force and now - _LAST_CHECK < 0.02:
37
+ return
38
+ _LAST_CHECK = now
39
+ path, paused, cancelled = state()
40
+ if cancelled:
41
+ raise InterruptedError("Workflow conversion cancelled")
42
+ if not paused or path is None:
43
+ return
44
+ ack = acknowledgement(path, _TOKEN)
45
+ ack.touch()
46
+ try:
47
+ while paused:
48
+ time.sleep(0.01)
49
+ _, paused, cancelled = state()
50
+ if cancelled:
51
+ raise InterruptedError("Workflow conversion cancelled")
52
+ finally:
53
+ ack.unlink(missing_ok=True)
54
+
55
+
56
+ def active_time() -> float:
57
+ """Converter diagnostic clock; Runtime remains authoritative for task budgets."""
58
+ state()
59
+ return (_PAUSE_STARTED if _PAUSE_STARTED is not None else time.monotonic()) - _PAUSE_TOTAL
@@ -0,0 +1,221 @@
1
+ # Python companion API
2
+
3
+ Python distribution/import: `scientific-workflow` / `scientific_workflow`, version
4
+ 0.4.4. Python 3.14+; Linux is the supported execution platform. The base package
5
+ has no runtime dependencies; `[npy]` installs NumPy. Imports have no environment,
6
+ logging, working-directory, subprocess, or output-creation side effects.
7
+
8
+ ## Basic API
9
+
10
+ ### Recording reader
11
+
12
+ `scientific_workflow.reader.open_completed_recording(directory, decoders=None)`
13
+ returns a `RecordingReader` after metadata validation and successful-lifecycle
14
+ validation. Directory parameters accept `str | pathlib.Path`. Root reexports
15
+ remain available for the reader, state containers, and recording errors; the old
16
+ `scientific_workflow_reader` import package is not provided.
17
+
18
+ `RecordingReader(directory, decoders=None)` validates metadata but permits
19
+ inspection of incomplete recordings. Properties: `directory`, `format_version`
20
+ (the file's actual 7 or 8), `stream_names` (ordered tuple), `user_metadata`,
21
+ `terminal_metadata`, `timing`. Methods: `stream_record_count(stream)`,
22
+ `stream_encoded_bytes(stream)`, `read_stream(stream)`, `read_all_streams()`,
23
+ `read_latest(stream)`, `iter_verified_records(stream)`. Complete reads are
24
+ transactional; the iterator verifies a bounded chunk before yielding and may
25
+ fail later after prior chunks were yielded. Decoders are mappings from field
26
+ name to callable; ordinary JSON values are the default. See the Python README
27
+ for integrity/lifecycle details and custom-decoder examples.
28
+
29
+ `FORMAT_NAME` identifies scientific-workflow-jsonl; `FORMAT_VERSION` is the
30
+ highest supported version, 8. Writers remain Rust-owned. `Decoder` is the decoder
31
+ type alias. `StateField`, `StateRecord`, `StateSeries` are frozen containers;
32
+ application-supplied payloads retain their own mutability. All recording error
33
+ classes are exported at package root: `RecordingError`, `MetadataError`,
34
+ `IntegrityError`, `RecordError`, `DecoderError`, `RecordingNotCompleteError`,
35
+ `UnknownStreamError`. They preserve contextual stream/file information; no
36
+ scientific partial series is returned by complete reads.
37
+
38
+ ### Dependencies
39
+
40
+ `scientific_workflow.dependencies.Dependencies(snapshot)` validates an owned
41
+ copy of a dependency JSON array. `load(path)` reads an explicit snapshot;
42
+ `from_env()` requires WORKFLOW_DEPENDENCIES_PATH. Both are class methods.
43
+ `recordings()`, `programs()`, `npy_batches()` return typed `Selection` objects.
44
+ `raw_json()` returns an independent mutable JSON copy including unknown kinds.
45
+
46
+ Result dataclasses are frozen and contain paths as `Path`:
47
+
48
+ | Type | Public attributes |
49
+ |---|---|
50
+ | `RecordingDependency` | phase, task, execution_unit, member, final_iteration, directory |
51
+ | `ProgramDependency` | phase, task, directory, executable, python_script (Path or None) |
52
+ | `NpyDependency` | phase, task, directory |
53
+
54
+ Acquire results from Dependencies; direct dataclass construction does not validate
55
+ an external snapshot. Program directory means `<task>/artifacts`; recording
56
+ means member root; NPY means aggregate batch root. Runtime already selected the
57
+ replicate/configuration scope. NPY batches may contain several global configurations.
58
+
59
+ `Selection.in_phase(key)` and `.task(identity)` return new intersections.
60
+ `.execution_unit(key)` and `.member(identity)` filter recordings; they match
61
+ nothing on other result types. `.one()` requires exactly one result;
62
+ `.optional()` allows zero or one but rejects ambiguity. `.iter()` and Python
63
+ iteration enumerate all matches in deterministic snapshot order. All lookups are
64
+ pure and perform no scientific I/O. Selection references keep results alive.
65
+
66
+ `DependencyError(ValueError)` covers malformed snapshots and read/environment
67
+ failures. `MissingDependencyError` means zero matches for one().
68
+ `AmbiguousDependencyError` exposes `selection` and `matches` and identifies all
69
+ matching phase/task/member sources. Known kinds require valid fields, unique
70
+ identifiers, absolute paths and u64 iterations; unknown extension keys/kinds are
71
+ preserved. File existence and scientific correctness are checked by the reader.
72
+
73
+ ### Standard project accessors
74
+
75
+ **REQUIRED LAYOUT:** declarations remain at `<study>/wf_configs/study.json` and
76
+ `parameters.json`. Runtime creates per-program `workflow-config.json` and
77
+ `workflow-dependencies.json` beside `artifacts/`, `stdout.log`, and `stderr.log`.
78
+ **Do not rename or relocate required files.** There is no heuristic discovery.
79
+
80
+ `scientific_workflow.project` exports:
81
+
82
+ - `project_root() -> Path`: verify WORKFLOW_PROJECT_ROOT is an absolute directory.
83
+ - `output_directory() -> Path`: verify WORKFLOW_TASK_OUTPUT, the artifacts directory.
84
+ - `study_path(root) -> Path`: require `<root>/wf_configs/study.json`; no parsing.
85
+ - `parameters(section=None, *, snapshot=None) -> object`: load resolved parameters
86
+ from WORKFLOW_CONFIG_PATH or an explicit runtime snapshot. Return all parameters
87
+ or one exact top-level section. Do not reread unresolved source declarations.
88
+ - `ProjectLayoutError(ValueError)`: identifies the required variable/file/layout
89
+ and chains underlying read/parse failures.
90
+
91
+ Accessors synchronously read files/environment. They do not create files, mutate
92
+ cwd, configure logging, activate environments or implement a second resolver.
93
+ Use explicit paths outside a Workflow program; from-env calls require the launch
94
+ contract. There is no ProgramContext.
95
+
96
+ ### NPY readers and whole-series views
97
+
98
+ `scientific_workflow.npy` requires the `[npy]` extra.
99
+ `open_npy_batch(directory) -> NpyBatch` verifies the batch and every member.
100
+ `open_npy_conversion(directory) -> NpyConversion` verifies one member. Acquire
101
+ objects through these functions; direct constructors do not establish integrity.
102
+ Both require the standard manifest directory, not an individual `.npy` path.
103
+
104
+ `NpyBatch` attributes: `directory`, `manifest`, `members` (ordered tuple).
105
+ `NpyConversion` attributes: `directory`, `manifest`, `stream_names` (tuple),
106
+ `execution_unit` (provenance key or None). Methods:
107
+
108
+ | Method | Result |
109
+ |---|---|
110
+ | `array(relative_path)` | Cached read-only memory map of a declared component |
111
+ | `field(stream, field)` | Field representation metadata |
112
+ | `reconstruct(stream, field, record)` | One exact numeric or JSON fallback record |
113
+ | `projection(stream, field, logical_path, record)` | One structured numeric projection record |
114
+ | `coordinates(stream)` | Tuple (iterations, physical_times); absent physical times are None |
115
+ | `series(stream, field, logical_path=None)` | Cached FixedSeries or RaggedSeries |
116
+
117
+ Omit logical_path for wholly numeric fields. Structured fields require an exact
118
+ projection path, including JSON-pointer escaping. Missing or ambiguous projections
119
+ raise `NpyConversionError`. Opening verifies checksums/layout up front; series
120
+ access does not repeat that complete validation or reconstruct every JSON record.
121
+ Callers must not mutate metadata dictionaries or source files after opening.
122
+
123
+ `NumericSeries = FixedSeries | RaggedSeries`. Both frozen dataclasses expose
124
+ `iterations`, optional `physical_times` (None when absent), `len(series)`, and `record(index)`.
125
+ `FixedSeries.values` is a read-only array with a leading record axis.
126
+ `RaggedSeries.data`, `.offsets`, `.shapes` are read-only components; record()
127
+ slices and reshapes one record in C order, including empty shapes. Indices must
128
+ be Python integers in [0, len); booleans, negative and out-of-range indices raise
129
+ IndexError. Views retain references to maps and remain usable while retained;
130
+ there is no explicit close API. Reading pages may incur filesystem I/O. Access
131
+ is read-only and has no cancellation or publication effects.
132
+
133
+ ## Advanced API
134
+
135
+ `convert_recording(recording_directory, output_directory=None)` verifies and
136
+ converts a completed recording, returning its manifest. Default output is the
137
+ recording sibling suffixed `-npy`. Conflicts fail; verified matching output is
138
+ reused. Successful publication is atomic after complete validation. Source files
139
+ must remain immutable. NPY_FORMAT and NPY_BATCH_FORMAT remain v2;
140
+ MANIFEST_FILE is `manifest.json`. NpyConversionError(ValueError) reports storage
141
+ contract failures; recording errors and I/O failures retain their own types.
142
+
143
+ `convert_workflow_dependencies(dependencies_path, output_directory)` converts
144
+ completed prerequisite recordings and publishes a batch in stable source order.
145
+ The installed CLI `scientific-workflow-to-npy` and `python -m
146
+ scientific_workflow.npy` call `main()`. Consult `--help` for CLI flags; public
147
+ conversion call shapes remain unchanged. No supported Python recording writer
148
+ or runtime scheduler/control handle is exposed.
149
+
150
+ Optional dependencies are imported only by their owning module. OF/Dispatcher
151
+ retain domain adapters, scientific validation, statistics and plotting. This
152
+ package owns generic wire/layout mechanics.
153
+
154
+ ## Example
155
+
156
+ ```python
157
+ from scientific_workflow.dependencies import Dependencies
158
+ from scientific_workflow.npy import open_npy_batch
159
+ from scientific_workflow.project import parameters
160
+
161
+ settings = parameters("analysis")
162
+ batch_path = Dependencies.from_env().npy_batches().one().directory
163
+ batch = open_npy_batch(batch_path)
164
+ for member in batch.members:
165
+ if member.execution_unit == "simulation":
166
+ signal = member.series("statistics", "stats", "/energy")
167
+ print(signal.iterations, signal.record(0))
168
+ ```
169
+
170
+ The example runs in a standard Workflow-launched analysis task. Outside Workflow,
171
+ use Dependencies.load(explicit_snapshot) and parameters(snapshot=explicit_path).
172
+
173
+ ## Not API
174
+
175
+ Underscore-prefixed validators, planners, writers, framing helpers, cache layout,
176
+ worker orchestration and temporary naming are internal. Recording and NPY wire
177
+ schemas are separately versioned contracts; implementation internals are not.
178
+
179
+ ## Reporting reference
180
+
181
+ `scientific_workflow.reporting.log(message: str, *, level="info")` emits one
182
+ flushed prefixed event to stderr. Levels: debug/info/warning/error/success.
183
+ `progress(stage: str, completed: int, total: int | None = None, *, unit="records")`
184
+ emits counts; nonempty stage/unit and u64 bounds are required. Invalid input or
185
+ frames over 16 KiB raise ValueError before output; stderr write/flush errors
186
+ propagate. Calls serialize threads in one process but do not synchronize unrelated
187
+ processes. Outside Workflow, output remains prefixed stderr lines.
188
+
189
+ `WorkflowHandler(logging.Handler)` follows standard Handler construction,
190
+ level/filter/formatter/close behavior and overrides emit(record). It maps standard
191
+ logging levels to Workflow severities and formats through the installed formatter.
192
+ `install_logging(logger=None, *, level=logging.INFO) -> WorkflowHandler` attaches
193
+ one handler idempotently to that logger (root when omitted). It sets handler level,
194
+ not logger level; callers retain logging policy. Remove through
195
+ logger.removeHandler(handler) and handler.close(). Imports do not configure logging.
196
+ Each process configures its own logging; converter workers use a bounded queue to
197
+ the parent emitter rather than writing progress directly. See program-events-v1.
198
+
199
+ ## Converter execution and publication
200
+
201
+ `convert_workflow_dependencies` uses min(WORKFLOW_THREADS, unique recordings),
202
+ with a standalone default of one. Rust supplies/reserves the allowance across
203
+ replicates. Spawn workers own their verified reader and arrays; threadpoolctl
204
+ limits native numeric pools to one thread each. There is no new mandatory public
205
+ argument. Progress reports planning, writing, verification, member reuse/completion,
206
+ and batch totals. Completion order never changes manifest member order.
207
+
208
+ Linux directory flock serializes competing publishers. Unique temporary paths
209
+ avoid same-process collisions. Failure terminates/joins workers, publishes no
210
+ success batch, and retains individually verified members for retry. Staging
211
+ folders left by abrupt termination are not published data. Successful metadata
212
+ publication uses atomic replacement. Private control checkpoints freeze work at
213
+ record/hash/job boundaries; parent acknowledgement requires all active jobs to
214
+ be paused or complete. Admission stops during pause. Cancel while paused wakes
215
+ and terminates work; raw log draining in Rust continues throughout.
216
+
217
+ State container detail: StateField(name, description=None) exposes those fields;
218
+ StateRecord(iteration, physical_time, values) exposes them, and create() wraps
219
+ values in a read-only MappingProxyType. StateSeries(stream, fields, records)
220
+ implements len, indexing/slicing, iteration, and an iterations tuple property.
221
+ These frozen containers do not deep-freeze decoded application payloads.
@@ -0,0 +1,198 @@
1
+ """Typed access to immutable results from Workflow's declared dependencies.
2
+
3
+ Snapshot selection performs no scientific I/O and never broadens runtime scope.
4
+ The core module has no NumPy dependency. See api.md for the complete contract.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import copy
9
+ import json
10
+ import os
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Generic, Iterator, TypeVar
14
+
15
+
16
+ class DependencyError(ValueError):
17
+ """Invalid dependency snapshot or selection."""
18
+
19
+
20
+ class MissingDependencyError(DependencyError):
21
+ """No result satisfies a selection requiring exactly one."""
22
+
23
+
24
+ class AmbiguousDependencyError(DependencyError):
25
+ """Multiple results satisfy a selection requiring at most one."""
26
+
27
+ def __init__(self, selection: str, matches: tuple[str, ...]):
28
+ self.selection, self.matches = selection, matches
29
+ super().__init__(f"ambiguous dependency {selection}: {', '.join(matches)}; select a phase or task")
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class RecordingDependency:
34
+ phase: str
35
+ task: str
36
+ execution_unit: str
37
+ member: str
38
+ final_iteration: int
39
+ directory: Path
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class ProgramDependency:
44
+ phase: str
45
+ task: str
46
+ directory: Path
47
+ executable: Path
48
+ python_script: Path | None
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class NpyDependency:
53
+ phase: str
54
+ task: str
55
+ directory: Path
56
+
57
+
58
+ T = TypeVar("T", RecordingDependency, ProgramDependency, NpyDependency)
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class Selection(Generic[T]):
63
+ """Immutable intersection of exact selectors, in snapshot order."""
64
+ _entries: tuple[T, ...]
65
+ _filters: tuple[str, ...] = ()
66
+
67
+ def _filter(self, field: str, value: str) -> Selection[T]:
68
+ return Selection(tuple(e for e in self._entries if getattr(e, field, None) == value),
69
+ (*self._filters, f"{field}={value!r}"))
70
+
71
+ def in_phase(self, phase: str) -> Selection[T]:
72
+ return self._filter("phase", phase)
73
+
74
+ def task(self, identity: str) -> Selection[T]:
75
+ return self._filter("task", identity)
76
+
77
+ def execution_unit(self, key: str) -> Selection[T]:
78
+ """Restrict recording results to an execution-unit key."""
79
+ return self._filter("execution_unit", key)
80
+
81
+ def member(self, identity: str) -> Selection[T]:
82
+ """Restrict recording results to a member identity."""
83
+ return self._filter("member", identity)
84
+
85
+ def one(self) -> T:
86
+ result = self.optional()
87
+ if result is None:
88
+ raise MissingDependencyError(f"no dependency matches {self._filters}")
89
+ return result
90
+
91
+ def optional(self) -> T | None:
92
+ if len(self._entries) > 1:
93
+ raise AmbiguousDependencyError(str(self._filters), tuple(
94
+ f"{e.phase}/{e.task}" + (f"/{e.member}" if isinstance(e, RecordingDependency) else "")
95
+ for e in self._entries))
96
+ return self._entries[0] if self._entries else None
97
+
98
+ def __iter__(self) -> Iterator[T]:
99
+ return iter(self._entries)
100
+
101
+ def iter(self) -> Iterator[T]:
102
+ return iter(self)
103
+
104
+
105
+ def _name(value: object) -> str:
106
+ if not isinstance(value, str) or not value or value.strip() != value:
107
+ raise DependencyError("expected a nonempty identifier without surrounding whitespace")
108
+ return value
109
+
110
+
111
+ def _path(value: object) -> Path:
112
+ if not isinstance(value, str) or not Path(value).is_absolute():
113
+ raise DependencyError(f"expected absolute path, got {value!r}")
114
+ return Path(value)
115
+
116
+
117
+ def _list(value: object) -> list:
118
+ if not isinstance(value, list):
119
+ raise DependencyError("expected an array in dependency snapshot")
120
+ return value
121
+
122
+
123
+ def _unique(value: str, seen: set[str]) -> None:
124
+ if value in seen:
125
+ raise DependencyError(f"duplicate dependency identity {value!r}")
126
+ seen.add(value)
127
+
128
+
129
+ class Dependencies:
130
+ """Validated dependency snapshot, including preserved unknown workload kinds."""
131
+
132
+ def __init__(self, snapshot: object):
133
+ self._raw = copy.deepcopy(snapshot)
134
+ recordings, programs, batches = [], [], []
135
+ try:
136
+ phases_seen = set()
137
+ for phase in _list(self._raw):
138
+ phase_name = _name(phase["phase"])
139
+ _unique(phase_name, phases_seen)
140
+ tasks_seen = set()
141
+ for task in _list(phase["tasks"]):
142
+ identity = _name(task["identity"])
143
+ _unique(identity, tasks_seen)
144
+ directory = _path(task["output_directory"])
145
+ workload = task["workload"]
146
+ kind = _name(workload["kind"])
147
+ if kind == "execution_unit":
148
+ key = _name(workload["execution_unit"])
149
+ members = _list(workload["members"])
150
+ if not members:
151
+ raise DependencyError("execution unit has no members")
152
+ members_seen = set()
153
+ for member in members:
154
+ name = _name(member["identity"])
155
+ _unique(name, members_seen)
156
+ iteration = member["final_iteration"]
157
+ if type(iteration) is not int or not 0 <= iteration <= 2**64 - 1:
158
+ raise DependencyError("final_iteration must be a u64")
159
+ recordings.append(RecordingDependency(phase_name, identity, key, name, iteration, _path(member["output_directory"])))
160
+ elif kind in ("program", "python"):
161
+ script = workload.get("python_script")
162
+ if kind == "python" and script is None:
163
+ raise DependencyError("python workload requires python_script")
164
+ programs.append(ProgramDependency(phase_name, identity, directory / "artifacts", _path(workload["executable"]), _path(script) if script is not None else None))
165
+ elif kind == "npy":
166
+ batches.append(NpyDependency(phase_name, identity, _path(workload["processed_directory"])))
167
+ except (KeyError, TypeError, AttributeError) as error:
168
+ raise DependencyError(f"malformed dependency snapshot: {error}") from error
169
+ self._recordings, self._programs, self._batches = tuple(recordings), tuple(programs), tuple(batches)
170
+
171
+ @classmethod
172
+ def load(cls, path: str | Path) -> Dependencies:
173
+ """Load an explicit snapshot; failures identify its expected path."""
174
+ try:
175
+ return cls(json.loads(Path(path).read_text(encoding="utf-8")))
176
+ except (OSError, ValueError) as error:
177
+ raise DependencyError(f"cannot load dependency snapshot {path}: {error}") from error
178
+
179
+ @classmethod
180
+ def from_env(cls) -> Dependencies:
181
+ """Load WORKFLOW_DEPENDENCIES_PATH from a standard Workflow launch."""
182
+ path = os.environ.get("WORKFLOW_DEPENDENCIES_PATH")
183
+ if not path:
184
+ raise DependencyError("missing WORKFLOW_DEPENDENCIES_PATH; run through Workflow's standard study layout or use Dependencies.load(path)")
185
+ return cls.load(path)
186
+
187
+ def raw_json(self) -> object:
188
+ """Return an independent JSON copy, including unknown extensions."""
189
+ return copy.deepcopy(self._raw)
190
+
191
+ def recordings(self) -> Selection[RecordingDependency]:
192
+ return Selection(self._recordings)
193
+
194
+ def programs(self) -> Selection[ProgramDependency]:
195
+ return Selection(self._programs)
196
+
197
+ def npy_batches(self) -> Selection[NpyDependency]:
198
+ return Selection(self._batches)
@@ -0,0 +1,29 @@
1
+ """Public exception hierarchy for recording validation and reconstruction."""
2
+
3
+
4
+ class RecordingError(Exception):
5
+ """Base class for every reader failure."""
6
+
7
+
8
+ class MetadataError(RecordingError):
9
+ """The authoritative metadata document violates the supported format."""
10
+
11
+
12
+ class RecordingNotCompleteError(MetadataError):
13
+ """The recording has not reached successful completion."""
14
+
15
+
16
+ class UnknownStreamError(RecordingError, KeyError):
17
+ """The requested logical stream is not declared."""
18
+
19
+
20
+ class IntegrityError(RecordingError):
21
+ """A declared immutable chunk is missing or fails integrity validation."""
22
+
23
+
24
+ class RecordError(RecordingError):
25
+ """A JSONL state record violates its stream contract."""
26
+
27
+
28
+ class DecoderError(RecordingError):
29
+ """A caller-supplied field decoder failed or is missing."""