@winkjs/composer 0.0.2 → 0.4.0

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 (451) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +42 -103
  3. package/package.json +53 -29
  4. package/src/composer.js +75 -0
  5. package/src/core/batch/aoo-encoder.js +148 -0
  6. package/src/core/batch/index.js +12 -0
  7. package/src/core/batch/sliding-window.js +91 -0
  8. package/src/core/codec/index.js +137 -0
  9. package/src/core/emitter-manager/mqtt/constants.js +102 -0
  10. package/src/core/emitter-manager/mqtt/emitter.js +780 -0
  11. package/src/core/emitter-manager/mqtt/index.js +190 -0
  12. package/src/core/emitter-manager/mqtt/mqtt-store.js +538 -0
  13. package/src/core/emitter-manager/terminal/assert-columns.js +98 -0
  14. package/src/core/emitter-manager/terminal/formatters.js +160 -0
  15. package/src/core/emitter-manager/terminal/index.js +341 -0
  16. package/src/core/env-vars.js +245 -0
  17. package/src/core/mqtt-protocol.js +47 -0
  18. package/src/core/partition-manager/index.js +7 -0
  19. package/src/core/partition-manager/init.js +44 -0
  20. package/src/core/partition-manager/introspect.js +15 -0
  21. package/src/core/partition-manager/publish-to.js +6 -0
  22. package/src/core/partition-manager/recompute.js +5 -0
  23. package/src/core/partition-manager/reset.js +5 -0
  24. package/src/core/partition-manager/update.js +264 -0
  25. package/src/core/partition-manager/validate-flow.js +157 -0
  26. package/src/core/semantics/digest.js +128 -0
  27. package/src/core/semantics/index.js +83 -0
  28. package/src/core/semantics/loader.js +571 -0
  29. package/src/core/semantics/schemas/asset-class-schema.js +218 -0
  30. package/src/core/semantics/schemas/column-schema.js +651 -0
  31. package/src/core/semantics/schemas/enum-schema.js +153 -0
  32. package/src/core/semantics/schemas/index.js +51 -0
  33. package/src/core/semantics/warnings.js +141 -0
  34. package/src/core/shutdown-manager/index.js +289 -0
  35. package/src/core/source-manager/csv/index.js +171 -0
  36. package/src/core/source-manager/csv/start.js +456 -0
  37. package/src/core/source-manager/mqtt/client.js +406 -0
  38. package/src/core/source-manager/mqtt/constants.js +147 -0
  39. package/src/core/source-manager/mqtt/dedup.js +244 -0
  40. package/src/core/source-manager/mqtt/index.js +260 -0
  41. package/src/core/source-manager/mqtt/status.js +546 -0
  42. package/src/core/source-manager/test-harness/comparator.js +226 -0
  43. package/src/core/source-manager/test-harness/field-generator.js +85 -0
  44. package/src/core/source-manager/test-harness/fuzz.js +87 -0
  45. package/src/core/source-manager/test-harness/index.js +115 -0
  46. package/src/core/source-manager/test-harness/prng.js +57 -0
  47. package/src/core/source-manager/test-harness/start.js +232 -0
  48. package/src/core/source-manager/test-harness/validate.js +213 -0
  49. package/src/core/storage-manager/questdb/assert-columns.js +120 -0
  50. package/src/core/storage-manager/questdb/ensure-tables.js +187 -0
  51. package/src/core/storage-manager/questdb/index.js +1251 -0
  52. package/src/core/storage-manager/questdb/persist-plan.js +488 -0
  53. package/src/core/storage-manager/questdb/writers.js +142 -0
  54. package/src/core/test-utils/sealed-record.js +80 -0
  55. package/src/core/test-utils/soak-signature.js +149 -0
  56. package/src/core/test-utils/tcp-proxy.js +106 -0
  57. package/src/core/tunable/helpers.js +254 -0
  58. package/src/core/tunable/index.js +87 -0
  59. package/src/core/utils/console-formatter/index.js +207 -0
  60. package/src/core/utils/create-smart-store-as/index.js +98 -0
  61. package/src/core/utils/create-smart-store-as/sanitize-field.js +46 -0
  62. package/src/core/utils/create-smart-store-as/smart-truncate.js +93 -0
  63. package/src/core/utils/flow/index.js +5 -0
  64. package/src/core/utils/flow/make-collision-checker.js +23 -0
  65. package/src/core/utils/flow/to-kebab.js +20 -0
  66. package/src/core/utils/half-life/index.js +111 -0
  67. package/src/core/utils/node/disable.js +11 -0
  68. package/src/core/utils/node/enable.js +11 -0
  69. package/src/core/utils/node/execute-triggers.js +40 -0
  70. package/src/core/utils/node/index.js +10 -0
  71. package/src/core/utils/node/pause.js +11 -0
  72. package/src/core/utils/node/populate-predicate-inputs.js +14 -0
  73. package/src/core/utils/node/publish-nan.js +19 -0
  74. package/src/core/utils/node/reset-predicate-inputs.js +11 -0
  75. package/src/core/utils/node/unpause.js +11 -0
  76. package/src/core/utils/node/validate-spec.js +22 -0
  77. package/src/core/utils/options/resolve-field-keyed.js +115 -0
  78. package/src/core/utils/quantize/index.js +75 -0
  79. package/src/core/utils/stats/formulas.js +67 -0
  80. package/src/core/utils/stats/index.js +3 -0
  81. package/src/core/utils/validate/composer-validators.js +141 -0
  82. package/src/core/utils/validate/helpers.js +206 -0
  83. package/src/core/utils/validate/index.js +12 -0
  84. package/src/core/utils/validate/type-validators.js +538 -0
  85. package/src/core/utils/validate/validate.js +107 -0
  86. package/src/core/utils/validate/validators.js +177 -0
  87. package/src/core/wiring/assert-handle.js +57 -0
  88. package/src/core/wiring/assert-module.js +63 -0
  89. package/src/core/wiring/index.js +5 -0
  90. package/src/core/wiring/node-type-to-module.js +17 -0
  91. package/src/core/wiring/wire-emitters.js +256 -0
  92. package/src/core/wiring/wire-linear-graph.js +25 -0
  93. package/src/core/wiring/wire-node.js +157 -0
  94. package/src/core/wiring/wire-semantics.js +173 -0
  95. package/src/core/wiring/wire-storages.js +344 -0
  96. package/src/flow/check-output-collisions.js +93 -0
  97. package/src/flow/consts.js +9 -0
  98. package/src/flow/deep-clone-spec.js +64 -0
  99. package/src/flow/driver.js +171 -0
  100. package/src/flow/expand-single-input-array.js +76 -0
  101. package/src/flow/expand-template-spec.js +93 -0
  102. package/src/flow/flow-api-schemas.js +300 -0
  103. package/src/flow/flow.js +658 -0
  104. package/src/flow/for-each.js +225 -0
  105. package/src/flow/get-signature-pattern.js +31 -0
  106. package/src/flow/get-target-array.js +46 -0
  107. package/src/flow/inspect.js +67 -0
  108. package/src/flow/load-node-module.js +81 -0
  109. package/src/flow/make-node-method.js +112 -0
  110. package/src/flow/resolve-pick-by-field.js +77 -0
  111. package/src/flow/resolve-tunables.js +76 -0
  112. package/src/flow/run.js +392 -0
  113. package/src/flow/serialize.js +161 -0
  114. package/src/flow/signatures-schema.js +222 -0
  115. package/src/flow/spec-builder.js +65 -0
  116. package/src/flow/validate-triggers.js +250 -0
  117. package/src/flow/validate.js +261 -0
  118. package/src/nodes/accumulate/index.js +21 -0
  119. package/src/nodes/accumulate/init.js +48 -0
  120. package/src/nodes/accumulate/introspect.js +123 -0
  121. package/src/nodes/accumulate/publish-to.js +34 -0
  122. package/src/nodes/accumulate/recompute.js +27 -0
  123. package/src/nodes/accumulate/reset.js +25 -0
  124. package/src/nodes/accumulate/update.js +42 -0
  125. package/src/nodes/appraise/calibrate.js +64 -0
  126. package/src/nodes/appraise/decision.js +66 -0
  127. package/src/nodes/appraise/deviation.js +140 -0
  128. package/src/nodes/appraise/index.js +23 -0
  129. package/src/nodes/appraise/init.js +165 -0
  130. package/src/nodes/appraise/integrate.js +45 -0
  131. package/src/nodes/appraise/introspect.js +352 -0
  132. package/src/nodes/appraise/publish-to.js +93 -0
  133. package/src/nodes/appraise/receptor.js +69 -0
  134. package/src/nodes/appraise/recompute.js +79 -0
  135. package/src/nodes/appraise/reset.js +53 -0
  136. package/src/nodes/appraise/update.js +130 -0
  137. package/src/nodes/archive/csv/index.js +6 -0
  138. package/src/nodes/archive/csv/init.js +36 -0
  139. package/src/nodes/archive/csv/load.js +70 -0
  140. package/src/nodes/archive/csv/reset.js +14 -0
  141. package/src/nodes/archive/csv/simple-parser.js +216 -0
  142. package/src/nodes/archive/csv/test.js +173 -0
  143. package/src/nodes/archive/csv/update.js +63 -0
  144. package/src/nodes/butterworth-filter/index.js +18 -0
  145. package/src/nodes/butterworth-filter/init.js +184 -0
  146. package/src/nodes/butterworth-filter/introspect.js +228 -0
  147. package/src/nodes/butterworth-filter/publish-to.js +21 -0
  148. package/src/nodes/butterworth-filter/recompute.js +13 -0
  149. package/src/nodes/butterworth-filter/reset.js +30 -0
  150. package/src/nodes/butterworth-filter/update.js +38 -0
  151. package/src/nodes/categorize/index.js +19 -0
  152. package/src/nodes/categorize/init.js +58 -0
  153. package/src/nodes/categorize/introspect.js +177 -0
  154. package/src/nodes/categorize/publish-to.js +29 -0
  155. package/src/nodes/categorize/recompute.js +14 -0
  156. package/src/nodes/categorize/reset.js +16 -0
  157. package/src/nodes/categorize/update.js +53 -0
  158. package/src/nodes/controller/index.js +9 -0
  159. package/src/nodes/controller/init.js +55 -0
  160. package/src/nodes/controller/introspect.js +91 -0
  161. package/src/nodes/controller/publish-to.js +8 -0
  162. package/src/nodes/controller/recompute.js +9 -0
  163. package/src/nodes/controller/reset.js +17 -0
  164. package/src/nodes/controller/update.js +63 -0
  165. package/src/nodes/diff/index.js +19 -0
  166. package/src/nodes/diff/init.js +37 -0
  167. package/src/nodes/diff/introspect.js +132 -0
  168. package/src/nodes/diff/publish-to.js +21 -0
  169. package/src/nodes/diff/recompute.js +12 -0
  170. package/src/nodes/diff/reset.js +12 -0
  171. package/src/nodes/diff/update.js +37 -0
  172. package/src/nodes/digest-moments/formulas.js +12 -0
  173. package/src/nodes/digest-moments/index.js +14 -0
  174. package/src/nodes/digest-moments/init.js +67 -0
  175. package/src/nodes/digest-moments/introspect.js +154 -0
  176. package/src/nodes/digest-moments/publish-to.js +34 -0
  177. package/src/nodes/digest-moments/recompute.js +14 -0
  178. package/src/nodes/digest-moments/reset.js +14 -0
  179. package/src/nodes/digest-moments/update.js +120 -0
  180. package/src/nodes/dwell-time-tracker/index.js +14 -0
  181. package/src/nodes/dwell-time-tracker/init.js +59 -0
  182. package/src/nodes/dwell-time-tracker/introspect.js +137 -0
  183. package/src/nodes/dwell-time-tracker/publish-to.js +22 -0
  184. package/src/nodes/dwell-time-tracker/recompute.js +8 -0
  185. package/src/nodes/dwell-time-tracker/reset.js +23 -0
  186. package/src/nodes/dwell-time-tracker/update.js +103 -0
  187. package/src/nodes/emit-if/helpers.js +94 -0
  188. package/src/nodes/emit-if/index.js +16 -0
  189. package/src/nodes/emit-if/init.js +67 -0
  190. package/src/nodes/emit-if/introspect.js +102 -0
  191. package/src/nodes/emit-if/publish-to.js +13 -0
  192. package/src/nodes/emit-if/recompute.js +12 -0
  193. package/src/nodes/emit-if/reset.js +27 -0
  194. package/src/nodes/emit-if/update.js +93 -0
  195. package/src/nodes/es-correlation/index.js +19 -0
  196. package/src/nodes/es-correlation/init.js +73 -0
  197. package/src/nodes/es-correlation/introspect.js +176 -0
  198. package/src/nodes/es-correlation/publish-to.js +32 -0
  199. package/src/nodes/es-correlation/recompute.js +56 -0
  200. package/src/nodes/es-correlation/reset.js +27 -0
  201. package/src/nodes/es-correlation/update.js +110 -0
  202. package/src/nodes/es-mean/index.js +20 -0
  203. package/src/nodes/es-mean/init.js +58 -0
  204. package/src/nodes/es-mean/introspect.js +129 -0
  205. package/src/nodes/es-mean/publish-to.js +19 -0
  206. package/src/nodes/es-mean/recompute.js +16 -0
  207. package/src/nodes/es-mean/reset.js +24 -0
  208. package/src/nodes/es-mean/update.js +69 -0
  209. package/src/nodes/es-pairwise-correlation/index.js +19 -0
  210. package/src/nodes/es-pairwise-correlation/init.js +98 -0
  211. package/src/nodes/es-pairwise-correlation/introspect.js +187 -0
  212. package/src/nodes/es-pairwise-correlation/publish-to.js +31 -0
  213. package/src/nodes/es-pairwise-correlation/recompute.js +69 -0
  214. package/src/nodes/es-pairwise-correlation/reset.js +29 -0
  215. package/src/nodes/es-pairwise-correlation/update.js +89 -0
  216. package/src/nodes/es-stats/index.js +21 -0
  217. package/src/nodes/es-stats/init.js +105 -0
  218. package/src/nodes/es-stats/introspect.js +157 -0
  219. package/src/nodes/es-stats/publish-to.js +42 -0
  220. package/src/nodes/es-stats/recompute.js +74 -0
  221. package/src/nodes/es-stats/reset.js +49 -0
  222. package/src/nodes/es-stats/update-envelope.js +44 -0
  223. package/src/nodes/es-stats/update-welford.js +64 -0
  224. package/src/nodes/es-stats/update.js +59 -0
  225. package/src/nodes/index.js +83 -0
  226. package/src/nodes/invert-flag/index.js +20 -0
  227. package/src/nodes/invert-flag/init.js +36 -0
  228. package/src/nodes/invert-flag/introspect.js +115 -0
  229. package/src/nodes/invert-flag/publish-to.js +23 -0
  230. package/src/nodes/invert-flag/recompute.js +12 -0
  231. package/src/nodes/invert-flag/reset.js +12 -0
  232. package/src/nodes/invert-flag/update.js +32 -0
  233. package/src/nodes/kalman1d/index.js +20 -0
  234. package/src/nodes/kalman1d/init.js +103 -0
  235. package/src/nodes/kalman1d/introspect.js +250 -0
  236. package/src/nodes/kalman1d/publish-to.js +34 -0
  237. package/src/nodes/kalman1d/recompute.js +23 -0
  238. package/src/nodes/kalman1d/reset.js +29 -0
  239. package/src/nodes/kalman1d/update.js +144 -0
  240. package/src/nodes/kernel/index.js +18 -0
  241. package/src/nodes/kernel/init.js +66 -0
  242. package/src/nodes/kernel/introspect.js +163 -0
  243. package/src/nodes/kernel/presets.js +74 -0
  244. package/src/nodes/kernel/publish-to.js +25 -0
  245. package/src/nodes/kernel/recompute.js +14 -0
  246. package/src/nodes/kernel/reset.js +20 -0
  247. package/src/nodes/kernel/update.js +69 -0
  248. package/src/nodes/lag/index.js +21 -0
  249. package/src/nodes/lag/init.js +87 -0
  250. package/src/nodes/lag/introspect.js +202 -0
  251. package/src/nodes/lag/publish-to.js +64 -0
  252. package/src/nodes/lag/recompute.js +33 -0
  253. package/src/nodes/lag/reset.js +49 -0
  254. package/src/nodes/lag/update.js +133 -0
  255. package/src/nodes/median3/index.js +19 -0
  256. package/src/nodes/median3/init.js +40 -0
  257. package/src/nodes/median3/introspect.js +116 -0
  258. package/src/nodes/median3/publish-to.js +21 -0
  259. package/src/nodes/median3/recompute.js +12 -0
  260. package/src/nodes/median3/reset.js +15 -0
  261. package/src/nodes/median3/update.js +58 -0
  262. package/src/nodes/moments-digest/copy-from.js +12 -0
  263. package/src/nodes/moments-digest/flush.js +17 -0
  264. package/src/nodes/moments-digest/index.js +132 -0
  265. package/src/nodes/moments-digest/init.js +84 -0
  266. package/src/nodes/moments-digest/introspect.js +122 -0
  267. package/src/nodes/moments-digest/prelude.js +28 -0
  268. package/src/nodes/moments-digest/publish-to.js +31 -0
  269. package/src/nodes/moments-digest/recompute.js +9 -0
  270. package/src/nodes/moments-digest/reset.js +20 -0
  271. package/src/nodes/moments-digest/update.js +182 -0
  272. package/src/nodes/node-names.js +67 -0
  273. package/src/nodes/page-hinkley/index.js +20 -0
  274. package/src/nodes/page-hinkley/init.js +66 -0
  275. package/src/nodes/page-hinkley/introspect.js +193 -0
  276. package/src/nodes/page-hinkley/publish-to.js +28 -0
  277. package/src/nodes/page-hinkley/recompute.js +14 -0
  278. package/src/nodes/page-hinkley/reset.js +23 -0
  279. package/src/nodes/page-hinkley/update.js +105 -0
  280. package/src/nodes/pass-if/index.js +21 -0
  281. package/src/nodes/pass-if/init.js +37 -0
  282. package/src/nodes/pass-if/introspect.js +93 -0
  283. package/src/nodes/pass-if/publish-to.js +15 -0
  284. package/src/nodes/pass-if/recompute.js +15 -0
  285. package/src/nodes/pass-if/reset.js +18 -0
  286. package/src/nodes/pass-if/update.js +37 -0
  287. package/src/nodes/persist-if/helpers.js +111 -0
  288. package/src/nodes/persist-if/index.js +9 -0
  289. package/src/nodes/persist-if/init.js +98 -0
  290. package/src/nodes/persist-if/introspect.js +106 -0
  291. package/src/nodes/persist-if/publish-to.js +21 -0
  292. package/src/nodes/persist-if/recompute.js +20 -0
  293. package/src/nodes/persist-if/reset.js +35 -0
  294. package/src/nodes/persist-if/update.js +112 -0
  295. package/src/nodes/persistence-check/index.js +14 -0
  296. package/src/nodes/persistence-check/init.js +38 -0
  297. package/src/nodes/persistence-check/introspect.js +144 -0
  298. package/src/nodes/persistence-check/publish-to.js +11 -0
  299. package/src/nodes/persistence-check/recompute.js +7 -0
  300. package/src/nodes/persistence-check/reset.js +12 -0
  301. package/src/nodes/persistence-check/update.js +57 -0
  302. package/src/nodes/process-index/index.js +15 -0
  303. package/src/nodes/process-index/init.js +48 -0
  304. package/src/nodes/process-index/introspect.js +182 -0
  305. package/src/nodes/process-index/publish-to.js +29 -0
  306. package/src/nodes/process-index/recompute.js +8 -0
  307. package/src/nodes/process-index/reset.js +8 -0
  308. package/src/nodes/process-index/update.js +75 -0
  309. package/src/nodes/ratio/index.js +14 -0
  310. package/src/nodes/ratio/init.js +37 -0
  311. package/src/nodes/ratio/introspect.js +155 -0
  312. package/src/nodes/ratio/publish-to.js +15 -0
  313. package/src/nodes/ratio/recompute.js +7 -0
  314. package/src/nodes/ratio/reset.js +7 -0
  315. package/src/nodes/ratio/update.js +47 -0
  316. package/src/nodes/sanitize/index.js +82 -0
  317. package/src/nodes/sanitize/init.js +82 -0
  318. package/src/nodes/sanitize/introspect.js +194 -0
  319. package/src/nodes/sanitize/publish-to.js +15 -0
  320. package/src/nodes/sanitize/recompute.js +8 -0
  321. package/src/nodes/sanitize/reset.js +14 -0
  322. package/src/nodes/sanitize/update.js +102 -0
  323. package/src/nodes/spike-guard/index.js +14 -0
  324. package/src/nodes/spike-guard/init.js +47 -0
  325. package/src/nodes/spike-guard/introspect.js +130 -0
  326. package/src/nodes/spike-guard/publish-to.js +35 -0
  327. package/src/nodes/spike-guard/recompute.js +14 -0
  328. package/src/nodes/spike-guard/reset.js +19 -0
  329. package/src/nodes/spike-guard/update.js +89 -0
  330. package/src/nodes/sse-emitter/index.js +7 -0
  331. package/src/nodes/sse-emitter/init.js +105 -0
  332. package/src/nodes/sse-emitter/introspect.js +134 -0
  333. package/src/nodes/sse-emitter/publish-to.js +6 -0
  334. package/src/nodes/sse-emitter/recompute.js +16 -0
  335. package/src/nodes/sse-emitter/reset.js +19 -0
  336. package/src/nodes/sse-emitter/update.js +69 -0
  337. package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +36 -0
  338. package/src/nodes/sse-emitter/utils/build-message.js +31 -0
  339. package/src/nodes/sse-emitter/utils/connection-manager.js +45 -0
  340. package/src/nodes/sse-emitter/utils/endpoint-initializer.js +20 -0
  341. package/src/nodes/sse-emitter/utils/health-monitor.js +38 -0
  342. package/src/nodes/sse-emitter/utils/qos-handler.js +41 -0
  343. package/src/nodes/state-change-detector/helpers.js +35 -0
  344. package/src/nodes/state-change-detector/index.js +12 -0
  345. package/src/nodes/state-change-detector/init.js +56 -0
  346. package/src/nodes/state-change-detector/introspect.js +146 -0
  347. package/src/nodes/state-change-detector/publish-to.js +20 -0
  348. package/src/nodes/state-change-detector/recompute.js +6 -0
  349. package/src/nodes/state-change-detector/reset.js +20 -0
  350. package/src/nodes/state-change-detector/update.js +97 -0
  351. package/src/nodes/sw-stats/index.js +14 -0
  352. package/src/nodes/sw-stats/init.js +51 -0
  353. package/src/nodes/sw-stats/introspect.js +131 -0
  354. package/src/nodes/sw-stats/publish-to.js +45 -0
  355. package/src/nodes/sw-stats/recompute.js +33 -0
  356. package/src/nodes/sw-stats/reset.js +15 -0
  357. package/src/nodes/sw-stats/update.js +39 -0
  358. package/src/nodes/swing-watch/index.js +17 -0
  359. package/src/nodes/swing-watch/init.js +144 -0
  360. package/src/nodes/swing-watch/introspect.js +188 -0
  361. package/src/nodes/swing-watch/publish-to.js +53 -0
  362. package/src/nodes/swing-watch/recompute.js +22 -0
  363. package/src/nodes/swing-watch/reset.js +55 -0
  364. package/src/nodes/swing-watch/update.js +767 -0
  365. package/src/nodes/tally/index.js +15 -0
  366. package/src/nodes/tally/init.js +32 -0
  367. package/src/nodes/tally/introspect.js +116 -0
  368. package/src/nodes/tally/publish-to.js +28 -0
  369. package/src/nodes/tally/recompute.js +10 -0
  370. package/src/nodes/tally/reset.js +10 -0
  371. package/src/nodes/tally/update.js +44 -0
  372. package/src/nodes/threshold/index.js +14 -0
  373. package/src/nodes/threshold/init.js +121 -0
  374. package/src/nodes/threshold/introspect.js +192 -0
  375. package/src/nodes/threshold/publish-to.js +11 -0
  376. package/src/nodes/threshold/recompute.js +8 -0
  377. package/src/nodes/threshold/reset.js +14 -0
  378. package/src/nodes/threshold/update.js +54 -0
  379. package/src/nodes/transform/helpers.js +39 -0
  380. package/src/nodes/transform/index.js +15 -0
  381. package/src/nodes/transform/init.js +35 -0
  382. package/src/nodes/transform/introspect.js +118 -0
  383. package/src/nodes/transform/publish-to.js +23 -0
  384. package/src/nodes/transform/recompute.js +12 -0
  385. package/src/nodes/transform/reset.js +11 -0
  386. package/src/nodes/transform/update.js +32 -0
  387. package/src/nodes/trend/compute-confidence.js +112 -0
  388. package/src/nodes/trend/index.js +14 -0
  389. package/src/nodes/trend/init.js +81 -0
  390. package/src/nodes/trend/introspect.js +151 -0
  391. package/src/nodes/trend/publish-to.js +22 -0
  392. package/src/nodes/trend/recompute.js +15 -0
  393. package/src/nodes/trend/reset.js +26 -0
  394. package/src/nodes/trend/update.js +93 -0
  395. package/src/nodes/tw-stats/copy-from.js +16 -0
  396. package/src/nodes/tw-stats/flush.js +12 -0
  397. package/src/nodes/tw-stats/index.js +41 -0
  398. package/src/nodes/tw-stats/init.js +92 -0
  399. package/src/nodes/tw-stats/introspect.js +171 -0
  400. package/src/nodes/tw-stats/publish-to.js +108 -0
  401. package/src/nodes/tw-stats/recompute.js +9 -0
  402. package/src/nodes/tw-stats/reset.js +16 -0
  403. package/src/nodes/tw-stats/update.js +113 -0
  404. package/src/nodes/unbalance/index.js +15 -0
  405. package/src/nodes/unbalance/init.js +59 -0
  406. package/src/nodes/unbalance/introspect.js +167 -0
  407. package/src/nodes/unbalance/publish-to.js +52 -0
  408. package/src/nodes/unbalance/recompute.js +11 -0
  409. package/src/nodes/unbalance/reset.js +10 -0
  410. package/src/nodes/unbalance/update.js +83 -0
  411. package/src/nodes/vector-distance/index.js +14 -0
  412. package/src/nodes/vector-distance/init.js +54 -0
  413. package/src/nodes/vector-distance/introspect.js +149 -0
  414. package/src/nodes/vector-distance/publish-to.js +35 -0
  415. package/src/nodes/vector-distance/recompute.js +11 -0
  416. package/src/nodes/vector-distance/reset.js +20 -0
  417. package/src/nodes/vector-distance/update.js +139 -0
  418. package/src/nodes/winnow/index.js +12 -0
  419. package/src/nodes/winnow/init.js +82 -0
  420. package/src/nodes/winnow/introspect.js +253 -0
  421. package/src/nodes/winnow/publish-to.js +51 -0
  422. package/src/nodes/winnow/recompute.js +14 -0
  423. package/src/nodes/winnow/reset.js +34 -0
  424. package/src/nodes/winnow/update.js +203 -0
  425. package/src/tools/training/features.js +227 -0
  426. package/src/tools/training/index.js +21 -0
  427. package/src/tools/training/logistic.js +238 -0
  428. package/src/tools/training/metrics.js +126 -0
  429. package/src/tools/training/scale.js +239 -0
  430. package/src/tools/training/validate.js +103 -0
  431. package/src/windowing/count-sliding/create.js +16 -0
  432. package/src/windowing/count-sliding/get-reverse-chronological-index.js +19 -0
  433. package/src/windowing/count-sliding/index.js +6 -0
  434. package/src/windowing/count-sliding/is-not-full.js +5 -0
  435. package/src/windowing/count-sliding/push.js +15 -0
  436. package/src/windowing/count-sliding/reset.js +11 -0
  437. package/src/windowing/count-sliding/size.js +5 -0
  438. package/src/windowing/time-sliding/create.js +25 -0
  439. package/src/windowing/time-sliding/push.js +42 -0
  440. package/CODE_OF_CONDUCT.md +0 -46
  441. package/CONTRIBUTING.md +0 -132
  442. package/docs/.nojekyll +0 -0
  443. package/docs/CONTRIBUTING.md +0 -134
  444. package/docs/README.md +0 -132
  445. package/docs/_coverpage.md +0 -26
  446. package/docs/_media/bg.png +0 -0
  447. package/docs/_media/wink-logo.png +0 -0
  448. package/docs/_navbar.md +0 -14
  449. package/docs/_sidebar.md +0 -2
  450. package/docs/index.html +0 -44
  451. package/eslint.config.js +0 -283
@@ -0,0 +1,546 @@
1
+ // core/source-manager/mqtt/status.js
2
+
3
+ /**
4
+ * @fileoverview Health and metrics reporter for the MQTT source.
5
+ *
6
+ * One small state machine owns everything the source tells the
7
+ * outside world. client.js maps mqtt.js events onto reporter calls
8
+ * 1:1; the reporter decides what is worth emitting and in what shape.
9
+ * Splitting it out keeps the transport wiring and the reporting rules
10
+ * separately testable — the specs drive this module with an injected
11
+ * clock and no MQTT client at all.
12
+ *
13
+ * What it emits, on which channel:
14
+ * - `onStatus` — the ADR-018 core payload `{status, connected,
15
+ * phase}` with `error: {code, message}` on the error path, plus
16
+ * this adapter's additions: `msSinceLastMsg`, and `note` on a
17
+ * forced stop.
18
+ * Emitted on every state TRANSITION (a change of the status /
19
+ * phase / error-code tuple — repeats are suppressed, so a retry
20
+ * storm cannot flood the channel), and once PER RECORD for every
21
+ * decode failure and every transform throw (ADR-018: a skipped
22
+ * record is never silent).
23
+ * - `onMetrics` (optional) — a fresh snapshot of the monotonic
24
+ * counters `{delivered, skipped, decodeErrors, reconnects,
25
+ * dedupHits, dedupMisses, dedupBypassed, dedupCacheSize}`, emitted
26
+ * on every tick() (the client's 1 Hz cadence) and on every health
27
+ * transition. Consumers diff snapshots; nothing is ever reset.
28
+ *
29
+ * Health rules (evaluated on events and on tick):
30
+ * - RED — a subscribe failure (connected but deaf — no retry
31
+ * happens until the next reconnect, so waiting for
32
+ * repetition could wait forever); or disconnected for
33
+ * more than DISCONNECT_RED_MS (strictly greater).
34
+ * - YELLOW — offline / reconnecting (the ADR-018 two-tier rule: the
35
+ * phase says the source is still alive); or a decode-error
36
+ * ratio above 1 % over the last DECODE_RING_SIZE messages
37
+ * (strictly greater); or, when `expectedQuietPeriodMs` is
38
+ * configured, no packet for longer than that.
39
+ * - GREEN — otherwise.
40
+ * Precedence is the order above; the first rule that matches names
41
+ * the code.
42
+ *
43
+ * Hot-path allocation profile (ADR-018 zero-alloc — allocation only where
44
+ * unavoidable, each residual stated):
45
+ * - Counter bumps, ring writes, and the health evaluation are plain
46
+ * integer work on pre-allocated state; no per-message allocation
47
+ * in our code.
48
+ * - Payloads ARE allocated at emission — transitions (rare by
49
+ * construction), per-record decode reports (mandated by ADR-018), and
50
+ * metrics snapshots (1 Hz). Snapshots are fresh objects because a
51
+ * consumer may retain them; mutating a shared one under the
52
+ * caller's feet is a footgun we refuse.
53
+ * - `nowFn()` (Date.now) runs once per received message to feed the
54
+ * quiet-period clock — the same transient boxing the dedup cache
55
+ * already documents.
56
+ *
57
+ * Callers without an `onStatus` handler still see error-path
58
+ * payloads via a classified `console.error` line (the CSV source's
59
+ * fallback pattern); lifecycle payloads stay quiet.
60
+ *
61
+ * ASSUMPTIONS
62
+ * -----------
63
+ * 1. All reporter calls come from one event loop (mqtt.js event
64
+ * handlers plus one interval) — there is no locking.
65
+ * 2. The clock (`nowFn`) moves forward; the time rules use plain
66
+ * subtraction.
67
+ *
68
+ * LIMITATIONS
69
+ * -----------
70
+ * 1. Transition suppression keys on (status, phase, error code) —
71
+ * a repeat of the same tuple with a DIFFERENT message text is
72
+ * not re-emitted. Deliberate: the tuple is what a consumer acts
73
+ * on, and re-emitting per text would let a retry storm flood
74
+ * the channel.
75
+ * 2. Counters are plain JS numbers: exact to 2^53. At one million
76
+ * messages per second that is ~285 years of uptime — not the
77
+ * binding constraint.
78
+ *
79
+ * @see src/core/source-manager/mqtt/client.js - The transport wiring
80
+ * @see ADR-018 - lifecycle phases, status shapes, error codes
81
+ */
82
+
83
+ import {
84
+ DISCONNECT_RED_MS,
85
+ DECODE_RING_SIZE
86
+ } from './constants.js';
87
+
88
+ // ============================================================================
89
+ // VALIDATION HELPERS
90
+ // ============================================================================
91
+
92
+ /**
93
+ * Throws a classified INVALID_CONFIG error (ADR-018 error vocabulary)
94
+ * when `value` is present but not a function.
95
+ *
96
+ * @param {*} value - Candidate value
97
+ * @param {string} name - Field name for the error message
98
+ */
99
+ const assertOptionalFunction = function ( value, name ) {
100
+ if ( value !== undefined && value !== null && typeof value !== 'function' ) {
101
+ const err = new Error( `WinkComposer/mqtt-source: ${name} must be a function` );
102
+ err.code = 'INVALID_CONFIG';
103
+ throw err;
104
+ }
105
+ };
106
+
107
+ // ============================================================================
108
+ // STATUS REPORTER FACTORY
109
+ // ============================================================================
110
+
111
+ /**
112
+ * Create the source's status/metrics reporter.
113
+ *
114
+ * @param {Object} [options={}] - Reporter options
115
+ * @param {function} [options.onStatus] - Structured status callback
116
+ * @param {function} [options.onMetrics] - Counter-snapshot callback
117
+ * @param {number} [options.expectedQuietPeriodMs] - Opt-in quiet rule:
118
+ * yellow when no packet arrives for longer than this
119
+ * @param {function} [options.dedupSizeFn] - Live dedup-cache size read
120
+ * @param {function} [options.nowFn=Date.now] - Clock source. Injection
121
+ * point for deterministic tests; production uses the default
122
+ * @returns {Object} Reporter with lifecycle notes, hot-path counters,
123
+ * tick and snapshot
124
+ */
125
+ const createStatusReporter = function ( options = {} ) {
126
+ if ( typeof options !== 'object' || options === null ) {
127
+ const err = new Error( 'WinkComposer/mqtt-source: status reporter options must be an object' );
128
+ err.code = 'INVALID_CONFIG';
129
+ throw err;
130
+ }
131
+
132
+ const {
133
+ onStatus = null,
134
+ onMetrics = null,
135
+ expectedQuietPeriodMs = null,
136
+ dedupSizeFn = null,
137
+ nowFn = Date.now
138
+ } = options;
139
+
140
+ assertOptionalFunction( onStatus, 'onStatus' );
141
+ assertOptionalFunction( onMetrics, 'onMetrics' );
142
+ assertOptionalFunction( dedupSizeFn, 'dedupSizeFn' );
143
+ if ( typeof nowFn !== 'function' ) {
144
+ const err = new Error( 'WinkComposer/mqtt-source: nowFn must be a function' );
145
+ err.code = 'INVALID_CONFIG';
146
+ throw err;
147
+ }
148
+ if ( expectedQuietPeriodMs !== null &&
149
+ ( typeof expectedQuietPeriodMs !== 'number' ||
150
+ expectedQuietPeriodMs < 1 ||
151
+ !Number.isInteger( expectedQuietPeriodMs ) ) ) {
152
+ const err = new Error( 'WinkComposer/mqtt-source: expectedQuietPeriodMs must be a positive integer' );
153
+ err.code = 'INVALID_CONFIG';
154
+ throw err;
155
+ }
156
+
157
+ // ── Connection & phase state ─────────────────────────────────────
158
+ const startedAt = nowFn();
159
+ let phase = 'starting';
160
+ let connected = false;
161
+ let disconnectedAt = startedAt; // Outage clock; reset on each fresh disconnect
162
+ let lastReceivedAt = startedAt; // Any packet counts — even a duplicate
163
+ let connectCount = 0;
164
+ let subscribeFailed = false;
165
+ let lastSubscribeError = null;
166
+ let lastConnectError = null;
167
+
168
+ // ── Monotonic counters (bumped inline, never reset) ──────────────
169
+ let delivered = 0;
170
+ let skipped = 0;
171
+ let decodeErrors = 0;
172
+ let reconnects = 0;
173
+ let dedupHits = 0;
174
+ let dedupMisses = 0;
175
+ let dedupBypassed = 0;
176
+
177
+ // ── Decode-outcome ring: last DECODE_RING_SIZE results ───────────
178
+ // Pre-allocated once; 1 = decode failure, 0 = success.
179
+ const ring = new Uint8Array( DECODE_RING_SIZE );
180
+ let ringSum = 0;
181
+ let ringIdx = 0;
182
+ let ringCount = 0;
183
+
184
+ // ── Emission de-duplication state ────────────────────────────────
185
+ let lastStatus = null;
186
+ let lastPhase = null;
187
+ let lastCode = null;
188
+
189
+ /**
190
+ * Record one decode outcome in the ring.
191
+ *
192
+ * @param {number} isError - 1 for a decode failure, 0 for success
193
+ */
194
+ const ringPush = function ( isError ) {
195
+ ringSum -= ring[ ringIdx ];
196
+ ring[ ringIdx ] = isError;
197
+ ringSum += isError;
198
+ ringIdx = ( ringIdx + 1 ) % DECODE_RING_SIZE;
199
+ if ( ringCount < DECODE_RING_SIZE ) {
200
+ ringCount += 1;
201
+ }
202
+ };
203
+
204
+ /**
205
+ * Build a fresh counter snapshot. Fresh per call — consumers may
206
+ * retain it.
207
+ *
208
+ * @returns {Object} Monotonic counters + live dedup cache size
209
+ */
210
+ const snapshot = function () {
211
+ return {
212
+ delivered,
213
+ skipped,
214
+ decodeErrors,
215
+ reconnects,
216
+ dedupHits,
217
+ dedupMisses,
218
+ dedupBypassed,
219
+ dedupCacheSize: dedupSizeFn ? dedupSizeFn() : 0
220
+ };
221
+ };
222
+
223
+ /**
224
+ * Emit a metrics snapshot when the caller asked for one.
225
+ */
226
+ const emitMetrics = function () {
227
+ if ( onMetrics ) {
228
+ onMetrics( snapshot() );
229
+ }
230
+ };
231
+
232
+ /**
233
+ * Route a status payload: to the caller's handler when supplied;
234
+ * otherwise error-path payloads go to a classified console.error
235
+ * (never silent — ADR-018's two-party rule) and lifecycle
236
+ * payloads stay quiet.
237
+ *
238
+ * @param {Object} payload - The structured status payload
239
+ */
240
+ const emitStatus = function ( payload ) {
241
+ if ( onStatus ) {
242
+ onStatus( payload );
243
+ } else if ( payload.error ) {
244
+ console.error( `MQTT source error [${payload.error.code}]: ${payload.error.message}` );
245
+ }
246
+ };
247
+
248
+ /**
249
+ * The health rules, in precedence order. Returns the status and
250
+ * leaves the matching code in `healthCode` (closure variable, so
251
+ * the no-emission path allocates nothing).
252
+ *
253
+ * @param {number} now - Current timestamp
254
+ * @returns {string} 'green' | 'yellow' | 'red'
255
+ */
256
+ let healthCode = null;
257
+ const computeHealth = function ( now ) {
258
+ healthCode = null;
259
+ if ( phase === 'stopped' ) {
260
+ return 'green';
261
+ }
262
+ if ( subscribeFailed ) {
263
+ healthCode = 'SUBSCRIBE_FAILED';
264
+ return 'red';
265
+ }
266
+ if ( !connected && ( ( now - disconnectedAt ) > DISCONNECT_RED_MS ) ) {
267
+ healthCode = 'CONNECTION_LOST';
268
+ return 'red';
269
+ }
270
+ if ( phase === 'offline' || phase === 'reconnecting' ) {
271
+ healthCode = lastConnectError === null ? null : 'CONNECT_FAILED';
272
+ return 'yellow';
273
+ }
274
+ if ( lastConnectError !== null ) {
275
+ healthCode = 'CONNECT_FAILED';
276
+ return 'yellow';
277
+ }
278
+ if ( ( ringSum * 100 ) > ringCount ) {
279
+ healthCode = 'DECODE_ERROR';
280
+ return 'yellow';
281
+ }
282
+ if ( expectedQuietPeriodMs !== null &&
283
+ ( ( now - lastReceivedAt ) > expectedQuietPeriodMs ) ) {
284
+ healthCode = 'QUIET_PERIOD_EXCEEDED';
285
+ return 'yellow';
286
+ }
287
+ return 'green';
288
+ };
289
+
290
+ /**
291
+ * Human message for a health code, built only at emission time.
292
+ *
293
+ * @param {string} code - The health code being emitted
294
+ * @param {number} now - Current timestamp
295
+ * @returns {string} Operator-facing message
296
+ */
297
+ const healthMessage = function ( code, now ) {
298
+ if ( code === 'SUBSCRIBE_FAILED' ) {
299
+ return lastSubscribeError;
300
+ }
301
+ if ( code === 'CONNECTION_LOST' ) {
302
+ return `not connected for ${now - disconnectedAt}ms (red threshold ${DISCONNECT_RED_MS}ms)`;
303
+ }
304
+ if ( code === 'CONNECT_FAILED' ) {
305
+ return lastConnectError;
306
+ }
307
+ if ( code === 'DECODE_ERROR' ) {
308
+ return `decode-error ratio above 1% over the last ${ringCount} messages`;
309
+ }
310
+ // QUIET_PERIOD_EXCEEDED — the only code left.
311
+ return `no message received for ${now - lastReceivedAt}ms (expected quiet period ${expectedQuietPeriodMs}ms)`;
312
+ };
313
+
314
+ /**
315
+ * Re-derive health and emit ONE status (plus a metrics snapshot)
316
+ * when the (status, phase, code) tuple changed. The no-change
317
+ * path is allocation-free.
318
+ */
319
+ const evaluate = function () {
320
+ const now = nowFn();
321
+ const status = computeHealth( now );
322
+ if ( status === lastStatus && phase === lastPhase && healthCode === lastCode ) {
323
+ return;
324
+ }
325
+ lastStatus = status;
326
+ lastPhase = phase;
327
+ lastCode = healthCode;
328
+
329
+ const payload = {
330
+ status,
331
+ connected,
332
+ phase,
333
+ msSinceLastMsg: now - lastReceivedAt
334
+ };
335
+ if ( healthCode !== null ) {
336
+ payload.error = { code: healthCode, message: healthMessage( healthCode, now ) };
337
+ }
338
+ emitStatus( payload );
339
+ emitMetrics();
340
+ };
341
+
342
+ return {
343
+ // ── Lifecycle notes (each may emit one transition) ───────────
344
+
345
+ /** Factory finished; the transport is coming up. */
346
+ starting: function () {
347
+ evaluate();
348
+ },
349
+
350
+ /** The transport connected. Counts re-connections; running
351
+ * is declared by subscribed(), not here. */
352
+ connected: function () {
353
+ connected = true;
354
+ connectCount += 1;
355
+ if ( connectCount > 1 ) {
356
+ reconnects += 1;
357
+ }
358
+ lastConnectError = null;
359
+ },
360
+
361
+ /** Subscription acknowledged — the source is producing. */
362
+ subscribed: function () {
363
+ subscribeFailed = false;
364
+ lastSubscribeError = null;
365
+ phase = 'running';
366
+ evaluate();
367
+ },
368
+
369
+ /**
370
+ * Subscription refused. Red immediately: there is no retry
371
+ * between reconnects, so a deaf-but-connected source would
372
+ * stay deaf forever while looking healthy.
373
+ *
374
+ * @param {Error} err - The subscribe error
375
+ */
376
+ subscribeFailed: function ( err ) {
377
+ subscribeFailed = true;
378
+ lastSubscribeError = err.message;
379
+ evaluate();
380
+ },
381
+
382
+ /** The transport dropped. Starts the outage clock; a stale
383
+ * subscribe failure died with the connection. */
384
+ offline: function () {
385
+ if ( connected ) {
386
+ disconnectedAt = nowFn();
387
+ }
388
+ connected = false;
389
+ subscribeFailed = false;
390
+ lastSubscribeError = null;
391
+ phase = 'offline';
392
+ evaluate();
393
+ },
394
+
395
+ /** The library started a reconnect attempt. */
396
+ reconnecting: function () {
397
+ phase = 'reconnecting';
398
+ evaluate();
399
+ },
400
+
401
+ /**
402
+ * A transport-level error. Stored, not emitted directly —
403
+ * evaluate() attaches it as CONNECT_FAILED, so a retry storm
404
+ * of identical failures emits once.
405
+ *
406
+ * @param {Error} err - The transport error
407
+ */
408
+ connectError: function ( err ) {
409
+ lastConnectError = err.message;
410
+ evaluate();
411
+ },
412
+
413
+ /** Clean stop finished. */
414
+ stopped: function () {
415
+ if ( phase === 'stopped' ) {
416
+ return;
417
+ }
418
+ phase = 'stopped';
419
+ connected = false;
420
+ evaluate();
421
+ },
422
+
423
+ /**
424
+ * The stop's time budget ran out and the socket was forced
425
+ * closed. Reported with the sources' shared `note` convention
426
+ * (CSV and testHarness use the same wording).
427
+ *
428
+ * @param {number} timeoutMs - The exceeded budget
429
+ */
430
+ stopForced: function ( timeoutMs ) {
431
+ phase = 'stopped';
432
+ connected = false;
433
+ const now = nowFn();
434
+ lastStatus = 'yellow';
435
+ lastPhase = 'stopped';
436
+ lastCode = null;
437
+ emitStatus( {
438
+ status: 'yellow',
439
+ connected: false,
440
+ phase: 'stopped',
441
+ msSinceLastMsg: now - lastReceivedAt,
442
+ note: `Stop took longer than ${timeoutMs}ms — forced.`
443
+ } );
444
+ emitMetrics();
445
+ },
446
+
447
+ // ── Hot-path counters (integer bumps; no allocation) ─────────
448
+
449
+ /** A duplicate was dropped. Still counts as arrival. */
450
+ dupSkipped: function () {
451
+ dedupHits += 1;
452
+ skipped += 1;
453
+ lastReceivedAt = nowFn();
454
+ evaluate();
455
+ },
456
+
457
+ /** A message arrived with no dedup id — the cache was bypassed. */
458
+ bypassed: function () {
459
+ dedupBypassed += 1;
460
+ },
461
+
462
+ /** A fresh dedup id was accepted into the cache. */
463
+ idAccepted: function () {
464
+ dedupMisses += 1;
465
+ },
466
+
467
+ /** A payload decoded cleanly. Heals a stored transport error —
468
+ * data flowing is proof the transport works. */
469
+ decodeOk: function () {
470
+ lastReceivedAt = nowFn();
471
+ lastConnectError = null;
472
+ ringPush( 0 );
473
+ evaluate();
474
+ },
475
+
476
+ /**
477
+ * A payload could not be decoded. Emits the mandated
478
+ * per-record report (ADR-018: skip, classify, continue — never
479
+ * silent), then re-evaluates the ratio rule.
480
+ *
481
+ * @param {string} detail - Operator-facing description
482
+ */
483
+ decodeFailed: function ( detail ) {
484
+ decodeErrors += 1;
485
+ skipped += 1;
486
+ lastReceivedAt = nowFn();
487
+ ringPush( 1 );
488
+ emitStatus( {
489
+ status: 'yellow',
490
+ connected,
491
+ phase,
492
+ msSinceLastMsg: 0,
493
+ error: { code: 'DECODE_ERROR', message: detail }
494
+ } );
495
+ evaluate();
496
+ },
497
+
498
+ /** The user's transform returned null/undefined — dropped.
499
+ * An intentional drop, so it counts but does not report. */
500
+ transformDropped: function () {
501
+ skipped += 1;
502
+ },
503
+
504
+ /**
505
+ * The user's transform threw. Emits the mandated per-record
506
+ * report (skip, classify, continue — the transform contract,
507
+ * source-transform-uniformity 2026-07-11), classified as
508
+ * CALLBACK_FAILED: user code, never a transport failure. No
509
+ * evaluate() — a transform throw feeds no health rule, so
510
+ * there is no transition to re-derive.
511
+ *
512
+ * @param {string} detail - Operator-facing description
513
+ */
514
+ transformFailed: function ( detail ) {
515
+ skipped += 1;
516
+ emitStatus( {
517
+ status: 'yellow',
518
+ connected,
519
+ phase,
520
+ msSinceLastMsg: 0,
521
+ error: { code: 'CALLBACK_FAILED', message: detail }
522
+ } );
523
+ },
524
+
525
+ /** A message reached the pipeline's onMessage. */
526
+ delivered: function () {
527
+ delivered += 1;
528
+ },
529
+
530
+ // ── Cadence & introspection ──────────────────────────────────
531
+
532
+ /** One heartbeat: re-check the time rules, emit metrics. */
533
+ tick: function () {
534
+ evaluate();
535
+ emitMetrics();
536
+ },
537
+
538
+ snapshot
539
+ };
540
+ };
541
+
542
+ // ============================================================================
543
+ // EXPORTS
544
+ // ============================================================================
545
+
546
+ export { createStatusReporter };