rocket-engine 0.0.2b1__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.
Files changed (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,279 @@
1
+ """In-memory execution service (slice 2): dispatch, evidence, evaluation.
2
+
3
+ dispatch/cancel manipulate run state; evaluate() judges findings and
4
+ record_not_run() pins honest NOT_RUN verdicts with reasons. The first
5
+ real worker path runs through Engine.run(); isolated workers, the live
6
+ console env, and persistence arrive in later slices.
7
+ """
8
+
9
+ import uuid
10
+
11
+ from ruamel.yaml import YAML
12
+
13
+ from .errors import ExecutionError
14
+
15
+ _SEVERITY_RANK = {"error": 0, "warning": 1}
16
+
17
+
18
+ class ExecutionService:
19
+ """In-memory run dispatch, evidence, and verdict evaluation.
20
+
21
+ Args:
22
+ project_id: Project this service tracks runs for.
23
+ """
24
+
25
+ def __init__(self, project_id):
26
+ self._project_id = project_id
27
+ self._runs = {}
28
+ self._source_changed = False
29
+ self._yaml = YAML(typ="safe")
30
+
31
+ def _require(self, run_id):
32
+ try:
33
+ return self._runs[run_id]
34
+ except KeyError:
35
+ raise ExecutionError("unknown_run", f"Unknown run: {run_id}.") from None
36
+
37
+ def dispatch(self, operation, target, snapshot_id, run_id=None):
38
+ """Queue one run and return its id.
39
+
40
+ Args:
41
+ operation: Operation the run executes.
42
+ target: Run target descriptor.
43
+ snapshot_id: Evidence snapshot the run pins.
44
+ run_id: Optional caller-supplied id so the queued ack and
45
+ its result share one id; generated when omitted.
46
+
47
+ Returns:
48
+ Admission with run_id, queued state, and snapshot_id.
49
+ """
50
+ run_id = run_id or uuid.uuid4().hex
51
+ self._runs[run_id] = {
52
+ "run_id": run_id,
53
+ "operation": operation,
54
+ "target": target,
55
+ "snapshot_id": snapshot_id,
56
+ "state": "queued",
57
+ "verdict": None,
58
+ "findings": [],
59
+ }
60
+ return {"run_id": run_id, "state": "queued", "snapshot_id": snapshot_id}
61
+
62
+ def known(self, run_id):
63
+ """Check whether a run id is registered.
64
+
65
+ Args:
66
+ run_id: Run id to look up.
67
+
68
+ Returns:
69
+ True when the id has a run record.
70
+ """
71
+ return run_id in self._runs
72
+
73
+ def status(self, run_id):
74
+ """Read one run's state, verdict, and evidence links.
75
+
76
+ Args:
77
+ run_id: Run to inspect.
78
+
79
+ Returns:
80
+ Run state mapping with run_id, operation, state,
81
+ snapshot_id, verdict, and the recorded native exit
82
+ code (None until the run is judged).
83
+
84
+ Raises:
85
+ ExecutionError: unknown_run for an untracked id.
86
+ """
87
+ record = self._require(run_id)
88
+ return {
89
+ key: record.get(key)
90
+ for key in ("run_id", "operation", "state", "snapshot_id",
91
+ "verdict", "exit_code")
92
+ }
93
+
94
+ def mark_running(self, run_id):
95
+ """Mark a queued run running.
96
+
97
+ Args:
98
+ run_id: Run to mark.
99
+
100
+ Returns:
101
+ Mapping with run_id and running state.
102
+
103
+ Raises:
104
+ ExecutionError: unknown_run for an untracked id.
105
+ """
106
+ record = self._require(run_id)
107
+ record["state"] = "running"
108
+ return {"run_id": run_id, "state": "running"}
109
+
110
+ def mark_completed(self, run_id, verdict):
111
+ """Mark a run completed with its verdict.
112
+
113
+ Args:
114
+ run_id: Run to mark.
115
+ verdict: Verdict mapping stored on the run.
116
+
117
+ Returns:
118
+ Mapping with run_id, completed state, and verdict.
119
+
120
+ Raises:
121
+ ExecutionError: unknown_run for an untracked id.
122
+ """
123
+ record = self._require(run_id)
124
+ record["state"] = "completed"
125
+ record["verdict"] = verdict
126
+ return {"run_id": run_id, "state": "completed", "verdict": verdict}
127
+
128
+ def cancel(self, run_id):
129
+ """Cancel a queued or running run; finished runs keep state.
130
+
131
+ Cancelling a finished run truthfully returns its terminal
132
+ state, never a fake cancelled.
133
+
134
+ Args:
135
+ run_id: Run to cancel.
136
+
137
+ Returns:
138
+ Mapping with run_id, state, and verdict when judged.
139
+
140
+ Raises:
141
+ ExecutionError: unknown_run for an untracked id.
142
+ """
143
+ record = self._require(run_id)
144
+ if record["state"] in ("queued", "running"):
145
+ # Real tree termination arrives with real workers.
146
+ record["state"] = "cancelled"
147
+ out = {"run_id": run_id, "state": record["state"]}
148
+ if record["verdict"] is not None:
149
+ out["verdict"] = record["verdict"]
150
+ return out
151
+
152
+ def note_source_change(self, revision):
153
+ """Flag evidence stale after a source edit.
154
+
155
+ Args:
156
+ revision: Revision the sources moved to.
157
+ """
158
+ self._source_changed = True
159
+ self._source_revision = revision
160
+
161
+ def evidence(self, run_id):
162
+ """Read a run's evidence pin plus freshness.
163
+
164
+ Args:
165
+ run_id: Run to read.
166
+
167
+ Returns:
168
+ Evidence with run_id, snapshot_id, and freshness
169
+ (current or stale).
170
+
171
+ Raises:
172
+ ExecutionError: unknown_run for an untracked id.
173
+ """
174
+ record = self._require(run_id)
175
+ return {
176
+ "run_id": run_id,
177
+ "snapshot_id": record["snapshot_id"],
178
+ "freshness": "stale" if self._source_changed else "current",
179
+ }
180
+
181
+ def _store_verdict(self, run_id, verdict, findings=(), exit_code=None):
182
+ record = self._runs.setdefault(
183
+ run_id,
184
+ {"run_id": run_id, "state": "completed", "verdict": None, "findings": []},
185
+ )
186
+ record["state"] = "completed"
187
+ record["verdict"] = verdict
188
+ record["findings"] = list(findings)
189
+ if exit_code is not None:
190
+ record["exit_code"] = exit_code
191
+ return verdict
192
+
193
+ def evaluate(self, run_id, exit_code, findings_fixture):
194
+ """Judge findings: FAIL on any error, else PASS.
195
+
196
+ Unparsable output judges NOT_RUN with reason rather than
197
+ guessing. The native exit code is recorded on the run
198
+ (surfaced by status), never in the verdict: verdicts stay
199
+ exit-independent so a replay reproduces them byte-identically
200
+ even when native output bytes differ.
201
+
202
+ Args:
203
+ run_id: Run to judge.
204
+ exit_code: Native exit code, recorded on the run only.
205
+ findings_fixture: YAML text with a findings list.
206
+
207
+ Returns:
208
+ Verdict with status and finding counts (no exit code).
209
+
210
+ Raises:
211
+ ValueError: No findings list (raised and caught inside
212
+ evaluate into a NOT_RUN verdict — never escapes).
213
+ """
214
+ try:
215
+ data = self._yaml.load(findings_fixture)
216
+ if not isinstance(data, dict) or not isinstance(data.get("findings"), list):
217
+ raise ValueError("no findings list")
218
+ findings = data["findings"]
219
+ except Exception:
220
+ return self._store_verdict(
221
+ run_id, {"status": "NOT_RUN", "reason": "unparsable output"},
222
+ exit_code=exit_code,
223
+ )
224
+ counts = {}
225
+ for finding in findings:
226
+ severity = finding.get("severity", "unknown")
227
+ counts[severity] = counts.get(severity, 0) + 1
228
+ status = "FAIL" if counts.get("error", 0) else "PASS"
229
+ return self._store_verdict(
230
+ run_id, {"status": status, "finding_counts": counts}, findings,
231
+ exit_code=exit_code,
232
+ )
233
+
234
+ def record_not_run(self, run_id, reason):
235
+ """Pin an honest NOT_RUN verdict with its reason.
236
+
237
+ Args:
238
+ run_id: Run to mark not run.
239
+ reason: Why no verdict exists.
240
+
241
+ Returns:
242
+ NOT_RUN verdict with the reason.
243
+ """
244
+ return self._store_verdict(run_id, {"status": "NOT_RUN", "reason": reason})
245
+
246
+ def findings(self, run_id, limit=50, cursor=None):
247
+ """Read one page of findings, errors first.
248
+
249
+ Args:
250
+ run_id: Run to read.
251
+ limit: Maximum findings in this page.
252
+ cursor: Opaque position from a previous page, if any.
253
+
254
+ Returns:
255
+ Finding page with total counts, findings, next cursor,
256
+ and truncation flag.
257
+
258
+ Raises:
259
+ ExecutionError: unknown_run for an untracked id.
260
+ """
261
+ record = self._require(run_id)
262
+ ordered = sorted(
263
+ record.get("findings", []),
264
+ key=lambda f: _SEVERITY_RANK.get(f.get("severity"), 2),
265
+ )
266
+ counts = {}
267
+ for finding in ordered:
268
+ severity = finding.get("severity", "unknown")
269
+ counts[severity] = counts.get(severity, 0) + 1
270
+ start = int(cursor) if cursor else 0
271
+ page = ordered[start : start + limit]
272
+ remaining = len(ordered) - (start + len(page))
273
+ return {
274
+ "total_counts": counts,
275
+ "findings": page,
276
+ "next_cursor": str(start + len(page)) if remaining > 0 else None,
277
+ "truncated": remaining > 0,
278
+ }
279
+