@hotmeshio/long-tail 0.27.0 → 0.28.1

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 (386) hide show
  1. package/README.md +9 -2
  2. package/build/api/files.d.ts +5 -0
  3. package/build/api/files.js +19 -2
  4. package/build/api/knowledge.d.ts +3 -1
  5. package/build/api/settings.d.ts +4 -1
  6. package/build/api/settings.js +7 -1
  7. package/build/index.js +9 -4
  8. package/build/lib/db/config-lock.d.ts +10 -0
  9. package/build/lib/db/config-lock.js +28 -0
  10. package/build/lib/db/schemas/041_machine_lines.sql +26 -0
  11. package/build/lib/db/schemas/042_knowledge_edition_labels.sql +9 -0
  12. package/build/lib/events/browser-view.js +8 -2
  13. package/build/lib/events/publish.d.ts +33 -0
  14. package/build/lib/events/publish.js +52 -1
  15. package/build/lib/machines/line.d.ts +7 -1
  16. package/build/lib/machines/line.js +25 -2
  17. package/build/lib/machines/protocol/codec.d.ts +14 -0
  18. package/build/lib/machines/protocol/codec.js +10 -1
  19. package/build/lib/machines/protocol/constants.d.ts +1 -1
  20. package/build/lib/machines/protocol/constants.js +1 -1
  21. package/build/lib/machines/protocol/contract.d.ts +2 -0
  22. package/build/lib/machines/protocol/contract.js +13 -8
  23. package/build/lib/machines/site/mosquitto-conf.d.ts +9 -4
  24. package/build/lib/machines/site/mosquitto-conf.js +32 -22
  25. package/build/lib/machines/site/site-conf.d.ts +13 -3
  26. package/build/lib/machines/site/site-conf.js +31 -8
  27. package/build/lib/machines/transport/callback-feed.d.ts +1 -0
  28. package/build/lib/machines/transport/callback-feed.js +4 -1
  29. package/build/lib/machines/transport/memory.d.ts +2 -0
  30. package/build/lib/machines/transport/memory.js +6 -0
  31. package/build/lib/machines/transport/mqtt-bus.d.ts +2 -0
  32. package/build/lib/machines/transport/mqtt-bus.js +4 -0
  33. package/build/lib/machines/transport/nats-bus.d.ts +3 -0
  34. package/build/lib/machines/transport/nats-bus.js +3 -0
  35. package/build/lib/machines/transport/nats-feed.d.ts +1 -0
  36. package/build/lib/machines/transport/nats-feed.js +4 -1
  37. package/build/lib/machines/transport/pubsub-feed.d.ts +1 -0
  38. package/build/lib/machines/transport/pubsub-feed.js +4 -1
  39. package/build/lib/machines/transport/stream-bus.d.ts +7 -1
  40. package/build/lib/machines/transport/stream-bus.js +17 -5
  41. package/build/lib/machines/transport/streams.d.ts +10 -2
  42. package/build/lib/machines/transport/streams.js +32 -5
  43. package/build/lib/messaging/memory.js +3 -0
  44. package/build/lib/messaging/nats/streams.d.ts +3 -0
  45. package/build/lib/messaging/nats/streams.js +16 -0
  46. package/build/lib/storage/local.d.ts +2 -0
  47. package/build/lib/storage/local.js +3 -0
  48. package/build/lib/storage/s3.d.ts +3 -2
  49. package/build/lib/storage/s3.js +6 -1
  50. package/build/lib/storage/types.d.ts +7 -1
  51. package/build/modules/machines.d.ts +8 -0
  52. package/build/modules/machines.js +16 -0
  53. package/build/routes/file-browser.js +3 -1
  54. package/build/routes/index.js +2 -0
  55. package/build/routes/knowledge.js +6 -5
  56. package/build/routes/machines.d.ts +2 -0
  57. package/build/routes/machines.js +253 -0
  58. package/build/services/knowledge/index.d.ts +4 -2
  59. package/build/services/knowledge/index.js +8 -2
  60. package/build/services/knowledge/lookup-cache.d.ts +8 -0
  61. package/build/services/knowledge/lookup-cache.js +30 -3
  62. package/build/services/knowledge/lookup-refs.js +1 -1
  63. package/build/services/knowledge/sql.d.ts +4 -2
  64. package/build/services/knowledge/sql.js +8 -3
  65. package/build/services/machines/commands.d.ts +5 -5
  66. package/build/services/machines/commands.js +3 -2
  67. package/build/services/machines/config.d.ts +4 -1
  68. package/build/services/machines/config.js +2 -1
  69. package/build/services/machines/contracts.d.ts +28 -0
  70. package/build/services/machines/contracts.js +15 -1
  71. package/build/services/machines/forms.d.ts +26 -1
  72. package/build/services/machines/forms.js +17 -1
  73. package/build/services/machines/gateway/actions.d.ts +14 -1
  74. package/build/services/machines/gateway/admission.d.ts +30 -4
  75. package/build/services/machines/gateway/admission.js +76 -14
  76. package/build/services/machines/gateway/api-escalations.js +4 -0
  77. package/build/services/machines/gateway/dispatcher.d.ts +12 -4
  78. package/build/services/machines/gateway/dispatcher.js +53 -17
  79. package/build/services/machines/gateway/executor.d.ts +13 -2
  80. package/build/services/machines/gateway/executor.js +84 -29
  81. package/build/services/machines/gateway/gateway.d.ts +21 -1
  82. package/build/services/machines/gateway/gateway.js +65 -25
  83. package/build/services/machines/gateway/index.d.ts +3 -0
  84. package/build/services/machines/gateway/index.js +3 -0
  85. package/build/services/machines/gateway/inspect.d.ts +17 -0
  86. package/build/services/machines/gateway/inspect.js +68 -0
  87. package/build/services/machines/gateway/log.d.ts +10 -0
  88. package/build/services/machines/gateway/log.js +20 -0
  89. package/build/services/machines/gateway/lt-http.js +11 -2
  90. package/build/services/machines/gateway/pacing.js +2 -1
  91. package/build/services/machines/gateway/plan-escalation.js +8 -3
  92. package/build/services/machines/gateway/plan-machine.js +41 -16
  93. package/build/services/machines/gateway/plan-shared.d.ts +5 -3
  94. package/build/services/machines/gateway/plan-shared.js +6 -4
  95. package/build/services/machines/gateway/project.d.ts +9 -0
  96. package/build/services/machines/gateway/project.js +55 -0
  97. package/build/services/machines/gateway/receipts.d.ts +3 -0
  98. package/build/services/machines/gateway/receipts.js +3 -1
  99. package/build/services/machines/gateway/reconcile.js +3 -3
  100. package/build/services/machines/gateway/registration.js +1 -1
  101. package/build/services/machines/gateway/reply-waiter.d.ts +8 -3
  102. package/build/services/machines/gateway/reply-waiter.js +37 -16
  103. package/build/services/machines/gateway/state.d.ts +53 -4
  104. package/build/services/machines/gateway/state.js +128 -13
  105. package/build/services/machines/gateway/wire-window.d.ts +21 -0
  106. package/build/services/machines/gateway/wire-window.js +61 -0
  107. package/build/services/machines/gateway/wire.d.ts +58 -0
  108. package/build/services/machines/gateway/wire.js +172 -0
  109. package/build/services/machines/index.d.ts +3 -0
  110. package/build/services/machines/index.js +2 -0
  111. package/build/services/machines/line-index.d.ts +16 -0
  112. package/build/services/machines/line-index.js +39 -0
  113. package/build/services/machines/registry-sql.d.ts +31 -0
  114. package/build/services/machines/registry-sql.js +110 -0
  115. package/build/services/machines/registry-sync.d.ts +31 -0
  116. package/build/services/machines/registry-sync.js +81 -0
  117. package/build/services/machines/registry.d.ts +77 -0
  118. package/build/services/machines/registry.js +158 -0
  119. package/build/services/machines/roles.d.ts +5 -4
  120. package/build/services/machines/roles.js +13 -4
  121. package/build/services/machines/rows.js +10 -1
  122. package/build/services/machines/select.d.ts +3 -2
  123. package/build/services/machines/select.js +4 -3
  124. package/build/services/machines/surface.d.ts +54 -5
  125. package/build/services/machines/surface.js +113 -17
  126. package/build/start/apply-report.d.ts +1 -1
  127. package/build/start/apply-report.js +3 -25
  128. package/build/start/index.js +16 -4
  129. package/build/system/activities/knowledge.d.ts +4 -0
  130. package/build/system/activities/knowledge.js +15 -0
  131. package/build/system/activities/sql.d.ts +2 -0
  132. package/build/system/activities/sql.js +6 -1
  133. package/build/system/index.js +13 -0
  134. package/build/system/mcp-servers/admin/schemas.d.ts +22 -22
  135. package/build/system/mcp-servers/human-queue-schemas.d.ts +7 -7
  136. package/build/system/mcp-servers/knowledge.js +4 -2
  137. package/build/system/mcp-servers/machines-schemas.d.ts +35 -0
  138. package/build/system/mcp-servers/machines-schemas.js +35 -0
  139. package/build/system/mcp-servers/machines.d.ts +20 -0
  140. package/build/system/mcp-servers/machines.js +154 -0
  141. package/build/system/seed/tool-manifests-machines.d.ts +2 -0
  142. package/build/system/seed/tool-manifests-machines.js +88 -0
  143. package/build/tsconfig.tsbuildinfo +1 -1
  144. package/build/types/escalation.d.ts +9 -6
  145. package/build/types/escalation.js +8 -2
  146. package/build/types/events.d.ts +6 -1
  147. package/build/types/index.d.ts +1 -0
  148. package/build/types/machines-view.d.ts +175 -0
  149. package/build/types/machines-view.js +7 -0
  150. package/build/types/machines.d.ts +5 -0
  151. package/build/types/messaging.d.ts +2 -0
  152. package/build/types/startup.d.ts +2 -0
  153. package/dashboard/dist/assets/{AdminDashboard-DlItqxFS.js → AdminDashboard-Bm7VG65H.js} +2 -2
  154. package/dashboard/dist/assets/{AdminDashboard-DlItqxFS.js.map → AdminDashboard-Bm7VG65H.js.map} +1 -1
  155. package/dashboard/dist/assets/{AgentConfigPage-BUmT4a7n.js → AgentConfigPage-aJF7Sdbc.js} +6 -6
  156. package/dashboard/dist/assets/{AgentConfigPage-BUmT4a7n.js.map → AgentConfigPage-aJF7Sdbc.js.map} +1 -1
  157. package/dashboard/dist/assets/{AgentDetailPage-Cww8rlBi.js → AgentDetailPage-GwwghhxT.js} +3 -3
  158. package/dashboard/dist/assets/{AgentDetailPage-Cww8rlBi.js.map → AgentDetailPage-GwwghhxT.js.map} +1 -1
  159. package/dashboard/dist/assets/AgentsPage-DVvCzZyg.js +2 -0
  160. package/dashboard/dist/assets/{AgentsPage-C9UuUKhr.js.map → AgentsPage-DVvCzZyg.js.map} +1 -1
  161. package/dashboard/dist/assets/AvailableEscalationsPage-Cxmcji_K.js +2 -0
  162. package/dashboard/dist/assets/{AvailableEscalationsPage-DrvfXz70.js.map → AvailableEscalationsPage-Cxmcji_K.js.map} +1 -1
  163. package/dashboard/dist/assets/{BotPicker-CDq9XRMp.js → BotPicker-CofRxk4L.js} +2 -2
  164. package/dashboard/dist/assets/{BotPicker-CDq9XRMp.js.map → BotPicker-CofRxk4L.js.map} +1 -1
  165. package/dashboard/dist/assets/{BulkAssignModal-jsvqtrEA.js → BulkAssignModal-BARWuz4L.js} +2 -2
  166. package/dashboard/dist/assets/{BulkAssignModal-jsvqtrEA.js.map → BulkAssignModal-BARWuz4L.js.map} +1 -1
  167. package/dashboard/dist/assets/{CapabilitiesPage-CSCrI6B1.js → CapabilitiesPage-BJAWVNEx.js} +2 -2
  168. package/dashboard/dist/assets/{CapabilitiesPage-CSCrI6B1.js.map → CapabilitiesPage-BJAWVNEx.js.map} +1 -1
  169. package/dashboard/dist/assets/{CollapsibleSection-uD9mn-CQ.js → CollapsibleSection-CtgoaG75.js} +2 -2
  170. package/dashboard/dist/assets/{CollapsibleSection-uD9mn-CQ.js.map → CollapsibleSection-CtgoaG75.js.map} +1 -1
  171. package/dashboard/dist/assets/{ConfirmDeleteModal-BRmx2hA4.js → ConfirmDeleteModal-BYXXBWs6.js} +2 -2
  172. package/dashboard/dist/assets/{ConfirmDeleteModal-BRmx2hA4.js.map → ConfirmDeleteModal-BYXXBWs6.js.map} +1 -1
  173. package/dashboard/dist/assets/{ConnectedAppsPage-DlMzE5a-.js → ConnectedAppsPage-D0JkzWa7.js} +2 -2
  174. package/dashboard/dist/assets/{ConnectedAppsPage-DlMzE5a-.js.map → ConnectedAppsPage-D0JkzWa7.js.map} +1 -1
  175. package/dashboard/dist/assets/{CountdownTimer-sRuM4eM5.js → CountdownTimer-BcFxdZYN.js} +2 -2
  176. package/dashboard/dist/assets/{CountdownTimer-sRuM4eM5.js.map → CountdownTimer-BcFxdZYN.js.map} +1 -1
  177. package/dashboard/dist/assets/{CredentialsPage-bLYydwqa.js → CredentialsPage-DPSY20rV.js} +2 -2
  178. package/dashboard/dist/assets/{CredentialsPage-bLYydwqa.js.map → CredentialsPage-DPSY20rV.js.map} +1 -1
  179. package/dashboard/dist/assets/{CronLabel-D_ATDL2N.js → CronLabel-Dg_Oew_Y.js} +2 -2
  180. package/dashboard/dist/assets/{CronLabel-D_ATDL2N.js.map → CronLabel-Dg_Oew_Y.js.map} +1 -1
  181. package/dashboard/dist/assets/{DropZone-u1JGfR9G.js → DropZone-Cl_Flb16.js} +2 -2
  182. package/dashboard/dist/assets/{DropZone-u1JGfR9G.js.map → DropZone-Cl_Flb16.js.map} +1 -1
  183. package/dashboard/dist/assets/{ElapsedCell-groSMfqB.js → ElapsedCell-Dm3G5fK2.js} +2 -2
  184. package/dashboard/dist/assets/{ElapsedCell-groSMfqB.js.map → ElapsedCell-Dm3G5fK2.js.map} +1 -1
  185. package/dashboard/dist/assets/{EntityTimelinePanel-D-F3_A9m.js → EntityTimelinePanel-CrEEP04e.js} +2 -2
  186. package/dashboard/dist/assets/{EntityTimelinePanel-D-F3_A9m.js.map → EntityTimelinePanel-CrEEP04e.js.map} +1 -1
  187. package/dashboard/dist/assets/{EscalationListSchemaPage-Bad6Jrmr.js → EscalationListSchemaPage-wOInXcjs.js} +3 -3
  188. package/dashboard/dist/assets/{EscalationListSchemaPage-Bad6Jrmr.js.map → EscalationListSchemaPage-wOInXcjs.js.map} +1 -1
  189. package/dashboard/dist/assets/{EscalationSchemaPage-BKi_npI9.js → EscalationSchemaPage-CrIj_g9d.js} +3 -3
  190. package/dashboard/dist/assets/{EscalationSchemaPage-BKi_npI9.js.map → EscalationSchemaPage-CrIj_g9d.js.map} +1 -1
  191. package/dashboard/dist/assets/{EscalationTimeline-DQUUm3b1.js → EscalationTimeline-CeAsl5IT.js} +2 -2
  192. package/dashboard/dist/assets/{EscalationTimeline-DQUUm3b1.js.map → EscalationTimeline-CeAsl5IT.js.map} +1 -1
  193. package/dashboard/dist/assets/{EscalationTitleSelect-3mhXh77a.js → EscalationTitleSelect-D8qpCYiy.js} +2 -2
  194. package/dashboard/dist/assets/{EscalationTitleSelect-3mhXh77a.js.map → EscalationTitleSelect-D8qpCYiy.js.map} +1 -1
  195. package/dashboard/dist/assets/{EscalationsOverview-Qaov-uML.js → EscalationsOverview-CvaqVPsr.js} +2 -2
  196. package/dashboard/dist/assets/{EscalationsOverview-Qaov-uML.js.map → EscalationsOverview-CvaqVPsr.js.map} +1 -1
  197. package/dashboard/dist/assets/{EventTable-V4RF_n58.js → EventTable-Be0JuEl6.js} +2 -2
  198. package/dashboard/dist/assets/{EventTable-V4RF_n58.js.map → EventTable-Be0JuEl6.js.map} +1 -1
  199. package/dashboard/dist/assets/FilterBar-BVyAjIgv.js +2 -0
  200. package/dashboard/dist/assets/FilterBar-BVyAjIgv.js.map +1 -0
  201. package/dashboard/dist/assets/{GraphInvokePage-Djslhf96.js → GraphInvokePage-D_QigJeD.js} +2 -2
  202. package/dashboard/dist/assets/{GraphInvokePage-Djslhf96.js.map → GraphInvokePage-D_QigJeD.js.map} +1 -1
  203. package/dashboard/dist/assets/{HomePage-EjcNxlKx.js → HomePage-C1haeuNy.js} +2 -2
  204. package/dashboard/dist/assets/{HomePage-EjcNxlKx.js.map → HomePage-C1haeuNy.js.map} +1 -1
  205. package/dashboard/dist/assets/LinePage-CWD5pv1g.js +5 -0
  206. package/dashboard/dist/assets/LinePage-CWD5pv1g.js.map +1 -0
  207. package/dashboard/dist/assets/LinesPage-NqChsKcO.js +3 -0
  208. package/dashboard/dist/assets/LinesPage-NqChsKcO.js.map +1 -0
  209. package/dashboard/dist/assets/{ListToolbar-RrEAePOX.js → ListToolbar-BZCNrTh6.js} +2 -2
  210. package/dashboard/dist/assets/{ListToolbar-RrEAePOX.js.map → ListToolbar-BZCNrTh6.js.map} +1 -1
  211. package/dashboard/dist/assets/{McpOverview-8gqiL3Vj.js → McpOverview-yJTYnh1z.js} +2 -2
  212. package/dashboard/dist/assets/{McpOverview-8gqiL3Vj.js.map → McpOverview-yJTYnh1z.js.map} +1 -1
  213. package/dashboard/dist/assets/{McpQueryDetailPage-COyn6Ak5.js → McpQueryDetailPage-ChuLpJ4u.js} +3 -3
  214. package/dashboard/dist/assets/{McpQueryDetailPage-COyn6Ak5.js.map → McpQueryDetailPage-ChuLpJ4u.js.map} +1 -1
  215. package/dashboard/dist/assets/{McpQueryPage-Dj6Qj72w.js → McpQueryPage-9S-v9soF.js} +2 -2
  216. package/dashboard/dist/assets/{McpQueryPage-Dj6Qj72w.js.map → McpQueryPage-9S-v9soF.js.map} +1 -1
  217. package/dashboard/dist/assets/McpRunDetailPage-BhrcfxD5.js +2 -0
  218. package/dashboard/dist/assets/{McpRunDetailPage-80fQn9iX.js.map → McpRunDetailPage-BhrcfxD5.js.map} +1 -1
  219. package/dashboard/dist/assets/{McpRunsPage-DaSsI-EM.js → McpRunsPage-C5_i8_Zx.js} +2 -2
  220. package/dashboard/dist/assets/{McpRunsPage-DaSsI-EM.js.map → McpRunsPage-C5_i8_Zx.js.map} +1 -1
  221. package/dashboard/dist/assets/{NamespacePill-C5epAgq6.js → NamespacePill-OTZD2hzt.js} +2 -2
  222. package/dashboard/dist/assets/{NamespacePill-C5epAgq6.js.map → NamespacePill-OTZD2hzt.js.map} +1 -1
  223. package/dashboard/dist/assets/{OperationsPage-BSu1o2gB.js → OperationsPage-C03CeU5P.js} +2 -2
  224. package/dashboard/dist/assets/{OperationsPage-BSu1o2gB.js.map → OperationsPage-C03CeU5P.js.map} +1 -1
  225. package/dashboard/dist/assets/{OperatorDashboard-HFI4WGIV.js → OperatorDashboard-BzC134Jt.js} +2 -2
  226. package/dashboard/dist/assets/{OperatorDashboard-HFI4WGIV.js.map → OperatorDashboard-BzC134Jt.js.map} +1 -1
  227. package/dashboard/dist/assets/{PageHeader-BSWIspNB.js → PageHeader-Hx8APPwE.js} +2 -2
  228. package/dashboard/dist/assets/{PageHeader-BSWIspNB.js.map → PageHeader-Hx8APPwE.js.map} +1 -1
  229. package/dashboard/dist/assets/{PageHeaderWithStats-DDjopQ-k.js → PageHeaderWithStats-BEQYYBJv.js} +2 -2
  230. package/dashboard/dist/assets/{PageHeaderWithStats-DDjopQ-k.js.map → PageHeaderWithStats-BEQYYBJv.js.map} +1 -1
  231. package/dashboard/dist/assets/{PersonaDetailPage-D0qlBuGi.js → PersonaDetailPage-D0mY9L6j.js} +2 -2
  232. package/dashboard/dist/assets/{PersonaDetailPage-D0qlBuGi.js.map → PersonaDetailPage-D0mY9L6j.js.map} +1 -1
  233. package/dashboard/dist/assets/{PersonasPage-BJClu-5H.js → PersonasPage-FFlm5Eug.js} +2 -2
  234. package/dashboard/dist/assets/{PersonasPage-BJClu-5H.js.map → PersonasPage-FFlm5Eug.js.map} +1 -1
  235. package/dashboard/dist/assets/{PortalPage-CucNZn_w.js → PortalPage-Cpc25uO-.js} +2 -2
  236. package/dashboard/dist/assets/{PortalPage-CucNZn_w.js.map → PortalPage-Cpc25uO-.js.map} +1 -1
  237. package/dashboard/dist/assets/{ProcessDetailPage-B9wMw0RV.js → ProcessDetailPage-Di2J7KwY.js} +2 -2
  238. package/dashboard/dist/assets/{ProcessDetailPage-B9wMw0RV.js.map → ProcessDetailPage-Di2J7KwY.js.map} +1 -1
  239. package/dashboard/dist/assets/{ProcessesListPage-B6k4RpmR.js → ProcessesListPage-BEXwU3Z0.js} +2 -2
  240. package/dashboard/dist/assets/{ProcessesListPage-B6k4RpmR.js.map → ProcessesListPage-BEXwU3Z0.js.map} +1 -1
  241. package/dashboard/dist/assets/{ResolverForm-ZVufJ4G-.js → ResolverForm-2ks-3jXC.js} +6 -6
  242. package/dashboard/dist/assets/{ResolverForm-ZVufJ4G-.js.map → ResolverForm-2ks-3jXC.js.map} +1 -1
  243. package/dashboard/dist/assets/{RoleDetailPage-CTbq0mz4.js → RoleDetailPage-DqoOk0xc.js} +3 -3
  244. package/dashboard/dist/assets/{RoleDetailPage-CTbq0mz4.js.map → RoleDetailPage-DqoOk0xc.js.map} +1 -1
  245. package/dashboard/dist/assets/{RolePill-CoETDpjF.js → RolePill-R5_ctGFy.js} +2 -2
  246. package/dashboard/dist/assets/{RolePill-CoETDpjF.js.map → RolePill-R5_ctGFy.js.map} +1 -1
  247. package/dashboard/dist/assets/{RolesPage-DBKxeam4.js → RolesPage-CPmo8Srr.js} +2 -2
  248. package/dashboard/dist/assets/{RolesPage-DBKxeam4.js.map → RolesPage-CPmo8Srr.js.map} +1 -1
  249. package/dashboard/dist/assets/{RosterWidget-BkLAwEYB.js → RosterWidget-D2yovCKw.js} +2 -2
  250. package/dashboard/dist/assets/{RosterWidget-BkLAwEYB.js.map → RosterWidget-D2yovCKw.js.map} +1 -1
  251. package/dashboard/dist/assets/{RunAsSelector-D2DC4iDf.js → RunAsSelector-CMv96On6.js} +2 -2
  252. package/dashboard/dist/assets/{RunAsSelector-D2DC4iDf.js.map → RunAsSelector-CMv96On6.js.map} +1 -1
  253. package/dashboard/dist/assets/{ScanCodesPage-BlKleaT0.js → ScanCodesPage-CA6cTT-M.js} +2 -2
  254. package/dashboard/dist/assets/{ScanCodesPage-BlKleaT0.js.map → ScanCodesPage-CA6cTT-M.js.map} +1 -1
  255. package/dashboard/dist/assets/{ScanSchemeDetailPage-CTW6gofi.js → ScanSchemeDetailPage-DnctN5-H.js} +2 -2
  256. package/dashboard/dist/assets/{ScanSchemeDetailPage-CTW6gofi.js.map → ScanSchemeDetailPage-DnctN5-H.js.map} +1 -1
  257. package/dashboard/dist/assets/{ScanStationPage-DPRjSPyT.js → ScanStationPage-Bk8RwkKL.js} +2 -2
  258. package/dashboard/dist/assets/{ScanStationPage-DPRjSPyT.js.map → ScanStationPage-Bk8RwkKL.js.map} +1 -1
  259. package/dashboard/dist/assets/{ScopeBadge-CGsTtqFr.js → ScopeBadge-BXZ084nD.js} +2 -2
  260. package/dashboard/dist/assets/{ScopeBadge-CGsTtqFr.js.map → ScopeBadge-BXZ084nD.js.map} +1 -1
  261. package/dashboard/dist/assets/{StickyPagination-CkmX3-Gu.js → StickyPagination-B0FSuUgs.js} +2 -2
  262. package/dashboard/dist/assets/{StickyPagination-CkmX3-Gu.js.map → StickyPagination-B0FSuUgs.js.map} +1 -1
  263. package/dashboard/dist/assets/{StreamMessageDetail-Xm_n510s.js → StreamMessageDetail-BwZEvMpP.js} +2 -2
  264. package/dashboard/dist/assets/{StreamMessageDetail-Xm_n510s.js.map → StreamMessageDetail-BwZEvMpP.js.map} +1 -1
  265. package/dashboard/dist/assets/{SwimlaneTimeline-De48aJan.js → SwimlaneTimeline-BDVQEaMw.js} +2 -2
  266. package/dashboard/dist/assets/{SwimlaneTimeline-De48aJan.js.map → SwimlaneTimeline-BDVQEaMw.js.map} +1 -1
  267. package/dashboard/dist/assets/{TagInput-S7UuVT_M.js → TagInput-DjPUDp2M.js} +2 -2
  268. package/dashboard/dist/assets/{TagInput-S7UuVT_M.js.map → TagInput-DjPUDp2M.js.map} +1 -1
  269. package/dashboard/dist/assets/{TaskDetailPage-HycMx3O9.js → TaskDetailPage-BOlv3iGB.js} +2 -2
  270. package/dashboard/dist/assets/{TaskDetailPage-HycMx3O9.js.map → TaskDetailPage-BOlv3iGB.js.map} +1 -1
  271. package/dashboard/dist/assets/{TaskQueuePill-BlHNpYiZ.js → TaskQueuePill-D-4Ynf3K.js} +2 -2
  272. package/dashboard/dist/assets/{TaskQueuePill-BlHNpYiZ.js.map → TaskQueuePill-D-4Ynf3K.js.map} +1 -1
  273. package/dashboard/dist/assets/{TasksListPage-OIcFFp1C.js → TasksListPage-e2teY0bD.js} +2 -2
  274. package/dashboard/dist/assets/{TasksListPage-OIcFFp1C.js.map → TasksListPage-e2teY0bD.js.map} +1 -1
  275. package/dashboard/dist/assets/{TimeAgo-OTC_55oZ.js → TimeAgo-Cvkpf_b3.js} +2 -2
  276. package/dashboard/dist/assets/{TimeAgo-OTC_55oZ.js.map → TimeAgo-Cvkpf_b3.js.map} +1 -1
  277. package/dashboard/dist/assets/{TimestampCell-CAwIj_AI.js → TimestampCell-B3btAV0N.js} +2 -2
  278. package/dashboard/dist/assets/{TimestampCell-CAwIj_AI.js.map → TimestampCell-B3btAV0N.js.map} +1 -1
  279. package/dashboard/dist/assets/{ToolPill-D40uLM_v.js → ToolPill-GsKQ80CO.js} +2 -2
  280. package/dashboard/dist/assets/{ToolPill-D40uLM_v.js.map → ToolPill-GsKQ80CO.js.map} +1 -1
  281. package/dashboard/dist/assets/{ToolTestPanel-8Vf6pfw4.js → ToolTestPanel-B03nebR7.js} +2 -2
  282. package/dashboard/dist/assets/{ToolTestPanel-8Vf6pfw4.js.map → ToolTestPanel-B03nebR7.js.map} +1 -1
  283. package/dashboard/dist/assets/{TopicDetailPage-iZW8rK9L.js → TopicDetailPage-DBRWne3O.js} +3 -3
  284. package/dashboard/dist/assets/{TopicDetailPage-iZW8rK9L.js.map → TopicDetailPage-DBRWne3O.js.map} +1 -1
  285. package/dashboard/dist/assets/{TopicsPage-CqiEcj-W.js → TopicsPage-CVe2M9HD.js} +2 -2
  286. package/dashboard/dist/assets/{TopicsPage-CqiEcj-W.js.map → TopicsPage-CVe2M9HD.js.map} +1 -1
  287. package/dashboard/dist/assets/{UserName-DQFjuoib.js → UserName-B_q_qp6V.js} +2 -2
  288. package/dashboard/dist/assets/{UserName-DQFjuoib.js.map → UserName-B_q_qp6V.js.map} +1 -1
  289. package/dashboard/dist/assets/WorkflowExecutionPage-Bh_dr9tM.js +2 -0
  290. package/dashboard/dist/assets/{WorkflowExecutionPage-Di08wCbs.js.map → WorkflowExecutionPage-Bh_dr9tM.js.map} +1 -1
  291. package/dashboard/dist/assets/{WorkflowPill-DJLcK_CM.js → WorkflowPill-D7244fKp.js} +2 -2
  292. package/dashboard/dist/assets/{WorkflowPill-DJLcK_CM.js.map → WorkflowPill-D7244fKp.js.map} +1 -1
  293. package/dashboard/dist/assets/{WorkflowsDashboard-Bb8HlrTJ.js → WorkflowsDashboard-DA6Mj7qH.js} +2 -2
  294. package/dashboard/dist/assets/{WorkflowsDashboard-Bb8HlrTJ.js.map → WorkflowsDashboard-DA6Mj7qH.js.map} +1 -1
  295. package/dashboard/dist/assets/{WorkflowsOverview-Dta1kurk.js → WorkflowsOverview-B1kBLj-a.js} +2 -2
  296. package/dashboard/dist/assets/{WorkflowsOverview-Dta1kurk.js.map → WorkflowsOverview-B1kBLj-a.js.map} +1 -1
  297. package/dashboard/dist/assets/{YamlWorkflowDetailPage-CfAajg68.js → YamlWorkflowDetailPage-6GJM8QIX.js} +8 -8
  298. package/dashboard/dist/assets/{YamlWorkflowDetailPage-CfAajg68.js.map → YamlWorkflowDetailPage-6GJM8QIX.js.map} +1 -1
  299. package/dashboard/dist/assets/{YamlWorkflowsPage-CQFUzT4t.js → YamlWorkflowsPage-Bu811C7f.js} +2 -2
  300. package/dashboard/dist/assets/{YamlWorkflowsPage-CQFUzT4t.js.map → YamlWorkflowsPage-Bu811C7f.js.map} +1 -1
  301. package/dashboard/dist/assets/{agents-PuftRTB2.js → agents-MPk_GnYz.js} +2 -2
  302. package/dashboard/dist/assets/{agents-PuftRTB2.js.map → agents-MPk_GnYz.js.map} +1 -1
  303. package/dashboard/dist/assets/{bots-BmEiPAWY.js → bots-2RihE2Tc.js} +2 -2
  304. package/dashboard/dist/assets/{bots-BmEiPAWY.js.map → bots-2RihE2Tc.js.map} +1 -1
  305. package/dashboard/dist/assets/{capabilities-CRii4Soa.js → capabilities-CC4iwJ1P.js} +2 -2
  306. package/dashboard/dist/assets/{capabilities-CRii4Soa.js.map → capabilities-CC4iwJ1P.js.map} +1 -1
  307. package/dashboard/dist/assets/{controlplane-DO_czaYo.js → controlplane-Bmkic8BH.js} +2 -2
  308. package/dashboard/dist/assets/{controlplane-DO_czaYo.js.map → controlplane-Bmkic8BH.js.map} +1 -1
  309. package/dashboard/dist/assets/{escalation-columns-CzCquCPh.js → escalation-columns-B0lhUUrQ.js} +2 -2
  310. package/dashboard/dist/assets/{escalation-columns-CzCquCPh.js.map → escalation-columns-B0lhUUrQ.js.map} +1 -1
  311. package/dashboard/dist/assets/{escalation-items-Cy0ZgJqV.js → escalation-items-7WFpPo46.js} +2 -2
  312. package/dashboard/dist/assets/{escalation-items-Cy0ZgJqV.js.map → escalation-items-7WFpPo46.js.map} +1 -1
  313. package/dashboard/dist/assets/index-B9uv-euR.css +1 -0
  314. package/dashboard/dist/assets/{index-DhSCCpqM.js → index-B_XV1rwu.js} +2 -2
  315. package/dashboard/dist/assets/{index-DhSCCpqM.js.map → index-B_XV1rwu.js.map} +1 -1
  316. package/dashboard/dist/assets/{index-Cx8xb-oU.js → index-C2CLfGwL.js} +2 -2
  317. package/dashboard/dist/assets/{index-Cx8xb-oU.js.map → index-C2CLfGwL.js.map} +1 -1
  318. package/dashboard/dist/assets/{index-Cga5IW3D.js → index-C3AeFihz.js} +2 -2
  319. package/dashboard/dist/assets/{index-Cga5IW3D.js.map → index-C3AeFihz.js.map} +1 -1
  320. package/dashboard/dist/assets/{index-DiaRlFYI.js → index-CBri8fYM.js} +2 -2
  321. package/dashboard/dist/assets/{index-DiaRlFYI.js.map → index-CBri8fYM.js.map} +1 -1
  322. package/dashboard/dist/assets/{index-CL_1VpGb.js → index-CRWsB7ZC.js} +2 -2
  323. package/dashboard/dist/assets/{index-CL_1VpGb.js.map → index-CRWsB7ZC.js.map} +1 -1
  324. package/dashboard/dist/assets/{index-XntldBd0.js → index-CTOv3G7a.js} +2 -2
  325. package/dashboard/dist/assets/{index-XntldBd0.js.map → index-CTOv3G7a.js.map} +1 -1
  326. package/dashboard/dist/assets/{index-CfP2nKRc.js → index-CcArGkzI.js} +3 -3
  327. package/dashboard/dist/assets/{index-CfP2nKRc.js.map → index-CcArGkzI.js.map} +1 -1
  328. package/dashboard/dist/assets/{index-Dz6R8-Dw.js → index-CkMHS-hG.js} +25 -25
  329. package/dashboard/dist/assets/index-CkMHS-hG.js.map +1 -0
  330. package/dashboard/dist/assets/index-CmsmwCyv.js +2 -0
  331. package/dashboard/dist/assets/{index-4YaDUcKd.js.map → index-CmsmwCyv.js.map} +1 -1
  332. package/dashboard/dist/assets/{index-2SM2_onB.js → index-CvJsvqIc.js} +2 -2
  333. package/dashboard/dist/assets/{index-2SM2_onB.js.map → index-CvJsvqIc.js.map} +1 -1
  334. package/dashboard/dist/assets/{index-Rwqrflm-.js → index-D-ilRyfy.js} +2 -2
  335. package/dashboard/dist/assets/{index-Rwqrflm-.js.map → index-D-ilRyfy.js.map} +1 -1
  336. package/dashboard/dist/assets/{index-CZ7A3AcD.js → index-DSUtfDXx.js} +2 -2
  337. package/dashboard/dist/assets/{index-CZ7A3AcD.js.map → index-DSUtfDXx.js.map} +1 -1
  338. package/dashboard/dist/assets/{index-jL2K6us4.js → index-Da6ycKyf.js} +2 -2
  339. package/dashboard/dist/assets/{index-jL2K6us4.js.map → index-Da6ycKyf.js.map} +1 -1
  340. package/dashboard/dist/assets/{knowledge-CeUqGPuo.js → knowledge-Bpbg20DC.js} +2 -2
  341. package/dashboard/dist/assets/{knowledge-CeUqGPuo.js.map → knowledge-Bpbg20DC.js.map} +1 -1
  342. package/dashboard/dist/assets/machines-BqOZZU-3.js +2 -0
  343. package/dashboard/dist/assets/machines-BqOZZU-3.js.map +1 -0
  344. package/dashboard/dist/assets/{mcp-Cx96NTeD.js → mcp-CmcUXYce.js} +2 -2
  345. package/dashboard/dist/assets/{mcp-Cx96NTeD.js.map → mcp-CmcUXYce.js.map} +1 -1
  346. package/dashboard/dist/assets/{mcp-query-XAzWl0gm.js → mcp-query-C8buQxN9.js} +2 -2
  347. package/dashboard/dist/assets/{mcp-query-XAzWl0gm.js.map → mcp-query-C8buQxN9.js.map} +1 -1
  348. package/dashboard/dist/assets/{personas--4kjwyOw.js → personas-C2n5oDmY.js} +2 -2
  349. package/dashboard/dist/assets/{personas--4kjwyOw.js.map → personas-C2n5oDmY.js.map} +1 -1
  350. package/dashboard/dist/assets/{pipelines-PgkwvG7f.js → pipelines-RKpSkFz7.js} +2 -2
  351. package/dashboard/dist/assets/{pipelines-PgkwvG7f.js.map → pipelines-RKpSkFz7.js.map} +1 -1
  352. package/dashboard/dist/assets/{tasks-CcYxdBa3.js → tasks-Dab4hExW.js} +2 -2
  353. package/dashboard/dist/assets/{tasks-CcYxdBa3.js.map → tasks-Dab4hExW.js.map} +1 -1
  354. package/dashboard/dist/assets/{topics-_lik7cNY.js → topics-FTMNP7r3.js} +2 -2
  355. package/dashboard/dist/assets/{topics-_lik7cNY.js.map → topics-FTMNP7r3.js.map} +1 -1
  356. package/dashboard/dist/assets/{useDebouncedValue-Bm_AoxVx.js → useDebouncedValue-B_1u1YIk.js} +2 -2
  357. package/dashboard/dist/assets/{useDebouncedValue-Bm_AoxVx.js.map → useDebouncedValue-B_1u1YIk.js.map} +1 -1
  358. package/dashboard/dist/assets/{useNamespace-DgXXKdx5.js → useNamespace-HUeAI7Qb.js} +2 -2
  359. package/dashboard/dist/assets/{useNamespace-DgXXKdx5.js.map → useNamespace-HUeAI7Qb.js.map} +1 -1
  360. package/dashboard/dist/assets/{useYamlActivityEvents-Dj2qtGSd.js → useYamlActivityEvents-Cax54qHH.js} +2 -2
  361. package/dashboard/dist/assets/{useYamlActivityEvents-Dj2qtGSd.js.map → useYamlActivityEvents-Cax54qHH.js.map} +1 -1
  362. package/dashboard/dist/assets/{validate-FlXCBBT6.js → validate-DxOaV7QS.js} +2 -2
  363. package/dashboard/dist/assets/{validate-FlXCBBT6.js.map → validate-DxOaV7QS.js.map} +1 -1
  364. package/dashboard/dist/assets/{vendor-icons-DUYcKTjP.js → vendor-icons-fxBvvHBi.js} +187 -182
  365. package/dashboard/dist/assets/vendor-icons-fxBvvHBi.js.map +1 -0
  366. package/dashboard/dist/assets/{yaml-workflows-B0_JxRYQ.js → yaml-workflows-BgY1XHJx.js} +2 -2
  367. package/dashboard/dist/assets/{yaml-workflows-B0_JxRYQ.js.map → yaml-workflows-BgY1XHJx.js.map} +1 -1
  368. package/dashboard/dist/index.html +3 -3
  369. package/docs/architecture.md +1 -0
  370. package/docs/code-owned-configuration.md +6 -0
  371. package/docs/dashboard.md +2 -1
  372. package/docs/events.md +34 -1
  373. package/docs/hitl/lookups.md +6 -4
  374. package/docs/machines.md +114 -28
  375. package/docs/mcp.md +19 -0
  376. package/package.json +1 -1
  377. package/dashboard/dist/assets/AgentsPage-C9UuUKhr.js +0 -2
  378. package/dashboard/dist/assets/AvailableEscalationsPage-DrvfXz70.js +0 -2
  379. package/dashboard/dist/assets/FilterBar-BygWrnB1.js +0 -2
  380. package/dashboard/dist/assets/FilterBar-BygWrnB1.js.map +0 -1
  381. package/dashboard/dist/assets/McpRunDetailPage-80fQn9iX.js +0 -2
  382. package/dashboard/dist/assets/WorkflowExecutionPage-Di08wCbs.js +0 -2
  383. package/dashboard/dist/assets/index-4YaDUcKd.js +0 -2
  384. package/dashboard/dist/assets/index-CnU7Jjz3.css +0 -1
  385. package/dashboard/dist/assets/index-Dz6R8-Dw.js.map +0 -1
  386. package/dashboard/dist/assets/vendor-icons-DUYcKTjP.js.map +0 -1
@@ -10,6 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.inspectMessage = inspectMessage;
13
+ exports.isUnreadableRead = isUnreadableRead;
13
14
  const ajv_1 = __importDefault(require("ajv"));
14
15
  const constants_1 = require("./constants");
15
16
  const envelope_1 = require("./envelope");
@@ -35,13 +36,13 @@ const ENVELOPE = shape({
35
36
  });
36
37
  const RECIPE = { anyOf: [shape({ id: STR, version: STR }), { type: 'null' }] };
37
38
  const BODY = {
38
- [topics_1.MESSAGE_KIND.PRESENCE]: shape({ online: BOOL, software: STR }, ['software']),
39
+ [topics_1.MESSAGE_KIND.PRESENCE]: shape({ online: BOOL, software: STR, sessionId: STR, reason: STR }, ['software', 'sessionId', 'reason']),
39
40
  [topics_1.MESSAGE_KIND.SNAPSHOT]: shape({
40
41
  bootId: STR, software: STR, mode: oneOf(constants_1.MODE), enabled: BOOL, recipe: RECIPE,
41
42
  jobs: { type: 'array', items: shape({ jobId: STR, units: { type: 'array', items: STR }, status: STR }) },
42
43
  alarms: { type: 'array', items: STR },
43
44
  cycles: INT0,
44
- }),
45
+ }, ['bootId', 'software', 'recipe', 'cycles']),
45
46
  [topics_1.MESSAGE_KIND.JOB]: shape({
46
47
  event: oneOf(constants_1.JOB_EVENT), jobId: STR, unitId: STR, orderRef: STR, recipe: RECIPE, carrierId: STR_N, peer: STR_N,
47
48
  result: oneOf(constants_1.JOB_RESULT), reason: STR_N,
@@ -52,15 +53,15 @@ const BODY = {
52
53
  materials: { type: 'array', items: shape({ material: STR, lot: STR }) },
53
54
  startedAt: TS, endedAt: TS, durationMs: INT0, failure: oneOf(constants_1.TRANSFER_FAILURE),
54
55
  percent: { type: 'number', minimum: 0, maximum: 100 }, remainingSec: INT0, stage: STR, detail: { type: 'object' },
55
- }, ['jobId', 'unitId', 'peer', 'result', 'reason', 'measurements', 'materials', 'startedAt', 'endedAt', 'durationMs', 'failure', 'percent', 'remainingSec', 'stage', 'detail']),
56
+ }, ['jobId', 'unitId', 'orderRef', 'peer', 'result', 'reason', 'measurements', 'materials', 'startedAt', 'endedAt', 'durationMs', 'failure', 'percent', 'remainingSec', 'stage', 'detail']),
56
57
  [topics_1.MESSAGE_KIND.ALARM]: shape({
57
58
  alarmId: STR, code: STR, level: oneOf(constants_1.LEVEL), text: STR, active: BOOL,
58
59
  raisedAt: TS, clearedAt: TS_N, clearedBy: STR_N, ackedBy: STR_N, ackedAt: TS_N,
59
- zone: STR_N, autoClears: BOOL, action: STR_N, jobId: STR_N,
60
- }),
60
+ zone: STR_N, autoClears: BOOL, action: STR_N, jobId: STR_N, needsPerson: BOOL,
61
+ }, ['needsPerson']),
61
62
  [topics_1.MESSAGE_KIND.TELEMETRY]: shape({ readings: { type: 'object', additionalProperties: NUM } }),
62
63
  [topics_1.MESSAGE_KIND.CMD]: shape({ cmdId: STR, op: oneOf(constants_1.OP), args: { type: 'object' } }),
63
- [topics_1.MESSAGE_KIND.REPLY]: shape({ cmdId: STR, status: oneOf(constants_1.REPLY_STATUS), reason: STR }),
64
+ [topics_1.MESSAGE_KIND.REPLY]: shape({ cmdId: STR, status: oneOf(constants_1.REPLY_STATUS), reason: STR, code: STR, preparing: BOOL }, ['code', 'preparing']),
64
65
  [topics_1.MESSAGE_KIND.REPORT]: { type: 'object' },
65
66
  };
66
67
  const ajv = new ajv_1.default({ allErrors: true, strict: false });
@@ -95,6 +96,10 @@ function inspectMessage(root, topic, raw, retained) {
95
96
  }
96
97
  /** Job events a receiver may report naming only the carrier it scanned. */
97
98
  const CARRIER_EVENTS = [constants_1.JOB_EVENT.ARRIVED, constants_1.JOB_EVENT.TRANSFER_FAILED];
99
+ /** A receiver's report that it could read no id at all: a transfer failure on the read, naming no unit and no carrier. */
100
+ function isUnreadableRead(b) {
101
+ return b.event === constants_1.JOB_EVENT.TRANSFER_FAILED && b.failure === constants_1.TRANSFER_FAILURE.UNREADABLE_ID && b.unitId === undefined && typeof b.carrierId !== 'string';
102
+ }
98
103
  function crossFieldRules(parsed, env, retained) {
99
104
  const out = [];
100
105
  const b = env.body ?? {};
@@ -105,8 +110,8 @@ function crossFieldRules(parsed, env, retained) {
105
110
  if (kind === topics_1.MESSAGE_KIND.ALARM && b.alarmId !== key)
106
111
  out.push('alarmId disagrees with the topic');
107
112
  if (kind === topics_1.MESSAGE_KIND.JOB) {
108
- // A scan at a receiver may read only the carrier; every other job event names its unit and job.
109
- const byCarrier = CARRIER_EVENTS.includes(b.event) && typeof b.carrierId === 'string';
113
+ // A scan at a receiver may read only the carrier, or nothing at all when the read failed; every other job event names its unit and job.
114
+ const byCarrier = CARRIER_EVENTS.includes(b.event) && (typeof b.carrierId === 'string' || isUnreadableRead(b));
110
115
  if (b.unitId === undefined && !byCarrier)
111
116
  out.push(`${b.event} without a unitId`);
112
117
  if (b.jobId === undefined && !byCarrier)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The machine broker (Mosquitto), rendered from the line. Machines speak
2
+ * The machine broker (Mosquitto), rendered from its lines. Machines speak
3
3
  * MQTT 5 to it over TLS, each with its own client certificate (the CN is
4
4
  * its username). The ACL lets a machine publish only its own channels and
5
5
  * read only its own commands, and lets the line controller command every
@@ -8,8 +8,9 @@
8
8
  */
9
9
  import { type LineCodec } from '../protocol/codec';
10
10
  import { type LineDef } from '../line';
11
+ import { type Lines, type TopicRoot } from './site-conf';
11
12
  export interface MosquittoConfOptions {
12
- root: string;
13
+ root: TopicRoot;
13
14
  /** Certificate directory as the broker sees it. */
14
15
  certDir: string;
15
16
  /** Where the site NATS MQTT listener is, host:port. */
@@ -17,11 +18,15 @@ export interface MosquittoConfOptions {
17
18
  aclFile: string;
18
19
  maxPacketBytes?: number;
19
20
  }
20
- export declare function renderMosquittoConf(line: LineDef, opts: MosquittoConfOptions): string;
21
+ /** The broker for one line or several: one listener, one bridge per line. */
22
+ export declare function renderMosquittoConf(lines: Lines, opts: MosquittoConfOptions): string;
21
23
  /**
22
24
  * The codec's channels decide the topics: a machine writes its events and telemetry and reads its commands.
23
25
  * A station whose address the line learns has no user of its own: `pattern` rules give every certificate
24
26
  * the channels under its own name (%u), so its certificate's common name must be its address. On a line
25
27
  * that also declares addresses, a certificate named for a declared address is denied that station's channels.
26
28
  */
27
- export declare function renderMosquittoAcl(line: LineDef, codec: Pick<LineCodec, 'root' | 'channels' | 'topicFor'>): string;
29
+ export type AclCodec = Pick<LineCodec, 'root' | 'channels' | 'topicFor'>;
30
+ /** One codec for every line, or the codec of each line. */
31
+ export type AclCodecs = AclCodec | ((line: LineDef) => AclCodec);
32
+ export declare function renderMosquittoAcl(lines: Lines, codec: AclCodecs): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * The machine broker (Mosquitto), rendered from the line. Machines speak
3
+ * The machine broker (Mosquitto), rendered from its lines. Machines speak
4
4
  * MQTT 5 to it over TLS, each with its own client certificate (the CN is
5
5
  * its username). The ACL lets a machine publish only its own channels and
6
6
  * read only its own commands, and lets the line controller command every
@@ -14,7 +14,26 @@ const envelope_1 = require("../protocol/envelope");
14
14
  const codec_1 = require("../protocol/codec");
15
15
  const line_1 = require("../line");
16
16
  const site_conf_1 = require("./site-conf");
17
- function renderMosquittoConf(line, opts) {
17
+ /** One bridge connection per line, carrying the line's topics out to the site NATS. */
18
+ function bridgeBlock(line, opts) {
19
+ return `connection ${line.siteId}-${line.lineId}-site-nats
20
+ address ${opts.siteNats}
21
+ bridge_protocol_version mqttv311
22
+ bridge_cafile ${opts.certDir}/ca.pem
23
+ bridge_certfile ${opts.certDir}/${(0, site_conf_1.bridgeId)(line)}.pem
24
+ bridge_keyfile ${opts.certDir}/${(0, site_conf_1.bridgeId)(line)}.key
25
+ remote_clientid ${(0, site_conf_1.bridgeId)(line)}
26
+ cleansession false
27
+ try_private false
28
+ notifications false
29
+ topic ${(0, site_conf_1.rootOf)(opts.root, line)}/${line.siteId}/${line.lineId}/# out 1
30
+ `;
31
+ }
32
+ /** The broker for one line or several: one listener, one bridge per line. */
33
+ function renderMosquittoConf(lines, opts) {
34
+ const list = (0, site_conf_1.linesOf)(lines);
35
+ if (!list.length)
36
+ throw new Error('renderMosquittoConf needs at least one line');
18
37
  return `# Rendered from the line definition by site/setup.ts. Do not edit by hand.
19
38
  persistence true
20
39
  persistence_location /mosquitto/data/
@@ -32,26 +51,17 @@ use_identity_as_username true
32
51
  acl_file ${opts.aclFile}
33
52
  max_packet_size ${opts.maxPacketBytes ?? 262_144}
34
53
 
35
- connection ${line.siteId}-${line.lineId}-site-nats
36
- address ${opts.siteNats}
37
- bridge_protocol_version mqttv311
38
- bridge_cafile ${opts.certDir}/ca.pem
39
- bridge_certfile ${opts.certDir}/${(0, site_conf_1.bridgeId)(line)}.pem
40
- bridge_keyfile ${opts.certDir}/${(0, site_conf_1.bridgeId)(line)}.key
41
- remote_clientid ${(0, site_conf_1.bridgeId)(line)}
42
- cleansession false
43
- try_private false
44
- notifications false
45
- topic ${opts.root}/${line.siteId}/${line.lineId}/# out 1
46
- `;
54
+ ${list.map((line) => bridgeBlock(line, opts)).join('\n')}`;
47
55
  }
48
- /**
49
- * The codec's channels decide the topics: a machine writes its events and telemetry and reads its commands.
50
- * A station whose address the line learns has no user of its own: `pattern` rules give every certificate
51
- * the channels under its own name (%u), so its certificate's common name must be its address. On a line
52
- * that also declares addresses, a certificate named for a declared address is denied that station's channels.
53
- */
54
- function renderMosquittoAcl(line, codec) {
56
+ function renderMosquittoAcl(lines, codec) {
57
+ const list = (0, site_conf_1.linesOf)(lines);
58
+ if (!list.length)
59
+ throw new Error('renderMosquittoAcl needs at least one line');
60
+ const codecOf = (line) => (typeof codec === 'function' ? codec(line) : codec);
61
+ return `# Rendered from the line definition by site/setup.ts. Do not edit by hand.\n\n${list.map((line) => aclRules(line, codecOf(line))).join('\n\n')}\n`;
62
+ }
63
+ /** One line's rules: its stations, its controller, and the shadows a learning line needs. */
64
+ function aclRules(line, codec) {
55
65
  const { siteId, lineId } = line;
56
66
  const { events, telemetry, commands } = codec.channels;
57
67
  const own = (address, channels) => channels.map((c) => (0, codec_1.topicOf)(codec, { siteId, lineId, address }, c));
@@ -70,7 +80,7 @@ function renderMosquittoAcl(line, codec) {
70
80
  ...commands.map((c) => `topic write ${(0, codec_1.topicOf)(codec, { siteId, lineId }, c)}`),
71
81
  `topic read ${codec.root}/${siteId}/${lineId}/#`,
72
82
  ].join('\n');
73
- return `# Rendered from the line definition by site/setup.ts. Do not edit by hand.\n\n${[...learned, ...shadows, ...machines, controller].join('\n\n')}\n`;
83
+ return [...learned, ...shadows, ...machines, controller].join('\n\n');
74
84
  }
75
85
  /** Deny a declared station's channels to a certificate named for its address, which the patterns would grant. */
76
86
  function shadowRules(line, address, topics) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The site's NATS server, rendered from the line. It runs JetStream and an
2
+ * The site's NATS server, rendered from its lines. It runs JetStream and an
3
3
  * MQTT listener that only the machine broker's bridge logs in to (by client
4
4
  * certificate; the CN is its user, and it may publish only under the line's
5
5
  * topics). The gateway logs in by password and may only keep and read the
@@ -8,8 +8,11 @@
8
8
  * site NATS stands alone; Long Tail's escalation events stay inside Long Tail.
9
9
  */
10
10
  import { type LineDef } from '../line';
11
+ /** One topic root for every line, or a root per line (lines on different codecs). */
12
+ export type TopicRoot = string | ((line: LineDef) => string);
13
+ export declare function rootOf(root: TopicRoot, line: LineDef): string;
11
14
  export interface SiteConfOptions {
12
- root: string;
15
+ root: TopicRoot;
13
16
  gatewayUser: string;
14
17
  gatewayPassword: string;
15
18
  certDir: string;
@@ -20,4 +23,11 @@ export interface SiteConfOptions {
20
23
  export declare function bridgeId(line: LineDef): string;
21
24
  /** What the gateway may publish: its streams' and its consumer's JetStream requests and acks, never a machine subject. */
22
25
  export declare function gatewayPublish(line: LineDef): string[];
23
- export declare function renderSiteConf(line: LineDef, opts: SiteConfOptions): string;
26
+ /** One line, or every line the site runs. */
27
+ export type Lines = LineDef | LineDef[];
28
+ export declare function linesOf(lines: Lines): LineDef[];
29
+ /**
30
+ * The site NATS for one line or several: one bridge user per line, one
31
+ * gateway user allowed every line's streams and subjects.
32
+ */
33
+ export declare function renderSiteConf(lines: Lines, opts: SiteConfOptions): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * The site's NATS server, rendered from the line. It runs JetStream and an
3
+ * The site's NATS server, rendered from its lines. It runs JetStream and an
4
4
  * MQTT listener that only the machine broker's bridge logs in to (by client
5
5
  * certificate; the CN is its user, and it may publish only under the line's
6
6
  * topics). The gateway logs in by password and may only keep and read the
@@ -9,13 +9,18 @@
9
9
  * site NATS stands alone; Long Tail's escalation events stay inside Long Tail.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.rootOf = rootOf;
12
13
  exports.bridgeId = bridgeId;
13
14
  exports.gatewayPublish = gatewayPublish;
15
+ exports.linesOf = linesOf;
14
16
  exports.renderSiteConf = renderSiteConf;
15
17
  const line_1 = require("../line");
16
18
  const protocol_1 = require("../protocol");
17
19
  const permissions_1 = require("../../messaging/nats/permissions");
18
20
  const streams_1 = require("../transport/streams");
21
+ function rootOf(root, line) {
22
+ return typeof root === 'string' ? root : root(line);
23
+ }
19
24
  const q = JSON.stringify;
20
25
  /** The machine broker's bridge identity for a line. */
21
26
  function bridgeId(line) {
@@ -25,18 +30,36 @@ function bridgeId(line) {
25
30
  function gatewayPublish(line) {
26
31
  return (0, permissions_1.jsPermissions)({ streams: Object.values(streams_1.LINE_STREAM), durable: `${(0, line_1.controllerId)(line)}-events` }).publish;
27
32
  }
28
- function renderSiteConf(line, opts) {
29
- const base = (0, protocol_1.toSubject)(`${opts.root}/${line.siteId}/${line.lineId}`);
30
- const bridge = [
33
+ function linesOf(lines) {
34
+ return Array.isArray(lines) ? lines : [lines];
35
+ }
36
+ /** The server's name: the line for one line, the site when every line shares it, `site` otherwise. */
37
+ function serverName(lines) {
38
+ if (lines.length === 1)
39
+ return `${lines[0].siteId}-${lines[0].lineId}`;
40
+ const sites = new Set(lines.map((l) => l.siteId));
41
+ return sites.size === 1 ? lines[0].siteId : 'site';
42
+ }
43
+ /**
44
+ * The site NATS for one line or several: one bridge user per line, one
45
+ * gateway user allowed every line's streams and subjects.
46
+ */
47
+ function renderSiteConf(lines, opts) {
48
+ const list = linesOf(lines);
49
+ if (!list.length)
50
+ throw new Error('renderSiteConf needs at least one line');
51
+ const baseOf = (line) => (0, protocol_1.toSubject)(`${rootOf(opts.root, line)}/${line.siteId}/${line.lineId}`);
52
+ const bridge = list.map((line) => [
31
53
  ` { user: ${q(`CN=${bridgeId(line)}`)}, allowed_connection_types: ["MQTT"],`,
32
- ` permissions: { publish: [${q(`${base}.>`)}], subscribe: { deny: [">"] } } }`,
33
- ].join('\n');
54
+ ` permissions: { publish: [${q(`${baseOf(line)}.>`)}], subscribe: { deny: [">"] } } }`,
55
+ ].join('\n')).join('\n');
56
+ const publish = [...new Set(list.flatMap((line) => gatewayPublish(line)))];
34
57
  const gateway = [
35
58
  ` { user: ${q(opts.gatewayUser)}, password: ${q(opts.gatewayPassword)}, allowed_connection_types: ["STANDARD"],`,
36
- ` permissions: { publish: ${q(gatewayPublish(line))}, subscribe: ${q(['_INBOX.>', `${base}.>`])} } }`,
59
+ ` permissions: { publish: ${q(publish)}, subscribe: ${q(['_INBOX.>', ...list.map((line) => `${baseOf(line)}.>`)])} } }`,
37
60
  ].join('\n');
38
61
  return `# Rendered from the line definition by site/setup.ts. Do not edit by hand.
39
- server_name: ${q(`${line.siteId}-${line.lineId}`)}
62
+ server_name: ${q(serverName(list))}
40
63
  port: 4222
41
64
  http: 8222
42
65
  jetstream { store_dir: ${q(opts.storeDir)} }
@@ -14,5 +14,6 @@ export declare class CallbackEscalationFeed implements EscalationFeed {
14
14
  constructor(adapter: CallbackEventAdapter, nakDelayMs?: number);
15
15
  connect(): Promise<void>;
16
16
  consume(handler: (event: EscalationEvent) => Promise<MachineDisposition>): Promise<void>;
17
+ unsubscribe(): Promise<void>;
17
18
  close(): Promise<void>;
18
19
  }
@@ -23,9 +23,12 @@ class CallbackEscalationFeed {
23
23
  void this.redelivery.deliver(() => handler(event));
24
24
  });
25
25
  }
26
- async close() {
26
+ async unsubscribe() {
27
27
  this.off?.();
28
28
  await this.redelivery.stop();
29
29
  }
30
+ async close() {
31
+ await this.unsubscribe();
32
+ }
30
33
  }
31
34
  exports.CallbackEscalationFeed = CallbackEscalationFeed;
@@ -26,6 +26,7 @@ export declare class MemoryMachineBus implements MachineBus {
26
26
  retain?: boolean;
27
27
  }): Promise<void>;
28
28
  retained(filter: string): Promise<InboundMessage[]>;
29
+ unsubscribe(): Promise<void>;
29
30
  close(): Promise<void>;
30
31
  }
31
32
  export declare class MemoryEscalationFeed implements EscalationFeed {
@@ -36,5 +37,6 @@ export declare class MemoryEscalationFeed implements EscalationFeed {
36
37
  emit(body: Record<string, any>): void;
37
38
  connect(): Promise<void>;
38
39
  consume(handler: (event: EscalationEvent) => Promise<Disposition>): Promise<void>;
40
+ unsubscribe(): Promise<void>;
39
41
  close(): Promise<void>;
40
42
  }
@@ -50,6 +50,9 @@ class MemoryMachineBus {
50
50
  async retained(filter) {
51
51
  return this.broker.retained(filter);
52
52
  }
53
+ async unsubscribe() {
54
+ this.broker.detach(this.durableName);
55
+ }
53
56
  async close() {
54
57
  this.broker.detach(this.durableName);
55
58
  }
@@ -72,6 +75,9 @@ class MemoryEscalationFeed {
72
75
  return event ? handler(event) : delivery_1.DISPOSITION.TERM;
73
76
  });
74
77
  }
78
+ async unsubscribe() {
79
+ this.broker.detach(this.durableName);
80
+ }
75
81
  async close() {
76
82
  this.broker.detach(this.durableName);
77
83
  }
@@ -19,6 +19,8 @@ export declare class MqttMachineBus implements MachineBus {
19
19
  retain?: boolean;
20
20
  }): Promise<void>;
21
21
  retained(filter: string): Promise<InboundMessage[]>;
22
+ /** Stops delivery in this process; the persistent session keeps its subscription, so messages queue for the next gateway. */
23
+ unsubscribe(): Promise<void>;
22
24
  close(): Promise<void>;
23
25
  private connected;
24
26
  }
@@ -30,6 +30,10 @@ class MqttMachineBus {
30
30
  retained(filter) {
31
31
  return this.connected().retained(filter);
32
32
  }
33
+ /** Stops delivery in this process; the persistent session keeps its subscription, so messages queue for the next gateway. */
34
+ async unsubscribe() {
35
+ await this.redelivery.stop();
36
+ }
33
37
  async close() {
34
38
  await this.redelivery.stop();
35
39
  await this.session?.close(true);
@@ -17,6 +17,8 @@ export interface NatsBusOptions {
17
17
  lineId: string;
18
18
  /** The codec's topics: which subjects are durable events and which are live telemetry. */
19
19
  codec: StreamCodec;
20
+ /** The codecs of every line on this site, so the streams keep all their subjects. */
21
+ siteCodecs?: StreamCodec[];
20
22
  nakDelayMs?: number;
21
23
  }
22
24
  export declare class NatsMachineBus implements MachineBus {
@@ -31,6 +33,7 @@ export declare class NatsMachineBus implements MachineBus {
31
33
  retain?: boolean;
32
34
  }): Promise<void>;
33
35
  retained(filter: string): Promise<InboundMessage[]>;
36
+ unsubscribe(): Promise<void>;
34
37
  close(): Promise<void>;
35
38
  private stream;
36
39
  }
@@ -34,6 +34,9 @@ class NatsMachineBus {
34
34
  retained(filter) {
35
35
  return this.stream().retained(filter);
36
36
  }
37
+ async unsubscribe() {
38
+ await this.bus?.unsubscribe();
39
+ }
37
40
  async close() {
38
41
  await this.bus?.close();
39
42
  await this.provider.disconnect();
@@ -21,6 +21,7 @@ export declare class CoreEscalationFeed implements EscalationFeed {
21
21
  constructor(opts: CoreFeedOptions);
22
22
  connect(): Promise<void>;
23
23
  consume(handler: Handler): Promise<void>;
24
+ unsubscribe(): Promise<void>;
24
25
  close(): Promise<void>;
25
26
  }
26
27
  export {};
@@ -40,9 +40,12 @@ class CoreEscalationFeed {
40
40
  },
41
41
  });
42
42
  }
43
- async close() {
43
+ async unsubscribe() {
44
44
  this.sub?.unsubscribe();
45
45
  await this.redelivery.stop();
46
+ }
47
+ async close() {
48
+ await this.unsubscribe();
46
49
  await this.nc?.drain();
47
50
  }
48
51
  }
@@ -13,5 +13,6 @@ export declare class PubSubEscalationFeed implements EscalationFeed {
13
13
  constructor(pubsub: LTPubSub, nakDelayMs?: number);
14
14
  connect(): Promise<void>;
15
15
  consume(handler: (event: EscalationEvent) => Promise<Disposition>): Promise<void>;
16
+ unsubscribe(): Promise<void>;
16
17
  close(): Promise<void>;
17
18
  }
@@ -31,9 +31,12 @@ class PubSubEscalationFeed {
31
31
  void this.redelivery.deliver(() => handler(event));
32
32
  });
33
33
  }
34
- async close() {
34
+ async unsubscribe() {
35
35
  await this.sub?.close();
36
36
  await this.redelivery.stop();
37
37
  }
38
+ async close() {
39
+ await this.unsubscribe();
40
+ }
38
41
  }
39
42
  exports.PubSubEscalationFeed = PubSubEscalationFeed;
@@ -3,7 +3,9 @@
3
3
  * line's durable consumer on LINE_EVENTS (replayed after a restart); telemetry
4
4
  * is read live, since it only proves a machine is alive. Commands leave through
5
5
  * an MQTT session at QoS 1 on the site broker, which queues them for a machine
6
- * that is away; retained machine state is read there too.
6
+ * that is away; retained machine state is read there too. The streams are
7
+ * ensured with every codec on the site, so lines on different roots share
8
+ * them, and a consumer the stream would feed nothing refuses to start.
7
9
  */
8
10
  import type { LTPubSub, LTStreams } from '../../../types/messaging';
9
11
  import type { MqttEndpoint } from '../../../services/machines/config';
@@ -19,6 +21,8 @@ export interface StreamBusOptions {
19
21
  lineId: string;
20
22
  /** The codec's topics: which subjects are durable events and which are live telemetry. */
21
23
  codec: StreamCodec;
24
+ /** The codecs of every line on this site; the streams keep all their subjects, whichever line starts last. */
25
+ siteCodecs?: StreamCodec[];
22
26
  nakDelayMs?: number;
23
27
  }
24
28
  export declare class StreamMachineBus implements MachineBus {
@@ -35,6 +39,8 @@ export declare class StreamMachineBus implements MachineBus {
35
39
  retain?: boolean;
36
40
  }): Promise<void>;
37
41
  retained(filter: string): Promise<InboundMessage[]>;
42
+ /** Stops the consumer and the live subscriptions; the MQTT session stays open for commands and receipts. */
43
+ unsubscribe(): Promise<void>;
38
44
  close(): Promise<void>;
39
45
  private mqtt;
40
46
  }
@@ -4,7 +4,9 @@
4
4
  * line's durable consumer on LINE_EVENTS (replayed after a restart); telemetry
5
5
  * is read live, since it only proves a machine is alive. Commands leave through
6
6
  * an MQTT session at QoS 1 on the site broker, which queues them for a machine
7
- * that is away; retained machine state is read there too.
7
+ * that is away; retained machine state is read there too. The streams are
8
+ * ensured with every codec on the site, so lines on different roots share
9
+ * them, and a consumer the stream would feed nothing refuses to start.
8
10
  */
9
11
  Object.defineProperty(exports, "__esModule", { value: true });
10
12
  exports.StreamMachineBus = void 0;
@@ -23,10 +25,16 @@ class StreamMachineBus {
23
25
  return `${this.opts.controllerId}-events`;
24
26
  }
25
27
  async connect() {
26
- const { streams, codec, siteId, lineId, controllerId } = this.opts;
27
- for (const spec of (0, streams_1.lineStreamSpecs)(codec))
28
+ const { streams, codec, siteId, lineId, controllerId, siteCodecs = [] } = this.opts;
29
+ for (const spec of (0, streams_1.lineStreamSpecs)([codec, ...siteCodecs]))
28
30
  await streams.ensureStream(spec);
29
- await streams.ensureConsumer(streams_1.LINE_STREAM.EVENTS, (0, streams_1.lineEventConsumerSpec)(codec, siteId, lineId, this.durableName));
31
+ const consumer = (0, streams_1.lineEventConsumerSpec)(codec, siteId, lineId, this.durableName);
32
+ await streams.ensureConsumer(streams_1.LINE_STREAM.EVENTS, consumer);
33
+ const events = await streams.describeStream(streams_1.LINE_STREAM.EVENTS);
34
+ const missing = events ? (0, streams_1.uncoveredSubjects)(events, consumer.filterSubjects) : consumer.filterSubjects;
35
+ if (missing.length) {
36
+ throw new Error(`${streams_1.LINE_STREAM.EVENTS} keeps nothing of ${missing.join(', ')}; the stream holds ${events?.subjects.join(', ') ?? 'nothing'}`);
37
+ }
30
38
  this.session = await mqtt_session_1.MqttSession.open(this.opts.mqtt, controllerId);
31
39
  }
32
40
  async consume(handler) {
@@ -42,10 +50,14 @@ class StreamMachineBus {
42
50
  retained(filter) {
43
51
  return this.mqtt().retained(filter);
44
52
  }
45
- async close() {
53
+ /** Stops the consumer and the live subscriptions; the MQTT session stays open for commands and receipts. */
54
+ async unsubscribe() {
46
55
  for (const sub of this.subs.splice(0))
47
56
  await sub.close();
48
57
  await this.queue.drained();
58
+ }
59
+ async close() {
60
+ await this.unsubscribe();
49
61
  await this.session?.close(true);
50
62
  }
51
63
  mqtt() {
@@ -13,8 +13,16 @@ export declare const LINE_STREAM: {
13
13
  export type StreamCodec = Pick<LineCodec, 'root' | 'channels' | 'topicFor'>;
14
14
  /** An MQTT filter as a subject: levels become tokens, `+` becomes `*`, `#` becomes `>`. */
15
15
  export declare function filterToSubject(filter: string): string;
16
- /** The line streams, for every line under the codec's root; a root alone means the generic layout. */
17
- export declare function lineStreamSpecs(codec: StreamCodec | string): StreamSpec[];
16
+ /**
17
+ * The site's streams: every line under each codec's root, since lines on
18
+ * different protocols share one site NATS. A root alone means the generic
19
+ * layout; several codecs give the union of their subjects, in order.
20
+ */
21
+ export declare function lineStreamSpecs(codecs: StreamCodec | string | (StreamCodec | string)[]): StreamSpec[];
22
+ /** Whether a stream subject pattern keeps everything a consumer filter would read. */
23
+ export declare function subjectCovers(stream: string, filter: string): boolean;
24
+ /** The consumer's filter subjects the stream keeps nothing of. */
25
+ export declare function uncoveredSubjects(stream: Pick<StreamSpec, 'subjects'>, filters: string[]): string[];
18
26
  /** The telemetry subjects for one line, read live rather than replayed. */
19
27
  export declare function telemetrySubjects(codec: StreamCodec | string, siteId: string, lineId: string): string[];
20
28
  /**
@@ -8,6 +8,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.LINE_STREAM = void 0;
9
9
  exports.filterToSubject = filterToSubject;
10
10
  exports.lineStreamSpecs = lineStreamSpecs;
11
+ exports.subjectCovers = subjectCovers;
12
+ exports.uncoveredSubjects = uncoveredSubjects;
11
13
  exports.telemetrySubjects = telemetrySubjects;
12
14
  exports.lineEventConsumerSpec = lineEventConsumerSpec;
13
15
  const codec_1 = require("../protocol/codec");
@@ -26,14 +28,39 @@ function filterToSubject(filter) {
26
28
  function subjectsFor(codec, channels, siteId, lineId) {
27
29
  return channels.map((c) => filterToSubject((0, codec_1.topicOf)(codec, { siteId, lineId }, c)));
28
30
  }
29
- /** The line streams, for every line under the codec's root; a root alone means the generic layout. */
30
- function lineStreamSpecs(codec) {
31
- const c = codecOf(codec);
31
+ /**
32
+ * The site's streams: every line under each codec's root, since lines on
33
+ * different protocols share one site NATS. A root alone means the generic
34
+ * layout; several codecs give the union of their subjects, in order.
35
+ */
36
+ function lineStreamSpecs(codecs) {
37
+ const list = (Array.isArray(codecs) ? codecs : [codecs]).map(codecOf);
38
+ const union = (channel) => [...new Set(list.flatMap((c) => subjectsFor(c, c.channels[channel])))];
32
39
  return [
33
- { name: exports.LINE_STREAM.EVENTS, subjects: subjectsFor(c, c.channels.events), maxAgeMs: 30 * DAY_MS },
34
- { name: exports.LINE_STREAM.TELEMETRY, subjects: subjectsFor(c, c.channels.telemetry), maxAgeMs: 3 * DAY_MS },
40
+ { name: exports.LINE_STREAM.EVENTS, subjects: union('events'), maxAgeMs: 30 * DAY_MS },
41
+ { name: exports.LINE_STREAM.TELEMETRY, subjects: union('telemetry'), maxAgeMs: 3 * DAY_MS },
35
42
  ];
36
43
  }
44
+ /** Whether a stream subject pattern keeps everything a consumer filter would read. */
45
+ function subjectCovers(stream, filter) {
46
+ const s = stream.split('.');
47
+ const f = filter.split('.');
48
+ for (let i = 0; i < s.length; i++) {
49
+ if (s[i] === '>')
50
+ return f.length > i;
51
+ if (i >= f.length)
52
+ return false;
53
+ if (f[i] === '>')
54
+ return false;
55
+ if (s[i] !== '*' && s[i] !== f[i])
56
+ return false;
57
+ }
58
+ return s.length === f.length;
59
+ }
60
+ /** The consumer's filter subjects the stream keeps nothing of. */
61
+ function uncoveredSubjects(stream, filters) {
62
+ return filters.filter((f) => !stream.subjects.some((s) => subjectCovers(s, f)));
63
+ }
37
64
  /** The telemetry subjects for one line, read live rather than replayed. */
38
65
  function telemetrySubjects(codec, siteId, lineId) {
39
66
  const c = codecOf(codec);
@@ -36,6 +36,9 @@ class MemoryStreams {
36
36
  else
37
37
  this.streams.set(spec.name, { spec, messages: [], seq: 0, consumers: new Map(), wake: new Set() });
38
38
  }
39
+ async describeStream(name) {
40
+ return this.streams.get(name)?.spec ?? null;
41
+ }
39
42
  async ensureConsumer(stream, spec) {
40
43
  const s = this.stream(stream);
41
44
  const existing = s.consumers.get(spec.name);
@@ -9,6 +9,8 @@ export declare function streamConfig(spec: StreamSpec): Partial<StreamConfig>;
9
9
  export declare function consumerConfig(spec: ConsumerSpec): Partial<ConsumerConfig>;
10
10
  /** Create the stream, or update an existing one to the spec. */
11
11
  export declare function ensureStream(jsm: JetStreamManager, spec: StreamSpec): Promise<void>;
12
+ /** The stream as JetStream holds it, or null when there is none. */
13
+ export declare function describeStream(jsm: JetStreamManager, name: string): Promise<StreamSpec | null>;
12
14
  /** Create the consumer, or move an existing one to the spec's subjects without losing its place. */
13
15
  export declare function ensureConsumer(jsm: JetStreamManager, stream: string, spec: ConsumerSpec): Promise<void>;
14
16
  export declare function payloadOf(m: {
@@ -20,6 +22,7 @@ export declare class NatsStreams implements LTStreams {
20
22
  constructor(jsm: JetStreamManager, js: JetStreamClient);
21
23
  ensureStream(spec: StreamSpec): Promise<void>;
22
24
  ensureConsumer(stream: string, spec: ConsumerSpec): Promise<void>;
25
+ describeStream(name: string): Promise<StreamSpec | null>;
23
26
  append(subject: Subject, payload: string): Promise<void>;
24
27
  /** One message at a time: the next is not taken until the handler settles this one. */
25
28
  consume(stream: string, consumer: string, handler: (m: LTStreamMessage) => Promise<Delivery>, opts?: LTConsumeOptions): Promise<LTSubscription>;