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,266 @@
1
+ """Xschem netlist-to-sim deck: one call from schematic to verdict.
2
+
3
+ The solver adapter (`src/adapters/xschem/__init__.py`) netlists one
4
+ schematic per call. This deck chains the two deck phases behind one
5
+ call: netlist the schematic, refuse an empty (device-free) netlist
6
+ loudly instead of fake-greening it, and optionally run the
7
+ schematic-owned sim (`-S`) whose failures combine under the same
8
+ verdict. Engine vocabulary/composition wiring belongs to the owning
9
+ surface; this file only orchestrates the solver.
10
+ """
11
+
12
+ from pathlib import Path
13
+
14
+ from ...interfaces import ToolAdapter
15
+ from ..errors import AdapterError
16
+ from . import XschemAdapter
17
+
18
+ OPERATION = "run_xschem"
19
+
20
+
21
+ def deck_elements(text):
22
+ """Device lines of one netlist, without comments or directives.
23
+
24
+ Args:
25
+ text: Raw (or normalized) netlist text.
26
+
27
+ Returns:
28
+ Device lines such as `V1 ...` or `X...`, in file order.
29
+ Comment lines (`*...`), dot directives (`.end`,
30
+ `.GLOBAL`), and blanks never count as devices.
31
+ """
32
+ lines = []
33
+ for line in (text or "").splitlines():
34
+ stripped = line.strip()
35
+ if not stripped:
36
+ continue
37
+ if stripped.startswith(("*", ".")):
38
+ continue
39
+ lines.append(line)
40
+ return lines
41
+
42
+
43
+ class XschemDeck(ToolAdapter):
44
+ """run_xschem as a deck: netlist, guard empty, optionally sim.
45
+
46
+ Serves the run_xschem operation with tool `xschem` in deck
47
+ modes (`netlist`, `sim`, `deck`). Phase one netlists the
48
+ schematic; a netlist with zero device lines fails loudly as
49
+ `empty_netlist` (an empty schematic or all symbols missing
50
+ never parses as clean). Phase two, only when the job sets
51
+ `sim`, reruns with the schematic-owned sim (`-S`) and reports
52
+ its failures under `sim`.
53
+
54
+ Job keys:
55
+ sch_file (required): Schematic file under netlist.
56
+ rcfile: Project rcfile path (library paths may derive
57
+ from the adapter-pinned XSCHEM_SHAREDIR).
58
+ output_name: Netlist file name (default `<stem>.spice`).
59
+ flat: Flat netlist (`-f`) when true.
60
+ sim: Run the schematic-owned sim (`-S`) when true.
61
+ workdir: Run directory (default ".").
62
+
63
+ Args:
64
+ runner: Optional isolated runner; direct subprocess when None.
65
+ xschem_bin: Explicit xschem binary; PATH when None.
66
+
67
+ Caution: The deck reruns the netlister for the sim phase, so
68
+ a schematic whose outputs differ run to run decks
69
+ non-deterministically — byte-compare only the netlist
70
+ phase, never the sim phase.
71
+ """
72
+
73
+ def __init__(self, runner=None, xschem_bin=None):
74
+ self._runner = runner
75
+ self._xschem_bin = xschem_bin
76
+
77
+ def capabilities(self, target):
78
+ """Report the served operation, tool, and deck modes.
79
+
80
+ Args:
81
+ target: Target the capabilities are reported for
82
+ (accepted, unused — one schematic per job).
83
+
84
+ Returns:
85
+ Capability mapping with operations, tool, and modes.
86
+ """
87
+ return {
88
+ "operations": [OPERATION],
89
+ "tool": "xschem",
90
+ "modes": ["netlist", "sim", "deck"],
91
+ }
92
+
93
+ def _adapter(self):
94
+ """One solver adapter sharing this deck's runner and binary."""
95
+ return XschemAdapter(
96
+ runner=self._runner, xschem_bin=self._xschem_bin)
97
+
98
+ def netlist(self, job):
99
+ """Netlist one schematic and parse it to a deck verdict.
100
+
101
+ Args:
102
+ job: Solver job with operation run_xschem plus
103
+ sch_file and deck inputs (any `sim` flag is
104
+ ignored: this phase never simulates).
105
+
106
+ Returns:
107
+ Mapping with netlist_file, normalized_text, elements
108
+ (device lines), total, and violations.
109
+
110
+ Raises:
111
+ AdapterError: invalid_reference for a missing
112
+ schematic/rcfile, missing_dependency without
113
+ xschem V3, runner_unavailable with no netlist
114
+ output, plus the solver preflight codes.
115
+ """
116
+ adapter = self._adapter()
117
+ native = adapter.execute(dict(job, sim=False))
118
+ parsed = adapter.parse(native)
119
+ elements = deck_elements(native.get("normalized_text"))
120
+ return {
121
+ "netlist_file": native.get("netlist_file"),
122
+ "normalized_text": native.get("normalized_text"),
123
+ "elements": elements,
124
+ "total": parsed.get("total"),
125
+ "violations": list(parsed.get("violations") or []),
126
+ }
127
+
128
+ def deck(self, job):
129
+ """Deck one schematic from netlist to an optional sim verdict.
130
+
131
+ Args:
132
+ job: Solver job with operation run_xschem plus
133
+ sch_file and deck inputs; `sim` true adds the
134
+ schematic-owned sim phase.
135
+
136
+ Returns:
137
+ Netlist verdict (netlist_file, normalized_text,
138
+ elements, total, violations) plus `sim`: None
139
+ without the sim flag, else the sim failures as
140
+ {total, violations, log_file}. A device-free
141
+ netlist appends an `empty_netlist` error and
142
+ skips the sim phase.
143
+
144
+ Raises:
145
+ AdapterError: invalid_reference for a missing
146
+ schematic, missing_dependency without xschem
147
+ V3, runner_unavailable with no netlist output,
148
+ plus the solver preflight codes (netlist first,
149
+ sim reruns the same checks).
150
+ """
151
+ verdict = self.netlist(job)
152
+ if not verdict["elements"]:
153
+ verdict["violations"] = verdict["violations"] + [{
154
+ "rule": "empty_netlist",
155
+ "severity": "error",
156
+ "message": ("xschem deck netlist has no device lines: "
157
+ "empty schematic or all symbols missing."),
158
+ }]
159
+ verdict["total"] = len(verdict["violations"])
160
+ verdict["sim"] = None
161
+ return verdict
162
+ if not job.get("sim"):
163
+ verdict["sim"] = None
164
+ return verdict
165
+ adapter = self._adapter()
166
+ sim_native = adapter.execute(dict(job, sim=True))
167
+ sim_parsed = adapter.parse(sim_native)
168
+ sim_fails = [v for v in (sim_parsed.get("violations") or [])
169
+ if v.get("rule") == "xschem_sim_fail"]
170
+ verdict["sim"] = {
171
+ "total": len(sim_fails),
172
+ "violations": sim_fails,
173
+ "log_file": sim_native.get("log_file"),
174
+ }
175
+ return verdict
176
+
177
+ def preflight(self, job):
178
+ """Check the deck job through the solver preflight.
179
+
180
+ Args:
181
+ job: Solver job with operation run_xschem plus
182
+ sch_file and deck inputs.
183
+
184
+ Returns:
185
+ Checked mapping from the solver preflight.
186
+
187
+ Raises:
188
+ AdapterError: unsupported_operation, invalid_reference,
189
+ invalid_command, or missing_dependency, exactly as
190
+ the solver reports them.
191
+ """
192
+ return self._adapter().preflight(job)
193
+
194
+ def execute(self, job):
195
+ """Deck one schematic: netlist natively, sim natively on flag.
196
+
197
+ Args:
198
+ job: Solver job with operation run_xschem plus
199
+ sch_file and deck inputs.
200
+
201
+ Returns:
202
+ Native output with the netlist phase and, on the sim
203
+ flag with devices present, the sim phase.
204
+
205
+ Raises:
206
+ AdapterError: invalid_reference, missing_dependency,
207
+ or runner_unavailable, exactly as the solver
208
+ reports them.
209
+ """
210
+ net_native = self._adapter().execute(dict(job, sim=False))
211
+ phases = {"netlist": net_native, "sim": None}
212
+ if job.get("sim") and deck_elements(
213
+ net_native.get("normalized_text")):
214
+ phases["sim"] = self._adapter().execute(dict(job, sim=True))
215
+ return phases
216
+
217
+ def parse(self, native_output):
218
+ """Parse deck natives to the combined deck verdict.
219
+
220
+ Args:
221
+ native_output: Native output with netlist (and
222
+ optional sim) phases from execute.
223
+
224
+ Returns:
225
+ Deck verdict with elements, violations (including
226
+ the empty_netlist guard), and the sim sub-verdict.
227
+
228
+ Raises:
229
+ AdapterError: runner_unavailable when the netlist
230
+ phase is missing.
231
+ """
232
+ adapter = self._adapter()
233
+ net_native = (native_output or {}).get("netlist")
234
+ if net_native is None:
235
+ raise AdapterError(
236
+ "runner_unavailable", "xschem deck has no netlist phase.")
237
+ parsed = adapter.parse(net_native)
238
+ elements = deck_elements(net_native.get("normalized_text"))
239
+ violations = list(parsed.get("violations") or [])
240
+ if not elements:
241
+ violations = violations + [{
242
+ "rule": "empty_netlist",
243
+ "severity": "error",
244
+ "message": ("xschem deck netlist has no device lines: "
245
+ "empty schematic or all symbols missing."),
246
+ }]
247
+ sim_native = (native_output or {}).get("sim")
248
+ sim = None
249
+ if sim_native is not None:
250
+ sim_parsed = adapter.parse(sim_native)
251
+ sim_fails = [v for v in (sim_parsed.get("violations") or [])
252
+ if v.get("rule") == "xschem_sim_fail"]
253
+ sim = {
254
+ "total": len(sim_fails),
255
+ "violations": sim_fails,
256
+ "log_file": sim_native.get("log_file"),
257
+ }
258
+ return {
259
+ "netlist_file": net_native.get("netlist_file"),
260
+ "normalized_text": net_native.get("normalized_text"),
261
+ "elements": elements,
262
+ "total": (len(violations) if not elements
263
+ else parsed.get("total")),
264
+ "violations": violations,
265
+ "sim": sim,
266
+ }
@@ -0,0 +1,5 @@
1
+ """Migrated v1 clients over one engine's integrated runtime."""
2
+
3
+ from .runtime import RuntimeClient
4
+
5
+ __all__ = ["RuntimeClient"]
@@ -0,0 +1,255 @@
1
+ """Shared client over one engine's integrated runtime (CLIENTS).
2
+
3
+ Every transport (MCP, HTTP, IPC, stdio console, Python) serves the
4
+ same EngineRuntime through this one client instead of inventing
5
+ its own queue. All run control delegates to the engine's public
6
+ deferred surface — the client never constructs a second runtime,
7
+ never reimplements admission, and never synthesizes verdicts.
8
+
9
+ Caution: The push-listener handle (`add_listener`) stays behind
10
+ the runtime object the servers bind explicitly; the engine
11
+ exposes no public listener accessor yet (see the API-V1-CLIENTS
12
+ verdict carry). Watch polling and cursor recovery ride the
13
+ public `runtime_events` surface instead.
14
+ """
15
+
16
+ from ..project.errors import ProjectError
17
+
18
+
19
+ class RuntimeClient:
20
+ """One engine's deferred runs, served to every client alike.
21
+
22
+ Args:
23
+ engine: Engine whose integrated runtime this client
24
+ serves (submit, deferred submit/pump/wait/fetch,
25
+ cancel, watch, survivors).
26
+ """
27
+
28
+ def __init__(self, engine):
29
+ self._engine = engine
30
+ self._paused = False
31
+
32
+ @property
33
+ def engine(self):
34
+ """Engine this client serves.
35
+
36
+ Returns:
37
+ The bound engine.
38
+ """
39
+ return self._engine
40
+
41
+ @property
42
+ def paused(self):
43
+ """Client-side capacity signal from the last refusal.
44
+
45
+ True after a deferred admission refused with admitted
46
+ False (retryable saturation); False again once a pump,
47
+ cancel, or admitted submit frees the lane.
48
+
49
+ Returns:
50
+ Whether the client believes admission is paused.
51
+ """
52
+ return self._paused
53
+
54
+ def submit(self, project_id, operation, arguments, request_id=None):
55
+ """Submit one call inline through the engine.
56
+
57
+ Args:
58
+ project_id: Project the call runs against.
59
+ operation: Cataloged operation or query name.
60
+ arguments: Operation arguments mapping.
61
+ request_id: Client envelope id; minted when omitted.
62
+
63
+ Returns:
64
+ Engine result for the call (ack, completion, or data).
65
+ """
66
+ command = {
67
+ "project_id": project_id,
68
+ "operation": operation,
69
+ "arguments": dict(arguments),
70
+ }
71
+ if request_id is not None:
72
+ command["request_id"] = request_id
73
+ return self._engine.submit(command)
74
+
75
+ def deferred_submit(self, operation, arguments, request_id=None):
76
+ """Admit one deferred run with a prompt acknowledgement.
77
+
78
+ Args:
79
+ operation: Cataloged operation name.
80
+ arguments: Operation arguments mapping.
81
+ request_id: Client envelope id; minted when omitted.
82
+
83
+ Returns:
84
+ Deferred handle watching the admitted run; the
85
+ receipt carries no verdict. A refused handle carries
86
+ admitted False and marks this client paused.
87
+ """
88
+ handle = self._engine.deferred_submit(
89
+ operation, dict(arguments or {}), request_id=request_id)
90
+ receipt = handle.receipt
91
+ if receipt.get("admitted") is False:
92
+ self._paused = True
93
+ else:
94
+ self._paused = False
95
+ return handle
96
+
97
+ def pump(self, limit=None):
98
+ """Dispatch queued deferred runs oldest first.
99
+
100
+ Args:
101
+ limit: Maximum entries to drain; the whole backlog
102
+ when omitted.
103
+
104
+ Returns:
105
+ Completions in dispatch order. A drained lane clears
106
+ this client's paused signal.
107
+ """
108
+ completions = self._engine.deferred_pump(limit=limit)
109
+ if completions:
110
+ self._paused = False
111
+ return completions
112
+
113
+ def wait_for(self, run_id, timeout=None):
114
+ """Block until one deferred run reaches a terminal state.
115
+
116
+ Args:
117
+ run_id: Deferred run id to wait for.
118
+ timeout: Maximum seconds to wait; waits with the run
119
+ when omitted.
120
+
121
+ Returns:
122
+ Terminal completion for the run.
123
+ """
124
+ return self._engine.deferred_wait(run_id, timeout=timeout)
125
+
126
+ def status(self, run_id):
127
+ """Read one deferred run's state, verdict, and operation.
128
+
129
+ Args:
130
+ run_id: Deferred run id to inspect.
131
+
132
+ Returns:
133
+ Status with run_id, operation, state, and verdict
134
+ once judged.
135
+ """
136
+ return self._engine.deferred_status(run_id)
137
+
138
+ def fetch(self, run_id):
139
+ """Fetch one deferred run's merged ack-plus-completion.
140
+
141
+ Args:
142
+ run_id: Deferred run id to fetch.
143
+
144
+ Returns:
145
+ Merged receipt-plus-completion; verdict present once
146
+ the run finished.
147
+ """
148
+ return self._engine.deferred_fetch(run_id)
149
+
150
+ def cancel(self, run_id, actor=None):
151
+ """Cancel one queued or running deferred run.
152
+
153
+ Args:
154
+ run_id: Deferred run id to cancel.
155
+ actor: Attribution for the lifecycle event; the
156
+ engine actor when omitted.
157
+
158
+ Returns:
159
+ Outcome with run_id, state, actor, and verdict when
160
+ judged. A freed lane clears this client's paused
161
+ signal.
162
+ """
163
+ outcome = self._engine.runtime_cancel(run_id, actor=actor)
164
+ self._paused = False
165
+ return outcome
166
+
167
+ def watch(self, cursor=0):
168
+ """Read ordered lifecycle events after a cursor.
169
+
170
+ A cursor the retained log no longer covers recovers
171
+ loudly: the page replays from the floor and reports
172
+ resynced True with the current revision instead of
173
+ failing the subscriber.
174
+
175
+ Args:
176
+ cursor: Opaque position from a previous read; 0
177
+ replays the retained log.
178
+
179
+ Returns:
180
+ Page with events oldest first, the current cursor,
181
+ and resynced True only after a recovery replay.
182
+
183
+ Raises:
184
+ ProjectError: any runtime refusal other than
185
+ cursor_expired — expired cursors recover,
186
+ never raise.
187
+ """
188
+ try:
189
+ page = self._engine.runtime_events(cursor)
190
+ except ProjectError as err:
191
+ if err.code != "cursor_expired":
192
+ raise
193
+ snapshot = self._engine.snapshot()
194
+ page = self._engine.runtime_events(0)
195
+ return {
196
+ "events": page["events"],
197
+ "cursor": page["cursor"],
198
+ "resynced": True,
199
+ "current_revision": snapshot["current_revision"],
200
+ }
201
+ return {
202
+ "events": page["events"],
203
+ "cursor": page["cursor"],
204
+ "resynced": False,
205
+ }
206
+
207
+ def survivors(self):
208
+ """Report unfinished deferred runs without executing any.
209
+
210
+ Returns:
211
+ Boot report with survivors, interrupted, and
212
+ quarantined run ids.
213
+ """
214
+ return self._engine.boot_report()
215
+
216
+ def resume(self, run_id, force=False):
217
+ """Resume one crash-surviving deferred run.
218
+
219
+ Args:
220
+ run_id: Survivor to execute now.
221
+ force: Required to resume a quarantined run.
222
+
223
+ Returns:
224
+ Resume outcome for the run.
225
+ """
226
+ return self._engine.resume_survivor(run_id, force=force)
227
+
228
+ def discard(self, run_id):
229
+ """Discard one crash-surviving deferred run.
230
+
231
+ Args:
232
+ run_id: Survivor to discard.
233
+
234
+ Returns:
235
+ Discard record for the run.
236
+ """
237
+ return self._engine.discard_survivor(run_id)
238
+
239
+ def resume_all(self):
240
+ """Resume every retryable crash survivor.
241
+
242
+ Returns:
243
+ Gated batch with resumed run ids, their
244
+ completions, and skipped entries.
245
+ """
246
+ return self._engine.resume_all_retryable()
247
+
248
+ def replay_pending(self):
249
+ """Resume crash-surviving RETRYABLE admissions, oldest first.
250
+
251
+ Returns:
252
+ Report with replayed run ids, their completions, and
253
+ skipped entries.
254
+ """
255
+ return self._engine.replay_pending()