@hotmeshio/long-tail 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/build/api/escalations/create.js +45 -0
  2. package/build/api/escalations/index.d.ts +1 -1
  3. package/build/api/escalations/index.js +2 -1
  4. package/build/api/escalations/list.d.ts +3 -0
  5. package/build/api/escalations/list.js +14 -0
  6. package/build/api/roles.d.ts +17 -0
  7. package/build/api/roles.js +46 -0
  8. package/build/bin/ltc.js +1 -0
  9. package/build/lib/cli/commands/escalations.d.ts +1 -0
  10. package/build/lib/cli/commands/escalations.js +2 -0
  11. package/build/lib/db/migrate.js +22 -2
  12. package/build/lib/db/schemas/014_roles_v2.sql +19 -0
  13. package/build/lib/db/schemas/015_role_ops.sql +50 -0
  14. package/build/lib/db/schemas/016_station_metrics_indexes.sql +13 -0
  15. package/build/lib/events/system-events.js +3 -0
  16. package/build/modules/auth.d.ts +9 -0
  17. package/build/modules/auth.js +38 -1
  18. package/build/routes/escalations/list.js +10 -0
  19. package/build/routes/roles.js +16 -5
  20. package/build/scripts/ortho-populate.d.ts +37 -0
  21. package/build/scripts/ortho-populate.js +228 -0
  22. package/build/sdk/index.d.ts +4 -0
  23. package/build/sdk/index.js +2 -0
  24. package/build/services/escalation/client.js +93 -2
  25. package/build/services/escalation/crud.js +18 -9
  26. package/build/services/escalation/metrics-cache.d.ts +14 -0
  27. package/build/services/escalation/metrics-cache.js +53 -0
  28. package/build/services/escalation/queries.d.ts +14 -2
  29. package/build/services/escalation/queries.js +121 -11
  30. package/build/services/escalation/sql.d.ts +29 -2
  31. package/build/services/escalation/sql.js +156 -27
  32. package/build/services/escalation/types.d.ts +17 -0
  33. package/build/services/escalation/types.js +1 -0
  34. package/build/services/mcp/external-server.js +5 -1
  35. package/build/services/orchestrator/condition.d.ts +20 -0
  36. package/build/services/orchestrator/condition.js +20 -0
  37. package/build/services/role/index.d.ts +22 -3
  38. package/build/services/role/index.js +50 -2
  39. package/build/services/role/sql.d.ts +11 -1
  40. package/build/services/role/sql.js +38 -1
  41. package/build/services/role/types.d.ts +32 -0
  42. package/build/system/mcp-servers/admin/escalations.js +8 -7
  43. package/build/system/mcp-servers/admin/index.js +5 -3
  44. package/build/system/mcp-servers/admin/ortho.d.ts +10 -0
  45. package/build/system/mcp-servers/admin/ortho.js +242 -0
  46. package/build/system/mcp-servers/admin/schemas.d.ts +40 -3
  47. package/build/system/mcp-servers/admin/schemas.js +17 -4
  48. package/build/system/mcp-servers/admin/users.js +31 -0
  49. package/build/system/seed/tool-manifests-admin.d.ts +4016 -712
  50. package/build/system/seed/tool-manifests-admin.js +14 -1
  51. package/build/tsconfig.tsbuildinfo +1 -1
  52. package/build/types/escalation.d.ts +3 -1
  53. package/dashboard/dist/assets/{AdminDashboard-C5gfVEeL.js → AdminDashboard-Bg1HgukI.js} +2 -2
  54. package/dashboard/dist/assets/{AdminDashboard-C5gfVEeL.js.map → AdminDashboard-Bg1HgukI.js.map} +1 -1
  55. package/dashboard/dist/assets/{AgentConfigPage-CWEnuiGN.js → AgentConfigPage-HBe_YDU_.js} +6 -6
  56. package/dashboard/dist/assets/{AgentConfigPage-CWEnuiGN.js.map → AgentConfigPage-HBe_YDU_.js.map} +1 -1
  57. package/dashboard/dist/assets/{AgentDetailPage-B6IE27iK.js → AgentDetailPage-BsaiYbwL.js} +4 -4
  58. package/dashboard/dist/assets/{AgentDetailPage-B6IE27iK.js.map → AgentDetailPage-BsaiYbwL.js.map} +1 -1
  59. package/dashboard/dist/assets/AgentsPage-DhN2lIBh.js +2 -0
  60. package/dashboard/dist/assets/{AgentsPage-Blhh2RsK.js.map → AgentsPage-DhN2lIBh.js.map} +1 -1
  61. package/dashboard/dist/assets/AvailableEscalationsPage-_xcWvlIz.js +2 -0
  62. package/dashboard/dist/assets/AvailableEscalationsPage-_xcWvlIz.js.map +1 -0
  63. package/dashboard/dist/assets/{BotPicker-DSozotGk.js → BotPicker-DY6iUVC9.js} +2 -2
  64. package/dashboard/dist/assets/{BotPicker-DSozotGk.js.map → BotPicker-DY6iUVC9.js.map} +1 -1
  65. package/dashboard/dist/assets/{CapabilitiesPage-CrS_DTA1.js → CapabilitiesPage-w1owqsmN.js} +2 -2
  66. package/dashboard/dist/assets/{CapabilitiesPage-CrS_DTA1.js.map → CapabilitiesPage-w1owqsmN.js.map} +1 -1
  67. package/dashboard/dist/assets/{CollapsibleSection-1i6bOQ1L.js → CollapsibleSection-DIMGjlJ-.js} +2 -2
  68. package/dashboard/dist/assets/{CollapsibleSection-1i6bOQ1L.js.map → CollapsibleSection-DIMGjlJ-.js.map} +1 -1
  69. package/dashboard/dist/assets/{CredentialsPage-BxJio06E.js → CredentialsPage-D1OmMHux.js} +2 -2
  70. package/dashboard/dist/assets/{CredentialsPage-BxJio06E.js.map → CredentialsPage-D1OmMHux.js.map} +1 -1
  71. package/dashboard/dist/assets/{CronLabel-BAfIHHz4.js → CronLabel-CQy3DrUQ.js} +2 -2
  72. package/dashboard/dist/assets/{CronLabel-BAfIHHz4.js.map → CronLabel-CQy3DrUQ.js.map} +1 -1
  73. package/dashboard/dist/assets/{CustomDurationPicker-BLF0NFJK.js → CustomDurationPicker-BoBkl9tw.js} +2 -2
  74. package/dashboard/dist/assets/{CustomDurationPicker-BLF0NFJK.js.map → CustomDurationPicker-BoBkl9tw.js.map} +1 -1
  75. package/dashboard/dist/assets/{DropZone-CEx_wk-g.js → DropZone-QXbiuU0b.js} +2 -2
  76. package/dashboard/dist/assets/{DropZone-CEx_wk-g.js.map → DropZone-QXbiuU0b.js.map} +1 -1
  77. package/dashboard/dist/assets/{ElapsedCell-nKGJvznH.js → ElapsedCell-Donjm9gT.js} +2 -2
  78. package/dashboard/dist/assets/{ElapsedCell-nKGJvznH.js.map → ElapsedCell-Donjm9gT.js.map} +1 -1
  79. package/dashboard/dist/assets/{EscalationsOverview-bNNVA6R2.js → EscalationsOverview-CvHIJ_GG.js} +2 -2
  80. package/dashboard/dist/assets/{EscalationsOverview-bNNVA6R2.js.map → EscalationsOverview-CvHIJ_GG.js.map} +1 -1
  81. package/dashboard/dist/assets/{EventTable-BCuDdqlU.js → EventTable-DpAXMmT3.js} +2 -2
  82. package/dashboard/dist/assets/{EventTable-BCuDdqlU.js.map → EventTable-DpAXMmT3.js.map} +1 -1
  83. package/dashboard/dist/assets/{GraphInvokePage-BPAvqHls.js → GraphInvokePage-De1XVx5d.js} +2 -2
  84. package/dashboard/dist/assets/{GraphInvokePage-BPAvqHls.js.map → GraphInvokePage-De1XVx5d.js.map} +1 -1
  85. package/dashboard/dist/assets/HomePage-BYR5VXXw.js +2 -0
  86. package/dashboard/dist/assets/HomePage-BYR5VXXw.js.map +1 -0
  87. package/dashboard/dist/assets/{ListToolbar-DIJZfEgu.js → ListToolbar-Bx7jGzPA.js} +2 -2
  88. package/dashboard/dist/assets/{ListToolbar-DIJZfEgu.js.map → ListToolbar-Bx7jGzPA.js.map} +1 -1
  89. package/dashboard/dist/assets/{McpOverview-DCfSCfPg.js → McpOverview-DXRKcJN0.js} +2 -2
  90. package/dashboard/dist/assets/{McpOverview-DCfSCfPg.js.map → McpOverview-DXRKcJN0.js.map} +1 -1
  91. package/dashboard/dist/assets/{McpQueryDetailPage-mb7rmtNe.js → McpQueryDetailPage-CmW15w5e.js} +2 -2
  92. package/dashboard/dist/assets/{McpQueryDetailPage-mb7rmtNe.js.map → McpQueryDetailPage-CmW15w5e.js.map} +1 -1
  93. package/dashboard/dist/assets/{McpQueryPage-D9b1T0I3.js → McpQueryPage-BE6I011z.js} +2 -2
  94. package/dashboard/dist/assets/{McpQueryPage-D9b1T0I3.js.map → McpQueryPage-BE6I011z.js.map} +1 -1
  95. package/dashboard/dist/assets/{McpRunDetailPage-Bz2UyuNm.js → McpRunDetailPage-Dsa3G3XQ.js} +2 -2
  96. package/dashboard/dist/assets/{McpRunDetailPage-Bz2UyuNm.js.map → McpRunDetailPage-Dsa3G3XQ.js.map} +1 -1
  97. package/dashboard/dist/assets/{McpRunsPage-Dw7w_ZO-.js → McpRunsPage-uTbym66Y.js} +2 -2
  98. package/dashboard/dist/assets/{McpRunsPage-Dw7w_ZO-.js.map → McpRunsPage-uTbym66Y.js.map} +1 -1
  99. package/dashboard/dist/assets/{NamespacePill-D27vqDXy.js → NamespacePill-BGf0jHwE.js} +2 -2
  100. package/dashboard/dist/assets/{NamespacePill-D27vqDXy.js.map → NamespacePill-BGf0jHwE.js.map} +1 -1
  101. package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js +2 -0
  102. package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js.map +1 -0
  103. package/dashboard/dist/assets/{OperatorDashboard-CTzSDW3N.js → OperatorDashboard-i8cxaDlk.js} +2 -2
  104. package/dashboard/dist/assets/{OperatorDashboard-CTzSDW3N.js.map → OperatorDashboard-i8cxaDlk.js.map} +1 -1
  105. package/dashboard/dist/assets/{PageHeader-B_1zTx2q.js → PageHeader-BZJsEVOC.js} +2 -2
  106. package/dashboard/dist/assets/{PageHeader-B_1zTx2q.js.map → PageHeader-BZJsEVOC.js.map} +1 -1
  107. package/dashboard/dist/assets/{PageHeaderWithStats-D-cPg7UD.js → PageHeaderWithStats-BWuD7s9c.js} +2 -2
  108. package/dashboard/dist/assets/{PageHeaderWithStats-D-cPg7UD.js.map → PageHeaderWithStats-BWuD7s9c.js.map} +1 -1
  109. package/dashboard/dist/assets/{ProcessDetailPage-olG2CS-h.js → ProcessDetailPage-CP7GZhgs.js} +2 -2
  110. package/dashboard/dist/assets/{ProcessDetailPage-olG2CS-h.js.map → ProcessDetailPage-CP7GZhgs.js.map} +1 -1
  111. package/dashboard/dist/assets/{ProcessesListPage-CUZSL06Y.js → ProcessesListPage-btsU6hBR.js} +2 -2
  112. package/dashboard/dist/assets/{ProcessesListPage-CUZSL06Y.js.map → ProcessesListPage-btsU6hBR.js.map} +1 -1
  113. package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js +16 -0
  114. package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js.map +1 -0
  115. package/dashboard/dist/assets/{RolePill-B-V-31lC.js → RolePill-Pb9TKSQN.js} +2 -2
  116. package/dashboard/dist/assets/{RolePill-B-V-31lC.js.map → RolePill-Pb9TKSQN.js.map} +1 -1
  117. package/dashboard/dist/assets/RolesPage-1DBoEMsb.js +2 -0
  118. package/dashboard/dist/assets/RolesPage-1DBoEMsb.js.map +1 -0
  119. package/dashboard/dist/assets/{RunAsSelector-BoLHF4fy.js → RunAsSelector-Cp6j3FEY.js} +2 -2
  120. package/dashboard/dist/assets/{RunAsSelector-BoLHF4fy.js.map → RunAsSelector-Cp6j3FEY.js.map} +1 -1
  121. package/dashboard/dist/assets/StatusBadge-D9MeQHrh.js +2 -0
  122. package/dashboard/dist/assets/StatusBadge-D9MeQHrh.js.map +1 -0
  123. package/dashboard/dist/assets/{StickyPagination-BEMY1L-H.js → StickyPagination-Pr8kawv8.js} +2 -2
  124. package/dashboard/dist/assets/{StickyPagination-BEMY1L-H.js.map → StickyPagination-Pr8kawv8.js.map} +1 -1
  125. package/dashboard/dist/assets/StreamMessageDetail-BqmQ-N3y.js +2 -0
  126. package/dashboard/dist/assets/{StreamMessageDetail-CVRVWXcU.js.map → StreamMessageDetail-BqmQ-N3y.js.map} +1 -1
  127. package/dashboard/dist/assets/{SwimlaneTimeline-DDpU0-Jn.js → SwimlaneTimeline-y1YmXrsj.js} +2 -2
  128. package/dashboard/dist/assets/{SwimlaneTimeline-DDpU0-Jn.js.map → SwimlaneTimeline-y1YmXrsj.js.map} +1 -1
  129. package/dashboard/dist/assets/{TagInput-BHVfG307.js → TagInput-Df4myuYF.js} +2 -2
  130. package/dashboard/dist/assets/{TagInput-BHVfG307.js.map → TagInput-Df4myuYF.js.map} +1 -1
  131. package/dashboard/dist/assets/{TaskDetailPage-B_xQtJMg.js → TaskDetailPage-CT5rXHK5.js} +2 -2
  132. package/dashboard/dist/assets/{TaskDetailPage-B_xQtJMg.js.map → TaskDetailPage-CT5rXHK5.js.map} +1 -1
  133. package/dashboard/dist/assets/{TaskQueuePill-CGlxXqWt.js → TaskQueuePill-YalYdCNo.js} +2 -2
  134. package/dashboard/dist/assets/{TaskQueuePill-CGlxXqWt.js.map → TaskQueuePill-YalYdCNo.js.map} +1 -1
  135. package/dashboard/dist/assets/{TasksListPage-CsrdIOKk.js → TasksListPage-DOy6JlKS.js} +2 -2
  136. package/dashboard/dist/assets/{TasksListPage-CsrdIOKk.js.map → TasksListPage-DOy6JlKS.js.map} +1 -1
  137. package/dashboard/dist/assets/{TimeAgo-DmicaxjT.js → TimeAgo-BAQwZSpq.js} +2 -2
  138. package/dashboard/dist/assets/{TimeAgo-DmicaxjT.js.map → TimeAgo-BAQwZSpq.js.map} +1 -1
  139. package/dashboard/dist/assets/{TimestampCell-DeK89eRV.js → TimestampCell-BK_jufns.js} +2 -2
  140. package/dashboard/dist/assets/{TimestampCell-DeK89eRV.js.map → TimestampCell-BK_jufns.js.map} +1 -1
  141. package/dashboard/dist/assets/{ToolPill-NV7PxSpA.js → ToolPill-VGpT6Quy.js} +2 -2
  142. package/dashboard/dist/assets/{ToolPill-NV7PxSpA.js.map → ToolPill-VGpT6Quy.js.map} +1 -1
  143. package/dashboard/dist/assets/{ToolTestPanel-D3s2n9-9.js → ToolTestPanel-5PMQyUCe.js} +2 -2
  144. package/dashboard/dist/assets/{ToolTestPanel-D3s2n9-9.js.map → ToolTestPanel-5PMQyUCe.js.map} +1 -1
  145. package/dashboard/dist/assets/{TopicDetailPage-qyv7UWT_.js → TopicDetailPage-Cn1ShfR8.js} +3 -3
  146. package/dashboard/dist/assets/{TopicDetailPage-qyv7UWT_.js.map → TopicDetailPage-Cn1ShfR8.js.map} +1 -1
  147. package/dashboard/dist/assets/{TopicsPage-CEUfMoed.js → TopicsPage-D67tjS8L.js} +2 -2
  148. package/dashboard/dist/assets/{TopicsPage-CEUfMoed.js.map → TopicsPage-D67tjS8L.js.map} +1 -1
  149. package/dashboard/dist/assets/{UserName-Bn_BxI8K.js → UserName-Dsu1Jk4N.js} +2 -2
  150. package/dashboard/dist/assets/{UserName-Bn_BxI8K.js.map → UserName-Dsu1Jk4N.js.map} +1 -1
  151. package/dashboard/dist/assets/{WorkflowExecutionPage-DAk2RqpB.js → WorkflowExecutionPage-BwQoOmUE.js} +2 -2
  152. package/dashboard/dist/assets/{WorkflowExecutionPage-DAk2RqpB.js.map → WorkflowExecutionPage-BwQoOmUE.js.map} +1 -1
  153. package/dashboard/dist/assets/{WorkflowPill-DDR0B6DN.js → WorkflowPill-pPUqmx6Q.js} +2 -2
  154. package/dashboard/dist/assets/{WorkflowPill-DDR0B6DN.js.map → WorkflowPill-pPUqmx6Q.js.map} +1 -1
  155. package/dashboard/dist/assets/{WorkflowsDashboard-DfBgM80t.js → WorkflowsDashboard-D3z7COgH.js} +2 -2
  156. package/dashboard/dist/assets/{WorkflowsDashboard-DfBgM80t.js.map → WorkflowsDashboard-D3z7COgH.js.map} +1 -1
  157. package/dashboard/dist/assets/{WorkflowsOverview--Dkoo9Jq.js → WorkflowsOverview-C15JcxyY.js} +2 -2
  158. package/dashboard/dist/assets/{WorkflowsOverview--Dkoo9Jq.js.map → WorkflowsOverview-C15JcxyY.js.map} +1 -1
  159. package/dashboard/dist/assets/{YamlWorkflowDetailPage-B6HYbfFj.js → YamlWorkflowDetailPage-BYq3t7E-.js} +2 -2
  160. package/dashboard/dist/assets/{YamlWorkflowDetailPage-B6HYbfFj.js.map → YamlWorkflowDetailPage-BYq3t7E-.js.map} +1 -1
  161. package/dashboard/dist/assets/YamlWorkflowsPage-B44WgwE_.js +2 -0
  162. package/dashboard/dist/assets/{YamlWorkflowsPage-CJE-PxC0.js.map → YamlWorkflowsPage-B44WgwE_.js.map} +1 -1
  163. package/dashboard/dist/assets/{agents-DUciUZpt.js → agents-BRmmK6Y9.js} +2 -2
  164. package/dashboard/dist/assets/{agents-DUciUZpt.js.map → agents-BRmmK6Y9.js.map} +1 -1
  165. package/dashboard/dist/assets/{bots-BnyqCfuC.js → bots-BD75mzGG.js} +2 -2
  166. package/dashboard/dist/assets/{bots-BnyqCfuC.js.map → bots-BD75mzGG.js.map} +1 -1
  167. package/dashboard/dist/assets/{capabilities-CkTWWT0q.js → capabilities-DDgC_Kds.js} +2 -2
  168. package/dashboard/dist/assets/{capabilities-CkTWWT0q.js.map → capabilities-DDgC_Kds.js.map} +1 -1
  169. package/dashboard/dist/assets/controlplane-BJh12lMv.js +2 -0
  170. package/dashboard/dist/assets/controlplane-BJh12lMv.js.map +1 -0
  171. package/dashboard/dist/assets/{escalation-De9JnTKI.js → escalation-DIJt2sgn.js} +2 -2
  172. package/dashboard/dist/assets/{escalation-De9JnTKI.js.map → escalation-DIJt2sgn.js.map} +1 -1
  173. package/dashboard/dist/assets/{escalation-columns-Df7Pluzg.js → escalation-columns-zl6E94Up.js} +2 -2
  174. package/dashboard/dist/assets/{escalation-columns-Df7Pluzg.js.map → escalation-columns-zl6E94Up.js.map} +1 -1
  175. package/dashboard/dist/assets/{index-Cqjf-yVP.js → index-B0kmiSmt.js} +2 -2
  176. package/dashboard/dist/assets/{index-Cqjf-yVP.js.map → index-B0kmiSmt.js.map} +1 -1
  177. package/dashboard/dist/assets/index-BRtKQtQZ.css +1 -0
  178. package/dashboard/dist/assets/{index-BxdiKgnH.js → index-BUbzfEA9.js} +2 -2
  179. package/dashboard/dist/assets/{index-BxdiKgnH.js.map → index-BUbzfEA9.js.map} +1 -1
  180. package/dashboard/dist/assets/{index-DpAS4-DA.js → index-BhznDv-4.js} +2 -2
  181. package/dashboard/dist/assets/{index-DpAS4-DA.js.map → index-BhznDv-4.js.map} +1 -1
  182. package/dashboard/dist/assets/{index-BAhkWcOq.js → index-CDPBXB4e.js} +4 -4
  183. package/dashboard/dist/assets/{index-BAhkWcOq.js.map → index-CDPBXB4e.js.map} +1 -1
  184. package/dashboard/dist/assets/{index-CgD9fA9P.js → index-CV4lEd4I.js} +2 -2
  185. package/dashboard/dist/assets/{index-CgD9fA9P.js.map → index-CV4lEd4I.js.map} +1 -1
  186. package/dashboard/dist/assets/{index-DjxTV3cn.js → index-CknKy4RA.js} +2 -2
  187. package/dashboard/dist/assets/{index-DjxTV3cn.js.map → index-CknKy4RA.js.map} +1 -1
  188. package/dashboard/dist/assets/{index-DNugmBs7.js → index-CpgV4Kkp.js} +2 -2
  189. package/dashboard/dist/assets/{index-DNugmBs7.js.map → index-CpgV4Kkp.js.map} +1 -1
  190. package/dashboard/dist/assets/{index-B1kc6itM.js → index-CtDf8vvQ.js} +24 -24
  191. package/dashboard/dist/assets/index-CtDf8vvQ.js.map +1 -0
  192. package/dashboard/dist/assets/{index-BzMsh6Ui.js → index-DU7YumVJ.js} +2 -2
  193. package/dashboard/dist/assets/{index-BzMsh6Ui.js.map → index-DU7YumVJ.js.map} +1 -1
  194. package/dashboard/dist/assets/{index-BsIaUbKD.js → index-DVVIeBBk.js} +2 -2
  195. package/dashboard/dist/assets/{index-BsIaUbKD.js.map → index-DVVIeBBk.js.map} +1 -1
  196. package/dashboard/dist/assets/index-DccCwyZl.js +6 -0
  197. package/dashboard/dist/assets/{index-CN0efCE9.js.map → index-DccCwyZl.js.map} +1 -1
  198. package/dashboard/dist/assets/{index-7hqtQI43.js → index-DrUkTcn-.js} +2 -2
  199. package/dashboard/dist/assets/{index-7hqtQI43.js.map → index-DrUkTcn-.js.map} +1 -1
  200. package/dashboard/dist/assets/{index-B07xLlyz.js → index-j-C7g_Ej.js} +2 -2
  201. package/dashboard/dist/assets/{index-B07xLlyz.js.map → index-j-C7g_Ej.js.map} +1 -1
  202. package/dashboard/dist/assets/{knowledge-D4SvdNv5.js → knowledge-pNDWACXB.js} +2 -2
  203. package/dashboard/dist/assets/{knowledge-D4SvdNv5.js.map → knowledge-pNDWACXB.js.map} +1 -1
  204. package/dashboard/dist/assets/{mcp-query-CLP9EnnU.js → mcp-query-CmNjvxTG.js} +2 -2
  205. package/dashboard/dist/assets/{mcp-query-CLP9EnnU.js.map → mcp-query-CmNjvxTG.js.map} +1 -1
  206. package/dashboard/dist/assets/{mcp-DVxt4T9J.js → mcp-xddLBwUW.js} +2 -2
  207. package/dashboard/dist/assets/{mcp-DVxt4T9J.js.map → mcp-xddLBwUW.js.map} +1 -1
  208. package/dashboard/dist/assets/pipelines-B05sd2nB.js +2 -0
  209. package/dashboard/dist/assets/pipelines-B05sd2nB.js.map +1 -0
  210. package/dashboard/dist/assets/roles-bRgaMcdy.js +2 -0
  211. package/dashboard/dist/assets/roles-bRgaMcdy.js.map +1 -0
  212. package/dashboard/dist/assets/tasks-Bm1OKrJA.js +2 -0
  213. package/dashboard/dist/assets/{tasks-1eTuP02d.js.map → tasks-Bm1OKrJA.js.map} +1 -1
  214. package/dashboard/dist/assets/{topics-B3p8K0PW.js → topics-BVIqtmd4.js} +2 -2
  215. package/dashboard/dist/assets/{topics-B3p8K0PW.js.map → topics-BVIqtmd4.js.map} +1 -1
  216. package/dashboard/dist/assets/useEventHooks-_qCzU74s.js +2 -0
  217. package/dashboard/dist/assets/useEventHooks-_qCzU74s.js.map +1 -0
  218. package/dashboard/dist/assets/{useNamespace-Dx81_JM0.js → useNamespace-CDnCSFpG.js} +2 -2
  219. package/dashboard/dist/assets/{useNamespace-Dx81_JM0.js.map → useNamespace-CDnCSFpG.js.map} +1 -1
  220. package/dashboard/dist/assets/{useYamlActivityEvents-D5T3TbE7.js → useYamlActivityEvents-zrHMhaFM.js} +2 -2
  221. package/dashboard/dist/assets/{useYamlActivityEvents-D5T3TbE7.js.map → useYamlActivityEvents-zrHMhaFM.js.map} +1 -1
  222. package/dashboard/dist/assets/{users-BEMysNdR.js → users-BPBMZg27.js} +2 -2
  223. package/dashboard/dist/assets/{users-BEMysNdR.js.map → users-BPBMZg27.js.map} +1 -1
  224. package/dashboard/dist/assets/{vendor-icons--Ivmp-RX.js → vendor-icons-C1zVP_dv.js} +125 -115
  225. package/dashboard/dist/assets/vendor-icons-C1zVP_dv.js.map +1 -0
  226. package/dashboard/dist/assets/workflows-CT6nTL1I.js +2 -0
  227. package/dashboard/dist/assets/workflows-CT6nTL1I.js.map +1 -0
  228. package/dashboard/dist/assets/{yaml-workflows-4rJ5v5rJ.js → yaml-workflows-CdfjkOKU.js} +2 -2
  229. package/dashboard/dist/assets/{yaml-workflows-4rJ5v5rJ.js.map → yaml-workflows-CdfjkOKU.js.map} +1 -1
  230. package/dashboard/dist/index.html +3 -3
  231. package/docs/api/http/escalations.md +82 -6
  232. package/docs/api/http/roles.md +82 -3
  233. package/docs/api/mcp/admin.md +127 -1
  234. package/docs/api/sdk/escalations.md +65 -4
  235. package/docs/api/sdk/roles.md +81 -0
  236. package/docs/cli.md +2 -0
  237. package/docs/dashboard.md +17 -2
  238. package/docs/hitl-guide.md +12 -3
  239. package/docs/operations.md +113 -0
  240. package/package.json +3 -2
  241. package/dashboard/dist/assets/AgentsPage-Blhh2RsK.js +0 -2
  242. package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js +0 -2
  243. package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js.map +0 -1
  244. package/dashboard/dist/assets/HomePage-BZ1YTrjR.js +0 -2
  245. package/dashboard/dist/assets/HomePage-BZ1YTrjR.js.map +0 -1
  246. package/dashboard/dist/assets/RolesPage-DLnaW6iD.js +0 -2
  247. package/dashboard/dist/assets/RolesPage-DLnaW6iD.js.map +0 -1
  248. package/dashboard/dist/assets/StatusBadge-Bl3FXVmF.js +0 -2
  249. package/dashboard/dist/assets/StatusBadge-Bl3FXVmF.js.map +0 -1
  250. package/dashboard/dist/assets/StreamMessageDetail-CVRVWXcU.js +0 -2
  251. package/dashboard/dist/assets/YamlWorkflowsPage-CJE-PxC0.js +0 -2
  252. package/dashboard/dist/assets/controlplane-D9HaFZsC.js +0 -2
  253. package/dashboard/dist/assets/controlplane-D9HaFZsC.js.map +0 -1
  254. package/dashboard/dist/assets/index-B1kc6itM.js.map +0 -1
  255. package/dashboard/dist/assets/index-CN0efCE9.js +0 -6
  256. package/dashboard/dist/assets/index-nO0cim3_.css +0 -1
  257. package/dashboard/dist/assets/pipelines-C_x0LTBN.js +0 -2
  258. package/dashboard/dist/assets/pipelines-C_x0LTBN.js.map +0 -1
  259. package/dashboard/dist/assets/roles-QaGFZQ58.js +0 -2
  260. package/dashboard/dist/assets/roles-QaGFZQ58.js.map +0 -1
  261. package/dashboard/dist/assets/tasks-1eTuP02d.js +0 -2
  262. package/dashboard/dist/assets/useEventHooks-BNZky4xM.js +0 -2
  263. package/dashboard/dist/assets/useEventHooks-BNZky4xM.js.map +0 -1
  264. package/dashboard/dist/assets/vendor-icons--Ivmp-RX.js.map +0 -1
  265. package/dashboard/dist/assets/workflows-C4rjvQbI.js +0 -2
  266. package/dashboard/dist/assets/workflows-C4rjvQbI.js.map +0 -1
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /**
3
+ * ortho-populate — Populate the Operations dashboard with ortho pipeline history.
4
+ *
5
+ * Adapted from the boilerplate's 08-day-resolver.ts pattern:
6
+ * - concurrent claim loop + resolve loop + watchdog via Promise.all
7
+ * - hold time read from esc.claimed_at (API), not tracked in memory
8
+ * - simple resolve payload { approved: true, station: role }
9
+ *
10
+ * Usage:
11
+ * npx ts-node scripts/ortho-populate.ts
12
+ * ORDERS=50 HOLD_S=3 npx ts-node scripts/ortho-populate.ts
13
+ * ORDERS=5 HOLD_S=0 POLL_MS=500 npx ts-node scripts/ortho-populate.ts
14
+ *
15
+ * Env vars:
16
+ * ORDERS — number of orders to run (default 20)
17
+ * HOLD_S — seconds to hold each escalation before resolving (default 2)
18
+ * POLL_MS — poll interval ms (default 1000)
19
+ * BASE_URL — server URL (default http://localhost:3000)
20
+ */
21
+ try {
22
+ require('dotenv/config');
23
+ }
24
+ catch { }
25
+ const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
26
+ const ORDERS = parseInt(process.env.ORDERS || '20', 10);
27
+ const HOLD_S = parseFloat(process.env.HOLD_S || '2');
28
+ const POLL_MS = parseInt(process.env.POLL_MS || '1000', 10);
29
+ const ORTHO_ROLES = ['design', 'review', 'print', 'grind', 'glue', 'finish', 'qa', 'ship'];
30
+ let token = '';
31
+ let userId = '';
32
+ let totalClaimed = 0;
33
+ let totalResolved = 0;
34
+ function ts() { return new Date().toISOString().slice(11, 19); }
35
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
36
+ function ageSeconds(iso) { return (Date.now() - new Date(iso).getTime()) / 1000; }
37
+ // ── HTTP helper (mirrors boilerplate 07-shared) ───────────────────────────────
38
+ async function api(method, path, body) {
39
+ const res = await fetch(`${BASE_URL}${path}`, {
40
+ method,
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
44
+ },
45
+ body: body !== undefined ? JSON.stringify(body) : undefined,
46
+ });
47
+ const data = await res.json().catch(() => ({}));
48
+ if (!res.ok)
49
+ throw new Error(`${method} ${path} → ${res.status}: ${JSON.stringify(data)}`);
50
+ return data;
51
+ }
52
+ // ── Auth ─────────────────────────────────────────────────────────────────────
53
+ async function login() {
54
+ const auth = await api('POST', '/api/auth/login', { username: 'superadmin', password: 'l0ngt@1l' });
55
+ token = auth.token;
56
+ userId = auth.user?.id || '';
57
+ if (!token) {
58
+ console.error('[auth] Login failed');
59
+ process.exit(1);
60
+ }
61
+ console.log(`[${ts()}] Logged in (userId: ${userId.slice(0, 8)}…)`);
62
+ }
63
+ // ── Claim: grab available ortho-stage escalations for this run ────────────────
64
+ async function claimBatch(batchTag) {
65
+ try {
66
+ const resp = await api('GET', '/api/escalations/available?type=ortho-stage&limit=100&sort_by=created_at&order=asc');
67
+ const escalations = resp?.escalations ?? [];
68
+ let claimed = 0;
69
+ for (const esc of escalations) {
70
+ if (!ORTHO_ROLES.includes(esc.role))
71
+ continue;
72
+ if (!String(esc.workflow_id ?? '').includes(batchTag))
73
+ continue;
74
+ try {
75
+ await api('POST', `/api/escalations/${esc.id}/claim`, { durationMinutes: 600 });
76
+ totalClaimed++;
77
+ claimed++;
78
+ console.log(`[${ts()}] [claim] ${esc.role.padEnd(8)} ${esc.id.slice(0, 8)}… [${totalClaimed} total]`);
79
+ }
80
+ catch (err) {
81
+ const msg = String(err.message).slice(0, 80);
82
+ if (!msg.includes('409') && !msg.includes('conflict') && !msg.includes('already')) {
83
+ console.warn(`[${ts()}] [claim] err: ${msg}`);
84
+ }
85
+ }
86
+ }
87
+ return claimed;
88
+ }
89
+ catch (err) {
90
+ console.error(`[${ts()}] [claim] poll error: ${err.message.slice(0, 80)}`);
91
+ return 0;
92
+ }
93
+ }
94
+ // ── Resolve: drain all claimed+held escalations for this run ─────────────────
95
+ async function resolveBatch(batchTag) {
96
+ try {
97
+ const resp = await api('GET', `/api/escalations?type=ortho-stage&status=pending&assigned_to=${userId}&limit=100&sort_by=created_at&order=asc`);
98
+ const escalations = resp?.escalations ?? [];
99
+ let resolved = 0;
100
+ for (const esc of escalations) {
101
+ if (!ORTHO_ROLES.includes(esc.role))
102
+ continue;
103
+ if (!String(esc.workflow_id ?? '').includes(batchTag))
104
+ continue;
105
+ const heldS = esc.claimed_at ? ageSeconds(esc.claimed_at) : 0;
106
+ if (heldS < HOLD_S)
107
+ continue;
108
+ try {
109
+ await api('POST', `/api/escalations/${esc.id}/resolve`, {
110
+ resolverPayload: { approved: true, station: esc.role, completed_at: new Date().toISOString() },
111
+ });
112
+ totalResolved++;
113
+ resolved++;
114
+ console.log(`[${ts()}] [resolve] ${esc.role.padEnd(8)} ${esc.id.slice(0, 8)}… held ${heldS.toFixed(1)}s [${totalResolved} total]`);
115
+ }
116
+ catch (err) {
117
+ console.error(`[${ts()}] [resolve] ${esc.id.slice(0, 8)}… error: ${err.message.slice(0, 60)}`);
118
+ }
119
+ }
120
+ return resolved;
121
+ }
122
+ catch (err) {
123
+ console.error(`[${ts()}] [resolve] poll error: ${err.message.slice(0, 80)}`);
124
+ return 0;
125
+ }
126
+ }
127
+ // ── Run all orders through all 8 stages ───────────────────────────────────────
128
+ async function runOrders(batchTag, target) {
129
+ let done = false;
130
+ // Claim loop — runs continuously until done.
131
+ const claimLoop = async () => {
132
+ while (!done) {
133
+ await claimBatch(batchTag);
134
+ if (!done)
135
+ await sleep(POLL_MS);
136
+ }
137
+ };
138
+ // Resolve loop — drains all held escalations each poll.
139
+ const resolveLoop = async () => {
140
+ while (!done) {
141
+ await resolveBatch(batchTag);
142
+ if (!done)
143
+ await sleep(POLL_MS);
144
+ }
145
+ };
146
+ // Watchdog — stops when target hit or stalled too long.
147
+ const STALL_S = Math.max(30, HOLD_S * 6);
148
+ const watchdog = async () => {
149
+ let lastResolved = totalResolved;
150
+ let stalledFor = 0;
151
+ while (!done) {
152
+ await sleep(1000);
153
+ if (totalResolved >= target) {
154
+ done = true;
155
+ break;
156
+ }
157
+ if (totalResolved > lastResolved) {
158
+ stalledFor = 0;
159
+ lastResolved = totalResolved;
160
+ }
161
+ else {
162
+ stalledFor++;
163
+ if (stalledFor % 10 === 0) {
164
+ console.log(`[${ts()}] … ${totalResolved}/${target} resolved — waiting (${stalledFor}s quiet)`);
165
+ }
166
+ if (stalledFor >= STALL_S) {
167
+ console.warn(`[${ts()}] ⚠ stalled ${stalledFor}s with ${totalResolved}/${target} — stopping`);
168
+ done = true;
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ };
174
+ await Promise.all([claimLoop(), resolveLoop(), watchdog()]);
175
+ }
176
+ // ── Main ─────────────────────────────────────────────────────────────────────
177
+ async function main() {
178
+ console.log(`\n Ortho Populate ── ${ORDERS} orders × 8 stages (hold=${HOLD_S}s, poll=${POLL_MS}ms)`);
179
+ console.log(` Server: ${BASE_URL}\n`);
180
+ await login();
181
+ const runId = Math.floor(Date.now() / 1000).toString();
182
+ const batchTag = `ortho-pop-${runId}`;
183
+ const workflowIds = [];
184
+ // Enqueue all orders.
185
+ console.log(`[${ts()}] Enqueuing ${ORDERS} orders…`);
186
+ for (let i = 0; i < ORDERS; i++) {
187
+ const wfId = `${batchTag}-${i}`;
188
+ try {
189
+ await api('POST', '/api/workflows/orthoPipeline/invoke', {
190
+ workflowId: wfId,
191
+ data: { order_id: `ORD-${runId}-${i}`, item_type: 'insole-standard' },
192
+ metadata: { source: 'populate', run_id: runId },
193
+ });
194
+ workflowIds.push(wfId);
195
+ console.log(`[${ts()}] ${i + 1}/${ORDERS} → ${wfId}`);
196
+ }
197
+ catch (err) {
198
+ console.error(`[${ts()}] ${i + 1}/${ORDERS} FAILED: ${err.message}`);
199
+ }
200
+ }
201
+ if (workflowIds.length === 0) {
202
+ console.error('Nothing enqueued — aborting');
203
+ process.exit(1);
204
+ }
205
+ const target = workflowIds.length * 8;
206
+ console.log(`\n[${ts()}] Running — target: ${target} resolutions (${workflowIds.length} orders × 8 stages)\n`);
207
+ const startMs = Date.now();
208
+ await runOrders(batchTag, target);
209
+ const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
210
+ await sleep(2000);
211
+ // Count completed workflows.
212
+ let completed = 0;
213
+ for (const wfId of workflowIds) {
214
+ try {
215
+ const resp = await api('GET', `/api/workflows/${wfId}/result`);
216
+ if (resp?.result?.data?.results?.length === 8)
217
+ completed++;
218
+ }
219
+ catch { /* still running */ }
220
+ }
221
+ console.log(`\n[${ts()}] Done in ${elapsed}s`);
222
+ console.log(` Orders submitted: ${workflowIds.length}`);
223
+ console.log(` Orders completed: ${completed}`);
224
+ console.log(` Claimed: ${totalClaimed}`);
225
+ console.log(` Resolved: ${totalResolved}`);
226
+ console.log(`\n Dashboard: ${BASE_URL}/operations\n`);
227
+ }
228
+ main().catch((err) => { console.error(err); process.exit(1); });
@@ -119,6 +119,9 @@ export declare function createClient(options?: LTClientOptions): {
119
119
  getStats: (input: {
120
120
  period?: string;
121
121
  }, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
122
+ getStationMetrics: (input: {
123
+ period?: string;
124
+ }, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
122
125
  get: (input: {
123
126
  id: string;
124
127
  }, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
@@ -268,6 +271,7 @@ export declare function createClient(options?: LTClientOptions): {
268
271
  list: typeof rolesApi.listRoles;
269
272
  listWithDetails: typeof rolesApi.listRolesWithDetails;
270
273
  create: typeof rolesApi.createRole;
274
+ update: typeof rolesApi.updateRole;
271
275
  delete: typeof rolesApi.deleteRole;
272
276
  getEscalationChains: typeof rolesApi.getEscalationChains;
273
277
  addEscalationChain: typeof rolesApi.addEscalationChain;
@@ -119,6 +119,7 @@ function createClient(options = {}) {
119
119
  listTypes: escalationsApi.listDistinctTypes,
120
120
  listFacetKeys: bindAuth(escalationsApi.listFacetKeys, auth),
121
121
  getStats: bindAuth(escalationsApi.getEscalationStats, auth),
122
+ getStationMetrics: bindAuth(escalationsApi.getStationMetrics, auth),
122
123
  get: bindAuth(escalationsApi.getEscalation, auth),
123
124
  getByWorkflowId: escalationsApi.getEscalationsByWorkflowId,
124
125
  escalate: bindAuth(escalationsApi.escalateToRole, auth),
@@ -193,6 +194,7 @@ function createClient(options = {}) {
193
194
  list: rolesApi.listRoles,
194
195
  listWithDetails: rolesApi.listRolesWithDetails,
195
196
  create: rolesApi.createRole,
197
+ update: rolesApi.updateRole,
196
198
  delete: rolesApi.deleteRole,
197
199
  getEscalationChains: rolesApi.getEscalationChains,
198
200
  addEscalationChain: rolesApi.addEscalationChain,
@@ -66,14 +66,69 @@ async function installEscalationCompatView() {
66
66
  const client = await pool.connect();
67
67
  try {
68
68
  await client.query('SELECT pg_advisory_lock($1)', [COMPAT_VIEW_LOCK_ID]);
69
- await client.query(MIGRATE_AND_RENAME_LEGACY_TABLE);
70
- await client.query(CREATE_COMPAT_VIEW);
69
+ // Atomic table→view swap. The legacy-row migration + RENAME and the
70
+ // CREATE VIEW commit together, so `lt_escalations` is never momentarily
71
+ // absent — a concurrent reader sees either the old table or the new view,
72
+ // never nothing. Postgres DDL is transactional, so a crash mid-swap rolls
73
+ // back cleanly and the next container retries from a consistent state.
74
+ try {
75
+ await client.query('BEGIN');
76
+ await client.query(MIGRATE_AND_RENAME_LEGACY_TABLE);
77
+ await client.query(CREATE_COMPAT_VIEW);
78
+ await client.query('COMMIT');
79
+ }
80
+ catch (err) {
81
+ await client.query('ROLLBACK').catch(() => { });
82
+ throw err;
83
+ }
84
+ // Superseded-index drops run AFTER the swap, in autocommit — instant
85
+ // catalog operations, idempotent across retries and multi-container
86
+ // restarts. hmsh 0.25.0's stats indexes (idx_hmsh_esc_stats_pending /
87
+ // _created / _resolved, shipped by the SDK's own deploy) serve the backlog
88
+ // and window aggregates these earlier app-layer indexes covered, so
89
+ // keeping them would only amplify every insert/claim/resolve write.
90
+ await client.query(DROP_OLD_RESOLVED_INDEX);
91
+ await client.query(DROP_OLD_CLAIMED_INDEX);
92
+ await client.query(DROP_SUPERSEDED_PENDING_INDEX);
93
+ await client.query(DROP_UNCONSUMED_CLAIMED_COVER_INDEX);
71
94
  logger_1.loggerRegistry.info('[escalation] lt_escalations compatibility view ensured');
72
95
  }
73
96
  finally {
74
97
  await client.query('SELECT pg_advisory_unlock($1)', [COMPAT_VIEW_LOCK_ID]).catch(() => { });
75
98
  client.release();
76
99
  }
100
+ // The one index long-tail still owns builds OUTSIDE the advisory lock:
101
+ // CREATE INDEX CONCURRENTLY waits on every transaction holding a snapshot,
102
+ // and sibling containers parked inside `SELECT pg_advisory_lock(...)` hold
103
+ // exactly that — building inside the critical section would stall the build
104
+ // against its own waiters. Out here the build blocks nothing (writes proceed
105
+ // during CONCURRENTLY) and nothing blocks it.
106
+ await ensureResolvedCoverIndex();
107
+ }
108
+ /**
109
+ * Idempotent, concurrent-boot-safe build of idx_hmsh_esc_resolved_cover.
110
+ *
111
+ * A crashed CONCURRENTLY build leaves an INVALID index behind, and
112
+ * `CREATE INDEX CONCURRENTLY IF NOT EXISTS` would then skip it forever — so
113
+ * validity is checked first and an invalid leftover is dropped and rebuilt.
114
+ * A create that loses a cross-container race logs and defers to the next
115
+ * boot, which observes the winner's valid index and no-ops.
116
+ */
117
+ async function ensureResolvedCoverIndex() {
118
+ const pool = (0, db_1.getPool)();
119
+ try {
120
+ const { rows } = await pool.query(RESOLVED_COVER_INDEX_STATE);
121
+ if (rows[0]?.valid === true)
122
+ return;
123
+ if (rows.length > 0) {
124
+ await pool.query('DROP INDEX IF EXISTS idx_hmsh_esc_resolved_cover');
125
+ }
126
+ await pool.query(ENSURE_RESOLVED_COVER_INDEX);
127
+ logger_1.loggerRegistry.info('[escalation] idx_hmsh_esc_resolved_cover ensured');
128
+ }
129
+ catch (err) {
130
+ logger_1.loggerRegistry.warn(`[escalation] resolved-cover index build deferred to next boot: ${err.message}`);
131
+ }
77
132
  }
78
133
  // Migrate legacy `lt_escalations` rows into `hmsh_escalations` (idempotent),
79
134
  // then preserve the original table as `lt_escalations_legacy` rather than
@@ -139,3 +194,39 @@ CREATE OR REPLACE VIEW public.lt_escalations AS
139
194
  SELECT *,
140
195
  (assigned_to IS NULL OR assigned_until IS NULL OR assigned_until <= NOW()) AS available
141
196
  FROM public.hmsh_escalations;`;
197
+ // Index ownership — hmsh 0.25.0 ships the general aggregate indexes on
198
+ // hmsh_escalations (idx_hmsh_esc_stats_pending covers the pending backlog
199
+ // incl. assigned_to/assigned_until; _stats_created and _stats_resolved bound
200
+ // the created/resolved windows). Long-tail keeps exactly ONE app-layer index:
201
+ //
202
+ // idx_hmsh_esc_resolved_cover (role, resolved_at DESC, claimed_at, created_at)
203
+ // WHERE status = 'resolved'
204
+ // Serves STATION_PERIOD_METRICS_SQL: the (role, resolved_at ≥ window)
205
+ // prefix bounds each station's percentile scan to its own window, and the
206
+ // trailing claimed_at/created_at feed PERCENTILE_CONT(resolved_at -
207
+ // claimed_at) and (claimed_at - created_at) from the index.
208
+ //
209
+ // Everything else this branch once created is dropped below: the pending
210
+ // index duplicated _stats_pending, and the claimed cover had no consuming
211
+ // query — both were pure write amplification on the hot claim/resolve path.
212
+ // ── Drops: superseded or branch-era-only names (idempotent no-ops elsewhere) ─
213
+ const DROP_OLD_RESOLVED_INDEX = `
214
+ DROP INDEX IF EXISTS idx_hmsh_escalations_role_resolved_at`;
215
+ const DROP_OLD_CLAIMED_INDEX = `
216
+ DROP INDEX IF EXISTS idx_hmsh_escalations_role_claimed_at`;
217
+ const DROP_SUPERSEDED_PENDING_INDEX = `
218
+ DROP INDEX IF EXISTS idx_hmsh_esc_pending_role_created`;
219
+ const DROP_UNCONSUMED_CLAIMED_COVER_INDEX = `
220
+ DROP INDEX IF EXISTS idx_hmsh_esc_claimed_cover`;
221
+ // ── The station-percentile cover ─────────────────────────────────────────────
222
+ /** Reports existence + validity so a crash-orphaned INVALID build is rebuilt. */
223
+ const RESOLVED_COVER_INDEX_STATE = `
224
+ SELECT i.indisvalid AS valid
225
+ FROM pg_class c
226
+ JOIN pg_index i ON i.indexrelid = c.oid
227
+ JOIN pg_namespace n ON n.oid = c.relnamespace
228
+ WHERE c.relname = 'idx_hmsh_esc_resolved_cover' AND n.nspname = 'public'`;
229
+ const ENSURE_RESOLVED_COVER_INDEX = `\
230
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_hmsh_esc_resolved_cover
231
+ ON public.hmsh_escalations (role, resolved_at DESC, claimed_at, created_at)
232
+ WHERE status = 'resolved'`;
@@ -28,6 +28,15 @@ const client_1 = require("./client");
28
28
  const queries_1 = require("./queries");
29
29
  const map_1 = require("./map");
30
30
  const sql_1 = require("./sql");
31
+ /**
32
+ * Every escalation write publishes an event AND drops this container's cached
33
+ * aggregates — the event triggers a dashboard refetch, which must observe the
34
+ * write rather than a pre-change aggregate held for up to a cache TTL.
35
+ */
36
+ function publishEscalationChange(...args) {
37
+ (0, queries_1.invalidateEscalationAggregates)();
38
+ return (0, publish_1.publishEscalationEvent)(...args);
39
+ }
31
40
  // All escalation state lives in `public.hmsh_escalations` (HotMesh 0.22.3),
32
41
  // reached through `client.escalations.*`. The function signatures and return
33
42
  // shapes below are the frozen public surface — only the storage path changed.
@@ -56,7 +65,7 @@ async function createEscalation(input) {
56
65
  escalationPayload: (0, map_1.toJsonObject)(input.escalation_payload),
57
66
  });
58
67
  const escalation = (0, map_1.toEscalationRecord)(entry);
59
- (0, publish_1.publishEscalationEvent)({
68
+ publishEscalationChange({
60
69
  type: 'escalation.created',
61
70
  source: 'service',
62
71
  workflowId: escalation.workflow_id || '',
@@ -79,7 +88,7 @@ async function claimEscalation(id, userId, durationMinutes = 30) {
79
88
  if (!result.ok)
80
89
  return null;
81
90
  const escalation = (0, map_1.toEscalationRecord)(result.entry);
82
- (0, publish_1.publishEscalationEvent)({
91
+ publishEscalationChange({
83
92
  type: 'escalation.claimed',
84
93
  source: 'service',
85
94
  workflowId: escalation.workflow_id || '',
@@ -110,7 +119,7 @@ async function resolveEscalation(id, resolverPayload, metadata) {
110
119
  if (!result.ok)
111
120
  return null;
112
121
  const escalation = (0, map_1.toEscalationRecord)(result.entry);
113
- (0, publish_1.publishEscalationEvent)({
122
+ publishEscalationChange({
114
123
  type: 'escalation.resolved',
115
124
  source: 'service',
116
125
  workflowId: escalation.workflow_id || '',
@@ -142,7 +151,7 @@ async function resolveEscalationsByIds(ids, resolverPayload, metadata) {
142
151
  const rows = await client.resolveMany({ ids, resolverPayload, metadata });
143
152
  const records = (0, map_1.toEscalationRecords)(rows);
144
153
  for (const escalation of records) {
145
- (0, publish_1.publishEscalationEvent)({
154
+ publishEscalationChange({
146
155
  type: 'escalation.resolved',
147
156
  source: 'service',
148
157
  workflowId: escalation.workflow_id || '',
@@ -197,7 +206,7 @@ async function cancelEscalation(id) {
197
206
  if (!result.ok)
198
207
  return null;
199
208
  const cancelled = (0, map_1.toEscalationRecord)(result.entry);
200
- (0, publish_1.publishEscalationEvent)({
209
+ publishEscalationChange({
201
210
  type: 'escalation.cancelled',
202
211
  source: 'service',
203
212
  workflowId: cancelled.workflow_id || '',
@@ -256,7 +265,7 @@ async function releaseEscalation(id, userId) {
256
265
  if (!result.ok)
257
266
  return null;
258
267
  const released = (0, map_1.toEscalationRecord)(result.entry);
259
- (0, publish_1.publishEscalationEvent)({
268
+ publishEscalationChange({
260
269
  type: 'escalation.released',
261
270
  source: 'service',
262
271
  workflowId: released.workflow_id || '',
@@ -332,7 +341,7 @@ async function cancelEscalationsByWorkflowId(workflowId) {
332
341
  AND status = 'pending'
333
342
  RETURNING id, workflow_id, workflow_type, task_queue`, [workflowId]);
334
343
  for (const row of rows) {
335
- (0, publish_1.publishEscalationEvent)({
344
+ publishEscalationChange({
336
345
  type: 'escalation.cancelled',
337
346
  source: 'service',
338
347
  workflowId: row.workflow_id || '',
@@ -419,7 +428,7 @@ async function claimByMetadata(key, value, userId, durationMinutes = 30, metadat
419
428
  if (!result.ok)
420
429
  return null;
421
430
  const escalation = (0, map_1.toEscalationRecord)(result.entry);
422
- (0, publish_1.publishEscalationEvent)({
431
+ publishEscalationChange({
423
432
  type: 'escalation.claimed',
424
433
  source: 'service',
425
434
  workflowId: escalation.workflow_id || '',
@@ -461,7 +470,7 @@ async function resolveByMetadataAtomic(key, value, userId, resolverPayload, meta
461
470
  const row = rows[0];
462
471
  if (row.outcome === 'resolved') {
463
472
  const escalation = (0, map_1.toEscalationRecord)(row);
464
- (0, publish_1.publishEscalationEvent)({
473
+ publishEscalationChange({
465
474
  type: 'escalation.resolved',
466
475
  source: 'service',
467
476
  workflowId: escalation.workflow_id || '',
@@ -0,0 +1,14 @@
1
+ export declare class TtlCache<T> {
2
+ private readonly ttlMs;
3
+ private readonly store;
4
+ constructor(ttlMs: number);
5
+ /**
6
+ * Return the cached value for `key` when it is younger than the TTL. On a
7
+ * miss, run `compute`, cache the in-flight promise so concurrent callers
8
+ * share one computation (no thundering herd), and return it. A rejected
9
+ * computation is evicted so the next caller retries rather than caching the
10
+ * failure.
11
+ */
12
+ resolve(key: string, compute: () => Promise<T>): Promise<T>;
13
+ clear(): void;
14
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Single-flight, short-TTL in-process cache.
4
+ //
5
+ // Memoizes the expensive half of the station-metrics query (percentile /
6
+ // throughput aggregates) so a burst of socket-driven dashboard refreshes and
7
+ // all concurrent viewers collapse to one computation per TTL window. Live
8
+ // counts are never cached — only slow-changing historical aggregates, which
9
+ // tolerate 30-60s staleness.
10
+ //
11
+ // Per-container by design: the values are read-only aggregates and cross-
12
+ // container drift within the TTL is acceptable. No cross-node coordination.
13
+ // ---------------------------------------------------------------------------
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TtlCache = void 0;
16
+ class TtlCache {
17
+ constructor(ttlMs) {
18
+ this.ttlMs = ttlMs;
19
+ this.store = new Map();
20
+ }
21
+ /**
22
+ * Return the cached value for `key` when it is younger than the TTL. On a
23
+ * miss, run `compute`, cache the in-flight promise so concurrent callers
24
+ * share one computation (no thundering herd), and return it. A rejected
25
+ * computation is evicted so the next caller retries rather than caching the
26
+ * failure.
27
+ */
28
+ async resolve(key, compute) {
29
+ const hit = this.store.get(key);
30
+ if (hit && Date.now() - hit.at < this.ttlMs)
31
+ return hit.value;
32
+ // Sweep every expired entry on each miss. Keys embed the caller's role
33
+ // set × period, so without eviction the map accretes one row-array per
34
+ // distinct viewer cohort for the life of the process.
35
+ for (const [k, entry] of this.store) {
36
+ if (Date.now() - entry.at >= this.ttlMs)
37
+ this.store.delete(k);
38
+ }
39
+ const value = compute();
40
+ this.store.set(key, { at: Date.now(), value });
41
+ try {
42
+ return await value;
43
+ }
44
+ catch (err) {
45
+ this.store.delete(key);
46
+ throw err;
47
+ }
48
+ }
49
+ clear() {
50
+ this.store.clear();
51
+ }
52
+ }
53
+ exports.TtlCache = TtlCache;
@@ -1,12 +1,13 @@
1
1
  import type { LTEscalationRecord, LTEscalationStatus } from '../../types';
2
- import type { EscalationStats } from './types';
2
+ import type { EscalationStats, StationMetric } from './types';
3
3
  import type { FacetQuery } from '../../types';
4
4
  /**
5
5
  * Scoped faceted search — the HUMAN operations query. Composes the read-scope
6
6
  * predicate (global → none; else `role ∈ allRoles OR (role ∈ selfRoles AND
7
7
  * assigned_to = me)`) with the full FacetQuery language (`buildFacetWhere`:
8
8
  * facets `@>`, block, numeric range, exists, status, available) plus the extra
9
- * top-level filters (type/subtype/priority/assigned_to) and free-text, then orders
9
+ * top-level filters (type/subtype/priority/assigned_to) and an exact correlation-id
10
+ * match (id/workflow_id/origin_id), then orders
10
11
  * by `buildFacetOrder` (columns + `metadata.<key>`). Every value is bound; the
11
12
  * COUNT shares the WHERE so totals stay correct across pages — no client-side
12
13
  * filtering. Reads go through the `public.lt_escalations` view.
@@ -83,3 +84,14 @@ export declare function listAvailableEscalations(filters: {
83
84
  escalations: LTEscalationRecord[];
84
85
  total: number;
85
86
  }>;
87
+ /**
88
+ * Drop cached aggregates (period metrics + escalation stats). Called by the
89
+ * write paths in crud.ts alongside each escalation event publish: the event
90
+ * tells dashboards "data changed", so the very next fetch on this container
91
+ * must recompute rather than serve the pre-change aggregate for up to a TTL.
92
+ * Cross-container staleness within the TTL remains and is acceptable.
93
+ */
94
+ export declare function invalidateEscalationAggregates(): void;
95
+ /** Test hook: drop cached period metrics AND escalation stats so tests observe fresh rows. */
96
+ export declare function resetStationMetricsCache(): void;
97
+ export declare function getStationMetrics(visibleRoles: string[] | undefined, period?: string): Promise<StationMetric[]>;