@senpi/runtime 3.0.129 → 3.0.131

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 (252) hide show
  1. package/dist/actions/action-events.d.ts +21 -7
  2. package/dist/actions/action-events.d.ts.map +1 -1
  3. package/dist/actions/action-events.js +54 -26
  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 +38 -7
  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 +122 -15
  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 +15 -2
  13. package/dist/actions/decision-engine.js.map +1 -1
  14. package/dist/actions/event-derivation.d.ts +71 -2
  15. package/dist/actions/event-derivation.d.ts.map +1 -1
  16. package/dist/actions/event-derivation.js +251 -8
  17. package/dist/actions/event-derivation.js.map +1 -1
  18. package/dist/actions/open-position/open-position.action.d.ts +12 -2
  19. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  20. package/dist/actions/open-position/open-position.action.js +141 -41
  21. package/dist/actions/open-position/open-position.action.js.map +1 -1
  22. package/dist/actions/position-tracker/position-tracker.action.d.ts +2 -1
  23. package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
  24. package/dist/actions/position-tracker/position-tracker.action.js +62 -17
  25. package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
  26. package/dist/actions/skipped-signal-results.d.ts +25 -0
  27. package/dist/actions/skipped-signal-results.d.ts.map +1 -0
  28. package/dist/actions/skipped-signal-results.js +71 -0
  29. package/dist/actions/skipped-signal-results.js.map +1 -0
  30. package/dist/actions/store/types.d.ts +9 -2
  31. package/dist/actions/store/types.d.ts.map +1 -1
  32. package/dist/actions/store/types.js.map +1 -1
  33. package/dist/actions/types.d.ts +13 -0
  34. package/dist/actions/types.d.ts.map +1 -1
  35. package/dist/actions/types.js +13 -0
  36. package/dist/actions/types.js.map +1 -1
  37. package/dist/cli/guide-commands.d.ts.map +1 -1
  38. package/dist/cli/guide-commands.js +3 -0
  39. package/dist/cli/guide-commands.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 +14 -0
  42. package/dist/dsl/engine-client/event-mapping.js.map +1 -1
  43. package/dist/dsl/events/build-closed-event.d.ts +16 -1
  44. package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
  45. package/dist/dsl/events/build-closed-event.js +6 -0
  46. package/dist/dsl/events/build-closed-event.js.map +1 -1
  47. package/dist/dsl/events/handlers.d.ts +7 -0
  48. package/dist/dsl/events/handlers.d.ts.map +1 -1
  49. package/dist/dsl/events/handlers.js +37 -7
  50. package/dist/dsl/events/handlers.js.map +1 -1
  51. package/dist/dsl/monitor/create-monitor.d.ts +6 -2
  52. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  53. package/dist/dsl/monitor/create-monitor.js +15 -9
  54. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  55. package/dist/dsl/monitor/dsl-failure-events.d.ts +2 -0
  56. package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
  57. package/dist/dsl/monitor/dsl-failure-events.js +2 -1
  58. package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
  59. package/dist/dsl/monitor/process-one-position.d.ts +1 -0
  60. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  61. package/dist/dsl/monitor/process-one-position.js +59 -25
  62. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  63. package/dist/dsl/plugin/index.d.ts +8 -2
  64. package/dist/dsl/plugin/index.d.ts.map +1 -1
  65. package/dist/dsl/plugin/index.js +8 -6
  66. package/dist/dsl/plugin/index.js.map +1 -1
  67. package/dist/health/types.d.ts +13 -0
  68. package/dist/health/types.d.ts.map +1 -1
  69. package/dist/health/types.js +12 -0
  70. package/dist/health/types.js.map +1 -1
  71. package/dist/index.d.ts +27 -4
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +251 -39
  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 +4 -3
  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 +7 -0
  80. package/dist/runtime/domain-events.js.map +1 -1
  81. package/dist/runtime/lifecycle-events.d.ts +9 -1
  82. package/dist/runtime/lifecycle-events.d.ts.map +1 -1
  83. package/dist/runtime/lifecycle-events.js +11 -2
  84. package/dist/runtime/lifecycle-events.js.map +1 -1
  85. package/dist/runtime/reconcile-run.d.ts +13 -3
  86. package/dist/runtime/reconcile-run.d.ts.map +1 -1
  87. package/dist/runtime/reconcile-run.js +4 -3
  88. package/dist/runtime/reconcile-run.js.map +1 -1
  89. package/dist/runtime/reconcile-state.d.ts +8 -0
  90. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  91. package/dist/runtime/reconcile-state.js +4 -1
  92. package/dist/runtime/reconcile-state.js.map +1 -1
  93. package/dist/runtime/run.d.ts +1 -0
  94. package/dist/runtime/run.d.ts.map +1 -1
  95. package/dist/runtime/run.js.map +1 -1
  96. package/dist/runtime/runtime-schema.js +3 -3
  97. package/dist/runtime/runtime-schema.js.map +1 -1
  98. package/dist/runtime/runtime.d.ts +1 -0
  99. package/dist/runtime/runtime.d.ts.map +1 -1
  100. package/dist/runtime/runtime.js +27 -3
  101. package/dist/runtime/runtime.js.map +1 -1
  102. package/dist/runtime/supervisor/long-running-supervisor.d.ts +13 -0
  103. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  104. package/dist/runtime/supervisor/long-running-supervisor.js +1 -0
  105. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  106. package/dist/runtime/validate-dsl-position-tracking.d.ts.map +1 -1
  107. package/dist/runtime/validate-dsl-position-tracking.js +2 -1
  108. package/dist/runtime/validate-dsl-position-tracking.js.map +1 -1
  109. package/dist/runtime-api/handlers/ingest-signals.d.ts.map +1 -1
  110. package/dist/runtime-api/handlers/ingest-signals.js +1 -0
  111. package/dist/runtime-api/handlers/ingest-signals.js.map +1 -1
  112. package/dist/runtime-api/handlers/types.d.ts +5 -0
  113. package/dist/runtime-api/handlers/types.d.ts.map +1 -1
  114. package/dist/runtime-api/intake/ingest-request.d.ts +17 -0
  115. package/dist/runtime-api/intake/ingest-request.d.ts.map +1 -0
  116. package/dist/runtime-api/intake/ingest-request.js +34 -0
  117. package/dist/runtime-api/intake/ingest-request.js.map +1 -0
  118. package/dist/runtime-api/intake/intake-events.d.ts +1 -1
  119. package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
  120. package/dist/runtime-api/intake/intake-events.js +9 -3
  121. package/dist/runtime-api/intake/intake-events.js.map +1 -1
  122. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  123. package/dist/runtime-api/intake/intake-mount.js +4 -19
  124. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  125. package/dist/runtime-api/intake/process-error-post.d.ts +6 -1
  126. package/dist/runtime-api/intake/process-error-post.d.ts.map +1 -1
  127. package/dist/runtime-api/intake/process-error-post.js +5 -1
  128. package/dist/runtime-api/intake/process-error-post.js.map +1 -1
  129. package/dist/runtime-api/intake/process-signal-post.d.ts +7 -1
  130. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  131. package/dist/runtime-api/intake/process-signal-post.js +5 -0
  132. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  133. package/dist/runtime-api/intake/scaffold-error-router.js.map +1 -1
  134. package/dist/runtime-api/intake/scaffold-tick-router.d.ts +39 -0
  135. package/dist/runtime-api/intake/scaffold-tick-router.d.ts.map +1 -0
  136. package/dist/runtime-api/intake/scaffold-tick-router.js +122 -0
  137. package/dist/runtime-api/intake/scaffold-tick-router.js.map +1 -0
  138. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  139. package/dist/runtime-api/intake/scanner-launcher.js +15 -0
  140. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  141. package/dist/runtime-api/intake/types.d.ts +55 -0
  142. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  143. package/dist/runtime-api/routes/signals.d.ts +2 -0
  144. package/dist/runtime-api/routes/signals.d.ts.map +1 -1
  145. package/dist/runtime-api/routes/signals.js +5 -1
  146. package/dist/runtime-api/routes/signals.js.map +1 -1
  147. package/dist/runtime-api/routes/signals.schema.d.ts +43 -0
  148. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  149. package/dist/runtime-api/routes/signals.schema.js +43 -0
  150. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  151. package/dist/runtime-api/server.d.ts +7 -1
  152. package/dist/runtime-api/server.d.ts.map +1 -1
  153. package/dist/runtime-api/server.js +1 -0
  154. package/dist/runtime-api/server.js.map +1 -1
  155. package/dist/scanners/engine/engine.d.ts +22 -1
  156. package/dist/scanners/engine/engine.d.ts.map +1 -1
  157. package/dist/scanners/engine/engine.js +78 -11
  158. package/dist/scanners/engine/engine.js.map +1 -1
  159. package/dist/scanners/engine/lifecycle.d.ts +7 -1
  160. package/dist/scanners/engine/lifecycle.d.ts.map +1 -1
  161. package/dist/scanners/events.d.ts +4 -0
  162. package/dist/scanners/events.d.ts.map +1 -1
  163. package/dist/scanners/events.js +2 -0
  164. package/dist/scanners/events.js.map +1 -1
  165. package/dist/scanners/external/scaffold/delivery.py +194 -25
  166. package/dist/scanners/external/scaffold/scaffold.py +172 -31
  167. package/dist/scanners/external/scaffold/senpi_mcp.py +150 -8
  168. package/dist/scanners/external/scaffold/tick_facts.py +130 -0
  169. package/dist/scanners/external/scaffold/ulid.py +58 -0
  170. package/dist/scanners/external-scanner-receiver.d.ts +17 -0
  171. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  172. package/dist/scanners/external-scanner-receiver.js +8 -0
  173. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  174. package/dist/scanners/runtime-module.d.ts +1 -0
  175. package/dist/scanners/runtime-module.d.ts.map +1 -1
  176. package/dist/scanners/runtime-module.js +66 -9
  177. package/dist/scanners/runtime-module.js.map +1 -1
  178. package/dist/telemetry/attributes.d.ts +20 -0
  179. package/dist/telemetry/attributes.d.ts.map +1 -1
  180. package/dist/telemetry/attributes.js +24 -0
  181. package/dist/telemetry/attributes.js.map +1 -1
  182. package/dist/telemetry/event-builders.d.ts +81 -6
  183. package/dist/telemetry/event-builders.d.ts.map +1 -1
  184. package/dist/telemetry/event-builders.js +141 -17
  185. package/dist/telemetry/event-builders.js.map +1 -1
  186. package/dist/telemetry/event-store.d.ts +6 -0
  187. package/dist/telemetry/event-store.d.ts.map +1 -1
  188. package/dist/telemetry/event-store.js +12 -8
  189. package/dist/telemetry/event-store.js.map +1 -1
  190. package/dist/telemetry/live-telemetry.d.ts +238 -0
  191. package/dist/telemetry/live-telemetry.d.ts.map +1 -0
  192. package/dist/telemetry/live-telemetry.js +853 -0
  193. package/dist/telemetry/live-telemetry.js.map +1 -0
  194. package/dist/telemetry/metrics.d.ts +14 -2
  195. package/dist/telemetry/metrics.d.ts.map +1 -1
  196. package/dist/telemetry/metrics.js +19 -4
  197. package/dist/telemetry/metrics.js.map +1 -1
  198. package/dist/telemetry/scanner-stats.d.ts +143 -0
  199. package/dist/telemetry/scanner-stats.d.ts.map +1 -0
  200. package/dist/telemetry/scanner-stats.js +426 -0
  201. package/dist/telemetry/scanner-stats.js.map +1 -0
  202. package/dist/telemetry/scanner-tick-feed.d.ts +87 -0
  203. package/dist/telemetry/scanner-tick-feed.d.ts.map +1 -0
  204. package/dist/telemetry/scanner-tick-feed.js +48 -0
  205. package/dist/telemetry/scanner-tick-feed.js.map +1 -0
  206. package/dist/telemetry/spans/redact-pii.d.ts.map +1 -1
  207. package/dist/telemetry/spans/redact-pii.js +18 -2
  208. package/dist/telemetry/spans/redact-pii.js.map +1 -1
  209. package/dist/telemetry/strategy-topology.d.ts +99 -0
  210. package/dist/telemetry/strategy-topology.d.ts.map +1 -0
  211. package/dist/telemetry/strategy-topology.js +93 -0
  212. package/dist/telemetry/strategy-topology.js.map +1 -0
  213. package/dist/telemetry/telemetry-publisher.d.ts +76 -0
  214. package/dist/telemetry/telemetry-publisher.d.ts.map +1 -0
  215. package/dist/telemetry/telemetry-publisher.js +247 -0
  216. package/dist/telemetry/telemetry-publisher.js.map +1 -0
  217. package/dist/telemetry/tick-frame.d.ts +212 -0
  218. package/dist/telemetry/tick-frame.d.ts.map +1 -0
  219. package/dist/telemetry/tick-frame.js +421 -0
  220. package/dist/telemetry/tick-frame.js.map +1 -0
  221. package/dist/telemetry/timeline-buffer.d.ts +95 -0
  222. package/dist/telemetry/timeline-buffer.d.ts.map +1 -0
  223. package/dist/telemetry/timeline-buffer.js +237 -0
  224. package/dist/telemetry/timeline-buffer.js.map +1 -0
  225. package/dist/telemetry/tracing.d.ts +9 -0
  226. package/dist/telemetry/tracing.d.ts.map +1 -1
  227. package/dist/telemetry/tracing.js +36 -0
  228. package/dist/telemetry/tracing.js.map +1 -1
  229. package/dist/types/dsl/index.d.ts +11 -0
  230. package/dist/types/dsl/index.d.ts.map +1 -1
  231. package/dist/types/scanner.d.ts +15 -0
  232. package/dist/types/scanner.d.ts.map +1 -1
  233. package/dist/utils/event-catalog.d.ts +166 -0
  234. package/dist/utils/event-catalog.d.ts.map +1 -1
  235. package/dist/utils/event-catalog.js +18 -0
  236. package/dist/utils/event-catalog.js.map +1 -1
  237. package/dist/utils/logger.d.ts +4 -1
  238. package/dist/utils/logger.d.ts.map +1 -1
  239. package/dist/utils/logger.js.map +1 -1
  240. package/dist/utils/telemetry-required-log.d.ts +60 -0
  241. package/dist/utils/telemetry-required-log.d.ts.map +1 -0
  242. package/dist/utils/telemetry-required-log.js +105 -0
  243. package/dist/utils/telemetry-required-log.js.map +1 -0
  244. package/dist/utils/ulid.d.ts +9 -0
  245. package/dist/utils/ulid.d.ts.map +1 -0
  246. package/dist/utils/ulid.js +43 -0
  247. package/dist/utils/ulid.js.map +1 -0
  248. package/dist/validate/recipe-checks.d.ts.map +1 -1
  249. package/dist/validate/recipe-checks.js +2 -2
  250. package/dist/validate/recipe-checks.js.map +1 -1
  251. package/openclaw.plugin.json +1 -1
  252. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/scanners/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzE,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,sFAAsF;AACtF,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,yCAAyC;AACzC,MAAM,WAAW,sBAAsB;IACrC,mBAAmB,EAAE,oBAAoB,CAAC;IAC1C,sBAAsB,EAAE,uBAAuB,CAAC;IAChD,kBAAkB,EAAE,mBAAmB,CAAC;IACxC,mBAAmB,EAAE,oBAAoB,CAAC;CAC3C;AAED,qEAAqE;AACrE,MAAM,MAAM,uBAAuB,GAAG,MAAM,sBAAsB,CAAC;AAEnE,sFAAsF;AACtF,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,CAAC,SAAS,EAAE,uBAAuB,EAAE,KAAK,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,KAAK,IAAI,EAC1G,MAAM,EAAE,UAAU,GACjB,IAAI,CAiCN"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/scanners/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzE,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,sFAAsF;AACtF,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,qGAAqG;IACrG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,sBAAsB;IACrC,mBAAmB,EAAE,oBAAoB,CAAC;IAC1C,sBAAsB,EAAE,uBAAuB,CAAC;IAChD,kBAAkB,EAAE,mBAAmB,CAAC;IACxC,mBAAmB,EAAE,oBAAoB,CAAC;CAC3C;AAED,qEAAqE;AACrE,MAAM,MAAM,uBAAuB,GAAG,MAAM,sBAAsB,CAAC;AAEnE,sFAAsF;AACtF,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,CAAC,SAAS,EAAE,uBAAuB,EAAE,KAAK,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,KAAK,IAAI,EAC1G,MAAM,EAAE,UAAU,GACjB,IAAI,CAmCN"}
@@ -8,6 +8,7 @@ export function emitTerminalScannerRunEvent(emit, result) {
8
8
  scannerId: result.scannerId,
9
9
  timestamp,
10
10
  error: result.reason ?? "Unknown scanner error.",
11
+ tickId: result.tickId,
11
12
  });
12
13
  return;
13
14
  }
@@ -20,6 +21,7 @@ export function emitTerminalScannerRunEvent(emit, result) {
20
21
  timestamp,
21
22
  status: result.status,
22
23
  reason: result.reason ?? "Scanner run skipped.",
24
+ tickId: result.tickId,
23
25
  });
24
26
  return;
25
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/scanners/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAmC,MAAM,YAAY,CAAC;AAmDzE,sFAAsF;AACtF,MAAM,UAAU,2BAA2B,CACzC,IAA0G,EAC1G,MAAkB;IAElB,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACnC,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,CAAC,mBAAmB,EAAE;YACxB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS;YACT,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,wBAAwB;SACjD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IACE,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,OAAO;QACpC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM;QACnC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,QAAQ,EACrC,CAAC;QACD,IAAI,CAAC,kBAAkB,EAAE;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS;YACT,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,sBAAsB;SAChD,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,CAAC,sBAAsB,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS;QACT,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/scanners/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAmC,MAAM,YAAY,CAAC;AAuDzE,sFAAsF;AACtF,MAAM,UAAU,2BAA2B,CACzC,IAA0G,EAC1G,MAAkB;IAElB,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACnC,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,CAAC,mBAAmB,EAAE;YACxB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS;YACT,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,wBAAwB;YAChD,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IACE,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,OAAO;QACpC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM;QACnC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,QAAQ,EACrC,CAAC;QACD,IAAI,CAAC,kBAAkB,EAAE;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS;YACT,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,sBAAsB;YAC/C,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,IAAI,CAAC,sBAAsB,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS;QACT,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
@@ -11,18 +11,23 @@ build_delivery_sink(
11
11
  clock: Callable[[], float] = time.time,
12
12
  ) -> sink
13
13
 
14
- The returned sink is a callable: sink(candidates)
15
- On each call it:
14
+ The returned sink is a callable: sink(candidates, tick_id=None, tick=None)
15
+ `tick` is the scaffold's per-tick observability facts; the sink stamps the journal's
16
+ pending count onto them (it owns the journal) and rides them on the POST it already
17
+ makes. On each call it:
16
18
  1. Replays journal on first call (if file-backed), re-queuing valid undelivered entries
17
19
  2. Flushes still-valid undelivered entries from prior ticks (retry)
18
- 3. Wraps this tick's candidates into wire signals (skips invalid ones)
20
+ 3. Wraps this tick's candidates into wire signals (skips invalid ones), stamping the
21
+ producing tick's id on each
19
22
  4. Drops locally superseded / expired entries
20
23
  5. POSTs {scanner_id, signals:[...]} to <intake_url>/signals
21
24
  6. Interprets the response: accepted/expired/superseded/rejected = delivered;
22
25
  transport failure or non-success envelope leaves entry queued for retry
23
26
 
24
- sink.post_error(error: dict) -> None
25
- POSTs {scanner_id, error} to <intake_url>/errors. Fire-and-forget.
27
+ sink.post_error(error: dict, tick: dict | None = None) -> None
28
+ POSTs {scanner_id, error} to <intake_url>/errors. Fire-and-forget. A failed tick's
29
+ facts ride here for the same reason they ride /signals on a clean one: this is the
30
+ POST that tick already makes.
26
31
 
27
32
  Module constants:
28
33
  SIGNALS_PATH = "/signals"
@@ -59,14 +64,47 @@ def build_delivery_sink(
59
64
 
60
65
  journal = Journal(journal_path)
61
66
  replayed = [False] # mutable cell; replay runs once on first invocation
67
+ # Mutable cell; the id of the tick currently inside _sink, so the reject/warn
68
+ # callbacks — which are built once, here — can name it. _sink sets it on entry,
69
+ # before anything can read it, and both callbacks run only from inside _sink.
70
+ # None when the caller passed no id, or one the wire would refuse.
71
+ current_tick_id: list[str | None] = [None]
62
72
  # Distinct dropped-field shapes already warned about (rate-limit: a scanner that
63
73
  # sets a stray key every tick warns ONCE per shape, not every tick).
64
74
  warned_dropped_shapes: set[frozenset] = set()
75
+ # Distinct unusable tick-id shapes already warned about, same rate-limit reason: a loop
76
+ # handing the sink the same bad id every tick warns ONCE, not on every tick forever.
77
+ warned_unusable_tick_ids: set[str] = set()
78
+ # Set once per process, the first time the intake refuses a batch for carrying tick_id.
79
+ # From then on the field is left off the wire and the "this intake predates the field" line
80
+ # is not written again. Never cleared: an intake that refuses the field once would refuse it
81
+ # again, so re-arming would buy back the guaranteed-400 round-trip on every later tick.
82
+ intake_predates_tick_id = [False]
83
+ # The same latch for the body-level `tick` facts. Separate from the one above because
84
+ # they are separate fields on separate levels of the body: an intake may know one and
85
+ # not the other, and stripping both on either refusal would throw away what still works.
86
+ intake_predates_tick_facts = [False]
65
87
 
66
88
  # ------------------------------------------------------------------
67
89
  # Internal helpers
68
90
  # ------------------------------------------------------------------
69
91
 
92
+ def _tick_field(tick_id: Any) -> dict:
93
+ """`tick_id=...` as log kwargs, or nothing at all when no tick is nameable.
94
+
95
+ An absent key is the honest answer; a null or a blank would join to nothing
96
+ while looking like an id.
97
+ """
98
+ return {"tick_id": tick_id} if isinstance(tick_id, str) and tick_id else {}
99
+
100
+ def _signal_tick_id(signal: Any) -> Any:
101
+ """The producing tick's id off a stored signal, or None.
102
+
103
+ A journal line is arbitrary text on disk: `signal` may be a null, a string or
104
+ a list, so this never assumes a mapping.
105
+ """
106
+ return signal.get("tick_id") if isinstance(signal, dict) else None
107
+
70
108
  def _now_ms() -> int:
71
109
  return int(clock() * 1000)
72
110
 
@@ -89,13 +127,40 @@ def build_delivery_sink(
89
127
  to_drop.append(entry)
90
128
  for entry in to_drop:
91
129
  journal.record_outcome(entry.id, "dropped_expired")
92
- log_event("delivery_dropped_expired", entry_id=entry.id, asset=entry.asset)
130
+ # The producing tick's id, read off the journalled signal — the tick that
131
+ # noticed the expiry did not cause it.
132
+ log_event(
133
+ "delivery_dropped_expired",
134
+ entry_id=entry.id,
135
+ asset=entry.asset,
136
+ **_tick_field(_signal_tick_id(entry.signal)),
137
+ )
93
138
  return to_send
94
139
 
140
+ def _usable_tick_id(tick_id: Any) -> str | None:
141
+ """The tick id to stamp, or None to leave the field off the wire entirely.
142
+
143
+ The intake requires a non-empty string and rejects the WHOLE post on one bad field, so an
144
+ unusable id is dropped here rather than taking the tick's signals down with it. No id at
145
+ all is the old call shape, not a fault, and is not logged.
146
+ """
147
+ if tick_id is None or (isinstance(tick_id, str) and tick_id):
148
+ return tick_id
149
+ shape = repr(tick_id)[:80]
150
+ if shape in warned_unusable_tick_ids:
151
+ return None
152
+ warned_unusable_tick_ids.add(shape)
153
+ log_event("delivery_tick_id_unusable", scanner_id=scanner_id, rejected_tick_id=shape)
154
+ return None
155
+
95
156
  def _on_reject(reason: dict) -> None:
96
157
  """Loud rejection: log AND POST to /errors (design §4 — never a silent drop)."""
97
- log_event("delivery_candidate_invalid", **reason)
98
- _post_error({"type": "candidate_rejected", **reason})
158
+ # Merged into one dict before expanding: a reject record that happens to carry
159
+ # its own `tick_id` would otherwise be a duplicate kwarg — a TypeError raised
160
+ # here would take down the whole tick's delivery. The scaffold's id goes last,
161
+ # so the tick that actually ran the candidate is the one named.
162
+ log_event("delivery_candidate_invalid", **{**reason, **_tick_field(current_tick_id[0])})
163
+ _post_error({"type": "candidate_rejected", **reason, **_tick_field(current_tick_id[0])})
99
164
 
100
165
  def _on_warn(info: dict) -> None:
101
166
  """Loud-but-non-fatal: a valid signal still emitted, but author top-level
@@ -110,9 +175,10 @@ def build_delivery_sink(
110
175
  scanner_id=scanner_id,
111
176
  dropped=info.get("dropped"),
112
177
  asset=info.get("asset"),
178
+ **_tick_field(current_tick_id[0]),
113
179
  )
114
180
 
115
- def _wrap_candidates(candidates, produced_at_ms: int) -> list[tuple]:
181
+ def _wrap_candidates(candidates, produced_at_ms: int, tick_id: str | None) -> list[tuple]:
116
182
  """Wrap candidates into (signal, entry_id) pairs, skipping invalid ones.
117
183
 
118
184
  Schema-rejected candidates are NOT silently dropped: candidate_to_signal
@@ -135,13 +201,51 @@ def build_delivery_sink(
135
201
  )
136
202
  if sig is None:
137
203
  continue
204
+ # Stamped before the journal writes the entry, so a retry — later in this process or
205
+ # after a restart — re-posts the id of the tick that PRODUCED the signal. Skipped
206
+ # once the intake has proved it predates the field: a stamped signal is refused and
207
+ # re-posted without it, and stamping again would buy that same 400 every tick.
208
+ if tick_id is not None and not intake_predates_tick_id[0]:
209
+ sig["tick_id"] = tick_id
138
210
  entry_id = journal.record_produced(sig, now_ms=produced_at_ms)
139
211
  result.append((sig, entry_id))
140
212
  return result
141
213
 
142
- def _post_signals(signals: list[dict]) -> tuple[int, Any]:
143
- """POST the signals list to the intake."""
214
+ def _is_shape_reject(status: int, parsed: Any) -> bool:
215
+ """True when the intake refused the whole POST because it did not know the shape."""
216
+ if status != 400 or not isinstance(parsed, dict):
217
+ return False
218
+ err = parsed.get("error")
219
+ return isinstance(err, dict) and err.get("code") == "INVALID_SHAPE"
220
+
221
+ def _without_tick_id(signals: list[dict]) -> list[dict]:
222
+ """Copies of `signals` with `tick_id` removed.
223
+
224
+ Copies, never edits: the journal keeps each entry's producing tick, so once the intake
225
+ is upgraded a later retry of the same entry names it again.
226
+ """
227
+ return [
228
+ {k: v for k, v in sig.items() if k != "tick_id"} if isinstance(sig, dict) else sig
229
+ for sig in signals
230
+ ]
231
+
232
+ def _with_pending(tick: dict | None) -> dict | None:
233
+ """The tick facts plus the journal's BACKLOG, which only this closure knows.
234
+
235
+ Counted before this tick's own candidates are journalled, so the number is
236
+ signals from EARLIER ticks that the intake never acknowledged — a scanner
237
+ producing work that is not arriving. Counting this tick's own signals too
238
+ would put a 1 on every healthy signal-bearing tick and say nothing.
239
+ """
240
+ if tick is None:
241
+ return None
242
+ return {**tick, "journal_pending": len(journal.pending())}
243
+
244
+ def _post_signals(signals: list[dict], tick: dict | None = None) -> tuple[int, Any]:
245
+ """POST the signals list to the intake, with this tick's facts when it has any."""
144
246
  body = {"scanner_id": scanner_id, "signals": signals}
247
+ if tick is not None and not intake_predates_tick_facts[0]:
248
+ body["tick"] = tick
145
249
  # Log the exact wire shape so a 400 can be matched to the field that broke.
146
250
  if signals:
147
251
  log_event(
@@ -154,7 +258,7 @@ def build_delivery_sink(
154
258
  url = intake_url.rstrip("/") + SIGNALS_PATH
155
259
  return post_json(url, body, timeout)
156
260
 
157
- def _interpret_response(status: int, parsed: Any, pending_eids: list[str], pending_entries: list) -> bool:
261
+ def _interpret_response(status: int, parsed: Any, pending_eids: list[str], pending_signals: list) -> bool:
158
262
  """Interpret POST response; journal outcomes. Returns True if successful envelope."""
159
263
  if status != 200 or parsed is None:
160
264
  if isinstance(parsed, dict):
@@ -194,6 +298,10 @@ def build_delivery_sink(
194
298
  # signals), and unknown/missing signal_id (ignored safely, never applied by
195
299
  # position). An unmatched entry stays pending and is retried.
196
300
  pending = set(pending_eids)
301
+ # eid -> the tick that produced it, for the per-entry lines below. Built only if one of
302
+ # those lines is actually reached: every POST pays for it otherwise, and the unknown-status
303
+ # branch that reads it is the rare one.
304
+ produced_by: dict | None = None
197
305
  for sig_status in per_signal:
198
306
  if not isinstance(sig_status, dict):
199
307
  continue
@@ -211,7 +319,16 @@ def build_delivery_sink(
211
319
  journal.record_outcome(eid, st)
212
320
  else:
213
321
  # Unknown status — treat as undelivered (no outcome written)
214
- log_event("delivery_unknown_status", entry_id=eid, status=st)
322
+ if produced_by is None:
323
+ produced_by = {
324
+ e: _signal_tick_id(sig) for e, sig in zip(pending_eids, pending_signals)
325
+ }
326
+ log_event(
327
+ "delivery_unknown_status",
328
+ entry_id=eid,
329
+ status=st,
330
+ **_tick_field(produced_by.get(eid)),
331
+ )
215
332
  # Entries with no matching status returned stay pending (retry next tick).
216
333
 
217
334
  return True
@@ -220,7 +337,10 @@ def build_delivery_sink(
220
337
  # The sink callable
221
338
  # ------------------------------------------------------------------
222
339
 
223
- def _sink(candidates) -> None:
340
+ def _sink(candidates, tick_id=None, tick=None) -> None:
341
+ tick_id = _usable_tick_id(tick_id)
342
+ current_tick_id[0] = tick_id
343
+
224
344
  # Replay journal on very first invocation
225
345
  if not replayed[0]:
226
346
  replayed[0] = True
@@ -232,8 +352,12 @@ def build_delivery_sink(
232
352
  # 1. Flush prior pending (retry candidates from older ticks)
233
353
  prior_entries = _flush_prior_pending(now_ms)
234
354
 
355
+ # Read here and not below: the backlog is what EARLIER ticks left behind, and
356
+ # step 2 is about to journal this tick's own candidates on top of it.
357
+ tick_body = _with_pending(tick)
358
+
235
359
  # 2. Wrap new candidates into signals
236
- new_signal_pairs = _wrap_candidates(candidates, produced_at_ms)
360
+ new_signal_pairs = _wrap_candidates(candidates, produced_at_ms, tick_id)
237
361
 
238
362
  # Build the ordered signal list and corresponding entry-id list:
239
363
  # prior entries first (retry), then new ones.
@@ -253,30 +377,75 @@ def build_delivery_sink(
253
377
  all_signals.append(sig)
254
378
  all_eids.append(eid)
255
379
 
256
- # 3. POST (always, even when signals==[])
380
+ # 3. POST (always, even when signals==[]) — which is what makes this the channel
381
+ # the tick facts ride: a quiet scanner still posts, every tick.
257
382
  try:
258
- status, parsed = _post_signals(all_signals)
383
+ status, parsed = _post_signals(all_signals, tick_body)
259
384
  except TransportError as exc:
260
385
  log_event("delivery_transport_error", error=str(exc))
261
386
  # All signals remain undelivered -> retry on next tick
262
387
  # Empty-list no-ops: nothing to retry (no produced lines were written)
263
388
  return
264
389
 
390
+ # 3b. Forward-compat guard for an intake rolled back to before tick_id. Such an intake
391
+ # validates with additionalProperties:false, so it 400s the WHOLE batch on the unknown
392
+ # field — and because the journal re-posts the stored signal untouched, that batch is
393
+ # re-sent every tick forever, taking every fresh signal batched with it down too. Re-post
394
+ # the same batch without the field so one unknown key cannot strand real signals, and
395
+ # latch the discovery so later ticks leave the field off and stop paying the refused POST.
396
+ # Entries already journalled with an id still reach here and are still stripped per POST.
397
+ sent_tick_id = any(isinstance(sig, dict) and "tick_id" in sig for sig in all_signals)
398
+ sent_tick_facts = tick_body is not None and not intake_predates_tick_facts[0]
399
+ if _is_shape_reject(status, parsed) and (sent_tick_id or sent_tick_facts):
400
+ if sent_tick_id and not intake_predates_tick_id[0]:
401
+ intake_predates_tick_id[0] = True
402
+ log_event("delivery_intake_predates_tick_id", scanner_id=scanner_id)
403
+ if sent_tick_facts:
404
+ intake_predates_tick_facts[0] = True
405
+ log_event("delivery_intake_predates_tick_facts", scanner_id=scanner_id)
406
+ try:
407
+ status, parsed = _post_signals(_without_tick_id(all_signals))
408
+ except TransportError as exc:
409
+ log_event("delivery_transport_error", error=str(exc))
410
+ return
411
+
265
412
  # 4. Interpret response
266
413
  # The response per-signal list corresponds only to all_signals that have
267
- # produced entries (all_eids). Empty no-ops: no eids.
268
- _interpret_response(status, parsed, all_eids, [])
414
+ # produced entries (all_eids). Empty no-ops: no eids. The signals are the ones the
415
+ # journal holds — with their tick ids — so a log line still names the producing tick
416
+ # even when the retry above had to send them without it.
417
+ _interpret_response(status, parsed, all_eids, all_signals)
418
+
419
+ def _post_error(error: dict, tick: dict | None = None) -> None:
420
+ """POST an error dict, with the failed tick's facts when the caller has them.
269
421
 
270
- def _post_error(error: dict) -> None:
271
- """POST an error dict. Fire-and-forget."""
272
- body = {"scanner_id": scanner_id, "error": error}
422
+ Fire-and-forget, with one exception: an intake that refuses the body because it
423
+ does not know `tick` is retried once without it and latched, so a failed tick's
424
+ error record is never lost to a field that only telemetry wanted.
425
+ """
426
+ tick_body = _with_pending(tick)
273
427
  url = intake_url.rstrip("/") + ERRORS_PATH
428
+ # The error belongs to exactly one tick and already names it; a body that
429
+ # names none leaves the key off rather than borrowing the current one.
430
+ origin = _tick_field(error.get("tick_id")) if isinstance(error, dict) else {}
431
+
432
+ def _body(with_tick: bool) -> dict:
433
+ body = {"scanner_id": scanner_id, "error": error}
434
+ if with_tick and tick_body is not None and not intake_predates_tick_facts[0]:
435
+ body["tick"] = tick_body
436
+ return body
437
+
274
438
  try:
275
- post_json(url, body, timeout)
439
+ sent_tick_facts = tick_body is not None and not intake_predates_tick_facts[0]
440
+ status, parsed = post_json(url, _body(True), timeout)
441
+ if sent_tick_facts and _is_shape_reject(status, parsed):
442
+ intake_predates_tick_facts[0] = True
443
+ log_event("delivery_intake_predates_tick_facts", scanner_id=scanner_id)
444
+ post_json(url, _body(False), timeout)
276
445
  except TransportError as exc:
277
- log_event("delivery_error_post_failed", error=str(exc))
446
+ log_event("delivery_error_post_failed", error=str(exc), **origin)
278
447
  except Exception as exc: # noqa: BLE001
279
- log_event("delivery_error_post_failed", error=str(exc))
448
+ log_event("delivery_error_post_failed", error=str(exc), **origin)
280
449
 
281
450
  _sink.post_error = _post_error # type: ignore[attr-defined]
282
451
  return _sink