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,246 @@
1
+ """MCP pull path over the cursor event log (ADR-017 D7).
2
+
3
+ The MCP port is pull-by-default: the client polls the existing
4
+ cursor log from a stale cursor and converges, never waiting on a
5
+ server push. Push stays a per-port opt-in: subscribing without
6
+ the explicit flag is refused, never silently armed.
7
+
8
+ Position dedupe reuses the bus-owned PositionGuard (the one
9
+ shared helper every port applies through): the pull path reads
10
+ the shared cursor log through the wired get_events_since query
11
+ every transport serves, so there is no second event shape and
12
+ no duplicated seam — receiving one page twice re-renders the
13
+ same state.
14
+ """
15
+
16
+ from ...execution.ports import PositionGuard
17
+ from .. import api
18
+ from ..errors import AdapterError
19
+
20
+
21
+ def poll_events(engine, project_id, cursor, require_contiguous_from=None,
22
+ request_id=None):
23
+ """Poll one page of ledger events after a cursor.
24
+
25
+ The poll rides the wired get_events_since query — the same
26
+ cursor log every transport shares — so a stale cursor
27
+ converges with no diff reconstruction and no snapshot
28
+ pinning.
29
+
30
+ Args:
31
+ engine: Engine the MCP port reads through.
32
+ project_id: Project the cursor belongs to.
33
+ cursor: Only events with a higher seq are returned; 0
34
+ replays the retained log.
35
+ require_contiguous_from: Assert the reader holds
36
+ contiguous history from this seq; an unmet demand
37
+ fails with resync_required instead of gap-hiding.
38
+ request_id: Envelope id; minted when omitted.
39
+
40
+ Returns:
41
+ Page mapping with the events after the cursor, oldest
42
+ first, plus the live cursor.
43
+ """
44
+ arguments = {"cursor": cursor}
45
+ if require_contiguous_from is not None:
46
+ arguments["require_contiguous_from"] = require_contiguous_from
47
+ return api.submit(engine, project_id, "get_events_since",
48
+ arguments, request_id=request_id)
49
+
50
+
51
+ class McpPullClient:
52
+ """Pull-by-default MCP cursor over one project's event log.
53
+
54
+ The client holds its cursor and reuses the bus-owned
55
+ PositionGuard for position-check-then-apply, so
56
+ same-position redelivery applies once through the one
57
+ shared helper every port uses — never a per-client
58
+ reinvention. Bind the port's bus guard (guard_for) and
59
+ port name and the pull path shares one dedupe record
60
+ with the push path. Polling advances the cursor; a
61
+ failed contiguity demand resyncs from the snapshot plus
62
+ the full retained log and resets the position, never
63
+ skipping over a trimmed gap.
64
+
65
+ Args:
66
+ engine: Engine the MCP port reads through.
67
+ project_id: Project the cursor belongs to.
68
+ cursor: Last consumed ledger position; 0 replays the
69
+ retained log.
70
+ guard: PositionGuard to check positions against; the
71
+ port's bound bus guard when the MCP port is
72
+ bus-attached, a client-owned guard otherwise.
73
+ port: Port name positions apply under; the attached
74
+ port name for a bus-bound guard, the project id
75
+ otherwise.
76
+ """
77
+
78
+ def __init__(self, engine, project_id, cursor=0, guard=None,
79
+ port=None):
80
+ self._engine = engine
81
+ self._project_id = project_id
82
+ self._cursor = cursor
83
+ self._applied = cursor
84
+ self._guard = guard if guard is not None else PositionGuard()
85
+ self._owns_guard = guard is None
86
+ self._port = port if port is not None else project_id
87
+
88
+ @property
89
+ def cursor(self):
90
+ """Last consumed ledger position.
91
+
92
+ Returns:
93
+ Cursor the next poll reads after.
94
+ """
95
+ return self._cursor
96
+
97
+ @property
98
+ def applied_position(self):
99
+ """Highest ledger position this client handed over.
100
+
101
+ The guard stays the dedupe authority (it also records
102
+ positions the push path applied); this is the client's
103
+ own high-water mark for cursor bookkeeping.
104
+
105
+ Returns:
106
+ Seq of the last applied event, or the starting
107
+ cursor when nothing applied yet.
108
+ """
109
+ return self._applied
110
+
111
+ def poll(self, require_contiguous=False, request_id=None):
112
+ """Poll the next page after the stored cursor.
113
+
114
+ A stale cursor converges: the page carries every
115
+ retained event after it, and the stored cursor advances
116
+ to the live head. With require_contiguous, the poll
117
+ asserts the reader holds contiguous history from
118
+ cursor + 1; a fresh cursor (0) skips the demand because
119
+ an empty log cannot be non-contiguous, and an unmet
120
+ demand resyncs instead of gap-hiding.
121
+
122
+ Args:
123
+ require_contiguous: Assert contiguous history from
124
+ cursor + 1 and resync when the demand cannot
125
+ be met (a trimmed gap).
126
+ request_id: Envelope id; minted when omitted.
127
+
128
+ Returns:
129
+ Page mapping with events, the live cursor, and
130
+ whether a resync served it.
131
+
132
+ Raises:
133
+ ProjectError: invalid_command for a malformed
134
+ cursor. An unmet contiguity demand never escapes
135
+ — it resyncs instead.
136
+ """
137
+ demand = self._cursor + 1 if require_contiguous and self._cursor else None
138
+ try:
139
+ page = poll_events(self._engine, self._project_id,
140
+ self._cursor,
141
+ require_contiguous_from=demand,
142
+ request_id=request_id)
143
+ except Exception as exc:
144
+ if getattr(exc, "code", None) != "resync_required":
145
+ raise
146
+ return self.resync(request_id=request_id)
147
+ self._cursor = page["cursor"]
148
+ return {"events": page["events"], "cursor": page["cursor"],
149
+ "resynced": False}
150
+
151
+ def apply(self, events, handler):
152
+ """Apply one page idempotently: position check, then apply.
153
+
154
+ The check runs through the bus-owned PositionGuard, so
155
+ a position the push path already applied (or this page
156
+ before) applies once per port. Events are offered in
157
+ seq order; events without an integer seq carry no
158
+ ledger position, so the guard cannot admit them and
159
+ they are skipped. A raising handler leaves its
160
+ position unrecorded, so redelivery retries it.
161
+
162
+ Args:
163
+ events: Page of ledger events, oldest first.
164
+ handler: Called once per newly positioned event.
165
+
166
+ Returns:
167
+ Count of events handed to the handler.
168
+ """
169
+ ordered = sorted(
170
+ (e for e in events if isinstance(e.get("seq"), int)
171
+ and not isinstance(e.get("seq"), bool)),
172
+ key=lambda e: e["seq"],
173
+ )
174
+ applied = 0
175
+ for event in ordered:
176
+ done, _ = self._guard.apply_once(
177
+ self._port, event["seq"], handler, event)
178
+ if not done:
179
+ continue
180
+ if event["seq"] > self._applied:
181
+ self._applied = event["seq"]
182
+ applied += 1
183
+ return applied
184
+
185
+ def resync(self, request_id=None):
186
+ """Resync from the snapshot plus the full retained log.
187
+
188
+ Drops the stale position, re-reads the live head, and
189
+ replays the retained log from 0 so the caller
190
+ re-renders current state. The applied position resets
191
+ so the replayed page applies in full through apply;
192
+ running the onboarding ritual twice stays safe. A
193
+ client-owned guard is replaced with a fresh one; a
194
+ bus-bound guard is left alone — bus resync rebinds
195
+ through the bus (a new onboarding, per ADR-017), so
196
+ the shared dedupe record is never wiped here.
197
+
198
+ Args:
199
+ request_id: Envelope id; minted when omitted.
200
+
201
+ Returns:
202
+ Page mapping with the full retained events, the
203
+ live cursor, and resynced True.
204
+ """
205
+ self._cursor = 0
206
+ self._applied = 0
207
+ if self._owns_guard:
208
+ self._guard = PositionGuard()
209
+ page = poll_events(self._engine, self._project_id, 0,
210
+ request_id=request_id)
211
+ self._cursor = page["cursor"]
212
+ return {"events": page["events"], "cursor": page["cursor"],
213
+ "resynced": True}
214
+
215
+
216
+ def subscribe_push(runtime, send, enable_push=False):
217
+ """Push lifecycle events to one subscriber, opt-in only.
218
+
219
+ Pull is the default; this is the per-port push escape hatch
220
+ (ADR-017 D7). Without the explicit flag the call is
221
+ refused and no listener is registered — the AI wakes on
222
+ its own polls, never on UI churn.
223
+
224
+ Args:
225
+ runtime: EngineRuntime emitting lifecycle events.
226
+ send: Called synchronously with each built
227
+ notification mapping.
228
+ enable_push: Explicit opt-in; push stays off unless
229
+ this is True.
230
+
231
+ Returns:
232
+ Unsubscribe callable removing the listener.
233
+
234
+ Raises:
235
+ AdapterError: unsupported_operation when push was not
236
+ explicitly opted into.
237
+ """
238
+ if not enable_push:
239
+ raise AdapterError(
240
+ "unsupported_operation",
241
+ "MCP push is opt-in only (ADR-017 D7): pass "
242
+ "enable_push=True, or keep polling the cursor log.",
243
+ )
244
+ from .server import subscribe_runtime
245
+
246
+ return subscribe_runtime(runtime, send)