@hotmeshio/long-tail 0.5.11 β†’ 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 (350) hide show
  1. package/README.md +1 -0
  2. package/build/api/diagnostics.d.ts +5 -0
  3. package/build/api/diagnostics.js +5 -1
  4. package/build/api/escalations/bulk.js +9 -1
  5. package/build/api/escalations/cancel.js +5 -7
  6. package/build/api/escalations/claim.js +18 -15
  7. package/build/api/escalations/create.d.ts +6 -0
  8. package/build/api/escalations/create.js +11 -8
  9. package/build/api/escalations/facets.d.ts +18 -0
  10. package/build/api/escalations/facets.js +115 -0
  11. package/build/api/escalations/helpers.d.ts +70 -1
  12. package/build/api/escalations/helpers.js +101 -7
  13. package/build/api/escalations/index.d.ts +3 -2
  14. package/build/api/escalations/index.js +7 -1
  15. package/build/api/escalations/list.d.ts +23 -2
  16. package/build/api/escalations/list.js +111 -10
  17. package/build/api/escalations/metadata.js +22 -27
  18. package/build/api/escalations/resolve.d.ts +19 -1
  19. package/build/api/escalations/resolve.js +111 -26
  20. package/build/api/escalations/single.js +8 -8
  21. package/build/api/settings.d.ts +1 -1
  22. package/build/api/settings.js +3 -1
  23. package/build/api/users.d.ts +6 -0
  24. package/build/api/users.js +29 -1
  25. package/build/api/workflows/invocation.d.ts +6 -0
  26. package/build/api/workflows/invocation.js +6 -2
  27. package/build/bin/ltc.js +40 -0
  28. package/build/lib/cli/commands/escalations.d.ts +36 -0
  29. package/build/lib/cli/commands/escalations.js +98 -0
  30. package/build/lib/db/schemas/001_schema.sql +10 -5
  31. package/build/lib/db/schemas/012_lt_tasks_workflow_id_unique.sql +20 -0
  32. package/build/lib/db/schemas/013_role_scope.sql +37 -0
  33. package/build/modules/features.d.ts +19 -0
  34. package/build/modules/features.js +16 -0
  35. package/build/modules/version.d.ts +10 -5
  36. package/build/modules/version.js +27 -11
  37. package/build/routes/diagnostics.js +15 -2
  38. package/build/routes/escalations/facets.d.ts +7 -0
  39. package/build/routes/escalations/facets.js +81 -0
  40. package/build/routes/escalations/index.js +3 -0
  41. package/build/routes/escalations/list.js +48 -0
  42. package/build/routes/escalations/resolve.js +22 -4
  43. package/build/routes/users.js +9 -5
  44. package/build/routes/workflows/invocation.js +12 -4
  45. package/build/sdk/index.d.ts +35 -2
  46. package/build/sdk/index.js +7 -0
  47. package/build/services/diagnostics/index.d.ts +40 -6
  48. package/build/services/diagnostics/index.js +54 -5
  49. package/build/services/escalation/crud.d.ts +56 -6
  50. package/build/services/escalation/crud.js +102 -31
  51. package/build/services/escalation/facet-sql.d.ts +18 -0
  52. package/build/services/escalation/facet-sql.js +118 -0
  53. package/build/services/escalation/facets.d.ts +45 -0
  54. package/build/services/escalation/facets.js +218 -0
  55. package/build/services/escalation/index.d.ts +1 -0
  56. package/build/services/escalation/index.js +8 -1
  57. package/build/services/escalation/queries.d.ts +47 -0
  58. package/build/services/escalation/queries.js +185 -2
  59. package/build/services/escalation/sql.d.ts +15 -2
  60. package/build/services/escalation/sql.js +80 -3
  61. package/build/services/interceptor/activities/escalation.d.ts +2 -0
  62. package/build/services/interceptor/activities/escalation.js +1 -1
  63. package/build/services/interceptor/lifecycle.js +4 -0
  64. package/build/services/mcp/server-tools.js +10 -4
  65. package/build/services/orchestrator/condition.js +6 -2
  66. package/build/services/role/index.js +2 -3
  67. package/build/services/role/sql.d.ts +8 -0
  68. package/build/services/role/sql.js +17 -1
  69. package/build/services/task/crud.js +22 -14
  70. package/build/services/task/process.js +3 -21
  71. package/build/services/task/resolve.d.ts +5 -3
  72. package/build/services/task/resolve.js +9 -6
  73. package/build/services/task/sql.d.ts +30 -2
  74. package/build/services/task/sql.js +72 -2
  75. package/build/services/user/crud.d.ts +1 -0
  76. package/build/services/user/crud.js +29 -11
  77. package/build/services/user/index.d.ts +2 -1
  78. package/build/services/user/index.js +12 -1
  79. package/build/services/user/rbac.d.ts +18 -0
  80. package/build/services/user/rbac.js +27 -0
  81. package/build/services/user/roles.d.ts +7 -2
  82. package/build/services/user/roles.js +21 -5
  83. package/build/services/user/scope.d.ts +22 -0
  84. package/build/services/user/scope.js +50 -0
  85. package/build/services/user/seed-admin.js +34 -17
  86. package/build/services/user/sql.d.ts +18 -7
  87. package/build/services/user/sql.js +38 -13
  88. package/build/services/user/sso-provision.js +41 -24
  89. package/build/services/user/types.d.ts +9 -5
  90. package/build/start/adapters.js +3 -0
  91. package/build/system/mcp-servers/admin/diagnostics.d.ts +5 -4
  92. package/build/system/mcp-servers/admin/diagnostics.js +12 -10
  93. package/build/system/mcp-servers/admin/escalations.d.ts +6 -0
  94. package/build/system/mcp-servers/admin/escalations.js +247 -54
  95. package/build/system/mcp-servers/admin/index.js +3 -3
  96. package/build/system/mcp-servers/admin/schemas.d.ts +417 -3
  97. package/build/system/mcp-servers/admin/schemas.js +101 -5
  98. package/build/system/mcp-servers/admin/users.js +28 -2
  99. package/build/system/mcp-servers/admin/workflows.js +23 -12
  100. package/build/system/seed/tool-manifests-admin.d.ts +1324 -562
  101. package/build/system/seed/tool-manifests-admin.js +10 -2
  102. package/build/tsconfig.tsbuildinfo +1 -1
  103. package/build/types/envelope.d.ts +7 -0
  104. package/build/types/facets.d.ts +60 -0
  105. package/build/types/facets.js +10 -0
  106. package/build/types/index.d.ts +2 -1
  107. package/build/types/startup.d.ts +9 -0
  108. package/build/types/user.d.ts +11 -0
  109. package/dashboard/dist/assets/AdminDashboard-2CZ8py9X.js +2 -0
  110. package/dashboard/dist/assets/{AdminDashboard-DZpduYdW.js.map β†’ AdminDashboard-2CZ8py9X.js.map} +1 -1
  111. package/dashboard/dist/assets/{AgentConfigPage-SFnHMIP6.js β†’ AgentConfigPage-D68sodgo.js} +7 -7
  112. package/dashboard/dist/assets/{AgentConfigPage-SFnHMIP6.js.map β†’ AgentConfigPage-D68sodgo.js.map} +1 -1
  113. package/dashboard/dist/assets/AgentDetailPage-BmhuWbKi.js +4 -0
  114. package/dashboard/dist/assets/AgentDetailPage-BmhuWbKi.js.map +1 -0
  115. package/dashboard/dist/assets/AgentsPage-C4SKQVeU.js +2 -0
  116. package/dashboard/dist/assets/AgentsPage-C4SKQVeU.js.map +1 -0
  117. package/dashboard/dist/assets/AvailableEscalationsPage-9RBEYwkt.js +2 -0
  118. package/dashboard/dist/assets/AvailableEscalationsPage-9RBEYwkt.js.map +1 -0
  119. package/dashboard/dist/assets/{BotPicker-Dse2jW4b.js β†’ BotPicker-D-FvXJOM.js} +2 -2
  120. package/dashboard/dist/assets/{BotPicker-Dse2jW4b.js.map β†’ BotPicker-D-FvXJOM.js.map} +1 -1
  121. package/dashboard/dist/assets/CapabilitiesPage-ByDf6WJr.js +2 -0
  122. package/dashboard/dist/assets/{CapabilitiesPage-DTfKCzBQ.js.map β†’ CapabilitiesPage-ByDf6WJr.js.map} +1 -1
  123. package/dashboard/dist/assets/{CollapsibleSection-8vf38J6K.js β†’ CollapsibleSection-BjGNYEvh.js} +2 -2
  124. package/dashboard/dist/assets/{CollapsibleSection-8vf38J6K.js.map β†’ CollapsibleSection-BjGNYEvh.js.map} +1 -1
  125. package/dashboard/dist/assets/CopyableId-blg-gZvT.js +2 -0
  126. package/dashboard/dist/assets/CopyableId-blg-gZvT.js.map +1 -0
  127. package/dashboard/dist/assets/{CredentialsPage-7C4M-hrH.js β†’ CredentialsPage-DvCA-wQN.js} +2 -2
  128. package/dashboard/dist/assets/{CredentialsPage-7C4M-hrH.js.map β†’ CredentialsPage-DvCA-wQN.js.map} +1 -1
  129. package/dashboard/dist/assets/{CronLabel-DrGWtaSY.js β†’ CronLabel-DBMurd_c.js} +2 -2
  130. package/dashboard/dist/assets/{CronLabel-DrGWtaSY.js.map β†’ CronLabel-DBMurd_c.js.map} +1 -1
  131. package/dashboard/dist/assets/{CustomDurationPicker-Vcmr4LiR.js β†’ CustomDurationPicker-DHflFoEa.js} +2 -2
  132. package/dashboard/dist/assets/{CustomDurationPicker-Vcmr4LiR.js.map β†’ CustomDurationPicker-DHflFoEa.js.map} +1 -1
  133. package/dashboard/dist/assets/{DropZone-Dwpgbueb.js β†’ DropZone-CUtdp99Z.js} +2 -2
  134. package/dashboard/dist/assets/{DropZone-Dwpgbueb.js.map β†’ DropZone-CUtdp99Z.js.map} +1 -1
  135. package/dashboard/dist/assets/{ElapsedCell-BNexhR5d.js β†’ ElapsedCell-CSjcVOpo.js} +2 -2
  136. package/dashboard/dist/assets/{ElapsedCell-BNexhR5d.js.map β†’ ElapsedCell-CSjcVOpo.js.map} +1 -1
  137. package/dashboard/dist/assets/{EscalationsOverview-BI3DEwYP.js β†’ EscalationsOverview-BUqRNJbK.js} +2 -2
  138. package/dashboard/dist/assets/{EscalationsOverview-BI3DEwYP.js.map β†’ EscalationsOverview-BUqRNJbK.js.map} +1 -1
  139. package/dashboard/dist/assets/{EventTable-CwvX_L9t.js β†’ EventTable-Chz-6XPE.js} +2 -2
  140. package/dashboard/dist/assets/{EventTable-CwvX_L9t.js.map β†’ EventTable-Chz-6XPE.js.map} +1 -1
  141. package/dashboard/dist/assets/{GraphInvokePage-C-3b8WK0.js β†’ GraphInvokePage-BXdgciWg.js} +2 -2
  142. package/dashboard/dist/assets/{GraphInvokePage-C-3b8WK0.js.map β†’ GraphInvokePage-BXdgciWg.js.map} +1 -1
  143. package/dashboard/dist/assets/{HomePage-BS_-jcHr.js β†’ HomePage-CEMxnk0r.js} +2 -2
  144. package/dashboard/dist/assets/{HomePage-BS_-jcHr.js.map β†’ HomePage-CEMxnk0r.js.map} +1 -1
  145. package/dashboard/dist/assets/{ListToolbar-BH9rwQVE.js β†’ ListToolbar-_73JCdbB.js} +2 -2
  146. package/dashboard/dist/assets/{ListToolbar-BH9rwQVE.js.map β†’ ListToolbar-_73JCdbB.js.map} +1 -1
  147. package/dashboard/dist/assets/{McpOverview-C9iOdE7E.js β†’ McpOverview-DfdqxhSz.js} +2 -2
  148. package/dashboard/dist/assets/{McpOverview-C9iOdE7E.js.map β†’ McpOverview-DfdqxhSz.js.map} +1 -1
  149. package/dashboard/dist/assets/{McpQueryDetailPage-DMN-yqvJ.js β†’ McpQueryDetailPage-DPAljieY.js} +2 -2
  150. package/dashboard/dist/assets/{McpQueryDetailPage-DMN-yqvJ.js.map β†’ McpQueryDetailPage-DPAljieY.js.map} +1 -1
  151. package/dashboard/dist/assets/McpQueryPage-D5cSziYS.js +2 -0
  152. package/dashboard/dist/assets/McpQueryPage-D5cSziYS.js.map +1 -0
  153. package/dashboard/dist/assets/{McpRunDetailPage-Wb47sRvo.js β†’ McpRunDetailPage-DwNb9wZg.js} +2 -2
  154. package/dashboard/dist/assets/{McpRunDetailPage-Wb47sRvo.js.map β†’ McpRunDetailPage-DwNb9wZg.js.map} +1 -1
  155. package/dashboard/dist/assets/McpRunsPage-O5H6yStl.js +2 -0
  156. package/dashboard/dist/assets/McpRunsPage-O5H6yStl.js.map +1 -0
  157. package/dashboard/dist/assets/{NamespacePill-DnJl4Lre.js β†’ NamespacePill-uW-xOedj.js} +2 -2
  158. package/dashboard/dist/assets/{NamespacePill-DnJl4Lre.js.map β†’ NamespacePill-uW-xOedj.js.map} +1 -1
  159. package/dashboard/dist/assets/OperatorDashboard-DYw9Ekv5.js +2 -0
  160. package/dashboard/dist/assets/OperatorDashboard-DYw9Ekv5.js.map +1 -0
  161. package/dashboard/dist/assets/PageHeader-DVOZjBfL.js +2 -0
  162. package/dashboard/dist/assets/PageHeader-DVOZjBfL.js.map +1 -0
  163. package/dashboard/dist/assets/{PageHeaderWithStats-BuKc9MKt.js β†’ PageHeaderWithStats-DtTpDHOd.js} +2 -2
  164. package/dashboard/dist/assets/{PageHeaderWithStats-BuKc9MKt.js.map β†’ PageHeaderWithStats-DtTpDHOd.js.map} +1 -1
  165. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js +2 -0
  166. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js.map +1 -0
  167. package/dashboard/dist/assets/{ProcessDetailPage-CvPx-h8y.js β†’ ProcessDetailPage-B0Dokcx9.js} +2 -2
  168. package/dashboard/dist/assets/{ProcessDetailPage-CvPx-h8y.js.map β†’ ProcessDetailPage-B0Dokcx9.js.map} +1 -1
  169. package/dashboard/dist/assets/ProcessesListPage-Cc87WIjL.js +2 -0
  170. package/dashboard/dist/assets/ProcessesListPage-Cc87WIjL.js.map +1 -0
  171. package/dashboard/dist/assets/RolePill-BaW-30TC.js +2 -0
  172. package/dashboard/dist/assets/RolePill-BaW-30TC.js.map +1 -0
  173. package/dashboard/dist/assets/{RolesPage-W5ZjOHpg.js β†’ RolesPage-cp6PiF08.js} +2 -2
  174. package/dashboard/dist/assets/{RolesPage-W5ZjOHpg.js.map β†’ RolesPage-cp6PiF08.js.map} +1 -1
  175. package/dashboard/dist/assets/RowActions-BA9L9djf.js +2 -0
  176. package/dashboard/dist/assets/RowActions-BA9L9djf.js.map +1 -0
  177. package/dashboard/dist/assets/{RunAsSelector-CvxGTNGT.js β†’ RunAsSelector-2LWkjSnO.js} +2 -2
  178. package/dashboard/dist/assets/{RunAsSelector-CvxGTNGT.js.map β†’ RunAsSelector-2LWkjSnO.js.map} +1 -1
  179. package/dashboard/dist/assets/{StickyPagination-BWhFSr2d.js β†’ StickyPagination-BQEm--kI.js} +2 -2
  180. package/dashboard/dist/assets/{StickyPagination-BWhFSr2d.js.map β†’ StickyPagination-BQEm--kI.js.map} +1 -1
  181. package/dashboard/dist/assets/StreamMessageDetail-BTtw3GjX.js +2 -0
  182. package/dashboard/dist/assets/{StreamMessageDetail-BF0tM0cd.js.map β†’ StreamMessageDetail-BTtw3GjX.js.map} +1 -1
  183. package/dashboard/dist/assets/{SwimlaneTimeline-Bv-KHY9s.js β†’ SwimlaneTimeline-DdXxP4fO.js} +2 -2
  184. package/dashboard/dist/assets/{SwimlaneTimeline-Bv-KHY9s.js.map β†’ SwimlaneTimeline-DdXxP4fO.js.map} +1 -1
  185. package/dashboard/dist/assets/{TagInput-B00uqhjX.js β†’ TagInput-x9XTuqMb.js} +2 -2
  186. package/dashboard/dist/assets/{TagInput-B00uqhjX.js.map β†’ TagInput-x9XTuqMb.js.map} +1 -1
  187. package/dashboard/dist/assets/{TaskDetailPage-Bzb_fuLy.js β†’ TaskDetailPage-B8veYG4X.js} +2 -2
  188. package/dashboard/dist/assets/{TaskDetailPage-Bzb_fuLy.js.map β†’ TaskDetailPage-B8veYG4X.js.map} +1 -1
  189. package/dashboard/dist/assets/{TaskQueuePill-CUsofm0X.js β†’ TaskQueuePill-DSEQZ-HT.js} +2 -2
  190. package/dashboard/dist/assets/{TaskQueuePill-CUsofm0X.js.map β†’ TaskQueuePill-DSEQZ-HT.js.map} +1 -1
  191. package/dashboard/dist/assets/{TasksListPage-CBezWaZL.js β†’ TasksListPage-CsN1gZj5.js} +2 -2
  192. package/dashboard/dist/assets/{TasksListPage-CBezWaZL.js.map β†’ TasksListPage-CsN1gZj5.js.map} +1 -1
  193. package/dashboard/dist/assets/{TimeAgo-CKHfTzat.js β†’ TimeAgo-n182EcLx.js} +2 -2
  194. package/dashboard/dist/assets/{TimeAgo-CKHfTzat.js.map β†’ TimeAgo-n182EcLx.js.map} +1 -1
  195. package/dashboard/dist/assets/{TimestampCell-Dsje8GH-.js β†’ TimestampCell-2dlcQAfX.js} +2 -2
  196. package/dashboard/dist/assets/{TimestampCell-Dsje8GH-.js.map β†’ TimestampCell-2dlcQAfX.js.map} +1 -1
  197. package/dashboard/dist/assets/{ToolPill-DjvedZSn.js β†’ ToolPill-B_e4eT77.js} +2 -2
  198. package/dashboard/dist/assets/{ToolPill-DjvedZSn.js.map β†’ ToolPill-B_e4eT77.js.map} +1 -1
  199. package/dashboard/dist/assets/{ToolTestPanel-3OcG18e5.js β†’ ToolTestPanel-Bhnncc6M.js} +2 -2
  200. package/dashboard/dist/assets/{ToolTestPanel-3OcG18e5.js.map β†’ ToolTestPanel-Bhnncc6M.js.map} +1 -1
  201. package/dashboard/dist/assets/{TopicDetailPage-DCtv91hH.js β†’ TopicDetailPage-29lsNsqI.js} +3 -3
  202. package/dashboard/dist/assets/{TopicDetailPage-DCtv91hH.js.map β†’ TopicDetailPage-29lsNsqI.js.map} +1 -1
  203. package/dashboard/dist/assets/{TopicsPage-DVGSNc_E.js β†’ TopicsPage-KJ7_LAvo.js} +2 -2
  204. package/dashboard/dist/assets/{TopicsPage-DVGSNc_E.js.map β†’ TopicsPage-KJ7_LAvo.js.map} +1 -1
  205. package/dashboard/dist/assets/{UserName-xHAgkk8M.js β†’ UserName-CMoFi9zb.js} +2 -2
  206. package/dashboard/dist/assets/{UserName-xHAgkk8M.js.map β†’ UserName-CMoFi9zb.js.map} +1 -1
  207. package/dashboard/dist/assets/{WorkflowExecutionPage-CWNxsS8a.js β†’ WorkflowExecutionPage-Cymfx-Bp.js} +2 -2
  208. package/dashboard/dist/assets/{WorkflowExecutionPage-CWNxsS8a.js.map β†’ WorkflowExecutionPage-Cymfx-Bp.js.map} +1 -1
  209. package/dashboard/dist/assets/{WorkflowPill-F1oKUzmc.js β†’ WorkflowPill-B66_AnaY.js} +2 -2
  210. package/dashboard/dist/assets/{WorkflowPill-F1oKUzmc.js.map β†’ WorkflowPill-B66_AnaY.js.map} +1 -1
  211. package/dashboard/dist/assets/WorkflowsDashboard-CqLN8spd.js +2 -0
  212. package/dashboard/dist/assets/WorkflowsDashboard-CqLN8spd.js.map +1 -0
  213. package/dashboard/dist/assets/{WorkflowsOverview-e7jWSj_n.js β†’ WorkflowsOverview-0Rc2WT6q.js} +2 -2
  214. package/dashboard/dist/assets/{WorkflowsOverview-e7jWSj_n.js.map β†’ WorkflowsOverview-0Rc2WT6q.js.map} +1 -1
  215. package/dashboard/dist/assets/{YamlWorkflowDetailPage-CdAeam76.js β†’ YamlWorkflowDetailPage-CARQ7_LN.js} +2 -2
  216. package/dashboard/dist/assets/{YamlWorkflowDetailPage-CdAeam76.js.map β†’ YamlWorkflowDetailPage-CARQ7_LN.js.map} +1 -1
  217. package/dashboard/dist/assets/{YamlWorkflowsPage-S_gKs2Di.js β†’ YamlWorkflowsPage-B-7sz1nf.js} +2 -2
  218. package/dashboard/dist/assets/{YamlWorkflowsPage-S_gKs2Di.js.map β†’ YamlWorkflowsPage-B-7sz1nf.js.map} +1 -1
  219. package/dashboard/dist/assets/{agents-JE4aL07D.js β†’ agents-zM43d5Yf.js} +2 -2
  220. package/dashboard/dist/assets/{agents-JE4aL07D.js.map β†’ agents-zM43d5Yf.js.map} +1 -1
  221. package/dashboard/dist/assets/api-path-u2Jf9QXS.js +2 -0
  222. package/dashboard/dist/assets/api-path-u2Jf9QXS.js.map +1 -0
  223. package/dashboard/dist/assets/{bots-cAzKkDtQ.js β†’ bots-D5BasOGz.js} +2 -2
  224. package/dashboard/dist/assets/{bots-cAzKkDtQ.js.map β†’ bots-D5BasOGz.js.map} +1 -1
  225. package/dashboard/dist/assets/{capabilities-RBFXLAXF.js β†’ capabilities-BT3PW9CZ.js} +2 -2
  226. package/dashboard/dist/assets/{capabilities-RBFXLAXF.js.map β†’ capabilities-BT3PW9CZ.js.map} +1 -1
  227. package/dashboard/dist/assets/{controlplane-DGhv3rwI.js β†’ controlplane-NpJr49rN.js} +2 -2
  228. package/dashboard/dist/assets/{controlplane-DGhv3rwI.js.map β†’ controlplane-NpJr49rN.js.map} +1 -1
  229. package/dashboard/dist/assets/{escalation-ne7dIaPa.js β†’ escalation-DrzQolPG.js} +2 -2
  230. package/dashboard/dist/assets/{escalation-ne7dIaPa.js.map β†’ escalation-DrzQolPG.js.map} +1 -1
  231. package/dashboard/dist/assets/escalation-columns-QzwmtD60.js +2 -0
  232. package/dashboard/dist/assets/escalation-columns-QzwmtD60.js.map +1 -0
  233. package/dashboard/dist/assets/index-B5w4Gjyk.js +6 -0
  234. package/dashboard/dist/assets/index-B5w4Gjyk.js.map +1 -0
  235. package/dashboard/dist/assets/{index-CByjpYlU.js β†’ index-BCT4kqGR.js} +2 -2
  236. package/dashboard/dist/assets/{index-CByjpYlU.js.map β†’ index-BCT4kqGR.js.map} +1 -1
  237. package/dashboard/dist/assets/{index-CgyucK0e.js β†’ index-C69XvBfg.js} +2 -2
  238. package/dashboard/dist/assets/{index-CgyucK0e.js.map β†’ index-C69XvBfg.js.map} +1 -1
  239. package/dashboard/dist/assets/index-CHw4Fl0t.css +1 -0
  240. package/dashboard/dist/assets/index-C_NwHTMh.js +2 -0
  241. package/dashboard/dist/assets/index-C_NwHTMh.js.map +1 -0
  242. package/dashboard/dist/assets/{index-DgzgRTNg.js β†’ index-Cq0trGqf.js} +2 -2
  243. package/dashboard/dist/assets/{index-DgzgRTNg.js.map β†’ index-Cq0trGqf.js.map} +1 -1
  244. package/dashboard/dist/assets/{index-DPJW96d2.js β†’ index-DE0r9SEX.js} +26 -26
  245. package/dashboard/dist/assets/index-DE0r9SEX.js.map +1 -0
  246. package/dashboard/dist/assets/{index-BTqhAT6p.js β†’ index-DH_CiD0W.js} +2 -2
  247. package/dashboard/dist/assets/{index-BTqhAT6p.js.map β†’ index-DH_CiD0W.js.map} +1 -1
  248. package/dashboard/dist/assets/{index-C2l4GcZ7.js β†’ index-K23FdS-T.js} +3 -3
  249. package/dashboard/dist/assets/{index-C2l4GcZ7.js.map β†’ index-K23FdS-T.js.map} +1 -1
  250. package/dashboard/dist/assets/index-f6K1UNrn.js +2 -0
  251. package/dashboard/dist/assets/index-f6K1UNrn.js.map +1 -0
  252. package/dashboard/dist/assets/{index-BbJuC9kg.js β†’ index-qr1fndqM.js} +2 -2
  253. package/dashboard/dist/assets/{index-BbJuC9kg.js.map β†’ index-qr1fndqM.js.map} +1 -1
  254. package/dashboard/dist/assets/index-vKTvJ_li.js +2 -0
  255. package/dashboard/dist/assets/index-vKTvJ_li.js.map +1 -0
  256. package/dashboard/dist/assets/{index-CQDn-bLr.js β†’ index-y-3ODtEE.js} +2 -2
  257. package/dashboard/dist/assets/{index-CQDn-bLr.js.map β†’ index-y-3ODtEE.js.map} +1 -1
  258. package/dashboard/dist/assets/{index-QxAYCJ1S.js β†’ index-y56Ds-LB.js} +2 -2
  259. package/dashboard/dist/assets/{index-QxAYCJ1S.js.map β†’ index-y56Ds-LB.js.map} +1 -1
  260. package/dashboard/dist/assets/{knowledge-DzRpsm3D.js β†’ knowledge-DSSATOTX.js} +2 -2
  261. package/dashboard/dist/assets/{knowledge-DzRpsm3D.js.map β†’ knowledge-DSSATOTX.js.map} +1 -1
  262. package/dashboard/dist/assets/{mcp-B1lc6nL4.js β†’ mcp-CfJFVo5k.js} +2 -2
  263. package/dashboard/dist/assets/{mcp-B1lc6nL4.js.map β†’ mcp-CfJFVo5k.js.map} +1 -1
  264. package/dashboard/dist/assets/{mcp-query-CXuweaN3.js β†’ mcp-query-DKVx2Q-X.js} +2 -2
  265. package/dashboard/dist/assets/{mcp-query-CXuweaN3.js.map β†’ mcp-query-DKVx2Q-X.js.map} +1 -1
  266. package/dashboard/dist/assets/{pipelines-D4VLWDvu.js β†’ pipelines-BSWzE-6M.js} +2 -2
  267. package/dashboard/dist/assets/{pipelines-D4VLWDvu.js.map β†’ pipelines-BSWzE-6M.js.map} +1 -1
  268. package/dashboard/dist/assets/{roles-CSpHRRu8.js β†’ roles-t9HzhI5N.js} +2 -2
  269. package/dashboard/dist/assets/{roles-CSpHRRu8.js.map β†’ roles-t9HzhI5N.js.map} +1 -1
  270. package/dashboard/dist/assets/{tasks-CWUsKhM4.js β†’ tasks-B7rUihoH.js} +2 -2
  271. package/dashboard/dist/assets/{tasks-CWUsKhM4.js.map β†’ tasks-B7rUihoH.js.map} +1 -1
  272. package/dashboard/dist/assets/{topics-BXwstrzB.js β†’ topics-PC2fEiyD.js} +2 -2
  273. package/dashboard/dist/assets/{topics-BXwstrzB.js.map β†’ topics-PC2fEiyD.js.map} +1 -1
  274. package/dashboard/dist/assets/useDebouncedValue-BA5S7V54.js +2 -0
  275. package/dashboard/dist/assets/useDebouncedValue-BA5S7V54.js.map +1 -0
  276. package/dashboard/dist/assets/{useEventHooks-CjygrbYy.js β†’ useEventHooks-DK9mw1cI.js} +2 -2
  277. package/dashboard/dist/assets/{useEventHooks-CjygrbYy.js.map β†’ useEventHooks-DK9mw1cI.js.map} +1 -1
  278. package/dashboard/dist/assets/{useNamespace-CsepDyR5.js β†’ useNamespace-3IjtUtAw.js} +2 -2
  279. package/dashboard/dist/assets/{useNamespace-CsepDyR5.js.map β†’ useNamespace-3IjtUtAw.js.map} +1 -1
  280. package/dashboard/dist/assets/{useYamlActivityEvents-gWIG6akB.js β†’ useYamlActivityEvents-uxukp3gO.js} +2 -2
  281. package/dashboard/dist/assets/{useYamlActivityEvents-gWIG6akB.js.map β†’ useYamlActivityEvents-uxukp3gO.js.map} +1 -1
  282. package/dashboard/dist/assets/users-CC4B5Fm5.js +2 -0
  283. package/dashboard/dist/assets/users-CC4B5Fm5.js.map +1 -0
  284. package/dashboard/dist/assets/{vendor-icons-Dj2F0Jrb.js β†’ vendor-icons-Dy5Jpknf.js} +114 -109
  285. package/dashboard/dist/assets/vendor-icons-Dy5Jpknf.js.map +1 -0
  286. package/dashboard/dist/assets/{workflows-C7Cam2Ok.js β†’ workflows-S7MF1nNk.js} +2 -2
  287. package/dashboard/dist/assets/{workflows-C7Cam2Ok.js.map β†’ workflows-S7MF1nNk.js.map} +1 -1
  288. package/dashboard/dist/assets/{yaml-workflows-ChUOE22W.js β†’ yaml-workflows-WfdaMHNn.js} +2 -2
  289. package/dashboard/dist/assets/{yaml-workflows-ChUOE22W.js.map β†’ yaml-workflows-WfdaMHNn.js.map} +1 -1
  290. package/dashboard/dist/index.html +3 -3
  291. package/docs/api/http/escalations.md +131 -10
  292. package/docs/api/http/roles.md +44 -9
  293. package/docs/api/http/users.md +15 -6
  294. package/docs/api/mcp/admin.md +164 -14
  295. package/docs/api/mcp/human-queue.md +34 -2
  296. package/docs/api/sdk/escalations.md +155 -2
  297. package/docs/api/sdk/roles.md +11 -0
  298. package/docs/api/sdk/users.md +26 -1
  299. package/docs/auth.md +1 -1
  300. package/docs/dashboard.md +5 -4
  301. package/docs/data.md +9 -2
  302. package/docs/faceted-routing.md +221 -0
  303. package/docs/hitl-guide.md +50 -0
  304. package/docs/iam.md +31 -0
  305. package/docs/mcp.md +8 -0
  306. package/package.json +2 -2
  307. package/dashboard/dist/assets/AdminDashboard-DZpduYdW.js +0 -2
  308. package/dashboard/dist/assets/AgentDetailPage-C-2iSaDs.js +0 -4
  309. package/dashboard/dist/assets/AgentDetailPage-C-2iSaDs.js.map +0 -1
  310. package/dashboard/dist/assets/AgentsPage-C3FztFd_.js +0 -2
  311. package/dashboard/dist/assets/AgentsPage-C3FztFd_.js.map +0 -1
  312. package/dashboard/dist/assets/AvailableEscalationsPage-DIqdBCwe.js +0 -2
  313. package/dashboard/dist/assets/AvailableEscalationsPage-DIqdBCwe.js.map +0 -1
  314. package/dashboard/dist/assets/CapabilitiesPage-DTfKCzBQ.js +0 -2
  315. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js +0 -2
  316. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js.map +0 -1
  317. package/dashboard/dist/assets/McpQueryPage-Db1LUKVs.js +0 -2
  318. package/dashboard/dist/assets/McpQueryPage-Db1LUKVs.js.map +0 -1
  319. package/dashboard/dist/assets/McpRunsPage--tgT5uo7.js +0 -2
  320. package/dashboard/dist/assets/McpRunsPage--tgT5uo7.js.map +0 -1
  321. package/dashboard/dist/assets/OperatorDashboard-BWVUbJkZ.js +0 -2
  322. package/dashboard/dist/assets/OperatorDashboard-BWVUbJkZ.js.map +0 -1
  323. package/dashboard/dist/assets/PageHeader-Bnt2iQQa.js +0 -2
  324. package/dashboard/dist/assets/PageHeader-Bnt2iQQa.js.map +0 -1
  325. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js +0 -2
  326. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js.map +0 -1
  327. package/dashboard/dist/assets/ProcessesListPage-CWiRM_h3.js +0 -2
  328. package/dashboard/dist/assets/ProcessesListPage-CWiRM_h3.js.map +0 -1
  329. package/dashboard/dist/assets/RolePill-DQTT1s9v.js +0 -2
  330. package/dashboard/dist/assets/RolePill-DQTT1s9v.js.map +0 -1
  331. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js +0 -2
  332. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js.map +0 -1
  333. package/dashboard/dist/assets/StreamMessageDetail-BF0tM0cd.js +0 -2
  334. package/dashboard/dist/assets/WorkflowsDashboard-M13lIaw7.js +0 -2
  335. package/dashboard/dist/assets/WorkflowsDashboard-M13lIaw7.js.map +0 -1
  336. package/dashboard/dist/assets/escalation-columns-DqTkKbCv.js +0 -2
  337. package/dashboard/dist/assets/escalation-columns-DqTkKbCv.js.map +0 -1
  338. package/dashboard/dist/assets/index-CEpw_Fh8.js +0 -2
  339. package/dashboard/dist/assets/index-CEpw_Fh8.js.map +0 -1
  340. package/dashboard/dist/assets/index-ClvYSNZd.css +0 -1
  341. package/dashboard/dist/assets/index-CucXk58o.js +0 -6
  342. package/dashboard/dist/assets/index-CucXk58o.js.map +0 -1
  343. package/dashboard/dist/assets/index-DPJW96d2.js.map +0 -1
  344. package/dashboard/dist/assets/index-DeZhNCf5.js +0 -2
  345. package/dashboard/dist/assets/index-DeZhNCf5.js.map +0 -1
  346. package/dashboard/dist/assets/index-Do8Xp3pa.js +0 -2
  347. package/dashboard/dist/assets/index-Do8Xp3pa.js.map +0 -1
  348. package/dashboard/dist/assets/users-DvhHbR0R.js +0 -2
  349. package/dashboard/dist/assets/users-DvhHbR0R.js.map +0 -1
  350. package/dashboard/dist/assets/vendor-icons-Dj2F0Jrb.js.map +0 -1
@@ -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
+ };
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_WRITE_SCOPE = exports.DEFAULT_READ_SCOPE = exports.WRITE_SCOPES = exports.READ_SCOPES = void 0;
4
+ exports.isValidReadScope = isValidReadScope;
5
+ exports.isValidWriteScope = isValidWriteScope;
6
+ exports.isValidScopePair = isValidScopePair;
7
+ exports.effectiveScope = effectiveScope;
8
+ // ─── Role scope: the work-surface axes for an open-role membership ───────────
9
+ //
10
+ // A membership's `type` (member/admin/superadmin) is the management/global tier.
11
+ // Orthogonal to it, a member also carries two scope axes that gate the four task
12
+ // queue verbs:
13
+ //
14
+ // read_scope (self | all) β†’ governs SEARCH (which items appear)
15
+ // write_scope (none | self | all) β†’ governs CLAIM / ACK / DELETE (which items
16
+ // the user may act on)
17
+ //
18
+ // `self` = items assigned to the user (`assigned_to = userId`). `all` = the whole
19
+ // role queue. The only constraint is write βŠ† read: you cannot act on what you
20
+ // cannot see (enforced in the DB by chk_lt_user_roles_scope). admin/superadmin
21
+ // ignore scope and always operate on all.
22
+ exports.READ_SCOPES = ['self', 'all'];
23
+ exports.WRITE_SCOPES = ['none', 'self', 'all'];
24
+ /** Defaults preserve legacy `member` behavior (full role worker). */
25
+ exports.DEFAULT_READ_SCOPE = 'all';
26
+ exports.DEFAULT_WRITE_SCOPE = 'all';
27
+ function isValidReadScope(scope) {
28
+ return exports.READ_SCOPES.includes(scope);
29
+ }
30
+ function isValidWriteScope(scope) {
31
+ return exports.WRITE_SCOPES.includes(scope);
32
+ }
33
+ /**
34
+ * Is (read, write) a valid point in the lattice? Enforces write βŠ† read:
35
+ * write='all' requires read='all'. (write='self'/'none' allowed under read='self'.)
36
+ */
37
+ function isValidScopePair(read, write) {
38
+ return !(write === 'all' && read === 'self');
39
+ }
40
+ /**
41
+ * Resolve the scope a membership actually grants. The management tier
42
+ * (admin/superadmin) short-circuits both axes to `all` β€” those users always
43
+ * search and act on the whole role queue regardless of stored scope columns.
44
+ */
45
+ function effectiveScope(type, read, write) {
46
+ if (type === 'admin' || type === 'superadmin') {
47
+ return { read: 'all', write: 'all' };
48
+ }
49
+ return { read, write };
50
+ }
@@ -20,23 +20,40 @@ async function seedAdmin(input) {
20
20
  catch { /* ON CONFLICT DO NOTHING */ }
21
21
  const existing = await (0, crud_1.getUserByExternalId)(externalId);
22
22
  if (existing) {
23
- // Ensure existing user has the superadmin role
24
- const roles = await (0, roles_1.getUserRoles)(existing.id);
25
- const hasSuperadmin = roles.some((r) => r.role === 'superadmin' && r.type === 'superadmin');
26
- if (!hasSuperadmin) {
27
- await (0, roles_1.addUserRole)(existing.id, 'superadmin', 'superadmin');
28
- logger_1.loggerRegistry.info(`[seed-admin] granted superadmin role to ${externalId}`);
23
+ return ensureSuperadmin(existing.id, externalId);
24
+ }
25
+ // Create. Concurrent container startups can both reach here; external_id UNIQUE
26
+ // is the arbiter β€” on the loser's 23505, adopt the winner's row instead of
27
+ // crashing the boot sequence.
28
+ try {
29
+ const user = await (0, crud_1.createUser)({
30
+ external_id: externalId,
31
+ email: email || undefined,
32
+ display_name: displayName || externalId,
33
+ password: password || undefined,
34
+ roles: [{ role: 'superadmin', type: 'superadmin' }],
35
+ });
36
+ logger_1.loggerRegistry.info(`[seed-admin] created superadmin user: ${externalId}`);
37
+ return user.id;
38
+ }
39
+ catch (err) {
40
+ if (err?.code === '23505') {
41
+ const winner = await (0, crud_1.getUserByExternalId)(externalId);
42
+ if (winner) {
43
+ return ensureSuperadmin(winner.id, externalId);
44
+ }
29
45
  }
30
- logger_1.loggerRegistry.info(`[seed-admin] ${externalId} already exists, skipping creation`);
31
- return existing.id;
46
+ throw err;
47
+ }
48
+ }
49
+ /** Ensure an existing user carries the superadmin role; returns its id. */
50
+ async function ensureSuperadmin(userId, externalId) {
51
+ const roles = await (0, roles_1.getUserRoles)(userId);
52
+ const hasSuperadmin = roles.some((r) => r.role === 'superadmin' && r.type === 'superadmin');
53
+ if (!hasSuperadmin) {
54
+ await (0, roles_1.addUserRole)(userId, 'superadmin', 'superadmin');
55
+ logger_1.loggerRegistry.info(`[seed-admin] granted superadmin role to ${externalId}`);
32
56
  }
33
- const user = await (0, crud_1.createUser)({
34
- external_id: externalId,
35
- email: email || undefined,
36
- display_name: displayName || externalId,
37
- password: password || undefined,
38
- roles: [{ role: 'superadmin', type: 'superadmin' }],
39
- });
40
- logger_1.loggerRegistry.info(`[seed-admin] created superadmin user: ${externalId}`);
41
- return user.id;
57
+ logger_1.loggerRegistry.info(`[seed-admin] ${externalId} already exists, skipping creation`);
58
+ return userId;
42
59
  }
@@ -1,24 +1,35 @@
1
1
  /** Ensure a role name exists in the lt_roles lookup table (FK target). */
2
2
  export declare const ENSURE_ROLE_EXISTS = "INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING";
3
3
  /** Fetch roles for a single user, ordered by creation time. */
4
- export declare const GET_ROLES_BY_USER_ID = "SELECT role, type, created_at FROM lt_user_roles WHERE user_id = $1 ORDER BY created_at";
4
+ export declare const GET_ROLES_BY_USER_ID = "SELECT role, type, read_scope, write_scope, created_at FROM lt_user_roles WHERE user_id = $1 ORDER BY created_at";
5
5
  /** Fetch a user row by external_id. */
6
6
  export declare const GET_USER_BY_EXTERNAL_ID = "SELECT * FROM lt_users WHERE external_id = $1";
7
- export declare const INSERT_USER = "INSERT INTO lt_users (external_id, email, display_name, status, metadata, password_hash, oauth_provider, oauth_provider_id)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n RETURNING *";
7
+ /**
8
+ * Create a user and its role assignments in ONE atomic statement. The user row,
9
+ * the lt_roles FK targets ($9 = role names), and the lt_user_roles links
10
+ * ($9 names Γ— $10 types Γ— $11 read_scope Γ— $12 write_scope) all commit together β€”
11
+ * a failure can never leave a half-provisioned user with a partial role set, and
12
+ * there is no per-role N+1. All four are passed as parallel text[] arrays (empty
13
+ * arrays → no roles). Postgres checks the lt_user_roles→lt_roles FK at statement
14
+ * end, after the sibling role-ensure CTE has run, so brand-new roles are valid
15
+ * FK targets.
16
+ */
17
+ export declare const CREATE_USER_WITH_ROLES = "WITH new_user AS (\n INSERT INTO lt_users (external_id, email, display_name, status, metadata, password_hash, oauth_provider, oauth_provider_id)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n RETURNING *\n ), ensured_roles AS (\n INSERT INTO lt_roles (role)\n SELECT DISTINCT unnest($9::text[])\n ON CONFLICT DO NOTHING\n ), assigned_roles AS (\n INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope)\n SELECT (SELECT id FROM new_user), x.role, x.type, x.read_scope, x.write_scope\n FROM unnest($9::text[], $10::text[], $11::text[], $12::text[]) AS x(role, type, read_scope, write_scope)\n ON CONFLICT DO NOTHING\n )\n SELECT * FROM new_user";
8
18
  export declare const GET_USER_BY_EMAIL = "SELECT * FROM lt_users WHERE email = $1 LIMIT 1";
9
- export declare const INSERT_USER_ROLE_IGNORE = "INSERT INTO lt_user_roles (user_id, role, type) VALUES ($1, $2, $3)\n ON CONFLICT DO NOTHING";
10
19
  export declare const GET_USER_BY_ID = "SELECT * FROM lt_users WHERE id = $1";
11
20
  /** Check if a user exists by id. Lightweight β€” returns only the id column. */
12
21
  export declare const VERIFY_USER_BY_ID = "SELECT id FROM lt_users WHERE id = $1 LIMIT 1";
13
22
  /** Fetch user + roles in a single query. Returns one row per role (or one row with nulls if no roles). */
14
- export declare const GET_USER_WITH_ROLES = "SELECT u.id, u.external_id, u.display_name, u.status, u.metadata,\n r.role, r.type AS role_type\n FROM lt_users u\n LEFT JOIN lt_user_roles r ON r.user_id = u.id\n WHERE u.external_id = $1\n ORDER BY r.created_at";
23
+ export declare const GET_USER_WITH_ROLES = "SELECT u.id, u.external_id, u.display_name, u.status, u.metadata,\n r.role, r.type AS role_type, r.read_scope, r.write_scope\n FROM lt_users u\n LEFT JOIN lt_user_roles r ON r.user_id = u.id\n WHERE u.external_id = $1\n ORDER BY r.created_at";
15
24
  export declare const DELETE_USER_BY_ID = "DELETE FROM lt_users WHERE id = $1";
16
25
  /** Batch-load roles for many users (avoids N+1). */
17
- export declare const GET_ROLES_BY_USER_IDS = "SELECT user_id, role, type, created_at FROM lt_user_roles WHERE user_id = ANY($1) ORDER BY created_at";
18
- /** Upsert a user–role assignment, promoting the type if the row exists. */
19
- export declare const UPSERT_USER_ROLE = "INSERT INTO lt_user_roles (user_id, role, type) VALUES ($1, $2, $3)\n ON CONFLICT (user_id, role) DO UPDATE SET type = EXCLUDED.type\n RETURNING role, type, created_at";
26
+ export declare const GET_ROLES_BY_USER_IDS = "SELECT user_id, role, type, read_scope, write_scope, created_at FROM lt_user_roles WHERE user_id = ANY($1) ORDER BY created_at";
27
+ /** Upsert a user–role assignment, promoting the type/scope if the row exists. */
28
+ export declare const UPSERT_USER_ROLE = "INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (user_id, role) DO UPDATE SET\n type = EXCLUDED.type,\n read_scope = EXCLUDED.read_scope,\n write_scope = EXCLUDED.write_scope\n RETURNING role, type, read_scope, write_scope, created_at";
20
29
  export declare const DELETE_USER_ROLE = "DELETE FROM lt_user_roles WHERE user_id = $1 AND role = $2";
21
30
  export declare const HAS_ROLE = "SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND role = $2 LIMIT 1";
22
31
  export declare const HAS_ROLE_TYPE = "SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND type = $2 LIMIT 1";
32
+ /** Fetch the management tier + scope for one (user, role) β€” used for write enforcement. */
33
+ export declare const GET_ROLE_SCOPE = "SELECT type, read_scope, write_scope FROM lt_user_roles WHERE user_id = $1 AND role = $2 LIMIT 1";
23
34
  /** Check if a user is an admin (or superadmin) of a specific role group. */
24
35
  export declare const IS_GROUP_ADMIN = "SELECT 1 FROM lt_user_roles\n WHERE user_id = $1\n AND (\n (role = $2 AND type IN ('admin', 'superadmin'))\n OR type = 'superadmin'\n )\n LIMIT 1";
@@ -1,26 +1,46 @@
1
1
  "use strict";
2
2
  // ─── Shared queries (used by multiple modules) ──────────────────────────────
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.IS_GROUP_ADMIN = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.VERIFY_USER_BY_ID = exports.GET_USER_BY_ID = exports.INSERT_USER_ROLE_IGNORE = exports.GET_USER_BY_EMAIL = exports.INSERT_USER = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = void 0;
4
+ exports.IS_GROUP_ADMIN = exports.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.VERIFY_USER_BY_ID = exports.GET_USER_BY_ID = exports.GET_USER_BY_EMAIL = exports.CREATE_USER_WITH_ROLES = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = void 0;
5
5
  /** Ensure a role name exists in the lt_roles lookup table (FK target). */
6
6
  exports.ENSURE_ROLE_EXISTS = 'INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING';
7
7
  /** Fetch roles for a single user, ordered by creation time. */
8
- exports.GET_ROLES_BY_USER_ID = 'SELECT role, type, created_at FROM lt_user_roles WHERE user_id = $1 ORDER BY created_at';
8
+ exports.GET_ROLES_BY_USER_ID = 'SELECT role, type, read_scope, write_scope, created_at FROM lt_user_roles WHERE user_id = $1 ORDER BY created_at';
9
9
  /** Fetch a user row by external_id. */
10
10
  exports.GET_USER_BY_EXTERNAL_ID = 'SELECT * FROM lt_users WHERE external_id = $1';
11
11
  // ─── User CRUD ───────────────────────────────────────────────────────────────
12
- exports.INSERT_USER = `INSERT INTO lt_users (external_id, email, display_name, status, metadata, password_hash, oauth_provider, oauth_provider_id)
13
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
14
- RETURNING *`;
12
+ /**
13
+ * Create a user and its role assignments in ONE atomic statement. The user row,
14
+ * the lt_roles FK targets ($9 = role names), and the lt_user_roles links
15
+ * ($9 names Γ— $10 types Γ— $11 read_scope Γ— $12 write_scope) all commit together β€”
16
+ * a failure can never leave a half-provisioned user with a partial role set, and
17
+ * there is no per-role N+1. All four are passed as parallel text[] arrays (empty
18
+ * arrays → no roles). Postgres checks the lt_user_roles→lt_roles FK at statement
19
+ * end, after the sibling role-ensure CTE has run, so brand-new roles are valid
20
+ * FK targets.
21
+ */
22
+ exports.CREATE_USER_WITH_ROLES = `WITH new_user AS (
23
+ INSERT INTO lt_users (external_id, email, display_name, status, metadata, password_hash, oauth_provider, oauth_provider_id)
24
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
25
+ RETURNING *
26
+ ), ensured_roles AS (
27
+ INSERT INTO lt_roles (role)
28
+ SELECT DISTINCT unnest($9::text[])
29
+ ON CONFLICT DO NOTHING
30
+ ), assigned_roles AS (
31
+ INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope)
32
+ SELECT (SELECT id FROM new_user), x.role, x.type, x.read_scope, x.write_scope
33
+ FROM unnest($9::text[], $10::text[], $11::text[], $12::text[]) AS x(role, type, read_scope, write_scope)
34
+ ON CONFLICT DO NOTHING
35
+ )
36
+ SELECT * FROM new_user`;
15
37
  exports.GET_USER_BY_EMAIL = `SELECT * FROM lt_users WHERE email = $1 LIMIT 1`;
16
- exports.INSERT_USER_ROLE_IGNORE = `INSERT INTO lt_user_roles (user_id, role, type) VALUES ($1, $2, $3)
17
- ON CONFLICT DO NOTHING`;
18
38
  exports.GET_USER_BY_ID = 'SELECT * FROM lt_users WHERE id = $1';
19
39
  /** Check if a user exists by id. Lightweight β€” returns only the id column. */
20
40
  exports.VERIFY_USER_BY_ID = 'SELECT id FROM lt_users WHERE id = $1 LIMIT 1';
21
41
  /** Fetch user + roles in a single query. Returns one row per role (or one row with nulls if no roles). */
22
42
  exports.GET_USER_WITH_ROLES = `SELECT u.id, u.external_id, u.display_name, u.status, u.metadata,
23
- r.role, r.type AS role_type
43
+ r.role, r.type AS role_type, r.read_scope, r.write_scope
24
44
  FROM lt_users u
25
45
  LEFT JOIN lt_user_roles r ON r.user_id = u.id
26
46
  WHERE u.external_id = $1
@@ -28,14 +48,19 @@ exports.GET_USER_WITH_ROLES = `SELECT u.id, u.external_id, u.display_name, u.sta
28
48
  exports.DELETE_USER_BY_ID = 'DELETE FROM lt_users WHERE id = $1';
29
49
  // ─── Role management ─────────────────────────────────────────────────────────
30
50
  /** Batch-load roles for many users (avoids N+1). */
31
- exports.GET_ROLES_BY_USER_IDS = 'SELECT user_id, role, type, created_at FROM lt_user_roles WHERE user_id = ANY($1) ORDER BY created_at';
32
- /** Upsert a user–role assignment, promoting the type if the row exists. */
33
- exports.UPSERT_USER_ROLE = `INSERT INTO lt_user_roles (user_id, role, type) VALUES ($1, $2, $3)
34
- ON CONFLICT (user_id, role) DO UPDATE SET type = EXCLUDED.type
35
- RETURNING role, type, created_at`;
51
+ exports.GET_ROLES_BY_USER_IDS = 'SELECT user_id, role, type, read_scope, write_scope, created_at FROM lt_user_roles WHERE user_id = ANY($1) ORDER BY created_at';
52
+ /** Upsert a user–role assignment, promoting the type/scope if the row exists. */
53
+ exports.UPSERT_USER_ROLE = `INSERT INTO lt_user_roles (user_id, role, type, read_scope, write_scope) VALUES ($1, $2, $3, $4, $5)
54
+ ON CONFLICT (user_id, role) DO UPDATE SET
55
+ type = EXCLUDED.type,
56
+ read_scope = EXCLUDED.read_scope,
57
+ write_scope = EXCLUDED.write_scope
58
+ RETURNING role, type, read_scope, write_scope, created_at`;
36
59
  exports.DELETE_USER_ROLE = 'DELETE FROM lt_user_roles WHERE user_id = $1 AND role = $2';
37
60
  exports.HAS_ROLE = 'SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND role = $2 LIMIT 1';
38
61
  exports.HAS_ROLE_TYPE = 'SELECT 1 FROM lt_user_roles WHERE user_id = $1 AND type = $2 LIMIT 1';
62
+ /** Fetch the management tier + scope for one (user, role) β€” used for write enforcement. */
63
+ exports.GET_ROLE_SCOPE = 'SELECT type, read_scope, write_scope FROM lt_user_roles WHERE user_id = $1 AND role = $2 LIMIT 1';
39
64
  // ─── RBAC ────────────────────────────────────────────────────────────────────
40
65
  /** Check if a user is an admin (or superadmin) of a specific role group. */
41
66
  exports.IS_GROUP_ADMIN = `SELECT 1 FROM lt_user_roles
@@ -18,34 +18,51 @@ async function ssoProvision(identity, ssoConfig) {
18
18
  const ltRoles = resolveRoles(identity.roles || [], defaultRoleType, ssoConfig.roleMap);
19
19
  const existing = await (0, crud_1.getUserByExternalId)(identity.externalId);
20
20
  if (existing) {
21
- // Sync roles: ensure all resolved roles exist on the user
22
- const currentRoles = await (0, roles_1.getUserRoles)(existing.id);
23
- for (const lr of ltRoles) {
24
- const has = currentRoles.some((r) => r.role === lr.role);
25
- if (!has) {
26
- await (0, roles_1.addUserRole)(existing.id, lr.role, lr.type);
27
- }
28
- }
29
- const updatedRoles = await (0, roles_1.getUserRoles)(existing.id);
21
+ return syncExistingUser(existing.id, ltRoles);
22
+ }
23
+ // Create new user. Two concurrent first-logins for the same identity can both
24
+ // pass the existence check above; external_id UNIQUE is the arbiter. The loser's
25
+ // insert raises 23505 β€” adopt the winner's committed row and sync roles rather
26
+ // than failing the login.
27
+ try {
28
+ const user = await (0, crud_1.createUser)({
29
+ external_id: identity.externalId,
30
+ email: identity.email || undefined,
31
+ display_name: identity.displayName || identity.externalId,
32
+ roles: ltRoles.map((r) => ({ role: r.role, type: r.type })),
33
+ metadata: identity.metadata,
34
+ });
35
+ logger_1.loggerRegistry.info(`[lt-sso] provisioned user: ${identity.externalId} β†’ ${user.id}`);
30
36
  return {
31
- userId: existing.id,
32
- roles: updatedRoles.map((r) => ({ role: r.role, type: r.type })),
33
- created: false,
37
+ userId: user.id,
38
+ roles: (user.roles || []).map((r) => ({ role: r.role, type: r.type })),
39
+ created: true,
34
40
  };
35
41
  }
36
- // Create new user
37
- const user = await (0, crud_1.createUser)({
38
- external_id: identity.externalId,
39
- email: identity.email || undefined,
40
- display_name: identity.displayName || identity.externalId,
41
- roles: ltRoles.map((r) => ({ role: r.role, type: r.type })),
42
- metadata: identity.metadata,
43
- });
44
- logger_1.loggerRegistry.info(`[lt-sso] provisioned user: ${identity.externalId} β†’ ${user.id}`);
42
+ catch (err) {
43
+ if (err?.code === '23505') {
44
+ const winner = await (0, crud_1.getUserByExternalId)(identity.externalId);
45
+ if (winner) {
46
+ return syncExistingUser(winner.id, ltRoles);
47
+ }
48
+ }
49
+ throw err;
50
+ }
51
+ }
52
+ /** Ensure all resolved roles exist on an already-provisioned user. */
53
+ async function syncExistingUser(userId, ltRoles) {
54
+ const currentRoles = await (0, roles_1.getUserRoles)(userId);
55
+ for (const lr of ltRoles) {
56
+ const has = currentRoles.some((r) => r.role === lr.role);
57
+ if (!has) {
58
+ await (0, roles_1.addUserRole)(userId, lr.role, lr.type);
59
+ }
60
+ }
61
+ const updatedRoles = await (0, roles_1.getUserRoles)(userId);
45
62
  return {
46
- userId: user.id,
47
- roles: (user.roles || []).map((r) => ({ role: r.role, type: r.type })),
48
- created: true,
63
+ userId,
64
+ roles: updatedRoles.map((r) => ({ role: r.role, type: r.type })),
65
+ created: false,
49
66
  };
50
67
  }
51
68
  function resolveRoles(hostRoles, defaultRoleType, roleMap) {
@@ -1,5 +1,12 @@
1
- import type { LTRoleType, LTUserStatus } from '../../types';
1
+ import type { LTReadScope, LTRoleType, LTUserStatus, LTWriteScope } from '../../types';
2
2
  export declare const VALID_ROLE_TYPES: LTRoleType[];
3
+ /** A role grant on user create: management tier + optional work-surface scope. */
4
+ export interface RoleGrantInput {
5
+ role: string;
6
+ type: LTRoleType;
7
+ read_scope?: LTReadScope;
8
+ write_scope?: LTWriteScope;
9
+ }
3
10
  export interface CreateUserInput {
4
11
  external_id: string;
5
12
  email?: string;
@@ -7,10 +14,7 @@ export interface CreateUserInput {
7
14
  password?: string;
8
15
  status?: LTUserStatus;
9
16
  metadata?: Record<string, any>;
10
- roles?: {
11
- role: string;
12
- type: LTRoleType;
13
- }[];
17
+ roles?: RoleGrantInput[];
14
18
  /** OAuth identity link (set during OAuth auto-provisioning). */
15
19
  oauth_provider?: string;
16
20
  oauth_provider_id?: string;
@@ -15,12 +15,15 @@ const adapter_1 = require("../services/mcp/adapter");
15
15
  const escalation_strategy_1 = require("../services/escalation-strategy");
16
16
  const default_1 = require("../services/escalation-strategy/default");
17
17
  const mcp_2 = require("../services/escalation-strategy/mcp");
18
+ const features_1 = require("../modules/features");
18
19
  const socket_auth_1 = require("./socket-auth");
19
20
  /**
20
21
  * Register all adapters (logging, telemetry, events, maintenance,
21
22
  * escalation strategy, MCP) based on the startup config.
22
23
  */
23
24
  function registerAdapters(startConfig) {
25
+ // Dashboard feature flags (default permissive; deployment opts out).
26
+ (0, features_1.configureFeatureFlags)(startConfig.features);
24
27
  // Logging (register first so subsequent log calls use it)
25
28
  if (startConfig.logging?.adapter) {
26
29
  logger_1.loggerRegistry.register(startConfig.logging.adapter);