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,553 @@
|
|
|
1
|
+
"""Engine ops mixin: Run pumping, batching, replay and solver execution."""
|
|
2
|
+
|
|
3
|
+
from ..adapters.errors import AdapterError
|
|
4
|
+
from ..execution.apig.pool import request_for_run
|
|
5
|
+
from ..execution.apig.submit import merge_ack_completion
|
|
6
|
+
from ..execution.block_runs_scope import (build_scope_record, narrow_job_for_block, stamp_verdict)
|
|
7
|
+
from ..execution.errors import ExecutionError
|
|
8
|
+
from ..execution.replay_safe import content_key_for
|
|
9
|
+
from ..interfaces.yaml_dump import dump_yaml_text
|
|
10
|
+
from ..project.errors import ProjectError
|
|
11
|
+
from .gates import check_submit_gates
|
|
12
|
+
|
|
13
|
+
#: Per-item state rank for the batch worst-state-wins summary —
|
|
14
|
+
#: a failed run outranks a cancelled one, which outranks a refused
|
|
15
|
+
#: (never attempted) one, which outranks a clean completion.
|
|
16
|
+
_STATE_RANK = {"failed": 3, "cancelled": 2, "refused": 1, "completed": 0}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def refuse_smuggled_budget(arguments):
|
|
20
|
+
"""Refuse a root-level budget smuggled past the frozen request.
|
|
21
|
+
|
|
22
|
+
The budget travels in the frozen `run.request` only, the way
|
|
23
|
+
caller min/max targets never ride the call. Admission
|
|
24
|
+
refuses it fail-fast; execution stamps the frozen value
|
|
25
|
+
itself, so the stored command never carries a root budget.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
arguments: Solver arguments to inspect.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ProjectError: invalid_command for a root-level
|
|
32
|
+
`timeout_s`.
|
|
33
|
+
"""
|
|
34
|
+
if "timeout_s" in arguments:
|
|
35
|
+
raise ProjectError(
|
|
36
|
+
"invalid_command",
|
|
37
|
+
"Budget comes from the frozen run.request timeout_s, "
|
|
38
|
+
"not the job root: remove timeout_s.",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def stamp_solver_budget(arguments):
|
|
43
|
+
"""Refuse a smuggled root budget; stamp the frozen one.
|
|
44
|
+
|
|
45
|
+
The budget travels in the frozen `run.request` (None means
|
|
46
|
+
"adapter default": a spice sim keeps 120s, a LibreLane flow
|
|
47
|
+
keeps its hour). Called at execution: admission already
|
|
48
|
+
refused smuggling, so the stored command carries none and
|
|
49
|
+
the stamp lands exactly once.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
arguments: Solver arguments with a normalized `request`.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Arguments with the frozen budget stamped as the job
|
|
56
|
+
`timeout_s`, or untouched when the request keeps
|
|
57
|
+
the adapter default.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ProjectError: invalid_command for a root-level
|
|
61
|
+
`timeout_s`.
|
|
62
|
+
"""
|
|
63
|
+
refuse_smuggled_budget(arguments)
|
|
64
|
+
budget = (arguments.get("request") or {}).get("timeout_s")
|
|
65
|
+
if budget is None:
|
|
66
|
+
return arguments
|
|
67
|
+
return {**arguments, "timeout_s": budget}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _item_state(completion):
|
|
71
|
+
"""Batch state for one completion: verdict FAIL reads failed."""
|
|
72
|
+
verdict = (completion or {}).get("verdict") or {}
|
|
73
|
+
if verdict.get("status") == "FAIL":
|
|
74
|
+
return "failed"
|
|
75
|
+
return completion.get("state", "completed")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class _RunMixin:
|
|
79
|
+
"""Run pumping, batching, replay and solver execution."""
|
|
80
|
+
|
|
81
|
+
def _await_shared(self, ack, run_id, waiter):
|
|
82
|
+
"""Resolve a coalesced envelope to the shared completion.
|
|
83
|
+
|
|
84
|
+
The waiter delivers the owner's completion; a run evicted
|
|
85
|
+
just before attach (or discarded when its drain raised)
|
|
86
|
+
falls back to the stored completion — the run event
|
|
87
|
+
precedes eviction, so fetch cannot miss a finished run.
|
|
88
|
+
"""
|
|
89
|
+
completion = None
|
|
90
|
+
if waiter is not None:
|
|
91
|
+
completion = self._coalescer.await_completion(waiter)
|
|
92
|
+
if completion is None:
|
|
93
|
+
completion = self._stored_completion(run_id)
|
|
94
|
+
if completion is None:
|
|
95
|
+
raise ExecutionError(
|
|
96
|
+
"coalesced_run_lost",
|
|
97
|
+
f"Shared run finished with no completion: {run_id}.",
|
|
98
|
+
)
|
|
99
|
+
return merge_ack_completion(ack, completion)
|
|
100
|
+
|
|
101
|
+
def _resolve_pending(self, request_id):
|
|
102
|
+
"""Admitted (command, ack) for a queued request, registered.
|
|
103
|
+
|
|
104
|
+
Reboot path: unknown execution records are registered and
|
|
105
|
+
the run job stashed, so the pool runner finds both.
|
|
106
|
+
"""
|
|
107
|
+
stored_command, ack = self._project.command_for(request_id)
|
|
108
|
+
run_id = ack.get("run_id")
|
|
109
|
+
if not self._execution.known(run_id):
|
|
110
|
+
self._execution.dispatch(
|
|
111
|
+
stored_command.get("operation"),
|
|
112
|
+
{"cell": (stored_command.get("arguments") or {}).get("cell")},
|
|
113
|
+
ack.get("snapshot_id", ""),
|
|
114
|
+
run_id=run_id,
|
|
115
|
+
)
|
|
116
|
+
self._run_jobs.setdefault(
|
|
117
|
+
run_id,
|
|
118
|
+
{
|
|
119
|
+
"operation": stored_command.get("operation"),
|
|
120
|
+
"arguments": dict(stored_command.get("arguments") or {}),
|
|
121
|
+
"snapshot_id": ack.get("snapshot_id", ""),
|
|
122
|
+
"request_id": str(request_id),
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
return stored_command, ack
|
|
126
|
+
|
|
127
|
+
def _pool_execute(self, operation, arguments, snapshot_id, request_id,
|
|
128
|
+
run_id):
|
|
129
|
+
"""Execute one pooled run through its solver, contained."""
|
|
130
|
+
try:
|
|
131
|
+
cancelled = (
|
|
132
|
+
self._execution.status(run_id)["state"] == "cancelled"
|
|
133
|
+
)
|
|
134
|
+
except ExecutionError:
|
|
135
|
+
cancelled = False
|
|
136
|
+
if cancelled:
|
|
137
|
+
verdict = {
|
|
138
|
+
"status": "NOT_RUN",
|
|
139
|
+
"reason": "cancelled before dispatch",
|
|
140
|
+
}
|
|
141
|
+
self._project.append_run_event(
|
|
142
|
+
"run_failed", run_id, operation, verdict, request_id
|
|
143
|
+
)
|
|
144
|
+
return {
|
|
145
|
+
"run_id": run_id,
|
|
146
|
+
"state": "cancelled",
|
|
147
|
+
"operation": operation,
|
|
148
|
+
"reason": "cancelled before dispatch",
|
|
149
|
+
"verdict": verdict,
|
|
150
|
+
}
|
|
151
|
+
if not self._solvers.get(operation):
|
|
152
|
+
self._latest[operation] = run_id
|
|
153
|
+
self._logs[run_id] = (
|
|
154
|
+
f"unsupported_operation: No solver registered: {operation}."
|
|
155
|
+
)
|
|
156
|
+
verdict = self._execution.record_not_run(
|
|
157
|
+
run_id, self._logs[run_id]
|
|
158
|
+
)
|
|
159
|
+
self._project.append_run_event(
|
|
160
|
+
"run_failed", run_id, operation, verdict, request_id
|
|
161
|
+
)
|
|
162
|
+
return {
|
|
163
|
+
"run_id": run_id,
|
|
164
|
+
"state": "completed",
|
|
165
|
+
"verdict": verdict,
|
|
166
|
+
"operation": operation,
|
|
167
|
+
}
|
|
168
|
+
run_arguments = dict(arguments)
|
|
169
|
+
run_arguments.pop("expected_revision", None)
|
|
170
|
+
# REPLAY-SAFE: a kill after this mark reads as an
|
|
171
|
+
# interruption at the next boot (AMBIGUOUS, counted toward
|
|
172
|
+
# poison); a kill before it stays RETRYABLE.
|
|
173
|
+
self._project.append_replay_event(
|
|
174
|
+
"run_started",
|
|
175
|
+
request_id,
|
|
176
|
+
{
|
|
177
|
+
"run_id": run_id,
|
|
178
|
+
"content_key": content_key_for(operation, run_arguments),
|
|
179
|
+
"operation": operation,
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
return self._run_operation(
|
|
183
|
+
operation,
|
|
184
|
+
run_arguments,
|
|
185
|
+
snapshot_id,
|
|
186
|
+
request_id,
|
|
187
|
+
run_id=run_id,
|
|
188
|
+
contain=True,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def _run_batch(self, run_ids, force=False):
|
|
192
|
+
"""Pump a batch of admitted runs oldest-listed first.
|
|
193
|
+
|
|
194
|
+
Finished runs contribute their stored completion without
|
|
195
|
+
re-executing; runs cancelled before drain contribute a
|
|
196
|
+
cancelled completion without running; the rest are
|
|
197
|
+
admitted to the pool (pool_full refuses loudly past the
|
|
198
|
+
bound), gate-rechecked at drain time (a verdict that moved
|
|
199
|
+
since submit refuses loud), and drained — concurrently
|
|
200
|
+
when the pool fans out. Every listed id lands in exactly
|
|
201
|
+
one of completions/refused, and states/summary roll the
|
|
202
|
+
per-item outcomes up worst-first.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
run_ids: Admitted run ids to pump, in order.
|
|
206
|
+
force: Bypass drain-time verdict gates only (hard deps
|
|
207
|
+
still refuse).
|
|
208
|
+
"""
|
|
209
|
+
if not isinstance(run_ids, list) or not run_ids:
|
|
210
|
+
raise ProjectError(
|
|
211
|
+
"invalid_command",
|
|
212
|
+
"run_job batch needs a non-empty run_ids list.",
|
|
213
|
+
)
|
|
214
|
+
completions = []
|
|
215
|
+
refused = []
|
|
216
|
+
seen = set()
|
|
217
|
+
admitted = []
|
|
218
|
+
for run_id in run_ids:
|
|
219
|
+
if run_id in seen:
|
|
220
|
+
refused.append({
|
|
221
|
+
"run_id": run_id,
|
|
222
|
+
"code": "duplicate_in_batch",
|
|
223
|
+
"message": f"Run listed twice in one batch: {run_id}.",
|
|
224
|
+
})
|
|
225
|
+
continue
|
|
226
|
+
seen.add(run_id)
|
|
227
|
+
stored = self._stored_completion(run_id)
|
|
228
|
+
if stored is not None:
|
|
229
|
+
completions.append(stored)
|
|
230
|
+
continue
|
|
231
|
+
pending = self._run_jobs.get(run_id)
|
|
232
|
+
if pending is not None:
|
|
233
|
+
request_id = pending["request_id"]
|
|
234
|
+
else:
|
|
235
|
+
request_id = request_for_run(
|
|
236
|
+
self._project.events_since(0), run_id
|
|
237
|
+
)
|
|
238
|
+
if request_id is None:
|
|
239
|
+
refused.append({
|
|
240
|
+
"run_id": run_id,
|
|
241
|
+
"code": "unknown_request",
|
|
242
|
+
"message": f"No queued request: {run_id}.",
|
|
243
|
+
})
|
|
244
|
+
continue
|
|
245
|
+
stored_command, ack = self._project.command_for(request_id)
|
|
246
|
+
operation = stored_command.get("operation")
|
|
247
|
+
if not self._solvers.get(operation):
|
|
248
|
+
refused.append({
|
|
249
|
+
"run_id": run_id,
|
|
250
|
+
"code": "unsupported_operation",
|
|
251
|
+
"message": f"No solver registered: {operation}.",
|
|
252
|
+
})
|
|
253
|
+
continue
|
|
254
|
+
if self._is_cancelled(run_id):
|
|
255
|
+
completions.append(
|
|
256
|
+
self._record_cancelled_skip(
|
|
257
|
+
run_id, operation, stored_command, request_id
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
continue
|
|
261
|
+
try:
|
|
262
|
+
check_submit_gates(
|
|
263
|
+
operation,
|
|
264
|
+
stored_command.get("arguments") or {},
|
|
265
|
+
self._project,
|
|
266
|
+
force=force,
|
|
267
|
+
)
|
|
268
|
+
except ProjectError as exc:
|
|
269
|
+
refused.append({
|
|
270
|
+
"run_id": run_id,
|
|
271
|
+
"code": exc.code,
|
|
272
|
+
"message": str(exc),
|
|
273
|
+
})
|
|
274
|
+
continue
|
|
275
|
+
self._resolve_pending(request_id)
|
|
276
|
+
try:
|
|
277
|
+
adopted = self._pool.adopt(
|
|
278
|
+
request_id, run_id, stored_command,
|
|
279
|
+
ack.get("snapshot_id", ""),
|
|
280
|
+
)
|
|
281
|
+
except ExecutionError as exc:
|
|
282
|
+
refused.append({
|
|
283
|
+
"run_id": run_id,
|
|
284
|
+
"code": exc.code,
|
|
285
|
+
"message": str(exc),
|
|
286
|
+
})
|
|
287
|
+
continue
|
|
288
|
+
if not adopted:
|
|
289
|
+
stored = self._stored_completion(run_id)
|
|
290
|
+
if stored is not None:
|
|
291
|
+
completions.append(stored)
|
|
292
|
+
continue
|
|
293
|
+
admitted.append(run_id)
|
|
294
|
+
if admitted:
|
|
295
|
+
completions.extend(self._pool.drain_ids(admitted))
|
|
296
|
+
states = {}
|
|
297
|
+
for completion in completions:
|
|
298
|
+
states[completion.get("run_id")] = _item_state(completion)
|
|
299
|
+
for entry in refused:
|
|
300
|
+
states[entry.get("run_id")] = "refused"
|
|
301
|
+
summary = "completed"
|
|
302
|
+
for state in states.values():
|
|
303
|
+
if _STATE_RANK.get(state, 0) > _STATE_RANK.get(summary, 0):
|
|
304
|
+
summary = state
|
|
305
|
+
return {
|
|
306
|
+
"run_ids": list(run_ids),
|
|
307
|
+
"completions": completions,
|
|
308
|
+
"refused": refused,
|
|
309
|
+
"states": states,
|
|
310
|
+
"summary": summary,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
def _is_cancelled(self, run_id):
|
|
314
|
+
"""True when the run was cancelled before this drain."""
|
|
315
|
+
try:
|
|
316
|
+
return self._execution.status(run_id)["state"] == "cancelled"
|
|
317
|
+
except ExecutionError:
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
def _record_cancelled_skip(self, run_id, operation, stored_command,
|
|
321
|
+
request_id):
|
|
322
|
+
"""Record a cancel-before-drain skip as a terminal NOT_RUN."""
|
|
323
|
+
verdict = {
|
|
324
|
+
"status": "NOT_RUN",
|
|
325
|
+
"reason": "cancelled before dispatch",
|
|
326
|
+
}
|
|
327
|
+
self._project.append_run_event(
|
|
328
|
+
"run_failed", run_id, operation, verdict, request_id
|
|
329
|
+
)
|
|
330
|
+
return {
|
|
331
|
+
"run_id": run_id,
|
|
332
|
+
"state": "cancelled",
|
|
333
|
+
"operation": operation,
|
|
334
|
+
"reason": "cancelled before dispatch",
|
|
335
|
+
"verdict": verdict,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
def _replay_once(self):
|
|
339
|
+
"""Build the crash-survivor boot report once per Engine."""
|
|
340
|
+
if self._replayed:
|
|
341
|
+
return None
|
|
342
|
+
self._replayed = True
|
|
343
|
+
return self.boot_report()
|
|
344
|
+
|
|
345
|
+
def _replay_execute(self, operation, arguments, snapshot_id, request_id,
|
|
346
|
+
run_id):
|
|
347
|
+
"""Registered pool execution for one explicit resume."""
|
|
348
|
+
self._resolve_pending(request_id)
|
|
349
|
+
return self._pool_execute(
|
|
350
|
+
operation, arguments, snapshot_id, request_id, run_id
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def _run_pyexec(self, run_id, request_id):
|
|
354
|
+
"""Execute an admitted python_execute run (phase 2).
|
|
355
|
+
|
|
356
|
+
Approval lapses propagate as approval errors — approval
|
|
357
|
+
state is never folded into a verdict. Completion carries
|
|
358
|
+
the NOT_RUN verdict (zero exit is never check PASS) and
|
|
359
|
+
appends the run_failed ledger event the NOT_RUN shape
|
|
360
|
+
owns; cancellation returns the cancelled state with no
|
|
361
|
+
ledger event, like every cancelled job.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
run_id: Admitted execution id (the submit ack id).
|
|
365
|
+
request_id: Queued request this run discharges.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Completion mapping with run_id, state, verdict, and
|
|
369
|
+
operation.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
ExecutionError: unknown_run for an id never admitted,
|
|
373
|
+
approval_pending/approval_denied/approval_expired
|
|
374
|
+
when the approval lapses, approval_invalid for
|
|
375
|
+
binding drift.
|
|
376
|
+
|
|
377
|
+
Caution: Runs the approved worker now — read the verdict
|
|
378
|
+
(always NOT_RUN) plus the execution record, never the
|
|
379
|
+
exit path.
|
|
380
|
+
"""
|
|
381
|
+
record = self._pyexec.run_job(run_id)
|
|
382
|
+
self._latest["python_execute"] = run_id
|
|
383
|
+
log = record.get("stdout", "")
|
|
384
|
+
if record.get("stderr"):
|
|
385
|
+
log += "\n--- stderr ---\n" + record["stderr"]
|
|
386
|
+
self._logs[run_id] = log
|
|
387
|
+
if record["state"] == "cancelled":
|
|
388
|
+
return {
|
|
389
|
+
"run_id": run_id,
|
|
390
|
+
"state": "cancelled",
|
|
391
|
+
"verdict": record["verdict"],
|
|
392
|
+
"operation": "python_execute",
|
|
393
|
+
}
|
|
394
|
+
self._project.append_run_event(
|
|
395
|
+
"run_failed", run_id, "python_execute", record["verdict"],
|
|
396
|
+
request_id,
|
|
397
|
+
)
|
|
398
|
+
return {
|
|
399
|
+
"run_id": run_id,
|
|
400
|
+
"state": "completed",
|
|
401
|
+
"verdict": record["verdict"],
|
|
402
|
+
"operation": "python_execute",
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
def _run_operation(self, name, arguments, snapshot_id, request_id, run_id=None,
|
|
406
|
+
contain=False, block_scope=None):
|
|
407
|
+
"""Select the solver, run it, judge the verdict, log completion."""
|
|
408
|
+
from ..composition import _LOCATION_KEYS # deferred: breaks the ops<->composition cycle
|
|
409
|
+
solvers = self._solvers.get(name) or {}
|
|
410
|
+
if not solvers:
|
|
411
|
+
raise ProjectError(
|
|
412
|
+
"unsupported_operation", f"No solver registered: {name}."
|
|
413
|
+
)
|
|
414
|
+
arguments = stamp_solver_budget(dict(arguments))
|
|
415
|
+
try:
|
|
416
|
+
adapter = self._select_solver(name, arguments)
|
|
417
|
+
except ProjectError as exc:
|
|
418
|
+
if not contain:
|
|
419
|
+
raise
|
|
420
|
+
self._latest[name] = run_id
|
|
421
|
+
self._logs[run_id] = f"{exc.code}: {exc}"
|
|
422
|
+
verdict = self._execution.record_not_run(run_id, f"{exc.code}: {exc}")
|
|
423
|
+
stored_scope = self._run_jobs.get(run_id, {}).get("block_scope")
|
|
424
|
+
if stored_scope is not None:
|
|
425
|
+
verdict = stamp_verdict(verdict, stored_scope["provenance"])
|
|
426
|
+
self._execution.mark_completed(run_id, verdict)
|
|
427
|
+
self._project.append_run_event(
|
|
428
|
+
"run_failed", run_id, name, verdict, request_id
|
|
429
|
+
)
|
|
430
|
+
return {
|
|
431
|
+
"run_id": run_id,
|
|
432
|
+
"state": "completed",
|
|
433
|
+
"verdict": verdict,
|
|
434
|
+
"operation": name,
|
|
435
|
+
}
|
|
436
|
+
if run_id is None:
|
|
437
|
+
issued = self._execution.dispatch(
|
|
438
|
+
name, {"cell": arguments.get("cell")}, snapshot_id
|
|
439
|
+
)
|
|
440
|
+
run_id = issued["run_id"]
|
|
441
|
+
else:
|
|
442
|
+
# Reuses the submit-time record: unknown ids fail as unknown_run.
|
|
443
|
+
self._execution.status(run_id)
|
|
444
|
+
self._execution.mark_running(run_id)
|
|
445
|
+
self._latest[name] = run_id
|
|
446
|
+
scope_record = block_scope
|
|
447
|
+
if scope_record is None:
|
|
448
|
+
scope_record = self._run_jobs.get(run_id, {}).get("block_scope")
|
|
449
|
+
elif "provenance" not in scope_record:
|
|
450
|
+
# Direct runs admit the closure without a run_id; bind it now.
|
|
451
|
+
scope_record = build_scope_record(
|
|
452
|
+
run_id=run_id,
|
|
453
|
+
revision=self._project.snapshot()["current_revision"],
|
|
454
|
+
block_id=scope_record["block_id"],
|
|
455
|
+
deck=arguments.get("deck"),
|
|
456
|
+
closure=scope_record["closure"],
|
|
457
|
+
)
|
|
458
|
+
if scope_record is not None:
|
|
459
|
+
arguments = narrow_job_for_block(arguments, scope_record["closure"])
|
|
460
|
+
try:
|
|
461
|
+
adapter.preflight({"operation": name, **arguments})
|
|
462
|
+
native = adapter.execute({"operation": name, **arguments})
|
|
463
|
+
result = adapter.parse(native)
|
|
464
|
+
except AdapterError as exc:
|
|
465
|
+
self._logs[run_id] = f"{exc.code}: {exc}"
|
|
466
|
+
verdict = self._execution.record_not_run(run_id, f"{exc.code}: {exc}")
|
|
467
|
+
if scope_record is not None:
|
|
468
|
+
verdict = stamp_verdict(verdict, scope_record["provenance"])
|
|
469
|
+
self._execution.mark_completed(run_id, verdict)
|
|
470
|
+
self._project.append_run_event(
|
|
471
|
+
"run_failed", run_id, name, verdict, request_id
|
|
472
|
+
)
|
|
473
|
+
return {
|
|
474
|
+
"run_id": run_id,
|
|
475
|
+
"state": "completed",
|
|
476
|
+
"verdict": verdict,
|
|
477
|
+
"operation": name,
|
|
478
|
+
}
|
|
479
|
+
except Exception as exc:
|
|
480
|
+
# A solver bug in a queued job is NOT_RUN with a reason,
|
|
481
|
+
# never a crash and never a verdict. Direct runs re-raise.
|
|
482
|
+
if not contain:
|
|
483
|
+
raise
|
|
484
|
+
self._logs[run_id] = f"internal_error: {exc!r}"
|
|
485
|
+
verdict = self._execution.record_not_run(run_id, f"internal_error: {exc}")
|
|
486
|
+
if scope_record is not None:
|
|
487
|
+
verdict = stamp_verdict(verdict, scope_record["provenance"])
|
|
488
|
+
self._execution.mark_completed(run_id, verdict)
|
|
489
|
+
self._project.append_run_event(
|
|
490
|
+
"run_failed", run_id, name, verdict, request_id
|
|
491
|
+
)
|
|
492
|
+
return {
|
|
493
|
+
"run_id": run_id,
|
|
494
|
+
"state": "completed",
|
|
495
|
+
"verdict": verdict,
|
|
496
|
+
"operation": name,
|
|
497
|
+
}
|
|
498
|
+
self._logs[run_id] = native.get("log") or "\n".join(
|
|
499
|
+
part for part in (native.get("stdout"), native.get("stderr")) if part
|
|
500
|
+
)
|
|
501
|
+
self._datasets[run_id] = list(native.get("waveforms") or [])
|
|
502
|
+
self._spectra[run_id] = list(result.get("spectra") or [])
|
|
503
|
+
# Solver convention (see ToolAdapter.parse): violations carry
|
|
504
|
+
# rule/severity/message plus flat location keys, nested here.
|
|
505
|
+
findings = []
|
|
506
|
+
for violation in result.get("violations", []):
|
|
507
|
+
location = {}
|
|
508
|
+
for source, dest in _LOCATION_KEYS:
|
|
509
|
+
if violation.get(source) is not None:
|
|
510
|
+
location[dest] = violation[source]
|
|
511
|
+
findings.append(
|
|
512
|
+
{
|
|
513
|
+
"severity": violation.get("severity", "error"),
|
|
514
|
+
"rule": violation["rule"],
|
|
515
|
+
"message": violation.get("message", violation["rule"]),
|
|
516
|
+
"location": location,
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
findings_text = dump_yaml_text({"findings": findings})
|
|
520
|
+
verdict = self._execution.evaluate(
|
|
521
|
+
run_id, native.get("exit_code", 0), findings_text
|
|
522
|
+
)
|
|
523
|
+
# Keep measured values on PASS too: a verdict without numbers can't be reviewed.
|
|
524
|
+
if result.get("measurements") is not None:
|
|
525
|
+
verdict = dict(verdict, measurements=result["measurements"])
|
|
526
|
+
self._execution.mark_completed(run_id, verdict)
|
|
527
|
+
# Post-layout evidence rides the same way: the pre/post delta
|
|
528
|
+
# table plus the extraction/schematic provenance are small and
|
|
529
|
+
# reviewable (raw waveform samples stay adapter-level so the
|
|
530
|
+
# ledger never carries bulk vectors).
|
|
531
|
+
if result.get("comparison") is not None:
|
|
532
|
+
verdict = dict(verdict, comparison=result["comparison"])
|
|
533
|
+
self._execution.mark_completed(run_id, verdict)
|
|
534
|
+
if result.get("provenance") is not None:
|
|
535
|
+
verdict = dict(verdict, provenance=result["provenance"])
|
|
536
|
+
self._execution.mark_completed(run_id, verdict)
|
|
537
|
+
if scope_record is not None:
|
|
538
|
+
verdict = stamp_verdict(verdict, scope_record["provenance"])
|
|
539
|
+
self._execution.mark_completed(run_id, verdict)
|
|
540
|
+
if (verdict or {}).get("status") == "NOT_RUN":
|
|
541
|
+
outcome = "run_failed"
|
|
542
|
+
else:
|
|
543
|
+
outcome = "run_completed"
|
|
544
|
+
self._project.append_run_event(
|
|
545
|
+
outcome, run_id, name, verdict, request_id,
|
|
546
|
+
findings=findings, log=self._logs.get(run_id, ""),
|
|
547
|
+
)
|
|
548
|
+
return {
|
|
549
|
+
"run_id": run_id,
|
|
550
|
+
"state": "completed",
|
|
551
|
+
"verdict": verdict,
|
|
552
|
+
"operation": name,
|
|
553
|
+
}
|