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,318 @@
|
|
|
1
|
+
"""Approval-bound console cells on the atomic apply-context.
|
|
2
|
+
|
|
3
|
+
CONSOLE-SESSION-MIGRATE (ADR-016 follow-up): the old in-memory
|
|
4
|
+
`ConsoleSession` (approved code plus history cursor, environment
|
|
5
|
+
digest, and revision binding, with reset killing every outstanding
|
|
6
|
+
approval) now rides `ApplySession` instead of standing beside it.
|
|
7
|
+
One object owns both halves: the approval binding admits one
|
|
8
|
+
worker run, and any worker-staged intents the run leaves behind
|
|
9
|
+
land as exactly one atomic batch through `Engine.apply_batch` —
|
|
10
|
+
the same path the editor surface uses, so console writes are
|
|
11
|
+
never a side-channel writer.
|
|
12
|
+
|
|
13
|
+
Identity follows ADR-016: one live instance per (console,
|
|
14
|
+
generation). `reset` retires the apply side (dropping buffered
|
|
15
|
+
and staged intents), rotates the generation, kills every
|
|
16
|
+
outstanding approval, and rearms the context for the next
|
|
17
|
+
generation. `retire` from `ApplySession` is permanent and is
|
|
18
|
+
only used inside `reset`, never by callers.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import uuid
|
|
22
|
+
|
|
23
|
+
from ..errors import ExecutionError
|
|
24
|
+
from ..pyexec.apply_context import ApplySession
|
|
25
|
+
|
|
26
|
+
CONTRACT_VERSION = "rocketeda/tooling/v0.1"
|
|
27
|
+
|
|
28
|
+
_HELP = {
|
|
29
|
+
"run_drc": {
|
|
30
|
+
"signature": "run_drc(target, deck, profile?)",
|
|
31
|
+
"example": "op = run_drc(target='amplifier', deck='sky130A/decks.drc')",
|
|
32
|
+
},
|
|
33
|
+
"run_spice": {
|
|
34
|
+
"signature": "run_spice(netlist, benches, corners?)",
|
|
35
|
+
"example": "op = run_spice(netlist='amp_tb', benches=['ac', 'tran'])",
|
|
36
|
+
},
|
|
37
|
+
"python_execute": {
|
|
38
|
+
"signature": "python_execute(code_ref, profile)",
|
|
39
|
+
"example": "cell = python_execute(code_ref='analysis/fit.py', profile='design')",
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_help(topic):
|
|
45
|
+
"""Return versioned help metadata. Pure data — executes nothing.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
topic: Help topic name (run_drc, run_spice, python_execute).
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Help entry with topic, contract version, signature, and
|
|
52
|
+
example.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ExecutionError: unknown_topic for an unlisted topic.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
entry = _HELP[topic]
|
|
59
|
+
except KeyError:
|
|
60
|
+
raise ExecutionError("unknown_topic", f"Unknown help topic: {topic}.") from None
|
|
61
|
+
return {"topic": topic, "version": CONTRACT_VERSION, **entry}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ConsoleSession(ApplySession):
|
|
65
|
+
"""Approval-bound console cells on one atomic apply-context.
|
|
66
|
+
|
|
67
|
+
The approval half pins one cell plus its context (full worker
|
|
68
|
+
source, history cursor, environment digest, expected revision);
|
|
69
|
+
admitting re-checks every field and any drift fails instead
|
|
70
|
+
of running a stranger's cell. The apply half stages worker
|
|
71
|
+
intent lines and emits one batch per outermost clean exit;
|
|
72
|
+
`land_worker_intents` drains a worker ops log and lands the
|
|
73
|
+
emitted batch through `Engine.apply_batch`.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
project_id: Project this session approves cells for.
|
|
77
|
+
actor: Actor recorded on approvals.
|
|
78
|
+
console_id: Console owning this context; minted when
|
|
79
|
+
omitted.
|
|
80
|
+
pdk_name: PDK auto-activated at first block entry.
|
|
81
|
+
activate_pdk: Callable mapping the PDK name to proof;
|
|
82
|
+
a no-op activator when omitted.
|
|
83
|
+
resolve_layer: Callable mapping a layer spec to its lid;
|
|
84
|
+
the identity mapping when omitted.
|
|
85
|
+
emit: Callable receiving each outermost-exit batch;
|
|
86
|
+
batches accumulate on `emitted` when omitted.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, project_id, actor, console_id=None,
|
|
90
|
+
pdk_name="sky130", activate_pdk=None,
|
|
91
|
+
resolve_layer=None, emit=None):
|
|
92
|
+
self._actor = actor
|
|
93
|
+
self._approvals = {}
|
|
94
|
+
self._cursor = 0
|
|
95
|
+
self._emitted = []
|
|
96
|
+
super().__init__(
|
|
97
|
+
emit=emit if emit is not None else self._emitted.append,
|
|
98
|
+
project_id=project_id,
|
|
99
|
+
console_id=console_id or uuid.uuid4().hex,
|
|
100
|
+
generation=uuid.uuid4().hex,
|
|
101
|
+
pdk_name=pdk_name,
|
|
102
|
+
activate_pdk=(activate_pdk if activate_pdk is not None
|
|
103
|
+
else (lambda name: {"pdk": name,
|
|
104
|
+
"activated": True})),
|
|
105
|
+
resolve_layer=(resolve_layer if resolve_layer is not None
|
|
106
|
+
else (lambda spec: spec)),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def console_id(self):
|
|
111
|
+
"""Console owning this context (fixed for its lifetime).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Console id string.
|
|
115
|
+
"""
|
|
116
|
+
return self._console_id
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def generation(self):
|
|
120
|
+
"""Current approval generation; reset retires it.
|
|
121
|
+
|
|
122
|
+
Reads and writes the generation the apply-context
|
|
123
|
+
stamps on every emitted batch, so approval fencing and
|
|
124
|
+
batch identity can never disagree.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Generation id string.
|
|
128
|
+
"""
|
|
129
|
+
return self._generation
|
|
130
|
+
|
|
131
|
+
@generation.setter
|
|
132
|
+
def generation(self, value):
|
|
133
|
+
"""Sync the generation (for example, after a restart reload).
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
value: Generation id to fence on from here.
|
|
137
|
+
"""
|
|
138
|
+
self._generation = value
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def emitted(self):
|
|
142
|
+
"""Batches emitted so far, oldest first (evidence only).
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Emitted batch mappings in emission order.
|
|
146
|
+
"""
|
|
147
|
+
return list(self._emitted)
|
|
148
|
+
|
|
149
|
+
def request_cell_approval(self, code, history_cursor, env_digest,
|
|
150
|
+
expected_revision):
|
|
151
|
+
"""Bind a pending approval to one cell plus its context.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
code: Full worker source the approval pins.
|
|
155
|
+
history_cursor: History position the approval pins.
|
|
156
|
+
env_digest: Environment digest the approval pins.
|
|
157
|
+
expected_revision: Revision the approval pins.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Approval record with approval_id and pending state.
|
|
161
|
+
"""
|
|
162
|
+
approval_id = uuid.uuid4().hex
|
|
163
|
+
self._approvals[approval_id] = {
|
|
164
|
+
"approval_id": approval_id,
|
|
165
|
+
"code": code,
|
|
166
|
+
"history_cursor": history_cursor,
|
|
167
|
+
"env_digest": env_digest,
|
|
168
|
+
"expected_revision": expected_revision,
|
|
169
|
+
"state": "pending_approval",
|
|
170
|
+
}
|
|
171
|
+
return {"approval_id": approval_id, "state": "pending_approval"}
|
|
172
|
+
|
|
173
|
+
def _binding(self, approval_id):
|
|
174
|
+
"""Return the live binding for one approval.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
approval_id: Approval bound by request_cell_approval.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Live approval record.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
ExecutionError: approval_invalid for an unknown or
|
|
184
|
+
expired approval.
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
return self._approvals[approval_id]
|
|
188
|
+
except KeyError:
|
|
189
|
+
raise ExecutionError(
|
|
190
|
+
"approval_invalid", "Unknown or expired approval."
|
|
191
|
+
) from None
|
|
192
|
+
|
|
193
|
+
def admit_cell(self, approval_id, code, history_cursor, env_digest,
|
|
194
|
+
expected_revision):
|
|
195
|
+
"""Admit a cell whose context still matches its binding.
|
|
196
|
+
|
|
197
|
+
Any drift in code, cursor, env, or revision fails instead
|
|
198
|
+
of running a stranger's cell. This is the seam the worker
|
|
199
|
+
lane (`CellRunner.run_cell`) admits through.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
approval_id: Approval bound by request_cell_approval.
|
|
203
|
+
code: Full worker source to re-check against the binding.
|
|
204
|
+
history_cursor: History position to re-check.
|
|
205
|
+
env_digest: Environment digest to re-check.
|
|
206
|
+
expected_revision: Revision to re-check.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Approval record with approval_id and approved state.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ExecutionError: approval_invalid for an unknown approval
|
|
213
|
+
or any binding drift.
|
|
214
|
+
"""
|
|
215
|
+
binding = self._binding(approval_id)
|
|
216
|
+
if (
|
|
217
|
+
binding["code"] != code
|
|
218
|
+
or binding["history_cursor"] != history_cursor
|
|
219
|
+
or binding["env_digest"] != env_digest
|
|
220
|
+
or binding["expected_revision"] != expected_revision
|
|
221
|
+
):
|
|
222
|
+
raise ExecutionError(
|
|
223
|
+
"approval_invalid", "Approval no longer matches its binding."
|
|
224
|
+
)
|
|
225
|
+
binding["state"] = "approved"
|
|
226
|
+
return {"approval_id": approval_id, "state": "approved"}
|
|
227
|
+
|
|
228
|
+
def execute_cell(self, approval_id, code):
|
|
229
|
+
"""Record a cell execution without running code.
|
|
230
|
+
|
|
231
|
+
The worker run itself happens in the worker lane (admitted
|
|
232
|
+
through `admit_cell`); landing worker-staged intents is
|
|
233
|
+
`land_worker_intents`, not this method.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
approval_id: Approval whose code must match.
|
|
237
|
+
code: Cell code to re-check against the binding.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Record with the advanced cursor and recorded state.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
ExecutionError: approval_invalid for an unknown approval
|
|
244
|
+
or code that differs from the approved code.
|
|
245
|
+
"""
|
|
246
|
+
binding = self._binding(approval_id)
|
|
247
|
+
if binding["code"] != code:
|
|
248
|
+
raise ExecutionError(
|
|
249
|
+
"approval_invalid", "Cell code differs from the approved code."
|
|
250
|
+
)
|
|
251
|
+
self._cursor += 1
|
|
252
|
+
return {"cursor": self._cursor, "state": "recorded"}
|
|
253
|
+
|
|
254
|
+
def land_worker_intents(self, engine, ops_log_path, label):
|
|
255
|
+
"""Drain worker intent lines and land one atomic batch.
|
|
256
|
+
|
|
257
|
+
Lines shaped ``{"apply_intent": {...}}`` in the worker ops
|
|
258
|
+
log join the session; anything else in the log is not ours
|
|
259
|
+
and stays unread. The staged intents emit as one batch at
|
|
260
|
+
the outermost exit and land through
|
|
261
|
+
`Engine.apply_batch` — one transaction, one revision.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
engine: Engine landing the batch; when it has no
|
|
265
|
+
`apply_batch`, nothing is collected and None
|
|
266
|
+
returns.
|
|
267
|
+
ops_log_path: Worker ops-log path to drain.
|
|
268
|
+
label: Batch label carried on the envelope.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Landing with ok True, the engine receipt, and the
|
|
272
|
+
intent count — or None when nothing was staged
|
|
273
|
+
or the engine cannot land.
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
ProjectError: retired_session on a retired session;
|
|
277
|
+
invalid_command for an unknown sink kind;
|
|
278
|
+
revision_conflict when the head moved under the
|
|
279
|
+
batch.
|
|
280
|
+
"""
|
|
281
|
+
land = getattr(engine, "apply_batch", None)
|
|
282
|
+
if land is None:
|
|
283
|
+
return None
|
|
284
|
+
staged = self.collect_ops(ops_log_path)
|
|
285
|
+
if staged == 0 and not self._staged:
|
|
286
|
+
return None
|
|
287
|
+
with self.edit(label):
|
|
288
|
+
pass
|
|
289
|
+
batch = self.last_batch
|
|
290
|
+
if batch is None:
|
|
291
|
+
return None
|
|
292
|
+
receipt = land(batch)
|
|
293
|
+
return {"ok": True, "receipt": receipt,
|
|
294
|
+
"intents": len(batch["intents"]), "label": batch["label"]}
|
|
295
|
+
|
|
296
|
+
def reset(self):
|
|
297
|
+
"""Retire the generation and kill every outstanding approval.
|
|
298
|
+
|
|
299
|
+
The apply side retires too (buffered and staged intents
|
|
300
|
+
drop), then the context rearms under a fresh generation:
|
|
301
|
+
in-flight blocks must never span a reset — blocks are
|
|
302
|
+
synchronous, so a reset lands between cells, never inside
|
|
303
|
+
one.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Mapping with the fresh generation id.
|
|
307
|
+
"""
|
|
308
|
+
self.retire()
|
|
309
|
+
self._generation = uuid.uuid4().hex
|
|
310
|
+
self._approvals = {}
|
|
311
|
+
self._cursor = 0
|
|
312
|
+
self._retired = False
|
|
313
|
+
self._depth = 0
|
|
314
|
+
self._buffer = []
|
|
315
|
+
self._span = []
|
|
316
|
+
self._staged = []
|
|
317
|
+
self._pdk_active = False
|
|
318
|
+
return {"generation": self._generation}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Console lifecycle: open/status/reset/close on the shared envelope.
|
|
2
|
+
|
|
3
|
+
Identity plus generation, per the BWRAP-STUDY ruling: the console
|
|
4
|
+
is a plain python console on trusted-local user permissions, every
|
|
5
|
+
cell runs in a confined worker, and where bwrap is unprovable the
|
|
6
|
+
surface still records generations and approvals but admits
|
|
7
|
+
nothing. This module owns the recording half only — identity,
|
|
8
|
+
generation rotation, workspace binding — and never spawns a
|
|
9
|
+
worker, never touches the runner, and never changes sandbox
|
|
10
|
+
behavior.
|
|
11
|
+
|
|
12
|
+
Scoping decision (CONSOLE-LIFECYCLE): `stdio_console` keeps its
|
|
13
|
+
local-panel REPL protocol (`submit`/`history`/`reset` over stdio);
|
|
14
|
+
the Engine-side lifecycle operations (`python_open`,
|
|
15
|
+
`python_execute`, `python_reset`) travel as shared-envelope
|
|
16
|
+
command texts rendered here, so the adapt lane wires the three
|
|
17
|
+
operations through `console_envelope`/`parse_console_envelope`
|
|
18
|
+
without inventing a second envelope shape. The operations
|
|
19
|
+
themselves are vocabulary/composition wiring (adapt lane); this
|
|
20
|
+
module pins the envelope contract they must speak.
|
|
21
|
+
|
|
22
|
+
Seam: `session_for` exposes the underlying `ConsoleSession` to the
|
|
23
|
+
worker lane (`CellRunner.admit_cell`) and to tests; Engine wiring
|
|
24
|
+
calls `open_console`/`reset_console` and never reaches past them.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import uuid
|
|
28
|
+
|
|
29
|
+
from ruamel.yaml import YAML
|
|
30
|
+
|
|
31
|
+
from ..errors import ExecutionError
|
|
32
|
+
from ..routing.calls import admission_envelope
|
|
33
|
+
from .apply_session import ConsoleSession
|
|
34
|
+
|
|
35
|
+
#: Lifecycle operations converging on the shared envelope. The
|
|
36
|
+
#: Engine does not wire them yet (adapt lane); the envelope shape
|
|
37
|
+
#: here is the contract that wiring must speak.
|
|
38
|
+
CONSOLE_OPERATIONS = frozenset(
|
|
39
|
+
{"python_open", "python_execute", "python_reset"}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
_yaml = YAML(typ="safe")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def console_envelope(operation, arguments):
|
|
46
|
+
"""Render one console lifecycle command on the shared envelope.
|
|
47
|
+
|
|
48
|
+
The text uses the identical YAML dumper the Engine uses for
|
|
49
|
+
every admission, so equal calls admit with equal identity and
|
|
50
|
+
console history replays as command scripts by construction.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
operation: Console lifecycle operation (python_open,
|
|
54
|
+
python_execute, python_reset).
|
|
55
|
+
arguments: Operation arguments mapping.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Envelope command text for the call.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ExecutionError: invalid_command for an operation outside
|
|
62
|
+
the console lifecycle set or unserializable arguments.
|
|
63
|
+
"""
|
|
64
|
+
if operation not in CONSOLE_OPERATIONS:
|
|
65
|
+
raise ExecutionError(
|
|
66
|
+
"invalid_command",
|
|
67
|
+
f"Not a console lifecycle operation: {operation!r}.",
|
|
68
|
+
)
|
|
69
|
+
if not isinstance(arguments, dict):
|
|
70
|
+
raise ExecutionError(
|
|
71
|
+
"invalid_command",
|
|
72
|
+
f"Envelope needs mapping arguments, got {arguments!r}.",
|
|
73
|
+
)
|
|
74
|
+
try:
|
|
75
|
+
return admission_envelope(operation, arguments)
|
|
76
|
+
except ExecutionError as exc:
|
|
77
|
+
raise ExecutionError("invalid_command", str(exc)) from exc
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_console_envelope(text):
|
|
81
|
+
"""Parse one console envelope back to operation + arguments.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
text: Envelope command text from `console_envelope`.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Mapping with operation and arguments.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ExecutionError: invalid_command for unparsable text, a
|
|
91
|
+
non-mapping envelope, or an operation outside the
|
|
92
|
+
console lifecycle set.
|
|
93
|
+
"""
|
|
94
|
+
if not isinstance(text, str) or not text.strip():
|
|
95
|
+
raise ExecutionError(
|
|
96
|
+
"invalid_command", "Console envelope must be text."
|
|
97
|
+
)
|
|
98
|
+
try:
|
|
99
|
+
document = _yaml.load(text)
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
raise ExecutionError(
|
|
102
|
+
"invalid_command",
|
|
103
|
+
f"Console envelope does not parse: {exc}.",
|
|
104
|
+
) from exc
|
|
105
|
+
if not isinstance(document, dict):
|
|
106
|
+
raise ExecutionError(
|
|
107
|
+
"invalid_command",
|
|
108
|
+
f"Console envelope must be a mapping, got {document!r}.",
|
|
109
|
+
)
|
|
110
|
+
operation = document.get("operation")
|
|
111
|
+
arguments = document.get("arguments")
|
|
112
|
+
if operation not in CONSOLE_OPERATIONS or not isinstance(
|
|
113
|
+
arguments, dict
|
|
114
|
+
):
|
|
115
|
+
raise ExecutionError(
|
|
116
|
+
"invalid_command",
|
|
117
|
+
f"Not a console lifecycle envelope: {document!r}.",
|
|
118
|
+
)
|
|
119
|
+
return {"operation": operation, "arguments": dict(arguments)}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ConsoleLifecycle:
|
|
123
|
+
"""Console identity + generation registry (lifecycle only).
|
|
124
|
+
|
|
125
|
+
`open_console` mints one console per call — or reuses the live
|
|
126
|
+
handle when the same workspace root opens again (single
|
|
127
|
+
console per panel). `reset_console` retires the generation and
|
|
128
|
+
kills every outstanding approval through the session; live
|
|
129
|
+
workers are cancel territory, never reset. Nothing here
|
|
130
|
+
executes: admission and execution stay in the worker lane.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
None.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self._consoles = {}
|
|
138
|
+
self._roots = {}
|
|
139
|
+
|
|
140
|
+
def open_console(self, project_id, actor, workspace_root=None):
|
|
141
|
+
"""Open a console, reusing the live one for its workspace.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
project_id: Project the console approves cells for.
|
|
145
|
+
actor: Actor recorded on approvals.
|
|
146
|
+
workspace_root: Panel workspace binding; one live
|
|
147
|
+
handle per root, unbound when omitted.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Opening with console_id, project_id, generation, and
|
|
151
|
+
the workspace_root binding (None when unbound).
|
|
152
|
+
"""
|
|
153
|
+
if workspace_root is not None:
|
|
154
|
+
live = self._roots.get(str(workspace_root))
|
|
155
|
+
if live is not None and live in self._consoles:
|
|
156
|
+
record = self._consoles[live]
|
|
157
|
+
return self._describe(record)
|
|
158
|
+
console_id = uuid.uuid4().hex
|
|
159
|
+
session = ConsoleSession(project_id, actor)
|
|
160
|
+
record = {
|
|
161
|
+
"console_id": console_id,
|
|
162
|
+
"project_id": project_id,
|
|
163
|
+
"actor": actor,
|
|
164
|
+
"workspace_root": (
|
|
165
|
+
None if workspace_root is None
|
|
166
|
+
else str(workspace_root)
|
|
167
|
+
),
|
|
168
|
+
"session": session,
|
|
169
|
+
}
|
|
170
|
+
self._consoles[console_id] = record
|
|
171
|
+
if record["workspace_root"] is not None:
|
|
172
|
+
self._roots[record["workspace_root"]] = console_id
|
|
173
|
+
return self._describe(record)
|
|
174
|
+
|
|
175
|
+
def status(self, console_id):
|
|
176
|
+
"""Read one console's lifecycle state (never executes).
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
console_id: Console from `open_console`.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Status with console_id, project_id, generation, and
|
|
183
|
+
the workspace_root binding.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ExecutionError: unknown_console for an id never
|
|
187
|
+
opened or already closed.
|
|
188
|
+
"""
|
|
189
|
+
return self._describe(self._record(console_id))
|
|
190
|
+
|
|
191
|
+
def session_for(self, console_id):
|
|
192
|
+
"""Return the live session for worker-lane admission.
|
|
193
|
+
|
|
194
|
+
Worker-lane seam: `CellRunner.admit_cell` binds through
|
|
195
|
+
the session; Engine wiring never reaches past
|
|
196
|
+
`open_console`/`reset_console`.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
console_id: Console from `open_console`.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Live `ConsoleSession` for the console.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ExecutionError: unknown_console for an id never
|
|
206
|
+
opened or already closed.
|
|
207
|
+
"""
|
|
208
|
+
return self._record(console_id)["session"]
|
|
209
|
+
|
|
210
|
+
def reset_console(self, console_id):
|
|
211
|
+
"""Retire the generation; kill every outstanding approval.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
console_id: Console from `open_console`.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Mapping with the console_id and the fresh generation.
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
ExecutionError: unknown_console for an id never
|
|
221
|
+
opened or already closed.
|
|
222
|
+
"""
|
|
223
|
+
record = self._record(console_id)
|
|
224
|
+
outcome = record["session"].reset()
|
|
225
|
+
return {
|
|
226
|
+
"console_id": console_id,
|
|
227
|
+
"generation": outcome["generation"],
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
def close_console(self, console_id):
|
|
231
|
+
"""Close a console and release its workspace binding.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
console_id: Console from `open_console`.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Closing with console_id and closed True.
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
ExecutionError: unknown_console for an id never
|
|
241
|
+
opened or already closed.
|
|
242
|
+
"""
|
|
243
|
+
record = self._record(console_id)
|
|
244
|
+
del self._consoles[console_id]
|
|
245
|
+
root = record["workspace_root"]
|
|
246
|
+
if root is not None and self._roots.get(root) == console_id:
|
|
247
|
+
del self._roots[root]
|
|
248
|
+
return {"console_id": console_id, "closed": True}
|
|
249
|
+
|
|
250
|
+
def _record(self, console_id):
|
|
251
|
+
try:
|
|
252
|
+
return self._consoles[console_id]
|
|
253
|
+
except KeyError:
|
|
254
|
+
raise ExecutionError(
|
|
255
|
+
"unknown_console", f"Unknown console: {console_id!r}."
|
|
256
|
+
) from None
|
|
257
|
+
|
|
258
|
+
@staticmethod
|
|
259
|
+
def _describe(record):
|
|
260
|
+
return {
|
|
261
|
+
"console_id": record["console_id"],
|
|
262
|
+
"project_id": record["project_id"],
|
|
263
|
+
"generation": record["session"].generation,
|
|
264
|
+
"workspace_root": record["workspace_root"],
|
|
265
|
+
}
|