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,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
|
+
]
|