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,218 @@
1
+ """Meow v1 normalizer: v0 parse output to a ResultBundle.
2
+
3
+ Maps the ``run_em`` parse shape (``measurements[]`` with ratio
4
+ value, target, margin, and pass/fail status plus convergence
5
+ values) onto a typed metrics table, a bound view, measurements,
6
+ and findings. Pure function of the parsed output: no I/O, no
7
+ tool calls, deterministic ids derived from the run id. PASS
8
+ measurements keep value, target, margin, and status.
9
+ """
10
+
11
+ TASK = "run_em"
12
+
13
+ _DIAG_CAP = 240
14
+
15
+ _SEVERITY = {"error": "error", "warning": "warning"}
16
+
17
+
18
+ def _cap(text):
19
+ """Cap one diagnostic string at the bundle prose limit.
20
+
21
+ Args:
22
+ text: Raw diagnostic text, possibly unbounded tool prose.
23
+
24
+ Returns:
25
+ The text unchanged when short, else truncated with an
26
+ ellipsis marker.
27
+ """
28
+ text = text if isinstance(text, str) else str(text)
29
+ if len(text) <= _DIAG_CAP:
30
+ return text
31
+ return text[: _DIAG_CAP - 3] + "..."
32
+
33
+
34
+ def _quantity(value, unit):
35
+ """Build one v1 Quantity, mapping unitless to dimensionless.
36
+
37
+ Args:
38
+ value: Numeric value.
39
+ unit: Unit string, possibly empty for unitless measures.
40
+
41
+ Returns:
42
+ Quantity mapping with an explicit unit.
43
+ """
44
+ return {"value": float(value), "unit": unit if unit else "1"}
45
+
46
+
47
+ def _limit(item):
48
+ """Pick the binding target bound for one measurement.
49
+
50
+ Args:
51
+ item: v0 measurement with a ``target`` min/max mapping.
52
+
53
+ Returns:
54
+ The bound closest to the measured value (the margin
55
+ binding bound), or None when the target is empty.
56
+ """
57
+ target = item.get("target") or {}
58
+ scored = []
59
+ if target.get("min") is not None:
60
+ scored.append((float(item["value"]) - float(target["min"]),
61
+ float(target["min"])))
62
+ if target.get("max") is not None:
63
+ scored.append((float(target["max"]) - float(item["value"]),
64
+ float(target["max"])))
65
+ if not scored:
66
+ return None
67
+ return min(scored)[1]
68
+
69
+
70
+ def _provenance(project_id, run_id, tools, provenance):
71
+ """Build ResultProvenance, defaulting to a placeholder closure.
72
+
73
+ Args:
74
+ project_id: Project the run belongs to.
75
+ run_id: Run the bundle describes.
76
+ tools: Tool ingredient mappings (id, version, digest?).
77
+ provenance: Full provenance mapping when the caller has
78
+ one; a deterministic placeholder otherwise.
79
+
80
+ Returns:
81
+ Provenance mapping valid against the v1 schema.
82
+ """
83
+ if provenance is not None:
84
+ return provenance
85
+ ref = {"scope": "project", "id": project_id, "revision": "r1"}
86
+ return {
87
+ "snapshot": dict(ref),
88
+ "input_lock": dict(ref),
89
+ "closure_hash": "0" * 64,
90
+ "tools": [
91
+ {
92
+ "id": item["id"],
93
+ "version": item.get("version", ""),
94
+ "digest": item.get("digest", "0" * 64),
95
+ }
96
+ for item in tools
97
+ ],
98
+ "parents": [],
99
+ }
100
+
101
+
102
+ def normalize(parsed, *, run_id, project_id="project", target=None,
103
+ tools=(), provenance=None):
104
+ """Normalize one meow parse output to a v1 ResultBundle.
105
+
106
+ Metric rows become a table dataset plus a bound table view;
107
+ violations become findings with capped messages. PASS
108
+ measurements keep value, target, margin, and status. A
109
+ malformed device (no metrics) reports not_converged with its
110
+ error finding.
111
+
112
+ Args:
113
+ parsed: v0 parse mapping with ``measurements[]`` and
114
+ ``violations[]``.
115
+ run_id: Run the bundle describes (anchors all ids).
116
+ project_id: Project the run belongs to.
117
+ target: v1 target mapping; project kind by default.
118
+ tools: Tool ingredient mappings for provenance.
119
+ provenance: Full provenance mapping when the caller has
120
+ one; a deterministic placeholder otherwise.
121
+
122
+ Returns:
123
+ Schema-valid ResultBundle mapping.
124
+ """
125
+ measurements_in = parsed.get("measurements") or []
126
+ violations = parsed.get("violations") or []
127
+ table_id = f"{run_id}:dataset:metrics"
128
+ datasets = [
129
+ {
130
+ "id": table_id,
131
+ "kind": "table",
132
+ "row_count": len(measurements_in),
133
+ "columns": [
134
+ {
135
+ "id": f"{table_id}:name",
136
+ "name": "name",
137
+ "dtype": "utf8",
138
+ "unit": "1",
139
+ "data": [item["name"] for item in measurements_in],
140
+ },
141
+ {
142
+ "id": f"{table_id}:value",
143
+ "name": "value",
144
+ "dtype": "float64",
145
+ "unit": "1",
146
+ "data": [float(item["value"]) for item in measurements_in],
147
+ },
148
+ {
149
+ "id": f"{table_id}:margin",
150
+ "name": "margin",
151
+ "dtype": "float64",
152
+ "unit": "1",
153
+ "data": [float(item["margin"]) for item in measurements_in],
154
+ },
155
+ {
156
+ "id": f"{table_id}:status",
157
+ "name": "status",
158
+ "dtype": "utf8",
159
+ "unit": "1",
160
+ "data": [item["status"] for item in measurements_in],
161
+ },
162
+ ],
163
+ "conditions": [],
164
+ }
165
+ ]
166
+ views = [
167
+ {
168
+ "id": f"{run_id}:view:metrics",
169
+ "kind": "table",
170
+ "dataset_id": table_id,
171
+ "name": "em metrics",
172
+ }
173
+ ]
174
+ measurements = []
175
+ for item in measurements_in:
176
+ entry = {
177
+ "name": item["name"],
178
+ "value": _quantity(item["value"], item.get("unit", "")),
179
+ "margin": _quantity(item["margin"], item.get("unit", "")),
180
+ "status": "PASS" if item["status"] == "pass" else "FAIL",
181
+ }
182
+ bound = _limit(item)
183
+ if bound is not None:
184
+ entry["limit"] = _quantity(bound, item.get("unit", ""))
185
+ measurements.append(entry)
186
+ findings = [
187
+ {
188
+ "id": f"{run_id}:finding:{index}",
189
+ "severity": _SEVERITY.get(v.get("severity"), "info"),
190
+ "code": v.get("rule", "device"),
191
+ "message": _cap(v.get("message", "")),
192
+ }
193
+ for index, v in enumerate(violations)
194
+ ]
195
+ converged = not any(
196
+ v.get("rule") in ("device", "s_param", "convergence")
197
+ for v in violations
198
+ )
199
+ return {
200
+ "schema": "rocketeda/results/v1",
201
+ "ref": {"scope": "project", "id": run_id, "revision": "r1"},
202
+ "run_id": run_id,
203
+ "task": TASK,
204
+ "target": dict(target)
205
+ if target is not None
206
+ else {"project_id": project_id, "kind": "project"},
207
+ "state": "complete",
208
+ "freshness": "current",
209
+ "convergence": "converged" if converged else "not_converged",
210
+ "provenance": _provenance(project_id, run_id, tools, provenance),
211
+ "datasets": datasets,
212
+ "views": views,
213
+ "measurements": measurements,
214
+ "findings": findings,
215
+ "artifacts": [],
216
+ "logs": [],
217
+ "gaps": [],
218
+ }
@@ -0,0 +1,192 @@
1
+ """Mutation adapter: run_mutation with the mcy provider."""
2
+
3
+ import re
4
+ import shutil
5
+ import sqlite3
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from ...execution.errors import ExecutionError
10
+ from ...interfaces import ToolAdapter
11
+ from ..errors import AdapterError
12
+
13
+ OPERATION = "run_mutation"
14
+ _TOOL = "mcy"
15
+ _DB = "database/db.sqlite3"
16
+ _OUTPUT_RE = re.compile(r"^(\d+)\s+(\S+)\s*$")
17
+
18
+
19
+ def _parse_output_txt(text):
20
+ """Parse one output.txt streaming verdict file.
21
+
22
+ Args:
23
+ text: output.txt text with `<idx> <RESULT>` per line.
24
+
25
+ Returns:
26
+ List of {mutation_id, result} rows.
27
+ """
28
+ rows = []
29
+ for line in (text or "").splitlines():
30
+ match = _OUTPUT_RE.match(line.strip())
31
+ if match is None:
32
+ continue
33
+ rows.append({"mutation_id": int(match.group(1)),
34
+ "result": match.group(2)})
35
+ return rows
36
+
37
+
38
+ class McyAdapter(ToolAdapter):
39
+ """run_mutation via mcy: sqlite DB plus output.txt streaming.
40
+
41
+ Serves the run_mutation operation with tool `mcy`. Primary
42
+ results come from database/db.sqlite3 via stdlib sqlite3;
43
+ output.txt `<idx> <RESULT>` rows stream per-task verdicts
44
+ before DB commit. Never scrapes list/source/status stdout.
45
+
46
+ Job keys:
47
+ project (required): Mcy project dir with database/.
48
+ test: Test name for run scoping.
49
+ workdir: Run directory (default ".").
50
+ timeout_s: Budget in seconds (default 600).
51
+
52
+ Args:
53
+ runner: Optional isolated runner; direct subprocess when None.
54
+ """
55
+
56
+ def __init__(self, runner=None):
57
+ self._runner = runner
58
+
59
+ parse_output_txt = staticmethod(_parse_output_txt)
60
+
61
+ def capabilities(self, target):
62
+ """Report the served operation and tool.
63
+
64
+ Args:
65
+ target: Target the capabilities are reported for.
66
+
67
+ Returns:
68
+ Capability mapping with operations and tool.
69
+ """
70
+ return {"operations": [OPERATION], "tool": _TOOL, "db": _DB}
71
+
72
+ def preflight(self, job):
73
+ """Check operation, project dir, and mcy presence.
74
+
75
+ Args:
76
+ job: Solver job with operation run_mutation plus project.
77
+
78
+ Returns:
79
+ Checked mapping with project, binary, and timeout.
80
+
81
+ Raises:
82
+ AdapterError: unsupported_operation, invalid_reference
83
+ for a missing project, missing_dependency when mcy
84
+ is absent (BLOCKED-needs-host).
85
+ """
86
+ if job.get("operation") != OPERATION:
87
+ raise AdapterError(
88
+ "unsupported_operation",
89
+ f"Mcy handles {OPERATION}, got {job.get('operation')}.")
90
+ project = job.get("project")
91
+ if not project or not Path(project).is_dir():
92
+ raise AdapterError(
93
+ "invalid_reference", f"Mcy project not found: {project}.")
94
+ binary = shutil.which(_TOOL)
95
+ if binary is None:
96
+ raise AdapterError(
97
+ "missing_dependency",
98
+ "BLOCKED-needs-host: mcy not on PATH (oss-cad-suite).")
99
+ timeout = job.get("timeout_s", 600)
100
+ if (not isinstance(timeout, (int, float))
101
+ or isinstance(timeout, bool) or not timeout > 0):
102
+ raise AdapterError("invalid_command", f"Bad timeout: {timeout!r}.")
103
+ checked = {"project": str(project), "binary": binary,
104
+ "timeout_s": timeout, "test": job.get("test")}
105
+ if self._runner is not None:
106
+ try:
107
+ self._runner.preflight()
108
+ except ExecutionError as exc:
109
+ raise AdapterError(exc.code, str(exc)) from exc
110
+ return checked
111
+
112
+ def execute(self, job):
113
+ """Run mcy on the project under a timeout.
114
+
115
+ Args:
116
+ job: Solver job with operation run_mutation plus project.
117
+
118
+ Returns:
119
+ Native output with exit_code, stdout/stderr, results,
120
+ and db file.
121
+ """
122
+ checked = self.preflight(job)
123
+ workdir = job.get("workdir", checked["project"])
124
+ if not Path(workdir).is_dir():
125
+ raise AdapterError(
126
+ "invalid_reference", f"Workdir not found: {workdir}.")
127
+ argv = [checked["binary"], "run"]
128
+ if checked["test"]:
129
+ argv.extend(["--test", str(checked["test"])])
130
+ try:
131
+ proc = subprocess.run(argv, capture_output=True, text=True,
132
+ timeout=checked["timeout_s"], cwd=workdir)
133
+ except subprocess.TimeoutExpired as exc:
134
+ raise AdapterError(
135
+ "runner_unavailable", "Mcy run timed out.") from exc
136
+ results = self.read_db(Path(workdir) / _DB)
137
+ return {"exit_code": proc.returncode, "stdout": proc.stdout,
138
+ "stderr": proc.stderr, "results": results,
139
+ "db_file": _DB}
140
+
141
+ def parse(self, native_output):
142
+ """Parse DB results to coverage plus uncovered violations.
143
+
144
+ Args:
145
+ native_output: Native output with the results rows.
146
+
147
+ Returns:
148
+ {total, violations[], coverage, source}.
149
+
150
+ Raises:
151
+ AdapterError: runner_unavailable with no results.
152
+ """
153
+ results = native_output.get("results")
154
+ if not isinstance(results, list) or not results:
155
+ raise AdapterError(
156
+ "runner_unavailable", "Mcy output has no results.")
157
+ uncovered = [r for r in results
158
+ if str(r.get("result", "")).upper() != "COVERED"
159
+ and str(r.get("result", "")).upper() != "PASS"]
160
+ coverage = ((len(results) - len(uncovered)) / len(results)
161
+ if results else 0.0)
162
+ violations = [{"rule": "mutation_uncovered", "severity": "error",
163
+ "message": f"Mutation {r.get('mutation_id')}"
164
+ f" {r.get('result')}."}
165
+ for r in uncovered]
166
+ return {"total": len(violations), "violations": violations,
167
+ "coverage": coverage, "source": "db",
168
+ "db_file": native_output.get("db_file", _DB)}
169
+
170
+ @staticmethod
171
+ def read_db(path):
172
+ """Read cached results from one mcy sqlite DB.
173
+
174
+ Args:
175
+ path: database/db.sqlite3 path (missing reads as empty).
176
+
177
+ Returns:
178
+ Results rows (empty when the DB is absent).
179
+ """
180
+ if not Path(path).is_file():
181
+ return []
182
+ try:
183
+ conn = sqlite3.connect(str(path))
184
+ try:
185
+ rows = conn.execute(
186
+ "SELECT mutation_id, test, result FROM results").fetchall()
187
+ finally:
188
+ conn.close()
189
+ except Exception:
190
+ return []
191
+ return [{"mutation_id": r[0], "test": r[1], "result": r[2]}
192
+ for r in rows]
@@ -0,0 +1,28 @@
1
+ # ngspice adapter
2
+
3
+ `run_spice` behind the ToolAdapter port (SIM-SKY130).
4
+
5
+ Job: `sources` (netlists, `.end` lines stripped), `measurements`
6
+ (`name`/`expr`/`min`/`max`/`unit`), `analyses` (default `op`),
7
+ `options` (default `wnflag=1`), `params` (`.param` lines),
8
+ `corner` (default `tt`), `temp_c` (default 27), optional `pdk`
9
+ (`id`/`revision`/`model_rel`/`engine_root`, resolved through the
10
+ PDK store so a wrong rev fails loudly before any sim).
11
+
12
+ Staging: `ngspice_deck.spice` (preamble + bodies + generated
13
+ `.control` with `let`/`print meas_*`) plus `.spiceinit`
14
+ (neutral base lines plus the lane profile extras, see
15
+ `../profile.py` and `docs/pdk/<lane>/profile.yaml`). Runs
16
+ `ngspice -b -o ngspice.log ngspice_deck.spice`.
17
+
18
+ Simulator needs are profile-driven (PDK-PROFILE pattern): the
19
+ default model relpath and `.spiceinit` extras come from the PDK
20
+ lane profile, never from adapter special-cases. An explicit
21
+ `model_rel` in the job pin always wins; a PDK with no sim
22
+ profile and no explicit `model_rel` fails loudly
23
+ (`invalid_command`, never a guessed relpath).
24
+
25
+ Parse: `meas_* = value` lines evaluated against bounds (SAX
26
+ `measurements` shape); a sim yielding no values synthesizes
27
+ `sim_fail` violations carrying ngspice's own error text. The
28
+ verdict always comes from parsed output, never the exit code.