@hotmeshio/long-tail 0.27.0 → 0.28.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.
- package/README.md +9 -2
- package/build/api/settings.d.ts +4 -1
- package/build/api/settings.js +7 -1
- package/build/index.js +9 -4
- package/build/lib/db/config-lock.d.ts +10 -0
- package/build/lib/db/config-lock.js +28 -0
- package/build/lib/db/schemas/041_machine_lines.sql +26 -0
- package/build/lib/events/browser-view.js +8 -2
- package/build/lib/events/publish.d.ts +33 -0
- package/build/lib/events/publish.js +52 -1
- package/build/lib/machines/line.d.ts +4 -1
- package/build/lib/machines/line.js +18 -1
- package/build/lib/machines/protocol/codec.d.ts +8 -0
- package/build/lib/machines/protocol/codec.js +9 -0
- package/build/lib/machines/protocol/constants.d.ts +1 -1
- package/build/lib/machines/protocol/constants.js +1 -1
- package/build/lib/machines/protocol/contract.js +2 -2
- package/build/lib/machines/site/mosquitto-conf.d.ts +5 -4
- package/build/lib/machines/site/mosquitto-conf.js +31 -16
- package/build/lib/machines/site/site-conf.d.ts +9 -2
- package/build/lib/machines/site/site-conf.js +27 -8
- package/build/lib/machines/transport/callback-feed.d.ts +1 -0
- package/build/lib/machines/transport/callback-feed.js +4 -1
- package/build/lib/machines/transport/memory.d.ts +2 -0
- package/build/lib/machines/transport/memory.js +6 -0
- package/build/lib/machines/transport/mqtt-bus.d.ts +2 -0
- package/build/lib/machines/transport/mqtt-bus.js +4 -0
- package/build/lib/machines/transport/nats-bus.d.ts +1 -0
- package/build/lib/machines/transport/nats-bus.js +3 -0
- package/build/lib/machines/transport/nats-feed.d.ts +1 -0
- package/build/lib/machines/transport/nats-feed.js +4 -1
- package/build/lib/machines/transport/pubsub-feed.d.ts +1 -0
- package/build/lib/machines/transport/pubsub-feed.js +4 -1
- package/build/lib/machines/transport/stream-bus.d.ts +2 -0
- package/build/lib/machines/transport/stream-bus.js +5 -1
- package/build/modules/machines.d.ts +8 -0
- package/build/modules/machines.js +16 -0
- package/build/routes/index.js +2 -0
- package/build/routes/machines.d.ts +2 -0
- package/build/routes/machines.js +220 -0
- package/build/services/machines/commands.d.ts +5 -5
- package/build/services/machines/commands.js +3 -2
- package/build/services/machines/config.d.ts +2 -1
- package/build/services/machines/config.js +1 -1
- package/build/services/machines/contracts.d.ts +10 -0
- package/build/services/machines/gateway/actions.d.ts +1 -0
- package/build/services/machines/gateway/admission.d.ts +15 -3
- package/build/services/machines/gateway/admission.js +29 -14
- package/build/services/machines/gateway/dispatcher.d.ts +3 -0
- package/build/services/machines/gateway/dispatcher.js +12 -4
- package/build/services/machines/gateway/executor.d.ts +11 -2
- package/build/services/machines/gateway/executor.js +68 -29
- package/build/services/machines/gateway/gateway.d.ts +16 -1
- package/build/services/machines/gateway/gateway.js +55 -23
- package/build/services/machines/gateway/index.d.ts +3 -0
- package/build/services/machines/gateway/index.js +3 -0
- package/build/services/machines/gateway/inspect.d.ts +17 -0
- package/build/services/machines/gateway/inspect.js +68 -0
- package/build/services/machines/gateway/log.d.ts +8 -0
- package/build/services/machines/gateway/log.js +18 -0
- package/build/services/machines/gateway/pacing.js +1 -0
- package/build/services/machines/gateway/plan-escalation.js +8 -3
- package/build/services/machines/gateway/plan-machine.js +16 -6
- package/build/services/machines/gateway/project.d.ts +9 -0
- package/build/services/machines/gateway/project.js +55 -0
- package/build/services/machines/gateway/receipts.d.ts +3 -0
- package/build/services/machines/gateway/receipts.js +3 -1
- package/build/services/machines/gateway/reconcile.js +3 -3
- package/build/services/machines/gateway/registration.js +1 -1
- package/build/services/machines/gateway/reply-waiter.d.ts +6 -3
- package/build/services/machines/gateway/reply-waiter.js +37 -16
- package/build/services/machines/gateway/state.d.ts +29 -3
- package/build/services/machines/gateway/state.js +61 -7
- package/build/services/machines/gateway/wire.d.ts +58 -0
- package/build/services/machines/gateway/wire.js +171 -0
- package/build/services/machines/index.d.ts +3 -0
- package/build/services/machines/index.js +2 -0
- package/build/services/machines/line-index.d.ts +16 -0
- package/build/services/machines/line-index.js +39 -0
- package/build/services/machines/registry-sql.d.ts +31 -0
- package/build/services/machines/registry-sql.js +110 -0
- package/build/services/machines/registry-sync.d.ts +31 -0
- package/build/services/machines/registry-sync.js +81 -0
- package/build/services/machines/registry.d.ts +77 -0
- package/build/services/machines/registry.js +158 -0
- package/build/services/machines/rows.js +10 -1
- package/build/services/machines/surface.d.ts +45 -5
- package/build/services/machines/surface.js +95 -15
- package/build/start/apply-report.d.ts +1 -1
- package/build/start/apply-report.js +3 -25
- package/build/start/index.js +16 -4
- package/build/system/index.js +13 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +22 -22
- package/build/system/mcp-servers/human-queue-schemas.d.ts +7 -7
- package/build/system/mcp-servers/machines-schemas.d.ts +35 -0
- package/build/system/mcp-servers/machines-schemas.js +35 -0
- package/build/system/mcp-servers/machines.d.ts +20 -0
- package/build/system/mcp-servers/machines.js +154 -0
- package/build/system/seed/tool-manifests-machines.d.ts +2 -0
- package/build/system/seed/tool-manifests-machines.js +88 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/events.d.ts +6 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/machines-view.d.ts +159 -0
- package/build/types/machines-view.js +7 -0
- package/build/types/machines.d.ts +4 -0
- package/dashboard/dist/assets/{AdminDashboard-DlItqxFS.js → AdminDashboard-Bm7VG65H.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-DlItqxFS.js.map → AdminDashboard-Bm7VG65H.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-BUmT4a7n.js → AgentConfigPage-aJF7Sdbc.js} +6 -6
- package/dashboard/dist/assets/{AgentConfigPage-BUmT4a7n.js.map → AgentConfigPage-aJF7Sdbc.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-Cww8rlBi.js → AgentDetailPage-GwwghhxT.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-Cww8rlBi.js.map → AgentDetailPage-GwwghhxT.js.map} +1 -1
- package/dashboard/dist/assets/AgentsPage-DVvCzZyg.js +2 -0
- package/dashboard/dist/assets/{AgentsPage-C9UuUKhr.js.map → AgentsPage-DVvCzZyg.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-Cxmcji_K.js +2 -0
- package/dashboard/dist/assets/{AvailableEscalationsPage-DrvfXz70.js.map → AvailableEscalationsPage-Cxmcji_K.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-CDq9XRMp.js → BotPicker-CofRxk4L.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-CDq9XRMp.js.map → BotPicker-CofRxk4L.js.map} +1 -1
- package/dashboard/dist/assets/{BulkAssignModal-jsvqtrEA.js → BulkAssignModal-BARWuz4L.js} +2 -2
- package/dashboard/dist/assets/{BulkAssignModal-jsvqtrEA.js.map → BulkAssignModal-BARWuz4L.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-CSCrI6B1.js → CapabilitiesPage-BJAWVNEx.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-CSCrI6B1.js.map → CapabilitiesPage-BJAWVNEx.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-uD9mn-CQ.js → CollapsibleSection-CtgoaG75.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-uD9mn-CQ.js.map → CollapsibleSection-CtgoaG75.js.map} +1 -1
- package/dashboard/dist/assets/{ConfirmDeleteModal-BRmx2hA4.js → ConfirmDeleteModal-BYXXBWs6.js} +2 -2
- package/dashboard/dist/assets/{ConfirmDeleteModal-BRmx2hA4.js.map → ConfirmDeleteModal-BYXXBWs6.js.map} +1 -1
- package/dashboard/dist/assets/{ConnectedAppsPage-DlMzE5a-.js → ConnectedAppsPage-D0JkzWa7.js} +2 -2
- package/dashboard/dist/assets/{ConnectedAppsPage-DlMzE5a-.js.map → ConnectedAppsPage-D0JkzWa7.js.map} +1 -1
- package/dashboard/dist/assets/{CountdownTimer-sRuM4eM5.js → CountdownTimer-BcFxdZYN.js} +2 -2
- package/dashboard/dist/assets/{CountdownTimer-sRuM4eM5.js.map → CountdownTimer-BcFxdZYN.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-bLYydwqa.js → CredentialsPage-DPSY20rV.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-bLYydwqa.js.map → CredentialsPage-DPSY20rV.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-D_ATDL2N.js → CronLabel-Dg_Oew_Y.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-D_ATDL2N.js.map → CronLabel-Dg_Oew_Y.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-u1JGfR9G.js → DropZone-Cl_Flb16.js} +2 -2
- package/dashboard/dist/assets/{DropZone-u1JGfR9G.js.map → DropZone-Cl_Flb16.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-groSMfqB.js → ElapsedCell-Dm3G5fK2.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-groSMfqB.js.map → ElapsedCell-Dm3G5fK2.js.map} +1 -1
- package/dashboard/dist/assets/{EntityTimelinePanel-D-F3_A9m.js → EntityTimelinePanel-CrEEP04e.js} +2 -2
- package/dashboard/dist/assets/{EntityTimelinePanel-D-F3_A9m.js.map → EntityTimelinePanel-CrEEP04e.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-Bad6Jrmr.js → EscalationListSchemaPage-wOInXcjs.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-Bad6Jrmr.js.map → EscalationListSchemaPage-wOInXcjs.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-BKi_npI9.js → EscalationSchemaPage-CrIj_g9d.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-BKi_npI9.js.map → EscalationSchemaPage-CrIj_g9d.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationTimeline-DQUUm3b1.js → EscalationTimeline-CeAsl5IT.js} +2 -2
- package/dashboard/dist/assets/{EscalationTimeline-DQUUm3b1.js.map → EscalationTimeline-CeAsl5IT.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationTitleSelect-3mhXh77a.js → EscalationTitleSelect-D8qpCYiy.js} +2 -2
- package/dashboard/dist/assets/{EscalationTitleSelect-3mhXh77a.js.map → EscalationTitleSelect-D8qpCYiy.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-Qaov-uML.js → EscalationsOverview-CvaqVPsr.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-Qaov-uML.js.map → EscalationsOverview-CvaqVPsr.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-V4RF_n58.js → EventTable-Be0JuEl6.js} +2 -2
- package/dashboard/dist/assets/{EventTable-V4RF_n58.js.map → EventTable-Be0JuEl6.js.map} +1 -1
- package/dashboard/dist/assets/FilterBar-BVyAjIgv.js +2 -0
- package/dashboard/dist/assets/FilterBar-BVyAjIgv.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-Djslhf96.js → GraphInvokePage-D_QigJeD.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-Djslhf96.js.map → GraphInvokePage-D_QigJeD.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-EjcNxlKx.js → HomePage-C1haeuNy.js} +2 -2
- package/dashboard/dist/assets/{HomePage-EjcNxlKx.js.map → HomePage-C1haeuNy.js.map} +1 -1
- package/dashboard/dist/assets/LinePage-CWD5pv1g.js +5 -0
- package/dashboard/dist/assets/LinePage-CWD5pv1g.js.map +1 -0
- package/dashboard/dist/assets/LinesPage-NqChsKcO.js +3 -0
- package/dashboard/dist/assets/LinesPage-NqChsKcO.js.map +1 -0
- package/dashboard/dist/assets/{ListToolbar-RrEAePOX.js → ListToolbar-BZCNrTh6.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-RrEAePOX.js.map → ListToolbar-BZCNrTh6.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-8gqiL3Vj.js → McpOverview-yJTYnh1z.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-8gqiL3Vj.js.map → McpOverview-yJTYnh1z.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-COyn6Ak5.js → McpQueryDetailPage-ChuLpJ4u.js} +3 -3
- package/dashboard/dist/assets/{McpQueryDetailPage-COyn6Ak5.js.map → McpQueryDetailPage-ChuLpJ4u.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-Dj6Qj72w.js → McpQueryPage-9S-v9soF.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-Dj6Qj72w.js.map → McpQueryPage-9S-v9soF.js.map} +1 -1
- package/dashboard/dist/assets/McpRunDetailPage-BhrcfxD5.js +2 -0
- package/dashboard/dist/assets/{McpRunDetailPage-80fQn9iX.js.map → McpRunDetailPage-BhrcfxD5.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-DaSsI-EM.js → McpRunsPage-C5_i8_Zx.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-DaSsI-EM.js.map → McpRunsPage-C5_i8_Zx.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-C5epAgq6.js → NamespacePill-OTZD2hzt.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-C5epAgq6.js.map → NamespacePill-OTZD2hzt.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-BSu1o2gB.js → OperationsPage-C03CeU5P.js} +2 -2
- package/dashboard/dist/assets/{OperationsPage-BSu1o2gB.js.map → OperationsPage-C03CeU5P.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-HFI4WGIV.js → OperatorDashboard-BzC134Jt.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-HFI4WGIV.js.map → OperatorDashboard-BzC134Jt.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-BSWIspNB.js → PageHeader-Hx8APPwE.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-BSWIspNB.js.map → PageHeader-Hx8APPwE.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-DDjopQ-k.js → PageHeaderWithStats-BEQYYBJv.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-DDjopQ-k.js.map → PageHeaderWithStats-BEQYYBJv.js.map} +1 -1
- package/dashboard/dist/assets/{PersonaDetailPage-D0qlBuGi.js → PersonaDetailPage-D0mY9L6j.js} +2 -2
- package/dashboard/dist/assets/{PersonaDetailPage-D0qlBuGi.js.map → PersonaDetailPage-D0mY9L6j.js.map} +1 -1
- package/dashboard/dist/assets/{PersonasPage-BJClu-5H.js → PersonasPage-FFlm5Eug.js} +2 -2
- package/dashboard/dist/assets/{PersonasPage-BJClu-5H.js.map → PersonasPage-FFlm5Eug.js.map} +1 -1
- package/dashboard/dist/assets/{PortalPage-CucNZn_w.js → PortalPage-Cpc25uO-.js} +2 -2
- package/dashboard/dist/assets/{PortalPage-CucNZn_w.js.map → PortalPage-Cpc25uO-.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-B9wMw0RV.js → ProcessDetailPage-Di2J7KwY.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-B9wMw0RV.js.map → ProcessDetailPage-Di2J7KwY.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-B6k4RpmR.js → ProcessesListPage-BEXwU3Z0.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-B6k4RpmR.js.map → ProcessesListPage-BEXwU3Z0.js.map} +1 -1
- package/dashboard/dist/assets/{ResolverForm-ZVufJ4G-.js → ResolverForm-2ks-3jXC.js} +6 -6
- package/dashboard/dist/assets/{ResolverForm-ZVufJ4G-.js.map → ResolverForm-2ks-3jXC.js.map} +1 -1
- package/dashboard/dist/assets/{RoleDetailPage-CTbq0mz4.js → RoleDetailPage-DqoOk0xc.js} +3 -3
- package/dashboard/dist/assets/{RoleDetailPage-CTbq0mz4.js.map → RoleDetailPage-DqoOk0xc.js.map} +1 -1
- package/dashboard/dist/assets/{RolePill-CoETDpjF.js → RolePill-R5_ctGFy.js} +2 -2
- package/dashboard/dist/assets/{RolePill-CoETDpjF.js.map → RolePill-R5_ctGFy.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-DBKxeam4.js → RolesPage-CPmo8Srr.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-DBKxeam4.js.map → RolesPage-CPmo8Srr.js.map} +1 -1
- package/dashboard/dist/assets/{RosterWidget-BkLAwEYB.js → RosterWidget-D2yovCKw.js} +2 -2
- package/dashboard/dist/assets/{RosterWidget-BkLAwEYB.js.map → RosterWidget-D2yovCKw.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-D2DC4iDf.js → RunAsSelector-CMv96On6.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-D2DC4iDf.js.map → RunAsSelector-CMv96On6.js.map} +1 -1
- package/dashboard/dist/assets/{ScanCodesPage-BlKleaT0.js → ScanCodesPage-CA6cTT-M.js} +2 -2
- package/dashboard/dist/assets/{ScanCodesPage-BlKleaT0.js.map → ScanCodesPage-CA6cTT-M.js.map} +1 -1
- package/dashboard/dist/assets/{ScanSchemeDetailPage-CTW6gofi.js → ScanSchemeDetailPage-DnctN5-H.js} +2 -2
- package/dashboard/dist/assets/{ScanSchemeDetailPage-CTW6gofi.js.map → ScanSchemeDetailPage-DnctN5-H.js.map} +1 -1
- package/dashboard/dist/assets/{ScanStationPage-DPRjSPyT.js → ScanStationPage-Bk8RwkKL.js} +2 -2
- package/dashboard/dist/assets/{ScanStationPage-DPRjSPyT.js.map → ScanStationPage-Bk8RwkKL.js.map} +1 -1
- package/dashboard/dist/assets/{ScopeBadge-CGsTtqFr.js → ScopeBadge-BXZ084nD.js} +2 -2
- package/dashboard/dist/assets/{ScopeBadge-CGsTtqFr.js.map → ScopeBadge-BXZ084nD.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-CkmX3-Gu.js → StickyPagination-B0FSuUgs.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-CkmX3-Gu.js.map → StickyPagination-B0FSuUgs.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-Xm_n510s.js → StreamMessageDetail-BwZEvMpP.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-Xm_n510s.js.map → StreamMessageDetail-BwZEvMpP.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-De48aJan.js → SwimlaneTimeline-BDVQEaMw.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-De48aJan.js.map → SwimlaneTimeline-BDVQEaMw.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-S7UuVT_M.js → TagInput-DjPUDp2M.js} +2 -2
- package/dashboard/dist/assets/{TagInput-S7UuVT_M.js.map → TagInput-DjPUDp2M.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-HycMx3O9.js → TaskDetailPage-BOlv3iGB.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-HycMx3O9.js.map → TaskDetailPage-BOlv3iGB.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-BlHNpYiZ.js → TaskQueuePill-D-4Ynf3K.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-BlHNpYiZ.js.map → TaskQueuePill-D-4Ynf3K.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-OIcFFp1C.js → TasksListPage-e2teY0bD.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-OIcFFp1C.js.map → TasksListPage-e2teY0bD.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-OTC_55oZ.js → TimeAgo-Cvkpf_b3.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-OTC_55oZ.js.map → TimeAgo-Cvkpf_b3.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-CAwIj_AI.js → TimestampCell-B3btAV0N.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-CAwIj_AI.js.map → TimestampCell-B3btAV0N.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-D40uLM_v.js → ToolPill-GsKQ80CO.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-D40uLM_v.js.map → ToolPill-GsKQ80CO.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-8Vf6pfw4.js → ToolTestPanel-B03nebR7.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-8Vf6pfw4.js.map → ToolTestPanel-B03nebR7.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-iZW8rK9L.js → TopicDetailPage-DBRWne3O.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-iZW8rK9L.js.map → TopicDetailPage-DBRWne3O.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-CqiEcj-W.js → TopicsPage-CVe2M9HD.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-CqiEcj-W.js.map → TopicsPage-CVe2M9HD.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-DQFjuoib.js → UserName-B_q_qp6V.js} +2 -2
- package/dashboard/dist/assets/{UserName-DQFjuoib.js.map → UserName-B_q_qp6V.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-Bh_dr9tM.js +2 -0
- package/dashboard/dist/assets/{WorkflowExecutionPage-Di08wCbs.js.map → WorkflowExecutionPage-Bh_dr9tM.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-DJLcK_CM.js → WorkflowPill-D7244fKp.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-DJLcK_CM.js.map → WorkflowPill-D7244fKp.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-Bb8HlrTJ.js → WorkflowsDashboard-DA6Mj7qH.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-Bb8HlrTJ.js.map → WorkflowsDashboard-DA6Mj7qH.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-Dta1kurk.js → WorkflowsOverview-B1kBLj-a.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-Dta1kurk.js.map → WorkflowsOverview-B1kBLj-a.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-CfAajg68.js → YamlWorkflowDetailPage-6GJM8QIX.js} +8 -8
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-CfAajg68.js.map → YamlWorkflowDetailPage-6GJM8QIX.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-CQFUzT4t.js → YamlWorkflowsPage-Bu811C7f.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-CQFUzT4t.js.map → YamlWorkflowsPage-Bu811C7f.js.map} +1 -1
- package/dashboard/dist/assets/{agents-PuftRTB2.js → agents-MPk_GnYz.js} +2 -2
- package/dashboard/dist/assets/{agents-PuftRTB2.js.map → agents-MPk_GnYz.js.map} +1 -1
- package/dashboard/dist/assets/{bots-BmEiPAWY.js → bots-2RihE2Tc.js} +2 -2
- package/dashboard/dist/assets/{bots-BmEiPAWY.js.map → bots-2RihE2Tc.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-CRii4Soa.js → capabilities-CC4iwJ1P.js} +2 -2
- package/dashboard/dist/assets/{capabilities-CRii4Soa.js.map → capabilities-CC4iwJ1P.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-DO_czaYo.js → controlplane-Bmkic8BH.js} +2 -2
- package/dashboard/dist/assets/{controlplane-DO_czaYo.js.map → controlplane-Bmkic8BH.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-CzCquCPh.js → escalation-columns-B0lhUUrQ.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-CzCquCPh.js.map → escalation-columns-B0lhUUrQ.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-items-Cy0ZgJqV.js → escalation-items-7WFpPo46.js} +2 -2
- package/dashboard/dist/assets/{escalation-items-Cy0ZgJqV.js.map → escalation-items-7WFpPo46.js.map} +1 -1
- package/dashboard/dist/assets/index-B9uv-euR.css +1 -0
- package/dashboard/dist/assets/{index-DhSCCpqM.js → index-B_XV1rwu.js} +2 -2
- package/dashboard/dist/assets/{index-DhSCCpqM.js.map → index-B_XV1rwu.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cx8xb-oU.js → index-C2CLfGwL.js} +2 -2
- package/dashboard/dist/assets/{index-Cx8xb-oU.js.map → index-C2CLfGwL.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cga5IW3D.js → index-C3AeFihz.js} +2 -2
- package/dashboard/dist/assets/{index-Cga5IW3D.js.map → index-C3AeFihz.js.map} +1 -1
- package/dashboard/dist/assets/{index-DiaRlFYI.js → index-CBri8fYM.js} +2 -2
- package/dashboard/dist/assets/{index-DiaRlFYI.js.map → index-CBri8fYM.js.map} +1 -1
- package/dashboard/dist/assets/{index-CL_1VpGb.js → index-CRWsB7ZC.js} +2 -2
- package/dashboard/dist/assets/{index-CL_1VpGb.js.map → index-CRWsB7ZC.js.map} +1 -1
- package/dashboard/dist/assets/{index-XntldBd0.js → index-CTOv3G7a.js} +2 -2
- package/dashboard/dist/assets/{index-XntldBd0.js.map → index-CTOv3G7a.js.map} +1 -1
- package/dashboard/dist/assets/{index-CfP2nKRc.js → index-CcArGkzI.js} +3 -3
- package/dashboard/dist/assets/{index-CfP2nKRc.js.map → index-CcArGkzI.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dz6R8-Dw.js → index-CkMHS-hG.js} +25 -25
- package/dashboard/dist/assets/index-CkMHS-hG.js.map +1 -0
- package/dashboard/dist/assets/index-CmsmwCyv.js +2 -0
- package/dashboard/dist/assets/{index-4YaDUcKd.js.map → index-CmsmwCyv.js.map} +1 -1
- package/dashboard/dist/assets/{index-2SM2_onB.js → index-CvJsvqIc.js} +2 -2
- package/dashboard/dist/assets/{index-2SM2_onB.js.map → index-CvJsvqIc.js.map} +1 -1
- package/dashboard/dist/assets/{index-Rwqrflm-.js → index-D-ilRyfy.js} +2 -2
- package/dashboard/dist/assets/{index-Rwqrflm-.js.map → index-D-ilRyfy.js.map} +1 -1
- package/dashboard/dist/assets/{index-CZ7A3AcD.js → index-DSUtfDXx.js} +2 -2
- package/dashboard/dist/assets/{index-CZ7A3AcD.js.map → index-DSUtfDXx.js.map} +1 -1
- package/dashboard/dist/assets/{index-jL2K6us4.js → index-Da6ycKyf.js} +2 -2
- package/dashboard/dist/assets/{index-jL2K6us4.js.map → index-Da6ycKyf.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-CeUqGPuo.js → knowledge-Bpbg20DC.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CeUqGPuo.js.map → knowledge-Bpbg20DC.js.map} +1 -1
- package/dashboard/dist/assets/machines-BqOZZU-3.js +2 -0
- package/dashboard/dist/assets/machines-BqOZZU-3.js.map +1 -0
- package/dashboard/dist/assets/{mcp-Cx96NTeD.js → mcp-CmcUXYce.js} +2 -2
- package/dashboard/dist/assets/{mcp-Cx96NTeD.js.map → mcp-CmcUXYce.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-XAzWl0gm.js → mcp-query-C8buQxN9.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-XAzWl0gm.js.map → mcp-query-C8buQxN9.js.map} +1 -1
- package/dashboard/dist/assets/{personas--4kjwyOw.js → personas-C2n5oDmY.js} +2 -2
- package/dashboard/dist/assets/{personas--4kjwyOw.js.map → personas-C2n5oDmY.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-PgkwvG7f.js → pipelines-RKpSkFz7.js} +2 -2
- package/dashboard/dist/assets/{pipelines-PgkwvG7f.js.map → pipelines-RKpSkFz7.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-CcYxdBa3.js → tasks-Dab4hExW.js} +2 -2
- package/dashboard/dist/assets/{tasks-CcYxdBa3.js.map → tasks-Dab4hExW.js.map} +1 -1
- package/dashboard/dist/assets/{topics-_lik7cNY.js → topics-FTMNP7r3.js} +2 -2
- package/dashboard/dist/assets/{topics-_lik7cNY.js.map → topics-FTMNP7r3.js.map} +1 -1
- package/dashboard/dist/assets/{useDebouncedValue-Bm_AoxVx.js → useDebouncedValue-B_1u1YIk.js} +2 -2
- package/dashboard/dist/assets/{useDebouncedValue-Bm_AoxVx.js.map → useDebouncedValue-B_1u1YIk.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-DgXXKdx5.js → useNamespace-HUeAI7Qb.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-DgXXKdx5.js.map → useNamespace-HUeAI7Qb.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-Dj2qtGSd.js → useYamlActivityEvents-Cax54qHH.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-Dj2qtGSd.js.map → useYamlActivityEvents-Cax54qHH.js.map} +1 -1
- package/dashboard/dist/assets/{validate-FlXCBBT6.js → validate-DxOaV7QS.js} +2 -2
- package/dashboard/dist/assets/{validate-FlXCBBT6.js.map → validate-DxOaV7QS.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-DUYcKTjP.js → vendor-icons-fxBvvHBi.js} +187 -182
- package/dashboard/dist/assets/vendor-icons-fxBvvHBi.js.map +1 -0
- package/dashboard/dist/assets/{yaml-workflows-B0_JxRYQ.js → yaml-workflows-BgY1XHJx.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-B0_JxRYQ.js.map → yaml-workflows-BgY1XHJx.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/architecture.md +1 -0
- package/docs/code-owned-configuration.md +6 -0
- package/docs/dashboard.md +1 -0
- package/docs/events.md +34 -1
- package/docs/machines.md +93 -16
- package/docs/mcp.md +19 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/AgentsPage-C9UuUKhr.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-DrvfXz70.js +0 -2
- package/dashboard/dist/assets/FilterBar-BygWrnB1.js +0 -2
- package/dashboard/dist/assets/FilterBar-BygWrnB1.js.map +0 -1
- package/dashboard/dist/assets/McpRunDetailPage-80fQn9iX.js +0 -2
- package/dashboard/dist/assets/WorkflowExecutionPage-Di08wCbs.js +0 -2
- package/dashboard/dist/assets/index-4YaDUcKd.js +0 -2
- package/dashboard/dist/assets/index-CnU7Jjz3.css +0 -1
- package/dashboard/dist/assets/index-Dz6R8-Dw.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-DUYcKTjP.js.map +0 -1
package/README.md
CHANGED
|
@@ -63,6 +63,12 @@ The `reviewer` role owns a versioned form schema. The platform renders it as the
|
|
|
63
63
|
|
|
64
64
|
Two surfaces, one model. A `proxyActivity` targets a machine: call it, get a result. `conditional` targets the external world — a reviewer, an operator, a factory cell. It suspends the workflow and writes a single escalation row carrying everything needed to route the work: the `role` that should act, its `type` and `priority`, and any `metadata` to display or filter on. People work that row through an RBAC-scoped surface — find it, claim it, resolve it — from the dashboard, the API, or MCP, and resolving it resumes the workflow exactly where it paused.
|
|
65
65
|
|
|
66
|
+
Three kinds of resolver answer the same row:
|
|
67
|
+
|
|
68
|
+
- **People**, on the [dashboard](https://github.com/hotmeshio/long-tail/blob/main/docs/dashboard.md): the role's versioned form is their UI.
|
|
69
|
+
- **Systems and agents**, over the [API](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/escalations.md) and MCP: the same find, claim and resolve verbs, as a service account.
|
|
70
|
+
- **Machines**, over [MQTT](https://github.com/hotmeshio/long-tail/blob/main/docs/machines.md): a line's gateway claims and resolves as each machine, and the protocol lands on the row as data; the dashboard's Lines view watches the wire, and lines are declared in code or through the API.
|
|
71
|
+
|
|
66
72
|
Activities are plain functions:
|
|
67
73
|
|
|
68
74
|
```typescript
|
|
@@ -76,7 +82,7 @@ export async function analyzeContent(content: string) {
|
|
|
76
82
|
|
|
77
83
|
The non-deterministic queue has qualities the deterministic one never needed:
|
|
78
84
|
|
|
79
|
-
- **RBAC is intrinsic.** The queue is a role; permission lives in the queue itself. Any member answers — a person in the dashboard, a service account through the API — and the workflow is indifferent to which.
|
|
85
|
+
- **RBAC is intrinsic.** The queue is a role; permission lives in the queue itself. Any member answers — a person in the dashboard, a service account through the API, a machine over MQTT — and the workflow is indifferent to which.
|
|
80
86
|
- **The form is data.** A versioned JSON Schema on the role renders the resolver's UI — fields, validation, conditionals, layout — with zero frontend code. Assign an escalation to one named user with self scope and you have a just-in-time form: one person, one item, one auditable edition.
|
|
81
87
|
- **Claims are locks.** A claim is a TTL window with an extend prompt before it lapses and a resolve guard behind it — the platform rejects a submission against an expired claim atomically, in the same statement that settles the row. Stale work cannot land.
|
|
82
88
|
- **Cancel is home.** Cancellation is a first-class settlement, not an exception. When the pressure no longer applies, the waiting flow sees `null` and reconciles to a modeled resting state. Handled is broader than resolved — timeout, cancel, and hop-onward are all endings the loop was built to reach.
|
|
@@ -170,6 +176,7 @@ const lt = await start({
|
|
|
170
176
|
telemetry: { honeycomb: { apiKey: process.env.HNY } },
|
|
171
177
|
logging: { pino: { level: 'info' } },
|
|
172
178
|
maintenance: true,
|
|
179
|
+
machines: { lines: [Machines.loadLine('/etc/line.json')] }, // rows resolved over MQTT; the dashboard gains a Lines view
|
|
173
180
|
});
|
|
174
181
|
```
|
|
175
182
|
|
|
@@ -246,7 +253,7 @@ All modes share PostgreSQL and scale independently. See [Cloud Deployment](https
|
|
|
246
253
|
| [Schema Enforcement](https://github.com/hotmeshio/long-tail/blob/main/docs/schema-enforcement.md) | form_schema as an enforced API contract on every resolve surface |
|
|
247
254
|
| [Code-Owned Configuration](https://github.com/hotmeshio/long-tail/blob/main/docs/code-owned-configuration.md) | `configSource` — declared config compared and applied at every boot |
|
|
248
255
|
| [Faceted Routing](https://github.com/hotmeshio/long-tail/blob/main/docs/faceted-routing.md) | Query and atomically claim the queue by facets; dispatcher pattern |
|
|
249
|
-
| [Machines](https://github.com/hotmeshio/long-tail/blob/main/docs/machines.md) | `start({ machines })`: machines resolve escalation rows over MQTT, as people do with forms |
|
|
256
|
+
| [Machines](https://github.com/hotmeshio/long-tail/blob/main/docs/machines.md) | `start({ machines })`: machines resolve escalation rows over MQTT, as people do with forms; the Lines view, the line registry API and the machines MCP server |
|
|
250
257
|
| [SDK](https://github.com/hotmeshio/long-tail/blob/main/docs/sdk.md) | Embedded usage, `createClient`, event subscriptions |
|
|
251
258
|
| [Architecture](https://github.com/hotmeshio/long-tail/blob/main/docs/architecture.md) | Project structure, conventions, discovery |
|
|
252
259
|
| [Cloud](https://github.com/hotmeshio/long-tail/blob/main/docs/cloud.md) | AWS ECS, GCP Cloud Run, Docker |
|
package/build/api/settings.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ import type { LTApiResult } from '../types/sdk';
|
|
|
8
8
|
* environment block (long-tail + HotMesh versions, Node env/version).
|
|
9
9
|
*
|
|
10
10
|
* @param req — the incoming HTTP request, used to derive NATS WS URL from headers when proxying
|
|
11
|
-
*
|
|
11
|
+
* `machines` is present only when `start({ machines })` runs a surface; it carries the
|
|
12
|
+
* number of lines this replica runs and nothing else, since the settings are public.
|
|
13
|
+
*
|
|
14
|
+
* @returns `{ status: 200, data: { telemetry, escalation, events, auth, ai, features, search, branding, display, environment, machines? } }`
|
|
12
15
|
*/
|
|
13
16
|
export declare function getSettings(req?: IncomingMessage): Promise<LTApiResult>;
|
package/build/api/settings.js
CHANGED
|
@@ -12,6 +12,7 @@ const search_1 = require("../modules/search");
|
|
|
12
12
|
const branding_1 = require("../modules/branding");
|
|
13
13
|
const oauth_server_1 = require("../modules/oauth-server");
|
|
14
14
|
const display_1 = require("../modules/display");
|
|
15
|
+
const machines_1 = require("../modules/machines");
|
|
15
16
|
const version_1 = require("../modules/version");
|
|
16
17
|
const sso_1 = require("../modules/sso");
|
|
17
18
|
const defaults_1 = require("../modules/defaults");
|
|
@@ -43,7 +44,10 @@ function resolveNatsWsUrl(adapter, req) {
|
|
|
43
44
|
* environment block (long-tail + HotMesh versions, Node env/version).
|
|
44
45
|
*
|
|
45
46
|
* @param req — the incoming HTTP request, used to derive NATS WS URL from headers when proxying
|
|
46
|
-
*
|
|
47
|
+
* `machines` is present only when `start({ machines })` runs a surface; it carries the
|
|
48
|
+
* number of lines this replica runs and nothing else, since the settings are public.
|
|
49
|
+
*
|
|
50
|
+
* @returns `{ status: 200, data: { telemetry, escalation, events, auth, ai, features, search, branding, display, environment, machines? } }`
|
|
47
51
|
*/
|
|
48
52
|
async function getSettings(req) {
|
|
49
53
|
try {
|
|
@@ -55,6 +59,7 @@ async function getSettings(req) {
|
|
|
55
59
|
// connect via NATS instead of Socket.IO.
|
|
56
60
|
const forceNats = config_1.config.EVENT_TRANSPORT === 'nats';
|
|
57
61
|
const transport = forceNats && natsAdapter ? 'nats' : hasSocketIO ? 'socketio' : natsAdapter ? 'nats' : 'none';
|
|
62
|
+
const machines = (0, machines_1.getMachineSurface)();
|
|
58
63
|
return {
|
|
59
64
|
status: 200,
|
|
60
65
|
data: {
|
|
@@ -97,6 +102,7 @@ async function getSettings(req) {
|
|
|
97
102
|
nodeVersion: process.version,
|
|
98
103
|
eventTransport: transport,
|
|
99
104
|
},
|
|
105
|
+
...(machines ? { machines: { declared: machines.describeLines().length } } : {}),
|
|
100
106
|
},
|
|
101
107
|
};
|
|
102
108
|
}
|
package/build/index.js
CHANGED
|
@@ -283,15 +283,20 @@ async function main() {
|
|
|
283
283
|
: undefined,
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
|
-
/**
|
|
286
|
+
/**
|
|
287
|
+
* The configured lines (MACHINE_LINE_CONFIG, one path or several), spoken to by
|
|
288
|
+
* the generic codec, or for one line by the codec MACHINE_CODEC_MODULE names (its `createCodec(line)`).
|
|
289
|
+
*/
|
|
287
290
|
function machinesFromEnv(lineConfig, codecModule) {
|
|
288
|
-
const
|
|
291
|
+
const lines = (0, line_1.loadLines)(lineConfig);
|
|
289
292
|
if (!codecModule)
|
|
290
|
-
return { lines
|
|
293
|
+
return { lines };
|
|
294
|
+
if (lines.length > 1)
|
|
295
|
+
throw new Error('MACHINE_CODEC_MODULE speaks for one line; MACHINE_LINE_CONFIG names several');
|
|
291
296
|
const { createCodec } = require(path.resolve(codecModule));
|
|
292
297
|
if (typeof createCodec !== 'function')
|
|
293
298
|
throw new Error(`MACHINE_CODEC_MODULE=${codecModule} exports no createCodec(line)`);
|
|
294
|
-
return { lines
|
|
299
|
+
return { lines, codec: createCodec(lines[0]) };
|
|
295
300
|
}
|
|
296
301
|
// Run server when executed directly
|
|
297
302
|
if (require.main === module) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes multi-statement config writes (role-list replaces, escalation
|
|
3
|
+
* target replaces, line declarations) across concurrently booting containers
|
|
4
|
+
* and live API writes. Single-statement upserts are individually race-safe;
|
|
5
|
+
* the lock exists for the replace transactions and keeps boot reports
|
|
6
|
+
* coherent. Distinct from the migration lock so a slow migration on one
|
|
7
|
+
* container never blocks another's config pass behind two locks at once.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CONFIG_APPLY_LOCK_ID = 8675310;
|
|
10
|
+
export declare function withConfigLock<T>(fn: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_APPLY_LOCK_ID = void 0;
|
|
4
|
+
exports.withConfigLock = withConfigLock;
|
|
5
|
+
const index_1 = require("./index");
|
|
6
|
+
/**
|
|
7
|
+
* Serializes multi-statement config writes (role-list replaces, escalation
|
|
8
|
+
* target replaces, line declarations) across concurrently booting containers
|
|
9
|
+
* and live API writes. Single-statement upserts are individually race-safe;
|
|
10
|
+
* the lock exists for the replace transactions and keeps boot reports
|
|
11
|
+
* coherent. Distinct from the migration lock so a slow migration on one
|
|
12
|
+
* container never blocks another's config pass behind two locks at once.
|
|
13
|
+
*/
|
|
14
|
+
exports.CONFIG_APPLY_LOCK_ID = 8675310;
|
|
15
|
+
async function withConfigLock(fn) {
|
|
16
|
+
const pool = (0, index_1.getPool)();
|
|
17
|
+
const client = await pool.connect();
|
|
18
|
+
try {
|
|
19
|
+
await client.query(`SELECT pg_advisory_lock(${exports.CONFIG_APPLY_LOCK_ID})`);
|
|
20
|
+
return await fn();
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await client
|
|
24
|
+
.query(`SELECT pg_advisory_unlock(${exports.CONFIG_APPLY_LOCK_ID})`)
|
|
25
|
+
.catch(() => { });
|
|
26
|
+
client.release();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Migration 041: The machine line registry
|
|
2
|
+
--
|
|
3
|
+
-- One row per line a deployment controls: the line definition as the
|
|
4
|
+
-- gateway reads it (site, line, machines in route order), who declared it,
|
|
5
|
+
-- and a version that advances on every change. Lines declared in code
|
|
6
|
+
-- (start({ machines })) are applied here at boot under the configSource
|
|
7
|
+
-- rules; lines declared through PUT /api/machines/lines live here alone.
|
|
8
|
+
-- Every replica reads the registry to know which lines to contend for, and
|
|
9
|
+
-- the Lines index reads it with the pending rows that say how each line is.
|
|
10
|
+
-- search_text is the service's lowercase projection of site, line, machine
|
|
11
|
+
-- ids, processes, roles and addresses, for the index's search.
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS lt_machine_lines (
|
|
14
|
+
site_id TEXT NOT NULL,
|
|
15
|
+
line_id TEXT NOT NULL,
|
|
16
|
+
definition JSONB NOT NULL,
|
|
17
|
+
source TEXT NOT NULL CHECK (source IN ('code', 'api')),
|
|
18
|
+
version INT NOT NULL DEFAULT 1,
|
|
19
|
+
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
20
|
+
search_text TEXT NOT NULL,
|
|
21
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
22
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
23
|
+
PRIMARY KEY (site_id, line_id)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS lt_machine_lines_site_idx ON lt_machine_lines (site_id, line_id);
|
|
@@ -41,13 +41,15 @@ exports.holderView = holderView;
|
|
|
41
41
|
/**
|
|
42
42
|
* What a browser receives of an event. Builders receive events whole. Anyone
|
|
43
43
|
* else receives enough to route and refresh: escalation events keep their
|
|
44
|
-
* routing fields,
|
|
45
|
-
*
|
|
44
|
+
* routing fields, machine wire events keep everything but the raw payload,
|
|
45
|
+
* and every other event arrives without `data` and `milestones`. Full
|
|
46
|
+
* records are read through the scoped API, by id.
|
|
46
47
|
*
|
|
47
48
|
* Server-side consumers (agent triggers, callbacks) are unaffected: this view
|
|
48
49
|
* applies only where events leave for a browser.
|
|
49
50
|
*/
|
|
50
51
|
const ESCALATION_SUBJECT = 'system.escalation.';
|
|
52
|
+
const MACHINE_WIRE_SUBJECT = 'machine.wire.';
|
|
51
53
|
const ACTIVE_ACCOUNT = 'active';
|
|
52
54
|
/** Escalation event fields a browser needs to route a refresh and follow a claim. */
|
|
53
55
|
exports.BROWSER_ESCALATION_FIELDS = [
|
|
@@ -69,6 +71,10 @@ function eventForViewer(event, viewer) {
|
|
|
69
71
|
}
|
|
70
72
|
return { ...rest, data: kept };
|
|
71
73
|
}
|
|
74
|
+
if (typeof event.type === 'string' && event.type.startsWith(MACHINE_WIRE_SUBJECT) && data) {
|
|
75
|
+
const { payload: _payload, ...wire } = data;
|
|
76
|
+
return { ...rest, data: wire };
|
|
77
|
+
}
|
|
72
78
|
return rest;
|
|
73
79
|
}
|
|
74
80
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LTMilestone } from '../../types';
|
|
2
|
+
import type { WireEvent } from '../../types/machines-view';
|
|
2
3
|
/**
|
|
3
4
|
* Publish a milestone event. Called from handleCompletion (workflow
|
|
4
5
|
* interceptor), ltCompleteTask (orchestrator activity), and the
|
|
@@ -124,3 +125,35 @@ export declare function publishWorkflowEvent(params: {
|
|
|
124
125
|
status: string;
|
|
125
126
|
data?: Record<string, any>;
|
|
126
127
|
}): Promise<void>;
|
|
128
|
+
/** The subject family of every message a line's gateway reads or sends. */
|
|
129
|
+
export declare const MACHINE_WIRE_TYPE = "machine.wire";
|
|
130
|
+
/** The subject family of the gateway log. */
|
|
131
|
+
export declare const MACHINE_LOG_TYPE = "machine.log";
|
|
132
|
+
/** The machine token of a wire event from an address no station claims. */
|
|
133
|
+
export declare const MACHINE_UNKNOWN_TOKEN = "unknown";
|
|
134
|
+
/**
|
|
135
|
+
* Publish one wire record from a line's gateway.
|
|
136
|
+
* Subject: machine.wire.{site}.{line}.{machineId|unknown}.{in|out}
|
|
137
|
+
*/
|
|
138
|
+
export declare function publishMachineWireEvent(event: WireEvent): Promise<void>;
|
|
139
|
+
/** The log event as one subject token: `machine_gateway.dispatch.accepted` becomes `dispatch-accepted`. */
|
|
140
|
+
export declare function machineLogToken(event: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* Publish one gateway log entry.
|
|
143
|
+
* Subject: machine.log.{site}.{line}.{event}
|
|
144
|
+
*/
|
|
145
|
+
export declare function publishMachineLogEvent(params: {
|
|
146
|
+
siteId: string;
|
|
147
|
+
lineId: string;
|
|
148
|
+
level: 'info' | 'warn' | 'error';
|
|
149
|
+
event: string;
|
|
150
|
+
context?: Record<string, unknown>;
|
|
151
|
+
}): Promise<void>;
|
|
152
|
+
/** A line declared or retired in the registry; every replica reads the row again and follows. */
|
|
153
|
+
export declare function publishMachineLineEvent(params: {
|
|
154
|
+
siteId: string;
|
|
155
|
+
lineId: string;
|
|
156
|
+
source: 'code' | 'api';
|
|
157
|
+
version: number;
|
|
158
|
+
action: 'declared' | 'retired';
|
|
159
|
+
}): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ANNOUNCEMENT_TOPIC = void 0;
|
|
3
|
+
exports.MACHINE_UNKNOWN_TOKEN = exports.MACHINE_LOG_TYPE = exports.MACHINE_WIRE_TYPE = exports.ANNOUNCEMENT_TOPIC = void 0;
|
|
4
4
|
exports.publishMilestoneEvent = publishMilestoneEvent;
|
|
5
5
|
exports.publishTaskEvent = publishTaskEvent;
|
|
6
6
|
exports.publishEscalationEvent = publishEscalationEvent;
|
|
@@ -10,6 +10,10 @@ exports.publishKnowledgeEvent = publishKnowledgeEvent;
|
|
|
10
10
|
exports.publishFileEvent = publishFileEvent;
|
|
11
11
|
exports.publishAgentEvent = publishAgentEvent;
|
|
12
12
|
exports.publishWorkflowEvent = publishWorkflowEvent;
|
|
13
|
+
exports.publishMachineWireEvent = publishMachineWireEvent;
|
|
14
|
+
exports.machineLogToken = machineLogToken;
|
|
15
|
+
exports.publishMachineLogEvent = publishMachineLogEvent;
|
|
16
|
+
exports.publishMachineLineEvent = publishMachineLineEvent;
|
|
13
17
|
const index_1 = require("./index");
|
|
14
18
|
const matching_1 = require("./matching");
|
|
15
19
|
const logger_1 = require("../logger");
|
|
@@ -202,3 +206,50 @@ function publishWorkflowEvent(params) {
|
|
|
202
206
|
timestamp: new Date().toISOString(),
|
|
203
207
|
});
|
|
204
208
|
}
|
|
209
|
+
/** The subject family of every message a line's gateway reads or sends. */
|
|
210
|
+
exports.MACHINE_WIRE_TYPE = 'machine.wire';
|
|
211
|
+
/** The subject family of the gateway log. */
|
|
212
|
+
exports.MACHINE_LOG_TYPE = 'machine.log';
|
|
213
|
+
/** The machine token of a wire event from an address no station claims. */
|
|
214
|
+
exports.MACHINE_UNKNOWN_TOKEN = 'unknown';
|
|
215
|
+
const MACHINE_SOURCE = 'machine-gateway';
|
|
216
|
+
/**
|
|
217
|
+
* Publish one wire record from a line's gateway.
|
|
218
|
+
* Subject: machine.wire.{site}.{line}.{machineId|unknown}.{in|out}
|
|
219
|
+
*/
|
|
220
|
+
function publishMachineWireEvent(event) {
|
|
221
|
+
const machine = event.machineId ? (0, matching_1.sanitizeSubjectToken)(event.machineId) : exports.MACHINE_UNKNOWN_TOKEN;
|
|
222
|
+
return fireAndForget({
|
|
223
|
+
type: `${exports.MACHINE_WIRE_TYPE}.${(0, matching_1.sanitizeSubjectToken)(event.siteId)}.${(0, matching_1.sanitizeSubjectToken)(event.lineId)}.${machine}.${event.direction}`,
|
|
224
|
+
source: MACHINE_SOURCE,
|
|
225
|
+
data: event,
|
|
226
|
+
timestamp: event.at,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const GATEWAY_LOG_PREFIX = 'machine_gateway.';
|
|
230
|
+
/** The log event as one subject token: `machine_gateway.dispatch.accepted` becomes `dispatch-accepted`. */
|
|
231
|
+
function machineLogToken(event) {
|
|
232
|
+
return (0, matching_1.sanitizeSubjectToken)(event.startsWith(GATEWAY_LOG_PREFIX) ? event.slice(GATEWAY_LOG_PREFIX.length) : event);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Publish one gateway log entry.
|
|
236
|
+
* Subject: machine.log.{site}.{line}.{event}
|
|
237
|
+
*/
|
|
238
|
+
function publishMachineLogEvent(params) {
|
|
239
|
+
return fireAndForget({
|
|
240
|
+
type: `${exports.MACHINE_LOG_TYPE}.${(0, matching_1.sanitizeSubjectToken)(params.siteId)}.${(0, matching_1.sanitizeSubjectToken)(params.lineId)}.${machineLogToken(params.event)}`,
|
|
241
|
+
source: MACHINE_SOURCE,
|
|
242
|
+
data: { level: params.level, event: params.event, siteId: params.siteId, lineId: params.lineId, ...(params.context ?? {}) },
|
|
243
|
+
timestamp: new Date().toISOString(),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const MACHINE_LINE_TYPE = 'machine.line';
|
|
247
|
+
/** A line declared or retired in the registry; every replica reads the row again and follows. */
|
|
248
|
+
function publishMachineLineEvent(params) {
|
|
249
|
+
return fireAndForget({
|
|
250
|
+
type: `${MACHINE_LINE_TYPE}.${(0, matching_1.sanitizeSubjectToken)(params.siteId)}.${(0, matching_1.sanitizeSubjectToken)(params.lineId)}.${params.action}`,
|
|
251
|
+
source: MACHINE_SOURCE,
|
|
252
|
+
data: { ...params },
|
|
253
|
+
timestamp: new Date().toISOString(),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The line: site, line and the machines on it, in route order, read from a
|
|
3
|
-
* JSON file
|
|
3
|
+
* JSON file or passed in by the host. MACHINE_LINE_CONFIG names one file, or
|
|
4
|
+
* several separated by commas.
|
|
4
5
|
*/
|
|
5
6
|
import type { LineDef, MachineDef, MachineIdent, MaterialDef, MeasurementDef } from '../../types/machines';
|
|
6
7
|
export type { LineDef, MachineDef, MaterialDef, MeasurementDef };
|
|
@@ -22,6 +23,8 @@ export type CarrierRole = typeof CARRIER_ROLE[keyof typeof CARRIER_ROLE];
|
|
|
22
23
|
export declare function validateLine(line: LineDef): LineDef;
|
|
23
24
|
/** The line at `path` (JSON), or `fallback` when no path is given. */
|
|
24
25
|
export declare function loadLine(path: string | undefined, fallback?: LineDef): LineDef;
|
|
26
|
+
/** Every line in `paths`, a comma-separated list of JSON files (MACHINE_LINE_CONFIG); each site and line once. */
|
|
27
|
+
export declare function loadLines(paths: string | undefined, fallback?: LineDef): LineDef[];
|
|
25
28
|
export declare function identOf(line: LineDef, machineId: string): MachineIdent;
|
|
26
29
|
export declare function machineById(line: LineDef, machineId: string): MachineDef;
|
|
27
30
|
/** The broker identity of each machine whose address the line declares, {site}-{line}-{address}. */
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* The line: site, line and the machines on it, in route order, read from a
|
|
4
|
-
* JSON file
|
|
4
|
+
* JSON file or passed in by the host. MACHINE_LINE_CONFIG names one file, or
|
|
5
|
+
* several separated by commas.
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
exports.DEFAULT_OPERATOR_ROLE = exports.DEFAULT_EXIT_ROLE = exports.CARRIER_ROLE = exports.ADDRESSING = void 0;
|
|
8
9
|
exports.learnsAddress = learnsAddress;
|
|
9
10
|
exports.validateLine = validateLine;
|
|
10
11
|
exports.loadLine = loadLine;
|
|
12
|
+
exports.loadLines = loadLines;
|
|
11
13
|
exports.identOf = identOf;
|
|
12
14
|
exports.machineById = machineById;
|
|
13
15
|
exports.clientIds = clientIds;
|
|
@@ -68,6 +70,21 @@ function loadLine(path, fallback) {
|
|
|
68
70
|
}
|
|
69
71
|
return validateLine(JSON.parse((0, fs_1.readFileSync)(path, 'utf8')));
|
|
70
72
|
}
|
|
73
|
+
/** Every line in `paths`, a comma-separated list of JSON files (MACHINE_LINE_CONFIG); each site and line once. */
|
|
74
|
+
function loadLines(paths, fallback) {
|
|
75
|
+
const list = (paths ?? '').split(',').map((p) => p.trim()).filter(Boolean);
|
|
76
|
+
if (!list.length)
|
|
77
|
+
return [loadLine(undefined, fallback)];
|
|
78
|
+
const lines = list.map((p) => loadLine(p));
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const key = `${line.siteId}/${line.lineId}`;
|
|
82
|
+
if (seen.has(key))
|
|
83
|
+
throw new Error(`line ${key} is declared twice in MACHINE_LINE_CONFIG`);
|
|
84
|
+
seen.add(key);
|
|
85
|
+
}
|
|
86
|
+
return lines;
|
|
87
|
+
}
|
|
71
88
|
function identOf(line, machineId) {
|
|
72
89
|
const address = line.machines.find((m) => m.machineId === machineId)?.address;
|
|
73
90
|
return { siteId: line.siteId, lineId: line.lineId, machineId, ...(address ? { address } : {}) };
|
|
@@ -71,6 +71,12 @@ export interface LineCodec {
|
|
|
71
71
|
* no longer lists has cleared.
|
|
72
72
|
*/
|
|
73
73
|
readonly retainedAlarms?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Commands of the machines' own, beyond the line protocol's control ops, that a machine-command row
|
|
76
|
+
* may ask for. Each is sent through `encode` and the row resolves with the reply. Every entry is a
|
|
77
|
+
* non-empty string; the gateway refuses a codec whose entries are not.
|
|
78
|
+
*/
|
|
79
|
+
readonly rowOps?: readonly string[];
|
|
74
80
|
/** One wire message as zero or more line-protocol messages; empty when the topic is not a machine's. */
|
|
75
81
|
decode(topic: string, payload: string, retained?: boolean): Inspection[];
|
|
76
82
|
/** The command as wire messages; none when the machines have no such command, and the gateway then sends nothing. */
|
|
@@ -81,6 +87,8 @@ export interface LineCodec {
|
|
|
81
87
|
*/
|
|
82
88
|
receipt?(ident: MachineIdent, messageIds: string[]): WireMessage[];
|
|
83
89
|
}
|
|
90
|
+
/** The codec's own row ops, each a non-empty string; anything else is a misconfigured codec and throws. */
|
|
91
|
+
export declare function declaredRowOps(codec: Pick<LineCodec, 'rowOps'>): readonly string[];
|
|
84
92
|
export declare const DISPATCH_ON: {
|
|
85
93
|
readonly CREATED: "created";
|
|
86
94
|
readonly ARRIVAL: "arrival";
|
|
@@ -8,12 +8,21 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.GENERIC_CHANNELS = exports.RECEIPT_FROM = exports.DISPATCH_ON = void 0;
|
|
11
|
+
exports.declaredRowOps = declaredRowOps;
|
|
11
12
|
exports.topicOf = topicOf;
|
|
12
13
|
exports.genericCodec = genericCodec;
|
|
13
14
|
const constants_1 = require("./constants");
|
|
14
15
|
const contract_1 = require("./contract");
|
|
15
16
|
const envelope_1 = require("./envelope");
|
|
16
17
|
const topics_1 = require("./topics");
|
|
18
|
+
/** The codec's own row ops, each a non-empty string; anything else is a misconfigured codec and throws. */
|
|
19
|
+
function declaredRowOps(codec) {
|
|
20
|
+
const ops = codec.rowOps ?? [];
|
|
21
|
+
if (!Array.isArray(ops) || ops.some((op) => typeof op !== 'string' || !op)) {
|
|
22
|
+
throw new Error('codec.rowOps must be a list of non-empty strings');
|
|
23
|
+
}
|
|
24
|
+
return ops;
|
|
25
|
+
}
|
|
17
26
|
exports.DISPATCH_ON = { CREATED: 'created', ARRIVAL: 'arrival' };
|
|
18
27
|
/** Which job event counts as the receiver's receipt. */
|
|
19
28
|
exports.RECEIPT_FROM = { ARRIVED: 'arrived', BOUND: 'bound', STARTED: 'started' };
|
|
@@ -93,7 +93,7 @@ export declare const OP: {
|
|
|
93
93
|
readonly CANCEL: "cancel";
|
|
94
94
|
/** Take work: begin or continue the queue. */
|
|
95
95
|
readonly START: "start";
|
|
96
|
-
/** Finish the current unit, then take no more; the queue is kept. */
|
|
96
|
+
/** Finish the current unit, then take no more; the queue is kept. With `args.jobId`, stop that job at a safe point and hold its units. */
|
|
97
97
|
readonly STOP: "stop";
|
|
98
98
|
/** Halt at the next safe point, holding the current unit; nothing new starts. */
|
|
99
99
|
readonly PAUSE: "pause";
|
|
@@ -77,7 +77,7 @@ exports.OP = {
|
|
|
77
77
|
CANCEL: 'cancel',
|
|
78
78
|
/** Take work: begin or continue the queue. */
|
|
79
79
|
START: 'start',
|
|
80
|
-
/** Finish the current unit, then take no more; the queue is kept. */
|
|
80
|
+
/** Finish the current unit, then take no more; the queue is kept. With `args.jobId`, stop that job at a safe point and hold its units. */
|
|
81
81
|
STOP: 'stop',
|
|
82
82
|
/** Halt at the next safe point, holding the current unit; nothing new starts. */
|
|
83
83
|
PAUSE: 'pause',
|
|
@@ -35,7 +35,7 @@ const ENVELOPE = shape({
|
|
|
35
35
|
});
|
|
36
36
|
const RECIPE = { anyOf: [shape({ id: STR, version: STR }), { type: 'null' }] };
|
|
37
37
|
const BODY = {
|
|
38
|
-
[topics_1.MESSAGE_KIND.PRESENCE]: shape({ online: BOOL, software: STR }, ['software']),
|
|
38
|
+
[topics_1.MESSAGE_KIND.PRESENCE]: shape({ online: BOOL, software: STR, sessionId: STR, reason: STR }, ['software', 'sessionId', 'reason']),
|
|
39
39
|
[topics_1.MESSAGE_KIND.SNAPSHOT]: shape({
|
|
40
40
|
bootId: STR, software: STR, mode: oneOf(constants_1.MODE), enabled: BOOL, recipe: RECIPE,
|
|
41
41
|
jobs: { type: 'array', items: shape({ jobId: STR, units: { type: 'array', items: STR }, status: STR }) },
|
|
@@ -60,7 +60,7 @@ const BODY = {
|
|
|
60
60
|
}),
|
|
61
61
|
[topics_1.MESSAGE_KIND.TELEMETRY]: shape({ readings: { type: 'object', additionalProperties: NUM } }),
|
|
62
62
|
[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 }),
|
|
63
|
+
[topics_1.MESSAGE_KIND.REPLY]: shape({ cmdId: STR, status: oneOf(constants_1.REPLY_STATUS), reason: STR, code: STR }, ['code']),
|
|
64
64
|
[topics_1.MESSAGE_KIND.REPORT]: { type: 'object' },
|
|
65
65
|
};
|
|
66
66
|
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The machine broker (Mosquitto), rendered from
|
|
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
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* NATS, where JetStream keeps them.
|
|
8
8
|
*/
|
|
9
9
|
import { type LineCodec } from '../protocol/codec';
|
|
10
|
-
import { type
|
|
10
|
+
import { type Lines } from './site-conf';
|
|
11
11
|
export interface MosquittoConfOptions {
|
|
12
12
|
root: string;
|
|
13
13
|
/** Certificate directory as the broker sees it. */
|
|
@@ -17,11 +17,12 @@ export interface MosquittoConfOptions {
|
|
|
17
17
|
aclFile: string;
|
|
18
18
|
maxPacketBytes?: number;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
/** The broker for one line or several: one listener, one bridge per line. */
|
|
21
|
+
export declare function renderMosquittoConf(lines: Lines, opts: MosquittoConfOptions): string;
|
|
21
22
|
/**
|
|
22
23
|
* The codec's channels decide the topics: a machine writes its events and telemetry and reads its commands.
|
|
23
24
|
* A station whose address the line learns has no user of its own: `pattern` rules give every certificate
|
|
24
25
|
* the channels under its own name (%u), so its certificate's common name must be its address. On a line
|
|
25
26
|
* that also declares addresses, a certificate named for a declared address is denied that station's channels.
|
|
26
27
|
*/
|
|
27
|
-
export declare function renderMosquittoAcl(
|
|
28
|
+
export declare function renderMosquittoAcl(lines: Lines, codec: Pick<LineCodec, 'root' | 'channels' | 'topicFor'>): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* The machine broker (Mosquitto), rendered from
|
|
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
|
-
|
|
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 ${opts.root}/${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,18 +51,7 @@ use_identity_as_username true
|
|
|
32
51
|
acl_file ${opts.aclFile}
|
|
33
52
|
max_packet_size ${opts.maxPacketBytes ?? 262_144}
|
|
34
53
|
|
|
35
|
-
|
|
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
56
|
/**
|
|
49
57
|
* The codec's channels decide the topics: a machine writes its events and telemetry and reads its commands.
|
|
@@ -51,7 +59,14 @@ topic ${opts.root}/${line.siteId}/${line.lineId}/# out 1
|
|
|
51
59
|
* the channels under its own name (%u), so its certificate's common name must be its address. On a line
|
|
52
60
|
* that also declares addresses, a certificate named for a declared address is denied that station's channels.
|
|
53
61
|
*/
|
|
54
|
-
function renderMosquittoAcl(
|
|
62
|
+
function renderMosquittoAcl(lines, codec) {
|
|
63
|
+
const list = (0, site_conf_1.linesOf)(lines);
|
|
64
|
+
if (!list.length)
|
|
65
|
+
throw new Error('renderMosquittoAcl needs at least one line');
|
|
66
|
+
return `# Rendered from the line definition by site/setup.ts. Do not edit by hand.\n\n${list.map((line) => aclRules(line, codec)).join('\n\n')}\n`;
|
|
67
|
+
}
|
|
68
|
+
/** One line's rules: its stations, its controller, and the shadows a learning line needs. */
|
|
69
|
+
function aclRules(line, codec) {
|
|
55
70
|
const { siteId, lineId } = line;
|
|
56
71
|
const { events, telemetry, commands } = codec.channels;
|
|
57
72
|
const own = (address, channels) => channels.map((c) => (0, codec_1.topicOf)(codec, { siteId, lineId, address }, c));
|
|
@@ -70,7 +85,7 @@ function renderMosquittoAcl(line, codec) {
|
|
|
70
85
|
...commands.map((c) => `topic write ${(0, codec_1.topicOf)(codec, { siteId, lineId }, c)}`),
|
|
71
86
|
`topic read ${codec.root}/${siteId}/${lineId}/#`,
|
|
72
87
|
].join('\n');
|
|
73
|
-
return
|
|
88
|
+
return [...learned, ...shadows, ...machines, controller].join('\n\n');
|
|
74
89
|
}
|
|
75
90
|
/** Deny a declared station's channels to a certificate named for its address, which the patterns would grant. */
|
|
76
91
|
function shadowRules(line, address, topics) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The site's NATS server, rendered from
|
|
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
|
|
@@ -20,4 +20,11 @@ export interface SiteConfOptions {
|
|
|
20
20
|
export declare function bridgeId(line: LineDef): string;
|
|
21
21
|
/** What the gateway may publish: its streams' and its consumer's JetStream requests and acks, never a machine subject. */
|
|
22
22
|
export declare function gatewayPublish(line: LineDef): string[];
|
|
23
|
-
|
|
23
|
+
/** One line, or every line the site runs. */
|
|
24
|
+
export type Lines = LineDef | LineDef[];
|
|
25
|
+
export declare function linesOf(lines: Lines): LineDef[];
|
|
26
|
+
/**
|
|
27
|
+
* The site NATS for one line or several: one bridge user per line, one
|
|
28
|
+
* gateway user allowed every line's streams and subjects.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderSiteConf(lines: Lines, opts: SiteConfOptions): string;
|