rocket-engine 0.0.2b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,614 @@
1
+ """Engine ops mixin: Submit-immediate admission paths (CRUD, editors, place-cell, pyexec)."""
2
+
3
+ import hashlib
4
+ import uuid
5
+ from ..execution.console.lifecycle import console_envelope
6
+ from ..interfaces.yaml_dump import dump_yaml_text
7
+ from ..library.errors import LibraryError
8
+ from ..project.errors import ProjectError
9
+
10
+
11
+ #: Wave-3 editor operation -> required argument names.
12
+ _EDITOR_OPS = {
13
+ "apply_transaction": ("changes",),
14
+ "create_shape": ("shape_type", "layer", "data"),
15
+ "create_sheet": ("name",),
16
+ "place_instance": ("sheet", "symbol", "x", "y"),
17
+ "bind_block": ("parent_sheet", "child_sheet", "instance"),
18
+ "create_symbol": ("name", "pins"),
19
+ "create_cell": ("name", "ports"),
20
+ "map_pins": ("symbol", "cell", "pin_map"),
21
+ "ip_register": ("pdk", "name", "version", "symbol", "cell"),
22
+ "ip_instantiate": ("pdk", "name", "sheet", "x", "y"),
23
+ "ip_promote": ("pdk", "name", "version"),
24
+ "bind_layers": ("profile", "layers"),
25
+ "annotate": (),
26
+ "create_testbench": ("name", "kind", "targets"),
27
+ "regenerate_testbench": ("testbench",),
28
+ }
29
+
30
+
31
+ class _SubmitMixin:
32
+ """Submit-immediate admission paths (CRUD, editors, place-cell, pyexec)."""
33
+
34
+ def _submit_crud(self, name, arguments, command):
35
+ """Execute one CRUD/rewind/editor mutation immediately (no queue)."""
36
+ if name in _EDITOR_OPS:
37
+ return self._submit_editor(name, arguments, command)
38
+ command_text = dump_yaml_text(
39
+ {"operation": name, "arguments": arguments}
40
+ )
41
+ request_id = str(command.get("request_id") or uuid.uuid4().hex)
42
+ if name == "create_project":
43
+ if "new_project_id" not in arguments:
44
+ raise ProjectError(
45
+ "invalid_command",
46
+ "create_project needs a new_project_id argument.",
47
+ )
48
+ return self._project.create_project(
49
+ arguments["new_project_id"], request_id, command_text
50
+ )
51
+ if name == "write_doc":
52
+ if "path" not in arguments or "content" not in arguments:
53
+ raise ProjectError(
54
+ "invalid_command",
55
+ "write_doc needs path and content arguments.",
56
+ )
57
+ return self._project.write_doc(
58
+ arguments["path"],
59
+ arguments["content"],
60
+ request_id,
61
+ command_text,
62
+ expected_revision=arguments.get("expected_revision"),
63
+ )
64
+ if name == "delete_doc":
65
+ if "path" not in arguments:
66
+ raise ProjectError(
67
+ "invalid_command", "delete_doc needs a path argument."
68
+ )
69
+ return self._project.delete_doc(
70
+ arguments["path"],
71
+ request_id,
72
+ command_text,
73
+ expected_revision=arguments.get("expected_revision"),
74
+ )
75
+ if name == "rewind":
76
+ if "target_revision" not in arguments:
77
+ raise ProjectError(
78
+ "invalid_command",
79
+ "rewind needs a target_revision argument.",
80
+ )
81
+ return self._project.rewind(
82
+ arguments["target_revision"],
83
+ request_id,
84
+ command_text,
85
+ expected_revision=arguments.get("expected_revision"),
86
+ )
87
+ if name == "write_settings":
88
+ if "kind" not in arguments or "settings" not in arguments:
89
+ raise ProjectError(
90
+ "invalid_command",
91
+ "write_settings needs kind and settings arguments.",
92
+ )
93
+ return self._project.write_settings(
94
+ arguments["kind"],
95
+ arguments["settings"],
96
+ request_id,
97
+ command_text,
98
+ expected_revision=arguments.get("expected_revision"),
99
+ )
100
+ if name == "create_entity":
101
+ return self._project.create_entity(
102
+ request_id,
103
+ command_text,
104
+ kind=arguments.get("kind", ""),
105
+ components=arguments.get("components"),
106
+ expected_revision=arguments.get("expected_revision"),
107
+ )
108
+ if name in ("set_component", "attach_component"):
109
+ if (
110
+ "entity" not in arguments
111
+ or "component" not in arguments
112
+ or "data" not in arguments
113
+ ):
114
+ raise ProjectError(
115
+ "invalid_command",
116
+ f"{name} needs entity, component, and data arguments.",
117
+ )
118
+ method = getattr(self._project, name)
119
+ return method(
120
+ arguments["entity"],
121
+ arguments["component"],
122
+ arguments["data"],
123
+ request_id,
124
+ command_text,
125
+ expected_revision=arguments.get("expected_revision"),
126
+ expected_entity_revision=arguments.get(
127
+ "expected_entity_revision"
128
+ ),
129
+ )
130
+ if name == "detach_component":
131
+ if "entity" not in arguments or "component" not in arguments:
132
+ raise ProjectError(
133
+ "invalid_command",
134
+ "detach_component needs entity and component arguments.",
135
+ )
136
+ return self._project.detach_component(
137
+ arguments["entity"],
138
+ arguments["component"],
139
+ request_id,
140
+ command_text,
141
+ expected_revision=arguments.get("expected_revision"),
142
+ expected_entity_revision=arguments.get(
143
+ "expected_entity_revision"
144
+ ),
145
+ )
146
+ if name == "delete_entity":
147
+ if "entity" not in arguments:
148
+ raise ProjectError(
149
+ "invalid_command",
150
+ "delete_entity needs an entity argument.",
151
+ )
152
+ return self._project.delete_entity(
153
+ arguments["entity"],
154
+ request_id,
155
+ command_text,
156
+ expected_revision=arguments.get("expected_revision"),
157
+ expected_entity_revision=arguments.get(
158
+ "expected_entity_revision"
159
+ ),
160
+ )
161
+ if name == "place_wire":
162
+ for key in ("segments", "layer", "endpoints"):
163
+ if key not in arguments:
164
+ raise ProjectError(
165
+ "invalid_command",
166
+ f"place_wire needs a {key} argument.",
167
+ )
168
+ return self._project.place_wire(
169
+ arguments["segments"],
170
+ arguments["layer"],
171
+ arguments["endpoints"],
172
+ request_id,
173
+ command_text,
174
+ expected_revision=arguments.get("expected_revision"),
175
+ )
176
+ if name == "place_junction":
177
+ for key in ("xy", "layer"):
178
+ if key not in arguments:
179
+ raise ProjectError(
180
+ "invalid_command",
181
+ f"place_junction needs a {key} argument.",
182
+ )
183
+ return self._project.place_junction(
184
+ arguments["xy"],
185
+ arguments["layer"],
186
+ request_id,
187
+ command_text,
188
+ expected_revision=arguments.get("expected_revision"),
189
+ )
190
+ if name == "connect_endpoint":
191
+ for key in ("wire_id", "end", "ref"):
192
+ if key not in arguments:
193
+ raise ProjectError(
194
+ "invalid_command",
195
+ f"connect_endpoint needs a {key} argument.",
196
+ )
197
+ return self._project.connect_endpoint(
198
+ arguments["wire_id"],
199
+ arguments["end"],
200
+ arguments["ref"],
201
+ request_id,
202
+ command_text,
203
+ expected_revision=arguments.get("expected_revision"),
204
+ )
205
+ if name == "move_wire":
206
+ for key in ("wire_id", "dx", "dy"):
207
+ if key not in arguments:
208
+ raise ProjectError(
209
+ "invalid_command",
210
+ f"move_wire needs a {key} argument.",
211
+ )
212
+ return self._project.move_wire(
213
+ arguments["wire_id"],
214
+ arguments["dx"],
215
+ arguments["dy"],
216
+ request_id,
217
+ command_text,
218
+ expected_revision=arguments.get("expected_revision"),
219
+ )
220
+ if name == "split_wire":
221
+ for key in ("wire_id", "at"):
222
+ if key not in arguments:
223
+ raise ProjectError(
224
+ "invalid_command",
225
+ f"split_wire needs a {key} argument.",
226
+ )
227
+ return self._project.split_wire(
228
+ arguments["wire_id"],
229
+ arguments["at"],
230
+ request_id,
231
+ command_text,
232
+ expected_revision=arguments.get("expected_revision"),
233
+ )
234
+ if name == "delete_wire":
235
+ if "wire_id" not in arguments:
236
+ raise ProjectError(
237
+ "invalid_command",
238
+ "delete_wire needs a wire_id argument.",
239
+ )
240
+ return self._project.delete_wire(
241
+ arguments["wire_id"],
242
+ request_id,
243
+ command_text,
244
+ expected_revision=arguments.get("expected_revision"),
245
+ )
246
+ if name == "delete_junction":
247
+ if "junction_id" not in arguments:
248
+ raise ProjectError(
249
+ "invalid_command",
250
+ "delete_junction needs a junction_id argument.",
251
+ )
252
+ return self._project.delete_junction(
253
+ arguments["junction_id"],
254
+ request_id,
255
+ command_text,
256
+ expected_revision=arguments.get("expected_revision"),
257
+ )
258
+ if name == "materialize_nets":
259
+ if "pins" not in arguments:
260
+ raise ProjectError(
261
+ "invalid_command",
262
+ "materialize_nets needs a pins argument.",
263
+ )
264
+ return self._project.materialize_nets(
265
+ arguments["pins"],
266
+ request_id,
267
+ command_text,
268
+ labels=arguments.get("labels"),
269
+ ports=arguments.get("ports"),
270
+ cell=arguments.get("cell", "top"),
271
+ named_refs=arguments.get("named_refs"),
272
+ expected_revision=arguments.get("expected_revision"),
273
+ )
274
+ if "mutations" not in arguments:
275
+ raise ProjectError(
276
+ "invalid_command",
277
+ "apply_mutations needs a mutations argument.",
278
+ )
279
+ return self._project.apply_mutations(
280
+ arguments["mutations"],
281
+ request_id,
282
+ command_text,
283
+ expected_revision=arguments.get("expected_revision"),
284
+ )
285
+
286
+ def _submit_editor(self, name, arguments, command):
287
+ """Execute one Wave-3 editor/testbench op immediately.
288
+
289
+ Required arguments are refused loudly before anything is
290
+ admitted; the ProjectService verb owns revision gating,
291
+ idempotent replay, and I28 pins.
292
+
293
+ Args:
294
+ name: Wave-3 editor operation name.
295
+ arguments: Operation arguments mapping.
296
+ command: Envelope carrying request_id.
297
+
298
+ Returns:
299
+ Mutation mapping for the editor verb.
300
+
301
+ Raises:
302
+ ProjectError: invalid_command for missing arguments.
303
+ """
304
+ missing = [
305
+ key for key in _EDITOR_OPS[name] if key not in arguments
306
+ ]
307
+ if missing:
308
+ raise ProjectError(
309
+ "invalid_command",
310
+ f"{name} needs {', '.join(sorted(missing))}.",
311
+ )
312
+ request_id = str(command.get("request_id") or uuid.uuid4().hex)
313
+ command_text = dump_yaml_text(
314
+ {"operation": name, "arguments": arguments}
315
+ )
316
+ head = arguments.get("expected_revision")
317
+ project = self._project
318
+ if name == "apply_transaction":
319
+ return project.apply_transaction(
320
+ arguments["changes"], request_id, command_text,
321
+ expected_revision=head)
322
+ if name == "create_shape":
323
+ return project.create_shape(
324
+ arguments["shape_type"], arguments["layer"],
325
+ arguments["data"], request_id, command_text,
326
+ units=arguments.get("units", "nm"),
327
+ profile=arguments.get("profile"),
328
+ anchor=arguments.get("anchor"),
329
+ expected_revision=head)
330
+ if name == "create_sheet":
331
+ return project.create_sheet(
332
+ arguments["name"], request_id, command_text,
333
+ title=arguments.get("title", ""),
334
+ expected_revision=head)
335
+ if name == "place_instance":
336
+ return project.place_instance(
337
+ arguments["sheet"], arguments["symbol"],
338
+ arguments["x"], arguments["y"], request_id,
339
+ command_text,
340
+ rotation=arguments.get("rotation", 0),
341
+ mirror=arguments.get("mirror", False),
342
+ properties=arguments.get("properties"),
343
+ ref=arguments.get("ref", ""),
344
+ expected_revision=head)
345
+ if name == "bind_block":
346
+ return project.bind_block(
347
+ arguments["parent_sheet"],
348
+ arguments["child_sheet"], arguments["instance"],
349
+ request_id, command_text,
350
+ expected_revision=head)
351
+ if name == "create_symbol":
352
+ return project.create_symbol(
353
+ arguments["name"], arguments["pins"], request_id,
354
+ command_text, graphics=arguments.get("graphics"),
355
+ properties=arguments.get("properties"),
356
+ expected_revision=head)
357
+ if name == "create_cell":
358
+ return project.create_cell(
359
+ arguments["name"], arguments["ports"], request_id,
360
+ command_text,
361
+ properties=arguments.get("properties"),
362
+ expected_revision=head)
363
+ if name == "map_pins":
364
+ return project.map_pins(
365
+ arguments["symbol"], arguments["cell"],
366
+ arguments["pin_map"], request_id, command_text,
367
+ expected_revision=head)
368
+ if name == "ip_register":
369
+ return project.ip_register(
370
+ arguments["pdk"], arguments["name"],
371
+ arguments["version"], arguments["symbol"],
372
+ arguments["cell"], request_id, command_text,
373
+ pin_map=arguments.get("pin_map"),
374
+ scope=arguments.get("scope", "pdk"),
375
+ reason=arguments.get("reason", ""),
376
+ expected_revision=head)
377
+ if name == "ip_instantiate":
378
+ return project.ip_instantiate(
379
+ arguments["pdk"], arguments["name"],
380
+ arguments["sheet"], arguments["x"],
381
+ arguments["y"], request_id, command_text,
382
+ version=arguments.get("version"),
383
+ rotation=arguments.get("rotation", 0),
384
+ mirror=arguments.get("mirror", False),
385
+ properties=arguments.get("properties"),
386
+ expected_revision=head)
387
+ if name == "ip_promote":
388
+ return project.ip_promote(
389
+ arguments["pdk"], arguments["name"],
390
+ arguments["version"], request_id, command_text,
391
+ expected_revision=head)
392
+ if name == "bind_layers":
393
+ return project.bind_layers(
394
+ arguments["profile"], arguments["layers"],
395
+ request_id, command_text,
396
+ source=arguments.get("source", "profile"),
397
+ version=arguments.get("version", "unknown"),
398
+ expected_revision=head)
399
+ if name == "annotate":
400
+ return project.annotate(
401
+ request_id, command_text,
402
+ prefixes=arguments.get("prefixes"),
403
+ sheet=arguments.get("sheet"),
404
+ expected_revision=head)
405
+ if name == "create_testbench":
406
+ return project.create_testbench(
407
+ arguments["name"], arguments["kind"],
408
+ arguments["targets"], request_id, command_text,
409
+ probes=arguments.get("probes"),
410
+ harness=arguments.get("harness"),
411
+ template=arguments.get("template"),
412
+ expected_revision=head)
413
+ return project.regenerate_testbench(
414
+ arguments["testbench"], request_id, command_text,
415
+ expected_revision=head)
416
+
417
+ def _submit_place_cell(self, arguments, command):
418
+ """Place a catalogue cell immediately (no queue, no solver).
419
+
420
+ Args:
421
+ arguments: place_cell arguments (cell plus the
422
+ optional pdk, instance_id, params, connections,
423
+ path_prefix, and expected_revision).
424
+ command: Envelope carrying request_id.
425
+
426
+ Returns:
427
+ Mutation mapping for the placed instance.
428
+
429
+ Raises:
430
+ ProjectError: invalid_command for a missing cell, or
431
+ the catalogue codes (unknown_cell,
432
+ ambiguous_cell, unknown_param,
433
+ incompatible_port, instance_exists) for a bad
434
+ placement.
435
+ """
436
+ if "cell" not in arguments:
437
+ raise ProjectError(
438
+ "invalid_command", "place_cell needs a cell argument."
439
+ )
440
+ request_id = str(command.get("request_id") or uuid.uuid4().hex)
441
+ try:
442
+ return self._cells.place_cell(
443
+ self._project,
444
+ arguments["cell"],
445
+ request_id,
446
+ expected_revision=arguments.get("expected_revision"),
447
+ pdk=arguments.get("pdk"),
448
+ instance_id=arguments.get("instance_id"),
449
+ params=arguments.get("params"),
450
+ connections=arguments.get("connections"),
451
+ path_prefix=arguments.get("path_prefix", "components"),
452
+ )
453
+ except LibraryError as exc:
454
+ raise ProjectError(exc.code, str(exc)) from exc
455
+
456
+ def apply_batch(self, batch):
457
+ """Land one apply-context batch as a single transaction.
458
+
459
+ The session compiler turns staged sink intents into entity
460
+ changes; they submit through one ``apply_transaction`` on
461
+ the live head, so a console batch is atomic exactly like an
462
+ editor batch. The request id derives from the batch
463
+ envelope, so replaying the same batch is idempotent
464
+ (same receipt, no new revision) while a reused id with
465
+ different arguments conflicts loudly instead of landing
466
+ twice.
467
+
468
+ Args:
469
+ batch: Emitted batch with label and intents.
470
+
471
+ Returns:
472
+ Receipt with the batch label, the new head revision,
473
+ landed kinds, declared-unlanded entries, and the
474
+ request id.
475
+
476
+ Raises:
477
+ ProjectError: invalid_command for a malformed batch
478
+ or an unknown sink kind, revision_conflict for a
479
+ head that moved under the batch, request_conflict
480
+ for a reused id with different arguments.
481
+ """
482
+ from ..execution.pyexec.apply_context import compile_batch
483
+
484
+ label = batch.get("label", "")
485
+ intents = batch.get("intents", ())
486
+ envelope = batch.get("envelope") or dump_yaml_text(
487
+ {"operation": "apply_batch", "arguments": batch}
488
+ )
489
+ compiled = compile_batch(batch)
490
+ request_id = "apply-" + hashlib.sha256(
491
+ envelope.encode("utf-8")).hexdigest()[:32]
492
+ command_text = dump_yaml_text(
493
+ {"operation": "apply_batch",
494
+ "arguments": {"label": label, "intents": intents}}
495
+ )
496
+ head = self._project.snapshot()["current_revision"]
497
+ landed = self._project.apply_transaction(
498
+ compiled["changes"], request_id, command_text,
499
+ expected_revision=head)
500
+ return {
501
+ "ok": True,
502
+ "operation": "apply_batch",
503
+ "project_id": self._project_id,
504
+ "label": label,
505
+ "revision": landed["current_revision"],
506
+ "previous_revision": landed.get("previous_revision"),
507
+ "landed": compiled["landed"],
508
+ "unlanded": compiled["unlanded"],
509
+ "applied": len(compiled["changes"]),
510
+ "request_id": request_id,
511
+ }
512
+
513
+ def _submit_pyexec(self, arguments, command):
514
+ """Admit a python_execute request: gate, then open an approval.
515
+
516
+ Shape validation runs before the revision gate so malformed
517
+ requests fail before anything is persisted or queued; the
518
+ gated submit then advances the revision and appends
519
+ job_queued like every other operation. The ack run_id IS
520
+ the execution id: approval admits it, run_job executes it.
521
+
522
+ Args:
523
+ arguments: python_execute arguments (mode plus lines
524
+ or path, timeout_s, artifacts, expected_revision).
525
+ command: Envelope carrying request_id.
526
+
527
+ Returns:
528
+ Admission mapping with the ack fields plus
529
+ execution_id, approval_id, and pending_approval
530
+ state. No run exists until the UI route approves.
531
+
532
+ Raises:
533
+ ProjectError: revision_conflict for a stale
534
+ expected_revision, request_conflict for a reused
535
+ envelope id with new text.
536
+ ExecutionError: invalid_command/invalid_cell/empty_cell
537
+ for malformed requests, path_escape/invalid_path/
538
+ cell_unreadable for bad project files.
539
+ """
540
+ self._pyexec.validate_request(
541
+ arguments.get("mode"),
542
+ lines=arguments.get("lines"),
543
+ path=arguments.get("path"),
544
+ artifacts=arguments.get("artifacts", ()),
545
+ timeout_s=arguments.get("timeout_s"),
546
+ )
547
+ command_text = dump_yaml_text(
548
+ {"operation": "python_execute", "arguments": arguments})
549
+ request_id = command.get("request_id") or uuid.uuid4().hex
550
+ ack = self._project.submit(
551
+ command_text,
552
+ str(request_id),
553
+ expected_revision=arguments.get("expected_revision"),
554
+ )
555
+ handle = self._pyexec.request_execution(
556
+ arguments["mode"],
557
+ lines=arguments.get("lines"),
558
+ path=arguments.get("path"),
559
+ request_id=str(request_id),
560
+ run_id=ack["run_id"],
561
+ expected_revision=arguments.get("expected_revision"),
562
+ artifacts=arguments.get("artifacts", ()),
563
+ timeout_s=arguments.get("timeout_s"),
564
+ )
565
+ self._run_jobs.setdefault(
566
+ ack["run_id"],
567
+ {
568
+ "operation": "python_execute",
569
+ "arguments": dict(arguments),
570
+ "snapshot_id": ack["snapshot_id"],
571
+ "request_id": str(request_id),
572
+ },
573
+ )
574
+ return {**ack, **handle, "state": "pending_approval"}
575
+
576
+ def _submit_console(self, name, arguments, command):
577
+ """Admit one console lifecycle op on the shared envelope.
578
+
579
+ Identity comes from the rendered console envelope, so
580
+ console history replays as command scripts by
581
+ construction. Consoles are Engine-memory and
582
+ workspace-bound — no ledger admission, no revision gate.
583
+ ExecutionError from the lifecycle (unknown_console)
584
+ passes through unwrapped, like python_execute refusals.
585
+
586
+ Args:
587
+ name: python_open or python_reset.
588
+ arguments: Lifecycle arguments (workspace_root for
589
+ open, console_id for reset).
590
+ command: Envelope carrying request_id.
591
+
592
+ Returns:
593
+ Opening (open) or fresh generation (reset) plus
594
+ the request_id.
595
+
596
+ Raises:
597
+ ProjectError: invalid_command for an unknown console
598
+ operation name.
599
+ """
600
+ if name not in ("python_open", "python_reset"):
601
+ raise ProjectError(
602
+ "invalid_command",
603
+ f"Not a console lifecycle operation: {name!r}.",
604
+ )
605
+ command_text = console_envelope(name, dict(arguments))
606
+ request_id = str(command.get("request_id") or uuid.uuid4().hex)
607
+ if name == "python_open":
608
+ out = self.open_console(
609
+ workspace_root=arguments.get("workspace_root"))
610
+ else:
611
+ out = self.reset_console(
612
+ console_id=arguments.get("console_id"))
613
+ return {**out, "request_id": request_id,
614
+ "command_text": command_text}
@@ -0,0 +1,27 @@
1
+ # project — revisioned project state and event ledger
2
+
3
+ `ProjectService` owns engine-mediated project state: documents, the project revision, idempotent mutation requests, snapshots, and the ordered event ledger. It also exposes the component/entity queries used by the engine's ECS-shaped design model.
4
+
5
+ ## State and persistence
6
+
7
+ - Every mutation checks `expected_revision`. A stale head returns `revision_conflict`; changes are never merged silently.
8
+ - Accepted mutations advance the project revision and append an actor-attributed event. Reusing a `request_id` with the same command replays its stored result; reusing it with different command text is refused.
9
+ - With `engine_root` configured, project ledger files, events, managed documents, and rewind snapshots persist beneath `projects/<project_id>/`. Without an engine root, `ProjectService` keeps them in memory.
10
+ - Event reads are cursor-based and bounded by retention. A reader that requires missing history receives `resync_required` and reloads the snapshot.
11
+ - `rewind` restores engine-managed bytes under a new revision; it does not rewrite history or overwrite caller-owned files in a linked project.
12
+ - `ledger.yaml`, `events.yaml`, `runs/`, and `snapshots/` are engine-owned paths and cannot be written through document CRUD.
13
+
14
+ ## Design entities
15
+
16
+ Entities and components are projected through the project document store. IDs are engine-minted; component changes use the same project mutation/event path. The service also handles settings and wire/junction operations through the corresponding interface stores. `get_world_snapshot`, `get_component`, `components_of`, and `entities_with` provide read surfaces for clients.
17
+
18
+ ## Boundaries
19
+
20
+ `ProjectService` owns project revisions, documents, snapshots, idempotency, and event ordering. Solver output parsing and verdicts belong to [`execution/`](../execution/README.md); transport framing belongs to [`adapters/`](../adapters/README.md). `Engine` in [`composition.py`](../composition.py) routes operations into this service.
21
+
22
+ ## Routing
23
+
24
+ - Contract shapes and path rules → [`interfaces/`](../interfaces/README.md).
25
+ - Run admission, results, and verdicts → [`execution/`](../execution/README.md).
26
+ - External behavior → [generated engine API](../../docs/engine-api.md) and [storage policy](../../docs/storage-policy.md).
27
+ - Product semantics → [PRD tooling spec](../../../PRD/tools-research/frontend-tooling-spec.md).
@@ -0,0 +1,6 @@
1
+ """Revisions, snapshots, idempotent request IDs, ordered events, cursors. Owns state."""
2
+
3
+ from .errors import ProjectError
4
+ from .service import ProjectService
5
+
6
+ __all__ = ["ProjectError", "ProjectService"]