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,371 @@
1
+ """Component catalogue (API-GENERALIZE): versioned cell index + place_cell.
2
+
3
+ The frontend cannot import gdsfactory, so the engine introspects cell
4
+ libraries upstream and serves plain data: every entry carries cell,
5
+ PDK, PDK revision, ports, and default params, all JSON-serializable.
6
+ Entries come from providers — callables returning entry lists — so the
7
+ upstream source stays replaceable: `gdsfactory_provider` introspects
8
+ installed gdsfactory cell modules (ports from best-effort
9
+ instantiation, default params from factory signatures, never raising),
10
+ `static_provider` serves fixed entries for seeds and tests.
11
+
12
+ `place_cell` turns a catalogue entry into a durable project mutation:
13
+ the instance lands as a `components/<instance_id>.yaml` document
14
+ through the ledger's atomic batch path, so every placement advances
15
+ the revision, appends an actor-attributed event, and replays
16
+ idempotently by request id.
17
+ """
18
+
19
+ import copy
20
+ import importlib
21
+ import inspect
22
+ import io
23
+ import uuid
24
+ from contextlib import redirect_stdout
25
+
26
+ from ruamel.yaml import YAML
27
+
28
+ try:
29
+ from .errors import LibraryError
30
+ from ..project.errors import ProjectError
31
+ except ImportError: # standalone verification before install
32
+ from rocket_engine.library.errors import LibraryError
33
+ from rocket_engine.project.errors import ProjectError
34
+
35
+ CATALOG_VERSION = 1
36
+
37
+ _yaml = YAML(typ="safe")
38
+
39
+
40
+ def _request_admitted(project, request_id):
41
+ """True when the ledger already admitted a request id.
42
+
43
+ A replayed placement skips the instance-exists guard: the batch
44
+ path returns the stored mapping instead of writing. New request
45
+ ids keep the guard, so a reused instance name is refused loudly
46
+ instead of overwritten silently.
47
+ """
48
+ try:
49
+ events = project.events_since(0)
50
+ except Exception:
51
+ return False
52
+ return any(event.get("request_id") == request_id for event in events)
53
+
54
+
55
+ _ENTRY_KEYS = (
56
+ "cell", "pdk", "pdk_revision", "ports", "default_params",
57
+ "catalog_version", "source",
58
+ )
59
+
60
+
61
+ def _json_scalar(value):
62
+ if value is None or isinstance(value, (bool, int, float, str)):
63
+ return value
64
+ if isinstance(value, (list, tuple)):
65
+ return [_json_scalar(item) for item in value]
66
+ if isinstance(value, dict):
67
+ return {
68
+ str(key): _json_scalar(item) for key, item in value.items()
69
+ }
70
+ return repr(value)
71
+
72
+
73
+ def _normalize(entry, source):
74
+ normalized = {
75
+ "cell": str(entry["cell"]),
76
+ "pdk": str(entry["pdk"]),
77
+ "pdk_revision": str(entry.get("pdk_revision", "")),
78
+ "ports": [str(port) for port in entry.get("ports", [])],
79
+ "default_params": {
80
+ str(key): _json_scalar(value)
81
+ for key, value in dict(entry.get("default_params", {})).items()
82
+ },
83
+ "catalog_version": CATALOG_VERSION,
84
+ "source": source,
85
+ }
86
+ return {key: normalized[key] for key in _ENTRY_KEYS}
87
+
88
+
89
+ def static_provider(entries, source="static"):
90
+ """Serve fixed catalogue entries (seeds, fixtures, tests).
91
+
92
+ Args:
93
+ entries: Entry mappings with cell, pdk, pdk_revision,
94
+ ports, and default_params.
95
+ source: Source label stamped on every entry.
96
+
97
+ Returns:
98
+ Provider callable returning normalized entries.
99
+ """
100
+ frozen = [_normalize(entry, source) for entry in entries]
101
+
102
+ def provide():
103
+ return [dict(entry) for entry in frozen]
104
+
105
+ return provide
106
+
107
+
108
+ def gdsfactory_provider(modules=("gdsfactory.components",), pdk="gdsfactory",
109
+ pdk_revision=None, instantiate=True):
110
+ """Introspect gdsfactory cell modules into catalogue entries.
111
+
112
+ gdsfactory is imported lazily inside the provider, engine-side
113
+ only — the served entries are plain data, so the frontend never
114
+ needs the import. Missing gdsfactory, missing modules, and broken
115
+ factories all degrade to fewer entries, never an exception.
116
+
117
+ Args:
118
+ modules: Cell-module names searched via `get_cells`.
119
+ pdk: PDK label stamped on every entry.
120
+ pdk_revision: PDK revision stamped on every entry; the
121
+ installed gdsfactory version when omitted.
122
+ instantiate: Best-effort instantiate each factory with
123
+ defaults to read port names; factories that refuse
124
+ keep empty ports rather than failing the listing.
125
+
126
+ Returns:
127
+ Provider callable returning normalized entries (possibly
128
+ none when gdsfactory is unavailable).
129
+ """
130
+ def provide():
131
+ try:
132
+ gf = importlib.import_module("gdsfactory")
133
+ except ImportError:
134
+ return []
135
+ revision = pdk_revision or getattr(gf, "__version__", "")
136
+ found = {}
137
+ for name in modules:
138
+ try:
139
+ module = importlib.import_module(name)
140
+ except ImportError:
141
+ continue
142
+ try:
143
+ cells = gf.get_cells([module])
144
+ except Exception:
145
+ continue
146
+ for cell_name, factory in cells.items():
147
+ found.setdefault(cell_name, factory)
148
+ entries = []
149
+ for cell_name in sorted(found):
150
+ factory = found[cell_name]
151
+ try:
152
+ signature = inspect.signature(factory)
153
+ except (TypeError, ValueError):
154
+ signature = None
155
+ defaults = {}
156
+ if signature is not None:
157
+ for param in signature.parameters.values():
158
+ if param.default is not inspect.Parameter.empty:
159
+ defaults[param.name] = _json_scalar(param.default)
160
+ ports = []
161
+ if instantiate:
162
+ try:
163
+ # Best-effort probe: some factories print
164
+ # while instantiating; keep the engine quiet.
165
+ with redirect_stdout(io.StringIO()):
166
+ component = factory()
167
+ ports = [str(name) for name in list(component.ports)]
168
+ except Exception:
169
+ ports = []
170
+ entries.append(_normalize({
171
+ "cell": cell_name,
172
+ "pdk": pdk,
173
+ "pdk_revision": revision,
174
+ "ports": ports,
175
+ "default_params": defaults,
176
+ }, "gdsfactory"))
177
+ return entries
178
+
179
+ return provide
180
+
181
+
182
+ class ApigCatalog:
183
+ """Versioned component index with durable placement.
184
+
185
+ Args:
186
+ root: Store root reserved for manifest-backed providers;
187
+ introspection providers ignore it.
188
+ providers: Provider callables returning entry lists; later
189
+ providers lose on (pdk, cell) collisions.
190
+ """
191
+
192
+ def __init__(self, root=".", providers=()):
193
+ self._root = root
194
+ self._providers = list(providers)
195
+ self._cache = None
196
+
197
+ def add_provider(self, provider):
198
+ """Append a fallback provider to the index.
199
+
200
+ Args:
201
+ provider: Provider callable returning entry lists.
202
+
203
+ Returns:
204
+ Mapping with the provider count now serving.
205
+ """
206
+ self._providers.append(provider)
207
+ self._cache = None
208
+ return {"providers": len(self._providers)}
209
+
210
+ def refresh(self):
211
+ """Drop the cached index so providers run again.
212
+
213
+ Returns:
214
+ Mapping with the cell count after rebuilding.
215
+ """
216
+ self._cache = None
217
+ return {"cells": len(self.list_cells()["cells"])}
218
+
219
+ def _index(self):
220
+ if self._cache is None:
221
+ index = {}
222
+ for provider in self._providers:
223
+ for entry in provider():
224
+ index.setdefault((entry["pdk"], entry["cell"]), entry)
225
+ self._cache = index
226
+ return self._cache
227
+
228
+ def list_cells(self, pdk=None):
229
+ """List versioned catalogue entries, optionally for one PDK.
230
+
231
+ Args:
232
+ pdk: Restrict to one PDK label; all entries when
233
+ omitted.
234
+
235
+ Returns:
236
+ Listing with catalog_version and cells sorted by
237
+ (pdk, cell). Every entry is plain JSON data.
238
+ """
239
+ entries = [
240
+ copy.deepcopy(entry)
241
+ for (entry_pdk, _cell), entry in self._index().items()
242
+ if pdk is None or entry_pdk == pdk
243
+ ]
244
+ entries.sort(key=lambda e: (e["pdk"], e["cell"]))
245
+ return {"catalog_version": CATALOG_VERSION, "cells": entries}
246
+
247
+ def get_cell(self, cell, pdk=None):
248
+ """Read one catalogue entry by cell, optionally by PDK.
249
+
250
+ Args:
251
+ cell: Cell name to look up.
252
+ pdk: PDK label disambiguating same-named cells.
253
+
254
+ Returns:
255
+ Catalogue entry for the cell.
256
+
257
+ Raises:
258
+ LibraryError: unknown_cell when no entry matches,
259
+ ambiguous_cell when the cell exists in several
260
+ PDKs and none was named.
261
+ """
262
+ matches = [
263
+ entry for (entry_pdk, entry_cell), entry in self._index().items()
264
+ if entry_cell == cell and (pdk is None or entry_pdk == pdk)
265
+ ]
266
+ if not matches:
267
+ raise LibraryError(
268
+ "unknown_cell", f"Unknown cell: {cell!r}."
269
+ )
270
+ if len(matches) > 1:
271
+ labels = sorted(entry["pdk"] for entry in matches)
272
+ raise LibraryError(
273
+ "ambiguous_cell",
274
+ f"Cell {cell!r} exists in several PDKs ({', '.join(labels)}); "
275
+ "name one.",
276
+ )
277
+ return copy.deepcopy(matches[0])
278
+
279
+ def place_cell(self, project, cell, request_id, expected_revision=None,
280
+ pdk=None, instance_id=None, params=None,
281
+ connections=None, path_prefix="components"):
282
+ """Place a catalogue cell as a durable project mutation.
283
+
284
+ The instance lands as `<prefix>/<instance_id>.yaml` through
285
+ the ledger's atomic batch path: one new revision, one
286
+ actor-attributed event, idempotent replay by request id.
287
+
288
+ Args:
289
+ project: Project port with `apply_mutations` and
290
+ `read_doc` (ProjectService serves).
291
+ cell: Catalogue cell name.
292
+ request_id: Client envelope id for idempotent replay.
293
+ expected_revision: Revision the writer read.
294
+ pdk: PDK label disambiguating same-named cells.
295
+ instance_id: Instance name; `<cell>_<8 hex>` when
296
+ omitted.
297
+ params: Override values for the cell's default params;
298
+ unknown names are refused, never stored.
299
+ connections: Connection mappings; every port must
300
+ exist on the cell.
301
+ path_prefix: Document directory for instances.
302
+
303
+ Returns:
304
+ Mutation mapping with the revision transition, new
305
+ head, actor, event, and request id.
306
+
307
+ Raises:
308
+ LibraryError: unknown_cell, ambiguous_cell,
309
+ unknown_param, incompatible_port, or
310
+ instance_exists when the instance document already
311
+ exists under a different request.
312
+ ProjectError: revision_conflict for a stale head,
313
+ request_conflict for a reused id with new text.
314
+ """
315
+ entry = self.get_cell(cell, pdk=pdk)
316
+ params = dict(params or {})
317
+ unknown = sorted(set(params) - set(entry["default_params"]))
318
+ if unknown:
319
+ raise LibraryError(
320
+ "unknown_param",
321
+ f"Unknown params for {entry['cell']!r}: "
322
+ f"{', '.join(unknown)}.",
323
+ )
324
+ ports = set(entry["ports"])
325
+ for conn in connections or []:
326
+ if (conn.get("port")) not in ports:
327
+ raise LibraryError(
328
+ "incompatible_port", f"Unknown port: {conn.get('port')}."
329
+ )
330
+ instance = instance_id or f"{entry['cell']}_{uuid.uuid4().hex[:8]}"
331
+ path = f"{path_prefix}/{instance}.yaml"
332
+ try:
333
+ project.read_doc(path)
334
+ except ProjectError as exc:
335
+ if exc.code != "unknown_document":
336
+ raise
337
+ exists = False
338
+ else:
339
+ exists = True
340
+ if exists and not _request_admitted(project, request_id):
341
+ raise LibraryError(
342
+ "instance_exists",
343
+ f"Instance already placed: {instance!r}.",
344
+ )
345
+ record = {
346
+ "instance_id": instance,
347
+ "cell": entry["cell"],
348
+ "pdk": entry["pdk"],
349
+ "pdk_revision": entry["pdk_revision"],
350
+ "catalog_version": CATALOG_VERSION,
351
+ "params": {**entry["default_params"], **params},
352
+ "connections": list(connections or []),
353
+ }
354
+ buf = io.StringIO()
355
+ _yaml.dump(
356
+ {"operation": "place_cell",
357
+ "arguments": {
358
+ "cell": cell, "pdk": entry["pdk"],
359
+ "instance_id": instance, "params": params,
360
+ "connections": list(connections or []),
361
+ }},
362
+ buf,
363
+ )
364
+ content = io.StringIO()
365
+ _yaml.dump(record, content)
366
+ return project.apply_mutations(
367
+ [{"op": "write", "path": path, "content": content.getvalue()}],
368
+ request_id,
369
+ buf.getvalue(),
370
+ expected_revision=expected_revision,
371
+ )
@@ -0,0 +1,185 @@
1
+ """In-memory catalog (slice 3): PDK lookup, readiness, IP registry.
2
+
3
+ Readiness answers per-operation availability from PDK manifests. The
4
+ IP registry snapshots submission manifests and tracks project/global
5
+ scope. Persistent storage and natural-language discovery arrive in
6
+ later slices.
7
+ """
8
+
9
+ import uuid
10
+ from pathlib import Path
11
+
12
+ from ruamel.yaml import YAML
13
+
14
+ from ..interfaces import resolve_reference
15
+ from .errors import LibraryError
16
+
17
+
18
+ class Catalog:
19
+ """In-memory PDK lookup, readiness, and IP registry.
20
+
21
+ Args:
22
+ root: Store root PDK references resolve against.
23
+ """
24
+
25
+ def __init__(self, root):
26
+ self._root = Path(root)
27
+ self._registry = {}
28
+ self._yaml = YAML(typ="safe")
29
+
30
+ def resolve(self, pdk_id, revision):
31
+ """Resolve one global PDK reference against the root.
32
+
33
+ Args:
34
+ pdk_id: PDK id to resolve.
35
+ revision: PDK revision pin.
36
+
37
+ Returns:
38
+ Matching validated PDK record.
39
+
40
+ Raises:
41
+ ContractError: invalid_reference for an unknown PDK.
42
+ """
43
+ return resolve_reference(
44
+ {"scope": "global", "id": pdk_id, "revision": revision},
45
+ root=self._root,
46
+ )
47
+
48
+ def active_context(self, job):
49
+ """Open an active context token for one job.
50
+
51
+ Args:
52
+ job: Job the context attaches to.
53
+
54
+ Returns:
55
+ Context with job and fresh token.
56
+ """
57
+ return {"job": job, "token": uuid.uuid4().hex}
58
+
59
+ def capabilities(self, pdk_id, revision):
60
+ """Report per-operation readiness from the PDK manifest.
61
+
62
+ Args:
63
+ pdk_id: PDK id to report for.
64
+ revision: PDK revision pin.
65
+
66
+ Returns:
67
+ Readiness per operation (ready, or unsupported with a
68
+ reason).
69
+
70
+ Raises:
71
+ ContractError: invalid_reference for an unknown PDK.
72
+ """
73
+ doc = self.resolve(pdk_id, revision)
74
+ objects = doc.get("objects", {}) or {}
75
+ decks = doc.get("decks", {}) or {}
76
+ generate = {"readiness": "ready" if objects else "unsupported"}
77
+ if not objects:
78
+ generate["reason"] = f"no generatable objects for {pdk_id} {revision}"
79
+ lvs = {"readiness": "ready" if decks else "unsupported"}
80
+ if not decks:
81
+ lvs["reason"] = f"no LVS deck for {pdk_id} {revision}"
82
+ return {"generate_views": generate, "run_lvs": lvs}
83
+
84
+ def register(self, text, scope):
85
+ """Register a submission manifest snapshot as UNVERIFIED.
86
+
87
+ Args:
88
+ text: Submission manifest YAML text.
89
+ scope: Scope to register under (project/global).
90
+
91
+ Returns:
92
+ Component ref with scope, id, revision, and
93
+ UNVERIFIED status.
94
+
95
+ Raises:
96
+ LibraryError: invalid_yaml for an unparsable manifest
97
+ or one without an id.
98
+ """
99
+ # Submission manifests are parsed plainly here; reconciling the
100
+ # submission shape with stored-record validation is a hardening item.
101
+ try:
102
+ doc = self._yaml.load(text)
103
+ except Exception as exc:
104
+ raise LibraryError("invalid_yaml", f"Cannot parse manifest: {exc}") from exc
105
+ if not isinstance(doc, dict) or "id" not in doc:
106
+ raise LibraryError("invalid_yaml", "Manifest must be a mapping with an id.")
107
+ ref = {
108
+ "scope": scope,
109
+ "id": doc["id"],
110
+ "revision": doc.get("revision", "r1"),
111
+ "status": "UNVERIFIED",
112
+ }
113
+ self._registry[(scope, doc["id"], ref["revision"])] = doc
114
+ return ref
115
+
116
+ def _registered(self, ref):
117
+ try:
118
+ return self._registry[(ref["scope"], ref["id"], ref["revision"])]
119
+ except KeyError:
120
+ raise LibraryError(
121
+ "invalid_reference",
122
+ f"Unknown component: {ref.get('scope')}/{ref.get('id')}.",
123
+ ) from None
124
+
125
+ def instantiate(self, ref, instance_id, project_id, connections=None):
126
+ """Instantiate a registered component into a project.
127
+
128
+ Args:
129
+ ref: Registered component ref (scope/id/revision).
130
+ instance_id: Instance id to create.
131
+ project_id: Project receiving the instance.
132
+ connections: Connections; every port must exist.
133
+
134
+ Returns:
135
+ Instance record with component ref.
136
+
137
+ Raises:
138
+ LibraryError: invalid_reference for an unregistered
139
+ component, incompatible_port for an unknown port.
140
+ """
141
+ doc = self._registered(ref)
142
+ ports = doc.get("ports", {})
143
+ for conn in connections or []:
144
+ if conn.get("port") not in ports:
145
+ raise LibraryError(
146
+ "incompatible_port", f"Unknown port: {conn.get('port')}."
147
+ )
148
+ return {
149
+ "instance_id": instance_id,
150
+ "project_id": project_id,
151
+ "component": {
152
+ "scope": ref["scope"],
153
+ "id": ref["id"],
154
+ "revision": ref["revision"],
155
+ },
156
+ }
157
+
158
+ def promote(self, ref):
159
+ """Promote a component to global scope as UNVERIFIED.
160
+
161
+ Args:
162
+ ref: Registered component ref to promote.
163
+
164
+ Returns:
165
+ Global ref with dependency closure and UNVERIFIED
166
+ status.
167
+
168
+ Raises:
169
+ LibraryError: invalid_reference for an unregistered
170
+ component.
171
+ """
172
+ doc = self._registered(ref)
173
+ closure = [
174
+ {"scope": "global", "id": dep.get("id"), "revision": dep.get("revision")}
175
+ for dep in doc.get("dependencies", [])
176
+ ]
177
+ glob = {
178
+ "scope": "global",
179
+ "id": ref["id"],
180
+ "revision": ref["revision"],
181
+ "status": "UNVERIFIED",
182
+ "dependency_closure": closure,
183
+ }
184
+ self._registry[("global", ref["id"], ref["revision"])] = doc
185
+ return glob
@@ -0,0 +1,16 @@
1
+ """Error type for catalog violations. Carries a stable machine code."""
2
+
3
+
4
+ class LibraryError(Exception):
5
+ """Catalog violation with a stable machine code.
6
+
7
+ Args:
8
+ code: Stable machine-readable error code.
9
+ message: Human-readable detail.
10
+ fields: Itemized fields, when the error names any.
11
+ """
12
+
13
+ def __init__(self, code, message, fields=None):
14
+ super().__init__(message)
15
+ self.code = code
16
+ self.fields = fields or []