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,464 @@
1
+ """Isolated worker substrate (I23/I24): one sandboxed worker per cell.
2
+
3
+ A CellRunner admits approved console cells and runs each in a fresh
4
+ worker: a BwrapRunner-confined `/usr/bin/python3` on a fresh scratch
5
+ directory (scratch-only writes, I16 output budgets, kill on timeout).
6
+ Approval binding is NOT reimplemented here — run_cell admits through
7
+ ConsoleSession.admit_cell and inherits approval_invalid for free.
8
+
9
+ Execution-scoped IPC credentials (I23): each run issues one token
10
+ from a CredentialGate and injects it as ROCKETEDA_CELL_TOKEN. The
11
+ gate accepts a token only while its run lives; background or
12
+ delayed callbacks after the run meet credential_revoked, and tokens
13
+ from other runs meet credential_revoked too — the namespace never
14
+ crosses cells. Revocation plus confirmed tree-kill strictly precede
15
+ the terminal state and therefore the next cell (runs are serial).
16
+
17
+ Reusable results persist only as approved artifacts (I24): the
18
+ caller declares relative scratch paths up front; at harvest time
19
+ each is resolved and only survivors strictly inside the scratch are
20
+ copied to the artifacts root (symlink escapes are rejected, never
21
+ followed). The scratch itself is always removed.
22
+
23
+ Crash/timeout reconciliation is truthful: completed (exit 0) is the
24
+ only ok state; failed (nonzero exit), timeout (budget kill, partial
25
+ stdout plus present artifacts retained), and crashed (death by
26
+ signal) all report ok False with a reason — never a fake PASS.
27
+
28
+ Network namespaces ride capability probing, loudly: the worker
29
+ tries ["user", "net"] first and falls back to ["user"] only where
30
+ the kernel refuses, stamping every admission and result with
31
+ network_isolated. require_net=True disables admission instead of
32
+ degrading. (This sandbox denies netns; re-prove on target, B10.)
33
+ """
34
+
35
+ import hashlib
36
+ import os
37
+ import secrets
38
+ import shutil
39
+ import signal
40
+ import subprocess
41
+ import tempfile
42
+ import threading
43
+ import time
44
+ import uuid
45
+ from pathlib import Path, PurePosixPath
46
+
47
+ from .errors import ExecutionError
48
+ from .runner import BwrapRunner, clean_stream, default_profile, pgid_survivors
49
+
50
+ _CELL_PYTHON = "/usr/bin/python3"
51
+ _CELL_ENTRY = "cell.py"
52
+ _TOKEN_BYTES = 32
53
+ _KILL_CONFIRM_GRACE_S = 2.0
54
+ _KILL_CONFIRM_STEP_S = 0.05
55
+
56
+
57
+ def worker_profile(tool_paths=(), extra_unset=()):
58
+ """Sandbox profile for cells: the default recipe, scrubbed by default.
59
+
60
+ DISPLAY is always unset inside workers (netgen's Tk init aborts
61
+ against an unreachable display); callers add more via extra_unset.
62
+ Namespaces stay ["user"] here — CellRunner upgrades to net where
63
+ the kernel proves it, and says so on every result.
64
+
65
+ Args:
66
+ tool_paths: Resolved tool binary paths to bind read-only.
67
+ extra_unset: Extra env names scrubbed inside workers.
68
+
69
+ Returns:
70
+ Cell sandbox profile mapping.
71
+ """
72
+ profile = default_profile(tool_paths)
73
+ profile["env_unset"] = list(profile.get("env_unset", [])) + list(extra_unset)
74
+ return profile
75
+
76
+
77
+ class CredentialGate:
78
+ """Thread-safe execution-scoped credential ledger.
79
+
80
+ Tokens are random per run. invoke() accepts a token only while
81
+ its run is live; revoked tokens raise credential_revoked and
82
+ never-issued tokens raise credential_unknown.
83
+ """
84
+
85
+ def __init__(self):
86
+ self._lock = threading.Lock()
87
+ self._live = {}
88
+ self._revoked = set()
89
+
90
+ def issue(self, run_id):
91
+ """Issue one random token scoped to a live run.
92
+
93
+ Args:
94
+ run_id: Run the token is scoped to.
95
+
96
+ Returns:
97
+ Token hex string.
98
+ """
99
+ token = secrets.token_hex(_TOKEN_BYTES)
100
+ with self._lock:
101
+ self._live[token] = run_id
102
+ return token
103
+
104
+ def invoke(self, token, call=None):
105
+ """Accept a token only while its run lives.
106
+
107
+ Args:
108
+ token: Token to invoke with.
109
+ call: Call descriptor recorded on acceptance.
110
+
111
+ Returns:
112
+ Acceptance with run_id, call, and accepted True.
113
+
114
+ Raises:
115
+ ExecutionError: credential_revoked for an expired token,
116
+ credential_unknown for a never-issued token.
117
+ """
118
+ with self._lock:
119
+ if token in self._live:
120
+ return {
121
+ "run_id": self._live[token],
122
+ "call": call,
123
+ "accepted": True,
124
+ }
125
+ revoked = token in self._revoked
126
+ if revoked:
127
+ raise ExecutionError(
128
+ "credential_revoked",
129
+ "Credential expired with its run; delayed calls rejected.",
130
+ )
131
+ raise ExecutionError(
132
+ "credential_unknown", "Unknown credential: never issued."
133
+ )
134
+
135
+ def revoke(self, run_id):
136
+ """Expire every token scoped to one run.
137
+
138
+ Args:
139
+ run_id: Run whose tokens expire.
140
+
141
+ Returns:
142
+ Mapping with run_id and whether any token expired.
143
+ """
144
+ with self._lock:
145
+ expired = [
146
+ token for token, owner in self._live.items() if owner == run_id
147
+ ]
148
+ for token in expired:
149
+ del self._live[token]
150
+ self._revoked.add(token)
151
+ return {"run_id": run_id, "revoked": bool(expired)}
152
+
153
+
154
+ def _validate_artifacts(artifacts):
155
+ names = []
156
+ for name in artifacts:
157
+ parsed = PurePosixPath(name)
158
+ if (
159
+ parsed.is_absolute()
160
+ or ".." in parsed.parts
161
+ or not parsed.parts
162
+ or str(name) != parsed.as_posix()
163
+ ):
164
+ raise ExecutionError(
165
+ "invalid_reference",
166
+ f"Artifact escapes the scratch: {name}.",
167
+ )
168
+ names.append(parsed.as_posix())
169
+ return names
170
+
171
+
172
+ def _confirm_tree_dead(pgid, grace_s=_KILL_CONFIRM_GRACE_S):
173
+ """True once no process-group member survives (bounded wait)."""
174
+ deadline = time.monotonic() + grace_s
175
+ while True:
176
+ if not pgid_survivors(pgid):
177
+ return True
178
+ if time.monotonic() >= deadline:
179
+ return False
180
+ time.sleep(_KILL_CONFIRM_STEP_S)
181
+
182
+
183
+ class CellRunner:
184
+ """Admit approved cells; run each in a fresh isolated worker.
185
+
186
+ Args:
187
+ binary: bwrap binary name or path.
188
+ profile: Sandbox profile; worker_profile() when omitted.
189
+ artifacts_root: Harvest destination root; temp dir default.
190
+ default_timeout_s: Cell budget when the call omits one.
191
+ require_net: Refuse admission unless net isolation probes;
192
+ degrade loudly to user-only otherwise.
193
+ """
194
+
195
+ def __init__(self, binary="bwrap", profile=None, artifacts_root=None,
196
+ default_timeout_s=120, require_net=False):
197
+ self._binary = binary
198
+ self._base_profile = dict(profile) if profile else worker_profile()
199
+ self._gate = CredentialGate()
200
+ self._default_timeout_s = default_timeout_s
201
+ self._artifacts_root = Path(
202
+ artifacts_root
203
+ if artifacts_root is not None
204
+ else Path(tempfile.gettempdir()) / "rocketeda_cell_artifacts"
205
+ )
206
+ self._admission, self._runner = self._preflight(require_net)
207
+
208
+ def _preflight(self, require_net):
209
+ try:
210
+ runner = BwrapRunner(self._base_profile, binary=self._binary)
211
+ runner.preflight()
212
+ except ExecutionError as exc:
213
+ return (
214
+ {"admitted": False, "reason": str(exc),
215
+ "network_isolated": False},
216
+ None,
217
+ )
218
+ network_isolated = False
219
+ try:
220
+ netted = dict(self._base_profile)
221
+ namespaces = list(netted.get("namespaces", ["user"]))
222
+ if "net" not in namespaces:
223
+ namespaces.append("net")
224
+ netted["namespaces"] = namespaces
225
+ BwrapRunner(netted, binary=self._binary).preflight()
226
+ network_isolated = True
227
+ runner = BwrapRunner(netted, binary=self._binary)
228
+ except ExecutionError:
229
+ if require_net:
230
+ return (
231
+ {
232
+ "admitted": False,
233
+ "reason": "network isolation unprovable: "
234
+ "net namespace refused by the kernel.",
235
+ "network_isolated": False,
236
+ },
237
+ None,
238
+ )
239
+ try:
240
+ self._prove_cell_python(runner)
241
+ except ExecutionError as exc:
242
+ return (
243
+ {"admitted": False, "reason": str(exc),
244
+ "network_isolated": False},
245
+ None,
246
+ )
247
+ return (
248
+ {"admitted": True, "reason": None,
249
+ "network_isolated": network_isolated},
250
+ runner,
251
+ )
252
+
253
+ def _prove_cell_python(self, runner):
254
+ with tempfile.TemporaryDirectory(prefix="cell_python_probe_") as tmp:
255
+ try:
256
+ proc = runner.run(
257
+ [_CELL_PYTHON, "-c", "pass"], tmp, timeout_s=30
258
+ )
259
+ except ExecutionError as exc:
260
+ raise ExecutionError(
261
+ "isolation_unavailable",
262
+ f"sandbox python3 unavailable: {exc}.",
263
+ ) from exc
264
+ if proc.returncode != 0:
265
+ raise ExecutionError(
266
+ "isolation_unavailable",
267
+ f"sandbox python3 unavailable: exit {proc.returncode}.",
268
+ )
269
+
270
+ @property
271
+ def admission(self):
272
+ """Isolation admission record, stamped on every result.
273
+
274
+ Returns:
275
+ Admission mapping copy (admitted, reason,
276
+ network_isolated).
277
+ """
278
+ return dict(self._admission)
279
+
280
+ @property
281
+ def gate(self):
282
+ """Execution-scoped credential gate for cell IPC.
283
+
284
+ Returns:
285
+ Credential gate.
286
+ """
287
+ return self._gate
288
+
289
+ def run_cell(self, session, approval_id, code, history_cursor,
290
+ env_digest, expected_revision, artifacts=(), timeout_s=None,
291
+ on_live=None):
292
+ """Run one approved cell in a fresh worker; return its record.
293
+
294
+ Pre-spawn checks precede any execution, cheapest first:
295
+ artifact paths, then the console approval binding, then
296
+ isolation admission. Without admission the call raises
297
+ isolation_unavailable and nothing runs anywhere — no host
298
+ fallback, ever.
299
+
300
+ Args:
301
+ session: Console session admitting the approval.
302
+ approval_id: Approval bound by request_cell_approval.
303
+ code: Full worker source; must match the binding.
304
+ history_cursor: History position to re-check.
305
+ env_digest: Environment digest to re-check.
306
+ expected_revision: Revision to re-check.
307
+ artifacts: Scratch-relative paths to harvest.
308
+ timeout_s: Cell budget; runner default when omitted.
309
+ on_live: Optional callback while the worker is live.
310
+
311
+ Returns:
312
+ Terminal cell record (completed/failed/timeout/crashed)
313
+ with stdio, artifacts, and kill confirmations.
314
+
315
+ Raises:
316
+ ExecutionError: invalid_reference for escaping artifact
317
+ paths, approval_invalid for binding drift,
318
+ isolation_unavailable without admission,
319
+ runner_unavailable when the worker cannot spawn.
320
+ """
321
+ names = _validate_artifacts(artifacts)
322
+ session.admit_cell(
323
+ approval_id, code=code, history_cursor=history_cursor,
324
+ env_digest=env_digest, expected_revision=expected_revision,
325
+ )
326
+ if not self._admission["admitted"]:
327
+ raise ExecutionError(
328
+ "isolation_unavailable", self._admission["reason"]
329
+ )
330
+ budget = self._default_timeout_s if timeout_s is None else timeout_s
331
+ scratch = tempfile.mkdtemp(prefix="rocketeda_cell_")
332
+ run_id = uuid.uuid4().hex
333
+ token = self._gate.issue(run_id)
334
+ Path(scratch, _CELL_ENTRY).write_text(code, encoding="utf-8")
335
+ argv = self._runner.argv_for(
336
+ scratch, [_CELL_PYTHON, _CELL_ENTRY],
337
+ extra_env={"ROCKETEDA_RUN_ID": run_id,
338
+ "ROCKETEDA_CELL_TOKEN": token},
339
+ )
340
+ try:
341
+ try:
342
+ proc = subprocess.Popen(
343
+ argv,
344
+ stdin=subprocess.DEVNULL,
345
+ stdout=subprocess.PIPE,
346
+ stderr=subprocess.PIPE,
347
+ cwd=scratch,
348
+ start_new_session=True,
349
+ )
350
+ except OSError as exc:
351
+ raise ExecutionError(
352
+ "runner_unavailable", f"Worker spawn failed: {exc}."
353
+ ) from exc
354
+ try:
355
+ if on_live is not None:
356
+ on_live(run_id, token)
357
+ try:
358
+ out_bytes, err_bytes = proc.communicate(timeout=budget)
359
+ except subprocess.TimeoutExpired:
360
+ self._terminate(proc)
361
+ out_bytes, err_bytes = proc.communicate()
362
+ return self._finish(
363
+ run_id, scratch, names, out_bytes, err_bytes,
364
+ state="timeout", ok=False,
365
+ reason=f"timeout after {budget}s; partials retained.",
366
+ exit_code=None, partial=True, pgid=proc.pid,
367
+ )
368
+ returncode = proc.returncode
369
+ if returncode == 0:
370
+ return self._finish(
371
+ run_id, scratch, names, out_bytes, err_bytes,
372
+ state="completed", ok=True, reason=None,
373
+ exit_code=0, partial=False, pgid=proc.pid,
374
+ )
375
+ # bwrap reports child signal death as 128+signo (the
376
+ # shell convention), so exits above 128 read as crashes.
377
+ # A cell deliberately exiting 129+ misreads the same way;
378
+ # both land ok False, so the ambiguity never fake-passes.
379
+ if returncode < 0 or returncode > 128:
380
+ signo = -returncode if returncode < 0 else returncode - 128
381
+ return self._finish(
382
+ run_id, scratch, names, out_bytes, err_bytes,
383
+ state="crashed", ok=False,
384
+ reason=f"death by signal {signo}.",
385
+ exit_code=returncode, partial=True, pgid=proc.pid,
386
+ )
387
+ return self._finish(
388
+ run_id, scratch, names, out_bytes, err_bytes,
389
+ state="failed", ok=False,
390
+ reason=f"exit {returncode}.",
391
+ exit_code=returncode, partial=False, pgid=proc.pid,
392
+ )
393
+ finally:
394
+ if proc.poll() is None:
395
+ self._terminate(proc)
396
+ proc.communicate()
397
+ finally:
398
+ self._gate.revoke(run_id)
399
+ shutil.rmtree(scratch, ignore_errors=True)
400
+
401
+ @staticmethod
402
+ def _terminate(proc):
403
+ try:
404
+ os.killpg(proc.pid, signal.SIGKILL)
405
+ except (ProcessLookupError, PermissionError, OSError):
406
+ pass
407
+ try:
408
+ proc.kill()
409
+ except (ProcessLookupError, OSError):
410
+ pass
411
+ proc.wait()
412
+
413
+ def _finish(self, run_id, scratch, names, out_bytes, err_bytes,
414
+ state, ok, reason, exit_code, partial, pgid):
415
+ # Revocation + tree-kill confirmation strictly precede the
416
+ # terminal record (and therefore the next cell).
417
+ self._gate.revoke(run_id)
418
+ try:
419
+ os.killpg(pgid, signal.SIGKILL)
420
+ except (ProcessLookupError, PermissionError, OSError):
421
+ pass
422
+ tree_killed = _confirm_tree_dead(pgid)
423
+ harvested, rejected = self._harvest(run_id, scratch, names)
424
+ return {
425
+ "run_id": run_id,
426
+ "state": state,
427
+ "ok": ok,
428
+ "reason": reason,
429
+ "exit_code": exit_code,
430
+ "stdout": clean_stream(out_bytes),
431
+ "stderr": clean_stream(err_bytes),
432
+ "partial": partial,
433
+ "artifacts": harvested,
434
+ "rejected_artifacts": rejected,
435
+ "network_isolated": self._admission["network_isolated"],
436
+ "credential_revoked": True,
437
+ "tree_killed": tree_killed,
438
+ "scratch": scratch,
439
+ }
440
+
441
+ def _harvest(self, run_id, scratch, names):
442
+ base = Path(scratch).resolve()
443
+ harvested, rejected = [], []
444
+ for name in names:
445
+ target = (base / name).resolve()
446
+ if not target.is_relative_to(base) or not target.is_file():
447
+ rejected.append(name)
448
+ continue
449
+ digest = hashlib.sha256()
450
+ with open(target, "rb") as handle:
451
+ for chunk in iter(lambda: handle.read(65536), b""):
452
+ digest.update(chunk)
453
+ dest = self._artifacts_root / run_id / name
454
+ dest.parent.mkdir(parents=True, exist_ok=True)
455
+ shutil.copyfile(target, dest)
456
+ harvested.append(
457
+ {
458
+ "name": name,
459
+ "path": str(dest),
460
+ "bytes": dest.stat().st_size,
461
+ "sha256": digest.hexdigest(),
462
+ }
463
+ )
464
+ return harvested, rejected
@@ -0,0 +1,26 @@
1
+ # interfaces — shared engine contracts and design data
2
+
3
+ `interfaces/` defines the operation and query vocabulary, shared records, validation helpers, and ports used across the engine. It is the source for the command names and core shapes that transports and solver adapters share.
4
+
5
+ ## What lives here
6
+
7
+ - `vocabulary.py`, `ports.py`, `errors.py`, and `envelopes.py`: operation/query names, the `ToolAdapter` and other boundary types, contract errors, and the typed v0 envelopes plus the v0→v1 field map.
8
+ - `documents.py`, `references.py`, `paths.py`, and `runparams.py`: document validation, reference resolution, path containment, and run-parameter validation.
9
+ - `celllink.py` and `layout.py`: cell-link and layout data operations.
10
+ - `layout_schema.yaml` and `yaml_dump.py`: persisted design-data formats.
11
+ - `pdks.py`, `setup.py`, `setup_catalogue.yaml`, and `setup_probes.py`: PDK registration/verification and tool readiness support. The PDK registry performs bounded filesystem I/O; this module is no longer entirely I/O-free.
12
+ - Project stores live in [`stores/`](../stores/README.md): `settings.py`, `ecs.py`, `wireplace.py`, `layers.py`.
13
+
14
+ The module does not invoke EDA solvers. Some low-level stores and registries are colocated here; project revisions and event ordering belong to [`project/`](../project/README.md), while user-facing engine operations are dispatched by [`composition.py`](../composition.py).
15
+
16
+ ## API contract generation
17
+
18
+ The operation vocabulary is maintained in `vocabulary.py`. Google-style handler docstrings feed `scripts/api_spec.py`, which generates `docs/engine-api.md`, `docs/api-schema.json`, and `docs/api-help.json`. Those generated artifacts must not be edited by hand. The [contributor rules](../../AGENTS.md) describe the required docstring shape; the [generated API reference](../../docs/engine-api.md) is the current operation and query inventory.
19
+
20
+ ## Routing
21
+
22
+ - Add or change a transport or solver edge → [`adapters/`](../adapters/README.md).
23
+ - Change revision, document, or event behavior → [`project/`](../project/README.md).
24
+ - Change run lifecycle or verdict evaluation → [`execution/`](../execution/README.md).
25
+ - Change stage coverage or completion rules → [`workflows/`](../workflows/README.md).
26
+ - Product-level contract → [PRD tooling spec](../../../PRD/tools-research/frontend-tooling-spec.md) and [tooling operation catalog](../../../PRD/tools-research/tooling-operations.yaml).
@@ -0,0 +1,58 @@
1
+ """Single source of truth for engine contracts: wire schemas, typed
2
+ commands, events, and outbound ports. No I/O, no logic."""
3
+
4
+ from .apig_assets import asset_manifest, list_assets, serve_asset
5
+ from .celllink import (
6
+ REFERENCE,
7
+ CellLinkError,
8
+ collect_erc,
9
+ match_cell_filters,
10
+ resolve_cell,
11
+ resolve_link,
12
+ resolve_pin,
13
+ resolve_run,
14
+ validate_celllink,
15
+ )
16
+ from .documents import validate_document
17
+ from .errors import ContractError
18
+ from .ports import ToolAdapter
19
+ from .references import check_connection, resolve_reference
20
+ from .runparams import (
21
+ NETLIST_TYPES,
22
+ RUN_REQUEST_DEFAULTS,
23
+ RUN_REQUEST_FIELDS,
24
+ RUN_REQUEST_SCHEMA,
25
+ RunParamsError,
26
+ freeze_run_request,
27
+ parse_run_request,
28
+ validate_run_request,
29
+ )
30
+
31
+
32
+ __all__ = [
33
+ "NETLIST_TYPES",
34
+ "RUN_REQUEST_DEFAULTS",
35
+ "RUN_REQUEST_FIELDS",
36
+ "RUN_REQUEST_SCHEMA",
37
+ "REFERENCE",
38
+ "CellLinkError",
39
+ "ContractError",
40
+ "RunParamsError",
41
+ "ToolAdapter",
42
+ "asset_manifest",
43
+ "check_connection",
44
+ "collect_erc",
45
+ "freeze_run_request",
46
+ "list_assets",
47
+ "match_cell_filters",
48
+ "parse_run_request",
49
+ "resolve_cell",
50
+ "resolve_link",
51
+ "resolve_pin",
52
+ "resolve_reference",
53
+ "resolve_run",
54
+ "serve_asset",
55
+ "validate_celllink",
56
+ "validate_document",
57
+ "validate_run_request",
58
+ ]