@hotmeshio/long-tail 0.18.0 → 0.19.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 (283) hide show
  1. package/build/api/escalations/index.d.ts +1 -1
  2. package/build/api/escalations/index.js +2 -1
  3. package/build/api/escalations/list.d.ts +11 -0
  4. package/build/api/escalations/list.js +31 -2
  5. package/build/api/settings.js +2 -0
  6. package/build/api/users.d.ts +11 -0
  7. package/build/api/users.js +19 -0
  8. package/build/index.js +6 -0
  9. package/build/lib/uuid.d.ts +11 -0
  10. package/build/lib/uuid.js +20 -0
  11. package/build/modules/search.d.ts +21 -0
  12. package/build/modules/search.js +55 -0
  13. package/build/routes/escalations/list.js +10 -1
  14. package/build/routes/users.js +9 -0
  15. package/build/scripts/ortho-populate.d.ts +3 -1
  16. package/build/scripts/ortho-populate.js +15 -5
  17. package/build/sdk/index.d.ts +1 -0
  18. package/build/services/escalation/batch.js +3 -0
  19. package/build/services/escalation/bulk.js +8 -0
  20. package/build/services/escalation/crud.js +26 -0
  21. package/build/services/escalation/queries.d.ts +14 -1
  22. package/build/services/escalation/queries.js +49 -7
  23. package/build/services/escalation/sql.d.ts +4 -2
  24. package/build/services/escalation/sql.js +4 -0
  25. package/build/services/role/types.d.ts +18 -0
  26. package/build/services/role/types.js +6 -0
  27. package/build/services/user/auth.js +2 -2
  28. package/build/services/user/crud.d.ts +13 -0
  29. package/build/services/user/crud.js +26 -3
  30. package/build/services/user/index.d.ts +1 -1
  31. package/build/services/user/index.js +2 -1
  32. package/build/services/user/sql.d.ts +11 -5
  33. package/build/services/user/sql.js +18 -6
  34. package/build/start/adapters.js +2 -0
  35. package/build/tsconfig.tsbuildinfo +1 -1
  36. package/build/types/startup.d.ts +12 -1
  37. package/dashboard/dist/assets/{AdminDashboard-mULAQuHV.js → AdminDashboard-DPkWtPc1.js} +2 -2
  38. package/dashboard/dist/assets/{AdminDashboard-mULAQuHV.js.map → AdminDashboard-DPkWtPc1.js.map} +1 -1
  39. package/dashboard/dist/assets/{AgentConfigPage-DHdzgXcr.js → AgentConfigPage-DRfnLiQS.js} +6 -6
  40. package/dashboard/dist/assets/{AgentConfigPage-DHdzgXcr.js.map → AgentConfigPage-DRfnLiQS.js.map} +1 -1
  41. package/dashboard/dist/assets/{AgentDetailPage-Du_33U2u.js → AgentDetailPage-CufRZXnT.js} +3 -3
  42. package/dashboard/dist/assets/{AgentDetailPage-Du_33U2u.js.map → AgentDetailPage-CufRZXnT.js.map} +1 -1
  43. package/dashboard/dist/assets/{AgentsPage-4KkQO2Hl.js → AgentsPage-CoCWGhUd.js} +2 -2
  44. package/dashboard/dist/assets/{AgentsPage-4KkQO2Hl.js.map → AgentsPage-CoCWGhUd.js.map} +1 -1
  45. package/dashboard/dist/assets/AvailableEscalationsPage-BSqOctep.js +2 -0
  46. package/dashboard/dist/assets/AvailableEscalationsPage-BSqOctep.js.map +1 -0
  47. package/dashboard/dist/assets/{BotPicker-BaFiLa-H.js → BotPicker-DMojDLlV.js} +2 -2
  48. package/dashboard/dist/assets/{BotPicker-BaFiLa-H.js.map → BotPicker-DMojDLlV.js.map} +1 -1
  49. package/dashboard/dist/assets/BulkAssignModal-DYbM_6Or.js +2 -0
  50. package/dashboard/dist/assets/BulkAssignModal-DYbM_6Or.js.map +1 -0
  51. package/dashboard/dist/assets/CapabilitiesPage-CDQ5JHNP.js +2 -0
  52. package/dashboard/dist/assets/{CapabilitiesPage-Cewd6Uku.js.map → CapabilitiesPage-CDQ5JHNP.js.map} +1 -1
  53. package/dashboard/dist/assets/{CollapsibleSection-D1WIbyDa.js → CollapsibleSection-D4VlCEbD.js} +2 -2
  54. package/dashboard/dist/assets/{CollapsibleSection-D1WIbyDa.js.map → CollapsibleSection-D4VlCEbD.js.map} +1 -1
  55. package/dashboard/dist/assets/{ConfirmDeleteModal-CQsJUzEa.js → ConfirmDeleteModal-DnNHjJtD.js} +2 -2
  56. package/dashboard/dist/assets/{ConfirmDeleteModal-CQsJUzEa.js.map → ConfirmDeleteModal-DnNHjJtD.js.map} +1 -1
  57. package/dashboard/dist/assets/CountdownTimer-DPHMBmgz.js +2 -0
  58. package/dashboard/dist/assets/CountdownTimer-DPHMBmgz.js.map +1 -0
  59. package/dashboard/dist/assets/CredentialsPage-CSYZw-rL.js +2 -0
  60. package/dashboard/dist/assets/{CredentialsPage-CwtkQUG3.js.map → CredentialsPage-CSYZw-rL.js.map} +1 -1
  61. package/dashboard/dist/assets/{CronLabel-CRC_uNuC.js → CronLabel-CpmqDrlJ.js} +2 -2
  62. package/dashboard/dist/assets/{CronLabel-CRC_uNuC.js.map → CronLabel-CpmqDrlJ.js.map} +1 -1
  63. package/dashboard/dist/assets/{DropZone-Pv-v5RAs.js → DropZone-BbXlQnYB.js} +2 -2
  64. package/dashboard/dist/assets/{DropZone-Pv-v5RAs.js.map → DropZone-BbXlQnYB.js.map} +1 -1
  65. package/dashboard/dist/assets/{ElapsedCell-CxAi0zN0.js → ElapsedCell-Cul22qBS.js} +2 -2
  66. package/dashboard/dist/assets/{ElapsedCell-CxAi0zN0.js.map → ElapsedCell-Cul22qBS.js.map} +1 -1
  67. package/dashboard/dist/assets/EntityTimelinePanel-CtzpJ-j2.js +2 -0
  68. package/dashboard/dist/assets/EntityTimelinePanel-CtzpJ-j2.js.map +1 -0
  69. package/dashboard/dist/assets/{EscalationListSchemaPage--yZfsE8s.js → EscalationListSchemaPage-BZQ8Y_UH.js} +3 -3
  70. package/dashboard/dist/assets/{EscalationListSchemaPage--yZfsE8s.js.map → EscalationListSchemaPage-BZQ8Y_UH.js.map} +1 -1
  71. package/dashboard/dist/assets/{EscalationSchemaPage-CmoVqaS7.js → EscalationSchemaPage-CydTtq_A.js} +3 -3
  72. package/dashboard/dist/assets/{EscalationSchemaPage-CmoVqaS7.js.map → EscalationSchemaPage-CydTtq_A.js.map} +1 -1
  73. package/dashboard/dist/assets/{EscalationsOverview-DDQTW6Jo.js → EscalationsOverview-DSYC0nI7.js} +2 -2
  74. package/dashboard/dist/assets/{EscalationsOverview-DDQTW6Jo.js.map → EscalationsOverview-DSYC0nI7.js.map} +1 -1
  75. package/dashboard/dist/assets/{EventTable-C5jN7XT7.js → EventTable-s48cTXIw.js} +2 -2
  76. package/dashboard/dist/assets/{EventTable-C5jN7XT7.js.map → EventTable-s48cTXIw.js.map} +1 -1
  77. package/dashboard/dist/assets/{FilterBar-CcIgQH9h.js → FilterBar-B61qKmFD.js} +2 -2
  78. package/dashboard/dist/assets/{FilterBar-CcIgQH9h.js.map → FilterBar-B61qKmFD.js.map} +1 -1
  79. package/dashboard/dist/assets/GraphInvokePage-DxZv_KC2.js +2 -0
  80. package/dashboard/dist/assets/{GraphInvokePage-LACIxaiH.js.map → GraphInvokePage-DxZv_KC2.js.map} +1 -1
  81. package/dashboard/dist/assets/HomePage-zK3VP_9g.js +2 -0
  82. package/dashboard/dist/assets/HomePage-zK3VP_9g.js.map +1 -0
  83. package/dashboard/dist/assets/{ListToolbar-CLwSkmUH.js → ListToolbar-DaxloPQc.js} +2 -2
  84. package/dashboard/dist/assets/{ListToolbar-CLwSkmUH.js.map → ListToolbar-DaxloPQc.js.map} +1 -1
  85. package/dashboard/dist/assets/{McpOverview-C2U1XfQw.js → McpOverview-Ciq8r6-p.js} +2 -2
  86. package/dashboard/dist/assets/{McpOverview-C2U1XfQw.js.map → McpOverview-Ciq8r6-p.js.map} +1 -1
  87. package/dashboard/dist/assets/{McpQueryDetailPage-DYOK8A4D.js → McpQueryDetailPage-DIC0FEEb.js} +3 -3
  88. package/dashboard/dist/assets/{McpQueryDetailPage-DYOK8A4D.js.map → McpQueryDetailPage-DIC0FEEb.js.map} +1 -1
  89. package/dashboard/dist/assets/McpQueryPage-BBy0NSNg.js +2 -0
  90. package/dashboard/dist/assets/{McpQueryPage-Ck75ikSd.js.map → McpQueryPage-BBy0NSNg.js.map} +1 -1
  91. package/dashboard/dist/assets/{McpRunDetailPage-CA9gWQ4L.js → McpRunDetailPage-BqKNmIyW.js} +2 -2
  92. package/dashboard/dist/assets/{McpRunDetailPage-CA9gWQ4L.js.map → McpRunDetailPage-BqKNmIyW.js.map} +1 -1
  93. package/dashboard/dist/assets/{McpRunsPage-BhPshset.js → McpRunsPage-D0RlOpzR.js} +2 -2
  94. package/dashboard/dist/assets/{McpRunsPage-BhPshset.js.map → McpRunsPage-D0RlOpzR.js.map} +1 -1
  95. package/dashboard/dist/assets/NamespacePill-Dbtm9R2T.js +2 -0
  96. package/dashboard/dist/assets/{NamespacePill-DONj6eUU.js.map → NamespacePill-Dbtm9R2T.js.map} +1 -1
  97. package/dashboard/dist/assets/OperationsPage-BX0SFizr.js +3 -0
  98. package/dashboard/dist/assets/OperationsPage-BX0SFizr.js.map +1 -0
  99. package/dashboard/dist/assets/OperatorDashboard-vXG0TjAQ.js +2 -0
  100. package/dashboard/dist/assets/{OperatorDashboard-DZOfHRE7.js.map → OperatorDashboard-vXG0TjAQ.js.map} +1 -1
  101. package/dashboard/dist/assets/{PageHeader-CFVw67M6.js → PageHeader-xB92ELs3.js} +2 -2
  102. package/dashboard/dist/assets/{PageHeader-CFVw67M6.js.map → PageHeader-xB92ELs3.js.map} +1 -1
  103. package/dashboard/dist/assets/{PageHeaderWithStats-D9tQxILm.js → PageHeaderWithStats-DVo_qqn1.js} +2 -2
  104. package/dashboard/dist/assets/{PageHeaderWithStats-D9tQxILm.js.map → PageHeaderWithStats-DVo_qqn1.js.map} +1 -1
  105. package/dashboard/dist/assets/PersonaDetailPage-w5TXcn4Q.js +2 -0
  106. package/dashboard/dist/assets/{PersonaDetailPage-eyHDupL0.js.map → PersonaDetailPage-w5TXcn4Q.js.map} +1 -1
  107. package/dashboard/dist/assets/PersonasPage-Dzcinl1L.js +2 -0
  108. package/dashboard/dist/assets/PersonasPage-Dzcinl1L.js.map +1 -0
  109. package/dashboard/dist/assets/{ProcessDetailPage-CKiAlpOc.js → ProcessDetailPage-BVXieFX1.js} +2 -2
  110. package/dashboard/dist/assets/{ProcessDetailPage-CKiAlpOc.js.map → ProcessDetailPage-BVXieFX1.js.map} +1 -1
  111. package/dashboard/dist/assets/{ProcessesListPage-C4ueZJxU.js → ProcessesListPage-CKKNpk4z.js} +2 -2
  112. package/dashboard/dist/assets/{ProcessesListPage-C4ueZJxU.js.map → ProcessesListPage-CKKNpk4z.js.map} +1 -1
  113. package/dashboard/dist/assets/RoleDetailPage-BytSo-YE.js +8 -0
  114. package/dashboard/dist/assets/RoleDetailPage-BytSo-YE.js.map +1 -0
  115. package/dashboard/dist/assets/{RolePill-Di_5hfvw.js → RolePill-CYpL3a2d.js} +2 -2
  116. package/dashboard/dist/assets/{RolePill-Di_5hfvw.js.map → RolePill-CYpL3a2d.js.map} +1 -1
  117. package/dashboard/dist/assets/RolesPage-eayR9H4f.js +2 -0
  118. package/dashboard/dist/assets/{RolesPage-B3-9Ek0F.js.map → RolesPage-eayR9H4f.js.map} +1 -1
  119. package/dashboard/dist/assets/{RunAsSelector-1k1J1_cv.js → RunAsSelector-D7RPwaAl.js} +2 -2
  120. package/dashboard/dist/assets/{RunAsSelector-1k1J1_cv.js.map → RunAsSelector-D7RPwaAl.js.map} +1 -1
  121. package/dashboard/dist/assets/{ScanCodesPage-CeuVKnpu.js → ScanCodesPage-C8-ZBXF5.js} +2 -2
  122. package/dashboard/dist/assets/{ScanCodesPage-CeuVKnpu.js.map → ScanCodesPage-C8-ZBXF5.js.map} +1 -1
  123. package/dashboard/dist/assets/ScanSchemeDetailPage-tQhEgFKf.js +2 -0
  124. package/dashboard/dist/assets/{ScanSchemeDetailPage-BnivPUJY.js.map → ScanSchemeDetailPage-tQhEgFKf.js.map} +1 -1
  125. package/dashboard/dist/assets/ScanStationPage-CuhqsBKo.js +2 -0
  126. package/dashboard/dist/assets/{ScanStationPage-DCSA7pcV.js.map → ScanStationPage-CuhqsBKo.js.map} +1 -1
  127. package/dashboard/dist/assets/{ScopeBadge-CqIIeWjK.js → ScopeBadge-BmNzZlSj.js} +2 -2
  128. package/dashboard/dist/assets/{ScopeBadge-CqIIeWjK.js.map → ScopeBadge-BmNzZlSj.js.map} +1 -1
  129. package/dashboard/dist/assets/{StickyPagination-BWuKz8uB.js → StickyPagination-BIVTAMnP.js} +2 -2
  130. package/dashboard/dist/assets/{StickyPagination-BWuKz8uB.js.map → StickyPagination-BIVTAMnP.js.map} +1 -1
  131. package/dashboard/dist/assets/{StreamMessageDetail-Cq6AGhgJ.js → StreamMessageDetail-CYSC2cO1.js} +2 -2
  132. package/dashboard/dist/assets/{StreamMessageDetail-Cq6AGhgJ.js.map → StreamMessageDetail-CYSC2cO1.js.map} +1 -1
  133. package/dashboard/dist/assets/{SwimlaneTimeline-DM88avgt.js → SwimlaneTimeline-BMZAs_2K.js} +2 -2
  134. package/dashboard/dist/assets/{SwimlaneTimeline-DM88avgt.js.map → SwimlaneTimeline-BMZAs_2K.js.map} +1 -1
  135. package/dashboard/dist/assets/{TagInput-BLwxTNsI.js → TagInput-D1uszNd2.js} +2 -2
  136. package/dashboard/dist/assets/{TagInput-BLwxTNsI.js.map → TagInput-D1uszNd2.js.map} +1 -1
  137. package/dashboard/dist/assets/{TaskDetailPage-Caqe3fG8.js → TaskDetailPage-CVUDUU5Q.js} +2 -2
  138. package/dashboard/dist/assets/{TaskDetailPage-Caqe3fG8.js.map → TaskDetailPage-CVUDUU5Q.js.map} +1 -1
  139. package/dashboard/dist/assets/{TaskQueuePill-DVgcqd-h.js → TaskQueuePill-C2H8oRfn.js} +2 -2
  140. package/dashboard/dist/assets/{TaskQueuePill-DVgcqd-h.js.map → TaskQueuePill-C2H8oRfn.js.map} +1 -1
  141. package/dashboard/dist/assets/{TasksListPage-DlQwpXOL.js → TasksListPage-DEQymbXC.js} +2 -2
  142. package/dashboard/dist/assets/{TasksListPage-DlQwpXOL.js.map → TasksListPage-DEQymbXC.js.map} +1 -1
  143. package/dashboard/dist/assets/{TimeAgo-BinTjiVC.js → TimeAgo-CCxP67Rb.js} +2 -2
  144. package/dashboard/dist/assets/{TimeAgo-BinTjiVC.js.map → TimeAgo-CCxP67Rb.js.map} +1 -1
  145. package/dashboard/dist/assets/{TimestampCell-DDcfSVeQ.js → TimestampCell-CE5ZUYhV.js} +2 -2
  146. package/dashboard/dist/assets/{TimestampCell-DDcfSVeQ.js.map → TimestampCell-CE5ZUYhV.js.map} +1 -1
  147. package/dashboard/dist/assets/{ToolPill-C6hkOqPx.js → ToolPill-D-h8AoB8.js} +2 -2
  148. package/dashboard/dist/assets/{ToolPill-C6hkOqPx.js.map → ToolPill-D-h8AoB8.js.map} +1 -1
  149. package/dashboard/dist/assets/ToolTestPanel-BHuQTyBY.js +2 -0
  150. package/dashboard/dist/assets/{ToolTestPanel-CZvegH_w.js.map → ToolTestPanel-BHuQTyBY.js.map} +1 -1
  151. package/dashboard/dist/assets/{TopicDetailPage-CShiCwz8.js → TopicDetailPage-SFuiludx.js} +3 -3
  152. package/dashboard/dist/assets/{TopicDetailPage-CShiCwz8.js.map → TopicDetailPage-SFuiludx.js.map} +1 -1
  153. package/dashboard/dist/assets/{TopicsPage-CAYxRRbI.js → TopicsPage-DO8pUmng.js} +2 -2
  154. package/dashboard/dist/assets/{TopicsPage-CAYxRRbI.js.map → TopicsPage-DO8pUmng.js.map} +1 -1
  155. package/dashboard/dist/assets/UserName-CsLnT75E.js +2 -0
  156. package/dashboard/dist/assets/UserName-CsLnT75E.js.map +1 -0
  157. package/dashboard/dist/assets/{WorkflowExecutionPage-DNdXEFcB.js → WorkflowExecutionPage-BttnEvOd.js} +2 -2
  158. package/dashboard/dist/assets/{WorkflowExecutionPage-DNdXEFcB.js.map → WorkflowExecutionPage-BttnEvOd.js.map} +1 -1
  159. package/dashboard/dist/assets/{WorkflowPill-CN-952iI.js → WorkflowPill-CZvBbkn4.js} +2 -2
  160. package/dashboard/dist/assets/{WorkflowPill-CN-952iI.js.map → WorkflowPill-CZvBbkn4.js.map} +1 -1
  161. package/dashboard/dist/assets/{WorkflowsDashboard-D7DPDeuQ.js → WorkflowsDashboard-BRboBhlK.js} +2 -2
  162. package/dashboard/dist/assets/{WorkflowsDashboard-D7DPDeuQ.js.map → WorkflowsDashboard-BRboBhlK.js.map} +1 -1
  163. package/dashboard/dist/assets/{WorkflowsOverview-C7Z0SpDH.js → WorkflowsOverview-CDFTYeQo.js} +2 -2
  164. package/dashboard/dist/assets/{WorkflowsOverview-C7Z0SpDH.js.map → WorkflowsOverview-CDFTYeQo.js.map} +1 -1
  165. package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bxklswko.js → YamlWorkflowDetailPage-CmnZFevp.js} +11 -11
  166. package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bxklswko.js.map → YamlWorkflowDetailPage-CmnZFevp.js.map} +1 -1
  167. package/dashboard/dist/assets/{YamlWorkflowsPage-CkArsPbi.js → YamlWorkflowsPage-Qq1jfcrH.js} +2 -2
  168. package/dashboard/dist/assets/{YamlWorkflowsPage-CkArsPbi.js.map → YamlWorkflowsPage-Qq1jfcrH.js.map} +1 -1
  169. package/dashboard/dist/assets/{agents-Ca0hOp2Y.js → agents-C7vUIO7e.js} +2 -2
  170. package/dashboard/dist/assets/{agents-Ca0hOp2Y.js.map → agents-C7vUIO7e.js.map} +1 -1
  171. package/dashboard/dist/assets/{bots-9GtbGtH9.js → bots-BaOblnW4.js} +2 -2
  172. package/dashboard/dist/assets/{bots-9GtbGtH9.js.map → bots-BaOblnW4.js.map} +1 -1
  173. package/dashboard/dist/assets/{capabilities-C0RpEvyY.js → capabilities-RZpmDWoy.js} +2 -2
  174. package/dashboard/dist/assets/{capabilities-C0RpEvyY.js.map → capabilities-RZpmDWoy.js.map} +1 -1
  175. package/dashboard/dist/assets/{controlplane-ClQiKGyQ.js → controlplane-BwGdcwXR.js} +2 -2
  176. package/dashboard/dist/assets/{controlplane-ClQiKGyQ.js.map → controlplane-BwGdcwXR.js.map} +1 -1
  177. package/dashboard/dist/assets/escalation-columns-B4DG_5v8.js +2 -0
  178. package/dashboard/dist/assets/escalation-columns-B4DG_5v8.js.map +1 -0
  179. package/dashboard/dist/assets/index-8LIpvj2e.js +3 -0
  180. package/dashboard/dist/assets/index-8LIpvj2e.js.map +1 -0
  181. package/dashboard/dist/assets/index-BICLHYJJ.js +2 -0
  182. package/dashboard/dist/assets/{index-BLLK2mzy.js.map → index-BICLHYJJ.js.map} +1 -1
  183. package/dashboard/dist/assets/{index-BLCFN3YR.js → index-BQPUlmmz.js} +2 -2
  184. package/dashboard/dist/assets/{index-BLCFN3YR.js.map → index-BQPUlmmz.js.map} +1 -1
  185. package/dashboard/dist/assets/index-BaC3sbJH.js +67 -0
  186. package/dashboard/dist/assets/index-BaC3sbJH.js.map +1 -0
  187. package/dashboard/dist/assets/index-BuqpU6ow.js +2 -0
  188. package/dashboard/dist/assets/{index-DEr9QjVv.js.map → index-BuqpU6ow.js.map} +1 -1
  189. package/dashboard/dist/assets/index-CD6jtND1.css +1 -0
  190. package/dashboard/dist/assets/{index-Ba3UsZzz.js → index-CsIw-ZLk.js} +4 -4
  191. package/dashboard/dist/assets/{index-Ba3UsZzz.js.map → index-CsIw-ZLk.js.map} +1 -1
  192. package/dashboard/dist/assets/index-CtJkvX9e.js +5 -0
  193. package/dashboard/dist/assets/{index-Ct_8Tpec.js.map → index-CtJkvX9e.js.map} +1 -1
  194. package/dashboard/dist/assets/index-D9NtDRtp.js +2 -0
  195. package/dashboard/dist/assets/{index-BoQirCxt.js.map → index-D9NtDRtp.js.map} +1 -1
  196. package/dashboard/dist/assets/{index-CeHOGQM4.js → index-DU9-flEo.js} +2 -2
  197. package/dashboard/dist/assets/{index-CeHOGQM4.js.map → index-DU9-flEo.js.map} +1 -1
  198. package/dashboard/dist/assets/{index-P1g8r09L.js → index-G3NmdMjt.js} +2 -2
  199. package/dashboard/dist/assets/{index-P1g8r09L.js.map → index-G3NmdMjt.js.map} +1 -1
  200. package/dashboard/dist/assets/index-IWufkcH9.js +2 -0
  201. package/dashboard/dist/assets/{index-DlzW75L4.js.map → index-IWufkcH9.js.map} +1 -1
  202. package/dashboard/dist/assets/index-iegzyfMv.js +2 -0
  203. package/dashboard/dist/assets/{index-B-Qpr29t.js.map → index-iegzyfMv.js.map} +1 -1
  204. package/dashboard/dist/assets/index-xNFTmmsv.js +2 -0
  205. package/dashboard/dist/assets/{index-CG8oLNHo.js.map → index-xNFTmmsv.js.map} +1 -1
  206. package/dashboard/dist/assets/{knowledge-DzueDGkw.js → knowledge-CjwCbm3E.js} +2 -2
  207. package/dashboard/dist/assets/{knowledge-DzueDGkw.js.map → knowledge-CjwCbm3E.js.map} +1 -1
  208. package/dashboard/dist/assets/{mcp-CCOzN8dH.js → mcp-C-DMktCT.js} +2 -2
  209. package/dashboard/dist/assets/{mcp-CCOzN8dH.js.map → mcp-C-DMktCT.js.map} +1 -1
  210. package/dashboard/dist/assets/{mcp-query-UeMDiM6I.js → mcp-query-KBeBNHb4.js} +2 -2
  211. package/dashboard/dist/assets/{mcp-query-UeMDiM6I.js.map → mcp-query-KBeBNHb4.js.map} +1 -1
  212. package/dashboard/dist/assets/{personas-CKcrzaQX.js → personas-B32sSCxm.js} +2 -2
  213. package/dashboard/dist/assets/{personas-CKcrzaQX.js.map → personas-B32sSCxm.js.map} +1 -1
  214. package/dashboard/dist/assets/{pipelines-orE-1gpj.js → pipelines-DNLwDumm.js} +2 -2
  215. package/dashboard/dist/assets/{pipelines-orE-1gpj.js.map → pipelines-DNLwDumm.js.map} +1 -1
  216. package/dashboard/dist/assets/{tasks-ZOmzxynB.js → tasks-DrWSrjmj.js} +2 -2
  217. package/dashboard/dist/assets/{tasks-ZOmzxynB.js.map → tasks-DrWSrjmj.js.map} +1 -1
  218. package/dashboard/dist/assets/{topics-CdYaOCi_.js → topics-DgzGU-vR.js} +2 -2
  219. package/dashboard/dist/assets/{topics-CdYaOCi_.js.map → topics-DgzGU-vR.js.map} +1 -1
  220. package/dashboard/dist/assets/{useDebouncedValue-D-uGnSj5.js → useDebouncedValue-DG5JOj1F.js} +2 -2
  221. package/dashboard/dist/assets/{useDebouncedValue-D-uGnSj5.js.map → useDebouncedValue-DG5JOj1F.js.map} +1 -1
  222. package/dashboard/dist/assets/{useNamespace-DMrjlrJJ.js → useNamespace-CdgF7Jxo.js} +2 -2
  223. package/dashboard/dist/assets/{useNamespace-DMrjlrJJ.js.map → useNamespace-CdgF7Jxo.js.map} +1 -1
  224. package/dashboard/dist/assets/{useYamlActivityEvents-ByyQgu8S.js → useYamlActivityEvents-BEaOtgSZ.js} +2 -2
  225. package/dashboard/dist/assets/{useYamlActivityEvents-ByyQgu8S.js.map → useYamlActivityEvents-BEaOtgSZ.js.map} +1 -1
  226. package/dashboard/dist/assets/{vendor-icons-h-jlRSb9.js → vendor-icons-BCWsItQ7.js} +138 -133
  227. package/dashboard/dist/assets/{vendor-icons-h-jlRSb9.js.map → vendor-icons-BCWsItQ7.js.map} +1 -1
  228. package/dashboard/dist/assets/{workflows-CLn05FRV.js → workflows-CX_oarls.js} +2 -2
  229. package/dashboard/dist/assets/{workflows-CLn05FRV.js.map → workflows-CX_oarls.js.map} +1 -1
  230. package/dashboard/dist/assets/{yaml-workflows-DFD_HO__.js → yaml-workflows-YASUDPo0.js} +2 -2
  231. package/dashboard/dist/assets/{yaml-workflows-DFD_HO__.js.map → yaml-workflows-YASUDPo0.js.map} +1 -1
  232. package/dashboard/dist/index.html +3 -3
  233. package/docs/dashboard.md +164 -69
  234. package/docs/faceted-routing.md +38 -0
  235. package/docs/hitl/x-lt-list-schema.md +3 -0
  236. package/docs/operations.md +61 -60
  237. package/docs/scan-codes.md +18 -0
  238. package/package.json +1 -1
  239. package/dashboard/dist/assets/AvailableEscalationsPage-CjcAIm-d.js +0 -2
  240. package/dashboard/dist/assets/AvailableEscalationsPage-CjcAIm-d.js.map +0 -1
  241. package/dashboard/dist/assets/BulkAssignModal-DNmji4l-.js +0 -2
  242. package/dashboard/dist/assets/BulkAssignModal-DNmji4l-.js.map +0 -1
  243. package/dashboard/dist/assets/CapabilitiesPage-Cewd6Uku.js +0 -2
  244. package/dashboard/dist/assets/CountdownTimer-BKMmFie6.js +0 -2
  245. package/dashboard/dist/assets/CountdownTimer-BKMmFie6.js.map +0 -1
  246. package/dashboard/dist/assets/CredentialsPage-CwtkQUG3.js +0 -2
  247. package/dashboard/dist/assets/EntityTimelinePanel-DzMuV7ZX.js +0 -2
  248. package/dashboard/dist/assets/EntityTimelinePanel-DzMuV7ZX.js.map +0 -1
  249. package/dashboard/dist/assets/GraphInvokePage-LACIxaiH.js +0 -2
  250. package/dashboard/dist/assets/HomePage-DP9Q-x4a.js +0 -2
  251. package/dashboard/dist/assets/HomePage-DP9Q-x4a.js.map +0 -1
  252. package/dashboard/dist/assets/McpQueryPage-Ck75ikSd.js +0 -2
  253. package/dashboard/dist/assets/Modal-BU7No9NH.js +0 -2
  254. package/dashboard/dist/assets/Modal-BU7No9NH.js.map +0 -1
  255. package/dashboard/dist/assets/NamespacePill-DONj6eUU.js +0 -2
  256. package/dashboard/dist/assets/OperationsPage-MlMzl0Bf.js +0 -3
  257. package/dashboard/dist/assets/OperationsPage-MlMzl0Bf.js.map +0 -1
  258. package/dashboard/dist/assets/OperatorDashboard-DZOfHRE7.js +0 -2
  259. package/dashboard/dist/assets/PersonaDetailPage-eyHDupL0.js +0 -2
  260. package/dashboard/dist/assets/PersonasPage-CCtRkWiP.js +0 -2
  261. package/dashboard/dist/assets/PersonasPage-CCtRkWiP.js.map +0 -1
  262. package/dashboard/dist/assets/RoleDetailPage-BOccQCkx.js +0 -8
  263. package/dashboard/dist/assets/RoleDetailPage-BOccQCkx.js.map +0 -1
  264. package/dashboard/dist/assets/RolesPage-B3-9Ek0F.js +0 -2
  265. package/dashboard/dist/assets/ScanSchemeDetailPage-BnivPUJY.js +0 -2
  266. package/dashboard/dist/assets/ScanStationPage-DCSA7pcV.js +0 -2
  267. package/dashboard/dist/assets/ToolTestPanel-CZvegH_w.js +0 -2
  268. package/dashboard/dist/assets/UserName-BovQxdSY.js +0 -2
  269. package/dashboard/dist/assets/UserName-BovQxdSY.js.map +0 -1
  270. package/dashboard/dist/assets/escalation-columns-CZ12_oQF.js +0 -2
  271. package/dashboard/dist/assets/escalation-columns-CZ12_oQF.js.map +0 -1
  272. package/dashboard/dist/assets/index-B-Qpr29t.js +0 -2
  273. package/dashboard/dist/assets/index-BLLK2mzy.js +0 -2
  274. package/dashboard/dist/assets/index-BoQirCxt.js +0 -2
  275. package/dashboard/dist/assets/index-C1oOLjdF.js +0 -3
  276. package/dashboard/dist/assets/index-C1oOLjdF.js.map +0 -1
  277. package/dashboard/dist/assets/index-CG8oLNHo.js +0 -2
  278. package/dashboard/dist/assets/index-CdavRyaX.js +0 -67
  279. package/dashboard/dist/assets/index-CdavRyaX.js.map +0 -1
  280. package/dashboard/dist/assets/index-Ct_8Tpec.js +0 -5
  281. package/dashboard/dist/assets/index-DEr9QjVv.js +0 -2
  282. package/dashboard/dist/assets/index-DlzW75L4.js +0 -2
  283. package/dashboard/dist/assets/index-fUqvRHOe.css +0 -1
@@ -54,6 +54,19 @@ export declare function listFacetKeys(opts: {
54
54
  selfRoles?: string[];
55
55
  meUserId?: string;
56
56
  }): Promise<string[]>;
57
+ /**
58
+ * Distinct VALUES for one metadata facet key, visible to the caller — feeds
59
+ * the value picker (e.g. facility → [north, south]) so a bound scope offers
60
+ * real choices. Role-scoped with the same predicate as {@link listFacetKeys};
61
+ * the key is parameterized (never interpolated) and charset-validated, so a
62
+ * malformed key returns [] rather than reaching an unguarded path.
63
+ */
64
+ export declare function listFacetValues(key: string, opts: {
65
+ global?: boolean;
66
+ visibleRoles?: string[];
67
+ selfRoles?: string[];
68
+ meUserId?: string;
69
+ }): Promise<string[]>;
57
70
  export declare function getEscalationStats(visibleRoles?: string[], period?: string): Promise<EscalationStats>;
58
71
  export declare function listDistinctTypes(): Promise<string[]>;
59
72
  export declare function listEscalations(filters: {
@@ -108,4 +121,4 @@ export declare function listAvailableEscalations(filters: {
108
121
  export declare function invalidateEscalationAggregates(): void;
109
122
  /** Test hook: drop cached period metrics AND escalation stats so tests observe fresh rows. */
110
123
  export declare function resetStationMetricsCache(): void;
111
- export declare function getStationMetrics(visibleRoles: string[] | undefined, period?: string): Promise<StationMetric[]>;
124
+ export declare function getStationMetrics(visibleRoles: string[] | undefined, period?: string, facets?: Record<string, unknown>): Promise<StationMetric[]>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.searchEscalationsFaceted = searchEscalationsFaceted;
4
4
  exports.getEscalationWithFormSchema = getEscalationWithFormSchema;
5
5
  exports.listFacetKeys = listFacetKeys;
6
+ exports.listFacetValues = listFacetValues;
6
7
  exports.getEscalationStats = getEscalationStats;
7
8
  exports.listDistinctTypes = listDistinctTypes;
8
9
  exports.listEscalations = listEscalations;
@@ -42,13 +43,13 @@ function buildSearchOrderBy(sortBy, order) {
42
43
  }
43
44
  return 'priority ASC, created_at ASC';
44
45
  }
45
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
46
+ const uuid_1 = require("../../lib/uuid");
46
47
  /**
47
48
  * The id arm of the correlation search binds a uuid param only when the term
48
49
  * parses as one — comparing `id::text = $term` casts the uuid column and has
49
50
  * no index path, which drags the whole OR into a full history scan.
50
51
  */
51
- const asUuidOrNull = (term) => term && UUID_RE.test(term) ? term : null;
52
+ const asUuidOrNull = (term) => term && uuid_1.UUID_RE.test(term) ? term : null;
52
53
  /**
53
54
  * Server-side free-text search over the `lt_escalations` view. Runs when a
54
55
  * caller supplies a non-empty `search` term — the SDK `client.list()` cannot do
@@ -155,6 +156,9 @@ async function searchEscalationsFaceted(opts) {
155
156
  * cast is guarded, so a non-numeric pin simply falls through to the latest form.
156
157
  */
157
158
  async function getEscalationWithFormSchema(id) {
159
+ // A non-UUID is definitionally not-found — it must never reach the uuid cast.
160
+ if (!(0, uuid_1.isUuid)(id))
161
+ return null;
158
162
  await (0, client_1.ensureEscalationCompatView)();
159
163
  const pool = (0, db_1.getPool)();
160
164
  const { rows } = await pool.query(`SELECT e.*, COALESCE(ps.form_schema, r.form_schema) AS resolved_form_schema
@@ -216,6 +220,39 @@ async function listFacetKeys(opts) {
216
220
  }
217
221
  return [...actualKeys].sort();
218
222
  }
223
+ // Mirrors the server-side facet key rule — metadata keys become a JSON path,
224
+ // so a lookup key is strictly validated before it reaches SQL.
225
+ const FACET_KEY_RE = /^[a-zA-Z0-9_]+$/;
226
+ /**
227
+ * Distinct VALUES for one metadata facet key, visible to the caller — feeds
228
+ * the value picker (e.g. facility → [north, south]) so a bound scope offers
229
+ * real choices. Role-scoped with the same predicate as {@link listFacetKeys};
230
+ * the key is parameterized (never interpolated) and charset-validated, so a
231
+ * malformed key returns [] rather than reaching an unguarded path.
232
+ */
233
+ async function listFacetValues(key, opts) {
234
+ if (!FACET_KEY_RE.test(key))
235
+ return [];
236
+ await (0, client_1.ensureEscalationCompatView)();
237
+ const pool = (0, db_1.getPool)();
238
+ const params = [key];
239
+ const clauses = ["jsonb_typeof(metadata) = 'object'", 'metadata ? $1'];
240
+ if (!opts.global) {
241
+ const ai = params.push(opts.visibleRoles?.length ? opts.visibleRoles : null);
242
+ const si = params.push(opts.selfRoles?.length ? opts.selfRoles : null);
243
+ const mi = params.push(opts.meUserId || null);
244
+ clauses.push(`(($${ai}::text[] IS NULL AND $${si}::text[] IS NULL)
245
+ OR ($${ai}::text[] IS NOT NULL AND role = ANY($${ai}))
246
+ OR ($${si}::text[] IS NOT NULL AND role = ANY($${si}) AND assigned_to = $${mi}))`);
247
+ }
248
+ const { rows } = await pool.query(`SELECT DISTINCT metadata->>$1 AS value
249
+ FROM public.lt_escalations
250
+ WHERE ${clauses.join('\n AND ')}
251
+ AND metadata->>$1 IS NOT NULL
252
+ ORDER BY value
253
+ LIMIT 200`, params);
254
+ return rows.map((r) => r.value);
255
+ }
219
256
  // Escalation stats back the home + overview surfaces and refresh on every
220
257
  // escalation event. As of hotmesh 0.25.0 the SDK stats query reads three
221
258
  // bounded, index-only sources (pending backlog, created window, resolved
@@ -388,7 +425,7 @@ function resetStationMetricsCache() {
388
425
  invalidateEscalationAggregates();
389
426
  }
390
427
  const toNum = (v) => (v != null ? Number(v) : null);
391
- async function getStationMetrics(visibleRoles, period) {
428
+ async function getStationMetrics(visibleRoles, period, facets) {
392
429
  await (0, client_1.ensureEscalationCompatView)();
393
430
  // hasOwnProperty: a caller-supplied period like 'constructor' must fall back
394
431
  // to 24h, not resolve to an inherited Object.prototype member.
@@ -396,13 +433,18 @@ async function getStationMetrics(visibleRoles, period) {
396
433
  ? types_1.VALID_PERIODS[period]
397
434
  : '24 hours';
398
435
  const roles = visibleRoles ?? null;
436
+ // Facet scope rides both queries as a GIN-served `metadata @>` arm; null = no
437
+ // filter. It never widens role scope — it only narrows within it.
438
+ const facetJson = facets && Object.keys(facets).length ? JSON.stringify(facets) : null;
399
439
  const pool = (0, db_1.getPool)();
400
440
  // Live counts: always fresh (cheap — bounded pending working set).
401
- // Period metrics: cached ~30s, single-flight (expensive percentile sort).
402
- const cacheKey = `${period ?? '24h'}::${roles ? [...roles].sort().join(',') : 'ALL'}`;
441
+ // Period metrics: cached ~30s, single-flight (expensive percentile sort). The
442
+ // facet scope is part of the cache key — a scoped board never reads unscoped
443
+ // rows, and vice versa.
444
+ const cacheKey = `${period ?? '24h'}::${roles ? [...roles].sort().join(',') : 'ALL'}::${facetJson ?? ''}`;
403
445
  const [countsResult, periodRows] = await Promise.all([
404
- pool.query(sql_1.STATION_LIVE_COUNTS_SQL, [roles]),
405
- stationPeriodCache.resolve(cacheKey, async () => (await pool.query(sql_1.STATION_PERIOD_METRICS_SQL, [roles, intervalStr])).rows),
446
+ pool.query(sql_1.STATION_LIVE_COUNTS_SQL, [roles, facetJson]),
447
+ stationPeriodCache.resolve(cacheKey, async () => (await pool.query(sql_1.STATION_PERIOD_METRICS_SQL, [roles, intervalStr, facetJson])).rows),
406
448
  ]);
407
449
  const periodByRole = new Map(periodRows.map((r) => [r.role, r]));
408
450
  return countsResult.rows.map((c) => {
@@ -80,6 +80,7 @@ export declare const RESOLVE_BY_METADATA_ATOMIC = "WITH target AS MATERIALIZED (
80
80
  * Live counts — pending, active claims, and priority (past-threshold) per role.
81
81
  *
82
82
  * $1 TEXT[] — role filter (NULL = all roles / superadmin).
83
+ * $2 JSONB — metadata facet scope (NULL = no filter; `metadata @>`, GIN-served).
83
84
  *
84
85
  * Hits idx_hmsh_esc_pending_role_created (role, created_at DESC) WHERE
85
86
  * status='pending'. The pending working set is bounded by real backlog depth,
@@ -102,13 +103,14 @@ export declare const RESOLVE_BY_METADATA_ATOMIC = "WITH target AS MATERIALIZED (
102
103
  * with a facet configured, items missing the key or holding an unparseable
103
104
  * value do not count (NULL age never passes the comparison).
104
105
  */
105
- export declare const STATION_LIVE_COUNTS_SQL = "\nWITH role_targets AS (\n SELECT role, sla_minutes, priority_threshold_minutes, priority_facet\n FROM lt_roles\n WHERE ($1::text[] IS NULL OR role = ANY($1::text[]))\n),\nlive_counts AS (\n SELECT\n e.role,\n COUNT(*) AS pending,\n COUNT(*) FILTER (WHERE e.assigned_to IS NOT NULL AND e.assigned_until > NOW()) AS claimed,\n COUNT(*) FILTER (\n WHERE COALESCE(rt.priority_threshold_minutes, rt.sla_minutes) IS NOT NULL\n AND (e.assigned_to IS NULL OR e.assigned_until IS NULL OR e.assigned_until <= NOW())\n AND CASE\n WHEN rt.priority_facet IS NULL THEN e.created_at\n WHEN pg_input_is_valid(e.metadata->>rt.priority_facet, 'timestamptz')\n THEN (e.metadata->>rt.priority_facet)::timestamptz\n ELSE NULL\n END < NOW() - (COALESCE(rt.priority_threshold_minutes, rt.sla_minutes) * INTERVAL '1 minute')\n ) AS priority_count\n FROM hmsh_escalations e\n JOIN role_targets rt ON rt.role = e.role\n WHERE e.status = 'pending'\n GROUP BY e.role\n)\nSELECT\n rt.role,\n COALESCE(lc.pending, 0) AS pending,\n COALESCE(lc.claimed, 0) AS claimed,\n COALESCE(lc.priority_count, 0) AS priority_count\nFROM role_targets rt\nLEFT JOIN live_counts lc ON lc.role = rt.role\nORDER BY rt.role\n";
106
+ export declare const STATION_LIVE_COUNTS_SQL = "\nWITH role_targets AS (\n SELECT role, sla_minutes, priority_threshold_minutes, priority_facet\n FROM lt_roles\n WHERE ($1::text[] IS NULL OR role = ANY($1::text[]))\n),\nlive_counts AS (\n SELECT\n e.role,\n COUNT(*) AS pending,\n COUNT(*) FILTER (WHERE e.assigned_to IS NOT NULL AND e.assigned_until > NOW()) AS claimed,\n COUNT(*) FILTER (\n WHERE COALESCE(rt.priority_threshold_minutes, rt.sla_minutes) IS NOT NULL\n AND (e.assigned_to IS NULL OR e.assigned_until IS NULL OR e.assigned_until <= NOW())\n AND CASE\n WHEN rt.priority_facet IS NULL THEN e.created_at\n WHEN pg_input_is_valid(e.metadata->>rt.priority_facet, 'timestamptz')\n THEN (e.metadata->>rt.priority_facet)::timestamptz\n ELSE NULL\n END < NOW() - (COALESCE(rt.priority_threshold_minutes, rt.sla_minutes) * INTERVAL '1 minute')\n ) AS priority_count\n FROM hmsh_escalations e\n JOIN role_targets rt ON rt.role = e.role\n WHERE e.status = 'pending'\n AND ($2::jsonb IS NULL OR e.metadata @> $2::jsonb)\n GROUP BY e.role\n)\nSELECT\n rt.role,\n COALESCE(lc.pending, 0) AS pending,\n COALESCE(lc.claimed, 0) AS claimed,\n COALESCE(lc.priority_count, 0) AS priority_count\nFROM role_targets rt\nLEFT JOIN live_counts lc ON lc.role = rt.role\nORDER BY rt.role\n";
106
107
  /**
107
108
  * Period metrics — resolved count, throughput efficiency, and P99/P50/avg/max
108
109
  * latency for wait (queue time) and work (processing) per role.
109
110
  *
110
111
  * $1 TEXT[] — role filter (NULL = all roles / superadmin).
111
112
  * $2 INTERVAL — time window (e.g. '24 hours', '15 minutes').
113
+ * $3 JSONB — metadata facet scope (NULL = no filter; `metadata @>`, GIN-served).
112
114
  *
113
115
  * Hits idx_hmsh_esc_resolved_cover (role, resolved_at DESC, claimed_at,
114
116
  * created_at) WHERE status='resolved' — a covering index, so the percentile
@@ -119,4 +121,4 @@ export declare const STATION_LIVE_COUNTS_SQL = "\nWITH role_targets AS (\n SELE
119
121
  * mix of resolved + in-flight — a clean "how did completed work perform?" read.
120
122
  * throughput_pct is NULL when the role has no target_per_hour (NULLIF guard).
121
123
  */
122
- export declare const STATION_PERIOD_METRICS_SQL = "\nWITH role_targets AS (\n SELECT role, target_per_hour\n FROM lt_roles\n WHERE ($1::text[] IS NULL OR role = ANY($1::text[]))\n),\nperiod_metrics AS (\n SELECT\n e.role,\n COUNT(*) AS resolved,\n ROUND((PERCENTILE_CONT(0.99) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p99_wait_min,\n ROUND((PERCENTILE_CONT(0.50) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p50_wait_min,\n ROUND((AVG(EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS avg_wait_min,\n ROUND((MAX(EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS max_wait_min,\n ROUND((PERCENTILE_CONT(0.99) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p99_work_min,\n ROUND((PERCENTILE_CONT(0.50) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p50_work_min,\n ROUND((AVG(EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS avg_work_min,\n ROUND((MAX(EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS max_work_min\n FROM hmsh_escalations e\n WHERE e.status = 'resolved'\n AND e.resolved_at >= NOW() - $2::interval\n AND ($1::text[] IS NULL OR e.role = ANY($1::text[]))\n GROUP BY e.role\n)\nSELECT\n rt.role,\n COALESCE(pm.resolved, 0) AS resolved,\n ROUND(\n (COALESCE(pm.resolved, 0)::numeric\n / NULLIF(rt.target_per_hour::numeric * EXTRACT(EPOCH FROM $2::interval) / 3600.0, 0)\n * 100\n )::numeric, 1\n ) AS throughput_pct,\n pm.p99_wait_min,\n pm.p50_wait_min,\n pm.avg_wait_min,\n pm.max_wait_min,\n pm.p99_work_min,\n pm.p50_work_min,\n pm.avg_work_min,\n pm.max_work_min\nFROM role_targets rt\nLEFT JOIN period_metrics pm ON pm.role = rt.role\nORDER BY rt.role\n";
124
+ export declare const STATION_PERIOD_METRICS_SQL = "\nWITH role_targets AS (\n SELECT role, target_per_hour\n FROM lt_roles\n WHERE ($1::text[] IS NULL OR role = ANY($1::text[]))\n),\nperiod_metrics AS (\n SELECT\n e.role,\n COUNT(*) AS resolved,\n ROUND((PERCENTILE_CONT(0.99) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p99_wait_min,\n ROUND((PERCENTILE_CONT(0.50) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p50_wait_min,\n ROUND((AVG(EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS avg_wait_min,\n ROUND((MAX(EXTRACT(EPOCH FROM (e.claimed_at - e.created_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS max_wait_min,\n ROUND((PERCENTILE_CONT(0.99) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p99_work_min,\n ROUND((PERCENTILE_CONT(0.50) WITHIN GROUP (\n ORDER BY EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS p50_work_min,\n ROUND((AVG(EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS avg_work_min,\n ROUND((MAX(EXTRACT(EPOCH FROM (e.resolved_at - e.claimed_at)) / 60)\n FILTER (WHERE e.claimed_at IS NOT NULL))::numeric, 3) AS max_work_min\n FROM hmsh_escalations e\n WHERE e.status = 'resolved'\n AND e.resolved_at >= NOW() - $2::interval\n AND ($1::text[] IS NULL OR e.role = ANY($1::text[]))\n AND ($3::jsonb IS NULL OR e.metadata @> $3::jsonb)\n GROUP BY e.role\n)\nSELECT\n rt.role,\n COALESCE(pm.resolved, 0) AS resolved,\n ROUND(\n (COALESCE(pm.resolved, 0)::numeric\n / NULLIF(rt.target_per_hour::numeric * EXTRACT(EPOCH FROM $2::interval) / 3600.0, 0)\n * 100\n )::numeric, 1\n ) AS throughput_pct,\n pm.p99_wait_min,\n pm.p50_wait_min,\n pm.avg_wait_min,\n pm.max_wait_min,\n pm.p99_work_min,\n pm.p50_work_min,\n pm.avg_work_min,\n pm.max_work_min\nFROM role_targets rt\nLEFT JOIN period_metrics pm ON pm.role = rt.role\nORDER BY rt.role\n";
@@ -270,6 +270,7 @@ LEFT JOIN written ON written.id = target.id`;
270
270
  * Live counts — pending, active claims, and priority (past-threshold) per role.
271
271
  *
272
272
  * $1 TEXT[] — role filter (NULL = all roles / superadmin).
273
+ * $2 JSONB — metadata facet scope (NULL = no filter; `metadata @>`, GIN-served).
273
274
  *
274
275
  * Hits idx_hmsh_esc_pending_role_created (role, created_at DESC) WHERE
275
276
  * status='pending'. The pending working set is bounded by real backlog depth,
@@ -316,6 +317,7 @@ live_counts AS (
316
317
  FROM hmsh_escalations e
317
318
  JOIN role_targets rt ON rt.role = e.role
318
319
  WHERE e.status = 'pending'
320
+ AND ($2::jsonb IS NULL OR e.metadata @> $2::jsonb)
319
321
  GROUP BY e.role
320
322
  )
321
323
  SELECT
@@ -333,6 +335,7 @@ ORDER BY rt.role
333
335
  *
334
336
  * $1 TEXT[] — role filter (NULL = all roles / superadmin).
335
337
  * $2 INTERVAL — time window (e.g. '24 hours', '15 minutes').
338
+ * $3 JSONB — metadata facet scope (NULL = no filter; `metadata @>`, GIN-served).
336
339
  *
337
340
  * Hits idx_hmsh_esc_resolved_cover (role, resolved_at DESC, claimed_at,
338
341
  * created_at) WHERE status='resolved' — a covering index, so the percentile
@@ -377,6 +380,7 @@ period_metrics AS (
377
380
  WHERE e.status = 'resolved'
378
381
  AND e.resolved_at >= NOW() - $2::interval
379
382
  AND ($1::text[] IS NULL OR e.role = ANY($1::text[]))
383
+ AND ($3::jsonb IS NULL OR e.metadata @> $3::jsonb)
380
384
  GROUP BY e.role
381
385
  )
382
386
  SELECT
@@ -15,13 +15,31 @@ export interface EscalationChain {
15
15
  * MEMBER of exactly this one role, the dashboard drops the left nav, the
16
16
  * role's escalation list becomes home, and navigation is held to the list,
17
17
  * the detail page, and the scan screens. Multi-role users get full chrome.
18
+ * - LINK_VARIABLES: RoleLinkVariable[] — metadata facet names members bind
19
+ * per device. Pins may reference `{lt:name}` as a whole facet value
20
+ * (e.g. `facets={"region":"{lt:region}"}`); the dashboard substitutes
21
+ * the device's bound value at render time, falls back to the declared
22
+ * default, and drops the facet entirely when both are unset.
18
23
  */
19
24
  export declare const ROLE_PROPERTY_KEYS: {
20
25
  readonly ON_CANCEL: "on_cancel";
21
26
  readonly ON_TIMEOUT: "on_timeout";
22
27
  readonly WORKED_BY: "worked_by";
23
28
  readonly KIOSK: "kiosk";
29
+ readonly LINK_VARIABLES: "link_variables";
24
30
  };
31
+ /**
32
+ * One link-variable declaration inside `properties.link_variables`. `name` is
33
+ * the metadata facet key (facet-key charset); `label` is the human annotation
34
+ * the binding modal shows; `default` is the value used when the device has no
35
+ * binding. Values are strings — jsonb containment is type-sensitive, so
36
+ * numeric facets are not templatable.
37
+ */
38
+ export interface RoleLinkVariable {
39
+ name: string;
40
+ label?: string;
41
+ default?: string;
42
+ }
25
43
  export interface RoleDetail {
26
44
  role: string;
27
45
  title: string | null;
@@ -14,10 +14,16 @@ exports.ROLE_PROPERTY_KEYS = void 0;
14
14
  * MEMBER of exactly this one role, the dashboard drops the left nav, the
15
15
  * role's escalation list becomes home, and navigation is held to the list,
16
16
  * the detail page, and the scan screens. Multi-role users get full chrome.
17
+ * - LINK_VARIABLES: RoleLinkVariable[] — metadata facet names members bind
18
+ * per device. Pins may reference `{lt:name}` as a whole facet value
19
+ * (e.g. `facets={"region":"{lt:region}"}`); the dashboard substitutes
20
+ * the device's bound value at render time, falls back to the declared
21
+ * default, and drops the facet entirely when both are unset.
17
22
  */
18
23
  exports.ROLE_PROPERTY_KEYS = {
19
24
  ON_CANCEL: 'on_cancel',
20
25
  ON_TIMEOUT: 'on_timeout',
21
26
  WORKED_BY: 'worked_by',
22
27
  KIOSK: 'kiosk',
28
+ LINK_VARIABLES: 'link_variables',
23
29
  };
@@ -16,11 +16,11 @@ const sql_1 = require("./sql");
16
16
  */
17
17
  async function verifyPassword(externalId, password) {
18
18
  const pool = (0, db_1.getPool)();
19
- const { rows } = await pool.query(sql_1.GET_USER_BY_EXTERNAL_ID, [externalId]);
19
+ const { rows } = await pool.query(sql_1.GET_USER_FOR_AUTH, [externalId]);
20
20
  if (!rows[0] || !rows[0].password_hash)
21
21
  return null;
22
22
  const valid = await bcryptjs_1.default.compare(password, rows[0].password_hash);
23
23
  if (!valid)
24
24
  return null;
25
- return (0, crud_1.attachRoles)(rows[0]);
25
+ return (0, crud_1.getUser)(rows[0].id);
26
26
  }
@@ -3,6 +3,19 @@ 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>;
6
+ export interface UserNameRecord {
7
+ id: string;
8
+ display_name: string | null;
9
+ external_id: string;
10
+ email: string | null;
11
+ }
12
+ /**
13
+ * Resolve many user ids to display fields in one query. Non-UUID input is
14
+ * dropped before the SQL (a bad id is not-found, never a 500), ids are deduped,
15
+ * and the batch is capped. Returns display fields only — never secrets, scopes,
16
+ * or metadata.
17
+ */
18
+ export declare function getUserNames(ids: string[]): Promise<UserNameRecord[]>;
6
19
  export declare function getUserByExternalId(externalId: string): Promise<LTUserRecord | null>;
7
20
  export declare function getUserByEmail(email: string): Promise<LTUserRecord | null>;
8
21
  /**
@@ -7,6 +7,7 @@ exports.UserPropertyValidationError = exports.UserPropertyConflictError = void 0
7
7
  exports.attachRoles = attachRoles;
8
8
  exports.createUser = createUser;
9
9
  exports.getUser = getUser;
10
+ exports.getUserNames = getUserNames;
10
11
  exports.getUserByExternalId = getUserByExternalId;
11
12
  exports.getUserByEmail = getUserByEmail;
12
13
  exports.getUserByMetadataValue = getUserByMetadataValue;
@@ -18,14 +19,21 @@ exports.listUsers = listUsers;
18
19
  const bcryptjs_1 = __importDefault(require("bcryptjs"));
19
20
  const db_1 = require("../../lib/db");
20
21
  const sql_1 = require("./sql");
22
+ const uuid_1 = require("../../lib/uuid");
21
23
  const scan_code_1 = require("../scan-code");
22
24
  const scan_code_2 = require("../../types/scan-code");
23
25
  const scope_1 = require("./scope");
24
26
  // ─── Private helpers (exported for internal use by auth.ts) ──────────────────
27
+ /** Last-line guard: strip credentials before any user record leaves the service, so a stray SELECT * can never leak them. */
28
+ function stripUserSecrets(user) {
29
+ delete user.password_hash;
30
+ delete user.oauth_provider_id;
31
+ return user;
32
+ }
25
33
  async function attachRoles(user) {
26
34
  const pool = (0, db_1.getPool)();
27
35
  const { rows } = await pool.query(sql_1.GET_ROLES_BY_USER_ID, [user.id]);
28
- return { ...user, roles: rows };
36
+ return stripUserSecrets({ ...user, roles: rows });
29
37
  }
30
38
  async function attachRolesToMany(users) {
31
39
  if (users.length === 0)
@@ -45,7 +53,7 @@ async function attachRolesToMany(users) {
45
53
  });
46
54
  roleMap.set(row.user_id, list);
47
55
  }
48
- return users.map((u) => ({ ...u, roles: roleMap.get(u.id) || [] }));
56
+ return users.map((u) => stripUserSecrets({ ...u, roles: roleMap.get(u.id) || [] }));
49
57
  }
50
58
  // ─── CRUD ─────────────────────────────────────────────────────────────────────
51
59
  async function createUser(input) {
@@ -85,6 +93,21 @@ async function getUser(id) {
85
93
  return null;
86
94
  return attachRoles(rows[0]);
87
95
  }
96
+ const MAX_NAME_LOOKUP = 200;
97
+ /**
98
+ * Resolve many user ids to display fields in one query. Non-UUID input is
99
+ * dropped before the SQL (a bad id is not-found, never a 500), ids are deduped,
100
+ * and the batch is capped. Returns display fields only — never secrets, scopes,
101
+ * or metadata.
102
+ */
103
+ async function getUserNames(ids) {
104
+ const clean = [...new Set((0, uuid_1.onlyUuids)(ids))].slice(0, MAX_NAME_LOOKUP);
105
+ if (clean.length === 0)
106
+ return [];
107
+ const pool = (0, db_1.getPool)();
108
+ const { rows } = await pool.query(sql_1.GET_NAMES_BY_IDS, [clean]);
109
+ return rows;
110
+ }
88
111
  async function getUserByExternalId(externalId) {
89
112
  const pool = (0, db_1.getPool)();
90
113
  const { rows } = await pool.query(sql_1.GET_USER_BY_EXTERNAL_ID, [externalId]);
@@ -288,7 +311,7 @@ async function listUsers(filters) {
288
311
  const offset = filters.offset || 0;
289
312
  const [countResult, dataResult] = await Promise.all([
290
313
  pool.query(`SELECT COUNT(DISTINCT u.id) FROM lt_users u ${join} ${where}`, values),
291
- pool.query(`SELECT DISTINCT u.* FROM lt_users u ${join} ${where} ORDER BY u.created_at DESC LIMIT $${idx++} OFFSET $${idx++}`, [...values, limit, offset]),
314
+ pool.query(`SELECT DISTINCT ${sql_1.USER_SAFE_COLUMNS_U} FROM lt_users u ${join} ${where} ORDER BY u.created_at DESC LIMIT $${idx++} OFFSET $${idx++}`, [...values, limit, offset]),
292
315
  ]);
293
316
  return {
294
317
  users: await attachRolesToMany(dataResult.rows),
@@ -1,5 +1,5 @@
1
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';
2
+ export { createUser, getUser, getUserNames, UserNameRecord, 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,11 +1,12 @@
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.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;
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.getUserNames = 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");
7
7
  Object.defineProperty(exports, "createUser", { enumerable: true, get: function () { return crud_1.createUser; } });
8
8
  Object.defineProperty(exports, "getUser", { enumerable: true, get: function () { return crud_1.getUser; } });
9
+ Object.defineProperty(exports, "getUserNames", { enumerable: true, get: function () { return crud_1.getUserNames; } });
9
10
  Object.defineProperty(exports, "getUserByExternalId", { enumerable: true, get: function () { return crud_1.getUserByExternalId; } });
10
11
  Object.defineProperty(exports, "getUserByEmail", { enumerable: true, get: function () { return crud_1.getUserByEmail; } });
11
12
  Object.defineProperty(exports, "getUserByMetadataValue", { enumerable: true, get: function () { return crud_1.getUserByMetadataValue; } });
@@ -1,10 +1,14 @@
1
+ export declare const USER_SAFE_COLUMNS: string;
2
+ export declare const USER_SAFE_COLUMNS_U: string;
1
3
  /** Ensure a role name exists in the lt_roles lookup table (FK target). */
2
4
  export declare const ENSURE_ROLE_EXISTS = "INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING";
3
5
  /** Fetch roles for a single user, ordered by creation time. granted_by_persona
4
6
  * is the sustaining persona's key (null = direct grant). */
5
7
  export declare const GET_ROLES_BY_USER_ID = "SELECT r.role, r.type, r.read_scope, r.write_scope, r.created_at, p.key AS granted_by_persona\n FROM lt_user_roles r\n LEFT JOIN lt_personas p ON p.id = r.granted_by_persona\n WHERE r.user_id = $1 ORDER BY r.created_at";
6
8
  /** Fetch a user row by external_id. */
7
- export declare const GET_USER_BY_EXTERNAL_ID = "SELECT * FROM lt_users WHERE external_id = $1";
9
+ export declare const GET_USER_BY_EXTERNAL_ID: string;
10
+ /** Password-check only: the one query that reads the hash, never returned to a client. */
11
+ export declare const GET_USER_FOR_AUTH = "SELECT id, password_hash FROM lt_users WHERE external_id = $1";
8
12
  /**
9
13
  * Create a user and its role assignments in ONE atomic statement. The user row,
10
14
  * the lt_roles FK targets ($9 = role names), and the lt_user_roles links
@@ -15,9 +19,11 @@ export declare const GET_USER_BY_EXTERNAL_ID = "SELECT * FROM lt_users WHERE ext
15
19
  * end, after the sibling role-ensure CTE has run, so brand-new roles are valid
16
20
  * FK targets.
17
21
  */
18
- 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";
19
- export declare const GET_USER_BY_EMAIL = "SELECT * FROM lt_users WHERE email = $1 LIMIT 1";
20
- export declare const GET_USER_BY_ID = "SELECT * FROM lt_users WHERE id = $1";
22
+ export declare const CREATE_USER_WITH_ROLES: string;
23
+ export declare const GET_USER_BY_EMAIL: string;
24
+ export declare const GET_USER_BY_ID: string;
25
+ /** Thin identity projection for batch name resolution — display fields only, never secrets, scopes, or metadata. */
26
+ export declare const GET_NAMES_BY_IDS = "SELECT id, display_name, external_id, email FROM lt_users WHERE id = ANY($1)";
21
27
  /**
22
28
  * Lookup by a caller-named metadata key (e.g. a badge binding). LIMIT 2 so an
23
29
  * ambiguous binding — two users carrying the same value — is detectable and
@@ -25,7 +31,7 @@ export declare const GET_USER_BY_ID = "SELECT * FROM lt_users WHERE id = $1";
25
31
  * is the people table (small); revisit with a GIN index on metadata if a
26
32
  * deployment ever holds tens of thousands of users.
27
33
  */
28
- export declare const GET_USERS_BY_METADATA_VALUE = "SELECT * FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2";
34
+ export declare const GET_USERS_BY_METADATA_VALUE: string;
29
35
  /** Check if a user exists by id. Lightweight — returns only the id column. */
30
36
  export declare const VERIFY_USER_BY_ID = "SELECT id FROM lt_users WHERE id = $1 LIMIT 1";
31
37
  /**
@@ -1,7 +1,15 @@
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.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;
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_NAMES_BY_IDS = exports.GET_USER_BY_ID = exports.GET_USER_BY_EMAIL = exports.CREATE_USER_WITH_ROLES = exports.GET_USER_FOR_AUTH = exports.GET_USER_BY_EXTERNAL_ID = exports.GET_ROLES_BY_USER_ID = exports.ENSURE_ROLE_EXISTS = exports.USER_SAFE_COLUMNS_U = exports.USER_SAFE_COLUMNS = void 0;
5
+ /**
6
+ * Every lt_users column a client may see. password_hash and oauth_provider_id
7
+ * are credentials and never appear here — no user read path selects them (the
8
+ * password check has its own GET_USER_FOR_AUTH). `U` prefixes for a joined list.
9
+ */
10
+ const USER_COLS = ['id', 'external_id', 'email', 'display_name', 'status', 'account_type', 'oauth_provider', 'metadata', 'created_at', 'updated_at', 'preferences'];
11
+ exports.USER_SAFE_COLUMNS = USER_COLS.join(', ');
12
+ exports.USER_SAFE_COLUMNS_U = USER_COLS.map((c) => `u.${c}`).join(', ');
5
13
  /** Ensure a role name exists in the lt_roles lookup table (FK target). */
6
14
  exports.ENSURE_ROLE_EXISTS = 'INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING';
7
15
  /** Fetch roles for a single user, ordered by creation time. granted_by_persona
@@ -11,7 +19,9 @@ exports.GET_ROLES_BY_USER_ID = `SELECT r.role, r.type, r.read_scope, r.write_sco
11
19
  LEFT JOIN lt_personas p ON p.id = r.granted_by_persona
12
20
  WHERE r.user_id = $1 ORDER BY r.created_at`;
13
21
  /** Fetch a user row by external_id. */
14
- exports.GET_USER_BY_EXTERNAL_ID = 'SELECT * FROM lt_users WHERE external_id = $1';
22
+ exports.GET_USER_BY_EXTERNAL_ID = `SELECT ${exports.USER_SAFE_COLUMNS} FROM lt_users WHERE external_id = $1`;
23
+ /** Password-check only: the one query that reads the hash, never returned to a client. */
24
+ exports.GET_USER_FOR_AUTH = 'SELECT id, password_hash FROM lt_users WHERE external_id = $1';
15
25
  // ─── User CRUD ───────────────────────────────────────────────────────────────
16
26
  /**
17
27
  * Create a user and its role assignments in ONE atomic statement. The user row,
@@ -37,9 +47,11 @@ exports.CREATE_USER_WITH_ROLES = `WITH new_user AS (
37
47
  FROM unnest($9::text[], $10::text[], $11::text[], $12::text[]) AS x(role, type, read_scope, write_scope)
38
48
  ON CONFLICT DO NOTHING
39
49
  )
40
- SELECT * FROM new_user`;
41
- exports.GET_USER_BY_EMAIL = `SELECT * FROM lt_users WHERE email = $1 LIMIT 1`;
42
- exports.GET_USER_BY_ID = 'SELECT * FROM lt_users WHERE id = $1';
50
+ SELECT ${exports.USER_SAFE_COLUMNS} FROM new_user`;
51
+ exports.GET_USER_BY_EMAIL = `SELECT ${exports.USER_SAFE_COLUMNS} FROM lt_users WHERE email = $1 LIMIT 1`;
52
+ exports.GET_USER_BY_ID = `SELECT ${exports.USER_SAFE_COLUMNS} FROM lt_users WHERE id = $1`;
53
+ /** Thin identity projection for batch name resolution — display fields only, never secrets, scopes, or metadata. */
54
+ exports.GET_NAMES_BY_IDS = 'SELECT id, display_name, external_id, email FROM lt_users WHERE id = ANY($1)';
43
55
  /**
44
56
  * Lookup by a caller-named metadata key (e.g. a badge binding). LIMIT 2 so an
45
57
  * ambiguous binding — two users carrying the same value — is detectable and
@@ -47,7 +59,7 @@ exports.GET_USER_BY_ID = 'SELECT * FROM lt_users WHERE id = $1';
47
59
  * is the people table (small); revisit with a GIN index on metadata if a
48
60
  * deployment ever holds tens of thousands of users.
49
61
  */
50
- exports.GET_USERS_BY_METADATA_VALUE = `SELECT * FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2`;
62
+ exports.GET_USERS_BY_METADATA_VALUE = `SELECT ${exports.USER_SAFE_COLUMNS} FROM lt_users WHERE metadata->>$1 = $2 AND status = 'active' LIMIT 2`;
51
63
  /** Check if a user exists by id. Lightweight — returns only the id column. */
52
64
  exports.VERIFY_USER_BY_ID = 'SELECT id FROM lt_users WHERE id = $1 LIMIT 1';
53
65
  /**
@@ -16,6 +16,7 @@ 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
18
  const features_1 = require("../modules/features");
19
+ const search_1 = require("../modules/search");
19
20
  const branding_1 = require("../modules/branding");
20
21
  const socket_auth_1 = require("./socket-auth");
21
22
  /**
@@ -25,6 +26,7 @@ const socket_auth_1 = require("./socket-auth");
25
26
  function registerAdapters(startConfig) {
26
27
  // Dashboard feature flags (default permissive; deployment opts out).
27
28
  (0, features_1.configureFeatureFlags)(startConfig.features);
29
+ (0, search_1.configureSearch)(startConfig.search);
28
30
  (0, branding_1.configureBranding)(startConfig.branding);
29
31
  // Logging (register first so subsequent log calls use it)
30
32
  if (startConfig.logging?.adapter) {