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
+ """Wave-3 deferred runtime seam (EngineRuntime integration).
2
+
3
+ Engine owns one EngineRuntime bound to the solver lane: deferred
4
+ submits acknowledge promptly with a run id and no verdict, the
5
+ pump executes through the ledger-admitting runner, and the
6
+ terminal completion merges back with merge_ack_completion. The
7
+ synchronous submit path is untouched — deferred runs correlate
8
+ with ledger admissions through the shared request id.
9
+ """
10
+
11
+ from ..execution.apig.submit import merge_ack_completion
12
+ from ..interfaces.yaml_dump import dump_yaml_text
13
+ from ..project.errors import ProjectError
14
+
15
+
16
+ class _DeferredMixin:
17
+ """Deferred prompt-ack runtime surface (Wave-3 EngineRuntime)."""
18
+
19
+ def _deferred_runner(
20
+ self, operation, arguments, snapshot_id, request_id,
21
+ run_id):
22
+ """Execute one deferred run through ledger admission.
23
+
24
+ Admission gates exactly like a synchronous submit
25
+ (targets, request knobs, confinement, budget, verdict
26
+ gates, block scope); the ledger ack run id executes while
27
+ the runtime tracks its own run id. Replay hits return the
28
+ stored completion without re-executing.
29
+
30
+ Args:
31
+ operation: Cataloged solver operation name.
32
+ arguments: Operation arguments mapping.
33
+ snapshot_id: Evidence snapshot the run pins.
34
+ request_id: Client envelope id.
35
+ run_id: Runtime run id tracking this run.
36
+
37
+ Returns:
38
+ Terminal completion for the run.
39
+ """
40
+ from .gates import check_submit_gates
41
+
42
+ args = dict(arguments or {})
43
+ args = self._apply_targets(operation, args)
44
+ args = self._apply_run_request(args)
45
+ if operation == "run_spice":
46
+ args = self.confine_spice_arguments(
47
+ args, self._project_dir)
48
+ from .runs import refuse_smuggled_budget
49
+
50
+ refuse_smuggled_budget(args)
51
+ check_submit_gates(operation, args, self._project)
52
+ scope_request = self._admit_block_scope(operation, args)
53
+ command_text = dump_yaml_text(
54
+ {"operation": operation, "arguments": args})
55
+ ack = self._pool.submit_request(
56
+ command_text, str(request_id),
57
+ expected_revision=args.get("expected_revision"),
58
+ )
59
+ self._deferred.apply_edit()
60
+ ledger_id = ack["run_id"]
61
+ stored = self._stored_completion(ledger_id)
62
+ if stored is not None:
63
+ self._pool.discard(ledger_id)
64
+ return {
65
+ "run_id": ledger_id, "state": stored["state"],
66
+ "verdict": stored.get("verdict"),
67
+ "operation": stored.get("operation"),
68
+ }
69
+ self._run_jobs.setdefault(
70
+ ledger_id,
71
+ {
72
+ "operation": operation,
73
+ "arguments": args,
74
+ "snapshot_id": ack["snapshot_id"],
75
+ "request_id": str(request_id),
76
+ },
77
+ )
78
+ if scope_request is not None:
79
+ from ..execution.block_runs_scope import (
80
+ build_scope_record,
81
+ )
82
+
83
+ pending = self._run_jobs[ledger_id]
84
+ if "block_scope" not in pending:
85
+ pending["block_scope"] = build_scope_record(
86
+ run_id=ledger_id,
87
+ revision=self._project.snapshot()[
88
+ "current_revision"],
89
+ block_id=scope_request["block_id"],
90
+ deck=args.get("deck"),
91
+ closure=scope_request["closure"],
92
+ )
93
+ if not self._execution.known(ledger_id):
94
+ self._execution.dispatch(
95
+ operation, {"cell": args.get("cell")},
96
+ ack["snapshot_id"], run_id=ledger_id,
97
+ )
98
+ try:
99
+ completion = self._pool_execute(
100
+ operation, args, ack["snapshot_id"],
101
+ str(request_id), ledger_id)
102
+ finally:
103
+ self._pool.discard(ledger_id)
104
+ self._maybe_record_evidence(operation, completion, args)
105
+ return completion
106
+
107
+ def deferred_submit(self, operation, arguments, request_id=None):
108
+ """Admit one deferred run with a prompt acknowledgement.
109
+
110
+ The receipt carries the run id with no verdict; pump (or
111
+ auto-dispatch) executes, then merge the terminal
112
+ completion back with merge_ack_completion.
113
+
114
+ Args:
115
+ operation: Cataloged solver operation name.
116
+ arguments: Operation arguments mapping.
117
+ request_id: Client envelope id; minted when omitted.
118
+
119
+ Returns:
120
+ DeferredHandle watching the admitted run.
121
+
122
+ Raises:
123
+ ProjectError: unsupported_operation for an unknown
124
+ name; the runtime refusal code otherwise.
125
+ """
126
+ from ..execution.errors import ExecutionError
127
+ from ..interfaces.vocabulary import OPERATIONS
128
+
129
+ if operation not in OPERATIONS:
130
+ raise ProjectError(
131
+ "unsupported_operation",
132
+ f"Unsupported operation: {operation}.",
133
+ )
134
+ import uuid as _uuid
135
+
136
+ try:
137
+ handle = self._deferred.submit(
138
+ operation, dict(arguments or {}),
139
+ request_id=str(request_id or _uuid.uuid4().hex),
140
+ )
141
+ except ExecutionError as exc:
142
+ raise ProjectError(exc.code, str(exc)) from exc
143
+ self._deferred_receipts[handle.run_id] = dict(
144
+ handle.receipt)
145
+ return handle
146
+
147
+ def deferred_pump(self, limit=None):
148
+ """Dispatch deferred runs oldest first through the runner.
149
+
150
+ Args:
151
+ limit: Maximum entries to drain; the whole backlog
152
+ when omitted.
153
+
154
+ Returns:
155
+ Completions in dispatch order.
156
+ """
157
+ return self._deferred.pump(limit=limit)
158
+
159
+ def deferred_status(self, run_id):
160
+ """Read one deferred run's state, verdict, and operation.
161
+
162
+ Args:
163
+ run_id: Deferred run id to inspect.
164
+
165
+ Returns:
166
+ Status with run_id, operation, state, and verdict
167
+ once judged.
168
+ """
169
+ from ..execution.errors import ExecutionError
170
+
171
+ try:
172
+ return self._deferred.status(run_id)
173
+ except ExecutionError as exc:
174
+ raise ProjectError(exc.code, str(exc)) from exc
175
+
176
+ def deferred_wait(self, run_id, timeout=None):
177
+ """Block until one deferred run reaches a terminal state.
178
+
179
+ Args:
180
+ run_id: Deferred run id to wait for.
181
+ timeout: Maximum seconds to wait; waits with the run
182
+ when omitted.
183
+
184
+ Returns:
185
+ Terminal completion for the run.
186
+ """
187
+ from ..execution.errors import ExecutionError
188
+
189
+ try:
190
+ return self._deferred.wait_for(
191
+ run_id, timeout=timeout)
192
+ except ExecutionError as exc:
193
+ raise ProjectError(exc.code, str(exc)) from exc
194
+
195
+ def runtime_events(self, cursor=0):
196
+ """Read ordered deferred lifecycle events after a cursor.
197
+
198
+ Args:
199
+ cursor: Opaque position from a previous read; 0
200
+ replays the retained log.
201
+
202
+ Returns:
203
+ Events appended after the cursor, oldest first,
204
+ plus the current cursor.
205
+ """
206
+ from ..execution.errors import ExecutionError
207
+
208
+ try:
209
+ return self._deferred.watch(cursor)
210
+ except ExecutionError as exc:
211
+ raise ProjectError(exc.code, str(exc)) from exc
212
+
213
+ def runtime_cancel(self, run_id, actor=None):
214
+ """Cancel one queued or running deferred run.
215
+
216
+ Args:
217
+ run_id: Deferred run id to cancel.
218
+ actor: Attribution for the lifecycle event.
219
+
220
+ Returns:
221
+ Outcome with run_id, state, actor, and verdict
222
+ when judged.
223
+ """
224
+ from ..execution.errors import ExecutionError
225
+
226
+ try:
227
+ return self._deferred.cancel(
228
+ run_id,
229
+ actor=actor if actor is not None else self._actor,
230
+ )
231
+ except ExecutionError as exc:
232
+ raise ProjectError(exc.code, str(exc)) from exc
233
+
234
+ def replay_pending(self):
235
+ """Resume crash-surviving RETRYABLE admissions, oldest first.
236
+
237
+ Explicit-call compat over the gated batch: same survivor
238
+ set as the boot report, but only RETRYABLE runs execute —
239
+ ambiguous and poison survivors are skipped loudly in the
240
+ report instead of re-running.
241
+
242
+ Returns:
243
+ Report with replayed run ids (the resumed set, seq
244
+ order), their completions, and skipped entries.
245
+ """
246
+ batch = self._replay.resume_all_retryable()
247
+ return {
248
+ "replayed": batch["resumed"],
249
+ "completions": batch["completions"],
250
+ "skipped": batch["skipped"],
251
+ }
252
+
253
+ def deferred_fetch(self, run_id):
254
+ """Fetch one deferred run's merged ack-plus-completion.
255
+
256
+ Args:
257
+ run_id: Deferred run id to fetch.
258
+
259
+ Returns:
260
+ Merged receipt-plus-completion; verdict present once
261
+ the run finished.
262
+
263
+ Raises:
264
+ ProjectError: unknown_run for an untracked id.
265
+ """
266
+ from ..execution.errors import ExecutionError
267
+
268
+ try:
269
+ receipt = self._deferred_receipts[run_id]
270
+ except (KeyError, AttributeError):
271
+ raise ProjectError(
272
+ "unknown_run", f"Unknown run: {run_id}.") from None
273
+ try:
274
+ completion = self._deferred.wait_for(run_id, timeout=0)
275
+ except TimeoutError:
276
+ return dict(receipt)
277
+ except ExecutionError as exc:
278
+ raise ProjectError(exc.code, str(exc)) from exc
279
+ return merge_ack_completion(receipt, completion)
@@ -0,0 +1,404 @@
1
+ """Wave-3 editor seam: shared admission helpers plus Engine dispatch.
2
+
3
+ Service-side endpoint methods stay thin: every Wave-3 mutation
4
+ rides the wire envelope through editor_mutation, every computed
5
+ read serves through editor_read. Receipts carry I28 closure pins
6
+ via pin_receipt. The Engine-side _EditorMixin (query dispatch)
7
+ lives here too, so both sides of the seam share one module.
8
+ """
9
+
10
+ from ..interfaces.errors import ContractError
11
+ from ..interfaces.refs import pin_closure
12
+ from ..project.errors import ProjectError
13
+
14
+
15
+ def editor_value_error(func):
16
+ """Run editor logic, mapping value errors to ProjectError.
17
+
18
+ Args:
19
+ func: Zero-argument callable running the domain op.
20
+
21
+ Returns:
22
+ Whatever the domain op returns.
23
+
24
+ Raises:
25
+ ProjectError: invalid_command (or the carried code).
26
+ """
27
+ try:
28
+ return func()
29
+ except ValueError as exc:
30
+ raise ProjectError(
31
+ getattr(exc, "code", "invalid_command"), str(exc)
32
+ ) from exc
33
+
34
+
35
+ def pin_receipt(service, receipt):
36
+ """Attach the I28 closure pin to one editor receipt.
37
+
38
+ Entity ids carried by the receipt pin their live revisions;
39
+ unresolvable names are skipped, never guessed. Prior partial
40
+ pins (template, profile) are preserved and folded in.
41
+
42
+ Args:
43
+ service: ProjectService handle to read through.
44
+ receipt: Editor receipt mapping (mutated in place).
45
+
46
+ Returns:
47
+ The same receipt with an i28 closure pin.
48
+ """
49
+ from ..project.edit import cas as _cas
50
+
51
+ revisions = {}
52
+ prior = {}
53
+ if isinstance(receipt, dict):
54
+ prior = receipt.get("i28") or {}
55
+ for key in (
56
+ "entity", "sheet", "instance", "binding",
57
+ "symbol", "cell", "ip", "testbench", "shape",
58
+ ):
59
+ candidate = receipt.get(key)
60
+ if not isinstance(candidate, str):
61
+ continue
62
+ try:
63
+ revisions[candidate] = _cas.read_entity_revision(
64
+ service, candidate)
65
+ except Exception: # noqa: BLE001 — names skip
66
+ continue
67
+ receipt["i28"] = pin_closure(
68
+ service.snapshot()["current_revision"], entities=revisions,
69
+ template=prior.get("template"),
70
+ profile=prior.get("profile"))
71
+ return receipt
72
+
73
+
74
+ def editor_mutation(service, operation, request_id, command_text,
75
+ expected_revision, run, describe):
76
+ """Admit one Wave-3 editor op through the wire envelope.
77
+
78
+ Inner envelopes derive from the outer request id, so a
79
+ retried envelope replays the verbs that already landed
80
+ instead of minting duplicates.
81
+
82
+ Args:
83
+ service: ProjectService handle to write through.
84
+ operation: Editor operation name for the event data.
85
+ request_id: Client envelope id for idempotent replay.
86
+ command_text: YAML command text for identity comparison.
87
+ expected_revision: Revision the writer read.
88
+ run: Callable (store, inner_id, inner_text, live)
89
+ returning the domain receipt.
90
+ describe: Callable (receipt) -> diff summary text.
91
+
92
+ Returns:
93
+ Mutation mapping with the pinned receipt, revision
94
+ transition, actor, events, cursor, and request id.
95
+
96
+ Raises:
97
+ ProjectError: request_conflict, revision_conflict,
98
+ or the domain refusal code.
99
+ """
100
+ def _run(store):
101
+ return pin_receipt(service, editor_value_error(
102
+ lambda: run(
103
+ store, f"{request_id}:{operation}",
104
+ f"{command_text}\n#inner {operation}",
105
+ service.snapshot()["current_revision"])))
106
+ return service._wire_mutation(
107
+ operation, request_id, command_text,
108
+ expected_revision, _run, describe)
109
+
110
+
111
+ def editor_read(service, operation, func):
112
+ """Serve one Wave-3 computed read with revision and I28.
113
+
114
+ Args:
115
+ service: ProjectService handle to read through.
116
+ operation: Read name for the entity-store seam tag.
117
+ func: Callable (store) returning the result mapping.
118
+
119
+ Returns:
120
+ Result mapping plus head revision and I28 pin.
121
+
122
+ Raises:
123
+ ProjectError: The domain refusal code.
124
+ """
125
+ store = service._entity_store(f"read:{operation}")
126
+ try:
127
+ result = func(store)
128
+ except ContractError as exc:
129
+ raise ProjectError(exc.code, str(exc)) from exc
130
+ return {
131
+ **result,
132
+ "current_revision": service.snapshot()["current_revision"],
133
+ "i28": pin_closure(service.snapshot()["current_revision"]),
134
+ }
135
+
136
+
137
+ #: Wave-3 computed reads served through ProjectService verbs.
138
+ _COMPUTED_READS = frozenset({
139
+ "read_entity_revision", "connection_graph", "net_occurrences",
140
+ "xprobe", "resolve_pin", "plot_export", "erc_check",
141
+ "write_netlist", "update_netlist", "check_capability",
142
+ "describe_testbench",
143
+ })
144
+
145
+
146
+ class _EditorMixin:
147
+ """Wave-3 editor/testbench query dispatch (Engine side)."""
148
+
149
+ def _query_editor(self, name, arguments):
150
+ """Serve one Wave-3 editor/testbench read query.
151
+
152
+ Generic entity reads ride the component seams; computed
153
+ reports delegate to the matching ProjectService verb.
154
+
155
+ Args:
156
+ name: Wave-3 read query name.
157
+ arguments: Query arguments mapping.
158
+
159
+ Returns:
160
+ Read mapping with head revision and I28 pin.
161
+
162
+ Raises:
163
+ ProjectError: invalid_command for missing arguments
164
+ or the domain refusal code.
165
+ """
166
+ project = self._project
167
+ if name == "read_entity_revision":
168
+ if "entity" not in arguments:
169
+ raise ProjectError(
170
+ "invalid_command",
171
+ "read_entity_revision needs an entity.",
172
+ )
173
+ return project.entity_revision(arguments["entity"])
174
+ if name in ("connection_graph", "net_occurrences"):
175
+ return project.graph_report(
176
+ sheet=arguments.get("sheet"),
177
+ net=arguments.get("net"),
178
+ net_pins=arguments.get("net_pins"))
179
+ if name in ("xprobe", "resolve_pin"):
180
+ for key in ("instance", "pin"):
181
+ if key not in arguments:
182
+ raise ProjectError(
183
+ "invalid_command",
184
+ f"{name} needs {key}.",
185
+ )
186
+ return project.probe_report(
187
+ arguments["instance"], arguments["pin"],
188
+ net_pins=arguments.get("net_pins"))
189
+ if name == "plot_export":
190
+ return project.plot_report(
191
+ format=arguments.get("format", "svg"),
192
+ sheet=arguments.get("sheet"))
193
+ if name == "erc_check":
194
+ return project.erc_report(
195
+ severities=arguments.get("severities"),
196
+ pin_conflict=arguments.get("pin_conflict"),
197
+ net_pins=arguments.get("net_pins"))
198
+ if name in ("write_netlist", "update_netlist"):
199
+ if "language" not in arguments:
200
+ raise ProjectError(
201
+ "invalid_command",
202
+ f"{name} needs a language.",
203
+ )
204
+ return project.netlist_report(
205
+ arguments["language"],
206
+ sheet=arguments.get("sheet"),
207
+ title=arguments.get("title", "top"),
208
+ net_pins=arguments.get("net_pins"),
209
+ prior_text=arguments.get("prior_text"),
210
+ adopted=arguments.get("adopted", False))
211
+ if name == "check_capability":
212
+ for key in ("profile", "operation"):
213
+ if key not in arguments:
214
+ raise ProjectError(
215
+ "invalid_command",
216
+ f"check_capability needs {key}.",
217
+ )
218
+ return project.capability_report(
219
+ arguments["profile"], arguments["operation"],
220
+ bindings=arguments.get("bindings"))
221
+ if name == "describe_testbench":
222
+ if "testbench" not in arguments:
223
+ raise ProjectError(
224
+ "invalid_command",
225
+ "describe_testbench needs a testbench.",
226
+ )
227
+ return project.testbench_report(
228
+ arguments["testbench"])
229
+ return self._query_entities(name, arguments)
230
+
231
+ def _query_entities(self, name, arguments):
232
+ """Serve Wave-3 reads through the component seams.
233
+
234
+ Args:
235
+ name: Wave-3 entity read query name.
236
+ arguments: Query arguments mapping.
237
+
238
+ Returns:
239
+ Entity data with head revision and I28 pin.
240
+
241
+ Raises:
242
+ ProjectError: invalid_command for missing arguments
243
+ or the seam refusal code.
244
+ """
245
+ from ..interfaces.refs import pin_closure as _pin
246
+
247
+ project = self._project
248
+ head = project.snapshot()["current_revision"]
249
+
250
+ def _wrap(payload):
251
+ """Attach head revision and I28 pin to one payload.
252
+
253
+ Args:
254
+ payload: Read payload mapping.
255
+
256
+ Returns:
257
+ Payload with current_revision and i28 pin.
258
+ """
259
+ return {
260
+ **payload, "current_revision": head,
261
+ "i28": _pin(head),
262
+ }
263
+
264
+ if name == "list_sheets":
265
+ return _wrap(project.entities_with("Sheet"))
266
+ if name == "get_sheet":
267
+ if "entity" not in arguments:
268
+ raise ProjectError(
269
+ "invalid_command",
270
+ "get_sheet needs an entity.",
271
+ )
272
+ return _wrap(project.get_component(
273
+ arguments["entity"], "Sheet"))
274
+ if name == "get_instance":
275
+ if "entity" not in arguments:
276
+ raise ProjectError(
277
+ "invalid_command",
278
+ "get_instance needs an entity.",
279
+ )
280
+ return _wrap(project.get_component(
281
+ arguments["entity"], "Instance"))
282
+ if name == "instances_on_sheet":
283
+ if "sheet" not in arguments:
284
+ raise ProjectError(
285
+ "invalid_command",
286
+ "instances_on_sheet needs a sheet.",
287
+ )
288
+ sheet = arguments["sheet"]
289
+ found = []
290
+ for entity in project.entities_with(
291
+ "Instance")["entities"]:
292
+ data = project.get_component(
293
+ entity, "Instance")["data"]
294
+ if isinstance(data, dict) and data.get(
295
+ "sheet") == sheet:
296
+ found.append(entity)
297
+ return _wrap({"sheet": sheet, "instances": found})
298
+ if name == "list_symbols":
299
+ return _wrap(project.entities_with("Symbol"))
300
+ if name == "get_symbol":
301
+ if "entity" not in arguments:
302
+ raise ProjectError(
303
+ "invalid_command",
304
+ "get_symbol needs an entity.",
305
+ )
306
+ return _wrap(project.get_component(
307
+ arguments["entity"], "Symbol"))
308
+ if name == "get_cell":
309
+ if "entity" not in arguments:
310
+ raise ProjectError(
311
+ "invalid_command",
312
+ "get_cell needs an entity.",
313
+ )
314
+ return _wrap(project.get_component(
315
+ arguments["entity"], "Cell"))
316
+ if name == "list_layers":
317
+ try:
318
+ doc = project.read_doc("layers/bindings.yaml")
319
+ except ProjectError as exc:
320
+ if exc.code == "unknown_document":
321
+ raise ProjectError(
322
+ "invalid_command",
323
+ "No bindings saved yet: run bind_layers.",
324
+ ) from exc
325
+ raise
326
+ return _wrap({"bindings_path": doc["path"],
327
+ "content": doc["content"]})
328
+ if name == "list_testbenches":
329
+ return _wrap(project.entities_with("Testbench"))
330
+ if name == "ip_search":
331
+ if "pdk" not in arguments:
332
+ raise ProjectError(
333
+ "invalid_command",
334
+ "ip_search needs a pdk.",
335
+ )
336
+ wanted = arguments.get("name_filter")
337
+ found = []
338
+ for entity in project.entities_with(
339
+ "IpBlock")["entities"]:
340
+ data = project.get_component(
341
+ entity, "IpBlock")["data"]
342
+ if not isinstance(data, dict):
343
+ continue
344
+ if data.get("pdk") != arguments["pdk"]:
345
+ continue
346
+ if wanted and wanted not in data.get("name", ""):
347
+ continue
348
+ item = dict(data)
349
+ item["id"] = entity
350
+ found.append(item)
351
+ found.sort(key=lambda item: item.get("version", ""))
352
+ return _wrap({"pdk": arguments["pdk"],
353
+ "blocks": found})
354
+ if name == "ip_get":
355
+ for key in ("pdk", "name"):
356
+ if key not in arguments:
357
+ raise ProjectError(
358
+ "invalid_command",
359
+ f"ip_get needs {key}.",
360
+ )
361
+ return _wrap(self._select_ip(
362
+ arguments["pdk"], arguments["name"],
363
+ arguments.get("version")))
364
+ raise ProjectError(
365
+ "unsupported_operation", f"Operation not wired yet: {name}.")
366
+
367
+ def _select_ip(self, pdk, name, version):
368
+ """Select one registered IP block, latest when unpinned.
369
+
370
+ Args:
371
+ pdk: Owning PDK id (shared blocks qualify too).
372
+ name: Block name within the registry.
373
+ version: Revision label, or None for the greatest.
374
+
375
+ Returns:
376
+ Full block mapping with id.
377
+
378
+ Raises:
379
+ ProjectError: unknown_ip when no block matches.
380
+ """
381
+ project = self._project
382
+ candidates = []
383
+ for entity in project.entities_with("IpBlock")["entities"]:
384
+ data = project.get_component(entity, "IpBlock")["data"]
385
+ if not isinstance(data, dict):
386
+ continue
387
+ if data.get("name") != name:
388
+ continue
389
+ if data.get("pdk") != pdk and data.get(
390
+ "scope") != "shared":
391
+ continue
392
+ item = dict(data)
393
+ item["id"] = entity
394
+ candidates.append(item)
395
+ if version is not None:
396
+ for item in candidates:
397
+ if item.get("version") == version:
398
+ return item
399
+ elif candidates:
400
+ return sorted(
401
+ candidates, key=lambda item: item.get(
402
+ "version", ""))[-1]
403
+ raise ProjectError(
404
+ "unknown_ip", f"No such IP block: {pdk}/{name}.")