@hotmeshio/long-tail 0.16.4 → 0.17.1

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 (251) hide show
  1. package/build/api/users.d.ts +31 -1
  2. package/build/api/users.js +54 -1
  3. package/build/bin/ltc.js +7 -0
  4. package/build/lib/cli/commands/users.d.ts +11 -0
  5. package/build/lib/cli/commands/users.js +59 -0
  6. package/build/routes/users.js +26 -0
  7. package/build/sdk/index.d.ts +2 -0
  8. package/build/sdk/index.js +2 -0
  9. package/build/services/user/crud.d.ts +32 -1
  10. package/build/services/user/crud.js +108 -0
  11. package/build/services/user/index.d.ts +2 -2
  12. package/build/services/user/index.js +5 -1
  13. package/build/services/user/sql.d.ts +16 -0
  14. package/build/services/user/sql.js +33 -1
  15. package/build/services/user/types.d.ts +11 -0
  16. package/build/system/mcp-servers/admin/schemas.d.ts +30 -14
  17. package/build/system/mcp-servers/admin/schemas.js +9 -3
  18. package/build/system/mcp-servers/admin/users.js +26 -0
  19. package/build/system/seed/tool-manifests-admin.d.ts +445 -0
  20. package/build/system/seed/tool-manifests-admin.js +1 -0
  21. package/dashboard/dist/assets/AdminDashboard-D-YHIR8w.js +2 -0
  22. package/dashboard/dist/assets/{AdminDashboard-Csq-oxmY.js.map → AdminDashboard-D-YHIR8w.js.map} +1 -1
  23. package/dashboard/dist/assets/{AgentConfigPage-Bnr5zbyJ.js → AgentConfigPage-CG54Ll2A.js} +7 -7
  24. package/dashboard/dist/assets/{AgentConfigPage-Bnr5zbyJ.js.map → AgentConfigPage-CG54Ll2A.js.map} +1 -1
  25. package/dashboard/dist/assets/{AgentDetailPage-DgRu-Fhg.js → AgentDetailPage-rTcofeW4.js} +3 -3
  26. package/dashboard/dist/assets/{AgentDetailPage-DgRu-Fhg.js.map → AgentDetailPage-rTcofeW4.js.map} +1 -1
  27. package/dashboard/dist/assets/AgentsPage-B7PNjrkn.js +2 -0
  28. package/dashboard/dist/assets/{AgentsPage-BuyXKkP3.js.map → AgentsPage-B7PNjrkn.js.map} +1 -1
  29. package/dashboard/dist/assets/AvailableEscalationsPage-CEDwyzlx.js +2 -0
  30. package/dashboard/dist/assets/AvailableEscalationsPage-CEDwyzlx.js.map +1 -0
  31. package/dashboard/dist/assets/{BotPicker-xpPjRnkf.js → BotPicker-cg1qXRF3.js} +2 -2
  32. package/dashboard/dist/assets/{BotPicker-xpPjRnkf.js.map → BotPicker-cg1qXRF3.js.map} +1 -1
  33. package/dashboard/dist/assets/{BulkAssignModal-CQ3k-7rO.js → BulkAssignModal-BDzaHGAb.js} +2 -2
  34. package/dashboard/dist/assets/{BulkAssignModal-CQ3k-7rO.js.map → BulkAssignModal-BDzaHGAb.js.map} +1 -1
  35. package/dashboard/dist/assets/CapabilitiesPage-CswExj9-.js +2 -0
  36. package/dashboard/dist/assets/{CapabilitiesPage-DCkEJ0In.js.map → CapabilitiesPage-CswExj9-.js.map} +1 -1
  37. package/dashboard/dist/assets/{CollapsibleSection-10sJSNNF.js → CollapsibleSection-CpVKfkkq.js} +2 -2
  38. package/dashboard/dist/assets/{CollapsibleSection-10sJSNNF.js.map → CollapsibleSection-CpVKfkkq.js.map} +1 -1
  39. package/dashboard/dist/assets/CountdownTimer-ClJhYGg1.js +2 -0
  40. package/dashboard/dist/assets/{CountdownTimer-BFy9Joxb.js.map → CountdownTimer-ClJhYGg1.js.map} +1 -1
  41. package/dashboard/dist/assets/{CredentialsPage-DIR-fUoF.js → CredentialsPage-DYBp7v8W.js} +2 -2
  42. package/dashboard/dist/assets/{CredentialsPage-DIR-fUoF.js.map → CredentialsPage-DYBp7v8W.js.map} +1 -1
  43. package/dashboard/dist/assets/{CronLabel-C66ASvgE.js → CronLabel-Bd_fipHy.js} +2 -2
  44. package/dashboard/dist/assets/{CronLabel-C66ASvgE.js.map → CronLabel-Bd_fipHy.js.map} +1 -1
  45. package/dashboard/dist/assets/ElapsedCell-B-Wn6Y8B.js +2 -0
  46. package/dashboard/dist/assets/{ElapsedCell-RWvrCHn7.js.map → ElapsedCell-B-Wn6Y8B.js.map} +1 -1
  47. package/dashboard/dist/assets/EntityTimelinePanel-DcgFUbk4.js +2 -0
  48. package/dashboard/dist/assets/{EntityTimelinePanel-CNaYs3nA.js.map → EntityTimelinePanel-DcgFUbk4.js.map} +1 -1
  49. package/dashboard/dist/assets/{EscalationListSchemaPage-BsRQtDDc.js → EscalationListSchemaPage-DpnorXeU.js} +3 -3
  50. package/dashboard/dist/assets/{EscalationListSchemaPage-BsRQtDDc.js.map → EscalationListSchemaPage-DpnorXeU.js.map} +1 -1
  51. package/dashboard/dist/assets/{EscalationSchemaPage-BMYQmwGA.js → EscalationSchemaPage-LPTJJDfP.js} +3 -3
  52. package/dashboard/dist/assets/{EscalationSchemaPage-BMYQmwGA.js.map → EscalationSchemaPage-LPTJJDfP.js.map} +1 -1
  53. package/dashboard/dist/assets/EscalationsOverview-Bq5_ICUt.js +2 -0
  54. package/dashboard/dist/assets/{EscalationsOverview-yv4Yn6zl.js.map → EscalationsOverview-Bq5_ICUt.js.map} +1 -1
  55. package/dashboard/dist/assets/{EventTable-ebZK7vXl.js → EventTable-Cinbdm1u.js} +2 -2
  56. package/dashboard/dist/assets/{EventTable-ebZK7vXl.js.map → EventTable-Cinbdm1u.js.map} +1 -1
  57. package/dashboard/dist/assets/FilterBar-DyojX5m4.js +2 -0
  58. package/dashboard/dist/assets/FilterBar-DyojX5m4.js.map +1 -0
  59. package/dashboard/dist/assets/{GraphInvokePage-BFntNMop.js → GraphInvokePage-Ba5fNcgv.js} +2 -2
  60. package/dashboard/dist/assets/{GraphInvokePage-BFntNMop.js.map → GraphInvokePage-Ba5fNcgv.js.map} +1 -1
  61. package/dashboard/dist/assets/HomePage-DuFXqfz3.js +2 -0
  62. package/dashboard/dist/assets/HomePage-DuFXqfz3.js.map +1 -0
  63. package/dashboard/dist/assets/ListToolbar-GpwHcjkZ.js +2 -0
  64. package/dashboard/dist/assets/{ListToolbar-DlGreUpP.js.map → ListToolbar-GpwHcjkZ.js.map} +1 -1
  65. package/dashboard/dist/assets/{McpOverview-D0G2vACW.js → McpOverview-Bi23-hih.js} +2 -2
  66. package/dashboard/dist/assets/{McpOverview-D0G2vACW.js.map → McpOverview-Bi23-hih.js.map} +1 -1
  67. package/dashboard/dist/assets/{McpQueryDetailPage-LZO04PEB.js → McpQueryDetailPage-FhHSfX9W.js} +4 -4
  68. package/dashboard/dist/assets/{McpQueryDetailPage-LZO04PEB.js.map → McpQueryDetailPage-FhHSfX9W.js.map} +1 -1
  69. package/dashboard/dist/assets/McpQueryPage-czMlM7vR.js +2 -0
  70. package/dashboard/dist/assets/{McpQueryPage-BIwSDHgB.js.map → McpQueryPage-czMlM7vR.js.map} +1 -1
  71. package/dashboard/dist/assets/McpRunDetailPage-BOh2M0Ca.js +2 -0
  72. package/dashboard/dist/assets/McpRunDetailPage-BOh2M0Ca.js.map +1 -0
  73. package/dashboard/dist/assets/McpRunsPage-DfJzbGYM.js +2 -0
  74. package/dashboard/dist/assets/McpRunsPage-DfJzbGYM.js.map +1 -0
  75. package/dashboard/dist/assets/OperationsPage-BS8blKLj.js +3 -0
  76. package/dashboard/dist/assets/OperationsPage-BS8blKLj.js.map +1 -0
  77. package/dashboard/dist/assets/OperatorDashboard-BWtTKrcJ.js +2 -0
  78. package/dashboard/dist/assets/OperatorDashboard-BWtTKrcJ.js.map +1 -0
  79. package/dashboard/dist/assets/{PersonaDetailPage-B48MX4lq.js → PersonaDetailPage-VkhuauBf.js} +2 -2
  80. package/dashboard/dist/assets/{PersonaDetailPage-B48MX4lq.js.map → PersonaDetailPage-VkhuauBf.js.map} +1 -1
  81. package/dashboard/dist/assets/PersonasPage-DRoneLfx.js +2 -0
  82. package/dashboard/dist/assets/PersonasPage-DRoneLfx.js.map +1 -0
  83. package/dashboard/dist/assets/ProcessDetailPage-B5mmkHS0.js +2 -0
  84. package/dashboard/dist/assets/{ProcessDetailPage-Co5oCg31.js.map → ProcessDetailPage-B5mmkHS0.js.map} +1 -1
  85. package/dashboard/dist/assets/ProcessesListPage-BV0e2OfH.js +2 -0
  86. package/dashboard/dist/assets/ProcessesListPage-BV0e2OfH.js.map +1 -0
  87. package/dashboard/dist/assets/{RoleDetailPage-C4UTNx0p.js → RoleDetailPage-eenl_a5Z.js} +2 -2
  88. package/dashboard/dist/assets/{RoleDetailPage-C4UTNx0p.js.map → RoleDetailPage-eenl_a5Z.js.map} +1 -1
  89. package/dashboard/dist/assets/{RolesPage-BXo5RirX.js → RolesPage-BLfJcFf7.js} +2 -2
  90. package/dashboard/dist/assets/{RolesPage-BXo5RirX.js.map → RolesPage-BLfJcFf7.js.map} +1 -1
  91. package/dashboard/dist/assets/{RunAsSelector-BRQiGv2-.js → RunAsSelector-BnLKKs51.js} +2 -2
  92. package/dashboard/dist/assets/{RunAsSelector-BRQiGv2-.js.map → RunAsSelector-BnLKKs51.js.map} +1 -1
  93. package/dashboard/dist/assets/{ScanCodesPage-BUT5wjzC.js → ScanCodesPage-BWIcjnr4.js} +2 -2
  94. package/dashboard/dist/assets/{ScanCodesPage-BUT5wjzC.js.map → ScanCodesPage-BWIcjnr4.js.map} +1 -1
  95. package/dashboard/dist/assets/ScanSchemeDetailPage-sAuU8cZj.js +2 -0
  96. package/dashboard/dist/assets/{ScanSchemeDetailPage-CkzoOyjd.js.map → ScanSchemeDetailPage-sAuU8cZj.js.map} +1 -1
  97. package/dashboard/dist/assets/ScanStationPage-CA_q1uaI.js +2 -0
  98. package/dashboard/dist/assets/{ScanStationPage-DYamACNZ.js.map → ScanStationPage-CA_q1uaI.js.map} +1 -1
  99. package/dashboard/dist/assets/{StickyPagination-B3yiAcrs.js → StickyPagination-sVI21e82.js} +2 -2
  100. package/dashboard/dist/assets/{StickyPagination-B3yiAcrs.js.map → StickyPagination-sVI21e82.js.map} +1 -1
  101. package/dashboard/dist/assets/{StreamMessageDetail-C56PtZSb.js → StreamMessageDetail-Djm_zQAv.js} +2 -2
  102. package/dashboard/dist/assets/{StreamMessageDetail-C56PtZSb.js.map → StreamMessageDetail-Djm_zQAv.js.map} +1 -1
  103. package/dashboard/dist/assets/{SwimlaneTimeline-D64ep5KY.js → SwimlaneTimeline-CwxXRmRQ.js} +2 -2
  104. package/dashboard/dist/assets/{SwimlaneTimeline-D64ep5KY.js.map → SwimlaneTimeline-CwxXRmRQ.js.map} +1 -1
  105. package/dashboard/dist/assets/{TaskDetailPage-C2V7BcgB.js → TaskDetailPage-SiBSTsfp.js} +2 -2
  106. package/dashboard/dist/assets/{TaskDetailPage-C2V7BcgB.js.map → TaskDetailPage-SiBSTsfp.js.map} +1 -1
  107. package/dashboard/dist/assets/{TasksListPage-C4ctoAa0.js → TasksListPage-CBTFz3Bp.js} +2 -2
  108. package/dashboard/dist/assets/{TasksListPage-C4ctoAa0.js.map → TasksListPage-CBTFz3Bp.js.map} +1 -1
  109. package/dashboard/dist/assets/TimeAgo-Ctwwdamz.js +2 -0
  110. package/dashboard/dist/assets/{TimeAgo-BpJ7In27.js.map → TimeAgo-Ctwwdamz.js.map} +1 -1
  111. package/dashboard/dist/assets/{TimestampCell-BKrIaU_R.js → TimestampCell-CI8UrDuQ.js} +2 -2
  112. package/dashboard/dist/assets/{TimestampCell-BKrIaU_R.js.map → TimestampCell-CI8UrDuQ.js.map} +1 -1
  113. package/dashboard/dist/assets/{ToolTestPanel-CdTXWPqn.js → ToolTestPanel-Cf9ZEBF7.js} +2 -2
  114. package/dashboard/dist/assets/{ToolTestPanel-CdTXWPqn.js.map → ToolTestPanel-Cf9ZEBF7.js.map} +1 -1
  115. package/dashboard/dist/assets/{TopicDetailPage-B6tb1RG4.js → TopicDetailPage-BYIOSVNy.js} +3 -3
  116. package/dashboard/dist/assets/{TopicDetailPage-B6tb1RG4.js.map → TopicDetailPage-BYIOSVNy.js.map} +1 -1
  117. package/dashboard/dist/assets/{TopicsPage-DgdOKjwE.js → TopicsPage-C9x4tIEi.js} +2 -2
  118. package/dashboard/dist/assets/{TopicsPage-DgdOKjwE.js.map → TopicsPage-C9x4tIEi.js.map} +1 -1
  119. package/dashboard/dist/assets/{UserName-A-ET2GnX.js → UserName-Ck-eLb-1.js} +2 -2
  120. package/dashboard/dist/assets/{UserName-A-ET2GnX.js.map → UserName-Ck-eLb-1.js.map} +1 -1
  121. package/dashboard/dist/assets/WorkflowExecutionPage-BPxoPK8g.js +2 -0
  122. package/dashboard/dist/assets/{WorkflowExecutionPage-CzhoRsis.js.map → WorkflowExecutionPage-BPxoPK8g.js.map} +1 -1
  123. package/dashboard/dist/assets/WorkflowsDashboard-CDHQUYEr.js +2 -0
  124. package/dashboard/dist/assets/WorkflowsDashboard-CDHQUYEr.js.map +1 -0
  125. package/dashboard/dist/assets/WorkflowsOverview-DGp8J0G2.js +2 -0
  126. package/dashboard/dist/assets/{WorkflowsOverview-B4Bh8MIo.js.map → WorkflowsOverview-DGp8J0G2.js.map} +1 -1
  127. package/dashboard/dist/assets/{YamlWorkflowDetailPage-zRrcxk0e.js → YamlWorkflowDetailPage-B112bGAY.js} +2 -2
  128. package/dashboard/dist/assets/{YamlWorkflowDetailPage-zRrcxk0e.js.map → YamlWorkflowDetailPage-B112bGAY.js.map} +1 -1
  129. package/dashboard/dist/assets/{YamlWorkflowsPage-DMK3ojp3.js → YamlWorkflowsPage-Cw5wddH7.js} +2 -2
  130. package/dashboard/dist/assets/{YamlWorkflowsPage-DMK3ojp3.js.map → YamlWorkflowsPage-Cw5wddH7.js.map} +1 -1
  131. package/dashboard/dist/assets/{agents-B67yyE1z.js → agents-CZ8WIdWH.js} +2 -2
  132. package/dashboard/dist/assets/{agents-B67yyE1z.js.map → agents-CZ8WIdWH.js.map} +1 -1
  133. package/dashboard/dist/assets/{bots-pSLFdOm-.js → bots-Xu3MUuXN.js} +2 -2
  134. package/dashboard/dist/assets/{bots-pSLFdOm-.js.map → bots-Xu3MUuXN.js.map} +1 -1
  135. package/dashboard/dist/assets/capabilities-XJukFiLn.js +2 -0
  136. package/dashboard/dist/assets/{capabilities-B2Dem6zw.js.map → capabilities-XJukFiLn.js.map} +1 -1
  137. package/dashboard/dist/assets/{controlplane-DGdaM9f3.js → controlplane-D8ZQQUAV.js} +2 -2
  138. package/dashboard/dist/assets/{controlplane-DGdaM9f3.js.map → controlplane-D8ZQQUAV.js.map} +1 -1
  139. package/dashboard/dist/assets/escalation-columns-C1GyAMeK.js +2 -0
  140. package/dashboard/dist/assets/{escalation-columns-BM7vp-hY.js.map → escalation-columns-C1GyAMeK.js.map} +1 -1
  141. package/dashboard/dist/assets/index-BEeGFfuP.js +67 -0
  142. package/dashboard/dist/assets/index-BEeGFfuP.js.map +1 -0
  143. package/dashboard/dist/assets/{index-DbLOvfv5.js → index-CHAmwNDI.js} +2 -2
  144. package/dashboard/dist/assets/index-CHAmwNDI.js.map +1 -0
  145. package/dashboard/dist/assets/index-COtlcGK_.js +2 -0
  146. package/dashboard/dist/assets/index-COtlcGK_.js.map +1 -0
  147. package/dashboard/dist/assets/index-CR96SICL.js +2 -0
  148. package/dashboard/dist/assets/index-CR96SICL.js.map +1 -0
  149. package/dashboard/dist/assets/{index-CGoXyPlO.js → index-CRxbF5vt.js} +2 -2
  150. package/dashboard/dist/assets/{index-CGoXyPlO.js.map → index-CRxbF5vt.js.map} +1 -1
  151. package/dashboard/dist/assets/{index-CLq228E2.js → index-CgGR0kM6.js} +3 -3
  152. package/dashboard/dist/assets/{index-CLq228E2.js.map → index-CgGR0kM6.js.map} +1 -1
  153. package/dashboard/dist/assets/{index-Cn__VdSf.js → index-DCaGxwKU.js} +2 -2
  154. package/dashboard/dist/assets/{index-Cn__VdSf.js.map → index-DCaGxwKU.js.map} +1 -1
  155. package/dashboard/dist/assets/{index-DajlugVw.js → index-DFmV19fc.js} +3 -3
  156. package/dashboard/dist/assets/{index-DajlugVw.js.map → index-DFmV19fc.js.map} +1 -1
  157. package/dashboard/dist/assets/{index-Brf_fpE4.js → index-DWhyH1kV.js} +2 -2
  158. package/dashboard/dist/assets/{index-Brf_fpE4.js.map → index-DWhyH1kV.js.map} +1 -1
  159. package/dashboard/dist/assets/{index-CunrdEfT.js → index-DYIy3uT5.js} +2 -2
  160. package/dashboard/dist/assets/{index-CunrdEfT.js.map → index-DYIy3uT5.js.map} +1 -1
  161. package/dashboard/dist/assets/index-D_5_1D7U.js +2 -0
  162. package/dashboard/dist/assets/index-D_5_1D7U.js.map +1 -0
  163. package/dashboard/dist/assets/{index-Cd2RdpOx.js → index-Dc43wixL.js} +2 -2
  164. package/dashboard/dist/assets/{index-Cd2RdpOx.js.map → index-Dc43wixL.js.map} +1 -1
  165. package/dashboard/dist/assets/{index-CKt5YK2z.js → index-DeRM0-Xz.js} +2 -2
  166. package/dashboard/dist/assets/{index-CKt5YK2z.js.map → index-DeRM0-Xz.js.map} +1 -1
  167. package/dashboard/dist/assets/{index-CJV2pWNH.css → index-fUqvRHOe.css} +1 -1
  168. package/dashboard/dist/assets/{knowledge-BExXJ0Ib.js → knowledge-sjJNIdJY.js} +2 -2
  169. package/dashboard/dist/assets/{knowledge-BExXJ0Ib.js.map → knowledge-sjJNIdJY.js.map} +1 -1
  170. package/dashboard/dist/assets/{mcp-D2WA-SHc.js → mcp-CQeu19Jf.js} +2 -2
  171. package/dashboard/dist/assets/{mcp-D2WA-SHc.js.map → mcp-CQeu19Jf.js.map} +1 -1
  172. package/dashboard/dist/assets/{mcp-query-4Tsojk-z.js → mcp-query-B9GuC88j.js} +2 -2
  173. package/dashboard/dist/assets/{mcp-query-4Tsojk-z.js.map → mcp-query-B9GuC88j.js.map} +1 -1
  174. package/dashboard/dist/assets/{personas-Dy_XSYfJ.js → personas-CQr9sXg3.js} +2 -2
  175. package/dashboard/dist/assets/{personas-Dy_XSYfJ.js.map → personas-CQr9sXg3.js.map} +1 -1
  176. package/dashboard/dist/assets/{pipelines-yDHgNtdx.js → pipelines-DzMf-VE7.js} +2 -2
  177. package/dashboard/dist/assets/{pipelines-yDHgNtdx.js.map → pipelines-DzMf-VE7.js.map} +1 -1
  178. package/dashboard/dist/assets/{tasks-CGoPhQyw.js → tasks-QKuXlyLe.js} +2 -2
  179. package/dashboard/dist/assets/{tasks-CGoPhQyw.js.map → tasks-QKuXlyLe.js.map} +1 -1
  180. package/dashboard/dist/assets/{topics-DA1MHDwJ.js → topics-DQAQnGV7.js} +2 -2
  181. package/dashboard/dist/assets/{topics-DA1MHDwJ.js.map → topics-DQAQnGV7.js.map} +1 -1
  182. package/dashboard/dist/assets/useDebouncedValue-DuE9Mj3I.js +2 -0
  183. package/dashboard/dist/assets/useDebouncedValue-DuE9Mj3I.js.map +1 -0
  184. package/dashboard/dist/assets/{useNamespace-BkuU0SeK.js → useNamespace-BTCrNkwo.js} +2 -2
  185. package/dashboard/dist/assets/{useNamespace-BkuU0SeK.js.map → useNamespace-BTCrNkwo.js.map} +1 -1
  186. package/dashboard/dist/assets/{useYamlActivityEvents-XXS3wg6-.js → useYamlActivityEvents-_aa1_rNu.js} +2 -2
  187. package/dashboard/dist/assets/{useYamlActivityEvents-XXS3wg6-.js.map → useYamlActivityEvents-_aa1_rNu.js.map} +1 -1
  188. package/dashboard/dist/assets/users-D-DhFQBy.js +2 -0
  189. package/dashboard/dist/assets/users-D-DhFQBy.js.map +1 -0
  190. package/dashboard/dist/assets/{workflows-5sds1Ko8.js → workflows-CEWX3imM.js} +2 -2
  191. package/dashboard/dist/assets/{workflows-5sds1Ko8.js.map → workflows-CEWX3imM.js.map} +1 -1
  192. package/dashboard/dist/assets/{yaml-workflows-CjNa2u3I.js → yaml-workflows-Bek1Mg86.js} +2 -2
  193. package/dashboard/dist/assets/{yaml-workflows-CjNa2u3I.js.map → yaml-workflows-Bek1Mg86.js.map} +1 -1
  194. package/dashboard/dist/index.html +2 -2
  195. package/docs/api/http/users.md +53 -1
  196. package/docs/api/mcp/admin.md +17 -0
  197. package/docs/api/sdk/users.md +24 -0
  198. package/docs/dashboard.md +19 -1
  199. package/package.json +1 -1
  200. package/dashboard/dist/assets/AdminDashboard-Csq-oxmY.js +0 -2
  201. package/dashboard/dist/assets/AgentsPage-BuyXKkP3.js +0 -2
  202. package/dashboard/dist/assets/AvailableEscalationsPage-BIzTjUMG.js +0 -2
  203. package/dashboard/dist/assets/AvailableEscalationsPage-BIzTjUMG.js.map +0 -1
  204. package/dashboard/dist/assets/CapabilitiesPage-DCkEJ0In.js +0 -2
  205. package/dashboard/dist/assets/CountdownTimer-BFy9Joxb.js +0 -2
  206. package/dashboard/dist/assets/ElapsedCell-RWvrCHn7.js +0 -2
  207. package/dashboard/dist/assets/EntityTimelinePanel-CNaYs3nA.js +0 -2
  208. package/dashboard/dist/assets/EscalationsOverview-yv4Yn6zl.js +0 -2
  209. package/dashboard/dist/assets/FilterBar-D6JDMSHi.js +0 -2
  210. package/dashboard/dist/assets/FilterBar-D6JDMSHi.js.map +0 -1
  211. package/dashboard/dist/assets/HomePage-B24GH9Ga.js +0 -2
  212. package/dashboard/dist/assets/HomePage-B24GH9Ga.js.map +0 -1
  213. package/dashboard/dist/assets/ListToolbar-DlGreUpP.js +0 -2
  214. package/dashboard/dist/assets/McpQueryPage-BIwSDHgB.js +0 -2
  215. package/dashboard/dist/assets/McpRunDetailPage-DjYWJLWh.js +0 -2
  216. package/dashboard/dist/assets/McpRunDetailPage-DjYWJLWh.js.map +0 -1
  217. package/dashboard/dist/assets/McpRunsPage-CTT7B-pf.js +0 -2
  218. package/dashboard/dist/assets/McpRunsPage-CTT7B-pf.js.map +0 -1
  219. package/dashboard/dist/assets/OperationsPage-Dd0WxAwH.js +0 -3
  220. package/dashboard/dist/assets/OperationsPage-Dd0WxAwH.js.map +0 -1
  221. package/dashboard/dist/assets/OperatorDashboard-BfKMaVzV.js +0 -2
  222. package/dashboard/dist/assets/OperatorDashboard-BfKMaVzV.js.map +0 -1
  223. package/dashboard/dist/assets/PersonasPage-dGX6E96g.js +0 -2
  224. package/dashboard/dist/assets/PersonasPage-dGX6E96g.js.map +0 -1
  225. package/dashboard/dist/assets/ProcessDetailPage-Co5oCg31.js +0 -2
  226. package/dashboard/dist/assets/ProcessesListPage-CrB4VAS6.js +0 -2
  227. package/dashboard/dist/assets/ProcessesListPage-CrB4VAS6.js.map +0 -1
  228. package/dashboard/dist/assets/ScanSchemeDetailPage-CkzoOyjd.js +0 -2
  229. package/dashboard/dist/assets/ScanStationPage-DYamACNZ.js +0 -2
  230. package/dashboard/dist/assets/TimeAgo-BpJ7In27.js +0 -2
  231. package/dashboard/dist/assets/WorkflowExecutionPage-CzhoRsis.js +0 -2
  232. package/dashboard/dist/assets/WorkflowsDashboard-B_tCYLei.js +0 -2
  233. package/dashboard/dist/assets/WorkflowsDashboard-B_tCYLei.js.map +0 -1
  234. package/dashboard/dist/assets/WorkflowsOverview-B4Bh8MIo.js +0 -2
  235. package/dashboard/dist/assets/capabilities-B2Dem6zw.js +0 -2
  236. package/dashboard/dist/assets/escalation-columns-BM7vp-hY.js +0 -2
  237. package/dashboard/dist/assets/index-5eRsj758.js +0 -2
  238. package/dashboard/dist/assets/index-5eRsj758.js.map +0 -1
  239. package/dashboard/dist/assets/index-BD6nhSOu.js +0 -2
  240. package/dashboard/dist/assets/index-BD6nhSOu.js.map +0 -1
  241. package/dashboard/dist/assets/index-Baf-tOnA.js +0 -2
  242. package/dashboard/dist/assets/index-Baf-tOnA.js.map +0 -1
  243. package/dashboard/dist/assets/index-DbLOvfv5.js.map +0 -1
  244. package/dashboard/dist/assets/index-zqoZDL5j.js +0 -67
  245. package/dashboard/dist/assets/index-zqoZDL5j.js.map +0 -1
  246. package/dashboard/dist/assets/useDebouncedValue-C4P2jQuh.js +0 -2
  247. package/dashboard/dist/assets/useDebouncedValue-C4P2jQuh.js.map +0 -1
  248. package/dashboard/dist/assets/useEventHooks-onx-Z6IY.js +0 -2
  249. package/dashboard/dist/assets/useEventHooks-onx-Z6IY.js.map +0 -1
  250. package/dashboard/dist/assets/users-B_9mC6D_.js +0 -2
  251. package/dashboard/dist/assets/users-B_9mC6D_.js.map +0 -1
@@ -63,7 +63,8 @@ export declare function createUser(input: {
63
63
  * @param input.email — new email address
64
64
  * @param input.display_name — new display name
65
65
  * @param input.status — new user status
66
- * @param input.metadata — replacement metadata object
66
+ * @param input.metadata — REPLACES the whole properties dictionary; for
67
+ * per-key edits use patchUserProperties (never clobbers siblings)
67
68
  * @returns `{ status: 200, data: User }` on success, or `{ status: 404 }` if not found
68
69
  */
69
70
  export declare function updateUser(input: {
@@ -74,6 +75,35 @@ export declare function updateUser(input: {
74
75
  status?: string;
75
76
  metadata?: Record<string, any>;
76
77
  }): Promise<LTApiResult>;
78
+ /**
79
+ * Atomically patch the user's properties dictionary (lt_users.metadata) —
80
+ * one statement, never read-merge-write. Deleting a key is explicit
81
+ * (`remove`); a key absent from the patch is kept. `rename` preserves the
82
+ * value with no key-absent window. Identity-binding keys (badge scan
83
+ * scheme target facets) assert uniqueness among active users in the same
84
+ * statement.
85
+ *
86
+ * @param input.id — the user's unique identifier
87
+ * @param input.set — properties to set (typed JSON values)
88
+ * @param input.remove — property keys to delete
89
+ * @param input.rename — `{ oldKey: newKey }` renames, values preserved
90
+ * @returns `{ status: 200, data: User }`; 400 malformed patch; 404 unknown
91
+ * user; 409 when an identity value already belongs to another active user
92
+ */
93
+ export declare function patchUserProperties(input: {
94
+ id: string;
95
+ set?: Record<string, unknown>;
96
+ remove?: string[];
97
+ rename?: Record<string, string>;
98
+ }): Promise<LTApiResult>;
99
+ /**
100
+ * The property keys the platform itself resolves identities against — every
101
+ * enabled identity scan scheme's target facet. The dashboard marks these as
102
+ * system properties.
103
+ *
104
+ * @returns `{ status: 200, data: { keys: string[] } }`
105
+ */
106
+ export declare function getSystemPropertyKeys(): Promise<LTApiResult>;
77
107
  /**
78
108
  * Delete a user by ID. Requires admin privileges.
79
109
  *
@@ -37,6 +37,8 @@ exports.listUsers = listUsers;
37
37
  exports.getUser = getUser;
38
38
  exports.createUser = createUser;
39
39
  exports.updateUser = updateUser;
40
+ exports.patchUserProperties = patchUserProperties;
41
+ exports.getSystemPropertyKeys = getSystemPropertyKeys;
40
42
  exports.deleteUser = deleteUser;
41
43
  exports.getUserRoles = getUserRoles;
42
44
  exports.addUserRole = addUserRole;
@@ -145,7 +147,8 @@ async function createUser(input) {
145
147
  * @param input.email — new email address
146
148
  * @param input.display_name — new display name
147
149
  * @param input.status — new user status
148
- * @param input.metadata — replacement metadata object
150
+ * @param input.metadata — REPLACES the whole properties dictionary; for
151
+ * per-key edits use patchUserProperties (never clobbers siblings)
149
152
  * @returns `{ status: 200, data: User }` on success, or `{ status: 404 }` if not found
150
153
  */
151
154
  async function updateUser(input) {
@@ -161,6 +164,56 @@ async function updateUser(input) {
161
164
  return { status: 500, error: err.message };
162
165
  }
163
166
  }
167
+ /**
168
+ * Atomically patch the user's properties dictionary (lt_users.metadata) —
169
+ * one statement, never read-merge-write. Deleting a key is explicit
170
+ * (`remove`); a key absent from the patch is kept. `rename` preserves the
171
+ * value with no key-absent window. Identity-binding keys (badge scan
172
+ * scheme target facets) assert uniqueness among active users in the same
173
+ * statement.
174
+ *
175
+ * @param input.id — the user's unique identifier
176
+ * @param input.set — properties to set (typed JSON values)
177
+ * @param input.remove — property keys to delete
178
+ * @param input.rename — `{ oldKey: newKey }` renames, values preserved
179
+ * @returns `{ status: 200, data: User }`; 400 malformed patch; 404 unknown
180
+ * user; 409 when an identity value already belongs to another active user
181
+ */
182
+ async function patchUserProperties(input) {
183
+ try {
184
+ const user = await userService.patchUserProperties(input.id, {
185
+ set: input.set,
186
+ remove: input.remove,
187
+ rename: input.rename,
188
+ });
189
+ if (!user) {
190
+ return { status: 404, error: 'User not found' };
191
+ }
192
+ return { status: 200, data: user };
193
+ }
194
+ catch (err) {
195
+ if (err.status === 400 || err.status === 409) {
196
+ return { status: err.status, error: err.message };
197
+ }
198
+ return { status: 500, error: err.message };
199
+ }
200
+ }
201
+ /**
202
+ * The property keys the platform itself resolves identities against — every
203
+ * enabled identity scan scheme's target facet. The dashboard marks these as
204
+ * system properties.
205
+ *
206
+ * @returns `{ status: 200, data: { keys: string[] } }`
207
+ */
208
+ async function getSystemPropertyKeys() {
209
+ try {
210
+ const keys = await userService.getIdentityPropertyKeys();
211
+ return { status: 200, data: { keys } };
212
+ }
213
+ catch (err) {
214
+ return { status: 500, error: err.message };
215
+ }
216
+ }
164
217
  /**
165
218
  * Delete a user by ID. Requires admin privileges.
166
219
  *
package/build/bin/ltc.js CHANGED
@@ -335,6 +335,13 @@ usrCmd.command('list')
335
335
  usrCmd.command('get <id>')
336
336
  .option('--json', 'JSON output')
337
337
  .action(wrap(usr.getUser));
338
+ usrCmd.command('props <id>')
339
+ .description('View or atomically patch the user\'s properties dictionary')
340
+ .option('--set <key=value...>', 'Set a property (repeatable; value JSON-parsed, string fallback)')
341
+ .option('--remove <key...>', 'Delete a property (repeatable)')
342
+ .option('--rename <old=new...>', 'Rename a property, value preserved (repeatable)')
343
+ .option('--json', 'JSON output')
344
+ .action(wrap(usr.userProps));
338
345
  // ── Roles ────────────────────────────────────────────────────────────────
339
346
  const rolesCmd = commander_1.program.command('roles').description('Roles — the queue-backed work surfaces where workflows hand off to people');
340
347
  rolesCmd.command('list')
@@ -5,3 +5,14 @@ export declare function listUsers(opts: {
5
5
  export declare function getUser(id: string, opts: {
6
6
  json?: boolean;
7
7
  }): Promise<void>;
8
+ /**
9
+ * View or atomically patch a user's properties dictionary. With no flags,
10
+ * prints the dictionary; with --set/--remove/--rename, applies ONE atomic
11
+ * patch (never read-merge-write) and prints the result.
12
+ */
13
+ export declare function userProps(id: string, opts: {
14
+ set?: string[];
15
+ remove?: string[];
16
+ rename?: string[];
17
+ json?: boolean;
18
+ }): Promise<void>;
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.listUsers = listUsers;
4
7
  exports.getUser = getUser;
8
+ exports.userProps = userProps;
9
+ const picocolors_1 = __importDefault(require("picocolors"));
5
10
  const client_1 = require("../client");
6
11
  const format_1 = require("../format");
7
12
  const COLUMNS = [
@@ -23,3 +28,57 @@ async function getUser(id, opts) {
23
28
  }
24
29
  console.log(JSON.stringify(data, null, 2));
25
30
  }
31
+ /** Parse a repeatable `key=value` flag; values JSON-parse with string fallback. */
32
+ function parsePairs(pairs) {
33
+ if (!pairs?.length)
34
+ return undefined;
35
+ const out = {};
36
+ for (const pair of pairs) {
37
+ const eq = pair.indexOf('=');
38
+ if (eq <= 0)
39
+ throw new Error(`Expected key=value, got "${pair}"`);
40
+ const key = pair.slice(0, eq);
41
+ const raw = pair.slice(eq + 1);
42
+ try {
43
+ out[key] = JSON.parse(raw);
44
+ }
45
+ catch {
46
+ out[key] = raw;
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * View or atomically patch a user's properties dictionary. With no flags,
53
+ * prints the dictionary; with --set/--remove/--rename, applies ONE atomic
54
+ * patch (never read-merge-write) and prints the result.
55
+ */
56
+ async function userProps(id, opts) {
57
+ const set = parsePairs(opts.set);
58
+ const rename = parsePairs(opts.rename);
59
+ const remove = opts.remove?.length ? opts.remove : undefined;
60
+ let user;
61
+ if (set || remove || rename) {
62
+ user = await (0, client_1.apiFetch)(`/users/${id}/properties`, {
63
+ method: 'PATCH',
64
+ body: JSON.stringify({ set, remove, rename }),
65
+ });
66
+ }
67
+ else {
68
+ user = await (0, client_1.apiFetch)(`/users/${id}`);
69
+ }
70
+ if (opts.json) {
71
+ console.log(JSON.stringify(user.metadata ?? {}, null, 2));
72
+ return;
73
+ }
74
+ const props = Object.entries(user.metadata ?? {});
75
+ console.log(`\n ${picocolors_1.default.bold(user.display_name ?? user.external_id ?? id)} ${picocolors_1.default.dim(user.id ?? id)}`);
76
+ if (props.length === 0) {
77
+ console.log(picocolors_1.default.dim(' (no properties)\n'));
78
+ return;
79
+ }
80
+ for (const [key, value] of props) {
81
+ console.log(` ${picocolors_1.default.bold(key)} = ${typeof value === 'string' ? value : JSON.stringify(value)}`);
82
+ }
83
+ console.log();
84
+ }
@@ -55,6 +55,16 @@ router.get('/', async (req, res) => {
55
55
  });
56
56
  res.status(result.status).json(result.data ?? { error: result.error });
57
57
  });
58
+ /**
59
+ * GET /api/users/system-property-keys
60
+ * The property keys the platform resolves identities against (enabled
61
+ * identity scan schemes' target facets) — marked as system properties in
62
+ * the dashboard editor. Registered before /:id so the literal wins.
63
+ */
64
+ router.get('/system-property-keys', async (_req, res) => {
65
+ const result = await api.getSystemPropertyKeys();
66
+ res.status(result.status).json(result.data ?? { error: result.error });
67
+ });
58
68
  /**
59
69
  * GET /api/users/:id
60
70
  * Get a single user by ID.
@@ -81,6 +91,22 @@ router.put('/:id', auth_1.requireBuilder, async (req, res) => {
81
91
  const result = await api.updateUser({ id: req.params.id, ...(req.body || {}) });
82
92
  res.status(result.status).json(result.data ?? { error: result.error });
83
93
  });
94
+ /**
95
+ * PATCH /api/users/:id/properties
96
+ * Atomically patch the user's properties dictionary. Builder only — the
97
+ * same gate as every other user write.
98
+ * Body: { set?: { key: value }, remove?: [key], rename?: { old: new } }
99
+ */
100
+ router.patch('/:id/properties', auth_1.requireBuilder, async (req, res) => {
101
+ const { set, remove, rename } = req.body || {};
102
+ const result = await api.patchUserProperties({
103
+ id: req.params.id,
104
+ set,
105
+ remove,
106
+ rename,
107
+ });
108
+ res.status(result.status).json(result.data ?? { error: result.error });
109
+ });
84
110
  /**
85
111
  * DELETE /api/users/:id
86
112
  * Delete a user. Builder only.
@@ -290,6 +290,8 @@ export declare function createClient(options?: LTClientOptions): {
290
290
  get: typeof usersApi.getUser;
291
291
  create: typeof usersApi.createUser;
292
292
  update: typeof usersApi.updateUser;
293
+ patchProperties: typeof usersApi.patchUserProperties;
294
+ getSystemPropertyKeys: typeof usersApi.getSystemPropertyKeys;
293
295
  delete: typeof usersApi.deleteUser;
294
296
  getRoles: typeof usersApi.getUserRoles;
295
297
  addRole: typeof usersApi.addUserRole;
@@ -194,6 +194,8 @@ function createClient(options = {}) {
194
194
  get: usersApi.getUser,
195
195
  create: usersApi.createUser,
196
196
  update: usersApi.updateUser,
197
+ patchProperties: usersApi.patchUserProperties,
198
+ getSystemPropertyKeys: usersApi.getSystemPropertyKeys,
197
199
  delete: usersApi.deleteUser,
198
200
  getRoles: usersApi.getUserRoles,
199
201
  addRole: usersApi.addUserRole,
@@ -1,5 +1,5 @@
1
1
  import type { LTUserRecord, LTRoleType, LTUserStatus } from '../../types';
2
- import type { CreateUserInput, UpdateUserInput } from './types';
2
+ import type { CreateUserInput, UpdateUserInput, UserPropertyOps } from './types';
3
3
  export declare function attachRoles(user: any): Promise<LTUserRecord>;
4
4
  export declare function createUser(input: CreateUserInput): Promise<LTUserRecord>;
5
5
  export declare function getUser(id: string): Promise<LTUserRecord | null>;
@@ -12,6 +12,37 @@ export declare function getUserByEmail(email: string): Promise<LTUserRecord | nu
12
12
  */
13
13
  export declare function getUserByMetadataValue(key: string, value: string): Promise<LTUserRecord | null>;
14
14
  export declare function updateUser(id: string, input: UpdateUserInput): Promise<LTUserRecord | null>;
15
+ /** A property write that would create an ambiguous identity binding. */
16
+ export declare class UserPropertyConflictError extends Error {
17
+ status: number;
18
+ constructor(key: string);
19
+ }
20
+ /** A malformed property patch — rejected before any write. */
21
+ export declare class UserPropertyValidationError extends Error {
22
+ status: number;
23
+ constructor(message: string);
24
+ }
25
+ /**
26
+ * The lt_users.metadata keys the platform itself resolves identities against:
27
+ * every enabled identity scan scheme's target_facet (e.g. badge_id). Writes
28
+ * to these keys carry a uniqueness guard; the dashboard marks them as system
29
+ * properties.
30
+ */
31
+ export declare function getIdentityPropertyKeys(): Promise<string[]>;
32
+ /**
33
+ * Atomic per-key patch of the user's properties dictionary (lt_users.metadata).
34
+ * One statement — never read-merge-write: concurrent patches of different
35
+ * keys both land, a rename never opens a key-absent window, and deleting is
36
+ * explicit (`remove`) — an absent key means keep. Precedence on collision:
37
+ * set > rename > existing. Values are raw JSON, so numbers/booleans/objects
38
+ * round-trip typed.
39
+ *
40
+ * Identity-binding keys (getIdentityPropertyKeys) additionally assert, in the
41
+ * same statement, that no OTHER active user carries the value — the
42
+ * write-side counterpart of getUserByMetadataValue's ambiguity throw.
43
+ * A tripped guard raises UserPropertyConflictError (409).
44
+ */
45
+ export declare function patchUserProperties(id: string, ops: UserPropertyOps): Promise<LTUserRecord | null>;
15
46
  export declare function deleteUser(id: string): Promise<boolean>;
16
47
  export declare function listUsers(filters: {
17
48
  role?: string;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UserPropertyValidationError = exports.UserPropertyConflictError = void 0;
6
7
  exports.attachRoles = attachRoles;
7
8
  exports.createUser = createUser;
8
9
  exports.getUser = getUser;
@@ -10,11 +11,15 @@ exports.getUserByExternalId = getUserByExternalId;
10
11
  exports.getUserByEmail = getUserByEmail;
11
12
  exports.getUserByMetadataValue = getUserByMetadataValue;
12
13
  exports.updateUser = updateUser;
14
+ exports.getIdentityPropertyKeys = getIdentityPropertyKeys;
15
+ exports.patchUserProperties = patchUserProperties;
13
16
  exports.deleteUser = deleteUser;
14
17
  exports.listUsers = listUsers;
15
18
  const bcryptjs_1 = __importDefault(require("bcryptjs"));
16
19
  const db_1 = require("../../lib/db");
17
20
  const sql_1 = require("./sql");
21
+ const scan_code_1 = require("../scan-code");
22
+ const scan_code_2 = require("../../types/scan-code");
18
23
  const scope_1 = require("./scope");
19
24
  // ─── Private helpers (exported for internal use by auth.ts) ──────────────────
20
25
  async function attachRoles(user) {
@@ -143,6 +148,109 @@ async function updateUser(id, input) {
143
148
  return null;
144
149
  return attachRoles(rows[0]);
145
150
  }
151
+ /** A property write that would create an ambiguous identity binding. */
152
+ class UserPropertyConflictError extends Error {
153
+ constructor(key) {
154
+ super(`"${key}" value is already bound to another active user`);
155
+ this.status = 409;
156
+ this.name = 'UserPropertyConflictError';
157
+ }
158
+ }
159
+ exports.UserPropertyConflictError = UserPropertyConflictError;
160
+ /** A malformed property patch — rejected before any write. */
161
+ class UserPropertyValidationError extends Error {
162
+ constructor(message) {
163
+ super(message);
164
+ this.status = 400;
165
+ this.name = 'UserPropertyValidationError';
166
+ }
167
+ }
168
+ exports.UserPropertyValidationError = UserPropertyValidationError;
169
+ /**
170
+ * The lt_users.metadata keys the platform itself resolves identities against:
171
+ * every enabled identity scan scheme's target_facet (e.g. badge_id). Writes
172
+ * to these keys carry a uniqueness guard; the dashboard marks them as system
173
+ * properties.
174
+ */
175
+ async function getIdentityPropertyKeys() {
176
+ const schemes = await (0, scan_code_1.listScanSchemes)();
177
+ return [...new Set(schemes
178
+ .filter((s) => s.enabled && s.kind === scan_code_2.SCAN_SCHEME_KINDS.IDENTITY)
179
+ .map((s) => s.target_facet))];
180
+ }
181
+ /**
182
+ * Atomic per-key patch of the user's properties dictionary (lt_users.metadata).
183
+ * One statement — never read-merge-write: concurrent patches of different
184
+ * keys both land, a rename never opens a key-absent window, and deleting is
185
+ * explicit (`remove`) — an absent key means keep. Precedence on collision:
186
+ * set > rename > existing. Values are raw JSON, so numbers/booleans/objects
187
+ * round-trip typed.
188
+ *
189
+ * Identity-binding keys (getIdentityPropertyKeys) additionally assert, in the
190
+ * same statement, that no OTHER active user carries the value — the
191
+ * write-side counterpart of getUserByMetadataValue's ambiguity throw.
192
+ * A tripped guard raises UserPropertyConflictError (409).
193
+ */
194
+ async function patchUserProperties(id, ops) {
195
+ const set = ops.set ?? {};
196
+ const remove = ops.remove ?? [];
197
+ const rename = ops.rename ?? {};
198
+ const setKeys = Object.keys(set);
199
+ const renameEntries = Object.entries(rename);
200
+ if (setKeys.length === 0 && remove.length === 0 && renameEntries.length === 0) {
201
+ throw new UserPropertyValidationError('A property patch requires at least one set, remove, or rename');
202
+ }
203
+ for (const key of [...setKeys, ...remove, ...renameEntries.flat()]) {
204
+ if (typeof key !== 'string' || key.trim().length === 0) {
205
+ throw new UserPropertyValidationError('Property keys must be non-empty strings');
206
+ }
207
+ }
208
+ for (const key of setKeys) {
209
+ if (remove.includes(key)) {
210
+ throw new UserPropertyValidationError(`"${key}" cannot be both set and removed in one patch`);
211
+ }
212
+ }
213
+ for (const [prev, next] of renameEntries) {
214
+ if (prev === next) {
215
+ throw new UserPropertyValidationError(`Renaming "${prev}" to itself is not a change`);
216
+ }
217
+ }
218
+ const renameTargets = renameEntries.map(([, next]) => next);
219
+ if (new Set(renameTargets).size !== renameTargets.length) {
220
+ throw new UserPropertyValidationError('Two properties cannot be renamed to the same key');
221
+ }
222
+ // Identity bindings resolve people — their values must be scalar and, for
223
+ // ACTIVE users, unique. The guard rides the same statement.
224
+ const identityKeys = await getIdentityPropertyKeys();
225
+ const guard = {};
226
+ for (const key of identityKeys) {
227
+ if (key in set) {
228
+ const value = set[key];
229
+ if (typeof value !== 'string' && typeof value !== 'number') {
230
+ throw new UserPropertyValidationError(`"${key}" is an identity binding — its value must be a string or number`);
231
+ }
232
+ guard[key] = String(value);
233
+ }
234
+ }
235
+ const removeAll = [...new Set([...remove, ...renameEntries.map(([prev]) => prev)])];
236
+ const renamePairs = renameEntries.map(([prev, next]) => ({ prev, next }));
237
+ const pool = (0, db_1.getPool)();
238
+ const { rows } = await pool.query(sql_1.PATCH_USER_PROPERTIES, [
239
+ id,
240
+ removeAll,
241
+ JSON.stringify(renamePairs),
242
+ JSON.stringify(set),
243
+ JSON.stringify(guard),
244
+ ]);
245
+ if (rows[0])
246
+ return attachRoles(rows[0]);
247
+ const exists = await pool.query(sql_1.VERIFY_USER_BY_ID, [id]);
248
+ if (!exists.rows[0])
249
+ return null;
250
+ // The user exists, so the identity guard is what blocked the write. Name
251
+ // the first guarded key for the caller; the row was not touched.
252
+ throw new UserPropertyConflictError(Object.keys(guard)[0] ?? 'identity binding');
253
+ }
146
254
  async function deleteUser(id) {
147
255
  const pool = (0, db_1.getPool)();
148
256
  const { rowCount } = await pool.query(sql_1.DELETE_USER_BY_ID, [id]);
@@ -1,5 +1,5 @@
1
- export { CreateUserInput, UpdateUserInput, VALID_ROLE_TYPES } from './types';
2
- export { createUser, getUser, getUserByExternalId, getUserByEmail, getUserByMetadataValue, updateUser, deleteUser, listUsers, } from './crud';
1
+ export { CreateUserInput, UpdateUserInput, UserPropertyOps, VALID_ROLE_TYPES } from './types';
2
+ export { createUser, getUser, getUserByExternalId, getUserByEmail, getUserByMetadataValue, updateUser, patchUserProperties, getIdentityPropertyKeys, UserPropertyConflictError, UserPropertyValidationError, deleteUser, listUsers, } from './crud';
3
3
  export { isValidRoleType, addUserRole, grantRoleIfAbsent, removeUserRole, getUserRoles, hasRole, hasRoleType, } from './roles';
4
4
  export { isSuperAdmin, isGroupAdmin, canManageRole, hasGlobalEscalationAccess, hasRolesAsAdmin, getRoleScope, getRoleWriteScope } from './rbac';
5
5
  export { READ_SCOPES, WRITE_SCOPES, DEFAULT_READ_SCOPE, DEFAULT_WRITE_SCOPE, isValidReadScope, isValidWriteScope, isValidScopePair, effectiveScope, } from './scope';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PREFERENCES_MAX_BYTES = exports.patchPreferences = exports.getPreferences = 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.grantRoleIfAbsent = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.updateUser = exports.getUserByMetadataValue = exports.getUserByEmail = exports.getUserByExternalId = exports.getUser = exports.createUser = exports.VALID_ROLE_TYPES = void 0;
3
+ exports.PREFERENCES_MAX_BYTES = exports.patchPreferences = exports.getPreferences = 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.grantRoleIfAbsent = exports.addUserRole = exports.isValidRoleType = exports.listUsers = exports.deleteUser = exports.UserPropertyValidationError = exports.UserPropertyConflictError = exports.getIdentityPropertyKeys = exports.patchUserProperties = exports.updateUser = exports.getUserByMetadataValue = 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");
@@ -10,6 +10,10 @@ Object.defineProperty(exports, "getUserByExternalId", { enumerable: true, get: f
10
10
  Object.defineProperty(exports, "getUserByEmail", { enumerable: true, get: function () { return crud_1.getUserByEmail; } });
11
11
  Object.defineProperty(exports, "getUserByMetadataValue", { enumerable: true, get: function () { return crud_1.getUserByMetadataValue; } });
12
12
  Object.defineProperty(exports, "updateUser", { enumerable: true, get: function () { return crud_1.updateUser; } });
13
+ Object.defineProperty(exports, "patchUserProperties", { enumerable: true, get: function () { return crud_1.patchUserProperties; } });
14
+ Object.defineProperty(exports, "getIdentityPropertyKeys", { enumerable: true, get: function () { return crud_1.getIdentityPropertyKeys; } });
15
+ Object.defineProperty(exports, "UserPropertyConflictError", { enumerable: true, get: function () { return crud_1.UserPropertyConflictError; } });
16
+ Object.defineProperty(exports, "UserPropertyValidationError", { enumerable: true, get: function () { return crud_1.UserPropertyValidationError; } });
13
17
  Object.defineProperty(exports, "deleteUser", { enumerable: true, get: function () { return crud_1.deleteUser; } });
14
18
  Object.defineProperty(exports, "listUsers", { enumerable: true, get: function () { return crud_1.listUsers; } });
15
19
  var roles_1 = require("./roles");
@@ -28,6 +28,22 @@ export declare const GET_USER_BY_ID = "SELECT * FROM lt_users WHERE id = $1";
28
28
  export declare const GET_USERS_BY_METADATA_VALUE = "SELECT * FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2";
29
29
  /** Check if a user exists by id. Lightweight — returns only the id column. */
30
30
  export declare const VERIFY_USER_BY_ID = "SELECT id FROM lt_users WHERE id = $1 LIMIT 1";
31
+ /**
32
+ * Atomic per-key patch of the user's properties dictionary — ONE statement,
33
+ * never read-merge-write. Composition (later wins on key collision):
34
+ *
35
+ * base minus ($2: removed keys ∪ rename old-names)
36
+ * || renames ($3: [{prev,next}] — each carries the OLD value, skipped when
37
+ * the source key is absent)
38
+ * || sets ($4: typed JSON values)
39
+ *
40
+ * $5 is the identity-binding guard: {key: value} pairs that must not already
41
+ * belong to ANOTHER active user (the write-side counterpart of the
42
+ * resolver's ambiguity throw in GET_USERS_BY_METADATA_VALUE). Zero rows
43
+ * back means the user is missing OR the guard tripped — the caller
44
+ * distinguishes with VERIFY_USER_BY_ID.
45
+ */
46
+ export declare const PATCH_USER_PROPERTIES = "\n UPDATE lt_users SET metadata = (\n (COALESCE(metadata, '{}'::jsonb) - $2::text[])\n || COALESCE((\n SELECT jsonb_object_agg(r.next, COALESCE(lt_users.metadata, '{}'::jsonb) -> r.prev)\n FROM jsonb_to_recordset($3::jsonb) AS r(prev text, next text)\n WHERE COALESCE(lt_users.metadata, '{}'::jsonb) ? r.prev\n ), '{}'::jsonb)\n || $4::jsonb\n )\n WHERE id = $1\n AND NOT EXISTS (\n SELECT 1\n FROM lt_users u2, jsonb_each_text($5::jsonb) AS g(k, v)\n WHERE u2.id <> $1 AND u2.status = 'active' AND u2.metadata ->> g.k = g.v\n )\n RETURNING *";
31
47
  /** Fetch user + roles in a single query. Returns one row per role (or one row with nulls if no roles). */
32
48
  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";
33
49
  export declare const DELETE_USER_BY_ID = "DELETE FROM lt_users WHERE id = $1";
@@ -1,7 +1,7 @@
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.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.INSERT_USER_ROLE_IF_ABSENT = 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_USERS_BY_METADATA_VALUE = 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;
4
+ exports.IS_GROUP_ADMIN = exports.GET_ROLE_SCOPE = exports.HAS_ROLE_TYPE = exports.HAS_ROLE = exports.DELETE_USER_ROLE = exports.INSERT_USER_ROLE_IF_ABSENT = exports.UPSERT_USER_ROLE = exports.GET_ROLES_BY_USER_IDS = exports.DELETE_USER_BY_ID = exports.GET_USER_WITH_ROLES = exports.PATCH_USER_PROPERTIES = exports.VERIFY_USER_BY_ID = exports.GET_USERS_BY_METADATA_VALUE = 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. granted_by_persona
@@ -50,6 +50,38 @@ exports.GET_USER_BY_ID = 'SELECT * FROM lt_users WHERE id = $1';
50
50
  exports.GET_USERS_BY_METADATA_VALUE = `SELECT * FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2`;
51
51
  /** Check if a user exists by id. Lightweight — returns only the id column. */
52
52
  exports.VERIFY_USER_BY_ID = 'SELECT id FROM lt_users WHERE id = $1 LIMIT 1';
53
+ /**
54
+ * Atomic per-key patch of the user's properties dictionary — ONE statement,
55
+ * never read-merge-write. Composition (later wins on key collision):
56
+ *
57
+ * base minus ($2: removed keys ∪ rename old-names)
58
+ * || renames ($3: [{prev,next}] — each carries the OLD value, skipped when
59
+ * the source key is absent)
60
+ * || sets ($4: typed JSON values)
61
+ *
62
+ * $5 is the identity-binding guard: {key: value} pairs that must not already
63
+ * belong to ANOTHER active user (the write-side counterpart of the
64
+ * resolver's ambiguity throw in GET_USERS_BY_METADATA_VALUE). Zero rows
65
+ * back means the user is missing OR the guard tripped — the caller
66
+ * distinguishes with VERIFY_USER_BY_ID.
67
+ */
68
+ exports.PATCH_USER_PROPERTIES = `
69
+ UPDATE lt_users SET metadata = (
70
+ (COALESCE(metadata, '{}'::jsonb) - $2::text[])
71
+ || COALESCE((
72
+ SELECT jsonb_object_agg(r.next, COALESCE(lt_users.metadata, '{}'::jsonb) -> r.prev)
73
+ FROM jsonb_to_recordset($3::jsonb) AS r(prev text, next text)
74
+ WHERE COALESCE(lt_users.metadata, '{}'::jsonb) ? r.prev
75
+ ), '{}'::jsonb)
76
+ || $4::jsonb
77
+ )
78
+ WHERE id = $1
79
+ AND NOT EXISTS (
80
+ SELECT 1
81
+ FROM lt_users u2, jsonb_each_text($5::jsonb) AS g(k, v)
82
+ WHERE u2.id <> $1 AND u2.status = 'active' AND u2.metadata ->> g.k = g.v
83
+ )
84
+ RETURNING *`;
53
85
  /** Fetch user + roles in a single query. Returns one row per role (or one row with nulls if no roles). */
54
86
  exports.GET_USER_WITH_ROLES = `SELECT u.id, u.external_id, u.display_name, u.status, u.metadata,
55
87
  r.role, r.type AS role_type, r.read_scope, r.write_scope
@@ -24,5 +24,16 @@ export interface UpdateUserInput {
24
24
  display_name?: string;
25
25
  password?: string;
26
26
  status?: LTUserStatus;
27
+ /** Wholesale REPLACE of the properties dictionary — for per-key edits use patchUserProperties. */
27
28
  metadata?: Record<string, any>;
28
29
  }
30
+ /**
31
+ * One atomic patch of the user's properties dictionary. Deleting is explicit
32
+ * (`remove`) — a key absent from the patch is kept. `rename` preserves the
33
+ * value. Precedence on collision: set > rename > existing.
34
+ */
35
+ export interface UserPropertyOps {
36
+ set?: Record<string, unknown>;
37
+ remove?: string[];
38
+ rename?: Record<string, string>;
39
+ }