@senpi/runtime 3.0.135 → 3.0.138

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 (272) hide show
  1. package/dist/actions/action-events.d.ts +7 -21
  2. package/dist/actions/action-events.d.ts.map +1 -1
  3. package/dist/actions/action-events.js +26 -54
  4. package/dist/actions/action-events.js.map +1 -1
  5. package/dist/actions/action-runtime-module.d.ts.map +1 -1
  6. package/dist/actions/action-runtime-module.js +7 -38
  7. package/dist/actions/action-runtime-module.js.map +1 -1
  8. package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
  9. package/dist/actions/close-position/close-position.action.js +15 -122
  10. package/dist/actions/close-position/close-position.action.js.map +1 -1
  11. package/dist/actions/decision-engine.d.ts.map +1 -1
  12. package/dist/actions/decision-engine.js +2 -15
  13. package/dist/actions/decision-engine.js.map +1 -1
  14. package/dist/actions/event-derivation.d.ts +2 -71
  15. package/dist/actions/event-derivation.d.ts.map +1 -1
  16. package/dist/actions/event-derivation.js +8 -251
  17. package/dist/actions/event-derivation.js.map +1 -1
  18. package/dist/actions/open-position/open-position.action.d.ts +2 -12
  19. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  20. package/dist/actions/open-position/open-position.action.js +41 -141
  21. package/dist/actions/open-position/open-position.action.js.map +1 -1
  22. package/dist/actions/position-tracker/position-tracker.action.d.ts +1 -2
  23. package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
  24. package/dist/actions/position-tracker/position-tracker.action.js +17 -62
  25. package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
  26. package/dist/actions/store/types.d.ts +2 -9
  27. package/dist/actions/store/types.d.ts.map +1 -1
  28. package/dist/actions/store/types.js.map +1 -1
  29. package/dist/actions/types.d.ts +0 -13
  30. package/dist/actions/types.d.ts.map +1 -1
  31. package/dist/actions/types.js +0 -13
  32. package/dist/actions/types.js.map +1 -1
  33. package/dist/cli/guide-commands.d.ts.map +1 -1
  34. package/dist/cli/guide-commands.js +7 -4
  35. package/dist/cli/guide-commands.js.map +1 -1
  36. package/dist/constants/dsl/index.d.ts +0 -6
  37. package/dist/constants/dsl/index.d.ts.map +1 -1
  38. package/dist/constants/dsl/index.js +0 -6
  39. package/dist/constants/dsl/index.js.map +1 -1
  40. package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
  41. package/dist/dsl/engine-client/event-mapping.js +0 -14
  42. package/dist/dsl/engine-client/event-mapping.js.map +1 -1
  43. package/dist/dsl/events/build-closed-event.d.ts +1 -16
  44. package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
  45. package/dist/dsl/events/build-closed-event.js +0 -6
  46. package/dist/dsl/events/build-closed-event.js.map +1 -1
  47. package/dist/dsl/events/handlers.d.ts +0 -7
  48. package/dist/dsl/events/handlers.d.ts.map +1 -1
  49. package/dist/dsl/events/handlers.js +7 -37
  50. package/dist/dsl/events/handlers.js.map +1 -1
  51. package/dist/dsl/monitor/create-monitor.d.ts +2 -6
  52. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  53. package/dist/dsl/monitor/create-monitor.js +9 -82
  54. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  55. package/dist/dsl/monitor/dsl-failure-events.d.ts +2 -45
  56. package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
  57. package/dist/dsl/monitor/dsl-failure-events.js +3 -52
  58. package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
  59. package/dist/dsl/monitor/process-one-position.d.ts +0 -1
  60. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  61. package/dist/dsl/monitor/process-one-position.js +36 -159
  62. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  63. package/dist/dsl/plugin/index.d.ts +2 -8
  64. package/dist/dsl/plugin/index.d.ts.map +1 -1
  65. package/dist/dsl/plugin/index.js +6 -8
  66. package/dist/dsl/plugin/index.js.map +1 -1
  67. package/dist/health/types.d.ts +0 -13
  68. package/dist/health/types.d.ts.map +1 -1
  69. package/dist/health/types.js +0 -12
  70. package/dist/health/types.js.map +1 -1
  71. package/dist/index.d.ts +4 -27
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +41 -271
  74. package/dist/index.js.map +1 -1
  75. package/dist/runtime/action-registry.d.ts.map +1 -1
  76. package/dist/runtime/action-registry.js +3 -4
  77. package/dist/runtime/action-registry.js.map +1 -1
  78. package/dist/runtime/domain-events.d.ts.map +1 -1
  79. package/dist/runtime/domain-events.js +0 -7
  80. package/dist/runtime/domain-events.js.map +1 -1
  81. package/dist/runtime/lifecycle-events.d.ts +3 -15
  82. package/dist/runtime/lifecycle-events.d.ts.map +1 -1
  83. package/dist/runtime/lifecycle-events.js +3 -17
  84. package/dist/runtime/lifecycle-events.js.map +1 -1
  85. package/dist/runtime/lifecycle-reaper.d.ts +6 -4
  86. package/dist/runtime/lifecycle-reaper.d.ts.map +1 -1
  87. package/dist/runtime/lifecycle-reaper.js +6 -4
  88. package/dist/runtime/lifecycle-reaper.js.map +1 -1
  89. package/dist/runtime/reconcile-run.d.ts +3 -13
  90. package/dist/runtime/reconcile-run.d.ts.map +1 -1
  91. package/dist/runtime/reconcile-run.js +3 -4
  92. package/dist/runtime/reconcile-run.js.map +1 -1
  93. package/dist/runtime/reconcile-state.d.ts +0 -8
  94. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  95. package/dist/runtime/reconcile-state.js +1 -4
  96. package/dist/runtime/reconcile-state.js.map +1 -1
  97. package/dist/runtime/risk-guard.d.ts +20 -1
  98. package/dist/runtime/risk-guard.d.ts.map +1 -1
  99. package/dist/runtime/risk-guard.js +47 -6
  100. package/dist/runtime/risk-guard.js.map +1 -1
  101. package/dist/runtime/run.d.ts +0 -1
  102. package/dist/runtime/run.d.ts.map +1 -1
  103. package/dist/runtime/run.js.map +1 -1
  104. package/dist/runtime/runtime-schema.js +3 -3
  105. package/dist/runtime/runtime-schema.js.map +1 -1
  106. package/dist/runtime/runtime.d.ts +3 -2
  107. package/dist/runtime/runtime.d.ts.map +1 -1
  108. package/dist/runtime/runtime.js +30 -37
  109. package/dist/runtime/runtime.js.map +1 -1
  110. package/dist/runtime/strategy-lifecycle.d.ts +108 -2
  111. package/dist/runtime/strategy-lifecycle.d.ts.map +1 -1
  112. package/dist/runtime/strategy-lifecycle.js +325 -4
  113. package/dist/runtime/strategy-lifecycle.js.map +1 -1
  114. package/dist/runtime/supervisor/long-running-supervisor.d.ts +0 -13
  115. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  116. package/dist/runtime/supervisor/long-running-supervisor.js +0 -1
  117. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  118. package/dist/runtime/validate-dsl-position-tracking.d.ts.map +1 -1
  119. package/dist/runtime/validate-dsl-position-tracking.js +1 -2
  120. package/dist/runtime/validate-dsl-position-tracking.js.map +1 -1
  121. package/dist/runtime-api/handlers/ingest-signals.d.ts.map +1 -1
  122. package/dist/runtime-api/handlers/ingest-signals.js +0 -1
  123. package/dist/runtime-api/handlers/ingest-signals.js.map +1 -1
  124. package/dist/runtime-api/handlers/types.d.ts +0 -5
  125. package/dist/runtime-api/handlers/types.d.ts.map +1 -1
  126. package/dist/runtime-api/intake/intake-events.d.ts +1 -1
  127. package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
  128. package/dist/runtime-api/intake/intake-events.js +3 -9
  129. package/dist/runtime-api/intake/intake-events.js.map +1 -1
  130. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  131. package/dist/runtime-api/intake/intake-mount.js +19 -4
  132. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  133. package/dist/runtime-api/intake/process-error-post.d.ts +1 -6
  134. package/dist/runtime-api/intake/process-error-post.d.ts.map +1 -1
  135. package/dist/runtime-api/intake/process-error-post.js +1 -5
  136. package/dist/runtime-api/intake/process-error-post.js.map +1 -1
  137. package/dist/runtime-api/intake/process-signal-post.d.ts +1 -7
  138. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  139. package/dist/runtime-api/intake/process-signal-post.js +0 -5
  140. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  141. package/dist/runtime-api/intake/scaffold-error-router.js.map +1 -1
  142. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  143. package/dist/runtime-api/intake/scanner-launcher.js +0 -15
  144. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  145. package/dist/runtime-api/intake/types.d.ts +0 -55
  146. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  147. package/dist/runtime-api/routes/signals.d.ts +0 -2
  148. package/dist/runtime-api/routes/signals.d.ts.map +1 -1
  149. package/dist/runtime-api/routes/signals.js +1 -5
  150. package/dist/runtime-api/routes/signals.js.map +1 -1
  151. package/dist/runtime-api/routes/signals.schema.d.ts +0 -43
  152. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  153. package/dist/runtime-api/routes/signals.schema.js +0 -43
  154. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  155. package/dist/runtime-api/server.d.ts +1 -7
  156. package/dist/runtime-api/server.d.ts.map +1 -1
  157. package/dist/runtime-api/server.js +0 -1
  158. package/dist/runtime-api/server.js.map +1 -1
  159. package/dist/scanners/engine/engine.d.ts +1 -22
  160. package/dist/scanners/engine/engine.d.ts.map +1 -1
  161. package/dist/scanners/engine/engine.js +11 -78
  162. package/dist/scanners/engine/engine.js.map +1 -1
  163. package/dist/scanners/engine/lifecycle.d.ts +1 -7
  164. package/dist/scanners/engine/lifecycle.d.ts.map +1 -1
  165. package/dist/scanners/events.d.ts +0 -4
  166. package/dist/scanners/events.d.ts.map +1 -1
  167. package/dist/scanners/events.js +0 -2
  168. package/dist/scanners/events.js.map +1 -1
  169. package/dist/scanners/external/scaffold/delivery.py +25 -194
  170. package/dist/scanners/external/scaffold/scaffold.py +31 -172
  171. package/dist/scanners/external/scaffold/senpi_mcp.py +8 -150
  172. package/dist/scanners/external-scanner-receiver.d.ts +0 -17
  173. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  174. package/dist/scanners/external-scanner-receiver.js +0 -8
  175. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  176. package/dist/scanners/runtime-module.d.ts +0 -1
  177. package/dist/scanners/runtime-module.d.ts.map +1 -1
  178. package/dist/scanners/runtime-module.js +9 -66
  179. package/dist/scanners/runtime-module.js.map +1 -1
  180. package/dist/telemetry/attributes.d.ts +0 -20
  181. package/dist/telemetry/attributes.d.ts.map +1 -1
  182. package/dist/telemetry/attributes.js +0 -24
  183. package/dist/telemetry/attributes.js.map +1 -1
  184. package/dist/telemetry/event-builders.d.ts +6 -81
  185. package/dist/telemetry/event-builders.d.ts.map +1 -1
  186. package/dist/telemetry/event-builders.js +17 -141
  187. package/dist/telemetry/event-builders.js.map +1 -1
  188. package/dist/telemetry/event-store.d.ts +0 -6
  189. package/dist/telemetry/event-store.d.ts.map +1 -1
  190. package/dist/telemetry/event-store.js +8 -12
  191. package/dist/telemetry/event-store.js.map +1 -1
  192. package/dist/telemetry/metrics.d.ts +2 -14
  193. package/dist/telemetry/metrics.d.ts.map +1 -1
  194. package/dist/telemetry/metrics.js +4 -19
  195. package/dist/telemetry/metrics.js.map +1 -1
  196. package/dist/telemetry/spans/redact-pii.d.ts.map +1 -1
  197. package/dist/telemetry/spans/redact-pii.js +2 -18
  198. package/dist/telemetry/spans/redact-pii.js.map +1 -1
  199. package/dist/telemetry/tracing.d.ts +0 -9
  200. package/dist/telemetry/tracing.d.ts.map +1 -1
  201. package/dist/telemetry/tracing.js +0 -36
  202. package/dist/telemetry/tracing.js.map +1 -1
  203. package/dist/types/dsl/index.d.ts +0 -48
  204. package/dist/types/dsl/index.d.ts.map +1 -1
  205. package/dist/types/scanner.d.ts +0 -15
  206. package/dist/types/scanner.d.ts.map +1 -1
  207. package/dist/utils/event-catalog.d.ts +0 -202
  208. package/dist/utils/event-catalog.d.ts.map +1 -1
  209. package/dist/utils/event-catalog.js +0 -26
  210. package/dist/utils/event-catalog.js.map +1 -1
  211. package/dist/utils/logger.d.ts +1 -4
  212. package/dist/utils/logger.d.ts.map +1 -1
  213. package/dist/utils/logger.js.map +1 -1
  214. package/dist/validate/recipe-checks.d.ts.map +1 -1
  215. package/dist/validate/recipe-checks.js +2 -2
  216. package/dist/validate/recipe-checks.js.map +1 -1
  217. package/openclaw.plugin.json +1 -1
  218. package/package.json +1 -1
  219. package/dist/actions/skipped-signal-results.d.ts +0 -25
  220. package/dist/actions/skipped-signal-results.d.ts.map +0 -1
  221. package/dist/actions/skipped-signal-results.js +0 -71
  222. package/dist/actions/skipped-signal-results.js.map +0 -1
  223. package/dist/runtime-api/intake/ingest-request.d.ts +0 -17
  224. package/dist/runtime-api/intake/ingest-request.d.ts.map +0 -1
  225. package/dist/runtime-api/intake/ingest-request.js +0 -34
  226. package/dist/runtime-api/intake/ingest-request.js.map +0 -1
  227. package/dist/runtime-api/intake/scaffold-tick-router.d.ts +0 -39
  228. package/dist/runtime-api/intake/scaffold-tick-router.d.ts.map +0 -1
  229. package/dist/runtime-api/intake/scaffold-tick-router.js +0 -122
  230. package/dist/runtime-api/intake/scaffold-tick-router.js.map +0 -1
  231. package/dist/scanners/external/scaffold/tick_facts.py +0 -130
  232. package/dist/scanners/external/scaffold/ulid.py +0 -58
  233. package/dist/telemetry/dsl-positions.d.ts +0 -111
  234. package/dist/telemetry/dsl-positions.d.ts.map +0 -1
  235. package/dist/telemetry/dsl-positions.js +0 -118
  236. package/dist/telemetry/dsl-positions.js.map +0 -1
  237. package/dist/telemetry/live-telemetry.d.ts +0 -257
  238. package/dist/telemetry/live-telemetry.d.ts.map +0 -1
  239. package/dist/telemetry/live-telemetry.js +0 -959
  240. package/dist/telemetry/live-telemetry.js.map +0 -1
  241. package/dist/telemetry/scanner-stats.d.ts +0 -143
  242. package/dist/telemetry/scanner-stats.d.ts.map +0 -1
  243. package/dist/telemetry/scanner-stats.js +0 -426
  244. package/dist/telemetry/scanner-stats.js.map +0 -1
  245. package/dist/telemetry/scanner-tick-feed.d.ts +0 -87
  246. package/dist/telemetry/scanner-tick-feed.d.ts.map +0 -1
  247. package/dist/telemetry/scanner-tick-feed.js +0 -48
  248. package/dist/telemetry/scanner-tick-feed.js.map +0 -1
  249. package/dist/telemetry/strategy-topology.d.ts +0 -99
  250. package/dist/telemetry/strategy-topology.d.ts.map +0 -1
  251. package/dist/telemetry/strategy-topology.js +0 -93
  252. package/dist/telemetry/strategy-topology.js.map +0 -1
  253. package/dist/telemetry/telemetry-publisher.d.ts +0 -83
  254. package/dist/telemetry/telemetry-publisher.d.ts.map +0 -1
  255. package/dist/telemetry/telemetry-publisher.js +0 -256
  256. package/dist/telemetry/telemetry-publisher.js.map +0 -1
  257. package/dist/telemetry/tick-frame.d.ts +0 -212
  258. package/dist/telemetry/tick-frame.d.ts.map +0 -1
  259. package/dist/telemetry/tick-frame.js +0 -421
  260. package/dist/telemetry/tick-frame.js.map +0 -1
  261. package/dist/telemetry/timeline-buffer.d.ts +0 -95
  262. package/dist/telemetry/timeline-buffer.d.ts.map +0 -1
  263. package/dist/telemetry/timeline-buffer.js +0 -237
  264. package/dist/telemetry/timeline-buffer.js.map +0 -1
  265. package/dist/utils/telemetry-required-log.d.ts +0 -60
  266. package/dist/utils/telemetry-required-log.d.ts.map +0 -1
  267. package/dist/utils/telemetry-required-log.js +0 -105
  268. package/dist/utils/telemetry-required-log.js.map +0 -1
  269. package/dist/utils/ulid.d.ts +0 -9
  270. package/dist/utils/ulid.d.ts.map +0 -1
  271. package/dist/utils/ulid.js +0 -43
  272. package/dist/utils/ulid.js.map +0 -1
package/dist/index.js CHANGED
@@ -64,11 +64,7 @@ import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
64
64
  import { wireRuntime, intakeUrlFor, selectScanners } from "./runtime-api/intake/intake-mount.js";
65
65
  import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
66
66
  import { buildScaffoldErrorRouter } from "./runtime-api/intake/scaffold-error-router.js";
67
- import { buildScaffoldTickRouter } from "./runtime-api/intake/scaffold-tick-router.js";
68
- import { toIngestRequest } from "./runtime-api/intake/ingest-request.js";
69
67
  import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
70
- import { createLiveTelemetry } from "./telemetry/live-telemetry.js";
71
- import { resolveTickTelemetry } from "./telemetry/tick-frame.js";
72
68
  /** Initialize all core features (DSL, risk, health) on the given event bus. */
73
69
  export function initCore(bus) {
74
70
  initDsl(bus);
@@ -227,16 +223,6 @@ const senpiRuntimePlugin = {
227
223
  ? "env SENPI_LOG_LEVEL"
228
224
  : "default";
229
225
  pluginLogger.info(`[senpi-runtime] log level: ${startupLogLevel} (source: ${logLevelSourceLabel}). Set plugin config "logLevel" or env SENPI_LOG_LEVEL to debug|info|warn|error to override.`);
230
- // Same shape as the log-level line above, and for the same reason: a box publishing less than
231
- // an operator expects must say so at boot rather than be diagnosed from an empty screen. No env
232
- // var is named because there is none — see `tickTelemetry` on SenpiRuntimePluginConfig.
233
- const tickTelemetry = resolveTickTelemetry(config.tickTelemetry);
234
- if (tickTelemetry.invalid !== undefined) {
235
- pluginLogger.warn(`[senpi-runtime] Ignoring invalid plugin config ${tickTelemetry.invalid} (expected off|summary|verbose); keeping mcp=${tickTelemetry.settings.mcp} state=${tickTelemetry.settings.state}.`);
236
- }
237
- pluginLogger.info(tickTelemetry.settings.enabled
238
- ? `[senpi-runtime] tick telemetry: mcp=${tickTelemetry.settings.mcp} state=${tickTelemetry.settings.state} (source: ${config.tickTelemetry === undefined ? "default" : "plugin config tickTelemetry"}). Set plugin config "tickTelemetry" to off|summary|verbose, or {mcp,state}, to override.`
239
- : `[senpi-runtime] tick telemetry: off (source: plugin config tickTelemetry). No per-tick frame is published.`);
240
226
  const senpiCliStore = readSenpiCliStore();
241
227
  const baseStateDir = resolveSenpiBaseStateDir({
242
228
  store: senpiCliStore,
@@ -369,7 +355,6 @@ const senpiRuntimePlugin = {
369
355
  }
370
356
  let routeAcceptedSignalToRuntime;
371
357
  let routeScaffoldErrorToRuntime;
372
- let routeScaffoldTickToTelemetry;
373
358
  const launchers = new Map();
374
359
  // Runtimes that are UP but whose external scanners never wired (mount/launch/install
375
360
  // failure), keyed runtimeId → failed phase. Populated at the four unwired sites,
@@ -397,168 +382,10 @@ const senpiRuntimePlugin = {
397
382
  * engines, one margin account: the exact state `[E_WALLET_HAS_RUNTIME]` exists to prevent.
398
383
  */
399
384
  const installsInFlight = new Map();
400
- // ── Live telemetry ────────────────────────────────────────────────
401
- // Every runtime this box knows about, running or not, keyed runtimeId. The topology payload has
402
- // to name a stopped strategy too, so this is populated from the registry rather than from
403
- // `runtimeHandles` (which holds only the live ones) and is the source of the two facts the
404
- // registry entry does not carry: the strategy's display name and its declared scanners. Both come
405
- // from the parsed YAML — `name` is the same `config.name` that `RuntimeHealthStatus.runtimeName`
406
- // is built from, so a runtime whose config never parsed falls back to its id rather than to a
407
- // guess, and its scanner list stays empty rather than inventing rows.
408
- const telemetryStrategies = new Map();
409
- const noteTelemetryRuntime = (runtimeId, address) => {
410
- const existing = telemetryStrategies.get(runtimeId);
411
- telemetryStrategies.set(runtimeId, {
412
- address,
413
- name: existing?.name ?? runtimeId,
414
- scanners: existing?.scanners ?? [],
415
- });
416
- };
417
- const noteTelemetryRuntimeConfig = (runtimeId, config) => {
418
- telemetryStrategies.set(runtimeId, {
419
- address: config.strategy?.wallet ?? telemetryStrategies.get(runtimeId)?.address ?? "",
420
- name: config.name,
421
- scanners: config.scanners ?? [],
422
- });
423
- };
424
385
  // Dedicated logger for the runtime-api subtree — required because the
425
386
  // PluginLogger surface (info/warn/error) lacks `debug`, while the
426
387
  // Logger contract used by `runtime-api/` includes it.
427
388
  const apiLogger = createLogger("[senpi-core][api]");
428
- /**
429
- * One gather per publish tick. Health and system state are read together because the two payloads
430
- * need both: the topology reads health and `runtimeName`, the stats read the per-scanner rows,
431
- * and `restartCount` / `degradedReason` live only on the health rows while every other scanner
432
- * field lives only on the system-state rows — so they are joined here on `(runtimeId, scannerId)`.
433
- */
434
- const collectTelemetryFacts = async () => {
435
- const [statuses, states] = await Promise.all([
436
- collectRuntimeHealthStatuses(runtimeHandles, api.logger),
437
- collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir),
438
- ]);
439
- const healthByRuntime = new Map();
440
- for (const status of statuses) {
441
- if (status.runtimeId !== undefined)
442
- healthByRuntime.set(status.runtimeId, status);
443
- }
444
- const scannerState = [];
445
- // Registration facts per (runtimeId, scannerId), so the topology can report a scanner's live
446
- // schedule rather than only what its YAML declared.
447
- const registrations = new Map();
448
- for (const state of states) {
449
- const runtimeId = state.runtimeId;
450
- const healthRows = runtimeId === undefined
451
- ? []
452
- : healthByRuntime.get(runtimeId)?.components.scanners.scanners ?? [];
453
- for (const row of state.components.scanners.state.scanners) {
454
- const supervision = healthRows.find((r) => r.scannerId === row.scannerId);
455
- const snapshot = {
456
- address: row.address,
457
- scannerId: row.scannerId,
458
- health: row.health,
459
- degradedReason: supervision?.degradedReason ?? null,
460
- enabled: row.enabled,
461
- inFlightSince: row.inFlightSince,
462
- lastRunStartedAt: row.lastRunStartedAt,
463
- lastRunFinishedAt: row.lastRunFinishedAt,
464
- lastRunStatus: row.lastRunStatus,
465
- lastRunReason: row.lastRunReason,
466
- nextRunAt: row.nextRunAt,
467
- lastAliveAt: row.lastAliveAt,
468
- // No supervision row means the restart count is unknown, not zero. A zero here would
469
- // read as "this scanner has never restarted" on a scanner nobody is supervising.
470
- restartCount: supervision?.restartCount ?? null,
471
- };
472
- scannerState.push(snapshot);
473
- if (runtimeId !== undefined) {
474
- registrations.set(`${runtimeId}::${row.scannerId}`, {
475
- ...snapshot,
476
- scheduleMode: row.scheduleMode,
477
- configVersion: row.configVersion,
478
- intervalSeconds: row.intervalSeconds,
479
- });
480
- }
481
- }
482
- }
483
- const strategies = [];
484
- for (const [runtimeId, entry] of telemetryStrategies) {
485
- const health = healthByRuntime.get(runtimeId);
486
- const scanners = entry.scanners.map((declared) => {
487
- const live = registrations.get(`${runtimeId}::${declared.name}`);
488
- return live === undefined
489
- ? { entry: declared }
490
- : {
491
- entry: declared,
492
- registration: {
493
- enabled: live.enabled,
494
- scheduleMode: live.scheduleMode,
495
- // The engine's own cadence, which is what the scanner actually polls on — a
496
- // recipe may omit it and take the scanner definition's default. An external
497
- // scanner registers zero because it is POSTed to rather than polled, and only
498
- // the recipe knows the cadence its scaffold was told to tick at.
499
- intervalSeconds: live.intervalSeconds > 0 ? live.intervalSeconds : declared.interval_seconds ?? null,
500
- configVersion: live.configVersion,
501
- },
502
- };
503
- });
504
- strategies.push({
505
- runtimeId,
506
- address: entry.address,
507
- // `runtimeName` is built from the same `config.name`; the indexed value is used because it
508
- // is available for a stopped runtime too, where there is no health status to read.
509
- name: health?.runtimeName ?? entry.name,
510
- scanners,
511
- });
512
- }
513
- // The DSL states, read straight off each runtime's plugin. Synchronous and in-memory — this is
514
- // the same store the `senpi.listDslPositions` RPC reads — so it costs the gather nothing, and a
515
- // recipe with no DSL exit block has no plugin and contributes no rows. A runtime whose read
516
- // throws is skipped rather than failing the whole gather, on the same rule health follows: a
517
- // strategy mid-teardown must not take the stream down.
518
- const dslPositions = [];
519
- for (const [runtimeId, handle] of runtimeHandles) {
520
- if (!handle.dsl)
521
- continue;
522
- try {
523
- for (const state of handle.dsl.listActiveDslStates()) {
524
- dslPositions.push({ runtimeId, state });
525
- }
526
- }
527
- catch (err) {
528
- api.logger.warn("[senpi-runtime] could not read DSL states for telemetry", { runtimeId, err });
529
- }
530
- }
531
- return {
532
- strategies,
533
- runningRuntimeIds: runtimeHandles,
534
- unwiredRuntimeIds: unwiredRuntimes,
535
- readHealth: (runtimeId) => healthByRuntime.get(runtimeId),
536
- scannerState,
537
- dslPositions,
538
- };
539
- };
540
- const liveTelemetry = createLiveTelemetry({
541
- collectFacts: collectTelemetryFacts,
542
- tickTelemetry: config.tickTelemetry,
543
- });
544
- /**
545
- * Wrapped gateway methods capture the broadcast handle on the FIRST request to ANY of them —
546
- * not only on subscribe. `ctx.context` is the only route to the websocket push layer and it
547
- * exists only inside a handler, so capturing it in the subscribe method alone would mean a box
548
- * whose client never subscribes can never publish anything, silently.
549
- *
550
- * The wrapper covers only what is registered through it. `senpi.deploy.start`,
551
- * `senpi.deploy.status` and `senpi.update` are registered straight onto the plugin api by their
552
- * own registrars and never arm the handle. That is not a gap in practice: one wrapped call arms
553
- * it for the life of the process, and the websocket client admits `senpi.telemetry.subscribe`,
554
- * which is wrapped.
555
- */
556
- const registerGatewayMethod = (name, handler, opts) => {
557
- api.registerGatewayMethod(name, (ctx) => {
558
- liveTelemetry.captureBroadcast(ctx, name);
559
- return handler(ctx);
560
- }, opts);
561
- };
562
389
  // A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
563
390
  // runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
564
391
  // event (the queryable/alertable fact, by phase) and the migrated exception-form `logger.error`
@@ -666,7 +493,8 @@ const senpiRuntimePlugin = {
666
493
  // ── lifecycle reaper ─────────────────────────────────────────────────────────────────────────
667
494
  // A runtime exists iff its strategy is open. Every close path ends at the backend and none of
668
495
  // them tells this box, so the reaper asks: once after boot, hourly, and whenever the backend
669
- // rejects an order with STRATEGY_NOT_ACTIVE. Observe-only unless enforcement is on (see
496
+ // rejects an order with STRATEGY_NOT_ACTIVE. Enforcing by default since the 3.0.121 observe-only
497
+ // validation; SENPI_REAPER_ENFORCE=false returns a box to observe-only (see
670
498
  // runtime/lifecycle-reaper.ts for the safety rule). Constructed here so both `run()` call sites
671
499
  // (boot and install) can hand it their not-active rejections; it does no I/O until started.
672
500
  const reaperBootDelayOverride = resolveReaperBootDelayMs(process.env);
@@ -732,14 +560,26 @@ const senpiRuntimePlugin = {
732
560
  // /signals will not be mounted and no signals will be routed.
733
561
  if (sharedIntakeContext) {
734
562
  const ctx = sharedIntakeContext;
735
- routeAcceptedSignalToRuntime = (signal, correlationId, scannerId) => {
563
+ routeAcceptedSignalToRuntime = (signal, _correlationId, scannerId) => {
736
564
  const resolved = ctx.registry.resolve(scannerId);
737
565
  if (!resolved)
738
566
  return;
739
567
  const h = runtimeHandles.get(resolved.runtimeId);
740
568
  if (!h)
741
569
  return;
742
- const request = toIngestRequest(signal, correlationId, resolved.scannerName);
570
+ const request = {
571
+ scanner: resolved.scannerName,
572
+ signal_id: signal.signal_id, // greppable handle across intake→ingest (obs only)
573
+ asset: signal.asset,
574
+ direction: signal.direction,
575
+ ...(signal.signal_type !== undefined && { signal_type: signal.signal_type }),
576
+ data: signal.data,
577
+ // Per-signal sizing rides at the top level; the open-position action
578
+ // reads request.marginPct / request.leverage directly. Without this a
579
+ // supervised signal's sizing is lost ("no margin source resolved").
580
+ ...(signal.marginPct !== undefined && { marginPct: signal.marginPct }),
581
+ ...(signal.leverage !== undefined && { leverage: signal.leverage }),
582
+ };
743
583
  // Trace the sizing-relevant fields the narrowed request carries, so a
744
584
  // field dropped at this seam is visible instead of vanishing silently.
745
585
  apiLogger.debug(`[intake] ingest req signal_id=${signal.signal_id} scanner=${resolved.scannerName} asset=${signal.asset} direction=${signal.direction} marginPct=${signal.marginPct} leverage=${signal.leverage}`);
@@ -759,18 +599,15 @@ const senpiRuntimePlugin = {
759
599
  // The single server-level onError closure — the `/errors` counterpart of
760
600
  // routeAcceptedSignalToRuntime. It records a scaffold-reported tick failure into
761
601
  // the owning runtime's scanner telemetry so a scanner that fails every tick stops
762
- // rendering "healthy". Filter: only tick-level failure types count — a
763
- // `candidate_rejected` body is a per-candidate reject on an otherwise-OK tick, not
764
- // a failed run.
602
+ // rendering "healthy".
765
603
  //
766
- // This is the SHARED implementation (scaffold-error-router.ts), so this route and
767
- // buildIntakeMount's onScaffoldError apply one filter, and scaffold-error-router.test.ts
768
- // drives THIS closure.
604
+ // This is the SHARED implementation (scaffold-error-router.ts), not a copy of
605
+ // buildIntakeMount's onScaffoldError. It used to be a copy, and the copy had its
606
+ // own hardcoded three-type filter: when `mcp_error` was added to the tick-failure
607
+ // list it reached the tested seam and never reached this route, so the
608
+ // wedged-scanner signal was green in CI and inert in production. There is now one
609
+ // filter, and scaffold-error-router.test.ts drives THIS closure.
769
610
  routeScaffoldErrorToRuntime = buildScaffoldErrorRouter(ctx, runtimeHandles);
770
- // The per-tick level for a supervised external scanner. Both intake routes call it, so a
771
- // quiet tick and a failed tick are equally visible; it needs no runtime handle because it
772
- // publishes a level rather than recording a run.
773
- routeScaffoldTickToTelemetry = buildScaffoldTickRouter(ctx);
774
611
  }
775
612
  // One bounded read: which registered wallets the platform says are dead, so a restart does
776
613
  // not re-scaffold runtimes on closed wallets (fail-open — see runtime/boot-skip.ts).
@@ -822,9 +659,6 @@ const senpiRuntimePlugin = {
822
659
  continue;
823
660
  const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
824
661
  const entryStateDir = buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? "");
825
- // Indexed before the boot attempt: a runtime whose start throws is installed but stopped,
826
- // and the telemetry topology has to be able to say so.
827
- noteTelemetryRuntime(entry.id, entry.wallet ?? "");
828
662
  const handle = await run(pathOrContent, {
829
663
  fromPath: hasPath,
830
664
  stateDir: entryStateDir,
@@ -848,7 +682,6 @@ const senpiRuntimePlugin = {
848
682
  if (sharedIntakeContext) {
849
683
  try {
850
684
  const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
851
- noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
852
685
  // Content-installed runtimes carry their recipe dir in the registry
853
686
  // entry (runtimeYamlDir) so relative scanner paths still resolve
854
687
  // after a restart; path-installed ones derive it from the path.
@@ -900,7 +733,6 @@ const senpiRuntimePlugin = {
900
733
  // event) so listRuntimes/health don't render a bare "running".
901
734
  try {
902
735
  const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
903
- noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
904
736
  if (selectScanners(runtimeConfig).length > 0) {
905
737
  unwiredRuntimes.set(entry.id, "no_intake");
906
738
  handle.attachSupervisionSource(() => ({
@@ -959,7 +791,6 @@ const senpiRuntimePlugin = {
959
791
  intakeContext: sharedIntakeContext,
960
792
  onAccepted: routeAcceptedSignalToRuntime,
961
793
  onError: routeScaffoldErrorToRuntime,
962
- onTick: routeScaffoldTickToTelemetry,
963
794
  });
964
795
  apiHandle.ready();
965
796
  }
@@ -1003,12 +834,6 @@ const senpiRuntimePlugin = {
1003
834
  }
1004
835
  }
1005
836
  }
1006
- // Publish ticks start only once the runtimes are up, so the first frames describe a real
1007
- // box. Gated by the same switch the skills coordinator uses: unit tests boot `register()`
1008
- // and must not acquire background timers. The subscribe method still answers without them.
1009
- if (process.env.SENPI_RUNTIME_API_DISABLED !== "true") {
1010
- liveTelemetry.start();
1011
- }
1012
837
  // Last, once everything above is up: the reaper's post-boot pass and hourly sweep. Its
1013
838
  // not-active escalation is also off until here, so no delete can land while the boot loop is
1014
839
  // still wiring runtimes and launchers. Nothing here may fail start() — a throw from inside the boot loop takes every runtime down, which
@@ -1026,11 +851,6 @@ const senpiRuntimePlugin = {
1026
851
  }
1027
852
  },
1028
853
  async stop() {
1029
- // Live telemetry goes first: it owns a timer and an in-memory buffer, nothing downstream
1030
- // depends on it, and stopping it here guarantees no publish tick reads a half-torn-down
1031
- // runtime handle. Records emitted during the teardown below land in a buffer nobody drains,
1032
- // which is the right trade for a process on its way out.
1033
- liveTelemetry.stop();
1034
854
  // Stop the reaper first so no pass starts deleting while the runtimes are torn down below,
1035
855
  // and wait (bounded) for a pass already under way, so its delete cannot stop a handle the
1036
856
  // teardown below is stopping too.
@@ -1370,10 +1190,6 @@ const senpiRuntimePlugin = {
1370
1190
  onStrategyNotActive: (address) => reaper.checkWallet(address),
1371
1191
  });
1372
1192
  runtimeHandles.set(resolvedId, handle);
1373
- if (parsedConfig)
1374
- noteTelemetryRuntimeConfig(resolvedId, parsedConfig);
1375
- else
1376
- noteTelemetryRuntime(resolvedId, wallet);
1377
1193
  // Registry create fact — distinct from the runtime.started boot transition (which also fires
1378
1194
  // on a transient restart). Emitted inside emitEvent's guard so telemetry never fails the install.
1379
1195
  emitEvent(apiLogger, () => buildRuntimeCreatedEvent({ runtimeId: resolvedId, address: wallet, recipeName: runtimeName }));
@@ -1486,7 +1302,7 @@ const senpiRuntimePlugin = {
1486
1302
  }
1487
1303
  }
1488
1304
  /** Gateway respond accepts (ok, payload?, error?) with error as { code, message }; plugin SDK types may omit the third arg. */
1489
- registerGatewayMethod("senpi.installRuntime", async (ctx) => {
1305
+ api.registerGatewayMethod("senpi.installRuntime", async (ctx) => {
1490
1306
  const respond = ctx.respond;
1491
1307
  const result = await installRuntimeCore(ctx.params);
1492
1308
  if (result.ok)
@@ -1494,7 +1310,7 @@ const senpiRuntimePlugin = {
1494
1310
  else
1495
1311
  respond(false, undefined, result.error);
1496
1312
  }, { scope: "operator.write" });
1497
- registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
1313
+ api.registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
1498
1314
  const respond = ctx.respond;
1499
1315
  const { readRegistry } = await import("./runtime/strategy-registry.js");
1500
1316
  const registry = await readRegistry(baseStateDir);
@@ -1637,9 +1453,6 @@ const senpiRuntimePlugin = {
1637
1453
  // "running — NO ENTRY SCANNERS" forever. The map is otherwise only cleared on a
1638
1454
  // successful wire/install, which a scanner-less reinstall never reaches.
1639
1455
  unwiredRuntimes.delete(targetId);
1640
- // Same reuse hazard: a deleted id left in the index would keep publishing a strategy that no
1641
- // longer exists, and would hand a reinstall the old name and scanner list.
1642
- telemetryStrategies.delete(targetId);
1643
1456
  bootSkippedRuntimes.delete(targetId);
1644
1457
  // Archive state directory before removing from registry
1645
1458
  if (deletedEntry) {
@@ -1670,7 +1483,7 @@ const senpiRuntimePlugin = {
1670
1483
  }));
1671
1484
  return { ok: true, payload: { id: targetId } };
1672
1485
  }
1673
- registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
1486
+ api.registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
1674
1487
  const respond = ctx.respond;
1675
1488
  const result = await deleteRuntimeCore(ctx.params);
1676
1489
  if (result.ok)
@@ -1777,17 +1590,6 @@ const senpiRuntimePlugin = {
1777
1590
  return checkProofCoversRecipe(packageDir, verifyProof(packageDir), appliedRecipe, (f) => readFileSync(f, "utf8"));
1778
1591
  },
1779
1592
  applyToRuntime: makeApplyToRuntime(runtimeHandles, async (runtimeId, next, recipe) => {
1780
- // Re-index the topology facts FIRST, ahead of every early return and every failure path
1781
- // below. `makeApplyToRuntime` only reaches this callback once the in-process swap has
1782
- // succeeded, so `next` is already the recipe this box runs: its display name and its
1783
- // declared scanner list are the ones the topology payload must name. Leaving the index on
1784
- // the previous config would publish strategy rows the new scanner stats cannot join to.
1785
- //
1786
- // Ahead of the early return because an apply on a box with no intake still swapped the
1787
- // recipe, and ahead of the launcher because a launcher that fails is a runtime running the
1788
- // new recipe with no external scanners — the degraded state a dashboard most needs to show,
1789
- // and one it can only render if the declared scanners are the new ones.
1790
- noteTelemetryRuntimeConfig(runtimeId, next);
1791
1593
  // Restart this runtime's external scanner children against the new package.
1792
1594
  //
1793
1595
  // The rebuild swaps in-process components; these are separate OS processes, spawned once
@@ -1935,7 +1737,7 @@ const senpiRuntimePlugin = {
1935
1737
  * producers — slated for removal in Task 5 once the migration counter
1936
1738
  * decays to zero (see `runtime-phase-2-api-plan.md` Section 8).
1937
1739
  */
1938
- registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
1740
+ api.registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
1939
1741
  const respond = ctx.respond;
1940
1742
  // TODO(s11): wire actual telemetry counter for
1941
1743
  // runtime.deprecated_gateway_ingest_call (event name, properties:
@@ -1954,7 +1756,6 @@ const senpiRuntimePlugin = {
1954
1756
  ...(request.direction !== undefined && { direction: request.direction }),
1955
1757
  ...(request.score !== undefined && { score: request.score }),
1956
1758
  ...(request.signal_type !== undefined && { signal_type: request.signal_type }),
1957
- ...(request.tick_id !== undefined && { tick_id: request.tick_id }),
1958
1759
  ...(request.data !== undefined && { data: request.data }),
1959
1760
  ...(request.signals !== undefined && { signals: request.signals }),
1960
1761
  };
@@ -2012,7 +1813,7 @@ const senpiRuntimePlugin = {
2012
1813
  });
2013
1814
  }
2014
1815
  }, { scope: "operator.write" });
2015
- registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
1816
+ api.registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
2016
1817
  const respond = ctx.respond;
2017
1818
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2018
1819
  const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
@@ -2037,7 +1838,7 @@ const senpiRuntimePlugin = {
2037
1838
  }
2038
1839
  respond(true, { positions });
2039
1840
  }, { scope: "operator.read" });
2040
- registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
1841
+ api.registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
2041
1842
  const respond = ctx.respond;
2042
1843
  const asset = typeof ctx.params?.asset === "string" ? ctx.params.asset : undefined;
2043
1844
  if (!asset) {
@@ -2078,7 +1879,7 @@ const senpiRuntimePlugin = {
2078
1879
  }
2079
1880
  respond(true, { positions: matches });
2080
1881
  }, { scope: "operator.read" });
2081
- registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
1882
+ api.registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
2082
1883
  const respond = ctx.respond;
2083
1884
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2084
1885
  const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
@@ -2104,7 +1905,7 @@ const senpiRuntimePlugin = {
2104
1905
  closes = closes.slice(0, limit);
2105
1906
  respond(true, { closes });
2106
1907
  }, { scope: "operator.read" });
2107
- registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
1908
+ api.registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
2108
1909
  const respond = ctx.respond;
2109
1910
  const updater = autoUpdateCoordinator.getSnapshot();
2110
1911
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
@@ -2132,7 +1933,7 @@ const senpiRuntimePlugin = {
2132
1933
  const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
2133
1934
  respond(true, { statuses, updater });
2134
1935
  }, { scope: "operator.read" });
2135
- registerGatewayMethod("senpi.getSystemState", async (ctx) => {
1936
+ api.registerGatewayMethod("senpi.getSystemState", async (ctx) => {
2136
1937
  const respond = ctx.respond;
2137
1938
  const updater = autoUpdateCoordinator.getSnapshot();
2138
1939
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
@@ -2199,36 +2000,6 @@ const senpiRuntimePlugin = {
2199
2000
  });
2200
2001
  }
2201
2002
  }, { scope: "operator.write" });
2202
- /**
2203
- * Live telemetry — the one method of the stream, called once per connect.
2204
- *
2205
- * It returns the current value of all three broadcast payloads plus the recent record window, so
2206
- * a client joining a box that has been running for hours renders a full screen immediately
2207
- * instead of waiting for the next thing to happen. `(afterEpoch, afterSeq)` resumes a client that
2208
- * already holds part of this epoch's run — a cursor naming any other epoch is dropped, because a
2209
- * seq from a previous run cannot address these records. `truncated` says when the records between
2210
- * a cursor of this epoch and the oldest record still held are gone, which nothing in `recent`
2211
- * alone can express.
2212
- *
2213
- * Read-only, like every other inspection method here. The scope that decides who RECEIVES the
2214
- * broadcasts is a property of the client's own connection, not of this method.
2215
- */
2216
- registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
2217
- const respond = ctx.respond;
2218
- try {
2219
- respond(true, await liveTelemetry.snapshot({
2220
- afterEpoch: ctx.params?.afterEpoch,
2221
- afterSeq: ctx.params?.afterSeq,
2222
- }));
2223
- }
2224
- catch (err) {
2225
- api.logger.warn("[senpi-runtime] telemetry subscribe error", { err });
2226
- respond(false, undefined, {
2227
- code: "INTERNAL_ERROR",
2228
- message: `Failed to build the telemetry snapshot: ${formatGatewayError(err)}`,
2229
- });
2230
- }
2231
- }, { scope: "operator.read" });
2232
2003
  /**
2233
2004
  * Action layer — read-only inspection.
2234
2005
  *
@@ -2248,7 +2019,7 @@ const senpiRuntimePlugin = {
2248
2019
  * persisted action-store machinery (`src/actions/store/`) usable
2249
2020
  * from the operator-facing CLI again.
2250
2021
  */
2251
- registerGatewayMethod("senpi.listActions", async (ctx) => {
2022
+ api.registerGatewayMethod("senpi.listActions", async (ctx) => {
2252
2023
  const respond = ctx.respond;
2253
2024
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2254
2025
  const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
@@ -2278,7 +2049,7 @@ const senpiRuntimePlugin = {
2278
2049
  }
2279
2050
  respond(true, { actions: merged });
2280
2051
  }, { scope: "operator.read" });
2281
- registerGatewayMethod("senpi.getActionState", async (ctx) => {
2052
+ api.registerGatewayMethod("senpi.getActionState", async (ctx) => {
2282
2053
  const respond = ctx.respond;
2283
2054
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2284
2055
  const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
@@ -2315,7 +2086,7 @@ const senpiRuntimePlugin = {
2315
2086
  });
2316
2087
  }
2317
2088
  }, { scope: "operator.read" });
2318
- registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
2089
+ api.registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
2319
2090
  const respond = ctx.respond;
2320
2091
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2321
2092
  const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
@@ -2383,7 +2154,7 @@ const senpiRuntimePlugin = {
2383
2154
  // reachable on HTTP `/audit`. Passes through the caller-controlled subset
2384
2155
  // of the audit_query contract; resource scoping (wallet → strategyId) is
2385
2156
  // owned by handle.auditQuery, never the caller.
2386
- registerGatewayMethod("senpi.auditQuery", async (ctx) => {
2157
+ api.registerGatewayMethod("senpi.auditQuery", async (ctx) => {
2387
2158
  const respond = ctx.respond;
2388
2159
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2389
2160
  if (!runtimeId) {
@@ -2431,7 +2202,7 @@ const senpiRuntimePlugin = {
2431
2202
  // its state root private, so re-derive it from baseStateDir + the handle's
2432
2203
  // wallet (same helper the writer's stateDir was built with); the writer
2433
2204
  // nests entries under `<root>/<address>/risk-guard/audit.jsonl`.
2434
- registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
2205
+ api.registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
2435
2206
  const respond = ctx.respond;
2436
2207
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2437
2208
  if (!runtimeId) {
@@ -2474,7 +2245,7 @@ const senpiRuntimePlugin = {
2474
2245
  // Per-strategy local domain-event ring (on-disk JSONL) — the trade narrative, queryable locally.
2475
2246
  // Same state-root re-derivation as getRiskAudit: the Runtime keeps its root private, so rebuild it
2476
2247
  // from baseStateDir + the handle's wallet; the writer nests rows under `<root>/strategies/<address>/events/`.
2477
- registerGatewayMethod("senpi.getEvents", async (ctx) => {
2248
+ api.registerGatewayMethod("senpi.getEvents", async (ctx) => {
2478
2249
  const respond = ctx.respond;
2479
2250
  const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
2480
2251
  if (!runtimeId) {
@@ -2531,12 +2302,12 @@ const senpiRuntimePlugin = {
2531
2302
  }
2532
2303
  }, { scope: "operator.read" });
2533
2304
  // ─── Skills manager — read + mutate ──────────────────────────────────────
2534
- registerGatewayMethod("senpi.skills.status", async (ctx) => {
2305
+ api.registerGatewayMethod("senpi.skills.status", async (ctx) => {
2535
2306
  const respond = ctx.respond;
2536
2307
  const snapshot = skillsCoordinator.getSnapshot();
2537
2308
  respond(true, { snapshot });
2538
2309
  }, { scope: "operator.read" });
2539
- registerGatewayMethod("senpi.skills.update", async (ctx) => {
2310
+ api.registerGatewayMethod("senpi.skills.update", async (ctx) => {
2540
2311
  const respond = ctx.respond;
2541
2312
  const name = typeof ctx.params?.name === "string" ? ctx.params.name.trim() : undefined;
2542
2313
  if (!name) {
@@ -2561,7 +2332,7 @@ const senpiRuntimePlugin = {
2561
2332
  });
2562
2333
  }
2563
2334
  }, { scope: "operator.write" });
2564
- registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
2335
+ api.registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
2565
2336
  const respond = ctx.respond;
2566
2337
  try {
2567
2338
  const results = await skillsCoordinator.updateAll();
@@ -2627,7 +2398,6 @@ function parseGatewayExternalScannerIngestParams(params) {
2627
2398
  direction: params?.direction,
2628
2399
  score: params?.score,
2629
2400
  signal_type: params?.signal_type,
2630
- tick_id: params?.tick_id,
2631
2401
  data: params?.data,
2632
2402
  signals: params?.signals,
2633
2403
  },