@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
@@ -32,10 +32,32 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.createEscalation = createEscalation;
40
+ const ajv_1 = __importDefault(require("ajv"));
37
41
  const escalationService = __importStar(require("../../services/escalation"));
42
+ const roleService = __importStar(require("../../services/role"));
38
43
  const helpers_1 = require("./helpers");
44
+ // strict: false — role metadata_schemas use the project's house extension
45
+ // keywords (x-lt-widget etc., the same style as seeded form_schemas); Ajv 8's
46
+ // strict mode throws at compile on any unknown keyword, which would turn every
47
+ // metadata-bearing create for that role into a 500.
48
+ const ajv = new ajv_1.default({ allErrors: true, strict: false });
49
+ // Compiled validators cached per role, keyed by the schema's serialized form
50
+ // so an admin's schema edit invalidates the stale validator on the next create.
51
+ const validatorCache = new Map();
52
+ function compileRoleValidator(role, schema) {
53
+ const key = JSON.stringify(schema);
54
+ const cached = validatorCache.get(role);
55
+ if (cached && cached.key === key)
56
+ return cached.validate;
57
+ const validate = ajv.compile(schema);
58
+ validatorCache.set(role, { key, validate });
59
+ return validate;
60
+ }
39
61
  // ── Create ────────────────────────────────────────────────────────────────
40
62
  /**
41
63
  * Create a standalone escalation (not tied to a workflow).
@@ -70,6 +92,29 @@ async function createEscalation(input, auth) {
70
92
  if (denied) {
71
93
  return { status: 403, error: `You must have write access to the "${role}" role or be a superadmin to create escalations for it` };
72
94
  }
95
+ // Validate metadata against the role's declared schema (if any).
96
+ if (input.metadata) {
97
+ const schema = await roleService.getRoleMetadataSchema(role);
98
+ if (schema) {
99
+ let validate;
100
+ try {
101
+ validate = compileRoleValidator(role, schema);
102
+ }
103
+ catch (compileErr) {
104
+ // A broken stored schema is the role admin's bug, not the creator's —
105
+ // name it explicitly instead of surfacing an opaque 500.
106
+ return {
107
+ status: 422,
108
+ error: `metadata_schema for role "${role}" is not a valid JSON Schema: ${compileErr.message}`,
109
+ };
110
+ }
111
+ const valid = validate(input.metadata);
112
+ if (!valid) {
113
+ const msgs = (validate.errors ?? []).map((e) => `${e.instancePath || '(root)'} ${e.message}`).join('; ');
114
+ return { status: 400, error: `metadata does not match the role schema: ${msgs}` };
115
+ }
116
+ }
117
+ }
73
118
  const escalation = await escalationService.createEscalation({
74
119
  type,
75
120
  subtype: input.subtype ?? type,
@@ -1,5 +1,5 @@
1
1
  export { createEscalation } from './create';
2
- export { listEscalations, listAvailableEscalations, listDistinctTypes, listFacetKeys, getEscalationStats } from './list';
2
+ export { listEscalations, listAvailableEscalations, listDistinctTypes, listFacetKeys, getEscalationStats, getStationMetrics } from './list';
3
3
  export { getEscalation, getEscalationsByWorkflowId, escalateToRole } from './single';
4
4
  export { claimEscalation, releaseEscalation } from './claim';
5
5
  export { releaseExpiredClaims, updatePriority, bulkClaim, bulkAssign, bulkEscalate, bulkTriage } from './bulk';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.claimByFacets = exports.claimGroups = exports.searchByFacets = exports.bulkCancel = exports.cancelSingleEscalation = exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveByIds = exports.resolveBySignalKey = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getEscalationStats = exports.listFacetKeys = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
3
+ exports.claimByFacets = exports.claimGroups = exports.searchByFacets = exports.bulkCancel = exports.cancelSingleEscalation = exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveByIds = exports.resolveBySignalKey = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getStationMetrics = exports.getEscalationStats = exports.listFacetKeys = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
4
4
  var create_1 = require("./create");
5
5
  Object.defineProperty(exports, "createEscalation", { enumerable: true, get: function () { return create_1.createEscalation; } });
6
6
  var list_1 = require("./list");
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "listAvailableEscalations", { enumerable: true, g
9
9
  Object.defineProperty(exports, "listDistinctTypes", { enumerable: true, get: function () { return list_1.listDistinctTypes; } });
10
10
  Object.defineProperty(exports, "listFacetKeys", { enumerable: true, get: function () { return list_1.listFacetKeys; } });
11
11
  Object.defineProperty(exports, "getEscalationStats", { enumerable: true, get: function () { return list_1.getEscalationStats; } });
12
+ Object.defineProperty(exports, "getStationMetrics", { enumerable: true, get: function () { return list_1.getStationMetrics; } });
12
13
  var single_1 = require("./single");
13
14
  Object.defineProperty(exports, "getEscalation", { enumerable: true, get: function () { return single_1.getEscalation; } });
14
15
  Object.defineProperty(exports, "getEscalationsByWorkflowId", { enumerable: true, get: function () { return single_1.getEscalationsByWorkflowId; } });
@@ -95,3 +95,6 @@ export declare function listDistinctTypes(): Promise<LTApiResult>;
95
95
  export declare function getEscalationStats(input: {
96
96
  period?: string;
97
97
  }, auth: LTApiAuth): Promise<LTApiResult>;
98
+ export declare function getStationMetrics(input: {
99
+ period?: string;
100
+ }, auth: LTApiAuth): Promise<LTApiResult>;
@@ -38,6 +38,7 @@ exports.listAvailableEscalations = listAvailableEscalations;
38
38
  exports.listFacetKeys = listFacetKeys;
39
39
  exports.listDistinctTypes = listDistinctTypes;
40
40
  exports.getEscalationStats = getEscalationStats;
41
+ exports.getStationMetrics = getStationMetrics;
41
42
  const escalationService = __importStar(require("../../services/escalation"));
42
43
  const helpers_1 = require("./helpers");
43
44
  /** True when the caller asked for anything the plain list path can't express. */
@@ -270,3 +271,16 @@ async function getEscalationStats(input, auth) {
270
271
  return { status: 500, error: err.message };
271
272
  }
272
273
  }
274
+ async function getStationMetrics(input, auth) {
275
+ try {
276
+ const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
277
+ if (!scope.global && scope.allRoles.length === 0) {
278
+ return { status: 200, data: { stations: [] } };
279
+ }
280
+ const stations = await escalationService.getStationMetrics(scope.global ? undefined : scope.allRoles, input.period);
281
+ return { status: 200, data: { stations } };
282
+ }
283
+ catch (err) {
284
+ return { status: 500, error: err.message };
285
+ }
286
+ }
@@ -75,6 +75,23 @@ export declare function replaceEscalationTargets(input: {
75
75
  role: string;
76
76
  targets: string[];
77
77
  }): Promise<LTApiResult>;
78
+ /**
79
+ * Update role metadata. Fields omitted from input are left unchanged.
80
+ * form_schema, metadata_schema, and parent_role can be set to null to clear them.
81
+ */
82
+ export declare function updateRole(input: {
83
+ role: string;
84
+ title?: string | null;
85
+ description?: string | null;
86
+ form_schema?: Record<string, any> | null;
87
+ metadata_schema?: Record<string, any> | null;
88
+ properties?: Record<string, any> | null;
89
+ ops_visible?: boolean;
90
+ parent_role?: string | null;
91
+ sla_minutes?: number | null;
92
+ target_per_hour?: number | null;
93
+ worker_count?: number | null;
94
+ }): Promise<LTApiResult>;
78
95
  /**
79
96
  * Delete a role from the system. Requires admin privileges.
80
97
  *
@@ -41,6 +41,7 @@ exports.addEscalationChain = addEscalationChain;
41
41
  exports.removeEscalationChain = removeEscalationChain;
42
42
  exports.getEscalationTargets = getEscalationTargets;
43
43
  exports.replaceEscalationTargets = replaceEscalationTargets;
44
+ exports.updateRole = updateRole;
44
45
  exports.deleteRole = deleteRole;
45
46
  const roleService = __importStar(require("../services/role"));
46
47
  /**
@@ -195,6 +196,51 @@ async function replaceEscalationTargets(input) {
195
196
  return { status: 500, error: err.message };
196
197
  }
197
198
  }
199
+ /**
200
+ * Update role metadata. Fields omitted from input are left unchanged.
201
+ * form_schema, metadata_schema, and parent_role can be set to null to clear them.
202
+ */
203
+ async function updateRole(input) {
204
+ try {
205
+ if (!input.role) {
206
+ return { status: 400, error: 'role is required' };
207
+ }
208
+ for (const field of ['sla_minutes', 'target_per_hour', 'worker_count']) {
209
+ const value = input[field];
210
+ if (value !== undefined && value !== null && (typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {
211
+ return { status: 400, error: `${field} must be a non-negative number or null` };
212
+ }
213
+ }
214
+ if (input.worker_count != null && !Number.isInteger(input.worker_count)) {
215
+ return { status: 400, error: 'worker_count must be a whole number' };
216
+ }
217
+ if (input.ops_visible !== undefined && typeof input.ops_visible !== 'boolean') {
218
+ return { status: 400, error: 'ops_visible must be a boolean' };
219
+ }
220
+ if (input.parent_role != null && input.parent_role === input.role) {
221
+ return { status: 400, error: 'parent_role must reference a different role' };
222
+ }
223
+ const updated = await roleService.updateRoleMetadata(input.role, {
224
+ title: input.title,
225
+ description: input.description,
226
+ form_schema: input.form_schema,
227
+ metadata_schema: input.metadata_schema,
228
+ properties: input.properties,
229
+ ops_visible: input.ops_visible,
230
+ parent_role: input.parent_role,
231
+ sla_minutes: input.sla_minutes,
232
+ target_per_hour: input.target_per_hour,
233
+ worker_count: input.worker_count,
234
+ });
235
+ if (!updated) {
236
+ return { status: 404, error: `Role '${input.role}' not found` };
237
+ }
238
+ return { status: 200, data: updated };
239
+ }
240
+ catch (err) {
241
+ return { status: 500, error: err.message };
242
+ }
243
+ }
198
244
  /**
199
245
  * Delete a role from the system. Requires admin privileges.
200
246
  *
package/build/bin/ltc.js CHANGED
@@ -119,6 +119,7 @@ const escCmd = commander_1.program.command('escalations').alias('esc').descripti
119
119
  escCmd.command('list')
120
120
  .option('--status <status>', 'Filter by status (pending, resolved, cancelled)')
121
121
  .option('--role <role>', 'Filter by role')
122
+ .option('--search <term>', 'Exact-match by correlation id (escalation id, workflow id, or origin id). For a value inside metadata, use --facets')
122
123
  .option('--limit <n>', 'Max results')
123
124
  .option('--facets <json>', 'Required metadata facets, JSON object (metadata @>), e.g. \'{"filament":"pla"}\'')
124
125
  .option('--block <json>', 'Exclude rows containing ANY of these facet sets, JSON array')
@@ -1,6 +1,7 @@
1
1
  interface ListOptions {
2
2
  status?: string;
3
3
  role?: string;
4
+ search?: string;
4
5
  limit?: string;
5
6
  json?: boolean;
6
7
  quiet?: boolean;
@@ -32,6 +32,8 @@ async function listEscalations(opts) {
32
32
  params.set('status', opts.status);
33
33
  if (opts.role)
34
34
  params.set('role', opts.role);
35
+ if (opts.search)
36
+ params.set('search', opts.search);
35
37
  if (opts.limit)
36
38
  params.set('limit', opts.limit);
37
39
  // Faceted query elements travel as JSON-encoded query params (the route JSON-parses
@@ -64,8 +64,28 @@ async function migrate() {
64
64
  const { rows } = await client.query('SELECT 1 FROM lt_migrations WHERE name = $1', [file]);
65
65
  if (rows.length === 0) {
66
66
  const sql = fs.readFileSync(path.join(SCHEMAS_DIR, file), 'utf-8');
67
- await client.query(sql);
68
- await client.query('INSERT INTO lt_migrations (name) VALUES ($1)', [file]);
67
+ // Apply the file and record it in ONE transaction. Without this, a crash
68
+ // between applying the SQL and inserting the tracking row leaves the
69
+ // migration applied-but-untracked, so the next boot re-runs it. That is
70
+ // only safe if every migration is idempotent — this wrapper removes that
71
+ // hidden requirement and makes apply+track atomic across containers
72
+ // (the advisory lock already serializes them; this closes the gap within
73
+ // a single file's application).
74
+ //
75
+ // Constraint: migration files must NOT use CREATE INDEX CONCURRENTLY —
76
+ // it is illegal inside a transaction block. Concurrent index builds on
77
+ // large runtime tables belong in the post-boot path (see
78
+ // services/escalation/client.ts), not in migrate().
79
+ try {
80
+ await client.query('BEGIN');
81
+ await client.query(sql);
82
+ await client.query('INSERT INTO lt_migrations (name) VALUES ($1)', [file]);
83
+ await client.query('COMMIT');
84
+ }
85
+ catch (err) {
86
+ await client.query('ROLLBACK').catch(() => { });
87
+ throw err;
88
+ }
69
89
  logger_1.loggerRegistry.info(`[migrate] applied: ${file}`);
70
90
  }
71
91
  }
@@ -0,0 +1,19 @@
1
+ -- Migration 014: Elevate lt_roles with rich metadata and process dependency graph
2
+ --
3
+ -- New columns:
4
+ -- title — display name separate from the technical role key
5
+ -- description — human-readable summary for card layout
6
+ -- form_schema — role-level template for rendering/validating escalation data
7
+ -- (used as default; workflow-level resolver_schema overrides)
8
+ -- properties — open bag for SLA targets, icon, color, station location, etc.
9
+ -- ops_visible — flag controlling COO /operations view visibility
10
+ -- parent_role — nullable self-referencing FK for the process dependency graph
11
+ -- (child references parent; roots have NULL)
12
+
13
+ ALTER TABLE lt_roles
14
+ ADD COLUMN IF NOT EXISTS title TEXT,
15
+ ADD COLUMN IF NOT EXISTS description TEXT,
16
+ ADD COLUMN IF NOT EXISTS form_schema JSONB,
17
+ ADD COLUMN IF NOT EXISTS properties JSONB NOT NULL DEFAULT '{}',
18
+ ADD COLUMN IF NOT EXISTS ops_visible BOOLEAN NOT NULL DEFAULT false,
19
+ ADD COLUMN IF NOT EXISTS parent_role TEXT REFERENCES lt_roles(role);
@@ -0,0 +1,50 @@
1
+ -- Migration 015: Role operational columns
2
+ --
3
+ -- Promotes the COO operational triangle to first-class typed columns on lt_roles
4
+ -- instead of loose keys inside the properties JSONB bag.
5
+ --
6
+ -- Triangle: if you know any two, the third is derived.
7
+ -- throughput = worker_count / (sla_minutes / 60)
8
+ -- worker_count = target_per_hour × (sla_minutes / 60)
9
+ -- sla_minutes = worker_count / target_per_hour × 60
10
+ --
11
+ -- Also formalises metadata_schema: a JSON Schema document that declares the
12
+ -- expected shape of lt_escalations.metadata for escalations created under
13
+ -- this role. Drives faceted-query key autocomplete and creation-time validation.
14
+ --
15
+ -- properties remains a free user-owned JSONB bag — no reserved keys.
16
+
17
+ ALTER TABLE lt_roles
18
+ ADD COLUMN IF NOT EXISTS sla_minutes NUMERIC,
19
+ ADD COLUMN IF NOT EXISTS target_per_hour NUMERIC,
20
+ ADD COLUMN IF NOT EXISTS worker_count NUMERIC,
21
+ ADD COLUMN IF NOT EXISTS metadata_schema JSONB;
22
+
23
+ -- Migrate values that lived in the properties bag (common key names).
24
+ -- The bag is user-owned free text, so every cast is guarded: only JSON numbers
25
+ -- and numeric strings are promoted. An unparseable value (e.g. "30 min") is
26
+ -- left in the bag rather than aborting the migration transaction and
27
+ -- crash-looping every container at boot.
28
+ UPDATE lt_roles
29
+ SET sla_minutes = (properties->>'sla_minutes')::numeric
30
+ WHERE sla_minutes IS NULL
31
+ AND (jsonb_typeof(properties->'sla_minutes') = 'number'
32
+ OR properties->>'sla_minutes' ~ '^[0-9]+(\.[0-9]+)?$');
33
+
34
+ UPDATE lt_roles
35
+ SET sla_minutes = (properties->>'tat_minutes')::numeric
36
+ WHERE sla_minutes IS NULL
37
+ AND (jsonb_typeof(properties->'tat_minutes') = 'number'
38
+ OR properties->>'tat_minutes' ~ '^[0-9]+(\.[0-9]+)?$');
39
+
40
+ UPDATE lt_roles
41
+ SET target_per_hour = (properties->>'target_per_hour')::numeric
42
+ WHERE target_per_hour IS NULL
43
+ AND (jsonb_typeof(properties->'target_per_hour') = 'number'
44
+ OR properties->>'target_per_hour' ~ '^[0-9]+(\.[0-9]+)?$');
45
+
46
+ UPDATE lt_roles
47
+ SET worker_count = (properties->>'worker_count')::numeric
48
+ WHERE worker_count IS NULL
49
+ AND (jsonb_typeof(properties->'worker_count') = 'number'
50
+ OR properties->>'worker_count' ~ '^[0-9]+(\.[0-9]+)?$');
@@ -0,0 +1,13 @@
1
+ -- Station-metrics indexes on hmsh_escalations
2
+ --
3
+ -- hmsh_escalations is created by HotMesh at runtime, AFTER migrate() runs.
4
+ -- These indexes cannot be created here because the table does not yet exist
5
+ -- when this migration file executes.
6
+ --
7
+ -- The indexes are created in services/escalation/client.ts:installEscalationCompatView()
8
+ -- which runs after HotMesh has bootstrapped, under advisory lock 8675310.
9
+ -- That is the single authoritative creation path.
10
+ --
11
+ -- This migration file exists only to claim the 016 slot in the sequence
12
+ -- so the numbering stays stable. It is intentionally a no-op.
13
+ SELECT 1;
@@ -12,6 +12,9 @@ const ESCALATION_STATUS_BY_VERB = {
12
12
  reassigned: 'pending',
13
13
  resolved: 'resolved',
14
14
  cancelled: 'cancelled',
15
+ // hotmesh 0.25.1: the SLA timer on a condition(config + timeout) wait fired
16
+ // first — the engine transitioned the row and emitted .expired.
17
+ expired: 'expired',
15
18
  };
16
19
  /**
17
20
  * Translate a HotMesh `SystemEvent` into long-tail's `LTEvent`. Escalation
@@ -59,6 +59,15 @@ export declare const requireAdmin: RequestHandler;
59
59
  * This is the backend equivalent of the dashboard's `isBuilder` check.
60
60
  */
61
61
  export declare const requireBuilder: RequestHandler;
62
+ /**
63
+ * Middleware that requires role-management access. Must be placed AFTER requireAuth.
64
+ *
65
+ * Grants access to superadmins, admin-type users, and engineers — the backend
66
+ * equivalent of the dashboard's `isBuilder || isOps`. Ops (admin type) manage
67
+ * users and roles; builders (superadmin, engineer) are a superset. This is the
68
+ * gate for reading and writing role definitions.
69
+ */
70
+ export declare const requireRoleManager: RequestHandler;
62
71
  /**
63
72
  * Generate a JWT token. Utility for tests and token provisioning.
64
73
  */
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.requireBuilder = exports.requireAdmin = exports.requireAuth = exports.JwtAuthAdapter = void 0;
39
+ exports.requireRoleManager = exports.requireBuilder = exports.requireAdmin = exports.requireAuth = exports.JwtAuthAdapter = void 0;
40
40
  exports.createAuthMiddleware = createAuthMiddleware;
41
41
  exports.setAuthAdapter = setAuthAdapter;
42
42
  exports.signToken = signToken;
@@ -257,6 +257,43 @@ const requireBuilder = async (req, res, next) => {
257
257
  }
258
258
  };
259
259
  exports.requireBuilder = requireBuilder;
260
+ /**
261
+ * Middleware that requires role-management access. Must be placed AFTER requireAuth.
262
+ *
263
+ * Grants access to superadmins, admin-type users, and engineers — the backend
264
+ * equivalent of the dashboard's `isBuilder || isOps`. Ops (admin type) manage
265
+ * users and roles; builders (superadmin, engineer) are a superset. This is the
266
+ * gate for reading and writing role definitions.
267
+ */
268
+ const requireRoleManager = async (req, res, next) => {
269
+ try {
270
+ if (!req.auth?.userId) {
271
+ res.status(403).json({ error: 'Forbidden' });
272
+ return;
273
+ }
274
+ // Fast path: trust the JWT role claim for admin/superadmin
275
+ if (req.auth.role === 'admin' || req.auth.role === 'superadmin') {
276
+ next();
277
+ return;
278
+ }
279
+ // Slow path: check database for superadmin role type
280
+ if (await (0, user_1.isSuperAdmin)(req.auth.userId)) {
281
+ next();
282
+ return;
283
+ }
284
+ // Check database for engineer role (builder)
285
+ const { hasRole } = await Promise.resolve().then(() => __importStar(require('../services/user/roles')));
286
+ if (await hasRole(req.auth.userId, 'engineer')) {
287
+ next();
288
+ return;
289
+ }
290
+ res.status(403).json({ error: 'Forbidden: role-management access required' });
291
+ }
292
+ catch {
293
+ res.status(403).json({ error: 'Forbidden' });
294
+ }
295
+ };
296
+ exports.requireRoleManager = requireRoleManager;
260
297
  /**
261
298
  * Generate a JWT token. Utility for tests and token provisioning.
262
299
  */
@@ -153,6 +153,16 @@ function registerListRoutes(router) {
153
153
  const result = await api.listFacetKeys(undefined, req.auth);
154
154
  res.status(result.status).json(result.data ?? { error: result.error });
155
155
  });
156
+ /**
157
+ * GET /api/escalations/station-metrics
158
+ * Per-role P99/P50/avg/max wait + work times, resolved count, and in-arrears
159
+ * count. Drives the Operations station pace chart.
160
+ * RBAC: superadmin sees all; others scoped to their roles.
161
+ */
162
+ router.get('/station-metrics', async (req, res) => {
163
+ const result = await api.getStationMetrics({ period: req.query.period || undefined }, req.auth);
164
+ res.status(result.status).json(result.data ?? { error: result.error });
165
+ });
156
166
  /**
157
167
  * GET /api/escalations/stats
158
168
  * Aggregated escalation statistics.
@@ -58,7 +58,7 @@ router.get('/details', async (_req, res) => {
58
58
  * Create a standalone role. Requires admin.
59
59
  * Body: { role: string }
60
60
  */
61
- router.post('/', auth_1.requireBuilder, async (req, res) => {
61
+ router.post('/', auth_1.requireRoleManager, async (req, res) => {
62
62
  const { role } = req.body || {};
63
63
  const result = await api.createRole({ role });
64
64
  res.status(result.status).json(result.data ?? { error: result.error });
@@ -76,7 +76,7 @@ router.get('/escalation-chains', async (_req, res) => {
76
76
  * Add a single escalation chain entry. Requires admin.
77
77
  * Body: { source_role: string, target_role: string }
78
78
  */
79
- router.post('/escalation-chains', auth_1.requireBuilder, async (req, res) => {
79
+ router.post('/escalation-chains', auth_1.requireRoleManager, async (req, res) => {
80
80
  const { source_role, target_role } = req.body || {};
81
81
  const result = await api.addEscalationChain({ source_role, target_role });
82
82
  res.status(result.status).json(result.data ?? { error: result.error });
@@ -86,12 +86,23 @@ router.post('/escalation-chains', auth_1.requireBuilder, async (req, res) => {
86
86
  * Remove a single escalation chain entry. Requires admin.
87
87
  * Body: { source_role: string, target_role: string }
88
88
  */
89
- router.delete('/escalation-chains', auth_1.requireBuilder, async (req, res) => {
89
+ router.delete('/escalation-chains', auth_1.requireRoleManager, async (req, res) => {
90
90
  const { source_role, target_role } = req.body || {};
91
91
  const result = await api.removeEscalationChain({ source_role, target_role });
92
92
  res.status(result.status).json(result.data ?? { error: result.error });
93
93
  });
94
94
  // ── Parameterized routes (must come AFTER named routes) ───────────────────
95
+ /**
96
+ * PATCH /api/roles/:role
97
+ * Update role metadata. Requires admin.
98
+ * Body: { title?, description?, form_schema?, metadata_schema?, properties?, ops_visible?, parent_role?, sla_minutes?, target_per_hour?, worker_count? }
99
+ */
100
+ router.patch('/:role', auth_1.requireRoleManager, async (req, res) => {
101
+ // URL is the resource identity — a `role` key in the body must not redirect
102
+ // the write to a different row.
103
+ const result = await api.updateRole({ ...req.body, role: req.params.role });
104
+ res.status(result.status).json(result.data ?? { error: result.error });
105
+ });
95
106
  /**
96
107
  * GET /api/roles/:role/escalation-targets
97
108
  * Get allowed escalation targets for a specific role.
@@ -105,7 +116,7 @@ router.get('/:role/escalation-targets', async (req, res) => {
105
116
  * Replace escalation targets for a role. Requires admin.
106
117
  * Body: { targets: string[] }
107
118
  */
108
- router.put('/:role/escalation-targets', auth_1.requireBuilder, async (req, res) => {
119
+ router.put('/:role/escalation-targets', auth_1.requireRoleManager, async (req, res) => {
109
120
  const { targets } = req.body || {};
110
121
  const result = await api.replaceEscalationTargets({ role: req.params.role, targets });
111
122
  res.status(result.status).json(result.data ?? { error: result.error });
@@ -114,7 +125,7 @@ router.put('/:role/escalation-targets', auth_1.requireBuilder, async (req, res)
114
125
  * DELETE /api/roles/:role
115
126
  * Delete a role if it has no references. Requires admin.
116
127
  */
117
- router.delete('/:role', auth_1.requireBuilder, async (req, res) => {
128
+ router.delete('/:role', auth_1.requireRoleManager, async (req, res) => {
118
129
  const result = await api.deleteRole({ role: req.params.role });
119
130
  res.status(result.status).json(result.data ?? { error: result.error });
120
131
  });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ortho-populate — Populate the Operations dashboard with ortho pipeline history.
3
+ *
4
+ * Adapted from the boilerplate's 08-day-resolver.ts pattern:
5
+ * - concurrent claim loop + resolve loop + watchdog via Promise.all
6
+ * - hold time read from esc.claimed_at (API), not tracked in memory
7
+ * - simple resolve payload { approved: true, station: role }
8
+ *
9
+ * Usage:
10
+ * npx ts-node scripts/ortho-populate.ts
11
+ * ORDERS=50 HOLD_S=3 npx ts-node scripts/ortho-populate.ts
12
+ * ORDERS=5 HOLD_S=0 POLL_MS=500 npx ts-node scripts/ortho-populate.ts
13
+ *
14
+ * Env vars:
15
+ * ORDERS — number of orders to run (default 20)
16
+ * HOLD_S — seconds to hold each escalation before resolving (default 2)
17
+ * POLL_MS — poll interval ms (default 1000)
18
+ * BASE_URL — server URL (default http://localhost:3000)
19
+ */
20
+ declare const BASE_URL: string;
21
+ declare const ORDERS: number;
22
+ declare const HOLD_S: number;
23
+ declare const POLL_MS: number;
24
+ declare const ORTHO_ROLES: string[];
25
+ declare let token: string;
26
+ declare let userId: string;
27
+ declare let totalClaimed: number;
28
+ declare let totalResolved: number;
29
+ declare function ts(): string;
30
+ declare function sleep(ms: number): Promise<unknown>;
31
+ declare function ageSeconds(iso: string): number;
32
+ declare function api(method: string, path: string, body?: unknown): Promise<any>;
33
+ declare function login(): Promise<void>;
34
+ declare function claimBatch(batchTag: string): Promise<number>;
35
+ declare function resolveBatch(batchTag: string): Promise<number>;
36
+ declare function runOrders(batchTag: string, target: number): Promise<void>;
37
+ declare function main(): Promise<void>;