@hotmeshio/long-tail 0.5.12 → 0.6.2

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 (337) 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-C5gfVEeL.js +2 -0
  87. package/dashboard/dist/assets/{AdminDashboard-LTiYLuzq.js.map → AdminDashboard-C5gfVEeL.js.map} +1 -1
  88. package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js → AgentConfigPage-CWEnuiGN.js} +6 -6
  89. package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js.map → AgentConfigPage-CWEnuiGN.js.map} +1 -1
  90. package/dashboard/dist/assets/{AgentDetailPage-BSUnDvGp.js → AgentDetailPage-B6IE27iK.js} +3 -3
  91. package/dashboard/dist/assets/AgentDetailPage-B6IE27iK.js.map +1 -0
  92. package/dashboard/dist/assets/AgentsPage-Blhh2RsK.js +2 -0
  93. package/dashboard/dist/assets/{AgentsPage-DpS0cCYe.js.map → AgentsPage-Blhh2RsK.js.map} +1 -1
  94. package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js +2 -0
  95. package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js.map +1 -0
  96. package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js → BotPicker-DSozotGk.js} +2 -2
  97. package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js.map → BotPicker-DSozotGk.js.map} +1 -1
  98. package/dashboard/dist/assets/CapabilitiesPage-CrS_DTA1.js +2 -0
  99. package/dashboard/dist/assets/CapabilitiesPage-CrS_DTA1.js.map +1 -0
  100. package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js → CollapsibleSection-1i6bOQ1L.js} +2 -2
  101. package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js.map → CollapsibleSection-1i6bOQ1L.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-BxJio06E.js} +2 -2
  105. package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js.map → CredentialsPage-BxJio06E.js.map} +1 -1
  106. package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js → CronLabel-BAfIHHz4.js} +2 -2
  107. package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js.map → CronLabel-BAfIHHz4.js.map} +1 -1
  108. package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js → CustomDurationPicker-BLF0NFJK.js} +2 -2
  109. package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js.map → CustomDurationPicker-BLF0NFJK.js.map} +1 -1
  110. package/dashboard/dist/assets/{DataTable-D9yuBv0w.js → DataTable-ZWgVQg7F.js} +2 -2
  111. package/dashboard/dist/assets/DataTable-ZWgVQg7F.js.map +1 -0
  112. package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js → DropZone-CEx_wk-g.js} +2 -2
  113. package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js.map → DropZone-CEx_wk-g.js.map} +1 -1
  114. package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js → ElapsedCell-nKGJvznH.js} +2 -2
  115. package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js.map → ElapsedCell-nKGJvznH.js.map} +1 -1
  116. package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js → EscalationsOverview-bNNVA6R2.js} +2 -2
  117. package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js.map → EscalationsOverview-bNNVA6R2.js.map} +1 -1
  118. package/dashboard/dist/assets/{EventTable-CeKfFk3m.js → EventTable-BCuDdqlU.js} +2 -2
  119. package/dashboard/dist/assets/{EventTable-CeKfFk3m.js.map → EventTable-BCuDdqlU.js.map} +1 -1
  120. package/dashboard/dist/assets/FilterBar-BQ2JGg8U.js +2 -0
  121. package/dashboard/dist/assets/FilterBar-BQ2JGg8U.js.map +1 -0
  122. package/dashboard/dist/assets/GraphInvokePage-BPAvqHls.js +2 -0
  123. package/dashboard/dist/assets/GraphInvokePage-BPAvqHls.js.map +1 -0
  124. package/dashboard/dist/assets/HomePage-BZ1YTrjR.js +2 -0
  125. package/dashboard/dist/assets/HomePage-BZ1YTrjR.js.map +1 -0
  126. package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js → ListToolbar-DIJZfEgu.js} +2 -2
  127. package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js.map → ListToolbar-DIJZfEgu.js.map} +1 -1
  128. package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js → McpOverview-DCfSCfPg.js} +2 -2
  129. package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js.map → McpOverview-DCfSCfPg.js.map} +1 -1
  130. package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js → McpQueryDetailPage-mb7rmtNe.js} +2 -2
  131. package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js.map → McpQueryDetailPage-mb7rmtNe.js.map} +1 -1
  132. package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js → McpQueryPage-D9b1T0I3.js} +2 -2
  133. package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js.map → McpQueryPage-D9b1T0I3.js.map} +1 -1
  134. package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js → McpRunDetailPage-Bz2UyuNm.js} +2 -2
  135. package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js.map → McpRunDetailPage-Bz2UyuNm.js.map} +1 -1
  136. package/dashboard/dist/assets/McpRunsPage-Dw7w_ZO-.js +2 -0
  137. package/dashboard/dist/assets/McpRunsPage-Dw7w_ZO-.js.map +1 -0
  138. package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js → NamespacePill-D27vqDXy.js} +2 -2
  139. package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js.map → NamespacePill-D27vqDXy.js.map} +1 -1
  140. package/dashboard/dist/assets/OperatorDashboard-CTzSDW3N.js +2 -0
  141. package/dashboard/dist/assets/{OperatorDashboard-g9o7I925.js.map → OperatorDashboard-CTzSDW3N.js.map} +1 -1
  142. package/dashboard/dist/assets/PageHeader-B_1zTx2q.js +2 -0
  143. package/dashboard/dist/assets/PageHeader-B_1zTx2q.js.map +1 -0
  144. package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js → PageHeaderWithStats-D-cPg7UD.js} +2 -2
  145. package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js.map → PageHeaderWithStats-D-cPg7UD.js.map} +1 -1
  146. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js +2 -0
  147. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js.map +1 -0
  148. package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js → ProcessDetailPage-olG2CS-h.js} +2 -2
  149. package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js.map → ProcessDetailPage-olG2CS-h.js.map} +1 -1
  150. package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js → ProcessesListPage-CUZSL06Y.js} +2 -2
  151. package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js.map → ProcessesListPage-CUZSL06Y.js.map} +1 -1
  152. package/dashboard/dist/assets/RolePill-B-V-31lC.js +2 -0
  153. package/dashboard/dist/assets/RolePill-B-V-31lC.js.map +1 -0
  154. package/dashboard/dist/assets/RolesPage-DLnaW6iD.js +2 -0
  155. package/dashboard/dist/assets/RolesPage-DLnaW6iD.js.map +1 -0
  156. package/dashboard/dist/assets/RowActions-BA9L9djf.js +2 -0
  157. package/dashboard/dist/assets/RowActions-BA9L9djf.js.map +1 -0
  158. package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js → RunAsSelector-BoLHF4fy.js} +2 -2
  159. package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js.map → RunAsSelector-BoLHF4fy.js.map} +1 -1
  160. package/dashboard/dist/assets/StickyPagination-BEMY1L-H.js +2 -0
  161. package/dashboard/dist/assets/StickyPagination-BEMY1L-H.js.map +1 -0
  162. package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js → StreamMessageDetail-CVRVWXcU.js} +2 -2
  163. package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js.map → StreamMessageDetail-CVRVWXcU.js.map} +1 -1
  164. package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js → SwimlaneTimeline-DDpU0-Jn.js} +2 -2
  165. package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js.map → SwimlaneTimeline-DDpU0-Jn.js.map} +1 -1
  166. package/dashboard/dist/assets/{TagInput-CZSeyesi.js → TagInput-BHVfG307.js} +2 -2
  167. package/dashboard/dist/assets/{TagInput-CZSeyesi.js.map → TagInput-BHVfG307.js.map} +1 -1
  168. package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js → TaskDetailPage-B_xQtJMg.js} +2 -2
  169. package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js.map → TaskDetailPage-B_xQtJMg.js.map} +1 -1
  170. package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js → TaskQueuePill-CGlxXqWt.js} +2 -2
  171. package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js.map → TaskQueuePill-CGlxXqWt.js.map} +1 -1
  172. package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js → TasksListPage-CsrdIOKk.js} +2 -2
  173. package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js.map → TasksListPage-CsrdIOKk.js.map} +1 -1
  174. package/dashboard/dist/assets/TimeAgo-DmicaxjT.js +2 -0
  175. package/dashboard/dist/assets/{TimeAgo-CUIYqSt_.js.map → TimeAgo-DmicaxjT.js.map} +1 -1
  176. package/dashboard/dist/assets/{TimestampCell--tnreI1i.js → TimestampCell-DeK89eRV.js} +2 -2
  177. package/dashboard/dist/assets/{TimestampCell--tnreI1i.js.map → TimestampCell-DeK89eRV.js.map} +1 -1
  178. package/dashboard/dist/assets/{ToolPill-O09jsTQM.js → ToolPill-NV7PxSpA.js} +2 -2
  179. package/dashboard/dist/assets/{ToolPill-O09jsTQM.js.map → ToolPill-NV7PxSpA.js.map} +1 -1
  180. package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js → ToolTestPanel-D3s2n9-9.js} +2 -2
  181. package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js.map → ToolTestPanel-D3s2n9-9.js.map} +1 -1
  182. package/dashboard/dist/assets/{TopicDetailPage-rh_dsTJI.js → TopicDetailPage-qyv7UWT_.js} +3 -3
  183. package/dashboard/dist/assets/TopicDetailPage-qyv7UWT_.js.map +1 -0
  184. package/dashboard/dist/assets/TopicsPage-CEUfMoed.js +2 -0
  185. package/dashboard/dist/assets/TopicsPage-CEUfMoed.js.map +1 -0
  186. package/dashboard/dist/assets/{UserName-C__c8BN0.js → UserName-Bn_BxI8K.js} +2 -2
  187. package/dashboard/dist/assets/{UserName-C__c8BN0.js.map → UserName-Bn_BxI8K.js.map} +1 -1
  188. package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js → WorkflowExecutionPage-DAk2RqpB.js} +2 -2
  189. package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js.map → WorkflowExecutionPage-DAk2RqpB.js.map} +1 -1
  190. package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js → WorkflowPill-DDR0B6DN.js} +2 -2
  191. package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js.map → WorkflowPill-DDR0B6DN.js.map} +1 -1
  192. package/dashboard/dist/assets/WorkflowsDashboard-DfBgM80t.js +2 -0
  193. package/dashboard/dist/assets/WorkflowsDashboard-DfBgM80t.js.map +1 -0
  194. package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js → WorkflowsOverview--Dkoo9Jq.js} +2 -2
  195. package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js.map → WorkflowsOverview--Dkoo9Jq.js.map} +1 -1
  196. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js → YamlWorkflowDetailPage-B6HYbfFj.js} +25 -25
  197. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js.map → YamlWorkflowDetailPage-B6HYbfFj.js.map} +1 -1
  198. package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js → YamlWorkflowsPage-CJE-PxC0.js} +2 -2
  199. package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js.map → YamlWorkflowsPage-CJE-PxC0.js.map} +1 -1
  200. package/dashboard/dist/assets/{agents-xstNkF6c.js → agents-DUciUZpt.js} +2 -2
  201. package/dashboard/dist/assets/{agents-xstNkF6c.js.map → agents-DUciUZpt.js.map} +1 -1
  202. package/dashboard/dist/assets/{bots-BztAI0s_.js → bots-BnyqCfuC.js} +2 -2
  203. package/dashboard/dist/assets/{bots-BztAI0s_.js.map → bots-BnyqCfuC.js.map} +1 -1
  204. package/dashboard/dist/assets/{capabilities-CEDwtxNL.js → capabilities-CkTWWT0q.js} +2 -2
  205. package/dashboard/dist/assets/{capabilities-CEDwtxNL.js.map → capabilities-CkTWWT0q.js.map} +1 -1
  206. package/dashboard/dist/assets/{controlplane-BlGEsRKe.js → controlplane-D9HaFZsC.js} +2 -2
  207. package/dashboard/dist/assets/{controlplane-BlGEsRKe.js.map → controlplane-D9HaFZsC.js.map} +1 -1
  208. package/dashboard/dist/assets/{escalation-iI4QKlP-.js → escalation-De9JnTKI.js} +2 -2
  209. package/dashboard/dist/assets/{escalation-iI4QKlP-.js.map → escalation-De9JnTKI.js.map} +1 -1
  210. package/dashboard/dist/assets/escalation-columns-Df7Pluzg.js +2 -0
  211. package/dashboard/dist/assets/escalation-columns-Df7Pluzg.js.map +1 -0
  212. package/dashboard/dist/assets/index-7hqtQI43.js +2 -0
  213. package/dashboard/dist/assets/index-7hqtQI43.js.map +1 -0
  214. package/dashboard/dist/assets/{index-CAqEy2Db.js → index-B07xLlyz.js} +2 -2
  215. package/dashboard/dist/assets/{index-CAqEy2Db.js.map → index-B07xLlyz.js.map} +1 -1
  216. package/dashboard/dist/assets/{index-D1xVY4JR.js → index-B1kc6itM.js} +24 -24
  217. package/dashboard/dist/assets/index-B1kc6itM.js.map +1 -0
  218. package/dashboard/dist/assets/index-BAhkWcOq.js +9 -0
  219. package/dashboard/dist/assets/index-BAhkWcOq.js.map +1 -0
  220. package/dashboard/dist/assets/{index-Dsi5o7dx.js → index-BsIaUbKD.js} +2 -2
  221. package/dashboard/dist/assets/{index-Dsi5o7dx.js.map → index-BsIaUbKD.js.map} +1 -1
  222. package/dashboard/dist/assets/index-BxdiKgnH.js +6 -0
  223. package/dashboard/dist/assets/index-BxdiKgnH.js.map +1 -0
  224. package/dashboard/dist/assets/{index-B7VC0vjf.js → index-BzMsh6Ui.js} +2 -2
  225. package/dashboard/dist/assets/{index-B7VC0vjf.js.map → index-BzMsh6Ui.js.map} +1 -1
  226. package/dashboard/dist/assets/index-CN0efCE9.js +6 -0
  227. package/dashboard/dist/assets/index-CN0efCE9.js.map +1 -0
  228. package/dashboard/dist/assets/{index-B5qsp8lb.js → index-CgD9fA9P.js} +2 -2
  229. package/dashboard/dist/assets/{index-B5qsp8lb.js.map → index-CgD9fA9P.js.map} +1 -1
  230. package/dashboard/dist/assets/{index-CUfgt3mV.js → index-Cqjf-yVP.js} +2 -2
  231. package/dashboard/dist/assets/{index-CUfgt3mV.js.map → index-Cqjf-yVP.js.map} +1 -1
  232. package/dashboard/dist/assets/{index-DjJ6VCuc.js → index-DNugmBs7.js} +2 -2
  233. package/dashboard/dist/assets/{index-DjJ6VCuc.js.map → index-DNugmBs7.js.map} +1 -1
  234. package/dashboard/dist/assets/{index-BUmnJ4eT.js → index-DjxTV3cn.js} +2 -2
  235. package/dashboard/dist/assets/{index-BUmnJ4eT.js.map → index-DjxTV3cn.js.map} +1 -1
  236. package/dashboard/dist/assets/index-DpAS4-DA.js +2 -0
  237. package/dashboard/dist/assets/index-DpAS4-DA.js.map +1 -0
  238. package/dashboard/dist/assets/index-nO0cim3_.css +1 -0
  239. package/dashboard/dist/assets/{knowledge-BKg0cr39.js → knowledge-D4SvdNv5.js} +2 -2
  240. package/dashboard/dist/assets/{knowledge-BKg0cr39.js.map → knowledge-D4SvdNv5.js.map} +1 -1
  241. package/dashboard/dist/assets/{mcp-CVdHpKlz.js → mcp-DVxt4T9J.js} +2 -2
  242. package/dashboard/dist/assets/{mcp-CVdHpKlz.js.map → mcp-DVxt4T9J.js.map} +1 -1
  243. package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js → mcp-query-CLP9EnnU.js} +2 -2
  244. package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js.map → mcp-query-CLP9EnnU.js.map} +1 -1
  245. package/dashboard/dist/assets/{pipelines-BOEv7P6O.js → pipelines-C_x0LTBN.js} +2 -2
  246. package/dashboard/dist/assets/{pipelines-BOEv7P6O.js.map → pipelines-C_x0LTBN.js.map} +1 -1
  247. package/dashboard/dist/assets/{roles-c5cV2QYQ.js → roles-QaGFZQ58.js} +2 -2
  248. package/dashboard/dist/assets/{roles-c5cV2QYQ.js.map → roles-QaGFZQ58.js.map} +1 -1
  249. package/dashboard/dist/assets/{tasks-JV3BjeEF.js → tasks-1eTuP02d.js} +2 -2
  250. package/dashboard/dist/assets/{tasks-JV3BjeEF.js.map → tasks-1eTuP02d.js.map} +1 -1
  251. package/dashboard/dist/assets/{topics-BDaPnhnQ.js → topics-B3p8K0PW.js} +2 -2
  252. package/dashboard/dist/assets/{topics-BDaPnhnQ.js.map → topics-B3p8K0PW.js.map} +1 -1
  253. package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js → useEventHooks-BNZky4xM.js} +2 -2
  254. package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js.map → useEventHooks-BNZky4xM.js.map} +1 -1
  255. package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js → useNamespace-Dx81_JM0.js} +2 -2
  256. package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js.map → useNamespace-Dx81_JM0.js.map} +1 -1
  257. package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js → useYamlActivityEvents-D5T3TbE7.js} +2 -2
  258. package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js.map → useYamlActivityEvents-D5T3TbE7.js.map} +1 -1
  259. package/dashboard/dist/assets/users-BEMysNdR.js +2 -0
  260. package/dashboard/dist/assets/users-BEMysNdR.js.map +1 -0
  261. package/dashboard/dist/assets/{vendor-icons-D5ycKPA2.js → vendor-icons--Ivmp-RX.js} +132 -112
  262. package/dashboard/dist/assets/vendor-icons--Ivmp-RX.js.map +1 -0
  263. package/dashboard/dist/assets/{workflows-DF-9Voyi.js → workflows-C4rjvQbI.js} +2 -2
  264. package/dashboard/dist/assets/{workflows-DF-9Voyi.js.map → workflows-C4rjvQbI.js.map} +1 -1
  265. package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js → yaml-workflows-4rJ5v5rJ.js} +2 -2
  266. package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js.map → yaml-workflows-4rJ5v5rJ.js.map} +1 -1
  267. package/dashboard/dist/index.html +3 -3
  268. package/docs/api/http/escalations.md +131 -10
  269. package/docs/api/http/roles.md +44 -9
  270. package/docs/api/http/users.md +15 -6
  271. package/docs/api/mcp/admin.md +8 -2
  272. package/docs/api/mcp/human-queue.md +34 -2
  273. package/docs/api/sdk/escalations.md +155 -2
  274. package/docs/api/sdk/roles.md +11 -0
  275. package/docs/api/sdk/users.md +26 -1
  276. package/docs/auth.md +1 -1
  277. package/docs/dashboard.md +5 -4
  278. package/docs/data.md +9 -2
  279. package/docs/faceted-routing.md +221 -0
  280. package/docs/hitl-guide.md +50 -0
  281. package/docs/iam.md +31 -0
  282. package/docs/mcp.md +8 -0
  283. package/package.json +2 -2
  284. package/dashboard/dist/assets/AdminDashboard-LTiYLuzq.js +0 -2
  285. package/dashboard/dist/assets/AgentDetailPage-BSUnDvGp.js.map +0 -1
  286. package/dashboard/dist/assets/AgentsPage-DpS0cCYe.js +0 -2
  287. package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js +0 -2
  288. package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js.map +0 -1
  289. package/dashboard/dist/assets/CapabilitiesPage-DLIleX2L.js +0 -2
  290. package/dashboard/dist/assets/CapabilitiesPage-DLIleX2L.js.map +0 -1
  291. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js +0 -2
  292. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js.map +0 -1
  293. package/dashboard/dist/assets/DataTable-D9yuBv0w.js.map +0 -1
  294. package/dashboard/dist/assets/FilterBar-Ck4K4rzu.js +0 -2
  295. package/dashboard/dist/assets/FilterBar-Ck4K4rzu.js.map +0 -1
  296. package/dashboard/dist/assets/GraphInvokePage-BZBPBCSq.js +0 -2
  297. package/dashboard/dist/assets/GraphInvokePage-BZBPBCSq.js.map +0 -1
  298. package/dashboard/dist/assets/HomePage-dM7U2tl6.js +0 -2
  299. package/dashboard/dist/assets/HomePage-dM7U2tl6.js.map +0 -1
  300. package/dashboard/dist/assets/McpRunsPage-CPMYWgm_.js +0 -2
  301. package/dashboard/dist/assets/McpRunsPage-CPMYWgm_.js.map +0 -1
  302. package/dashboard/dist/assets/OperatorDashboard-g9o7I925.js +0 -2
  303. package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js +0 -2
  304. package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js.map +0 -1
  305. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js +0 -2
  306. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js.map +0 -1
  307. package/dashboard/dist/assets/RolePill-Du-aNnjR.js +0 -2
  308. package/dashboard/dist/assets/RolePill-Du-aNnjR.js.map +0 -1
  309. package/dashboard/dist/assets/RolesPage-B2Kdf-L9.js +0 -2
  310. package/dashboard/dist/assets/RolesPage-B2Kdf-L9.js.map +0 -1
  311. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js +0 -2
  312. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js.map +0 -1
  313. package/dashboard/dist/assets/StickyPagination-BWhFSr2d.js +0 -2
  314. package/dashboard/dist/assets/StickyPagination-BWhFSr2d.js.map +0 -1
  315. package/dashboard/dist/assets/TimeAgo-CUIYqSt_.js +0 -2
  316. package/dashboard/dist/assets/TopicDetailPage-rh_dsTJI.js.map +0 -1
  317. package/dashboard/dist/assets/TopicsPage-CD_40_v-.js +0 -2
  318. package/dashboard/dist/assets/TopicsPage-CD_40_v-.js.map +0 -1
  319. package/dashboard/dist/assets/WorkflowsDashboard-BgI-iOP6.js +0 -2
  320. package/dashboard/dist/assets/WorkflowsDashboard-BgI-iOP6.js.map +0 -1
  321. package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js +0 -2
  322. package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js.map +0 -1
  323. package/dashboard/dist/assets/index-CMviPjXQ.js +0 -9
  324. package/dashboard/dist/assets/index-CMviPjXQ.js.map +0 -1
  325. package/dashboard/dist/assets/index-CP0JEQhZ.js +0 -6
  326. package/dashboard/dist/assets/index-CP0JEQhZ.js.map +0 -1
  327. package/dashboard/dist/assets/index-ClvYSNZd.css +0 -1
  328. package/dashboard/dist/assets/index-D1xVY4JR.js.map +0 -1
  329. package/dashboard/dist/assets/index-DPLy-jwO.js +0 -2
  330. package/dashboard/dist/assets/index-DPLy-jwO.js.map +0 -1
  331. package/dashboard/dist/assets/index-DzVYjoex.js +0 -6
  332. package/dashboard/dist/assets/index-DzVYjoex.js.map +0 -1
  333. package/dashboard/dist/assets/index-FuhiJuHL.js +0 -2
  334. package/dashboard/dist/assets/index-FuhiJuHL.js.map +0 -1
  335. package/dashboard/dist/assets/users-Pn85K_3C.js +0 -2
  336. package/dashboard/dist/assets/users-Pn85K_3C.js.map +0 -1
  337. package/dashboard/dist/assets/vendor-icons-D5ycKPA2.js.map +0 -1
@@ -54,14 +54,20 @@ const getAvailableWorkSchema = zod_1.z.object({
54
54
  role: zod_1.z.string().describe('Role to filter by'),
55
55
  limit: zod_1.z.number().optional().default(10).describe('Max results to return'),
56
56
  });
57
+ /** Outcome facets merged onto the row on resolve — see the field describe(). */
58
+ const outcomeMetadata = zod_1.z.record(zod_1.z.any()).optional().describe('Outcome facets to merge into the escalation\'s metadata on resolve — the durable, ' +
59
+ 'queryable record of what happened (disposition, reviewer, timing). Distinct from ' +
60
+ 'payload, which resumes the waiting workflow and is not indexed.');
57
61
  const claimAndResolveSchema = zod_1.z.object({
58
62
  escalation_id: zod_1.z.string().describe('The escalation ID to claim and resolve'),
59
63
  resolver_id: zod_1.z.string().describe('Identifier for who/what is resolving'),
60
64
  payload: zod_1.z.record(zod_1.z.any()).describe('Resolution payload data'),
65
+ metadata: outcomeMetadata,
61
66
  });
62
67
  const resolveEscalationSchema = zod_1.z.object({
63
68
  escalation_id: zod_1.z.string().describe('The escalation ID to resolve'),
64
69
  payload: zod_1.z.record(zod_1.z.any()).describe('Resolution payload data'),
70
+ metadata: outcomeMetadata,
65
71
  });
66
72
  const escalateAndWaitSchema = zod_1.z.object({
67
73
  role: zod_1.z.string().describe('Target role for the escalation (e.g., "reviewer")'),
@@ -176,7 +182,7 @@ function registerHumanQueueTools(server) {
176
182
  // ── claim_and_resolve ───────────────────────────────────────────────
177
183
  server.registerTool('claim_and_resolve', {
178
184
  title: 'Claim and Resolve',
179
- description: 'Claim an escalation and immediately resolve it with a payload. Atomic operation.',
185
+ description: 'Claim an escalation and immediately resolve it with a payload. Atomic operation. Optionally pass `metadata` to record the outcome (disposition, timing) onto the row for the audit trail.',
180
186
  inputSchema: claimAndResolveSchema,
181
187
  }, async (args) => {
182
188
  const claimed = await escalationService.claimEscalation(args.escalation_id, args.resolver_id, 5);
@@ -189,7 +195,7 @@ function registerHumanQueueTools(server) {
189
195
  isError: true,
190
196
  };
191
197
  }
192
- const resolved = await escalationService.resolveEscalation(args.escalation_id, args.payload);
198
+ const resolved = await escalationService.resolveEscalation(args.escalation_id, args.payload, args.metadata);
193
199
  if (!resolved) {
194
200
  return {
195
201
  content: [{
@@ -213,10 +219,10 @@ function registerHumanQueueTools(server) {
213
219
  // ── resolve_escalation ──────────────────────────────────────────────
214
220
  server.registerTool('resolve_escalation', {
215
221
  title: 'Resolve Escalation',
216
- description: 'Resolve an already-claimed escalation with a payload. Use when the claim happened externally (e.g. via API).',
222
+ description: 'Resolve an already-claimed escalation with a payload. Use when the claim happened externally (e.g. via API). Optionally pass `metadata` to record the outcome (disposition, timing) onto the row for the audit trail.',
217
223
  inputSchema: resolveEscalationSchema,
218
224
  }, async (args) => {
219
- const resolved = await escalationService.resolveEscalation(args.escalation_id, args.payload);
225
+ const resolved = await escalationService.resolveEscalation(args.escalation_id, args.payload, args.metadata);
220
226
  if (!resolved) {
221
227
  return {
222
228
  content: [{
@@ -87,11 +87,15 @@ async function conditionLT(signalId, escalation) {
87
87
  taskQueue: LT_ACTIVITY_QUEUE,
88
88
  retry: { maximumAttempts: 3 },
89
89
  });
90
- // Strip $escalation_id before passing as resolver payload
91
- const { $escalation_id: _, ...resolverPayload } = raw;
90
+ // Strip the injected control keys ($escalation_id, $escalation_metadata) before the
91
+ // payload is returned to the caller. The outcome patch ($escalation_metadata, set by
92
+ // the resolve orchestrator's signal paths) rides INTO the single atomic resolve below
93
+ // so it merges in the same guarded UPDATE — never a separate write.
94
+ const { $escalation_id: _id, $escalation_metadata: metadata, ...resolverPayload } = raw;
92
95
  await ltResolveEscalation({
93
96
  escalationId,
94
97
  resolverPayload: resolverPayload,
98
+ metadata: metadata,
95
99
  });
96
100
  return resolverPayload;
97
101
  }
@@ -34,9 +34,8 @@ async function getAllEscalationChains() {
34
34
  */
35
35
  async function addEscalationChain(sourceRole, targetRole) {
36
36
  const pool = (0, db_1.getPool)();
37
- await pool.query(sql_1.ENSURE_ROLE_EXISTS, [sourceRole]);
38
- await pool.query(sql_1.ENSURE_ROLE_EXISTS, [targetRole]);
39
- await pool.query(sql_1.INSERT_ESCALATION_CHAIN, [sourceRole, targetRole]);
37
+ // One atomic statement: ensure both role FK targets + insert the chain link.
38
+ await pool.query(sql_1.ADD_ESCALATION_CHAIN, [sourceRole, targetRole]);
40
39
  }
41
40
  /**
42
41
  * Remove a single escalation chain entry.
@@ -1,6 +1,14 @@
1
1
  export declare const GET_ESCALATION_TARGETS = "\n SELECT target_role\n FROM lt_config_role_escalations\n WHERE source_role = $1\n ORDER BY target_role";
2
2
  export declare const GET_ALL_ESCALATION_CHAINS = "\n SELECT source_role, target_role\n FROM lt_config_role_escalations\n ORDER BY source_role, target_role";
3
3
  export declare const INSERT_ESCALATION_CHAIN = "\n INSERT INTO lt_config_role_escalations (source_role, target_role)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING";
4
+ /**
5
+ * Add an escalation chain in ONE atomic statement: ensure both role FK targets
6
+ * exist, then insert the source→target link. Postgres checks the chain's FK to
7
+ * lt_roles at statement end (after the sibling ensure CTEs run), so brand-new
8
+ * roles are valid targets. Replaces the former ensure+ensure+insert three-call
9
+ * saga, which had no transaction guarding the writes together.
10
+ */
11
+ export declare const ADD_ESCALATION_CHAIN = "\n WITH ensured_source AS (\n INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING\n ), ensured_target AS (\n INSERT INTO lt_roles (role) VALUES ($2) ON CONFLICT DO NOTHING\n )\n INSERT INTO lt_config_role_escalations (source_role, target_role)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING";
4
12
  export declare const DELETE_ESCALATION_CHAIN = "\n DELETE FROM lt_config_role_escalations\n WHERE source_role = $1 AND target_role = $2";
5
13
  export declare const DELETE_ESCALATION_CHAINS_BY_SOURCE = "\n DELETE FROM lt_config_role_escalations\n WHERE source_role = $1";
6
14
  export declare const CHECK_ESCALATION_CHAIN_EXISTS = "\n SELECT 1\n FROM lt_config_role_escalations\n WHERE source_role = $1 AND target_role = $2";
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // ─── Escalation chain queries ───────────────────────────────────────────────
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.COUNT_ACTIVE_ESCALATION_REFS = exports.COUNT_WORKFLOW_REFS = exports.COUNT_CHAIN_REFS = exports.COUNT_USER_ROLE_REFS = exports.LIST_ROLES_WITH_DETAILS = exports.DELETE_ROLE = exports.LIST_ROLES = exports.ENSURE_ROLE_EXISTS = exports.CHECK_ESCALATION_CHAIN_EXISTS = exports.DELETE_ESCALATION_CHAINS_BY_SOURCE = exports.DELETE_ESCALATION_CHAIN = exports.INSERT_ESCALATION_CHAIN = exports.GET_ALL_ESCALATION_CHAINS = exports.GET_ESCALATION_TARGETS = void 0;
4
+ exports.COUNT_ACTIVE_ESCALATION_REFS = exports.COUNT_WORKFLOW_REFS = exports.COUNT_CHAIN_REFS = exports.COUNT_USER_ROLE_REFS = exports.LIST_ROLES_WITH_DETAILS = exports.DELETE_ROLE = exports.LIST_ROLES = exports.ENSURE_ROLE_EXISTS = exports.CHECK_ESCALATION_CHAIN_EXISTS = exports.DELETE_ESCALATION_CHAINS_BY_SOURCE = exports.DELETE_ESCALATION_CHAIN = exports.ADD_ESCALATION_CHAIN = exports.INSERT_ESCALATION_CHAIN = exports.GET_ALL_ESCALATION_CHAINS = exports.GET_ESCALATION_TARGETS = void 0;
5
5
  exports.GET_ESCALATION_TARGETS = `
6
6
  SELECT target_role
7
7
  FROM lt_config_role_escalations
@@ -15,6 +15,22 @@ exports.INSERT_ESCALATION_CHAIN = `
15
15
  INSERT INTO lt_config_role_escalations (source_role, target_role)
16
16
  VALUES ($1, $2)
17
17
  ON CONFLICT DO NOTHING`;
18
+ /**
19
+ * Add an escalation chain in ONE atomic statement: ensure both role FK targets
20
+ * exist, then insert the source→target link. Postgres checks the chain's FK to
21
+ * lt_roles at statement end (after the sibling ensure CTEs run), so brand-new
22
+ * roles are valid targets. Replaces the former ensure+ensure+insert three-call
23
+ * saga, which had no transaction guarding the writes together.
24
+ */
25
+ exports.ADD_ESCALATION_CHAIN = `
26
+ WITH ensured_source AS (
27
+ INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING
28
+ ), ensured_target AS (
29
+ INSERT INTO lt_roles (role) VALUES ($2) ON CONFLICT DO NOTHING
30
+ )
31
+ INSERT INTO lt_config_role_escalations (source_role, target_role)
32
+ VALUES ($1, $2)
33
+ ON CONFLICT DO NOTHING`;
18
34
  exports.DELETE_ESCALATION_CHAIN = `
19
35
  DELETE FROM lt_config_role_escalations
20
36
  WHERE source_role = $1 AND target_role = $2`;
@@ -31,17 +31,25 @@ async function createTask(input) {
31
31
  input.executing_as || null,
32
32
  input.status || null,
33
33
  ]);
34
- const task = rows[0];
35
- (0, publish_1.publishTaskEvent)({
36
- type: 'task.created',
37
- source: 'service',
38
- workflowId: task.workflow_id || '',
39
- workflowName: task.workflow_type || '',
40
- taskQueue: task.task_queue || '',
41
- taskId: task.id,
42
- originId: task.origin_id || undefined,
43
- status: task.status || 'pending',
44
- });
34
+ // ON CONFLICT (workflow_id) makes this idempotent: a retried proxyActivity or
35
+ // re-driven saga re-targets the same row. `_inserted` (xmax = 0) is true only
36
+ // when this call actually inserted, so the created event fires exactly once.
37
+ const row = rows[0];
38
+ const inserted = row._inserted === true;
39
+ delete row._inserted;
40
+ const task = row;
41
+ if (inserted) {
42
+ (0, publish_1.publishTaskEvent)({
43
+ type: 'task.created',
44
+ source: 'service',
45
+ workflowId: task.workflow_id || '',
46
+ workflowName: task.workflow_type || '',
47
+ taskQueue: task.task_queue || '',
48
+ taskId: task.id,
49
+ originId: task.origin_id || undefined,
50
+ status: task.status || 'pending',
51
+ });
52
+ }
45
53
  return task;
46
54
  }
47
55
  async function updateTask(id, input) {
@@ -70,7 +78,7 @@ async function updateTask(id, input) {
70
78
  values.push(JSON.stringify(input.milestones));
71
79
  }
72
80
  values.push(id);
73
- const { rows } = await pool.query(`UPDATE lt_tasks SET ${sets.join(', ')} WHERE id = $${idx} RETURNING *`, values);
81
+ const { rows } = await pool.query((0, sql_1.updateTaskById)(sets.join(', '), idx), values);
74
82
  const task = rows[0];
75
83
  // Publish status-driven events from the single write path
76
84
  if (input.status && task) {
@@ -148,8 +156,8 @@ async function listTasks(filters) {
148
156
  const limit = filters.limit || 50;
149
157
  const offset = filters.offset || 0;
150
158
  const [countResult, dataResult] = await Promise.all([
151
- pool.query(`SELECT COUNT(*) FROM lt_tasks ${where}`, values),
152
- pool.query(`SELECT * FROM lt_tasks ${where} ORDER BY created_at DESC LIMIT $${idx++} OFFSET $${idx++}`, [...values, limit, offset]),
159
+ pool.query((0, sql_1.listTasksCount)(where), values),
160
+ pool.query((0, sql_1.listTasksData)(where, idx++, idx++), [...values, limit, offset]),
153
161
  ]);
154
162
  return {
155
163
  tasks: dataResult.rows,
@@ -44,27 +44,9 @@ async function listProcesses(filters) {
44
44
  having = `HAVING COUNT(*) > COUNT(*) FILTER (WHERE status = 'completed')
45
45
  AND COUNT(*) FILTER (WHERE status = 'needs_intervention') = 0`;
46
46
  }
47
- // Count query wrap in subquery because of HAVING
48
- const countSql = having
49
- ? `SELECT COUNT(*) FROM (
50
- SELECT origin_id FROM lt_tasks ${where}
51
- GROUP BY origin_id ${having}
52
- ) sub`
53
- : `SELECT COUNT(DISTINCT origin_id) FROM lt_tasks ${where}`;
54
- const dataSql = `SELECT
55
- origin_id,
56
- COUNT(*)::int AS task_count,
57
- COUNT(*) FILTER (WHERE status = 'completed')::int AS completed,
58
- COUNT(*) FILTER (WHERE status = 'needs_intervention')::int AS escalated,
59
- array_agg(DISTINCT workflow_type) AS workflow_types,
60
- MIN(created_at) AS started_at,
61
- MAX(COALESCE(completed_at, created_at)) AS last_activity
62
- FROM lt_tasks
63
- ${where}
64
- GROUP BY origin_id
65
- ${having}
66
- ORDER BY MAX(created_at) DESC
67
- LIMIT $${idx++} OFFSET $${idx++}`;
47
+ // Count wraps the grouped set in a subquery when a status HAVING filter is present.
48
+ const countSql = (0, sql_1.listProcessesCount)(where, having);
49
+ const dataSql = (0, sql_1.listProcessesData)(where, having, idx++, idx++);
68
50
  const dataParams = [...filterParams, limit, offset];
69
51
  const [countResult, dataResult] = await Promise.all([
70
52
  pool.query(countSql, filterParams),
@@ -1,8 +1,19 @@
1
- export declare const CREATE_TASK = "\n INSERT INTO lt_tasks\n (workflow_id, workflow_type, lt_type, task_queue, signal_id,\n parent_workflow_id, origin_id, parent_id, envelope, metadata, priority,\n trace_id, span_id, initiated_by, principal_type, executing_as, status)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, COALESCE($17, 'pending'))\n RETURNING *";
1
+ export declare const CREATE_TASK = "\n INSERT INTO lt_tasks\n (workflow_id, workflow_type, lt_type, task_queue, signal_id,\n parent_workflow_id, origin_id, parent_id, envelope, metadata, priority,\n trace_id, span_id, initiated_by, principal_type, executing_as, status)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, COALESCE($17, 'pending'))\n ON CONFLICT (workflow_id) DO UPDATE SET workflow_id = lt_tasks.workflow_id\n RETURNING *, (xmax = 0) AS _inserted";
2
2
  export declare const APPEND_MILESTONES = "\n UPDATE lt_tasks\n SET milestones = milestones || $1::jsonb\n WHERE id = $2\n RETURNING *";
3
3
  export declare const GET_TASK = "SELECT * FROM lt_tasks WHERE id = $1";
4
4
  export declare const GET_TASK_BY_SIGNAL_ID = "SELECT * FROM lt_tasks WHERE signal_id = $1";
5
5
  export declare const GET_TASK_BY_WORKFLOW_ID = "SELECT * FROM lt_tasks WHERE workflow_id = $1 ORDER BY created_at DESC LIMIT 1";
6
+ /**
7
+ * Build the partial-update statement for updateTask. The caller assembles the
8
+ * `SET` clause from whichever fields are present (each as a `$n` placeholder) and
9
+ * passes the placeholder index of the trailing `id` bind. One guarded UPDATE by
10
+ * primary key — no read-then-write.
11
+ */
12
+ export declare function updateTaskById(setClause: string, idIdx: number): string;
13
+ /** Count query for listTasks. `where` is the caller-built `WHERE …` clause (or ''). */
14
+ export declare function listTasksCount(where: string): string;
15
+ /** Page query for listTasks. limit/offset placeholder indices follow the filter binds. */
16
+ export declare function listTasksData(where: string, limitIdx: number, offsetIdx: number): string;
6
17
  export declare const RESOLVE_TASK_BY_WORKFLOW_ID = "SELECT workflow_type, task_queue FROM lt_tasks WHERE workflow_id = $1 ORDER BY created_at DESC LIMIT 1";
7
18
  export declare const RESOLVE_CONFIG_TASK_QUEUE = "SELECT task_queue FROM lt_config_workflows WHERE workflow_type = $1";
8
19
  /**
@@ -12,6 +23,18 @@ export declare const RESOLVE_CONFIG_TASK_QUEUE = "SELECT task_queue FROM lt_conf
12
23
  */
13
24
  export declare const RESOLVE_JOB_ENTITY: (appId: string) => string;
14
25
  export declare const GET_PROCESS_TASKS = "SELECT * FROM lt_tasks WHERE origin_id = $1 ORDER BY created_at ASC";
26
+ /**
27
+ * Count query for listProcesses. `where` restricts which origin_ids are in scope;
28
+ * `having` (empty string when no status filter) aggregates per-origin counts, so the
29
+ * count must wrap the grouped set in a subquery. Both fragments carry only `$n` binds.
30
+ */
31
+ export declare function listProcessesCount(where: string, having: string): string;
32
+ /**
33
+ * Page query for listProcesses — one row per origin_id with rolled-up task counts,
34
+ * workflow types, and activity window. limit/offset placeholder indices follow the
35
+ * filter binds.
36
+ */
37
+ export declare function listProcessesData(where: string, having: string, limitIdx: number, offsetIdx: number): string;
15
38
  /**
16
39
  * Build the process-stats totals CTE query for a given interval.
17
40
  * The interval value MUST come from the VALID_PERIODS whitelist.
@@ -4,16 +4,29 @@
4
4
  // ---------------------------------------------------------------------------
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GET_PROCESS_TASKS = exports.RESOLVE_JOB_ENTITY = exports.RESOLVE_CONFIG_TASK_QUEUE = exports.RESOLVE_TASK_BY_WORKFLOW_ID = exports.GET_TASK_BY_WORKFLOW_ID = exports.GET_TASK_BY_SIGNAL_ID = exports.GET_TASK = exports.APPEND_MILESTONES = exports.CREATE_TASK = void 0;
7
+ exports.updateTaskById = updateTaskById;
8
+ exports.listTasksCount = listTasksCount;
9
+ exports.listTasksData = listTasksData;
10
+ exports.listProcessesCount = listProcessesCount;
11
+ exports.listProcessesData = listProcessesData;
7
12
  exports.processStatsTotals = processStatsTotals;
8
13
  exports.processStatsByType = processStatsByType;
9
14
  // ---- crud.ts queries ------------------------------------------------------
15
+ // A task is 1:1 with its workflow_id. ON CONFLICT (workflow_id) makes createTask
16
+ // idempotent under proxyActivity retry (maximumAttempts: 3) and re-driven sagas:
17
+ // a retry re-targets the SAME row instead of inserting a duplicate. The no-op
18
+ // DO UPDATE is required so RETURNING yields the existing row on conflict; the
19
+ // `(xmax = 0)` flag tells the caller whether this call actually inserted (so the
20
+ // `task.created` event fires exactly once). Requires the unique index from
21
+ // migration 012.
10
22
  exports.CREATE_TASK = `
11
23
  INSERT INTO lt_tasks
12
24
  (workflow_id, workflow_type, lt_type, task_queue, signal_id,
13
25
  parent_workflow_id, origin_id, parent_id, envelope, metadata, priority,
14
26
  trace_id, span_id, initiated_by, principal_type, executing_as, status)
15
27
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, COALESCE($17, 'pending'))
16
- RETURNING *`;
28
+ ON CONFLICT (workflow_id) DO UPDATE SET workflow_id = lt_tasks.workflow_id
29
+ RETURNING *, (xmax = 0) AS _inserted`;
17
30
  exports.APPEND_MILESTONES = `
18
31
  UPDATE lt_tasks
19
32
  SET milestones = milestones || $1::jsonb
@@ -22,6 +35,23 @@ exports.APPEND_MILESTONES = `
22
35
  exports.GET_TASK = `SELECT * FROM lt_tasks WHERE id = $1`;
23
36
  exports.GET_TASK_BY_SIGNAL_ID = `SELECT * FROM lt_tasks WHERE signal_id = $1`;
24
37
  exports.GET_TASK_BY_WORKFLOW_ID = `SELECT * FROM lt_tasks WHERE workflow_id = $1 ORDER BY created_at DESC LIMIT 1`;
38
+ /**
39
+ * Build the partial-update statement for updateTask. The caller assembles the
40
+ * `SET` clause from whichever fields are present (each as a `$n` placeholder) and
41
+ * passes the placeholder index of the trailing `id` bind. One guarded UPDATE by
42
+ * primary key — no read-then-write.
43
+ */
44
+ function updateTaskById(setClause, idIdx) {
45
+ return `UPDATE lt_tasks SET ${setClause} WHERE id = $${idIdx} RETURNING *`;
46
+ }
47
+ /** Count query for listTasks. `where` is the caller-built `WHERE …` clause (or ''). */
48
+ function listTasksCount(where) {
49
+ return `SELECT COUNT(*) FROM lt_tasks ${where}`;
50
+ }
51
+ /** Page query for listTasks. limit/offset placeholder indices follow the filter binds. */
52
+ function listTasksData(where, limitIdx, offsetIdx) {
53
+ return `SELECT * FROM lt_tasks ${where} ORDER BY created_at DESC LIMIT $${limitIdx} OFFSET $${offsetIdx}`;
54
+ }
25
55
  // ---- resolve.ts queries ---------------------------------------------------
26
56
  exports.RESOLVE_TASK_BY_WORKFLOW_ID = `SELECT workflow_type, task_queue FROM lt_tasks WHERE workflow_id = $1 ORDER BY created_at DESC LIMIT 1`;
27
57
  exports.RESOLVE_CONFIG_TASK_QUEUE = `SELECT task_queue FROM lt_config_workflows WHERE workflow_type = $1`;
@@ -34,6 +64,40 @@ const RESOLVE_JOB_ENTITY = (appId) => `SELECT entity FROM "${appId}".jobs WHERE
34
64
  exports.RESOLVE_JOB_ENTITY = RESOLVE_JOB_ENTITY;
35
65
  // ---- process.ts queries ---------------------------------------------------
36
66
  exports.GET_PROCESS_TASKS = `SELECT * FROM lt_tasks WHERE origin_id = $1 ORDER BY created_at ASC`;
67
+ /**
68
+ * Count query for listProcesses. `where` restricts which origin_ids are in scope;
69
+ * `having` (empty string when no status filter) aggregates per-origin counts, so the
70
+ * count must wrap the grouped set in a subquery. Both fragments carry only `$n` binds.
71
+ */
72
+ function listProcessesCount(where, having) {
73
+ return having
74
+ ? `SELECT COUNT(*) FROM (
75
+ SELECT origin_id FROM lt_tasks ${where}
76
+ GROUP BY origin_id ${having}
77
+ ) sub`
78
+ : `SELECT COUNT(DISTINCT origin_id) FROM lt_tasks ${where}`;
79
+ }
80
+ /**
81
+ * Page query for listProcesses — one row per origin_id with rolled-up task counts,
82
+ * workflow types, and activity window. limit/offset placeholder indices follow the
83
+ * filter binds.
84
+ */
85
+ function listProcessesData(where, having, limitIdx, offsetIdx) {
86
+ return `SELECT
87
+ origin_id,
88
+ COUNT(*)::int AS task_count,
89
+ COUNT(*) FILTER (WHERE status = 'completed')::int AS completed,
90
+ COUNT(*) FILTER (WHERE status = 'needs_intervention')::int AS escalated,
91
+ array_agg(DISTINCT workflow_type) AS workflow_types,
92
+ MIN(created_at) AS started_at,
93
+ MAX(COALESCE(completed_at, created_at)) AS last_activity
94
+ FROM lt_tasks
95
+ ${where}
96
+ GROUP BY origin_id
97
+ ${having}
98
+ ORDER BY MAX(created_at) DESC
99
+ LIMIT $${limitIdx} OFFSET $${offsetIdx}`;
100
+ }
37
101
  /**
38
102
  * Build the process-stats totals CTE query for a given interval.
39
103
  * The interval value MUST come from the VALID_PERIODS whitelist.
@@ -11,6 +11,7 @@ export declare function listUsers(filters: {
11
11
  role?: string;
12
12
  roleType?: LTRoleType;
13
13
  status?: LTUserStatus;
14
+ search?: string;
14
15
  limit?: number;
15
16
  offset?: number;
16
17
  }): Promise<{
@@ -14,6 +14,7 @@ exports.listUsers = listUsers;
14
14
  const bcryptjs_1 = __importDefault(require("bcryptjs"));
15
15
  const db_1 = require("../../lib/db");
16
16
  const sql_1 = require("./sql");
17
+ const scope_1 = require("./scope");
17
18
  // ─── Private helpers (exported for internal use by auth.ts) ──────────────────
18
19
  async function attachRoles(user) {
19
20
  const pool = (0, db_1.getPool)();
@@ -29,7 +30,13 @@ async function attachRolesToMany(users) {
29
30
  const roleMap = new Map();
30
31
  for (const row of rows) {
31
32
  const list = roleMap.get(row.user_id) || [];
32
- list.push({ role: row.role, type: row.type, created_at: row.created_at });
33
+ list.push({
34
+ role: row.role,
35
+ type: row.type,
36
+ read_scope: row.read_scope,
37
+ write_scope: row.write_scope,
38
+ created_at: row.created_at,
39
+ });
33
40
  roleMap.set(row.user_id, list);
34
41
  }
35
42
  return users.map((u) => ({ ...u, roles: roleMap.get(u.id) || [] }));
@@ -40,7 +47,16 @@ async function createUser(input) {
40
47
  const passwordHash = input.password
41
48
  ? await bcryptjs_1.default.hash(input.password, 10)
42
49
  : null;
43
- const { rows } = await pool.query(sql_1.INSERT_USER, [
50
+ // One atomic statement creates the user, ensures the role FK targets, and links
51
+ // the assignments — no partial-roles window, no per-role N+1. Roles + their
52
+ // effective scope travel as parallel text[] arrays. admin/superadmin normalize
53
+ // to ('all','all'); members store the requested scope.
54
+ const roleNames = (input.roles || []).map((r) => r.role);
55
+ const roleTypes = (input.roles || []).map((r) => r.type);
56
+ const effScopes = (input.roles || []).map((r) => (0, scope_1.effectiveScope)(r.type, r.read_scope ?? scope_1.DEFAULT_READ_SCOPE, r.write_scope ?? scope_1.DEFAULT_WRITE_SCOPE));
57
+ const roleReadScopes = effScopes.map((e) => e.read);
58
+ const roleWriteScopes = effScopes.map((e) => e.write);
59
+ const { rows } = await pool.query(sql_1.CREATE_USER_WITH_ROLES, [
44
60
  input.external_id,
45
61
  input.email || null,
46
62
  input.display_name || null,
@@ -49,16 +65,12 @@ async function createUser(input) {
49
65
  passwordHash,
50
66
  input.oauth_provider || null,
51
67
  input.oauth_provider_id || null,
68
+ roleNames,
69
+ roleTypes,
70
+ roleReadScopes,
71
+ roleWriteScopes,
52
72
  ]);
53
- const user = rows[0];
54
- if (input.roles && input.roles.length > 0) {
55
- for (const r of input.roles) {
56
- // Ensure the role exists in lt_roles (FK constraint)
57
- await pool.query(sql_1.ENSURE_ROLE_EXISTS, [r.role]);
58
- await pool.query(sql_1.INSERT_USER_ROLE_IGNORE, [user.id, r.role, r.type]);
59
- }
60
- }
61
- return attachRoles(user);
73
+ return attachRoles(rows[0]);
62
74
  }
63
75
  async function getUser(id) {
64
76
  const pool = (0, db_1.getPool)();
@@ -140,6 +152,12 @@ async function listUsers(filters) {
140
152
  conditions.push(`u.status = $${idx++}`);
141
153
  values.push(filters.status);
142
154
  }
155
+ if (filters.search) {
156
+ // Server-side free-text over the user's display fields — one term, one param.
157
+ const i = idx++;
158
+ conditions.push(`(u.display_name ILIKE '%' || $${i} || '%' OR u.email ILIKE '%' || $${i} || '%' OR u.external_id ILIKE '%' || $${i} || '%')`);
159
+ values.push(filters.search);
160
+ }
143
161
  const join = needsJoin ? 'INNER JOIN lt_user_roles r ON r.user_id = u.id' : '';
144
162
  const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
145
163
  const limit = filters.limit || 50;
@@ -1,6 +1,7 @@
1
1
  export { CreateUserInput, UpdateUserInput, VALID_ROLE_TYPES } from './types';
2
2
  export { createUser, getUser, getUserByExternalId, getUserByEmail, updateUser, deleteUser, listUsers, } from './crud';
3
3
  export { isValidRoleType, addUserRole, removeUserRole, getUserRoles, hasRole, hasRoleType, } from './roles';
4
- export { isSuperAdmin, isGroupAdmin, canManageRole, hasGlobalEscalationAccess, hasRolesAsAdmin } from './rbac';
4
+ export { isSuperAdmin, isGroupAdmin, canManageRole, hasGlobalEscalationAccess, hasRolesAsAdmin, getRoleScope, getRoleWriteScope } from './rbac';
5
+ export { READ_SCOPES, WRITE_SCOPES, DEFAULT_READ_SCOPE, DEFAULT_WRITE_SCOPE, isValidReadScope, isValidWriteScope, isValidScopePair, effectiveScope, } from './scope';
5
6
  export { verifyPassword } from './auth';
6
7
  export { seedAdmin } from './seed-admin';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.seedAdmin = exports.verifyPassword = exports.hasRolesAsAdmin = exports.hasGlobalEscalationAccess = exports.canManageRole = exports.isGroupAdmin = exports.isSuperAdmin = exports.hasRoleType = exports.hasRole = exports.getUserRoles = exports.removeUserRole = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.updateUser = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
3
+ exports.seedAdmin = exports.verifyPassword = exports.effectiveScope = exports.isValidScopePair = exports.isValidWriteScope = exports.isValidReadScope = exports.DEFAULT_WRITE_SCOPE = exports.DEFAULT_READ_SCOPE = exports.WRITE_SCOPES = exports.READ_SCOPES = exports.getRoleWriteScope = exports.getRoleScope = exports.hasRolesAsAdmin = exports.hasGlobalEscalationAccess = exports.canManageRole = exports.isGroupAdmin = exports.isSuperAdmin = exports.hasRoleType = exports.hasRole = exports.getUserRoles = exports.removeUserRole = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.updateUser = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
4
4
  var types_1 = require("./types");
5
5
  Object.defineProperty(exports, "VALID_ROLE_TYPES", { enumerable: true, get: function () { return types_1.VALID_ROLE_TYPES; } });
6
6
  var crud_1 = require("./crud");
@@ -24,6 +24,17 @@ Object.defineProperty(exports, "isGroupAdmin", { enumerable: true, get: function
24
24
  Object.defineProperty(exports, "canManageRole", { enumerable: true, get: function () { return rbac_1.canManageRole; } });
25
25
  Object.defineProperty(exports, "hasGlobalEscalationAccess", { enumerable: true, get: function () { return rbac_1.hasGlobalEscalationAccess; } });
26
26
  Object.defineProperty(exports, "hasRolesAsAdmin", { enumerable: true, get: function () { return rbac_1.hasRolesAsAdmin; } });
27
+ Object.defineProperty(exports, "getRoleScope", { enumerable: true, get: function () { return rbac_1.getRoleScope; } });
28
+ Object.defineProperty(exports, "getRoleWriteScope", { enumerable: true, get: function () { return rbac_1.getRoleWriteScope; } });
29
+ var scope_1 = require("./scope");
30
+ Object.defineProperty(exports, "READ_SCOPES", { enumerable: true, get: function () { return scope_1.READ_SCOPES; } });
31
+ Object.defineProperty(exports, "WRITE_SCOPES", { enumerable: true, get: function () { return scope_1.WRITE_SCOPES; } });
32
+ Object.defineProperty(exports, "DEFAULT_READ_SCOPE", { enumerable: true, get: function () { return scope_1.DEFAULT_READ_SCOPE; } });
33
+ Object.defineProperty(exports, "DEFAULT_WRITE_SCOPE", { enumerable: true, get: function () { return scope_1.DEFAULT_WRITE_SCOPE; } });
34
+ Object.defineProperty(exports, "isValidReadScope", { enumerable: true, get: function () { return scope_1.isValidReadScope; } });
35
+ Object.defineProperty(exports, "isValidWriteScope", { enumerable: true, get: function () { return scope_1.isValidWriteScope; } });
36
+ Object.defineProperty(exports, "isValidScopePair", { enumerable: true, get: function () { return scope_1.isValidScopePair; } });
37
+ Object.defineProperty(exports, "effectiveScope", { enumerable: true, get: function () { return scope_1.effectiveScope; } });
27
38
  var auth_1 = require("./auth");
28
39
  Object.defineProperty(exports, "verifyPassword", { enumerable: true, get: function () { return auth_1.verifyPassword; } });
29
40
  var seed_admin_1 = require("./seed-admin");
@@ -1,3 +1,4 @@
1
+ import type { LTReadScope, LTWriteScope } from '../../types';
1
2
  /**
2
3
  * Is the user a superadmin? (global — can manage all users and roles)
3
4
  */
@@ -24,6 +25,23 @@ export declare function canManageRole(actorId: string, role: string): Promise<bo
24
25
  * all roles, and can perform bulk actions.
25
26
  */
26
27
  export declare function hasGlobalEscalationAccess(userId: string): Promise<boolean>;
28
+ /**
29
+ * Resolve the user's effective scope for a single role — the breadth at which
30
+ * they may SEARCH (read) and ACT (write). One indexed PK lookup on lt_user_roles.
31
+ * Returns `null` when the user is not a member of the role.
32
+ *
33
+ * The management tier (admin/superadmin) short-circuits both axes to `'all'` via
34
+ * effectiveScope. Callers that already hold global access should skip this.
35
+ */
36
+ export declare function getRoleScope(userId: string, role: string): Promise<{
37
+ read: LTReadScope;
38
+ write: LTWriteScope;
39
+ } | null>;
40
+ /**
41
+ * Effective WRITE scope for a role: `'all'` (act on any item), `'self'` (only
42
+ * items assigned to the user), or `'none'` (read-only, or not a member).
43
+ */
44
+ export declare function getRoleWriteScope(userId: string, role: string): Promise<LTWriteScope>;
27
45
  /**
28
46
  * Batch check: does the user have admin type for ALL specified roles?
29
47
  * Single query — replaces the N+1 loop in checkBulkPermission.
@@ -4,9 +4,12 @@ exports.isSuperAdmin = isSuperAdmin;
4
4
  exports.isGroupAdmin = isGroupAdmin;
5
5
  exports.canManageRole = canManageRole;
6
6
  exports.hasGlobalEscalationAccess = hasGlobalEscalationAccess;
7
+ exports.getRoleScope = getRoleScope;
8
+ exports.getRoleWriteScope = getRoleWriteScope;
7
9
  exports.hasRolesAsAdmin = hasRolesAsAdmin;
8
10
  const db_1 = require("../../lib/db");
9
11
  const roles_1 = require("./roles");
12
+ const scope_1 = require("./scope");
10
13
  const sql_1 = require("./sql");
11
14
  // ─── RBAC helpers ─────────────────────────────────────────────────────────────
12
15
  /**
@@ -48,6 +51,30 @@ async function hasGlobalEscalationAccess(userId) {
48
51
  const roles = await (0, roles_1.getUserRoles)(userId);
49
52
  return roles.some((r) => r.role === 'admin' && r.type === 'admin');
50
53
  }
54
+ /**
55
+ * Resolve the user's effective scope for a single role — the breadth at which
56
+ * they may SEARCH (read) and ACT (write). One indexed PK lookup on lt_user_roles.
57
+ * Returns `null` when the user is not a member of the role.
58
+ *
59
+ * The management tier (admin/superadmin) short-circuits both axes to `'all'` via
60
+ * effectiveScope. Callers that already hold global access should skip this.
61
+ */
62
+ async function getRoleScope(userId, role) {
63
+ const pool = (0, db_1.getPool)();
64
+ const { rows } = await pool.query(sql_1.GET_ROLE_SCOPE, [userId, role]);
65
+ if (rows.length === 0)
66
+ return null;
67
+ const { type, read_scope, write_scope } = rows[0];
68
+ return (0, scope_1.effectiveScope)(type, read_scope, write_scope);
69
+ }
70
+ /**
71
+ * Effective WRITE scope for a role: `'all'` (act on any item), `'self'` (only
72
+ * items assigned to the user), or `'none'` (read-only, or not a member).
73
+ */
74
+ async function getRoleWriteScope(userId, role) {
75
+ const scope = await getRoleScope(userId, role);
76
+ return scope ? scope.write : 'none';
77
+ }
51
78
  /**
52
79
  * Batch check: does the user have admin type for ALL specified roles?
53
80
  * Single query — replaces the N+1 loop in checkBulkPermission.
@@ -1,6 +1,11 @@
1
- import type { LTUserRole, LTRoleType } from '../../types';
1
+ import type { LTReadScope, LTRoleType, LTUserRole, LTWriteScope } from '../../types';
2
2
  export declare function isValidRoleType(type: string): type is LTRoleType;
3
- export declare function addUserRole(userId: string, role: string, type: LTRoleType): Promise<LTUserRole>;
3
+ /** Optional work-surface scope for a membership. Defaults to ('all','all') = full worker. */
4
+ export interface RoleScopeInput {
5
+ read_scope?: LTReadScope;
6
+ write_scope?: LTWriteScope;
7
+ }
8
+ export declare function addUserRole(userId: string, role: string, type: LTRoleType, scope?: RoleScopeInput): Promise<LTUserRole>;
4
9
  export declare function removeUserRole(userId: string, role: string): Promise<boolean>;
5
10
  export declare function getUserRoles(userId: string): Promise<LTUserRole[]>;
6
11
  export declare function hasRole(userId: string, role: string): Promise<boolean>;
@@ -8,17 +8,33 @@ exports.hasRole = hasRole;
8
8
  exports.hasRoleType = hasRoleType;
9
9
  const db_1 = require("../../lib/db");
10
10
  const sql_1 = require("./sql");
11
+ const scope_1 = require("./scope");
11
12
  const types_1 = require("./types");
12
13
  // ─── Role management ──────────────────────────────────────────────────────────
13
14
  function isValidRoleType(type) {
14
15
  return types_1.VALID_ROLE_TYPES.includes(type);
15
16
  }
16
- async function addUserRole(userId, role, type) {
17
+ async function addUserRole(userId, role, type, scope) {
18
+ // admin/superadmin always store ('all','all'); members store the requested scope.
19
+ const eff = (0, scope_1.effectiveScope)(type, scope?.read_scope ?? scope_1.DEFAULT_READ_SCOPE, scope?.write_scope ?? scope_1.DEFAULT_WRITE_SCOPE);
17
20
  const pool = (0, db_1.getPool)();
18
- // Ensure the role exists in lt_roles (FK constraint)
19
- await pool.query(sql_1.ENSURE_ROLE_EXISTS, [role]);
20
- const { rows } = await pool.query(sql_1.UPSERT_USER_ROLE, [userId, role, type]);
21
- return rows[0];
21
+ // Ensure-role (FK target) and the scoped user-role upsert commit together so a
22
+ // failure between them cannot leave the FK target without its assignment.
23
+ const client = await pool.connect();
24
+ try {
25
+ await client.query('BEGIN');
26
+ await client.query(sql_1.ENSURE_ROLE_EXISTS, [role]);
27
+ const { rows } = await client.query(sql_1.UPSERT_USER_ROLE, [userId, role, type, eff.read, eff.write]);
28
+ await client.query('COMMIT');
29
+ return rows[0];
30
+ }
31
+ catch (err) {
32
+ await client.query('ROLLBACK');
33
+ throw err;
34
+ }
35
+ finally {
36
+ client.release();
37
+ }
22
38
  }
23
39
  async function removeUserRole(userId, role) {
24
40
  const pool = (0, db_1.getPool)();
@@ -0,0 +1,22 @@
1
+ import type { LTReadScope, LTRoleType, LTWriteScope } from '../../types';
2
+ export declare const READ_SCOPES: LTReadScope[];
3
+ export declare const WRITE_SCOPES: LTWriteScope[];
4
+ /** Defaults preserve legacy `member` behavior (full role worker). */
5
+ export declare const DEFAULT_READ_SCOPE: LTReadScope;
6
+ export declare const DEFAULT_WRITE_SCOPE: LTWriteScope;
7
+ export declare function isValidReadScope(scope: string): scope is LTReadScope;
8
+ export declare function isValidWriteScope(scope: string): scope is LTWriteScope;
9
+ /**
10
+ * Is (read, write) a valid point in the lattice? Enforces write ⊆ read:
11
+ * write='all' requires read='all'. (write='self'/'none' allowed under read='self'.)
12
+ */
13
+ export declare function isValidScopePair(read: LTReadScope, write: LTWriteScope): boolean;
14
+ /**
15
+ * Resolve the scope a membership actually grants. The management tier
16
+ * (admin/superadmin) short-circuits both axes to `all` — those users always
17
+ * search and act on the whole role queue regardless of stored scope columns.
18
+ */
19
+ export declare function effectiveScope(type: LTRoleType, read: LTReadScope, write: LTWriteScope): {
20
+ read: LTReadScope;
21
+ write: LTWriteScope;
22
+ };