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