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.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|