@hotmeshio/long-tail 0.5.12 → 0.6.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 (303) hide show
  1. package/README.md +1 -0
  2. package/build/api/escalations/bulk.js +9 -1
  3. package/build/api/escalations/cancel.js +5 -7
  4. package/build/api/escalations/claim.js +18 -15
  5. package/build/api/escalations/create.d.ts +6 -0
  6. package/build/api/escalations/create.js +11 -8
  7. package/build/api/escalations/facets.d.ts +18 -0
  8. package/build/api/escalations/facets.js +115 -0
  9. package/build/api/escalations/helpers.d.ts +70 -1
  10. package/build/api/escalations/helpers.js +101 -7
  11. package/build/api/escalations/index.d.ts +3 -2
  12. package/build/api/escalations/index.js +7 -1
  13. package/build/api/escalations/list.d.ts +21 -2
  14. package/build/api/escalations/list.js +109 -10
  15. package/build/api/escalations/metadata.js +22 -27
  16. package/build/api/escalations/resolve.d.ts +19 -1
  17. package/build/api/escalations/resolve.js +111 -26
  18. package/build/api/escalations/single.js +8 -8
  19. package/build/api/users.d.ts +6 -0
  20. package/build/api/users.js +29 -1
  21. package/build/bin/ltc.js +40 -0
  22. package/build/lib/cli/commands/escalations.d.ts +36 -0
  23. package/build/lib/cli/commands/escalations.js +98 -0
  24. package/build/lib/db/schemas/001_schema.sql +10 -5
  25. package/build/lib/db/schemas/012_lt_tasks_workflow_id_unique.sql +20 -0
  26. package/build/lib/db/schemas/013_role_scope.sql +37 -0
  27. package/build/modules/version.d.ts +10 -5
  28. package/build/modules/version.js +27 -11
  29. package/build/routes/escalations/facets.d.ts +7 -0
  30. package/build/routes/escalations/facets.js +81 -0
  31. package/build/routes/escalations/index.js +3 -0
  32. package/build/routes/escalations/list.js +46 -0
  33. package/build/routes/escalations/resolve.js +22 -4
  34. package/build/routes/users.js +9 -5
  35. package/build/sdk/index.d.ts +33 -2
  36. package/build/sdk/index.js +7 -0
  37. package/build/services/escalation/crud.d.ts +56 -6
  38. package/build/services/escalation/crud.js +102 -31
  39. package/build/services/escalation/facet-sql.d.ts +18 -0
  40. package/build/services/escalation/facet-sql.js +118 -0
  41. package/build/services/escalation/facets.d.ts +45 -0
  42. package/build/services/escalation/facets.js +218 -0
  43. package/build/services/escalation/index.d.ts +1 -0
  44. package/build/services/escalation/index.js +8 -1
  45. package/build/services/escalation/queries.d.ts +45 -0
  46. package/build/services/escalation/queries.js +107 -6
  47. package/build/services/escalation/sql.d.ts +10 -4
  48. package/build/services/escalation/sql.js +36 -8
  49. package/build/services/interceptor/activities/escalation.d.ts +2 -0
  50. package/build/services/interceptor/activities/escalation.js +1 -1
  51. package/build/services/interceptor/lifecycle.js +4 -0
  52. package/build/services/mcp/server-tools.js +10 -4
  53. package/build/services/orchestrator/condition.js +6 -2
  54. package/build/services/role/index.js +2 -3
  55. package/build/services/role/sql.d.ts +8 -0
  56. package/build/services/role/sql.js +17 -1
  57. package/build/services/task/crud.js +22 -14
  58. package/build/services/task/process.js +3 -21
  59. package/build/services/task/sql.d.ts +24 -1
  60. package/build/services/task/sql.js +65 -1
  61. package/build/services/user/crud.d.ts +1 -0
  62. package/build/services/user/crud.js +29 -11
  63. package/build/services/user/index.d.ts +2 -1
  64. package/build/services/user/index.js +12 -1
  65. package/build/services/user/rbac.d.ts +18 -0
  66. package/build/services/user/rbac.js +27 -0
  67. package/build/services/user/roles.d.ts +7 -2
  68. package/build/services/user/roles.js +21 -5
  69. package/build/services/user/scope.d.ts +22 -0
  70. package/build/services/user/scope.js +50 -0
  71. package/build/services/user/seed-admin.js +34 -17
  72. package/build/services/user/sql.d.ts +18 -7
  73. package/build/services/user/sql.js +38 -13
  74. package/build/services/user/sso-provision.js +41 -24
  75. package/build/services/user/types.d.ts +9 -5
  76. package/build/system/mcp-servers/admin/escalations.js +65 -0
  77. package/build/system/mcp-servers/admin/schemas.d.ts +325 -0
  78. package/build/system/mcp-servers/admin/schemas.js +63 -2
  79. package/build/system/mcp-servers/admin/users.js +28 -2
  80. package/build/tsconfig.tsbuildinfo +1 -1
  81. package/build/types/envelope.d.ts +7 -0
  82. package/build/types/facets.d.ts +60 -0
  83. package/build/types/facets.js +10 -0
  84. package/build/types/index.d.ts +2 -1
  85. package/build/types/user.d.ts +11 -0
  86. package/dashboard/dist/assets/AdminDashboard-2CZ8py9X.js +2 -0
  87. package/dashboard/dist/assets/{AdminDashboard-LTiYLuzq.js.map → AdminDashboard-2CZ8py9X.js.map} +1 -1
  88. package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js → AgentConfigPage-D68sodgo.js} +2 -2
  89. package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js.map → AgentConfigPage-D68sodgo.js.map} +1 -1
  90. package/dashboard/dist/assets/{AgentDetailPage-BSUnDvGp.js → AgentDetailPage-BmhuWbKi.js} +2 -2
  91. package/dashboard/dist/assets/{AgentDetailPage-BSUnDvGp.js.map → AgentDetailPage-BmhuWbKi.js.map} +1 -1
  92. package/dashboard/dist/assets/{AgentsPage-DpS0cCYe.js → AgentsPage-C4SKQVeU.js} +2 -2
  93. package/dashboard/dist/assets/{AgentsPage-DpS0cCYe.js.map → AgentsPage-C4SKQVeU.js.map} +1 -1
  94. package/dashboard/dist/assets/AvailableEscalationsPage-9RBEYwkt.js +2 -0
  95. package/dashboard/dist/assets/AvailableEscalationsPage-9RBEYwkt.js.map +1 -0
  96. package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js → BotPicker-D-FvXJOM.js} +2 -2
  97. package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js.map → BotPicker-D-FvXJOM.js.map} +1 -1
  98. package/dashboard/dist/assets/{CapabilitiesPage-DLIleX2L.js → CapabilitiesPage-ByDf6WJr.js} +2 -2
  99. package/dashboard/dist/assets/{CapabilitiesPage-DLIleX2L.js.map → CapabilitiesPage-ByDf6WJr.js.map} +1 -1
  100. package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js → CollapsibleSection-BjGNYEvh.js} +2 -2
  101. package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js.map → CollapsibleSection-BjGNYEvh.js.map} +1 -1
  102. package/dashboard/dist/assets/CopyableId-blg-gZvT.js +2 -0
  103. package/dashboard/dist/assets/CopyableId-blg-gZvT.js.map +1 -0
  104. package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js → CredentialsPage-DvCA-wQN.js} +2 -2
  105. package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js.map → CredentialsPage-DvCA-wQN.js.map} +1 -1
  106. package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js → CronLabel-DBMurd_c.js} +2 -2
  107. package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js.map → CronLabel-DBMurd_c.js.map} +1 -1
  108. package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js → CustomDurationPicker-DHflFoEa.js} +2 -2
  109. package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js.map → CustomDurationPicker-DHflFoEa.js.map} +1 -1
  110. package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js → DropZone-CUtdp99Z.js} +2 -2
  111. package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js.map → DropZone-CUtdp99Z.js.map} +1 -1
  112. package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js → ElapsedCell-CSjcVOpo.js} +2 -2
  113. package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js.map → ElapsedCell-CSjcVOpo.js.map} +1 -1
  114. package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js → EscalationsOverview-BUqRNJbK.js} +2 -2
  115. package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js.map → EscalationsOverview-BUqRNJbK.js.map} +1 -1
  116. package/dashboard/dist/assets/{EventTable-CeKfFk3m.js → EventTable-Chz-6XPE.js} +2 -2
  117. package/dashboard/dist/assets/{EventTable-CeKfFk3m.js.map → EventTable-Chz-6XPE.js.map} +1 -1
  118. package/dashboard/dist/assets/{GraphInvokePage-BZBPBCSq.js → GraphInvokePage-BXdgciWg.js} +2 -2
  119. package/dashboard/dist/assets/{GraphInvokePage-BZBPBCSq.js.map → GraphInvokePage-BXdgciWg.js.map} +1 -1
  120. package/dashboard/dist/assets/{HomePage-dM7U2tl6.js → HomePage-CEMxnk0r.js} +2 -2
  121. package/dashboard/dist/assets/{HomePage-dM7U2tl6.js.map → HomePage-CEMxnk0r.js.map} +1 -1
  122. package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js → ListToolbar-_73JCdbB.js} +2 -2
  123. package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js.map → ListToolbar-_73JCdbB.js.map} +1 -1
  124. package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js → McpOverview-DfdqxhSz.js} +2 -2
  125. package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js.map → McpOverview-DfdqxhSz.js.map} +1 -1
  126. package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js → McpQueryDetailPage-DPAljieY.js} +2 -2
  127. package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js.map → McpQueryDetailPage-DPAljieY.js.map} +1 -1
  128. package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js → McpQueryPage-D5cSziYS.js} +2 -2
  129. package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js.map → McpQueryPage-D5cSziYS.js.map} +1 -1
  130. package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js → McpRunDetailPage-DwNb9wZg.js} +2 -2
  131. package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js.map → McpRunDetailPage-DwNb9wZg.js.map} +1 -1
  132. package/dashboard/dist/assets/{McpRunsPage-CPMYWgm_.js → McpRunsPage-O5H6yStl.js} +2 -2
  133. package/dashboard/dist/assets/{McpRunsPage-CPMYWgm_.js.map → McpRunsPage-O5H6yStl.js.map} +1 -1
  134. package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js → NamespacePill-uW-xOedj.js} +2 -2
  135. package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js.map → NamespacePill-uW-xOedj.js.map} +1 -1
  136. package/dashboard/dist/assets/OperatorDashboard-DYw9Ekv5.js +2 -0
  137. package/dashboard/dist/assets/{OperatorDashboard-g9o7I925.js.map → OperatorDashboard-DYw9Ekv5.js.map} +1 -1
  138. package/dashboard/dist/assets/PageHeader-DVOZjBfL.js +2 -0
  139. package/dashboard/dist/assets/PageHeader-DVOZjBfL.js.map +1 -0
  140. package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js → PageHeaderWithStats-DtTpDHOd.js} +2 -2
  141. package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js.map → PageHeaderWithStats-DtTpDHOd.js.map} +1 -1
  142. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js +2 -0
  143. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js.map +1 -0
  144. package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js → ProcessDetailPage-B0Dokcx9.js} +2 -2
  145. package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js.map → ProcessDetailPage-B0Dokcx9.js.map} +1 -1
  146. package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js → ProcessesListPage-Cc87WIjL.js} +2 -2
  147. package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js.map → ProcessesListPage-Cc87WIjL.js.map} +1 -1
  148. package/dashboard/dist/assets/RolePill-BaW-30TC.js +2 -0
  149. package/dashboard/dist/assets/RolePill-BaW-30TC.js.map +1 -0
  150. package/dashboard/dist/assets/{RolesPage-B2Kdf-L9.js → RolesPage-cp6PiF08.js} +2 -2
  151. package/dashboard/dist/assets/{RolesPage-B2Kdf-L9.js.map → RolesPage-cp6PiF08.js.map} +1 -1
  152. package/dashboard/dist/assets/RowActions-BA9L9djf.js +2 -0
  153. package/dashboard/dist/assets/RowActions-BA9L9djf.js.map +1 -0
  154. package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js → RunAsSelector-2LWkjSnO.js} +2 -2
  155. package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js.map → RunAsSelector-2LWkjSnO.js.map} +1 -1
  156. package/dashboard/dist/assets/{StickyPagination-BWhFSr2d.js → StickyPagination-BQEm--kI.js} +2 -2
  157. package/dashboard/dist/assets/{StickyPagination-BWhFSr2d.js.map → StickyPagination-BQEm--kI.js.map} +1 -1
  158. package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js → StreamMessageDetail-BTtw3GjX.js} +2 -2
  159. package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js.map → StreamMessageDetail-BTtw3GjX.js.map} +1 -1
  160. package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js → SwimlaneTimeline-DdXxP4fO.js} +2 -2
  161. package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js.map → SwimlaneTimeline-DdXxP4fO.js.map} +1 -1
  162. package/dashboard/dist/assets/{TagInput-CZSeyesi.js → TagInput-x9XTuqMb.js} +2 -2
  163. package/dashboard/dist/assets/{TagInput-CZSeyesi.js.map → TagInput-x9XTuqMb.js.map} +1 -1
  164. package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js → TaskDetailPage-B8veYG4X.js} +2 -2
  165. package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js.map → TaskDetailPage-B8veYG4X.js.map} +1 -1
  166. package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js → TaskQueuePill-DSEQZ-HT.js} +2 -2
  167. package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js.map → TaskQueuePill-DSEQZ-HT.js.map} +1 -1
  168. package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js → TasksListPage-CsN1gZj5.js} +2 -2
  169. package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js.map → TasksListPage-CsN1gZj5.js.map} +1 -1
  170. package/dashboard/dist/assets/{TimeAgo-CUIYqSt_.js → TimeAgo-n182EcLx.js} +2 -2
  171. package/dashboard/dist/assets/{TimeAgo-CUIYqSt_.js.map → TimeAgo-n182EcLx.js.map} +1 -1
  172. package/dashboard/dist/assets/{TimestampCell--tnreI1i.js → TimestampCell-2dlcQAfX.js} +2 -2
  173. package/dashboard/dist/assets/{TimestampCell--tnreI1i.js.map → TimestampCell-2dlcQAfX.js.map} +1 -1
  174. package/dashboard/dist/assets/{ToolPill-O09jsTQM.js → ToolPill-B_e4eT77.js} +2 -2
  175. package/dashboard/dist/assets/{ToolPill-O09jsTQM.js.map → ToolPill-B_e4eT77.js.map} +1 -1
  176. package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js → ToolTestPanel-Bhnncc6M.js} +2 -2
  177. package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js.map → ToolTestPanel-Bhnncc6M.js.map} +1 -1
  178. package/dashboard/dist/assets/{TopicDetailPage-rh_dsTJI.js → TopicDetailPage-29lsNsqI.js} +2 -2
  179. package/dashboard/dist/assets/{TopicDetailPage-rh_dsTJI.js.map → TopicDetailPage-29lsNsqI.js.map} +1 -1
  180. package/dashboard/dist/assets/{TopicsPage-CD_40_v-.js → TopicsPage-KJ7_LAvo.js} +2 -2
  181. package/dashboard/dist/assets/{TopicsPage-CD_40_v-.js.map → TopicsPage-KJ7_LAvo.js.map} +1 -1
  182. package/dashboard/dist/assets/{UserName-C__c8BN0.js → UserName-CMoFi9zb.js} +2 -2
  183. package/dashboard/dist/assets/{UserName-C__c8BN0.js.map → UserName-CMoFi9zb.js.map} +1 -1
  184. package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js → WorkflowExecutionPage-Cymfx-Bp.js} +2 -2
  185. package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js.map → WorkflowExecutionPage-Cymfx-Bp.js.map} +1 -1
  186. package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js → WorkflowPill-B66_AnaY.js} +2 -2
  187. package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js.map → WorkflowPill-B66_AnaY.js.map} +1 -1
  188. package/dashboard/dist/assets/{WorkflowsDashboard-BgI-iOP6.js → WorkflowsDashboard-CqLN8spd.js} +2 -2
  189. package/dashboard/dist/assets/{WorkflowsDashboard-BgI-iOP6.js.map → WorkflowsDashboard-CqLN8spd.js.map} +1 -1
  190. package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js → WorkflowsOverview-0Rc2WT6q.js} +2 -2
  191. package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js.map → WorkflowsOverview-0Rc2WT6q.js.map} +1 -1
  192. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js → YamlWorkflowDetailPage-CARQ7_LN.js} +2 -2
  193. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js.map → YamlWorkflowDetailPage-CARQ7_LN.js.map} +1 -1
  194. package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js → YamlWorkflowsPage-B-7sz1nf.js} +2 -2
  195. package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js.map → YamlWorkflowsPage-B-7sz1nf.js.map} +1 -1
  196. package/dashboard/dist/assets/{agents-xstNkF6c.js → agents-zM43d5Yf.js} +2 -2
  197. package/dashboard/dist/assets/{agents-xstNkF6c.js.map → agents-zM43d5Yf.js.map} +1 -1
  198. package/dashboard/dist/assets/{bots-BztAI0s_.js → bots-D5BasOGz.js} +2 -2
  199. package/dashboard/dist/assets/{bots-BztAI0s_.js.map → bots-D5BasOGz.js.map} +1 -1
  200. package/dashboard/dist/assets/{capabilities-CEDwtxNL.js → capabilities-BT3PW9CZ.js} +2 -2
  201. package/dashboard/dist/assets/{capabilities-CEDwtxNL.js.map → capabilities-BT3PW9CZ.js.map} +1 -1
  202. package/dashboard/dist/assets/{controlplane-BlGEsRKe.js → controlplane-NpJr49rN.js} +2 -2
  203. package/dashboard/dist/assets/{controlplane-BlGEsRKe.js.map → controlplane-NpJr49rN.js.map} +1 -1
  204. package/dashboard/dist/assets/{escalation-iI4QKlP-.js → escalation-DrzQolPG.js} +2 -2
  205. package/dashboard/dist/assets/{escalation-iI4QKlP-.js.map → escalation-DrzQolPG.js.map} +1 -1
  206. package/dashboard/dist/assets/escalation-columns-QzwmtD60.js +2 -0
  207. package/dashboard/dist/assets/escalation-columns-QzwmtD60.js.map +1 -0
  208. package/dashboard/dist/assets/index-B5w4Gjyk.js +6 -0
  209. package/dashboard/dist/assets/index-B5w4Gjyk.js.map +1 -0
  210. package/dashboard/dist/assets/{index-CMviPjXQ.js → index-BCT4kqGR.js} +2 -2
  211. package/dashboard/dist/assets/{index-CMviPjXQ.js.map → index-BCT4kqGR.js.map} +1 -1
  212. package/dashboard/dist/assets/{index-B7VC0vjf.js → index-C69XvBfg.js} +2 -2
  213. package/dashboard/dist/assets/{index-B7VC0vjf.js.map → index-C69XvBfg.js.map} +1 -1
  214. package/dashboard/dist/assets/index-CHw4Fl0t.css +1 -0
  215. package/dashboard/dist/assets/{index-CUfgt3mV.js → index-C_NwHTMh.js} +2 -2
  216. package/dashboard/dist/assets/{index-CUfgt3mV.js.map → index-C_NwHTMh.js.map} +1 -1
  217. package/dashboard/dist/assets/{index-CAqEy2Db.js → index-Cq0trGqf.js} +2 -2
  218. package/dashboard/dist/assets/{index-CAqEy2Db.js.map → index-Cq0trGqf.js.map} +1 -1
  219. package/dashboard/dist/assets/{index-D1xVY4JR.js → index-DE0r9SEX.js} +22 -22
  220. package/dashboard/dist/assets/index-DE0r9SEX.js.map +1 -0
  221. package/dashboard/dist/assets/{index-DjJ6VCuc.js → index-DH_CiD0W.js} +2 -2
  222. package/dashboard/dist/assets/{index-DjJ6VCuc.js.map → index-DH_CiD0W.js.map} +1 -1
  223. package/dashboard/dist/assets/{index-DzVYjoex.js → index-K23FdS-T.js} +2 -2
  224. package/dashboard/dist/assets/{index-DzVYjoex.js.map → index-K23FdS-T.js.map} +1 -1
  225. package/dashboard/dist/assets/{index-FuhiJuHL.js → index-f6K1UNrn.js} +2 -2
  226. package/dashboard/dist/assets/{index-FuhiJuHL.js.map → index-f6K1UNrn.js.map} +1 -1
  227. package/dashboard/dist/assets/{index-B5qsp8lb.js → index-qr1fndqM.js} +2 -2
  228. package/dashboard/dist/assets/{index-B5qsp8lb.js.map → index-qr1fndqM.js.map} +1 -1
  229. package/dashboard/dist/assets/index-vKTvJ_li.js +2 -0
  230. package/dashboard/dist/assets/index-vKTvJ_li.js.map +1 -0
  231. package/dashboard/dist/assets/{index-BUmnJ4eT.js → index-y-3ODtEE.js} +2 -2
  232. package/dashboard/dist/assets/{index-BUmnJ4eT.js.map → index-y-3ODtEE.js.map} +1 -1
  233. package/dashboard/dist/assets/{index-Dsi5o7dx.js → index-y56Ds-LB.js} +2 -2
  234. package/dashboard/dist/assets/{index-Dsi5o7dx.js.map → index-y56Ds-LB.js.map} +1 -1
  235. package/dashboard/dist/assets/{knowledge-BKg0cr39.js → knowledge-DSSATOTX.js} +2 -2
  236. package/dashboard/dist/assets/{knowledge-BKg0cr39.js.map → knowledge-DSSATOTX.js.map} +1 -1
  237. package/dashboard/dist/assets/{mcp-CVdHpKlz.js → mcp-CfJFVo5k.js} +2 -2
  238. package/dashboard/dist/assets/{mcp-CVdHpKlz.js.map → mcp-CfJFVo5k.js.map} +1 -1
  239. package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js → mcp-query-DKVx2Q-X.js} +2 -2
  240. package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js.map → mcp-query-DKVx2Q-X.js.map} +1 -1
  241. package/dashboard/dist/assets/{pipelines-BOEv7P6O.js → pipelines-BSWzE-6M.js} +2 -2
  242. package/dashboard/dist/assets/{pipelines-BOEv7P6O.js.map → pipelines-BSWzE-6M.js.map} +1 -1
  243. package/dashboard/dist/assets/{roles-c5cV2QYQ.js → roles-t9HzhI5N.js} +2 -2
  244. package/dashboard/dist/assets/{roles-c5cV2QYQ.js.map → roles-t9HzhI5N.js.map} +1 -1
  245. package/dashboard/dist/assets/{tasks-JV3BjeEF.js → tasks-B7rUihoH.js} +2 -2
  246. package/dashboard/dist/assets/{tasks-JV3BjeEF.js.map → tasks-B7rUihoH.js.map} +1 -1
  247. package/dashboard/dist/assets/{topics-BDaPnhnQ.js → topics-PC2fEiyD.js} +2 -2
  248. package/dashboard/dist/assets/{topics-BDaPnhnQ.js.map → topics-PC2fEiyD.js.map} +1 -1
  249. package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js → useEventHooks-DK9mw1cI.js} +2 -2
  250. package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js.map → useEventHooks-DK9mw1cI.js.map} +1 -1
  251. package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js → useNamespace-3IjtUtAw.js} +2 -2
  252. package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js.map → useNamespace-3IjtUtAw.js.map} +1 -1
  253. package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js → useYamlActivityEvents-uxukp3gO.js} +2 -2
  254. package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js.map → useYamlActivityEvents-uxukp3gO.js.map} +1 -1
  255. package/dashboard/dist/assets/users-CC4B5Fm5.js +2 -0
  256. package/dashboard/dist/assets/users-CC4B5Fm5.js.map +1 -0
  257. package/dashboard/dist/assets/{vendor-icons-D5ycKPA2.js → vendor-icons-Dy5Jpknf.js} +2 -2
  258. package/dashboard/dist/assets/{vendor-icons-D5ycKPA2.js.map → vendor-icons-Dy5Jpknf.js.map} +1 -1
  259. package/dashboard/dist/assets/{workflows-DF-9Voyi.js → workflows-S7MF1nNk.js} +2 -2
  260. package/dashboard/dist/assets/{workflows-DF-9Voyi.js.map → workflows-S7MF1nNk.js.map} +1 -1
  261. package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js → yaml-workflows-WfdaMHNn.js} +2 -2
  262. package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js.map → yaml-workflows-WfdaMHNn.js.map} +1 -1
  263. package/dashboard/dist/index.html +3 -3
  264. package/docs/api/http/escalations.md +131 -10
  265. package/docs/api/http/roles.md +44 -9
  266. package/docs/api/http/users.md +15 -6
  267. package/docs/api/mcp/admin.md +8 -2
  268. package/docs/api/mcp/human-queue.md +34 -2
  269. package/docs/api/sdk/escalations.md +155 -2
  270. package/docs/api/sdk/roles.md +11 -0
  271. package/docs/api/sdk/users.md +26 -1
  272. package/docs/auth.md +1 -1
  273. package/docs/dashboard.md +5 -4
  274. package/docs/data.md +9 -2
  275. package/docs/faceted-routing.md +221 -0
  276. package/docs/hitl-guide.md +50 -0
  277. package/docs/iam.md +31 -0
  278. package/docs/mcp.md +8 -0
  279. package/package.json +2 -2
  280. package/dashboard/dist/assets/AdminDashboard-LTiYLuzq.js +0 -2
  281. package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js +0 -2
  282. package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js.map +0 -1
  283. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js +0 -2
  284. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js.map +0 -1
  285. package/dashboard/dist/assets/OperatorDashboard-g9o7I925.js +0 -2
  286. package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js +0 -2
  287. package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js.map +0 -1
  288. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js +0 -2
  289. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js.map +0 -1
  290. package/dashboard/dist/assets/RolePill-Du-aNnjR.js +0 -2
  291. package/dashboard/dist/assets/RolePill-Du-aNnjR.js.map +0 -1
  292. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js +0 -2
  293. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js.map +0 -1
  294. package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js +0 -2
  295. package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js.map +0 -1
  296. package/dashboard/dist/assets/index-CP0JEQhZ.js +0 -6
  297. package/dashboard/dist/assets/index-CP0JEQhZ.js.map +0 -1
  298. package/dashboard/dist/assets/index-ClvYSNZd.css +0 -1
  299. package/dashboard/dist/assets/index-D1xVY4JR.js.map +0 -1
  300. package/dashboard/dist/assets/index-DPLy-jwO.js +0 -2
  301. package/dashboard/dist/assets/index-DPLy-jwO.js.map +0 -1
  302. package/dashboard/dist/assets/users-Pn85K_3C.js +0 -2
  303. package/dashboard/dist/assets/users-Pn85K_3C.js.map +0 -1
@@ -35,11 +35,21 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.listEscalations = listEscalations;
37
37
  exports.listAvailableEscalations = listAvailableEscalations;
38
+ exports.listFacetKeys = listFacetKeys;
38
39
  exports.listDistinctTypes = listDistinctTypes;
39
40
  exports.getEscalationStats = getEscalationStats;
40
41
  const escalationService = __importStar(require("../../services/escalation"));
41
42
  const helpers_1 = require("./helpers");
42
- // ── List routes ────────────────────────────────────────────────────────────
43
+ /** True when the caller asked for anything the plain list path can't express. */
44
+ function hasFacetQuery(i) {
45
+ return !!(i.facets ||
46
+ i.block?.length ||
47
+ i.range?.length ||
48
+ i.exists?.length ||
49
+ i.orderBy?.length ||
50
+ i.roles?.length ||
51
+ i.available != null);
52
+ }
43
53
  /**
44
54
  * List escalations with optional filters.
45
55
  *
@@ -61,10 +71,40 @@ const helpers_1 = require("./helpers");
61
71
  */
62
72
  async function listEscalations(input, auth) {
63
73
  try {
64
- const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
65
- if (visibleRoles && visibleRoles.length === 0) {
74
+ const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
75
+ if (!scope.global && scope.allRoles.length === 0 && scope.selfRoles.length === 0) {
66
76
  return { status: 200, data: { escalations: [], total: 0 } };
67
77
  }
78
+ // Faceted path: the read-scope predicate composes with the full FacetQuery
79
+ // language (facets/block/range/exists/metadata-sort) IN SQL — the caller's
80
+ // role(s) can only NARROW within their scope, never widen past it.
81
+ if (hasFacetQuery(input)) {
82
+ const result = await escalationService.searchEscalationsFaceted({
83
+ global: scope.global,
84
+ visibleRoles: scope.global ? undefined : scope.allRoles,
85
+ selfRoles: scope.global ? undefined : scope.selfRoles,
86
+ meUserId: auth.userId,
87
+ facet: {
88
+ role: input.role,
89
+ roles: input.roles,
90
+ status: input.status,
91
+ available: input.available ?? (input.claimed ? false : undefined),
92
+ facets: input.facets,
93
+ block: input.block,
94
+ range: input.range,
95
+ exists: input.exists,
96
+ orderBy: input.orderBy,
97
+ },
98
+ type: input.type,
99
+ subtype: input.subtype,
100
+ priority: input.priority,
101
+ assigned_to: input.assigned_to,
102
+ search: input.search,
103
+ limit: input.limit ?? 50,
104
+ offset: input.offset ?? 0,
105
+ });
106
+ return { status: 200, data: result };
107
+ }
68
108
  const result = await escalationService.listEscalations({
69
109
  status: input.status,
70
110
  role: input.role,
@@ -78,7 +118,9 @@ async function listEscalations(input, auth) {
78
118
  sort_by: input.sort_by,
79
119
  order: input.order,
80
120
  search: input.search,
81
- visibleRoles,
121
+ visibleRoles: scope.global ? undefined : scope.allRoles,
122
+ selfRoles: scope.global ? undefined : scope.selfRoles,
123
+ meUserId: auth.userId,
82
124
  });
83
125
  return { status: 200, data: result };
84
126
  }
@@ -105,10 +147,37 @@ async function listEscalations(input, auth) {
105
147
  */
106
148
  async function listAvailableEscalations(input, auth) {
107
149
  try {
108
- const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
109
- if (visibleRoles && visibleRoles.length === 0) {
150
+ const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
151
+ if (!scope.global && scope.allRoles.length === 0 && scope.selfRoles.length === 0) {
110
152
  return { status: 200, data: { escalations: [], total: 0 } };
111
153
  }
154
+ // Faceted path — same as listEscalations but pinned to the available pool.
155
+ if (hasFacetQuery(input)) {
156
+ const result = await escalationService.searchEscalationsFaceted({
157
+ global: scope.global,
158
+ visibleRoles: scope.global ? undefined : scope.allRoles,
159
+ selfRoles: scope.global ? undefined : scope.selfRoles,
160
+ meUserId: auth.userId,
161
+ facet: {
162
+ role: input.role,
163
+ roles: input.roles,
164
+ status: 'pending',
165
+ available: true,
166
+ facets: input.facets,
167
+ block: input.block,
168
+ range: input.range,
169
+ exists: input.exists,
170
+ orderBy: input.orderBy,
171
+ },
172
+ type: input.type,
173
+ subtype: input.subtype,
174
+ priority: input.priority,
175
+ search: input.search,
176
+ limit: input.limit ?? 50,
177
+ offset: input.offset ?? 0,
178
+ });
179
+ return { status: 200, data: result };
180
+ }
112
181
  const result = await escalationService.listAvailableEscalations({
113
182
  role: input.role,
114
183
  type: input.type,
@@ -119,7 +188,9 @@ async function listAvailableEscalations(input, auth) {
119
188
  sort_by: input.sort_by,
120
189
  order: input.order,
121
190
  search: input.search,
122
- visibleRoles,
191
+ visibleRoles: scope.global ? undefined : scope.allRoles,
192
+ selfRoles: scope.global ? undefined : scope.selfRoles,
193
+ meUserId: auth.userId,
123
194
  });
124
195
  return { status: 200, data: result };
125
196
  }
@@ -127,6 +198,31 @@ async function listAvailableEscalations(input, auth) {
127
198
  return { status: 500, error: err.message };
128
199
  }
129
200
  }
201
+ /**
202
+ * List the distinct top-level metadata facet KEYS visible to the caller. Powers the
203
+ * faceted-query UI's key autocomplete — only keys that actually exist in the caller's
204
+ * (role-scoped) escalations are offered, never description-only text.
205
+ *
206
+ * @returns `{ status: 200, data: { keys: string[] } }`
207
+ */
208
+ async function listFacetKeys(_input, auth) {
209
+ try {
210
+ const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
211
+ if (!scope.global && scope.allRoles.length === 0 && scope.selfRoles.length === 0) {
212
+ return { status: 200, data: { keys: [] } };
213
+ }
214
+ const keys = await escalationService.listFacetKeys({
215
+ global: scope.global,
216
+ visibleRoles: scope.global ? undefined : scope.allRoles,
217
+ selfRoles: scope.global ? undefined : scope.selfRoles,
218
+ meUserId: auth.userId,
219
+ });
220
+ return { status: 200, data: { keys } };
221
+ }
222
+ catch (err) {
223
+ return { status: 500, error: err.message };
224
+ }
225
+ }
130
226
  /**
131
227
  * List all distinct escalation type values.
132
228
  *
@@ -150,8 +246,11 @@ async function listDistinctTypes() {
150
246
  */
151
247
  async function getEscalationStats(input, auth) {
152
248
  try {
153
- const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
154
- if (visibleRoles && visibleRoles.length === 0) {
249
+ // Aggregate stats reflect read_all roles only. read_self memberships get the
250
+ // single-item surface (auto user-mode), not a queue-overview dashboard, so
251
+ // their items are deliberately not aggregated here (and not leaked in counts).
252
+ const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
253
+ if (!scope.global && scope.allRoles.length === 0) {
155
254
  return {
156
255
  status: 200,
157
256
  data: {
@@ -164,7 +263,7 @@ async function getEscalationStats(input, auth) {
164
263
  },
165
264
  };
166
265
  }
167
- const stats = await escalationService.getEscalationStats(visibleRoles, input.period);
266
+ const stats = await escalationService.getEscalationStats(scope.global ? undefined : scope.allRoles, input.period);
168
267
  return { status: 200, data: stats };
169
268
  }
170
269
  catch (err) {
@@ -37,7 +37,6 @@ exports.findByMetadata = findByMetadata;
37
37
  exports.claimByMetadata = claimByMetadata;
38
38
  exports.resolveByMetadata = resolveByMetadata;
39
39
  const escalationService = __importStar(require("../../services/escalation"));
40
- const userService = __importStar(require("../../services/user"));
41
40
  const helpers_1 = require("./helpers");
42
41
  /**
43
42
  * Find escalations by a metadata key-value pair.
@@ -50,14 +49,14 @@ async function findByMetadata(input, auth) {
50
49
  if (!input.key || !input.value) {
51
50
  return { status: 400, error: 'key and value are required' };
52
51
  }
53
- const result = await escalationService.findByMetadata(input.key, input.value, input.status, input.limit, input.offset);
54
- // RBAC: scope to visible roles
55
- const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
56
- if (visibleRoles) {
57
- const roleSet = new Set(visibleRoles);
58
- result.escalations = result.escalations.filter(e => roleSet.has(e.role));
59
- result.total = result.escalations.length;
60
- }
52
+ // RBAC scoped IN SQL: the caller's read scope (read_all roles see every match;
53
+ // read_self roles see only matches assigned to them) reaches the scoped query's
54
+ // WHERE and COUNT. Global access → no filter. Never filter a fetched page
55
+ // controller-side — that shrinks the page and reports a wrong total.
56
+ const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
57
+ const result = await escalationService.findByMetadata(input.key, input.value, input.status, input.limit, input.offset, scope.global
58
+ ? undefined
59
+ : { allRoles: scope.allRoles, selfRoles: scope.selfRoles, meUserId: auth.userId });
61
60
  return { status: 200, data: result };
62
61
  }
63
62
  catch (err) {
@@ -80,8 +79,12 @@ async function claimByMetadata(input, auth) {
80
79
  if ('error' in resolved)
81
80
  return resolved.error;
82
81
  const claimUserId = resolved.userId;
83
- // Resolve allowed roles: null = global access (no filter), string[] = scoped
84
- const allowedRoles = await resolveAllowedRoles(auth.userId);
82
+ // Write-scope: global no filter; otherwise restrict to write_all roles. The
83
+ // SDK claim-by-metadata uses a flat role filter and cannot enforce write_self
84
+ // ownership, so self-scope members are excluded from this path (their items
85
+ // are pre-claimed and acted on by id).
86
+ const writeScope = await (0, helpers_1.getEscalationWriteScope)(auth.userId);
87
+ const allowedRoles = writeScope.global ? null : writeScope.allRoles;
85
88
  const result = await escalationService.claimByMetadata(input.key, input.value, claimUserId, input.durationMinutes, input.metadata, allowedRoles);
86
89
  if (!result) {
87
90
  // No rows matched. Check if candidates existed (role mismatch vs no match).
@@ -120,8 +123,14 @@ async function resolveByMetadata(input, auth) {
120
123
  if ('error' in resolved)
121
124
  return resolved.error;
122
125
  const resolveUserId = resolved.userId;
123
- const allowedRoles = await resolveAllowedRoles(auth.userId);
124
- const result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, allowedRoles);
126
+ // Write-scope folds into the atomic resolve SQL: global → no filter; otherwise
127
+ // write_all roles match any item, write_self roles match only items assigned
128
+ // to the resolver. A non-global caller cannot pass a foreign assignee, so the
129
+ // self-branch (assigned_to = resolveUserId) is the caller's own items.
130
+ const writeScope = await (0, helpers_1.getEscalationWriteScope)(auth.userId);
131
+ const writeAllRoles = writeScope.global ? null : writeScope.allRoles;
132
+ const writeSelfRoles = writeScope.global ? null : writeScope.selfRoles;
133
+ const result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles);
125
134
  if (result.outcome === 'not_found') {
126
135
  return { status: 404, error: 'No pending escalation found for this metadata, or insufficient role permissions' };
127
136
  }
@@ -161,17 +170,3 @@ async function resolveByMetadata(input, auth) {
161
170
  return { status: 500, error: err.message };
162
171
  }
163
172
  }
164
- // ── Helpers ──────────────────────────────────────────────────────────────────
165
- /**
166
- * Resolve the set of roles the caller is allowed to act on.
167
- * Returns null for global access (superadmin/admin), or string[] for scoped users.
168
- */
169
- async function resolveAllowedRoles(userId) {
170
- if (await userService.hasGlobalEscalationAccess(userId))
171
- return null;
172
- const userRoles = await userService.getUserRoles(userId);
173
- // Return the user's roles (may be empty → SQL filters out all rows).
174
- // System/service accounts that need unrestricted access should be
175
- // seeded with the superadmin role via start({ seed: { admin } }).
176
- return userRoles.map(r => r.role);
177
- }
@@ -15,7 +15,8 @@ import type { LTApiResult, LTApiAuth } from '../../types/sdk';
15
15
  export declare function resolveEscalation(input: {
16
16
  id: string;
17
17
  resolverPayload: Record<string, any>;
18
- }, _auth: LTApiAuth): Promise<LTApiResult>;
18
+ metadata?: Record<string, any>;
19
+ }, auth: LTApiAuth): Promise<LTApiResult>;
19
20
  /**
20
21
  * Resolve an efficient (atomic) escalation directly by its `signal_key` and
21
22
  * resume the waiting workflow in place. For webhook callers that know the
@@ -25,4 +26,21 @@ export declare function resolveEscalation(input: {
25
26
  export declare function resolveBySignalKey(input: {
26
27
  signalKey: string;
27
28
  resolverPayload: Record<string, any>;
29
+ metadata?: Record<string, any>;
30
+ }, auth: LTApiAuth): Promise<LTApiResult>;
31
+ /**
32
+ * Resolve a SET of escalations by id in one guarded statement (the public,
33
+ * set-based sibling of {@link resolveEscalation}). Delegates to the SDK's
34
+ * `client.resolveMany` via the service layer.
35
+ *
36
+ * RBAC: a scoped caller may only resolve rows whose role they hold. `role` is
37
+ * immutable per escalation, so this authorization read (`getEscalationRoles`)
38
+ * does not race the guarded `resolveMany` that follows — and global principals
39
+ * skip it entirely. Use for bookkeeping rows woken collectively; it does NOT
40
+ * deliver per-row signals (see services `resolveEscalationsByIds`).
41
+ */
42
+ export declare function resolveByIds(input: {
43
+ ids: string[];
44
+ resolverPayload: Record<string, any>;
45
+ metadata?: Record<string, any>;
28
46
  }, auth: LTApiAuth): Promise<LTApiResult>;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.resolveEscalation = resolveEscalation;
37
37
  exports.resolveBySignalKey = resolveBySignalKey;
38
+ exports.resolveByIds = resolveByIds;
38
39
  const escalationService = __importStar(require("../../services/escalation"));
39
40
  const taskService = __importStar(require("../../services/task"));
40
41
  const escalation_strategy_1 = require("../../services/escalation-strategy");
@@ -57,9 +58,9 @@ const helpers_1 = require("./helpers");
57
58
  * Password fields in the resolver payload are replaced with ephemeral
58
59
  * tokens (15-minute TTL) so plaintext never enters the signal store.
59
60
  */
60
- async function resolveEscalation(input, _auth) {
61
+ async function resolveEscalation(input, auth) {
61
62
  try {
62
- const { id, resolverPayload } = input;
63
+ const { id, resolverPayload, metadata } = input;
63
64
  if (!resolverPayload) {
64
65
  return { status: 400, error: 'resolverPayload is required' };
65
66
  }
@@ -70,22 +71,38 @@ async function resolveEscalation(input, _auth) {
70
71
  return { status: 409, error: 'Escalation is cancelled' };
71
72
  if (escalation.status !== 'pending')
72
73
  return { status: 409, error: 'Escalation not available for resolution' };
74
+ // Hybrid RBAC for resolve ("ack", a write verb). Non-disclosure first: an
75
+ // escalation the caller cannot even SEE returns 404 (matches resolveBySignalKey —
76
+ // do not reveal out-of-scope rows). One they CAN see but cannot act on returns an
77
+ // informative 403 (e.g. a write_self owner whose item isn't assigned to them).
78
+ // Global access bypasses both.
79
+ if (await (0, helpers_1.assertReadAccess)(auth.userId, escalation)) {
80
+ return { status: 404, error: 'Escalation not found' };
81
+ }
82
+ const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
83
+ if (denied)
84
+ return denied;
85
+ // `metadata` (the outcome patch) is never written separately — it rides WITH the
86
+ // resolverPayload to whichever path performs the resolve, so it merges inside the
87
+ // single status='pending'-guarded UPDATE (HotMesh `resolve(metadata)`). Paths that
88
+ // resolve here pass it as the 3rd arg; paths that resume a workflow carry it on the
89
+ // signal so the downstream resolve commits it atomically.
73
90
  // Path A: conditionLT signal
74
91
  const metadataSignalId = escalation.metadata?.signal_id;
75
92
  if (metadataSignalId && escalation.workflow_id && escalation.task_queue && escalation.workflow_type) {
76
- return resolveViaConditionSignal(escalation, resolverPayload);
93
+ return resolveViaConditionSignal(escalation, resolverPayload, metadata);
77
94
  }
78
95
  // Path B: waitFor signal routing
79
96
  const signalRouting = escalation.metadata?.signal_routing;
80
97
  if (signalRouting?.signalId) {
81
- return resolveViaSignalRouting(escalation, resolverPayload);
98
+ return resolveViaSignalRouting(escalation, resolverPayload, metadata);
82
99
  }
83
100
  // Path 0: efficient (atomic) escalation — signal_key resumes in place.
84
101
  // The row was written inside the workflow's Leg1 checkpoint via
85
102
  // `condition(signalId, config)`. The SDK's resolve marks it resolved AND
86
103
  // delivers the signal to `signal_key`, resuming THIS job — no re-run.
87
104
  if (escalation.signal_key) {
88
- return resolveViaSignalKey(escalation, resolverPayload);
105
+ return resolveViaSignalKey(escalation, resolverPayload, metadata);
89
106
  }
90
107
  // Path C: escalation strategy may redirect to triage
91
108
  const envelope = await reconstructEnvelope(escalation);
@@ -93,16 +110,16 @@ async function resolveEscalation(input, _auth) {
93
110
  if (strategy) {
94
111
  const directive = await strategy.onResolution({ escalation, resolverPayload, envelope });
95
112
  if (directive.action === 'triage') {
96
- return resolveViaTriage(escalation, resolverPayload, directive.triageEnvelope);
113
+ return resolveViaTriage(escalation, resolverPayload, directive.triageEnvelope, metadata);
97
114
  }
98
115
  }
99
- // Path D: notification-only — no workflow to restart
116
+ // Path D: notification-only — no workflow to restart. One atomic resolve.
100
117
  if (!escalation.workflow_type || !escalation.task_queue) {
101
- await escalationService.resolveEscalation(escalation.id, resolverPayload);
118
+ await escalationService.resolveEscalation(escalation.id, resolverPayload, metadata);
102
119
  return { status: 200, data: { acknowledged: true, escalationId: escalation.id } };
103
120
  }
104
121
  // Path E: standard re-run
105
- return resolveViaRerun(escalation, envelope, resolverPayload);
122
+ return resolveViaRerun(escalation, envelope, resolverPayload, metadata);
106
123
  }
107
124
  catch (err) {
108
125
  return { status: 500, error: err.message };
@@ -116,7 +133,7 @@ async function resolveEscalation(input, _auth) {
116
133
  */
117
134
  async function resolveBySignalKey(input, auth) {
118
135
  try {
119
- const { signalKey, resolverPayload } = input;
136
+ const { signalKey, resolverPayload, metadata } = input;
120
137
  if (!signalKey)
121
138
  return { status: 400, error: 'signalKey is required' };
122
139
  if (!resolverPayload)
@@ -126,11 +143,52 @@ async function resolveBySignalKey(input, auth) {
126
143
  return { status: 404, error: 'Escalation not found' };
127
144
  if (escalation.status !== 'pending')
128
145
  return { status: 409, error: 'Escalation not available for resolution' };
129
- const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
130
- if (visibleRoles && !visibleRoles.includes(escalation.role)) {
146
+ // Resolve is a write verb — scope-gate it (write_self may resolve only its own item).
147
+ const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
148
+ if (denied)
131
149
  return { status: 404, error: 'Escalation not found' };
150
+ return resolveViaSignalKey(escalation, resolverPayload, metadata);
151
+ }
152
+ catch (err) {
153
+ return { status: 500, error: err.message };
154
+ }
155
+ }
156
+ /**
157
+ * Resolve a SET of escalations by id in one guarded statement (the public,
158
+ * set-based sibling of {@link resolveEscalation}). Delegates to the SDK's
159
+ * `client.resolveMany` via the service layer.
160
+ *
161
+ * RBAC: a scoped caller may only resolve rows whose role they hold. `role` is
162
+ * immutable per escalation, so this authorization read (`getEscalationRoles`)
163
+ * does not race the guarded `resolveMany` that follows — and global principals
164
+ * skip it entirely. Use for bookkeeping rows woken collectively; it does NOT
165
+ * deliver per-row signals (see services `resolveEscalationsByIds`).
166
+ */
167
+ async function resolveByIds(input, auth) {
168
+ try {
169
+ const { ids, resolverPayload, metadata } = input;
170
+ if (!Array.isArray(ids) || ids.length === 0) {
171
+ return { status: 400, error: 'ids must be a non-empty array' };
132
172
  }
133
- return resolveViaSignalKey(escalation, resolverPayload);
173
+ if (!resolverPayload)
174
+ return { status: 400, error: 'resolverPayload is required' };
175
+ // Bulk resolve ("ack") is a write verb, gated PER ITEM: write_all roles may
176
+ // resolve any item in the role; write_self roles may resolve only items assigned
177
+ // to the caller (same rule single-resolve applies via assertWriteAccess). Global
178
+ // bypasses. A missing id or any out-of-scope item → 404 (non-disclosure), and
179
+ // nothing is resolved.
180
+ const writeScope = await (0, helpers_1.getEscalationWriteScope)(auth.userId);
181
+ if (!writeScope.global) {
182
+ const allSet = new Set(writeScope.allRoles);
183
+ const selfSet = new Set(writeScope.selfRoles);
184
+ const rows = await escalationService.getEscalationScopeRows(ids);
185
+ const writable = (r) => allSet.has(r.role) || (selfSet.has(r.role) && r.assigned_to === auth.userId);
186
+ if (rows.length !== ids.length || !rows.every(writable)) {
187
+ return { status: 404, error: 'One or more escalations not found' };
188
+ }
189
+ }
190
+ const resolved = await escalationService.resolveEscalationsByIds(ids, resolverPayload, metadata);
191
+ return { status: 200, data: { resolved: resolved.length, escalationIds: resolved.map((e) => e.id) } };
134
192
  }
135
193
  catch (err) {
136
194
  return { status: 500, error: err.message };
@@ -138,11 +196,18 @@ async function resolveBySignalKey(input, auth) {
138
196
  }
139
197
  // ── Resolution paths ─────────────────────────────────────────────────────
140
198
  /** Path A: lightweight conditionLT signal — inject $escalation_id and signal the running workflow. */
141
- async function resolveViaConditionSignal(escalation, resolverPayload) {
199
+ async function resolveViaConditionSignal(escalation, resolverPayload, metadata) {
142
200
  const signalId = escalation.metadata.signal_id;
143
201
  const client = (0, workers_1.createClient)();
144
202
  const handle = await client.workflow.getHandle(escalation.task_queue, escalation.workflow_type, escalation.workflow_id);
145
- await handle.signal(signalId, { ...resolverPayload, $escalation_id: escalation.id });
203
+ // The row is resolved downstream by the workflow's `conditionLT` → `ltResolveEscalation`.
204
+ // Carry the outcome patch on the signal ($escalation_metadata, symmetric to $escalation_id)
205
+ // so it merges inside that single atomic resolve — never a separate write here.
206
+ await handle.signal(signalId, {
207
+ ...resolverPayload,
208
+ $escalation_id: escalation.id,
209
+ ...(metadata ? { $escalation_metadata: metadata } : {}),
210
+ });
146
211
  // Event published by service layer (services/escalation/crud.ts)
147
212
  return signaledResult(escalation, escalation.workflow_id);
148
213
  }
@@ -152,9 +217,11 @@ async function resolveViaConditionSignal(escalation, resolverPayload) {
152
217
  * transaction, so the original job resumes in place (no re-run, no separate
153
218
  * resolve activity). Password fields are redacted before they enter the signal.
154
219
  */
155
- async function resolveViaSignalKey(escalation, resolverPayload) {
220
+ async function resolveViaSignalKey(escalation, resolverPayload, metadata) {
156
221
  const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
157
- const resolved = await escalationService.resolveEscalation(escalation.id, signalPayload);
222
+ // One atomic call: status→resolved, signal delivered, and the outcome patch merged
223
+ // into the GIN-indexed metadata — all inside the single WHERE-guarded UPDATE.
224
+ const resolved = await escalationService.resolveEscalation(escalation.id, signalPayload, metadata);
158
225
  if (!resolved) {
159
226
  return { status: 409, error: 'Escalation not available for resolution' };
160
227
  }
@@ -162,15 +229,18 @@ async function resolveViaSignalKey(escalation, resolverPayload) {
162
229
  return signaledResult(escalation, escalation.workflow_id || '');
163
230
  }
164
231
  /** Path B: waitFor signal escalation — signal via YAML engine or Durable handle. */
165
- async function resolveViaSignalRouting(escalation, resolverPayload) {
232
+ async function resolveViaSignalRouting(escalation, resolverPayload, metadata) {
166
233
  const signalRouting = escalation.metadata.signal_routing;
167
234
  const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
168
235
  if (signalRouting.engine === 'yaml' && signalRouting.hookTopic && signalRouting.appId) {
236
+ // YAML resolves transactionally inside the workflow — carry the patch on the signal
237
+ // so it commits with that resolve, never as a separate write.
169
238
  const engine = await (0, deployer_1.getEngine)(signalRouting.appId);
170
239
  await engine.signal(signalRouting.hookTopic, {
171
240
  ...signalPayload,
172
241
  escalationId: escalation.id,
173
242
  job_id: signalRouting.jobId,
243
+ ...(metadata ? { $escalation_metadata: metadata } : {}),
174
244
  });
175
245
  }
176
246
  else if (signalRouting.workflowId) {
@@ -178,16 +248,25 @@ async function resolveViaSignalRouting(escalation, resolverPayload) {
178
248
  const handle = await client.workflow.getHandle(signalRouting.taskQueue, signalRouting.workflowType, signalRouting.workflowId);
179
249
  await handle.signal(signalRouting.signalId, signalPayload);
180
250
  }
181
- // YAML workflows resolve transactionally inside the workflow; only resolve here for Durable
251
+ // Durable resolves here one atomic call carries the outcome patch.
252
+ // Persist the SAME redacted payload that was delivered to the signal: the raw
253
+ // resolverPayload (with plaintext passwords) must never land in resolver_payload.
254
+ // Ordering is crash-safe-forward: the signal is delivered BEFORE this resolve, so a
255
+ // crash in between leaves the row `pending` and a retry re-signals (a no-op to an
256
+ // already-resumed workflow) then resolves — never a resolved row with a stuck workflow.
182
257
  if (signalRouting.engine !== 'yaml') {
183
- await escalationService.resolveEscalation(escalation.id, resolverPayload);
258
+ await escalationService.resolveEscalation(escalation.id, signalPayload, metadata);
184
259
  }
185
260
  // Event published by service layer (services/escalation/crud.ts)
186
261
  return signaledResult(escalation, signalRouting.workflowId || signalRouting.appId);
187
262
  }
188
263
  /** Path C: escalation strategy directed triage — start a triage workflow. */
189
- async function resolveViaTriage(escalation, resolverPayload, triageEnvelope) {
190
- const triageWorkflowId = `triage-${escalation.id}-${Date.now()}`;
264
+ async function resolveViaTriage(escalation, resolverPayload, triageEnvelope, metadata) {
265
+ // Deterministic id: an escalation is terminal-once, so `triage-<id>` is unique for
266
+ // its lifetime. A retry after a mid-saga crash re-targets the SAME job key (HotMesh
267
+ // upserts jobs by (workflowId, app_id)) instead of spawning a duplicate triage —
268
+ // and the final resolve below is the `status='pending'`-guarded arbiter.
269
+ const triageWorkflowId = `triage-${escalation.id}`;
191
270
  const client = (0, workers_1.createClient)();
192
271
  await taskService.createTask({
193
272
  workflow_id: triageWorkflowId,
@@ -212,7 +291,7 @@ async function resolveViaTriage(escalation, resolverPayload, triageEnvelope) {
212
291
  await escalationService.resolveEscalation(escalation.id, {
213
292
  ...resolverPayload,
214
293
  _lt: { ...resolverPayload._lt, triaged: true, triageWorkflowId },
215
- });
294
+ }, metadata);
216
295
  // Event published by service layer (services/escalation/crud.ts)
217
296
  return {
218
297
  status: 200,
@@ -220,10 +299,16 @@ async function resolveViaTriage(escalation, resolverPayload, triageEnvelope) {
220
299
  };
221
300
  }
222
301
  /** Path E: standard re-run — inject resolver data and restart the original workflow. */
223
- async function resolveViaRerun(escalation, envelope, resolverPayload) {
302
+ async function resolveViaRerun(escalation, envelope, resolverPayload, metadata) {
224
303
  envelope.resolver = resolverPayload;
225
- envelope.lt = { ...envelope.lt, escalationId: escalation.id };
226
- const newWorkflowId = `rerun-${escalation.id}-${Date.now()}`;
304
+ // Carry the outcome patch on the rerun envelope so the downstream resolve (when the
305
+ // re-run completes) commits it atomically — not a separate write to the old row.
306
+ envelope.lt = { ...envelope.lt, escalationId: escalation.id, escalationMetadata: metadata };
307
+ // Deterministic id (see resolveViaTriage): `rerun-<id>` is unique per escalation
308
+ // lifetime, so a retry re-targets the same job rather than spawning a duplicate
309
+ // re-run. The old escalation is resolved downstream by the interceptor when the
310
+ // re-run executes (services/interceptor/lifecycle.ts), which is `pending`-guarded.
311
+ const newWorkflowId = `rerun-${escalation.id}`;
227
312
  const client = (0, workers_1.createClient)();
228
313
  await client.workflow.start({
229
314
  workflowName: escalation.workflow_type,
@@ -37,7 +37,6 @@ exports.getEscalation = getEscalation;
37
37
  exports.getEscalationsByWorkflowId = getEscalationsByWorkflowId;
38
38
  exports.escalateToRole = escalateToRole;
39
39
  const escalationService = __importStar(require("../../services/escalation"));
40
- const userService = __importStar(require("../../services/user"));
41
40
  const roleService = __importStar(require("../../services/role"));
42
41
  const helpers_1 = require("./helpers");
43
42
  // ── Single-escalation routes ───────────────────────────────────────────────
@@ -56,13 +55,9 @@ async function getEscalation(input, auth) {
56
55
  if (!escalation) {
57
56
  return { status: 404, error: 'Escalation not found' };
58
57
  }
59
- const hasGlobal = await (0, helpers_1.hasGlobalEscalationAccess)(auth.userId);
60
- if (!hasGlobal) {
61
- const userHasRole = await userService.hasRole(auth.userId, escalation.role);
62
- if (!userHasRole) {
63
- return { status: 403, error: 'Not authorized to view this escalation' };
64
- }
65
- }
58
+ const denied = await (0, helpers_1.assertReadAccess)(auth.userId, escalation);
59
+ if (denied)
60
+ return denied;
66
61
  return { status: 200, data: escalation };
67
62
  }
68
63
  catch (err) {
@@ -108,6 +103,11 @@ async function escalateToRole(input, auth) {
108
103
  if (escalation.status !== 'pending') {
109
104
  return { status: 409, error: 'Escalation is not pending' };
110
105
  }
106
+ // Escalating routes the item to another role — a queue-management verb that
107
+ // self-scope owners and read-only members may not perform.
108
+ const manageDenied = await (0, helpers_1.assertQueueManageAccess)(auth.userId, escalation.role);
109
+ if (manageDenied)
110
+ return manageDenied;
111
111
  const canEscalate = await roleService.canEscalateTo(auth.userId, escalation.role, targetRole);
112
112
  if (!canEscalate) {
113
113
  return { status: 403, error: 'Not authorized to escalate to this role' };
@@ -6,6 +6,7 @@ import type { LTRoleType } from '../types';
6
6
  * @param input.role — filter by role name
7
7
  * @param input.roleType — filter by role type (superadmin, admin, member)
8
8
  * @param input.status — filter by user status
9
+ * @param input.search — free-text match on display name, email, or external_id
9
10
  * @param input.limit — maximum number of users to return
10
11
  * @param input.offset — number of users to skip for pagination
11
12
  * @returns `{ status: 200, data: User[] }` on success
@@ -14,6 +15,7 @@ export declare function listUsers(input: {
14
15
  role?: string;
15
16
  roleType?: LTRoleType;
16
17
  status?: string;
18
+ search?: string;
17
19
  limit?: number;
18
20
  offset?: number;
19
21
  }): Promise<LTApiResult>;
@@ -47,6 +49,8 @@ export declare function createUser(input: {
47
49
  roles?: {
48
50
  role: string;
49
51
  type: string;
52
+ read_scope?: string;
53
+ write_scope?: string;
50
54
  }[];
51
55
  metadata?: Record<string, any>;
52
56
  }): Promise<LTApiResult>;
@@ -103,6 +107,8 @@ export declare function addUserRole(input: {
103
107
  id: string;
104
108
  role: string;
105
109
  type: string;
110
+ read_scope?: string;
111
+ write_scope?: string;
106
112
  }): Promise<LTApiResult>;
107
113
  /**
108
114
  * Remove a role from a user. Requires admin privileges.