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,552 @@
1
+ """Wire/junction ECS seam and wire-mutation admission."""
2
+
3
+ import copy
4
+
5
+ from ...interfaces.errors import ContractError
6
+ from ..errors import ProjectError
7
+
8
+ from .shared import _advance_revision
9
+
10
+ class _WireEntityStore:
11
+ """WIRE-PLACE seam over the ENGINE-ECS ProjectService verbs.
12
+
13
+ The wire ops take a store serving the seam in
14
+ docs/work/WIRE-PLACE/report.md: reads return deep copies and
15
+ raise LookupError for missing entities/components, and
16
+ transact threads aliases through one call list. ENGINE-ECS
17
+ landed as per-verb service methods with admission envelopes
18
+ instead of a bound store, so this adapter binds those verbs
19
+ to the seam: reads delegate with unknown/invalid entity and
20
+ component codes mapped to LookupError, and transact runs its
21
+ calls sequentially with alias resolution, one ledger
22
+ revision per verb.
23
+
24
+ Inner envelopes derive deterministically from the outer wire
25
+ request id, so a retried envelope replays the verbs that
26
+ already landed instead of minting duplicates. Sequential, not
27
+ atomic: a mid-transact refusal leaves the earlier verbs
28
+ applied (wire ops pre-validate, so a refusal after the first
29
+ verb implies a concurrent writer, and the outer revision gate
30
+ already serialized this op against those).
31
+ """
32
+
33
+ _READS = ("components_of", "get_component", "entities_with")
34
+ _WRITES = (
35
+ "create_entity", "set_component", "attach_component",
36
+ "detach_component", "delete_entity",
37
+ )
38
+ _LOOKUP_CODES = frozenset({
39
+ "unknown_entity", "unknown_component",
40
+ "invalid_entity", "invalid_component",
41
+ })
42
+
43
+ def __init__(self, service, request_id):
44
+ self._service = service
45
+ self._request_id = request_id
46
+ self._seq = 0
47
+
48
+ def _envelope(self, verb):
49
+ self._seq += 1
50
+ sub_id = f"{self._request_id}:wire-ecs:{self._seq}"
51
+ return sub_id, f"{verb} {sub_id}"
52
+
53
+ def _lookup(self, entity, component=None):
54
+ if component is None:
55
+ return LookupError(f"Unknown entity: {entity!r}.")
56
+ return LookupError(
57
+ f"Unknown component {component!r} on {entity!r}."
58
+ )
59
+
60
+ def components_of(self, entity):
61
+ """Return a deep copy of one entity's component mapping.
62
+
63
+ Args:
64
+ entity: Entity id to inspect.
65
+
66
+ Returns:
67
+ Mapping of component name to payload copy.
68
+
69
+ Raises:
70
+ LookupError: Unknown or malformed entity id.
71
+ """
72
+ try:
73
+ names = self._service.components_of(entity)["components"]
74
+ except ProjectError as exc:
75
+ if exc.code in self._LOOKUP_CODES:
76
+ raise self._lookup(entity) from None
77
+ raise
78
+ return {
79
+ name: self.get_component(entity, name) for name in names
80
+ }
81
+
82
+ def get_component(self, entity, component):
83
+ """Return a deep copy of one component payload.
84
+
85
+ Args:
86
+ entity: Entity id holding the component.
87
+ component: Component name to read.
88
+
89
+ Returns:
90
+ Deep copy of the component payload.
91
+
92
+ Raises:
93
+ LookupError: Unknown or malformed entity id or
94
+ component name.
95
+ """
96
+ try:
97
+ data = self._service.get_component(
98
+ entity, component
99
+ )["data"]
100
+ except ProjectError as exc:
101
+ if exc.code in self._LOOKUP_CODES:
102
+ raise self._lookup(entity, component) from None
103
+ raise
104
+ return copy.deepcopy(data)
105
+
106
+ def entities_with(self, component, predicate=None):
107
+ """List holder ids in id order, filtered by a callable.
108
+
109
+ Args:
110
+ component: Component name holders must carry.
111
+ predicate: Callable over the payload copy, or None
112
+ for no filtering.
113
+
114
+ Returns:
115
+ Sorted holder entity ids.
116
+ """
117
+ found = []
118
+ for entity in self._service.entities_with(
119
+ component
120
+ )["entities"]:
121
+ if predicate is None:
122
+ found.append(entity)
123
+ elif predicate(self.get_component(entity, component)):
124
+ found.append(entity)
125
+ return found
126
+
127
+ def create_entity(self, actor, description):
128
+ """Mint one entity; return its id and the new head.
129
+
130
+ Args:
131
+ actor: Attribution (the service actor applies).
132
+ description: Human note (carried on inner envelopes).
133
+
134
+ Returns:
135
+ Mapping with the minted id and the new head revision.
136
+ """
137
+ del actor, description
138
+ request_id, command_text = self._envelope("create_entity")
139
+ response = self._service.create_entity(
140
+ request_id, command_text,
141
+ expected_revision=self._service._current_revision,
142
+ )
143
+ return {
144
+ "id": response["entity"],
145
+ "revision": response["current_revision"],
146
+ }
147
+
148
+ def set_component(self, actor, entity, component, value,
149
+ description):
150
+ """Overwrite one component; refuse a missing target.
151
+
152
+ Args:
153
+ actor: Attribution (the service actor applies).
154
+ entity: Entity id holding the component.
155
+ component: Component name to overwrite.
156
+ value: JSON payload to store.
157
+ description: Human note (carried on inner envelopes).
158
+
159
+ Returns:
160
+ Mapping with the new head revision.
161
+
162
+ Raises:
163
+ ProjectError: unknown_entity or unknown_component
164
+ for a missing target.
165
+ """
166
+ del actor, description
167
+ try:
168
+ names = self._service.components_of(entity)["components"]
169
+ except ProjectError as exc:
170
+ if exc.code in self._LOOKUP_CODES:
171
+ raise ProjectError(
172
+ "unknown_entity",
173
+ f"Unknown entity: {entity!r}.",
174
+ ) from None
175
+ raise
176
+ if component not in names:
177
+ raise ProjectError(
178
+ "unknown_component",
179
+ f"Unknown component {component!r} on {entity!r}.",
180
+ )
181
+ request_id, command_text = self._envelope("set_component")
182
+ response = self._service.set_component(
183
+ entity, component, value, request_id, command_text,
184
+ expected_revision=self._service._current_revision,
185
+ )
186
+ return {"revision": response["current_revision"]}
187
+
188
+ def attach_component(self, actor, entity, component, value,
189
+ description):
190
+ """Attach one new component to its entity.
191
+
192
+ Args:
193
+ actor: Attribution (the service actor applies).
194
+ entity: Entity id gaining the component.
195
+ component: Component name to attach.
196
+ value: JSON payload to store.
197
+ description: Human note (carried on inner envelopes).
198
+
199
+ Returns:
200
+ Mapping with the new head revision.
201
+
202
+ Raises:
203
+ ProjectError: component_exists for a duplicate;
204
+ unknown_entity for a missing entity.
205
+ """
206
+ del actor, description
207
+ request_id, command_text = self._envelope(
208
+ "attach_component"
209
+ )
210
+ response = self._service.attach_component(
211
+ entity, component, value, request_id, command_text,
212
+ expected_revision=self._service._current_revision,
213
+ )
214
+ return {"revision": response["current_revision"]}
215
+
216
+ def detach_component(self, actor, entity, component, description):
217
+ """Detach one component from its entity.
218
+
219
+ Args:
220
+ actor: Attribution (the service actor applies).
221
+ entity: Entity id losing the component.
222
+ component: Component name to detach.
223
+ description: Human note (carried on inner envelopes).
224
+
225
+ Returns:
226
+ Mapping with the new head revision.
227
+
228
+ Raises:
229
+ ProjectError: unknown_entity or unknown_component
230
+ for a missing target.
231
+ """
232
+ del actor, description
233
+ request_id, command_text = self._envelope(
234
+ "detach_component"
235
+ )
236
+ response = self._service.detach_component(
237
+ entity, component, request_id, command_text,
238
+ expected_revision=self._service._current_revision,
239
+ )
240
+ return {"revision": response["current_revision"]}
241
+
242
+ def delete_entity(self, actor, entity, description):
243
+ """Delete one entity and its projected record.
244
+
245
+ Args:
246
+ actor: Attribution (the service actor applies).
247
+ entity: Entity id to delete.
248
+ description: Human note (carried on inner envelopes).
249
+
250
+ Returns:
251
+ Mapping with the new head revision.
252
+
253
+ Raises:
254
+ ProjectError: unknown_entity for a missing entity.
255
+ """
256
+ del actor, description
257
+ request_id, command_text = self._envelope("delete_entity")
258
+ response = self._service.delete_entity(
259
+ entity, request_id, command_text,
260
+ expected_revision=self._service._current_revision,
261
+ )
262
+ return {"revision": response["current_revision"]}
263
+
264
+ def _resolve(self, node, aliases):
265
+ if isinstance(node, dict) and set(node) == {"alias"}:
266
+ try:
267
+ return aliases[node["alias"]]
268
+ except KeyError:
269
+ raise ProjectError(
270
+ "invalid_command",
271
+ f"Unknown transact alias: {node['alias']!r}.",
272
+ ) from None
273
+ if isinstance(node, dict):
274
+ return {
275
+ key: self._resolve(val, aliases)
276
+ for key, val in node.items()
277
+ }
278
+ if isinstance(node, list):
279
+ return [self._resolve(val, aliases) for val in node]
280
+ return node
281
+
282
+ def transact(self, actor, description, calls):
283
+ """Run calls sequentially with alias threading.
284
+
285
+ Each call is {"verb", "args"?, "alias"?}; {"alias": name}
286
+ values resolve at any depth to ids created earlier in the
287
+ same call list.
288
+
289
+ Args:
290
+ actor: Attribution stamped on the seam events.
291
+ description: Human note for the call list.
292
+ calls: Verb calls with optional args and aliases.
293
+
294
+ Returns:
295
+ Mapping with the new head, the alias map, and the
296
+ stamped seam events.
297
+
298
+ Raises:
299
+ ProjectError: invalid_command for an unknown verb,
300
+ alias, or missing arg; the ECS refusal code
301
+ (unknown_entity, component_exists, ...) for a
302
+ refused verb.
303
+ """
304
+ aliases = {}
305
+ events = []
306
+ for call in calls:
307
+ verb = call["verb"]
308
+ try:
309
+ args = self._resolve(
310
+ dict(call.get("args", {})), aliases
311
+ )
312
+ except KeyError as exc:
313
+ raise ProjectError(
314
+ "invalid_command",
315
+ f"Malformed transact call: {call!r}.",
316
+ ) from exc
317
+ need = {
318
+ "create_entity": (),
319
+ "set_component": ("entity", "component", "value"),
320
+ "attach_component": ("entity", "component", "value"),
321
+ "detach_component": ("entity", "component"),
322
+ "delete_entity": ("entity",),
323
+ }
324
+ if verb not in need:
325
+ raise ProjectError(
326
+ "invalid_command",
327
+ f"Unknown transact verb: {verb!r}.",
328
+ )
329
+ missing = [k for k in need[verb] if k not in args]
330
+ if missing:
331
+ raise ProjectError(
332
+ "invalid_command",
333
+ f"transact {verb} misses {missing!r}.",
334
+ )
335
+ if verb == "create_entity":
336
+ made = self.create_entity(actor, description)
337
+ events.append({
338
+ "event": "entity_created",
339
+ "entity": made["id"],
340
+ })
341
+ if "alias" in call:
342
+ aliases[call["alias"]] = made["id"]
343
+ elif verb in ("set_component", "attach_component"):
344
+ method = getattr(self, verb)
345
+ method(
346
+ actor, args["entity"], args["component"],
347
+ args["value"], description,
348
+ )
349
+ events.append({
350
+ "event": "component_changed",
351
+ "entity": args["entity"],
352
+ "component": args["component"],
353
+ })
354
+ elif verb == "detach_component":
355
+ self.detach_component(
356
+ actor, args["entity"], args["component"],
357
+ description,
358
+ )
359
+ events.append({
360
+ "event": "component_changed",
361
+ "entity": args["entity"],
362
+ "component": args["component"],
363
+ })
364
+ else:
365
+ self.delete_entity(
366
+ actor, args["entity"], description
367
+ )
368
+ events.append({
369
+ "event": "entity_deleted",
370
+ "entity": args["entity"],
371
+ })
372
+ revision = self._service._current_revision
373
+ for event in events:
374
+ event["revision"] = revision
375
+ event["actor"] = actor
376
+ return {
377
+ "revision": revision,
378
+ "aliases": aliases,
379
+ "events": events,
380
+ }
381
+
382
+ def events_since(self, cursor):
383
+ """List entity events past a ledger sequence cursor.
384
+
385
+ Args:
386
+ cursor: Ledger sequence; only later events return.
387
+
388
+ Returns:
389
+ Seam-shaped entity events in ledger order.
390
+ """
391
+ self._service._refresh_durable()
392
+ found = []
393
+ for record in self._service._events:
394
+ if record["seq"] <= cursor:
395
+ continue
396
+ if record["event"] not in (
397
+ "entity_created", "component_changed",
398
+ "entity_deleted",
399
+ ):
400
+ continue
401
+ data = record.get("data", {})
402
+ event = {
403
+ "event": record["event"],
404
+ "entity": data.get("entity"),
405
+ "revision": data.get("revision"),
406
+ "actor": record.get("actor"),
407
+ }
408
+ if "component" in data:
409
+ event["component"] = data["component"]
410
+ found.append(event)
411
+ return found
412
+
413
+
414
+ class _WireMixin:
415
+ """Wire-mutation admission for ProjectService."""
416
+
417
+ def _entity_store(self, request_id):
418
+ """Return the ENGINE-ECS entity store seam for one wire op.
419
+
420
+ WIRE-PLACE/ENGINE-ECS seam: ENGINE-ECS owns the durable
421
+ store as per-verb service methods, so this binds the
422
+ _WireEntityStore adapter over those verbs for the given
423
+ outer request id (inner envelopes derive from it).
424
+
425
+ Args:
426
+ request_id: Outer wire envelope id; inner ECS
427
+ envelopes derive deterministically from it.
428
+
429
+ Returns:
430
+ Entity store serving the wireplace seam.
431
+
432
+ Raises:
433
+ ProjectError: unsupported_operation when the
434
+ ENGINE-ECS verbs are not present.
435
+ """
436
+ for name in (
437
+ _WireEntityStore._READS + _WireEntityStore._WRITES
438
+ ):
439
+ if not callable(getattr(self, name, None)):
440
+ raise ProjectError(
441
+ "unsupported_operation",
442
+ "Wire ops need the ENGINE-ECS entity store, "
443
+ "which is not wired yet.",
444
+ )
445
+ return _WireEntityStore(self, request_id)
446
+
447
+ def _wire_mutation(self, operation, request_id, command_text,
448
+ expected_revision, run, describe):
449
+ """Admit one wire op through the ledger admission machinery.
450
+
451
+ A resubmitted envelope returns its stored response; a new
452
+ envelope gates its expected_revision, snapshots the
453
+ previous head, runs the entity transact (one ledger
454
+ revision per verb), then advances once more for the wire
455
+ event and logs. A noop receipt returns the head
456
+ unadvanced with an empty event list.
457
+
458
+ Args:
459
+ operation: Wire operation name for the event data.
460
+ request_id: Client envelope id for idempotent replay.
461
+ command_text: YAML command text for identity comparison.
462
+ expected_revision: Revision the writer read.
463
+ run: Callable (store) -> wireplace receipt.
464
+ describe: Callable (receipt) -> diff summary text.
465
+
466
+ Returns:
467
+ Mutation mapping with the entity receipt, revision
468
+ transition, actor, diff summary, rewind handle,
469
+ appended event, cursor, and request id — or the
470
+ unadvanced noop mapping when the receipt is a noop.
471
+
472
+ Raises:
473
+ ProjectError: request_conflict for a reused id with
474
+ new text; revision_conflict for a stale head;
475
+ unsupported_operation without the ENGINE-ECS
476
+ store; the wire contract code (unknown_wire,
477
+ unknown_ref, ...) for a refused op.
478
+ """
479
+ self._refresh_durable()
480
+ replay = self._check_replay(request_id, command_text)
481
+ if replay is not None:
482
+ return replay
483
+ self._gate_revision(expected_revision)
484
+ store = self._entity_store(request_id)
485
+ previous = self._current_revision
486
+ self._ensure_snapshot(previous)
487
+ try:
488
+ receipt = run(store)
489
+ except ContractError as exc:
490
+ raise ProjectError(exc.code, str(exc)) from exc
491
+ if receipt.get("noop"):
492
+ response = {
493
+ "project_id": self._project_id,
494
+ "previous_revision": previous,
495
+ "current_revision": previous,
496
+ "revision_transition": {"from": previous, "to": previous},
497
+ "actor": self._actor,
498
+ "diff_summary": f"{operation} changed nothing",
499
+ "rewind_handle": {
500
+ "project_id": self._project_id,
501
+ "revision": previous,
502
+ "request_id": request_id,
503
+ },
504
+ "noop": True,
505
+ "receipt": receipt,
506
+ "events": [],
507
+ "event_cursor": self._cursor(),
508
+ "request_id": request_id,
509
+ }
510
+ self._requests[request_id] = (command_text, response)
511
+ self._persist_after_mutation()
512
+ return response
513
+ # Chain from the live head: the transact's verbs already
514
+ # advanced past `previous`, so advancing from `previous`
515
+ # would fork the revision line backwards.
516
+ advanced = _advance_revision(self._current_revision)
517
+ self._capture_snapshot(advanced)
518
+ self._current_revision = advanced
519
+ diff = describe(receipt)
520
+ record = self._append_event(
521
+ operation,
522
+ request_id,
523
+ {
524
+ "revision": self._current_revision,
525
+ "operation": operation,
526
+ "diff_summary": diff,
527
+ "receipt": receipt,
528
+ },
529
+ )
530
+ response = {
531
+ "project_id": self._project_id,
532
+ "previous_revision": previous,
533
+ "current_revision": self._current_revision,
534
+ "revision_transition": {
535
+ "from": previous,
536
+ "to": self._current_revision,
537
+ },
538
+ "actor": self._actor,
539
+ "diff_summary": diff,
540
+ "rewind_handle": {
541
+ "project_id": self._project_id,
542
+ "revision": previous,
543
+ "request_id": request_id,
544
+ },
545
+ "receipt": receipt,
546
+ "events": [record],
547
+ "event_cursor": self._cursor(),
548
+ "request_id": request_id,
549
+ }
550
+ self._requests[request_id] = (command_text, response)
551
+ self._persist_after_mutation()
552
+ return response
@@ -0,0 +1,14 @@
1
+ """Versioned technology semantics and derived-output recipes."""
2
+
3
+ from .model import TechProfile, bind_layer, check_capability, probe_tool
4
+ from .recipes import DerivedRecipe, apply_recipe, round_trip_ok
5
+
6
+ __all__ = [
7
+ "TechProfile",
8
+ "bind_layer",
9
+ "check_capability",
10
+ "probe_tool",
11
+ "DerivedRecipe",
12
+ "apply_recipe",
13
+ "round_trip_ok",
14
+ ]