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,155 @@
1
+ """GHDL provider: run_hdl_test with assert-line verdicts."""
2
+
3
+ import re
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from ...execution.errors import ExecutionError
9
+ from ...interfaces import ToolAdapter
10
+ from ..errors import AdapterError
11
+
12
+ OPERATION = "run_hdl_test"
13
+ _TOOL = "ghdl"
14
+ _ASSERT_RE = re.compile(
15
+ r"^(.+):(\d+):(\d+):@(\S+):\((\w[\w ]*) (\w+)\): (.*)$")
16
+
17
+
18
+ class GhdlAdapter(ToolAdapter):
19
+ """run_hdl_test via ghdl: analyze, elaborate, run, parse asserts.
20
+
21
+ Serves the run_hdl_test operation with tool `ghdl`. Compile
22
+ diagnostics arrive on stderr; sim assert/report lines arrive
23
+ on stdout (never merge blindly). PSL verdicts prefer
24
+ --psl-report JSON; waveforms prefer --vcd/--fst via pywellen.
25
+
26
+ Job keys:
27
+ sources (required): VHDL source paths.
28
+ top (required): Top entity name.
29
+ workdir: Run directory (default ".").
30
+ timeout_s: Budget in seconds (default 120).
31
+ """
32
+
33
+ def __init__(self, runner=None):
34
+ self._runner = runner
35
+
36
+ def capabilities(self, target):
37
+ """Report the served operation and tool.
38
+
39
+ Args:
40
+ target: Target the capabilities are reported for.
41
+
42
+ Returns:
43
+ Capability mapping with operations and tool.
44
+ """
45
+ return {"operations": [OPERATION], "tool": _TOOL,
46
+ "modes": ["analyze", "sim"]}
47
+
48
+ def preflight(self, job):
49
+ """Check operation, sources, top, and ghdl presence.
50
+
51
+ Args:
52
+ job: Solver job with operation run_hdl_test.
53
+
54
+ Returns:
55
+ Checked mapping with binary, sources, top, timeout.
56
+
57
+ Raises:
58
+ AdapterError: unsupported_operation, invalid_command
59
+ for a bad top, invalid_reference for missing
60
+ sources, missing_dependency when ghdl is absent.
61
+ """
62
+ if job.get("operation") != OPERATION:
63
+ raise AdapterError(
64
+ "unsupported_operation",
65
+ f"GHDL handles {OPERATION}, got {job.get('operation')}.")
66
+ top = job.get("top")
67
+ if not isinstance(top, str) or not top:
68
+ raise AdapterError("invalid_command", f"Bad top: {top!r}.")
69
+ sources = job.get("sources") or []
70
+ missing = [s for s in sources if not Path(s).is_file()]
71
+ if not sources or missing:
72
+ raise AdapterError(
73
+ "invalid_reference", f"Sources not found: {missing or sources}.")
74
+ binary = shutil.which(_TOOL)
75
+ if binary is None:
76
+ raise AdapterError(
77
+ "missing_dependency",
78
+ "BLOCKED-needs-host: ghdl not on PATH.")
79
+ timeout = job.get("timeout_s", 120)
80
+ if (not isinstance(timeout, (int, float))
81
+ or isinstance(timeout, bool) or not timeout > 0):
82
+ raise AdapterError("invalid_command", f"Bad timeout: {timeout!r}.")
83
+ return {"binary": binary, "sources": list(sources), "top": top,
84
+ "timeout_s": timeout}
85
+
86
+ def execute(self, job):
87
+ """Analyze, elaborate, and run the top under a timeout.
88
+
89
+ Args:
90
+ job: Solver job with operation run_hdl_test.
91
+
92
+ Returns:
93
+ Native output with exit_code, stdout, stderr, top.
94
+ """
95
+ checked = self.preflight(job)
96
+ workdir = job.get("workdir", ".")
97
+ if not Path(workdir).is_dir():
98
+ raise AdapterError(
99
+ "invalid_reference", f"Workdir not found: {workdir}.")
100
+ try:
101
+ for src in checked["sources"]:
102
+ proc = subprocess.run(
103
+ [checked["binary"], "-a", src], capture_output=True,
104
+ text=True, timeout=checked["timeout_s"], cwd=workdir)
105
+ if proc.returncode != 0:
106
+ return {"exit_code": proc.returncode,
107
+ "stdout": proc.stdout, "stderr": proc.stderr,
108
+ "top": checked["top"]}
109
+ proc = subprocess.run(
110
+ [checked["binary"], "-e", checked["top"]],
111
+ capture_output=True, text=True,
112
+ timeout=checked["timeout_s"], cwd=workdir)
113
+ if proc.returncode != 0:
114
+ return {"exit_code": proc.returncode,
115
+ "stdout": proc.stdout, "stderr": proc.stderr,
116
+ "top": checked["top"]}
117
+ proc = subprocess.run(
118
+ [checked["binary"], "-r", checked["top"]],
119
+ capture_output=True, text=True,
120
+ timeout=checked["timeout_s"], cwd=workdir)
121
+ except subprocess.TimeoutExpired as exc:
122
+ raise AdapterError(
123
+ "runner_unavailable", "GHDL run timed out.") from exc
124
+ return {"exit_code": proc.returncode, "stdout": proc.stdout,
125
+ "stderr": proc.stderr, "top": checked["top"]}
126
+
127
+ def parse(self, native_output):
128
+ """Parse ghdl assert lines; errors fail, notes do not.
129
+
130
+ Args:
131
+ native_output: Native output with sim stdout.
132
+
133
+ Returns:
134
+ {total, violations[], source}.
135
+ """
136
+ violations = []
137
+ for line in (native_output.get("stdout") or "").splitlines():
138
+ match = _ASSERT_RE.match(line.strip())
139
+ if match is None:
140
+ continue
141
+ path, line_no, _col, _time, _kind, sev, msg = match.groups()
142
+ if sev.lower() in ("note",):
143
+ continue
144
+ violations.append({"rule": "assert_" + sev.lower(),
145
+ "severity": ("error" if sev.lower() in
146
+ ("error", "failure")
147
+ else "warning"),
148
+ "message": msg[:300], "file": path,
149
+ "line": int(line_no)})
150
+ if "simulation failed" in (native_output.get("stdout") or ""):
151
+ if not violations:
152
+ violations.append({"rule": "sim_fail", "severity": "error",
153
+ "message": "GHDL simulation failed."})
154
+ return {"total": len(violations), "violations": violations,
155
+ "source": "stdout"}
@@ -0,0 +1,178 @@
1
+ """HDL-sim v1 normalizer: v0 parse output to a ResultBundle.
2
+
3
+ Maps the ``run_hdl_test`` parse shape (``violations[]`` with
4
+ rule, severity, message, file/line, and optional sim_time) onto a
5
+ typed violations table, bound views, and findings. Pure function
6
+ of the parsed output: no I/O, no tool calls, deterministic ids
7
+ derived from the run id. A clean sim yields an empty table and no
8
+ findings.
9
+ """
10
+
11
+ TASK = "run_hdl_test"
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 _location(violation):
35
+ """Render one file:line location string for a violation.
36
+
37
+ Args:
38
+ violation: v0 violation with optional file and line.
39
+
40
+ Returns:
41
+ Location text, or an empty string when unlocated.
42
+ """
43
+ file = violation.get("file") or ""
44
+ line = violation.get("line")
45
+ if file and line is not None:
46
+ return f"{file}:{line}"
47
+ return file or ""
48
+
49
+
50
+ def _provenance(project_id, run_id, tools, provenance):
51
+ """Build ResultProvenance, defaulting to a placeholder closure.
52
+
53
+ Args:
54
+ project_id: Project the run belongs to.
55
+ run_id: Run the bundle describes.
56
+ tools: Tool ingredient mappings (id, version, digest?).
57
+ provenance: Full provenance mapping when the caller has
58
+ one; a deterministic placeholder otherwise.
59
+
60
+ Returns:
61
+ Provenance mapping valid against the v1 schema.
62
+ """
63
+ if provenance is not None:
64
+ return provenance
65
+ ref = {"scope": "project", "id": project_id, "revision": "r1"}
66
+ return {
67
+ "snapshot": dict(ref),
68
+ "input_lock": dict(ref),
69
+ "closure_hash": "0" * 64,
70
+ "tools": [
71
+ {
72
+ "id": item["id"],
73
+ "version": item.get("version", ""),
74
+ "digest": item.get("digest", "0" * 64),
75
+ }
76
+ for item in tools
77
+ ],
78
+ "parents": [],
79
+ }
80
+
81
+
82
+ def normalize(parsed, *, run_id, project_id="project", target=None,
83
+ tools=(), provenance=None):
84
+ """Normalize one hdl-sim parse output to a v1 ResultBundle.
85
+
86
+ Violation rows become a table dataset (rule, severity,
87
+ message, location) plus a bound table view; every violation
88
+ also becomes a finding with a capped message.
89
+
90
+ Args:
91
+ parsed: v0 parse mapping with ``violations[]``.
92
+ run_id: Run the bundle describes (anchors all ids).
93
+ project_id: Project the run belongs to.
94
+ target: v1 target mapping; project kind by default.
95
+ tools: Tool ingredient mappings for provenance.
96
+ provenance: Full provenance mapping when the caller has
97
+ one; a deterministic placeholder otherwise.
98
+
99
+ Returns:
100
+ Schema-valid ResultBundle mapping.
101
+ """
102
+ violations = parsed.get("violations") or []
103
+ table_id = f"{run_id}:dataset:violations"
104
+ datasets = [
105
+ {
106
+ "id": table_id,
107
+ "kind": "table",
108
+ "row_count": len(violations),
109
+ "columns": [
110
+ {
111
+ "id": f"{table_id}:rule",
112
+ "name": "rule",
113
+ "dtype": "utf8",
114
+ "unit": "1",
115
+ "data": [v.get("rule", "") for v in violations],
116
+ },
117
+ {
118
+ "id": f"{table_id}:severity",
119
+ "name": "severity",
120
+ "dtype": "utf8",
121
+ "unit": "1",
122
+ "data": [v.get("severity", "") for v in violations],
123
+ },
124
+ {
125
+ "id": f"{table_id}:message",
126
+ "name": "message",
127
+ "dtype": "utf8",
128
+ "unit": "1",
129
+ "data": [_cap(v.get("message", "")) for v in violations],
130
+ },
131
+ {
132
+ "id": f"{table_id}:location",
133
+ "name": "location",
134
+ "dtype": "utf8",
135
+ "unit": "1",
136
+ "data": [_location(v) for v in violations],
137
+ },
138
+ ],
139
+ "conditions": [],
140
+ }
141
+ ]
142
+ views = [
143
+ {
144
+ "id": f"{run_id}:view:violations",
145
+ "kind": "table",
146
+ "dataset_id": table_id,
147
+ "name": "violations",
148
+ }
149
+ ]
150
+ findings = [
151
+ {
152
+ "id": f"{run_id}:finding:{index}",
153
+ "severity": _SEVERITY.get(v.get("severity"), "info"),
154
+ "code": v.get("rule", "sim"),
155
+ "message": _cap(v.get("message", "")),
156
+ }
157
+ for index, v in enumerate(violations)
158
+ ]
159
+ return {
160
+ "schema": "rocketeda/results/v1",
161
+ "ref": {"scope": "project", "id": run_id, "revision": "r1"},
162
+ "run_id": run_id,
163
+ "task": TASK,
164
+ "target": dict(target)
165
+ if target is not None
166
+ else {"project_id": project_id, "kind": "project"},
167
+ "state": "complete",
168
+ "freshness": "current",
169
+ "convergence": "not_applicable",
170
+ "provenance": _provenance(project_id, run_id, tools, provenance),
171
+ "datasets": datasets,
172
+ "views": views,
173
+ "measurements": [],
174
+ "findings": findings,
175
+ "artifacts": [],
176
+ "logs": [],
177
+ "gaps": [],
178
+ }
@@ -0,0 +1,167 @@
1
+ """Verilator provider: run_hdl_test with SARIF plus lint text."""
2
+
3
+ import json
4
+ import re
5
+ import shutil
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_hdl_test"
14
+ _TOOL = "verilator"
15
+ _DIAG_RE = re.compile(
16
+ r"^%((?:Error)|(Warning)-([A-Z0-9_]+)):\s*(.+?):(\d+):(\d+):")
17
+
18
+
19
+ class VerilatorAdapter(ToolAdapter):
20
+ """run_hdl_test via verilator: lint plus sim harness verdicts.
21
+
22
+ Serves the run_hdl_test operation with tool `verilator`.
23
+ Lint runs `--lint-only --diagnostics-sarif`; the SARIF file is
24
+ the first channel, `%Error`/`%Warning` stderr the fallback
25
+ (xml-only is dead since 5.046 — json-only/SARIF only).
26
+
27
+ Job keys:
28
+ sources (required): Verilog source paths.
29
+ top: Top module name.
30
+ workdir: Run directory (default ".").
31
+ timeout_s: Budget in seconds (default 120).
32
+ """
33
+
34
+ def __init__(self, runner=None):
35
+ self._runner = runner
36
+
37
+ def capabilities(self, target):
38
+ """Report the served operation and tool.
39
+
40
+ Args:
41
+ target: Target the capabilities are reported for.
42
+
43
+ Returns:
44
+ Capability mapping with operations and tool.
45
+ """
46
+ return {"operations": [OPERATION], "tool": _TOOL,
47
+ "modes": ["lint", "sim"]}
48
+
49
+ def preflight(self, job):
50
+ """Check operation, sources, and verilator presence.
51
+
52
+ Args:
53
+ job: Solver job with operation run_hdl_test.
54
+
55
+ Returns:
56
+ Checked mapping with binary, sources, timeout.
57
+
58
+ Raises:
59
+ AdapterError: unsupported_operation, invalid_reference
60
+ for missing sources, missing_dependency when
61
+ verilator is absent (BLOCKED-needs-host).
62
+ """
63
+ if job.get("operation") != OPERATION:
64
+ raise AdapterError(
65
+ "unsupported_operation",
66
+ f"Verilator handles {OPERATION}, got {job.get('operation')}.")
67
+ sources = job.get("sources") or []
68
+ missing = [s for s in sources if not Path(s).is_file()]
69
+ if not sources or missing:
70
+ raise AdapterError(
71
+ "invalid_reference", f"Sources not found: {missing or sources}.")
72
+ binary = shutil.which(_TOOL)
73
+ if binary is None:
74
+ raise AdapterError(
75
+ "missing_dependency",
76
+ "BLOCKED-needs-host: verilator not on PATH.")
77
+ timeout = job.get("timeout_s", 120)
78
+ if (not isinstance(timeout, (int, float))
79
+ or isinstance(timeout, bool) or not timeout > 0):
80
+ raise AdapterError("invalid_command", f"Bad timeout: {timeout!r}.")
81
+ return {"binary": binary, "sources": list(sources),
82
+ "top": job.get("top"), "timeout_s": timeout}
83
+
84
+ def execute(self, job):
85
+ """Lint the sources with verilator under a timeout.
86
+
87
+ Args:
88
+ job: Solver job with operation run_hdl_test.
89
+
90
+ Returns:
91
+ Native output with exit_code, stderr, sarif, sources.
92
+ """
93
+ checked = self.preflight(job)
94
+ workdir = job.get("workdir", ".")
95
+ if not Path(workdir).is_dir():
96
+ raise AdapterError(
97
+ "invalid_reference", f"Workdir not found: {workdir}.")
98
+ argv = [checked["binary"], "--lint-only", "--diagnostics-sarif",
99
+ "--Mdir", "obj_dir", *checked["sources"]]
100
+ try:
101
+ proc = subprocess.run(argv, capture_output=True, text=True,
102
+ timeout=checked["timeout_s"], cwd=workdir)
103
+ except subprocess.TimeoutExpired as exc:
104
+ raise AdapterError(
105
+ "runner_unavailable", "Verilator run timed out.") from exc
106
+ sarif = None
107
+ for cand in (Path(workdir) / "obj_dir").glob("*.sarif"):
108
+ try:
109
+ sarif = json.loads(cand.read_text())
110
+ except ValueError:
111
+ sarif = None
112
+ break
113
+ return {"exit_code": proc.returncode, "stdout": proc.stdout,
114
+ "stderr": proc.stderr, "sarif": sarif,
115
+ "sources": checked["sources"]}
116
+
117
+ def parse(self, native_output):
118
+ """Parse SARIF results, else lint stderr diagnostics.
119
+
120
+ Args:
121
+ native_output: Native output with sarif/stderr.
122
+
123
+ Returns:
124
+ {total, violations[], source}.
125
+ """
126
+ sarif = native_output.get("sarif")
127
+ if isinstance(sarif, dict):
128
+ violations = []
129
+ for run in sarif.get("runs") or []:
130
+ for res in run.get("results") or []:
131
+ locs = res.get("locations") or [{}]
132
+ phys = locs[0].get("physicalLocation") or {}
133
+ region = phys.get("region") or {}
134
+ art = phys.get("artifactLocation") or {}
135
+ violations.append({
136
+ "rule": res.get("ruleId", "verilator"),
137
+ "severity": ("error" if res.get("level") == "error"
138
+ else "warning"),
139
+ "message": ((res.get("message") or {}).get("text")
140
+ or "Verilator diagnostic."),
141
+ "file": art.get("uri", ""),
142
+ "line": int(region.get("startLine", 1)),
143
+ })
144
+ errors = [v for v in violations if v["severity"] == "error"]
145
+ if native_output.get("exit_code", 0) != 0 and not violations:
146
+ violations = [{"rule": "lint_error", "severity": "error",
147
+ "message": "Verilator exited "
148
+ f"{native_output.get('exit_code')}."}]
149
+ return {"total": len(violations), "violations": violations,
150
+ "source": "sarif"}
151
+ violations = []
152
+ for line in (native_output.get("stderr") or "").splitlines():
153
+ match = _DIAG_RE.match(line)
154
+ if match is None:
155
+ continue
156
+ kind, _warn, code, path, line_no, _col = match.groups()
157
+ violations.append({
158
+ "rule": code or ("lint_error"
159
+ if kind == "Error" else "lint_warning"),
160
+ "severity": "error" if kind == "Error" else "warning",
161
+ "message": line[:300], "file": path, "line": int(line_no)})
162
+ if native_output.get("exit_code", 0) != 0 and not violations:
163
+ violations = [{"rule": "lint_error", "severity": "error",
164
+ "message": "Verilator exited "
165
+ f"{native_output.get('exit_code')}."}]
166
+ return {"total": len(violations), "violations": violations,
167
+ "source": "stderr"}
@@ -0,0 +1,15 @@
1
+ # HTTP JSON command transport
2
+
3
+ HttpTransport.handle parses one JSON object containing project_id, operation, optional arguments, and optional request_id. It enforces an optional bound project and delegates to Engine.submit. `serve_http` wraps the same transport in a FastAPI app on uvicorn: POST a JSON envelope to `/` for an admission ack or query result, GET `/health` for a liveness probe, GET `/events` for the runtime lifecycle log as Server-Sent Events (PUSH-EVENTS; pass a runtime to `serve_http`/`create_server`/`create_app` to bind it — replay since `cursor`, then live frames with heartbeat comments). Without a bound runtime `/events` refuses loudly with `runner_unavailable` (503); polling get_events_since stays the watch path there.
4
+
5
+ ## Listener hardening
6
+
7
+ POST `/` requires `Content-Type: application/json` (415 otherwise), refuses bodies over 1 MiB (413), accepts a missing Origin or a loopback one only (403 for foreign origins — the browser-CSRF class), and bounds concurrency at 32 in-flight requests (503 + Retry-After on overflow). Engine errors serialize with their v0 code, message, and fields at the status from the shared code table (`adapters/errors.py`, pinned to the v1 errors catalog); uncoded crashes become a generic 500 with the traceback server-side only. Every request writes one audit line (`METHOD /path -> status`) to the `rocket_engine.http` logger.
8
+
9
+ ## Trust boundary (ADR-006)
10
+
11
+ This adapter is localhost-only with no auth, serving the same caller-blind API as every other transport. The trust boundary is the loopback interface: `serve_http`/`create_server` bind loopback hosts only (127.0.0.1 by default; any 127/8 address, `localhost`, or `::1`) and refuse any non-loopback bind with `non_loopback_bind_denied`. Startup prints a banner stating the model. Remote/multi-user access needs a separate authenticated adapter — never this one with a flag. See the [HTTP boundary decision](../../../docs/adr/006-localhost-only-http.md) and the [provision plan](../../../docs/provision-plan.md#loopback-only-http-no-remote-deploy).
12
+
13
+ ## Boundaries
14
+
15
+ The adapter owns JSON parsing, request IDs, and project-scope checks. It does not own operation validation, state, solver selection, or verdicts. Command shapes are shared with [interfaces](../../interfaces/README.md); the parent transport map is in [adapters](../README.md).