@hotmeshio/long-tail 0.10.13 → 0.11.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 (373) hide show
  1. package/README.md +2 -4
  2. package/build/api/escalations/analytics.d.ts +6 -0
  3. package/build/api/escalations/analytics.js +123 -0
  4. package/build/api/escalations/index.d.ts +1 -0
  5. package/build/api/escalations/index.js +4 -1
  6. package/build/api/escalations/metadata.js +1 -1
  7. package/build/api/escalations/resolve.js +4 -4
  8. package/build/api/roles.d.ts +4 -0
  9. package/build/api/roles.js +8 -0
  10. package/build/api/scan-codes/choice.d.ts +11 -0
  11. package/build/api/scan-codes/choice.js +136 -0
  12. package/build/api/scan-codes/context.d.ts +24 -0
  13. package/build/api/scan-codes/context.js +86 -0
  14. package/build/api/scan-codes/execute.d.ts +1 -0
  15. package/build/api/scan-codes/execute.js +49 -244
  16. package/build/api/scan-codes/identity.d.ts +18 -0
  17. package/build/api/scan-codes/identity.js +116 -0
  18. package/build/api/scan-codes/index.d.ts +2 -0
  19. package/build/api/scan-codes/index.js +6 -1
  20. package/build/api/scan-codes/locate.d.ts +14 -0
  21. package/build/api/scan-codes/locate.js +127 -0
  22. package/build/api/scan-codes/present.d.ts +9 -0
  23. package/build/api/scan-codes/present.js +59 -0
  24. package/build/api/scan-codes/verbs.d.ts +15 -0
  25. package/build/api/scan-codes/verbs.js +205 -0
  26. package/build/bin/ltc.js +33 -0
  27. package/build/lib/cli/commands/escalations.d.ts +31 -0
  28. package/build/lib/cli/commands/escalations.js +88 -0
  29. package/build/lib/db/schemas/028_role_entity_facet.sql +18 -0
  30. package/build/lib/db/schemas/029_scan_scheme_kind.sql +31 -0
  31. package/build/modules/config.d.ts +5 -0
  32. package/build/modules/config.js +12 -0
  33. package/build/routes/escalations/acting.d.ts +12 -0
  34. package/build/routes/escalations/acting.js +25 -0
  35. package/build/routes/escalations/analytics.d.ts +8 -0
  36. package/build/routes/escalations/analytics.js +63 -0
  37. package/build/routes/escalations/index.js +3 -0
  38. package/build/routes/escalations/resolve.js +8 -1
  39. package/build/routes/escalations/single.js +13 -4
  40. package/build/routes/roles.js +1 -1
  41. package/build/routes/scan-codes.js +28 -2
  42. package/build/scripts/ortho-populate.js +14 -0
  43. package/build/scripts/printer-cycle.d.ts +25 -0
  44. package/build/scripts/printer-cycle.js +169 -0
  45. package/build/scripts/twin-farm.js +19 -5
  46. package/build/sdk/index.d.ts +2 -0
  47. package/build/sdk/index.js +3 -0
  48. package/build/services/escalation/aggregate-sql.d.ts +36 -0
  49. package/build/services/escalation/aggregate-sql.js +253 -0
  50. package/build/services/escalation/aggregate-validate.d.ts +57 -0
  51. package/build/services/escalation/aggregate-validate.js +218 -0
  52. package/build/services/escalation/aggregates.d.ts +26 -0
  53. package/build/services/escalation/aggregates.js +204 -0
  54. package/build/services/escalation/client.js +57 -21
  55. package/build/services/escalation/facet-sql.js +17 -0
  56. package/build/services/escalation/index.d.ts +2 -0
  57. package/build/services/escalation/index.js +8 -1
  58. package/build/services/escalation/metrics-cache.d.ts +18 -0
  59. package/build/services/escalation/metrics-cache.js +47 -1
  60. package/build/services/escalation/queries.js +2 -0
  61. package/build/services/escalation/resolver-validation.d.ts +1 -1
  62. package/build/services/escalation/resolver-validation.js +12 -5
  63. package/build/services/escalation/submit-guard.d.ts +29 -0
  64. package/build/services/escalation/submit-guard.js +57 -0
  65. package/build/services/iam/acting-identity.d.ts +16 -0
  66. package/build/services/iam/acting-identity.js +60 -0
  67. package/build/services/role/index.js +2 -0
  68. package/build/services/role/sql.d.ts +10 -2
  69. package/build/services/role/sql.js +14 -1
  70. package/build/services/role/types.d.ts +16 -0
  71. package/build/services/scan-code/read.js +4 -0
  72. package/build/services/scan-code/sql.d.ts +4 -4
  73. package/build/services/scan-code/sql.js +14 -8
  74. package/build/services/scan-code/validate.d.ts +2 -0
  75. package/build/services/scan-code/validate.js +86 -0
  76. package/build/services/scan-code/write.d.ts +4 -0
  77. package/build/services/scan-code/write.js +16 -2
  78. package/build/services/user/crud.d.ts +6 -0
  79. package/build/services/user/crud.js +16 -0
  80. package/build/services/user/index.d.ts +1 -1
  81. package/build/services/user/index.js +2 -1
  82. package/build/services/user/sql.d.ts +8 -0
  83. package/build/services/user/sql.js +9 -1
  84. package/build/shared/form-validation/index.d.ts +4 -0
  85. package/build/shared/form-validation/index.js +4 -0
  86. package/build/shared/form-validation/x-lt-labels.d.ts +28 -0
  87. package/build/shared/form-validation/x-lt-labels.js +42 -0
  88. package/build/shared/form-validation/x-lt-query.d.ts +56 -0
  89. package/build/shared/form-validation/x-lt-query.js +49 -0
  90. package/build/shared/form-validation/x-lt-submit-guard.d.ts +46 -0
  91. package/build/shared/form-validation/x-lt-submit-guard.js +25 -0
  92. package/build/shared/form-validation/x-lt-submit-on-claim.d.ts +22 -0
  93. package/build/shared/form-validation/x-lt-submit-on-claim.js +28 -0
  94. package/build/system/mcp-servers/admin/escalations.js +34 -0
  95. package/build/system/mcp-servers/admin/scan-codes.js +17 -1
  96. package/build/system/mcp-servers/admin/schemas.d.ts +643 -25
  97. package/build/system/mcp-servers/admin/schemas.js +127 -19
  98. package/build/system/mcp-servers/admin/users.js +3 -1
  99. package/build/system/mcp-servers/human-queue-schemas.d.ts +2 -2
  100. package/build/system/seed/tool-manifests-admin.d.ts +2283 -0
  101. package/build/system/seed/tool-manifests-admin.js +8 -5
  102. package/build/tsconfig.tsbuildinfo +1 -1
  103. package/build/types/analytics.d.ts +154 -0
  104. package/build/types/analytics.js +14 -0
  105. package/build/types/facets.d.ts +13 -0
  106. package/build/types/index.d.ts +5 -2
  107. package/build/types/index.js +5 -1
  108. package/build/types/scan-choice.d.ts +49 -0
  109. package/build/types/scan-choice.js +9 -0
  110. package/build/types/scan-code.d.ts +72 -1
  111. package/build/types/scan-code.js +30 -1
  112. package/dashboard/dist/assets/{AdminDashboard-Bv3pHBzn.js → AdminDashboard-DJ3LJQ09.js} +2 -2
  113. package/dashboard/dist/assets/{AdminDashboard-Bv3pHBzn.js.map → AdminDashboard-DJ3LJQ09.js.map} +1 -1
  114. package/dashboard/dist/assets/{AgentConfigPage-Dk2zlvDb.js → AgentConfigPage-BMMvaD0z.js} +2 -2
  115. package/dashboard/dist/assets/{AgentConfigPage-Dk2zlvDb.js.map → AgentConfigPage-BMMvaD0z.js.map} +1 -1
  116. package/dashboard/dist/assets/{AgentDetailPage-DUno6EGs.js → AgentDetailPage-C6wfsnRe.js} +2 -2
  117. package/dashboard/dist/assets/{AgentDetailPage-DUno6EGs.js.map → AgentDetailPage-C6wfsnRe.js.map} +1 -1
  118. package/dashboard/dist/assets/AgentsPage-Cwqka7nE.js +2 -0
  119. package/dashboard/dist/assets/{AgentsPage-CToVu1qj.js.map → AgentsPage-Cwqka7nE.js.map} +1 -1
  120. package/dashboard/dist/assets/AvailableEscalationsPage-CVlG_AVC.js +2 -0
  121. package/dashboard/dist/assets/AvailableEscalationsPage-CVlG_AVC.js.map +1 -0
  122. package/dashboard/dist/assets/{BotPicker-DQOpVB4M.js → BotPicker-BBkJBXZB.js} +2 -2
  123. package/dashboard/dist/assets/{BotPicker-DQOpVB4M.js.map → BotPicker-BBkJBXZB.js.map} +1 -1
  124. package/dashboard/dist/assets/{CapabilitiesPage-Dmf46PUJ.js → CapabilitiesPage-wxmE4917.js} +2 -2
  125. package/dashboard/dist/assets/{CapabilitiesPage-Dmf46PUJ.js.map → CapabilitiesPage-wxmE4917.js.map} +1 -1
  126. package/dashboard/dist/assets/{CollapsibleSection-CDADefUY.js → CollapsibleSection-B3FsrHwH.js} +2 -2
  127. package/dashboard/dist/assets/{CollapsibleSection-CDADefUY.js.map → CollapsibleSection-B3FsrHwH.js.map} +1 -1
  128. package/dashboard/dist/assets/CountdownTimer-B3nCqJdC.js +2 -0
  129. package/dashboard/dist/assets/CountdownTimer-B3nCqJdC.js.map +1 -0
  130. package/dashboard/dist/assets/{CredentialsPage-8LIEvomi.js → CredentialsPage-Bv875IHe.js} +2 -2
  131. package/dashboard/dist/assets/{CredentialsPage-8LIEvomi.js.map → CredentialsPage-Bv875IHe.js.map} +1 -1
  132. package/dashboard/dist/assets/{CronLabel-CK3WSsM5.js → CronLabel-CSzjWuwj.js} +2 -2
  133. package/dashboard/dist/assets/{CronLabel-CK3WSsM5.js.map → CronLabel-CSzjWuwj.js.map} +1 -1
  134. package/dashboard/dist/assets/{CustomDurationPicker-FPmlA6cE.js → CustomDurationPicker-CCpqf3e4.js} +2 -2
  135. package/dashboard/dist/assets/{CustomDurationPicker-FPmlA6cE.js.map → CustomDurationPicker-CCpqf3e4.js.map} +1 -1
  136. package/dashboard/dist/assets/{DropZone-CL9gA7pu.js → DropZone-CJkk-U3o.js} +2 -2
  137. package/dashboard/dist/assets/{DropZone-CL9gA7pu.js.map → DropZone-CJkk-U3o.js.map} +1 -1
  138. package/dashboard/dist/assets/{ElapsedCell-BZVkVxE0.js → ElapsedCell-WV6pUaKo.js} +2 -2
  139. package/dashboard/dist/assets/{ElapsedCell-BZVkVxE0.js.map → ElapsedCell-WV6pUaKo.js.map} +1 -1
  140. package/dashboard/dist/assets/EntityTimelinePanel-DrL86Yrf.js +2 -0
  141. package/dashboard/dist/assets/EntityTimelinePanel-DrL86Yrf.js.map +1 -0
  142. package/dashboard/dist/assets/{EscalationListSchemaPage-kYLdUtZX.js → EscalationListSchemaPage-CTQCALw4.js} +3 -3
  143. package/dashboard/dist/assets/{EscalationListSchemaPage-kYLdUtZX.js.map → EscalationListSchemaPage-CTQCALw4.js.map} +1 -1
  144. package/dashboard/dist/assets/{EscalationSchemaPage-kUmCDivo.js → EscalationSchemaPage-D_UgP0pv.js} +3 -3
  145. package/dashboard/dist/assets/{EscalationSchemaPage-kUmCDivo.js.map → EscalationSchemaPage-D_UgP0pv.js.map} +1 -1
  146. package/dashboard/dist/assets/{EscalationsOverview-D17auO-m.js → EscalationsOverview-DuIUyxr0.js} +2 -2
  147. package/dashboard/dist/assets/{EscalationsOverview-D17auO-m.js.map → EscalationsOverview-DuIUyxr0.js.map} +1 -1
  148. package/dashboard/dist/assets/EventTable-BV6unphX.js +2 -0
  149. package/dashboard/dist/assets/EventTable-BV6unphX.js.map +1 -0
  150. package/dashboard/dist/assets/FilterBar-DYFgzXNB.js +2 -0
  151. package/dashboard/dist/assets/FilterBar-DYFgzXNB.js.map +1 -0
  152. package/dashboard/dist/assets/{GraphInvokePage-D11uOnZi.js → GraphInvokePage-BGHVUipJ.js} +2 -2
  153. package/dashboard/dist/assets/{GraphInvokePage-D11uOnZi.js.map → GraphInvokePage-BGHVUipJ.js.map} +1 -1
  154. package/dashboard/dist/assets/HomePage-B-S2cK3E.js +2 -0
  155. package/dashboard/dist/assets/{HomePage-Bcq10Bxx.js.map → HomePage-B-S2cK3E.js.map} +1 -1
  156. package/dashboard/dist/assets/ListToolbar-Bl7F3A28.js +2 -0
  157. package/dashboard/dist/assets/{ListToolbar-IPXsTb3Q.js.map → ListToolbar-Bl7F3A28.js.map} +1 -1
  158. package/dashboard/dist/assets/{McpOverview-Dx2j0KkF.js → McpOverview-_T9IsCD_.js} +2 -2
  159. package/dashboard/dist/assets/{McpOverview-Dx2j0KkF.js.map → McpOverview-_T9IsCD_.js.map} +1 -1
  160. package/dashboard/dist/assets/{McpQueryDetailPage-CzF_bna8.js → McpQueryDetailPage-DtsVqKPw.js} +2 -2
  161. package/dashboard/dist/assets/{McpQueryDetailPage-CzF_bna8.js.map → McpQueryDetailPage-DtsVqKPw.js.map} +1 -1
  162. package/dashboard/dist/assets/McpQueryPage-DX5u-1bx.js +2 -0
  163. package/dashboard/dist/assets/{McpQueryPage-CNsuiw_q.js.map → McpQueryPage-DX5u-1bx.js.map} +1 -1
  164. package/dashboard/dist/assets/McpRunDetailPage-BhcqqtYS.js +2 -0
  165. package/dashboard/dist/assets/McpRunDetailPage-BhcqqtYS.js.map +1 -0
  166. package/dashboard/dist/assets/{McpRunsPage-CK96s3Nt.js → McpRunsPage-L-1y8wnX.js} +2 -2
  167. package/dashboard/dist/assets/{McpRunsPage-CK96s3Nt.js.map → McpRunsPage-L-1y8wnX.js.map} +1 -1
  168. package/dashboard/dist/assets/{NamespacePill-B9Vc_Pz2.js → NamespacePill-in1SLd0W.js} +2 -2
  169. package/dashboard/dist/assets/{NamespacePill-B9Vc_Pz2.js.map → NamespacePill-in1SLd0W.js.map} +1 -1
  170. package/dashboard/dist/assets/OperationsPage-i8KcDpKo.js +3 -0
  171. package/dashboard/dist/assets/OperationsPage-i8KcDpKo.js.map +1 -0
  172. package/dashboard/dist/assets/OperatorDashboard-CIbDzOlh.js +2 -0
  173. package/dashboard/dist/assets/{OperatorDashboard-C-jE0IRA.js.map → OperatorDashboard-CIbDzOlh.js.map} +1 -1
  174. package/dashboard/dist/assets/{PageHeader-Ce4GcIYo.js → PageHeader-BJgWMZtw.js} +2 -2
  175. package/dashboard/dist/assets/{PageHeader-Ce4GcIYo.js.map → PageHeader-BJgWMZtw.js.map} +1 -1
  176. package/dashboard/dist/assets/{PageHeaderWithStats-Ck0KZvXD.js → PageHeaderWithStats-Ccio4vyG.js} +2 -2
  177. package/dashboard/dist/assets/{PageHeaderWithStats-Ck0KZvXD.js.map → PageHeaderWithStats-Ccio4vyG.js.map} +1 -1
  178. package/dashboard/dist/assets/{PersonaDetailPage-CJ6jqo7h.js → PersonaDetailPage-BOaf0TW1.js} +2 -2
  179. package/dashboard/dist/assets/{PersonaDetailPage-CJ6jqo7h.js.map → PersonaDetailPage-BOaf0TW1.js.map} +1 -1
  180. package/dashboard/dist/assets/{PersonasPage-D8hL3kL5.js → PersonasPage-DSdRb90f.js} +2 -2
  181. package/dashboard/dist/assets/{PersonasPage-D8hL3kL5.js.map → PersonasPage-DSdRb90f.js.map} +1 -1
  182. package/dashboard/dist/assets/{ProcessDetailPage-Vnx8Z5_y.js → ProcessDetailPage-DN9NqfeP.js} +2 -2
  183. package/dashboard/dist/assets/{ProcessDetailPage-Vnx8Z5_y.js.map → ProcessDetailPage-DN9NqfeP.js.map} +1 -1
  184. package/dashboard/dist/assets/{ProcessesListPage-DRYtvS3N.js → ProcessesListPage-CfQEWaD4.js} +2 -2
  185. package/dashboard/dist/assets/{ProcessesListPage-DRYtvS3N.js.map → ProcessesListPage-CfQEWaD4.js.map} +1 -1
  186. package/dashboard/dist/assets/RoleDetailPage-BC5HL7ls.js +8 -0
  187. package/dashboard/dist/assets/RoleDetailPage-BC5HL7ls.js.map +1 -0
  188. package/dashboard/dist/assets/{RolePill-CnuBPhPw.js → RolePill-BNAP-uK6.js} +2 -2
  189. package/dashboard/dist/assets/{RolePill-CnuBPhPw.js.map → RolePill-BNAP-uK6.js.map} +1 -1
  190. package/dashboard/dist/assets/RolesPage-DCnW0VmD.js +2 -0
  191. package/dashboard/dist/assets/RolesPage-DCnW0VmD.js.map +1 -0
  192. package/dashboard/dist/assets/RunAsSelector-twbuX7fB.js +2 -0
  193. package/dashboard/dist/assets/{RunAsSelector-CS3KQ-BF.js.map → RunAsSelector-twbuX7fB.js.map} +1 -1
  194. package/dashboard/dist/assets/ScanCodesPage-CPNg6K--.js +2 -0
  195. package/dashboard/dist/assets/ScanCodesPage-CPNg6K--.js.map +1 -0
  196. package/dashboard/dist/assets/ScanSchemeDetailPage-DyLmCamO.js +2 -0
  197. package/dashboard/dist/assets/ScanSchemeDetailPage-DyLmCamO.js.map +1 -0
  198. package/dashboard/dist/assets/ScanStationPage-DoZccU5B.js +2 -0
  199. package/dashboard/dist/assets/ScanStationPage-DoZccU5B.js.map +1 -0
  200. package/dashboard/dist/assets/{ScopeBadge-BUpYpS3x.js → ScopeBadge-BG7KkZ8Q.js} +2 -2
  201. package/dashboard/dist/assets/{ScopeBadge-BUpYpS3x.js.map → ScopeBadge-BG7KkZ8Q.js.map} +1 -1
  202. package/dashboard/dist/assets/SegmentedTabs-DXNzY05q.js +2 -0
  203. package/dashboard/dist/assets/SegmentedTabs-DXNzY05q.js.map +1 -0
  204. package/dashboard/dist/assets/StickyPagination-DIeYMPPd.js +2 -0
  205. package/dashboard/dist/assets/StickyPagination-DIeYMPPd.js.map +1 -0
  206. package/dashboard/dist/assets/{StreamMessageDetail-DyJyZwBl.js → StreamMessageDetail-gU4CUeQI.js} +2 -2
  207. package/dashboard/dist/assets/{StreamMessageDetail-DyJyZwBl.js.map → StreamMessageDetail-gU4CUeQI.js.map} +1 -1
  208. package/dashboard/dist/assets/{SwimlaneTimeline-BlzV74V5.js → SwimlaneTimeline-Br-nBA4l.js} +2 -2
  209. package/dashboard/dist/assets/{SwimlaneTimeline-BlzV74V5.js.map → SwimlaneTimeline-Br-nBA4l.js.map} +1 -1
  210. package/dashboard/dist/assets/{TagInput-BbZ25nNe.js → TagInput-CvM5n6ww.js} +2 -2
  211. package/dashboard/dist/assets/{TagInput-BbZ25nNe.js.map → TagInput-CvM5n6ww.js.map} +1 -1
  212. package/dashboard/dist/assets/{TaskDetailPage-B0p_Z4J_.js → TaskDetailPage-_9cr_R0u.js} +2 -2
  213. package/dashboard/dist/assets/{TaskDetailPage-B0p_Z4J_.js.map → TaskDetailPage-_9cr_R0u.js.map} +1 -1
  214. package/dashboard/dist/assets/{TaskQueuePill-D0QKBrN3.js → TaskQueuePill-D4JPHXOq.js} +2 -2
  215. package/dashboard/dist/assets/{TaskQueuePill-D0QKBrN3.js.map → TaskQueuePill-D4JPHXOq.js.map} +1 -1
  216. package/dashboard/dist/assets/{TasksListPage-CiGR2QiQ.js → TasksListPage-DVENC9Jp.js} +2 -2
  217. package/dashboard/dist/assets/{TasksListPage-CiGR2QiQ.js.map → TasksListPage-DVENC9Jp.js.map} +1 -1
  218. package/dashboard/dist/assets/{TimeAgo-D_DA9ucC.js → TimeAgo-C9yYnOui.js} +2 -2
  219. package/dashboard/dist/assets/{TimeAgo-D_DA9ucC.js.map → TimeAgo-C9yYnOui.js.map} +1 -1
  220. package/dashboard/dist/assets/{TimestampCell-C7rkC9nh.js → TimestampCell-DoB8cYgw.js} +2 -2
  221. package/dashboard/dist/assets/{TimestampCell-C7rkC9nh.js.map → TimestampCell-DoB8cYgw.js.map} +1 -1
  222. package/dashboard/dist/assets/{ToolPill-8xY9rkoc.js → ToolPill-BUjbC-Tj.js} +2 -2
  223. package/dashboard/dist/assets/{ToolPill-8xY9rkoc.js.map → ToolPill-BUjbC-Tj.js.map} +1 -1
  224. package/dashboard/dist/assets/{ToolTestPanel-E8efYGYs.js → ToolTestPanel-CBtsIP8K.js} +2 -2
  225. package/dashboard/dist/assets/{ToolTestPanel-E8efYGYs.js.map → ToolTestPanel-CBtsIP8K.js.map} +1 -1
  226. package/dashboard/dist/assets/{TopicDetailPage-B455XrRc.js → TopicDetailPage-mR4Jaz83.js} +4 -4
  227. package/dashboard/dist/assets/{TopicDetailPage-B455XrRc.js.map → TopicDetailPage-mR4Jaz83.js.map} +1 -1
  228. package/dashboard/dist/assets/TopicsPage-CP7HFxIC.js +2 -0
  229. package/dashboard/dist/assets/{TopicsPage-1VAc29Qw.js.map → TopicsPage-CP7HFxIC.js.map} +1 -1
  230. package/dashboard/dist/assets/{UserName-DikzjQHJ.js → UserName-BnLF6yhz.js} +2 -2
  231. package/dashboard/dist/assets/{UserName-DikzjQHJ.js.map → UserName-BnLF6yhz.js.map} +1 -1
  232. package/dashboard/dist/assets/WorkflowExecutionPage-xL956RZi.js +2 -0
  233. package/dashboard/dist/assets/WorkflowExecutionPage-xL956RZi.js.map +1 -0
  234. package/dashboard/dist/assets/{WorkflowPill-Da406nEj.js → WorkflowPill-tXU0-eXu.js} +2 -2
  235. package/dashboard/dist/assets/{WorkflowPill-Da406nEj.js.map → WorkflowPill-tXU0-eXu.js.map} +1 -1
  236. package/dashboard/dist/assets/WorkflowsDashboard-B-u7zFKM.js +2 -0
  237. package/dashboard/dist/assets/{WorkflowsDashboard-BMQd_0tW.js.map → WorkflowsDashboard-B-u7zFKM.js.map} +1 -1
  238. package/dashboard/dist/assets/{WorkflowsOverview-CdxjVifp.js → WorkflowsOverview-CqZVarK-.js} +2 -2
  239. package/dashboard/dist/assets/{WorkflowsOverview-CdxjVifp.js.map → WorkflowsOverview-CqZVarK-.js.map} +1 -1
  240. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DNVffIXa.js → YamlWorkflowDetailPage-DiFLavW0.js} +10 -10
  241. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DNVffIXa.js.map → YamlWorkflowDetailPage-DiFLavW0.js.map} +1 -1
  242. package/dashboard/dist/assets/{YamlWorkflowsPage-BGP8wuos.js → YamlWorkflowsPage-BuQM6uZA.js} +2 -2
  243. package/dashboard/dist/assets/{YamlWorkflowsPage-BGP8wuos.js.map → YamlWorkflowsPage-BuQM6uZA.js.map} +1 -1
  244. package/dashboard/dist/assets/{agents-BZJLIJ4h.js → agents-DU9GPovp.js} +2 -2
  245. package/dashboard/dist/assets/{agents-BZJLIJ4h.js.map → agents-DU9GPovp.js.map} +1 -1
  246. package/dashboard/dist/assets/{bots-ClLcDGJb.js → bots-Cy-Hmg7w.js} +2 -2
  247. package/dashboard/dist/assets/{bots-ClLcDGJb.js.map → bots-Cy-Hmg7w.js.map} +1 -1
  248. package/dashboard/dist/assets/capabilities-sSJJqrp-.js +2 -0
  249. package/dashboard/dist/assets/{capabilities-IaCcS7ge.js.map → capabilities-sSJJqrp-.js.map} +1 -1
  250. package/dashboard/dist/assets/{controlplane-Zi4m_MSx.js → controlplane-TVCjDHQ-.js} +2 -2
  251. package/dashboard/dist/assets/{controlplane-Zi4m_MSx.js.map → controlplane-TVCjDHQ-.js.map} +1 -1
  252. package/dashboard/dist/assets/escalation-columns-BpkjKYGd.js +2 -0
  253. package/dashboard/dist/assets/escalation-columns-BpkjKYGd.js.map +1 -0
  254. package/dashboard/dist/assets/index-5TblaCXQ.css +1 -0
  255. package/dashboard/dist/assets/index-7BP6FaCZ.js +2 -0
  256. package/dashboard/dist/assets/{index-Dldi3Q3M.js.map → index-7BP6FaCZ.js.map} +1 -1
  257. package/dashboard/dist/assets/{index-BzkE48xJ.js → index-B7IaBPBg.js} +4 -4
  258. package/dashboard/dist/assets/{index-BzkE48xJ.js.map → index-B7IaBPBg.js.map} +1 -1
  259. package/dashboard/dist/assets/index-BE0phFw0.js +2 -0
  260. package/dashboard/dist/assets/{index-CLmZaNTK.js.map → index-BE0phFw0.js.map} +1 -1
  261. package/dashboard/dist/assets/{index-BqGnhkh0.js → index-BYNN9N1T.js} +2 -2
  262. package/dashboard/dist/assets/{index-BqGnhkh0.js.map → index-BYNN9N1T.js.map} +1 -1
  263. package/dashboard/dist/assets/{index-kiKZresb.js → index-CLPVqbVY.js} +2 -2
  264. package/dashboard/dist/assets/{index-kiKZresb.js.map → index-CLPVqbVY.js.map} +1 -1
  265. package/dashboard/dist/assets/{index-C3o4OisO.js → index-Cfv86_L9.js} +2 -2
  266. package/dashboard/dist/assets/{index-C3o4OisO.js.map → index-Cfv86_L9.js.map} +1 -1
  267. package/dashboard/dist/assets/{index-yGNt29V9.js → index-CtwbQ_-m.js} +2 -2
  268. package/dashboard/dist/assets/{index-yGNt29V9.js.map → index-CtwbQ_-m.js.map} +1 -1
  269. package/dashboard/dist/assets/index-DOai950R.js +3 -0
  270. package/dashboard/dist/assets/index-DOai950R.js.map +1 -0
  271. package/dashboard/dist/assets/index-DTtHNC-D.js +5 -0
  272. package/dashboard/dist/assets/{index-CvNBdkB1.js.map → index-DTtHNC-D.js.map} +1 -1
  273. package/dashboard/dist/assets/{index-CJu2PBNx.js → index-DsWnVyVN.js} +2 -2
  274. package/dashboard/dist/assets/{index-CJu2PBNx.js.map → index-DsWnVyVN.js.map} +1 -1
  275. package/dashboard/dist/assets/{index-D0zFReGL.js → index-Dy-MMYti.js} +2 -2
  276. package/dashboard/dist/assets/{index-D0zFReGL.js.map → index-Dy-MMYti.js.map} +1 -1
  277. package/dashboard/dist/assets/index-errgQT5x.js +65 -0
  278. package/dashboard/dist/assets/index-errgQT5x.js.map +1 -0
  279. package/dashboard/dist/assets/{index-CmnNqpWg.js → index-fgBDhnI6.js} +2 -2
  280. package/dashboard/dist/assets/{index-CmnNqpWg.js.map → index-fgBDhnI6.js.map} +1 -1
  281. package/dashboard/dist/assets/{knowledge-DWLxGTib.js → knowledge-arh4NfmL.js} +2 -2
  282. package/dashboard/dist/assets/{knowledge-DWLxGTib.js.map → knowledge-arh4NfmL.js.map} +1 -1
  283. package/dashboard/dist/assets/{mcp-B6AARZ2I.js → mcp-QGf5jHCo.js} +2 -2
  284. package/dashboard/dist/assets/{mcp-B6AARZ2I.js.map → mcp-QGf5jHCo.js.map} +1 -1
  285. package/dashboard/dist/assets/{mcp-query-DrkvBU3a.js → mcp-query-BEJN1nMZ.js} +2 -2
  286. package/dashboard/dist/assets/{mcp-query-DrkvBU3a.js.map → mcp-query-BEJN1nMZ.js.map} +1 -1
  287. package/dashboard/dist/assets/{personas-Cmz2SAEU.js → personas-DYtw1COf.js} +2 -2
  288. package/dashboard/dist/assets/{personas-Cmz2SAEU.js.map → personas-DYtw1COf.js.map} +1 -1
  289. package/dashboard/dist/assets/{pipelines-CSgfVCMy.js → pipelines-Byi6PnbE.js} +2 -2
  290. package/dashboard/dist/assets/{pipelines-CSgfVCMy.js.map → pipelines-Byi6PnbE.js.map} +1 -1
  291. package/dashboard/dist/assets/{tasks-UOtUkDk-.js → tasks-D0SQdENz.js} +2 -2
  292. package/dashboard/dist/assets/{tasks-UOtUkDk-.js.map → tasks-D0SQdENz.js.map} +1 -1
  293. package/dashboard/dist/assets/{topics-Df5hGh6o.js → topics-DgDv5ZXP.js} +2 -2
  294. package/dashboard/dist/assets/{topics-Df5hGh6o.js.map → topics-DgDv5ZXP.js.map} +1 -1
  295. package/dashboard/dist/assets/useEventHooks-DZaYzE7s.js +2 -0
  296. package/dashboard/dist/assets/{useEventHooks-PDbuSJ12.js.map → useEventHooks-DZaYzE7s.js.map} +1 -1
  297. package/dashboard/dist/assets/{useNamespace-tZSOlge3.js → useNamespace-C1OqiNXN.js} +2 -2
  298. package/dashboard/dist/assets/{useNamespace-tZSOlge3.js.map → useNamespace-C1OqiNXN.js.map} +1 -1
  299. package/dashboard/dist/assets/{useYamlActivityEvents-CINLzE32.js → useYamlActivityEvents-CkmXDm-Z.js} +2 -2
  300. package/dashboard/dist/assets/{useYamlActivityEvents-CINLzE32.js.map → useYamlActivityEvents-CkmXDm-Z.js.map} +1 -1
  301. package/dashboard/dist/assets/{users-G8qwyZKS.js → users-D4DCFPH1.js} +2 -2
  302. package/dashboard/dist/assets/{users-G8qwyZKS.js.map → users-D4DCFPH1.js.map} +1 -1
  303. package/dashboard/dist/assets/{vendor-icons-BpZiWH2f.js → vendor-icons-BxuyFxH2.js} +156 -136
  304. package/dashboard/dist/assets/vendor-icons-BxuyFxH2.js.map +1 -0
  305. package/dashboard/dist/assets/{workflows-DbPqi1TW.js → workflows-qyc0ku_5.js} +2 -2
  306. package/dashboard/dist/assets/{workflows-DbPqi1TW.js.map → workflows-qyc0ku_5.js.map} +1 -1
  307. package/dashboard/dist/assets/{yaml-workflows-DozD8hpR.js → yaml-workflows-DWFww9Xn.js} +2 -2
  308. package/dashboard/dist/assets/{yaml-workflows-DozD8hpR.js.map → yaml-workflows-DWFww9Xn.js.map} +1 -1
  309. package/dashboard/dist/index.html +3 -3
  310. package/docs/api/http/escalations.md +133 -0
  311. package/docs/api/http/roles.md +6 -0
  312. package/docs/api/mcp/admin.md +53 -0
  313. package/docs/api/sdk/escalations.md +58 -0
  314. package/docs/cli.md +11 -0
  315. package/docs/dashboard.md +22 -20
  316. package/docs/data.md +12 -4
  317. package/docs/design-principles.md +9 -7
  318. package/docs/escalation-analytics.md +251 -0
  319. package/docs/hitl/roles.md +1 -1
  320. package/docs/hitl/x-lt-embed.md +8 -4
  321. package/docs/hitl/x-lt-footer.md +67 -0
  322. package/docs/hitl/x-lt-list-schema.md +10 -3
  323. package/docs/hitl-guide.md +11 -2
  324. package/docs/iam.md +12 -0
  325. package/docs/operations.md +17 -0
  326. package/docs/scan-codes.md +112 -0
  327. package/docs/sdk.md +1 -1
  328. package/package.json +2 -1
  329. package/dashboard/dist/assets/AgentsPage-CToVu1qj.js +0 -2
  330. package/dashboard/dist/assets/AvailableEscalationsPage-DldboL4W.js +0 -2
  331. package/dashboard/dist/assets/AvailableEscalationsPage-DldboL4W.js.map +0 -1
  332. package/dashboard/dist/assets/CountdownTimer-DPjIkerB.js +0 -2
  333. package/dashboard/dist/assets/CountdownTimer-DPjIkerB.js.map +0 -1
  334. package/dashboard/dist/assets/EventTable-CwPWl163.js +0 -2
  335. package/dashboard/dist/assets/EventTable-CwPWl163.js.map +0 -1
  336. package/dashboard/dist/assets/FilterBar-D7GghUsU.js +0 -2
  337. package/dashboard/dist/assets/FilterBar-D7GghUsU.js.map +0 -1
  338. package/dashboard/dist/assets/HomePage-Bcq10Bxx.js +0 -2
  339. package/dashboard/dist/assets/ListToolbar-IPXsTb3Q.js +0 -2
  340. package/dashboard/dist/assets/McpQueryPage-CNsuiw_q.js +0 -2
  341. package/dashboard/dist/assets/McpRunDetailPage-BJguEMS0.js +0 -2
  342. package/dashboard/dist/assets/McpRunDetailPage-BJguEMS0.js.map +0 -1
  343. package/dashboard/dist/assets/OperationsPage-DvWrLuH6.js +0 -2
  344. package/dashboard/dist/assets/OperationsPage-DvWrLuH6.js.map +0 -1
  345. package/dashboard/dist/assets/OperatorDashboard-C-jE0IRA.js +0 -2
  346. package/dashboard/dist/assets/RoleDetailPage-BKkUvTO-.js +0 -8
  347. package/dashboard/dist/assets/RoleDetailPage-BKkUvTO-.js.map +0 -1
  348. package/dashboard/dist/assets/RolesPage-BHzWK9n6.js +0 -2
  349. package/dashboard/dist/assets/RolesPage-BHzWK9n6.js.map +0 -1
  350. package/dashboard/dist/assets/RunAsSelector-CS3KQ-BF.js +0 -2
  351. package/dashboard/dist/assets/ScanCodesPage-BW4P4zWf.js +0 -2
  352. package/dashboard/dist/assets/ScanCodesPage-BW4P4zWf.js.map +0 -1
  353. package/dashboard/dist/assets/ScanSchemeDetailPage-BLykA4j-.js +0 -2
  354. package/dashboard/dist/assets/ScanSchemeDetailPage-BLykA4j-.js.map +0 -1
  355. package/dashboard/dist/assets/StickyPagination-hMrnLXde.js +0 -2
  356. package/dashboard/dist/assets/StickyPagination-hMrnLXde.js.map +0 -1
  357. package/dashboard/dist/assets/TopicsPage-1VAc29Qw.js +0 -2
  358. package/dashboard/dist/assets/WorkflowExecutionPage-C15GSAoe.js +0 -2
  359. package/dashboard/dist/assets/WorkflowExecutionPage-C15GSAoe.js.map +0 -1
  360. package/dashboard/dist/assets/WorkflowsDashboard-BMQd_0tW.js +0 -2
  361. package/dashboard/dist/assets/capabilities-IaCcS7ge.js +0 -2
  362. package/dashboard/dist/assets/escalation-columns-BY2mg9DY.js +0 -2
  363. package/dashboard/dist/assets/escalation-columns-BY2mg9DY.js.map +0 -1
  364. package/dashboard/dist/assets/index-CLmZaNTK.js +0 -2
  365. package/dashboard/dist/assets/index-CfAi1OsI.js +0 -65
  366. package/dashboard/dist/assets/index-CfAi1OsI.js.map +0 -1
  367. package/dashboard/dist/assets/index-CvNBdkB1.js +0 -5
  368. package/dashboard/dist/assets/index-Dldi3Q3M.js +0 -2
  369. package/dashboard/dist/assets/index-mKcQGi2B.js +0 -3
  370. package/dashboard/dist/assets/index-mKcQGi2B.js.map +0 -1
  371. package/dashboard/dist/assets/index-zQiUPfxA.css +0 -1
  372. package/dashboard/dist/assets/useEventHooks-PDbuSJ12.js +0 -2
  373. package/dashboard/dist/assets/vendor-icons-BpZiWH2f.js.map +0 -1
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Escalation analytics service — grouped/temporal reads over the interval
4
+ // time-series (see aggregate-sql.ts for the SQL semantics). Pure read surface:
5
+ // no lifecycle change, no write path.
6
+ //
7
+ // Caching splits by anchor. A now-anchored result (membership now, a window
8
+ // reaching now) is pace-board-grade: short TTL, cleared by
9
+ // invalidateEscalationAggregates() on every escalation write on this
10
+ // container. A fully-past anchor is immutable under every long-tail write
11
+ // path — created_at is set at insert and terminal transitions stamp NOW(),
12
+ // which is strictly after any past anchor — so those entries survive
13
+ // invalidation and live on the longer TTL (bounding the documented SDK
14
+ // post-terminal-touch caveat rather than caching forever). Keys canonicalize
15
+ // the whole input, so both caches are LRU-bounded.
16
+ // ---------------------------------------------------------------------------
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.clearNowAnchoredAnalyticsCache = clearNowAnchoredAnalyticsCache;
19
+ exports.resetAnalyticsCaches = resetAnalyticsCaches;
20
+ exports.resolveEntitySystem = resolveEntitySystem;
21
+ exports.aggregateByFacets = aggregateByFacets;
22
+ exports.timelineByFacet = timelineByFacet;
23
+ const db_1 = require("../../lib/db");
24
+ const config_1 = require("../../modules/config");
25
+ const facets_1 = require("./facets");
26
+ const metrics_cache_1 = require("./metrics-cache");
27
+ const aggregate_sql_1 = require("./aggregate-sql");
28
+ const aggregate_validate_1 = require("./aggregate-validate");
29
+ const nowAnchoredCache = new metrics_cache_1.BoundedTtlCache(config_1.config.LT_ANALYTICS_CACHE_TTL_MS, config_1.config.LT_ANALYTICS_CACHE_MAX_ENTRIES);
30
+ const pastAnchoredCache = new metrics_cache_1.BoundedTtlCache(config_1.config.LT_ANALYTICS_PAST_CACHE_TTL_MS, config_1.config.LT_ANALYTICS_CACHE_MAX_ENTRIES);
31
+ /** Key-order-independent stringify, so `{a,b}` and `{b,a}` share one entry. */
32
+ function canonical(value) {
33
+ return JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
34
+ ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => (a < b ? -1 : 1)))
35
+ : v);
36
+ }
37
+ /** Called on every escalation write (via invalidateEscalationAggregates). */
38
+ function clearNowAnchoredAnalyticsCache() {
39
+ nowAnchoredCache.clear();
40
+ }
41
+ /** Test hook: drop BOTH analytics caches. */
42
+ function resetAnalyticsCaches() {
43
+ nowAnchoredCache.clear();
44
+ pastAnchoredCache.clear();
45
+ }
46
+ function matchesState(row, m) {
47
+ if (m.role !== undefined && row.role !== m.role)
48
+ return false;
49
+ if (m.roles !== undefined && !m.roles.includes(row.role))
50
+ return false;
51
+ if (m.subtype !== undefined && row.subtype !== m.subtype)
52
+ return false;
53
+ for (const [key, value] of Object.entries(m.facets ?? {})) {
54
+ if (row.facets[key] == null || row.facets[key] !== String(value))
55
+ return false;
56
+ }
57
+ for (const key of m.exists ?? []) {
58
+ if (row.facets[key] == null)
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+ function toAggregateRow(raw, built, states) {
64
+ const facets = {};
65
+ for (const key of built.facets)
66
+ facets[key] = raw[`facet_${key}`] ?? null;
67
+ const row = { facets, sampleCount: Number(raw.sample_count) };
68
+ for (const column of built.columns)
69
+ row[column] = raw[column] ?? undefined;
70
+ if (built.measureKind === 'membership')
71
+ row.count = Number(raw.count);
72
+ else
73
+ row.dwellSeconds = Number(raw.dwell_seconds);
74
+ if (built.stateGrouped) {
75
+ row.state = raw.state_label;
76
+ }
77
+ else {
78
+ const label = (states ?? []).find((s) => matchesState(row, s.match));
79
+ if (label)
80
+ row.state = label.name;
81
+ }
82
+ return row;
83
+ }
84
+ function toIso(value) {
85
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
86
+ }
87
+ function toTimelineInterval(raw, built) {
88
+ const facets = {};
89
+ for (const key of built.facets)
90
+ facets[key] = raw[`facet_${key}`] ?? null;
91
+ const interval = {
92
+ role: raw.role,
93
+ facets,
94
+ startedAt: toIso(raw.started_at),
95
+ endedAt: raw.ended_at == null ? null : toIso(raw.ended_at),
96
+ durationSeconds: Number(raw.duration_seconds),
97
+ };
98
+ if (built.columns.includes('subtype'))
99
+ interval.subtype = raw.subtype ?? undefined;
100
+ if (built.columns.includes('status'))
101
+ interval.status = raw.status;
102
+ return interval;
103
+ }
104
+ // ── Entity-system resolution ─────────────────────────────────────────────────
105
+ const ENTITY_SYSTEM_SQL = `
106
+ SELECT role, entity_state_source FROM lt_roles WHERE entity_facet = $1 ORDER BY role`;
107
+ const STATE_SOURCES_SQL = `
108
+ SELECT role, entity_state_source FROM lt_roles WHERE role = ANY($1::text[])`;
109
+ /**
110
+ * The roles declaring `entity_facet = key` — the entity's SYSTEM — with each
111
+ * role's state naming. Loud when nothing declares the key: an empty system is
112
+ * a config gap, not an empty result.
113
+ */
114
+ async function resolveEntitySystem(entityKey) {
115
+ (0, aggregate_validate_1.requireFacetKey)(entityKey, 'query.entity');
116
+ await (0, facets_1.ensureFacetReady)();
117
+ const { rows } = await (0, db_1.getPool)().query(ENTITY_SYSTEM_SQL, [entityKey]);
118
+ if (rows.length === 0) {
119
+ throw new aggregate_sql_1.AnalyticsInputError(`no roles declare entity_facet "${entityKey}" — set Entity on the roles this entity moves through`);
120
+ }
121
+ return rows.map((r) => ({
122
+ role: r.role,
123
+ source: r.entity_state_source === 'subtype' ? 'subtype' : 'role',
124
+ }));
125
+ }
126
+ /** State naming for an explicit role list (roles missing from lt_roles default to 'role'). */
127
+ async function stateSourcesFor(roles) {
128
+ const { rows } = await (0, db_1.getPool)().query(STATE_SOURCES_SQL, [roles]);
129
+ const sources = {};
130
+ for (const role of roles)
131
+ sources[role] = 'role';
132
+ for (const r of rows)
133
+ sources[r.role] = r.entity_state_source === 'subtype' ? 'subtype' : 'role';
134
+ return sources;
135
+ }
136
+ /**
137
+ * Resolve `query.entity` (and `groupBy.state`) into concrete scope: the
138
+ * system's roles replace `entity` on the filter (plus `exists: [entityKey]`
139
+ * so rows without the entity stay out), and per-role state sources ride along
140
+ * for the builder. The resolved system joins the cache key, so a role-config
141
+ * edit changes the key rather than serving a stale state shape.
142
+ */
143
+ async function resolveScope(query, needsStateSources) {
144
+ if (query.entity !== undefined) {
145
+ const system = await resolveEntitySystem(query.entity);
146
+ const { entity, ...rest } = query;
147
+ const resolved = {
148
+ ...rest,
149
+ roles: system.map((s) => s.role),
150
+ exists: [...new Set([...(rest.exists ?? []), entity])],
151
+ };
152
+ const sources = {};
153
+ for (const s of system)
154
+ sources[s.role] = s.source;
155
+ return { query: resolved, stateSources: sources, cacheSuffix: `:${canonical(system)}` };
156
+ }
157
+ if (needsStateSources) {
158
+ const roles = query.role ? [query.role] : query.roles ?? [];
159
+ if (roles.length === 0)
160
+ return { query, cacheSuffix: '' }; // builder rejects loudly
161
+ const sources = await stateSourcesFor(roles);
162
+ return { query, stateSources: sources, cacheSuffix: `:${canonical(sources)}` };
163
+ }
164
+ return { query, cacheSuffix: '' };
165
+ }
166
+ /**
167
+ * Grouped aggregate over the escalation intervals — membership at an instant
168
+ * or dwell over a window, grouped by columns + facets (+ the derived state
169
+ * label). One call replaces N countByFacets round-trips.
170
+ */
171
+ async function aggregateByFacets(input) {
172
+ const scope = await resolveScope(input.query ?? {}, input.groupBy?.state === true);
173
+ const effective = { ...input, query: scope.query };
174
+ // Validates loudly BEFORE any cache entry.
175
+ const built = (0, aggregate_sql_1.buildAggregateQuery)(effective, { stateSources: scope.stateSources });
176
+ const cache = built.nowAnchored ? nowAnchoredCache : pastAnchoredCache;
177
+ return (await cache.resolve(`agg:${canonical(input)}${scope.cacheSuffix}`, async () => {
178
+ await (0, facets_1.ensureFacetReady)();
179
+ const { rows } = await (0, db_1.getPool)().query(built.sql, built.params);
180
+ return {
181
+ groups: rows.slice(0, built.pageLimit).map((r) => toAggregateRow(r, built, input.states)),
182
+ overflow: rows.length > built.pageLimit,
183
+ };
184
+ }));
185
+ }
186
+ /**
187
+ * One entity's ordered interval sequence — `[created_at, ended_at)` spans with
188
+ * durations. Gaps between consecutive intervals are untracked time and are
189
+ * deliberately preserved, not filled.
190
+ */
191
+ async function timelineByFacet(input) {
192
+ const scope = await resolveScope(input.query ?? {}, false);
193
+ const effective = { ...input, query: scope.query };
194
+ const built = (0, aggregate_sql_1.buildTimelineQuery)(effective);
195
+ const cache = built.nowAnchored ? nowAnchoredCache : pastAnchoredCache;
196
+ return (await cache.resolve(`tl:${canonical(input)}${scope.cacheSuffix}`, async () => {
197
+ await (0, facets_1.ensureFacetReady)();
198
+ const { rows } = await (0, db_1.getPool)().query(built.sql, built.params);
199
+ return {
200
+ intervals: rows.slice(0, built.pageLimit).map((r) => toTimelineInterval(r, built)),
201
+ overflow: rows.length > built.pageLimit,
202
+ };
203
+ }));
204
+ }
@@ -97,16 +97,24 @@ async function installEscalationCompatView() {
97
97
  await client.query('SELECT pg_advisory_unlock($1)', [COMPAT_VIEW_LOCK_ID]).catch(() => { });
98
98
  client.release();
99
99
  }
100
- // The one index long-tail still owns builds OUTSIDE the advisory lock:
100
+ // The indexes long-tail owns build OUTSIDE the advisory lock:
101
101
  // CREATE INDEX CONCURRENTLY waits on every transaction holding a snapshot,
102
102
  // and sibling containers parked inside `SELECT pg_advisory_lock(...)` hold
103
103
  // exactly that — building inside the critical section would stall the build
104
104
  // against its own waiters. Out here the build blocks nothing (writes proceed
105
105
  // during CONCURRENTLY) and nothing blocks it.
106
- await ensureResolvedCoverIndex();
106
+ await ensureAppIndex('idx_hmsh_esc_resolved_cover', ENSURE_RESOLVED_COVER_INDEX);
107
+ await ensureAppIndex('idx_hmsh_esc_ended_at', ENSURE_ENDED_AT_INDEX);
107
108
  }
109
+ /** Reports existence + validity so a crash-orphaned INVALID build is rebuilt. */
110
+ const APP_INDEX_STATE = `
111
+ SELECT i.indisvalid AS valid
112
+ FROM pg_class c
113
+ JOIN pg_index i ON i.indexrelid = c.oid
114
+ JOIN pg_namespace n ON n.oid = c.relnamespace
115
+ WHERE c.relname = $1 AND n.nspname = 'public'`;
108
116
  /**
109
- * Idempotent, concurrent-boot-safe build of idx_hmsh_esc_resolved_cover.
117
+ * Idempotent, concurrent-boot-safe build of one app-layer index.
110
118
  *
111
119
  * A crashed CONCURRENTLY build leaves an INVALID index behind, and
112
120
  * `CREATE INDEX CONCURRENTLY IF NOT EXISTS` would then skip it forever — so
@@ -114,20 +122,20 @@ async function installEscalationCompatView() {
114
122
  * A create that loses a cross-container race logs and defers to the next
115
123
  * boot, which observes the winner's valid index and no-ops.
116
124
  */
117
- async function ensureResolvedCoverIndex() {
125
+ async function ensureAppIndex(name, ddl) {
118
126
  const pool = (0, db_1.getPool)();
119
127
  try {
120
- const { rows } = await pool.query(RESOLVED_COVER_INDEX_STATE);
128
+ const { rows } = await pool.query(APP_INDEX_STATE, [name]);
121
129
  if (rows[0]?.valid === true)
122
130
  return;
123
131
  if (rows.length > 0) {
124
- await pool.query('DROP INDEX IF EXISTS idx_hmsh_esc_resolved_cover');
132
+ await pool.query(`DROP INDEX IF EXISTS ${name}`);
125
133
  }
126
- await pool.query(ENSURE_RESOLVED_COVER_INDEX);
127
- logger_1.loggerRegistry.info('[escalation] idx_hmsh_esc_resolved_cover ensured');
134
+ await pool.query(ddl);
135
+ logger_1.loggerRegistry.info(`[escalation] ${name} ensured`);
128
136
  }
129
137
  catch (err) {
130
- logger_1.loggerRegistry.warn(`[escalation] resolved-cover index build deferred to next boot: ${err.message}`);
138
+ logger_1.loggerRegistry.warn(`[escalation] ${name} index build deferred to next boot: ${err.message}`);
131
139
  }
132
140
  }
133
141
  // Migrate legacy `lt_escalations` rows into `hmsh_escalations` (idempotent),
@@ -188,16 +196,38 @@ BEGIN
188
196
  END IF;
189
197
  END $$;`;
190
198
  // `available` mirrors the legacy isEffectivelyClaimed/isAvailable heuristic so
191
- // existing `SELECT *` consumers are unaffected; the column is additive.
199
+ // existing `SELECT *` consumers are unaffected; the columns are additive.
200
+ //
201
+ // `ended_at` is the instant the row left the live set: resolve stamps
202
+ // resolved_at in its guarded transition; cancel and expire stamp only
203
+ // updated_at in theirs, and no long-tail write path touches a terminal row
204
+ // afterward, so COALESCE(resolved_at, updated_at) is the transition instant
205
+ // for all three terminal statuses. (The SDK's updateEscalation /
206
+ // appendEscalationMilestones carry no status guard, so a post-terminal touch
207
+ // WOULD skew a cancelled/expired end — documented contract, not enforced.)
208
+ // Analytics SQL inlines the equivalent expression rather than reading this
209
+ // column (so non-default liveStatuses compose and the partial index predicate
210
+ // stays provable); the view column serves external and debugging consumers.
211
+ // If the SDK ever ships a physical ended_at column, the SELECT * expansion
212
+ // collides with this alias — remediation is dropping the view-level column
213
+ // and reading the (strictly better) real one.
214
+ //
215
+ // DROP+CREATE (not CREATE OR REPLACE) because the base table's column set
216
+ // moves with SDK migrations: REPLACE re-expands SELECT * and errors the
217
+ // moment a new base column lands before the computed aliases. The swap runs
218
+ // inside the install transaction, so readers see the old view or the new
219
+ // one, never a gap.
192
220
  const CREATE_COMPAT_VIEW = `
193
- CREATE OR REPLACE VIEW public.lt_escalations AS
221
+ DROP VIEW IF EXISTS public.lt_escalations;
222
+ CREATE VIEW public.lt_escalations AS
194
223
  SELECT *,
195
- (assigned_to IS NULL OR assigned_until IS NULL OR assigned_until <= NOW()) AS available
224
+ (assigned_to IS NULL OR assigned_until IS NULL OR assigned_until <= NOW()) AS available,
225
+ (CASE WHEN status <> 'pending' THEN COALESCE(resolved_at, updated_at) END) AS ended_at
196
226
  FROM public.hmsh_escalations;`;
197
227
  // Index ownership — hmsh 0.25.0 ships the general aggregate indexes on
198
228
  // hmsh_escalations (idx_hmsh_esc_stats_pending covers the pending backlog
199
229
  // incl. assigned_to/assigned_until; _stats_created and _stats_resolved bound
200
- // the created/resolved windows). Long-tail keeps exactly ONE app-layer index:
230
+ // the created/resolved windows). Long-tail keeps exactly TWO app-layer indexes:
201
231
  //
202
232
  // idx_hmsh_esc_resolved_cover (role, resolved_at DESC, claimed_at, created_at)
203
233
  // WHERE status = 'resolved'
@@ -206,6 +236,15 @@ CREATE OR REPLACE VIEW public.lt_escalations AS
206
236
  // trailing claimed_at/created_at feed PERCENTILE_CONT(resolved_at -
207
237
  // claimed_at) and (claimed_at - created_at) from the index.
208
238
  //
239
+ // idx_hmsh_esc_ended_at ((COALESCE(resolved_at, updated_at)))
240
+ // WHERE status <> 'pending'
241
+ // Serves the analytics interval scans (aggregate-sql.ts): dwell and
242
+ // past-membership need "terminal rows whose end instant is after the
243
+ // window/asOf" — without this, every such query scans all terminal
244
+ // history, growing without bound. Write cost is the cheapest possible:
245
+ // inserts are 'pending' (excluded by the predicate), so exactly one
246
+ // index write occurs per row, at its terminal transition.
247
+ //
209
248
  // Everything else this branch once created is dropped below: the pending
210
249
  // index duplicated _stats_pending, and the claimed cover had no consuming
211
250
  // query — both were pure write amplification on the hot claim/resolve path.
@@ -218,15 +257,12 @@ const DROP_SUPERSEDED_PENDING_INDEX = `
218
257
  DROP INDEX IF EXISTS idx_hmsh_esc_pending_role_created`;
219
258
  const DROP_UNCONSUMED_CLAIMED_COVER_INDEX = `
220
259
  DROP INDEX IF EXISTS idx_hmsh_esc_claimed_cover`;
221
- // ── The station-percentile cover ─────────────────────────────────────────────
222
- /** Reports existence + validity so a crash-orphaned INVALID build is rebuilt. */
223
- const RESOLVED_COVER_INDEX_STATE = `
224
- SELECT i.indisvalid AS valid
225
- FROM pg_class c
226
- JOIN pg_index i ON i.indexrelid = c.oid
227
- JOIN pg_namespace n ON n.oid = c.relnamespace
228
- WHERE c.relname = 'idx_hmsh_esc_resolved_cover' AND n.nspname = 'public'`;
260
+ // ── The app-layer indexes (built via ensureAppIndex) ────────────────────────
229
261
  const ENSURE_RESOLVED_COVER_INDEX = `\
230
262
  CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_hmsh_esc_resolved_cover
231
263
  ON public.hmsh_escalations (role, resolved_at DESC, claimed_at, created_at)
232
264
  WHERE status = 'resolved'`;
265
+ const ENSURE_ENDED_AT_INDEX = `\
266
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_hmsh_esc_ended_at
267
+ ON public.hmsh_escalations ((COALESCE(resolved_at, updated_at)))
268
+ WHERE status <> 'pending'`;
@@ -22,6 +22,10 @@ exports.FACET_KEY = /^[a-zA-Z0-9_]+$/;
22
22
  function metaExpr(key, numeric = false) {
23
23
  return numeric ? `(metadata->>'${key}')::numeric` : `(metadata->>'${key}')`;
24
24
  }
25
+ /** Escape LIKE wildcards so a prefix VALUE is matched literally. */
26
+ function escapeLikePrefix(value) {
27
+ return value.replace(/[\\%_]/g, (c) => `\\${c}`);
28
+ }
25
29
  /**
26
30
  * Build the WHERE clause for a faceted query, pushing bound params into `params`.
27
31
  * Every value is parameterized; only validated column names / metadata keys and
@@ -51,10 +55,23 @@ function buildFacetWhere(q, params, opts = {}) {
51
55
  params.push(JSON.stringify(q.facets));
52
56
  clauses.push(`metadata @> $${params.length}::jsonb`); // GIN-served
53
57
  }
58
+ if (q.anyOf && q.anyOf.length) {
59
+ params.push(q.anyOf.map((a) => JSON.stringify(a)));
60
+ clauses.push(`metadata @> ANY($${params.length}::jsonb[])`); // GIN-served OR
61
+ }
54
62
  if (q.block && q.block.length) {
55
63
  params.push(q.block.map((b) => JSON.stringify(b)));
56
64
  clauses.push(`NOT (metadata @> ANY($${params.length}::jsonb[]))`);
57
65
  }
66
+ // Case-insensitive prefix on facet VALUES — the locate affordance. No
67
+ // dedicated index by design: the predicate composes into scans the rest of
68
+ // the filter already bounds (the live set, or an analytics window).
69
+ for (const [key, value] of Object.entries(q.prefix ?? {})) {
70
+ if (!exports.FACET_KEY.test(key) || typeof value !== 'string' || !value)
71
+ continue;
72
+ params.push(`${escapeLikePrefix(value)}%`);
73
+ clauses.push(`${metaExpr(key)} ILIKE $${params.length}`);
74
+ }
58
75
  for (const r of q.range ?? []) {
59
76
  if (!exports.FACET_KEY.test(r.facet) || !RANGE_OPS.has(r.op))
60
77
  continue;
@@ -5,3 +5,5 @@ export * from './queries';
5
5
  export * from './resolver-validation';
6
6
  export { ensureEscalationCompatView } from './client';
7
7
  export { ensureFacetReady, searchByFacets, searchGroups, countByFacets, claimGroups, claimByFacets, } from './facets';
8
+ export { aggregateByFacets, timelineByFacet, resolveEntitySystem, resetAnalyticsCaches, } from './aggregates';
9
+ export { AnalyticsInputError } from './aggregate-validate';
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.claimByFacets = exports.claimGroups = exports.countByFacets = exports.searchGroups = exports.searchByFacets = exports.ensureFacetReady = exports.ensureEscalationCompatView = void 0;
17
+ exports.AnalyticsInputError = exports.resetAnalyticsCaches = exports.resolveEntitySystem = exports.timelineByFacet = exports.aggregateByFacets = exports.claimByFacets = exports.claimGroups = exports.countByFacets = exports.searchGroups = exports.searchByFacets = exports.ensureFacetReady = exports.ensureEscalationCompatView = void 0;
18
18
  __exportStar(require("./types"), exports);
19
19
  __exportStar(require("./crud"), exports);
20
20
  __exportStar(require("./bulk"), exports);
@@ -29,3 +29,10 @@ Object.defineProperty(exports, "searchGroups", { enumerable: true, get: function
29
29
  Object.defineProperty(exports, "countByFacets", { enumerable: true, get: function () { return facets_1.countByFacets; } });
30
30
  Object.defineProperty(exports, "claimGroups", { enumerable: true, get: function () { return facets_1.claimGroups; } });
31
31
  Object.defineProperty(exports, "claimByFacets", { enumerable: true, get: function () { return facets_1.claimByFacets; } });
32
+ var aggregates_1 = require("./aggregates");
33
+ Object.defineProperty(exports, "aggregateByFacets", { enumerable: true, get: function () { return aggregates_1.aggregateByFacets; } });
34
+ Object.defineProperty(exports, "timelineByFacet", { enumerable: true, get: function () { return aggregates_1.timelineByFacet; } });
35
+ Object.defineProperty(exports, "resolveEntitySystem", { enumerable: true, get: function () { return aggregates_1.resolveEntitySystem; } });
36
+ Object.defineProperty(exports, "resetAnalyticsCaches", { enumerable: true, get: function () { return aggregates_1.resetAnalyticsCaches; } });
37
+ var aggregate_validate_1 = require("./aggregate-validate");
38
+ Object.defineProperty(exports, "AnalyticsInputError", { enumerable: true, get: function () { return aggregate_validate_1.AnalyticsInputError; } });
@@ -12,3 +12,21 @@ export declare class TtlCache<T> {
12
12
  resolve(key: string, compute: () => Promise<T>): Promise<T>;
13
13
  clear(): void;
14
14
  }
15
+ /**
16
+ * TtlCache with an LRU bound — for key spaces the caller doesn't control.
17
+ *
18
+ * The analytics caches key by a canonical stringify of the whole query input,
19
+ * so distinct keys are unbounded; the expired-entry sweep alone can't bound
20
+ * memory the way it does for TtlCache's small role-set × period key space.
21
+ * Same single-flight and reject-evict semantics; a hit refreshes recency
22
+ * (Map delete+set, the enforcement-cache pattern), and inserting past
23
+ * `maxEntries` evicts the least-recently-used entry.
24
+ */
25
+ export declare class BoundedTtlCache<T> {
26
+ private readonly ttlMs;
27
+ private readonly maxEntries;
28
+ private readonly store;
29
+ constructor(ttlMs: number, maxEntries: number);
30
+ resolve(key: string, compute: () => Promise<T>): Promise<T>;
31
+ clear(): void;
32
+ }
@@ -12,7 +12,7 @@
12
12
  // container drift within the TTL is acceptable. No cross-node coordination.
13
13
  // ---------------------------------------------------------------------------
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.TtlCache = void 0;
15
+ exports.BoundedTtlCache = exports.TtlCache = void 0;
16
16
  class TtlCache {
17
17
  constructor(ttlMs) {
18
18
  this.ttlMs = ttlMs;
@@ -51,3 +51,49 @@ class TtlCache {
51
51
  }
52
52
  }
53
53
  exports.TtlCache = TtlCache;
54
+ /**
55
+ * TtlCache with an LRU bound — for key spaces the caller doesn't control.
56
+ *
57
+ * The analytics caches key by a canonical stringify of the whole query input,
58
+ * so distinct keys are unbounded; the expired-entry sweep alone can't bound
59
+ * memory the way it does for TtlCache's small role-set × period key space.
60
+ * Same single-flight and reject-evict semantics; a hit refreshes recency
61
+ * (Map delete+set, the enforcement-cache pattern), and inserting past
62
+ * `maxEntries` evicts the least-recently-used entry.
63
+ */
64
+ class BoundedTtlCache {
65
+ constructor(ttlMs, maxEntries) {
66
+ this.ttlMs = ttlMs;
67
+ this.maxEntries = maxEntries;
68
+ this.store = new Map();
69
+ }
70
+ async resolve(key, compute) {
71
+ const hit = this.store.get(key);
72
+ if (hit && Date.now() - hit.at < this.ttlMs) {
73
+ this.store.delete(key); // refresh recency
74
+ this.store.set(key, hit);
75
+ return hit.value;
76
+ }
77
+ for (const [k, entry] of this.store) {
78
+ if (Date.now() - entry.at >= this.ttlMs)
79
+ this.store.delete(k);
80
+ }
81
+ while (this.store.size >= this.maxEntries) {
82
+ const oldest = this.store.keys().next().value;
83
+ this.store.delete(oldest);
84
+ }
85
+ const value = compute();
86
+ this.store.set(key, { at: Date.now(), value });
87
+ try {
88
+ return await value;
89
+ }
90
+ catch (err) {
91
+ this.store.delete(key);
92
+ throw err;
93
+ }
94
+ }
95
+ clear() {
96
+ this.store.clear();
97
+ }
98
+ }
99
+ exports.BoundedTtlCache = BoundedTtlCache;
@@ -17,6 +17,7 @@ const facet_sql_1 = require("./facet-sql");
17
17
  const types_1 = require("./types");
18
18
  const sql_1 = require("./sql");
19
19
  const metrics_cache_1 = require("./metrics-cache");
20
+ const aggregates_1 = require("./aggregates");
20
21
  /**
21
22
  * Default sort is priority ASC, created_at ASC. A user-chosen `sort_by` maps to
22
23
  * a single column (DESC unless `order='asc'`), matching the legacy behavior.
@@ -380,6 +381,7 @@ const stationPeriodCache = new metrics_cache_1.TtlCache(STATION_PERIOD_CACHE_TTL
380
381
  function invalidateEscalationAggregates() {
381
382
  stationPeriodCache.clear();
382
383
  escalationStatsCache.clear();
384
+ (0, aggregates_1.clearNowAnchoredAnalyticsCache)();
383
385
  }
384
386
  /** Test hook: drop cached period metrics AND escalation stats so tests observe fresh rows. */
385
387
  function resetStationMetricsCache() {
@@ -23,6 +23,6 @@ export interface ResolverSchemaViolationReport {
23
23
  * surface's task fallback); by default the row's own envelope column is used.
24
24
  * It is only invoked when the role actually enforces.
25
25
  */
26
- export declare function checkResolverPayload(escalation: ResolvableEscalationRow, resolverPayload: Record<string, any>, loadEnvelope?: () => Promise<Record<string, any>>): Promise<ResolverSchemaViolationReport | null>;
26
+ export declare function checkResolverPayload(escalation: ResolvableEscalationRow, resolverPayload: Record<string, any>, loadEnvelope?: () => Promise<Record<string, any>>, resolvingUserId?: string): Promise<ResolverSchemaViolationReport | null>;
27
27
  /** The canonical 422 body for a violation report (types/validation.ts). */
28
28
  export declare function toValidationErrorBody(report: ResolverSchemaViolationReport): LTValidationErrorBody;
@@ -22,6 +22,7 @@ exports.toValidationErrorBody = toValidationErrorBody;
22
22
  * the gate passes.
23
23
  */
24
24
  const form_validation_1 = require("../../shared/form-validation");
25
+ const submit_guard_1 = require("./submit-guard");
25
26
  const enforcement_cache_1 = require("../role/enforcement-cache");
26
27
  const escalation_1 = require("../../types/escalation");
27
28
  const validation_1 = require("../../types/validation");
@@ -54,7 +55,7 @@ function pinnedVersionOf(metadata) {
54
55
  * surface's task fallback); by default the row's own envelope column is used.
55
56
  * It is only invoked when the role actually enforces.
56
57
  */
57
- async function checkResolverPayload(escalation, resolverPayload, loadEnvelope) {
58
+ async function checkResolverPayload(escalation, resolverPayload, loadEnvelope, resolvingUserId) {
58
59
  const role = escalation.role;
59
60
  if (!role)
60
61
  return null;
@@ -72,15 +73,21 @@ async function checkResolverPayload(escalation, resolverPayload, loadEnvelope) {
72
73
  const envelope = loadEnvelope
73
74
  ? await loadEnvelope()
74
75
  : parseJsonOrNull(escalation.envelope) ?? {};
75
- const violations = (0, form_validation_1.validateResolverPayload)(schema, resolverPayload, {
76
+ const ctx = {
76
77
  escalation: escalation,
77
78
  metadata,
78
79
  envelope,
79
80
  payload: parseJsonOrNull(escalation.escalation_payload),
80
- });
81
- if (violations.length === 0)
81
+ };
82
+ const violations = (0, form_validation_1.validateResolverPayload)(schema, resolverPayload, ctx);
83
+ // The query precondition (x-lt-submit-guard): reject while the embedded query
84
+ // still returns rows. Enforced on the same enforce_schema opt-in as the field
85
+ // contract, so a raw-API resolve honors the gate the dashboard shows.
86
+ const guardViolation = await (0, submit_guard_1.checkSubmitGuard)(schema, ctx, resolvingUserId);
87
+ const allViolations = guardViolation ? [...violations, guardViolation] : violations;
88
+ if (allViolations.length === 0)
82
89
  return null;
83
- return { role, schemaVersion: pin, violations };
90
+ return { role, schemaVersion: pin, violations: allViolations };
84
91
  }
85
92
  /** The canonical 422 body for a violation report (types/validation.ts). */
86
93
  function toValidationErrorBody(report) {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Server-side enforcement of the `x-lt-submit-guard` query precondition — the
3
+ * API-first counterpart of the dashboard's `useSubmitGuard`. Before an
4
+ * escalation resolves, if its form schema declares a submit guard, the same
5
+ * embedded query runs and the resolve is REJECTED while it still returns rows.
6
+ *
7
+ * Isomorphism with the dashboard is exact: the guard reuses `resolveScopedQuery`
8
+ * (the mapping the escalation-list embed uses) and runs it through
9
+ * `searchEscalationsFaceted` — the same faceted query the list API composes.
10
+ *
11
+ * Two deliberate rules:
12
+ * - The count is taken with `global: true`, bypassing the resolver's read
13
+ * scope, so a role that cannot SEE the children can never falsely clear the
14
+ * gate. Only a real, empty result count clears it (the "403 ≠ empty" rule).
15
+ * - A guard whose scope cannot be evaluated (e.g. `assigned:'me'` on a surface
16
+ * with no resolving user, like an MCP resolve) is skipped here; the consuming
17
+ * workflow's own verification remains the durable backstop.
18
+ */
19
+ import { type ShowIfContext } from '../../shared/form-validation';
20
+ import type { LTFieldViolation } from '../../types/validation';
21
+ /** The synthetic violation field for a guard rejection — not a form field. */
22
+ export declare const SUBMIT_GUARD_VIOLATION_FIELD = "_submitGuard";
23
+ /**
24
+ * Returns a violation when the escalation's submit guard still matches rows, or
25
+ * null when there is no guard, its scope is inert, or the query is confirmed
26
+ * empty. The context carries the escalation domains the query facets and the
27
+ * message interpolate against.
28
+ */
29
+ export declare function checkSubmitGuard(schema: Record<string, any> | null | undefined, ctx: ShowIfContext, resolvingUserId: string | undefined): Promise<LTFieldViolation | null>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUBMIT_GUARD_VIOLATION_FIELD = void 0;
4
+ exports.checkSubmitGuard = checkSubmitGuard;
5
+ /**
6
+ * Server-side enforcement of the `x-lt-submit-guard` query precondition — the
7
+ * API-first counterpart of the dashboard's `useSubmitGuard`. Before an
8
+ * escalation resolves, if its form schema declares a submit guard, the same
9
+ * embedded query runs and the resolve is REJECTED while it still returns rows.
10
+ *
11
+ * Isomorphism with the dashboard is exact: the guard reuses `resolveScopedQuery`
12
+ * (the mapping the escalation-list embed uses) and runs it through
13
+ * `searchEscalationsFaceted` — the same faceted query the list API composes.
14
+ *
15
+ * Two deliberate rules:
16
+ * - The count is taken with `global: true`, bypassing the resolver's read
17
+ * scope, so a role that cannot SEE the children can never falsely clear the
18
+ * gate. Only a real, empty result count clears it (the "403 ≠ empty" rule).
19
+ * - A guard whose scope cannot be evaluated (e.g. `assigned:'me'` on a surface
20
+ * with no resolving user, like an MCP resolve) is skipped here; the consuming
21
+ * workflow's own verification remains the durable backstop.
22
+ */
23
+ const form_validation_1 = require("../../shared/form-validation");
24
+ const queries_1 = require("./queries");
25
+ /** The synthetic violation field for a guard rejection — not a form field. */
26
+ exports.SUBMIT_GUARD_VIOLATION_FIELD = '_submitGuard';
27
+ /**
28
+ * Returns a violation when the escalation's submit guard still matches rows, or
29
+ * null when there is no guard, its scope is inert, or the query is confirmed
30
+ * empty. The context carries the escalation domains the query facets and the
31
+ * message interpolate against.
32
+ */
33
+ async function checkSubmitGuard(schema, ctx, resolvingUserId) {
34
+ const guard = (0, form_validation_1.readSubmitGuard)(schema);
35
+ if (!guard || guard.mustBeEmpty === false)
36
+ return null;
37
+ const scoped = (0, form_validation_1.resolveScopedQuery)(guard.query, ctx, resolvingUserId);
38
+ if (!scoped.enabled)
39
+ return null;
40
+ const { total } = await (0, queries_1.searchEscalationsFaceted)({
41
+ global: true,
42
+ facet: {
43
+ role: scoped.role,
44
+ status: scoped.status,
45
+ available: scoped.available,
46
+ facets: scoped.facets,
47
+ },
48
+ assigned_to: scoped.assigned_to,
49
+ limit: 1,
50
+ offset: 0,
51
+ });
52
+ if (total <= 0)
53
+ return null;
54
+ const template = guard.message ?? form_validation_1.SUBMIT_GUARD_DEFAULT_MESSAGE;
55
+ const message = (0, form_validation_1.interpolateHelp)(template.replace(/\{\{count\}\}/g, String(total)), ctx);
56
+ return { field: exports.SUBMIT_GUARD_VIOLATION_FIELD, message };
57
+ }
@@ -0,0 +1,16 @@
1
+ import type { LTApiAuth } from '../../types/sdk';
2
+ /** The acting-auth resolution result: an auth to act as, or the loud reason not to. */
3
+ export type ActingAuthResult = {
4
+ ok: true;
5
+ auth: LTApiAuth;
6
+ } | {
7
+ ok: false;
8
+ error: string;
9
+ };
10
+ /**
11
+ * Exchange a supplied acting grant for the person it names. A supplied token
12
+ * that fails — wrong label, expired, exhausted, or a vanished user — is a
13
+ * terminal error, never a silent fall-back to the session identity: that
14
+ * would misattribute whatever follows.
15
+ */
16
+ export declare function resolveActingAuth(actingToken: string): Promise<ActingAuthResult>;