@hotmeshio/long-tail 0.24.1 → 0.25.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 (294) hide show
  1. package/build/api/escalations/accumulate.js +8 -4
  2. package/build/api/escalations/metadata.js +4 -3
  3. package/build/api/escalations/related.d.ts +1 -1
  4. package/build/api/escalations/related.js +28 -18
  5. package/build/lib/db/schemas/040_oauth_client_consent.sql +15 -0
  6. package/build/modules/config.d.ts +4 -0
  7. package/build/modules/config.js +4 -0
  8. package/build/modules/maintenance.d.ts +1 -1
  9. package/build/modules/maintenance.js +1 -1
  10. package/build/services/auth/oauth-server/index.d.ts +1 -1
  11. package/build/services/auth/oauth-server/index.js +2 -1
  12. package/build/services/auth/oauth-server/sql.d.ts +7 -4
  13. package/build/services/auth/oauth-server/sql.js +10 -4
  14. package/build/services/auth/oauth-server/store.d.ts +8 -1
  15. package/build/services/auth/oauth-server/store.js +31 -2
  16. package/build/services/escalation/accumulate.d.ts +3 -1
  17. package/build/services/escalation/facet-sql.js +4 -2
  18. package/build/services/escalation/index.d.ts +1 -0
  19. package/build/services/escalation/index.js +4 -1
  20. package/build/services/escalation/related-extremes.d.ts +17 -0
  21. package/build/services/escalation/related-extremes.js +65 -0
  22. package/build/services/escalation/related.d.ts +4 -0
  23. package/build/services/escalation/related.js +2 -0
  24. package/build/services/escalation/resolver-validation.js +7 -0
  25. package/build/services/escalation/roster.d.ts +13 -0
  26. package/build/services/escalation/roster.js +37 -0
  27. package/build/services/escalation/submit-guard.d.ts +3 -0
  28. package/build/services/escalation/submit-guard.js +21 -2
  29. package/build/services/maintenance/index.js +1 -1
  30. package/build/services/mcp/server-tools.js +1 -1
  31. package/build/shared/form-validation/display-only-widgets.d.ts +1 -0
  32. package/build/shared/form-validation/display-only-widgets.js +1 -0
  33. package/build/shared/form-validation/index.d.ts +1 -0
  34. package/build/shared/form-validation/index.js +1 -0
  35. package/build/shared/form-validation/validate-resolver-payload.d.ts +1 -0
  36. package/build/shared/form-validation/validate-resolver-payload.js +2 -1
  37. package/build/shared/form-validation/x-lt-help.d.ts +1 -1
  38. package/build/shared/form-validation/x-lt-help.js +2 -1
  39. package/build/shared/form-validation/x-lt-roster.d.ts +92 -0
  40. package/build/shared/form-validation/x-lt-roster.js +234 -0
  41. package/build/shared/form-validation/x-lt-show-if.d.ts +8 -0
  42. package/build/shared/form-validation/x-lt-show-if.js +13 -0
  43. package/build/shared/form-validation/x-lt-submit-guard.d.ts +14 -1
  44. package/build/shared/form-validation/x-lt-submit-guard.js +4 -1
  45. package/build/shared/list-schema/aggregate.d.ts +19 -0
  46. package/build/shared/list-schema/aggregate.js +66 -0
  47. package/build/shared/list-schema/index.d.ts +2 -0
  48. package/build/shared/list-schema/index.js +7 -1
  49. package/build/shared/list-schema/types.d.ts +15 -2
  50. package/build/shared/list-schema/types.js +6 -1
  51. package/build/shared/list-schema/validate.js +28 -2
  52. package/build/tsconfig.tsbuildinfo +1 -1
  53. package/dashboard/dist/assets/{AdminDashboard-B3mkMIOT.js → AdminDashboard-DWVRAu63.js} +2 -2
  54. package/dashboard/dist/assets/{AdminDashboard-B3mkMIOT.js.map → AdminDashboard-DWVRAu63.js.map} +1 -1
  55. package/dashboard/dist/assets/{AgentConfigPage-CQj2B678.js → AgentConfigPage-Exjtjbvp.js} +6 -6
  56. package/dashboard/dist/assets/{AgentConfigPage-CQj2B678.js.map → AgentConfigPage-Exjtjbvp.js.map} +1 -1
  57. package/dashboard/dist/assets/{AgentDetailPage-CzrD8JJI.js → AgentDetailPage-nDbZo6RB.js} +3 -3
  58. package/dashboard/dist/assets/{AgentDetailPage-CzrD8JJI.js.map → AgentDetailPage-nDbZo6RB.js.map} +1 -1
  59. package/dashboard/dist/assets/{AgentsPage-jg2OFwyW.js → AgentsPage-Dsk9iHps.js} +2 -2
  60. package/dashboard/dist/assets/{AgentsPage-jg2OFwyW.js.map → AgentsPage-Dsk9iHps.js.map} +1 -1
  61. package/dashboard/dist/assets/AvailableEscalationsPage-D1mCqj8Z.js +2 -0
  62. package/dashboard/dist/assets/{AvailableEscalationsPage-IS28XBRe.js.map → AvailableEscalationsPage-D1mCqj8Z.js.map} +1 -1
  63. package/dashboard/dist/assets/{BotPicker-DvEJFpwS.js → BotPicker-u5760MWZ.js} +2 -2
  64. package/dashboard/dist/assets/{BotPicker-DvEJFpwS.js.map → BotPicker-u5760MWZ.js.map} +1 -1
  65. package/dashboard/dist/assets/{BulkAssignModal-BoQCi-YC.js → BulkAssignModal-CtISXXmW.js} +2 -2
  66. package/dashboard/dist/assets/{BulkAssignModal-BoQCi-YC.js.map → BulkAssignModal-CtISXXmW.js.map} +1 -1
  67. package/dashboard/dist/assets/{CapabilitiesPage-kwlrcb_t.js → CapabilitiesPage-BKIlrQfU.js} +2 -2
  68. package/dashboard/dist/assets/{CapabilitiesPage-kwlrcb_t.js.map → CapabilitiesPage-BKIlrQfU.js.map} +1 -1
  69. package/dashboard/dist/assets/{CollapsibleSection-UMIV4KIj.js → CollapsibleSection-DlBVTFqc.js} +2 -2
  70. package/dashboard/dist/assets/{CollapsibleSection-UMIV4KIj.js.map → CollapsibleSection-DlBVTFqc.js.map} +1 -1
  71. package/dashboard/dist/assets/{ConfirmDeleteModal-C5puwMg7.js → ConfirmDeleteModal-Pabkchg_.js} +2 -2
  72. package/dashboard/dist/assets/{ConfirmDeleteModal-C5puwMg7.js.map → ConfirmDeleteModal-Pabkchg_.js.map} +1 -1
  73. package/dashboard/dist/assets/{ConnectedAppsPage-drZa65kP.js → ConnectedAppsPage-DG2YZGN3.js} +2 -2
  74. package/dashboard/dist/assets/{ConnectedAppsPage-drZa65kP.js.map → ConnectedAppsPage-DG2YZGN3.js.map} +1 -1
  75. package/dashboard/dist/assets/CountdownTimer-5t8S2qMi.js +2 -0
  76. package/dashboard/dist/assets/CountdownTimer-5t8S2qMi.js.map +1 -0
  77. package/dashboard/dist/assets/{CredentialsPage-CaFQ6vLb.js → CredentialsPage-CPVbYQow.js} +2 -2
  78. package/dashboard/dist/assets/{CredentialsPage-CaFQ6vLb.js.map → CredentialsPage-CPVbYQow.js.map} +1 -1
  79. package/dashboard/dist/assets/{CronLabel-Co5j7tzW.js → CronLabel-C7UbTrO-.js} +2 -2
  80. package/dashboard/dist/assets/{CronLabel-Co5j7tzW.js.map → CronLabel-C7UbTrO-.js.map} +1 -1
  81. package/dashboard/dist/assets/{DropZone-B1Tdfddc.js → DropZone-DCZpORx-.js} +2 -2
  82. package/dashboard/dist/assets/{DropZone-B1Tdfddc.js.map → DropZone-DCZpORx-.js.map} +1 -1
  83. package/dashboard/dist/assets/{ElapsedCell-WJyE9G0T.js → ElapsedCell-Yz7-LbtE.js} +2 -2
  84. package/dashboard/dist/assets/{ElapsedCell-WJyE9G0T.js.map → ElapsedCell-Yz7-LbtE.js.map} +1 -1
  85. package/dashboard/dist/assets/{EntityTimelinePanel-CDFFfX9d.js → EntityTimelinePanel-xgPRdKEq.js} +2 -2
  86. package/dashboard/dist/assets/{EntityTimelinePanel-CDFFfX9d.js.map → EntityTimelinePanel-xgPRdKEq.js.map} +1 -1
  87. package/dashboard/dist/assets/{EscalationListSchemaPage-CMrUlaKI.js → EscalationListSchemaPage-CqCd-y6B.js} +3 -3
  88. package/dashboard/dist/assets/{EscalationListSchemaPage-CMrUlaKI.js.map → EscalationListSchemaPage-CqCd-y6B.js.map} +1 -1
  89. package/dashboard/dist/assets/{EscalationSchemaPage-Dx2Lyfrz.js → EscalationSchemaPage-D8YVQNph.js} +3 -3
  90. package/dashboard/dist/assets/{EscalationSchemaPage-Dx2Lyfrz.js.map → EscalationSchemaPage-D8YVQNph.js.map} +1 -1
  91. package/dashboard/dist/assets/EscalationTimeline-D6jrDcBb.js +2 -0
  92. package/dashboard/dist/assets/{EscalationTimeline-d3nvshOk.js.map → EscalationTimeline-D6jrDcBb.js.map} +1 -1
  93. package/dashboard/dist/assets/{EscalationTitleSelect-DWQieOM9.js → EscalationTitleSelect-CGGT-QTu.js} +2 -2
  94. package/dashboard/dist/assets/{EscalationTitleSelect-DWQieOM9.js.map → EscalationTitleSelect-CGGT-QTu.js.map} +1 -1
  95. package/dashboard/dist/assets/{EscalationsOverview-BX8Cpi0X.js → EscalationsOverview-B7O4ttP4.js} +2 -2
  96. package/dashboard/dist/assets/{EscalationsOverview-BX8Cpi0X.js.map → EscalationsOverview-B7O4ttP4.js.map} +1 -1
  97. package/dashboard/dist/assets/{EventTable-D1-ITVXS.js → EventTable-DJniXnhe.js} +2 -2
  98. package/dashboard/dist/assets/{EventTable-D1-ITVXS.js.map → EventTable-DJniXnhe.js.map} +1 -1
  99. package/dashboard/dist/assets/{FilterBar-ABC9ctin.js → FilterBar-f-LEKcHI.js} +2 -2
  100. package/dashboard/dist/assets/{FilterBar-ABC9ctin.js.map → FilterBar-f-LEKcHI.js.map} +1 -1
  101. package/dashboard/dist/assets/{FormattedValue-k27T4bG7.js → FormattedValue-CUtv1g3k.js} +2 -2
  102. package/dashboard/dist/assets/{FormattedValue-k27T4bG7.js.map → FormattedValue-CUtv1g3k.js.map} +1 -1
  103. package/dashboard/dist/assets/{GraphInvokePage-DKUErxWc.js → GraphInvokePage-CR0MtYv8.js} +2 -2
  104. package/dashboard/dist/assets/{GraphInvokePage-DKUErxWc.js.map → GraphInvokePage-CR0MtYv8.js.map} +1 -1
  105. package/dashboard/dist/assets/{HomePage-C6u9dIpR.js → HomePage-HVw7T5ER.js} +2 -2
  106. package/dashboard/dist/assets/{HomePage-C6u9dIpR.js.map → HomePage-HVw7T5ER.js.map} +1 -1
  107. package/dashboard/dist/assets/{ListToolbar-gA5rFzkz.js → ListToolbar-CTGK5BXW.js} +2 -2
  108. package/dashboard/dist/assets/{ListToolbar-gA5rFzkz.js.map → ListToolbar-CTGK5BXW.js.map} +1 -1
  109. package/dashboard/dist/assets/{McpOverview-BOPo3J_i.js → McpOverview-B2bbKKu2.js} +2 -2
  110. package/dashboard/dist/assets/{McpOverview-BOPo3J_i.js.map → McpOverview-B2bbKKu2.js.map} +1 -1
  111. package/dashboard/dist/assets/{McpQueryDetailPage-BRVVi0Zh.js → McpQueryDetailPage-DgLxwwJy.js} +2 -2
  112. package/dashboard/dist/assets/{McpQueryDetailPage-BRVVi0Zh.js.map → McpQueryDetailPage-DgLxwwJy.js.map} +1 -1
  113. package/dashboard/dist/assets/{McpQueryPage-Dmbb6SAc.js → McpQueryPage-CSkczeJP.js} +2 -2
  114. package/dashboard/dist/assets/{McpQueryPage-Dmbb6SAc.js.map → McpQueryPage-CSkczeJP.js.map} +1 -1
  115. package/dashboard/dist/assets/McpRunDetailPage-CrUsDPRD.js +2 -0
  116. package/dashboard/dist/assets/{McpRunDetailPage-bMXNO-6V.js.map → McpRunDetailPage-CrUsDPRD.js.map} +1 -1
  117. package/dashboard/dist/assets/{McpRunsPage-Bi9AMWja.js → McpRunsPage-twxFdTtJ.js} +2 -2
  118. package/dashboard/dist/assets/{McpRunsPage-Bi9AMWja.js.map → McpRunsPage-twxFdTtJ.js.map} +1 -1
  119. package/dashboard/dist/assets/{NamespacePill-DwM5pNA4.js → NamespacePill-CSZT2fA4.js} +2 -2
  120. package/dashboard/dist/assets/{NamespacePill-DwM5pNA4.js.map → NamespacePill-CSZT2fA4.js.map} +1 -1
  121. package/dashboard/dist/assets/{OperationsPage-CvR74ZUJ.js → OperationsPage-gOVMrL_a.js} +3 -3
  122. package/dashboard/dist/assets/{OperationsPage-CvR74ZUJ.js.map → OperationsPage-gOVMrL_a.js.map} +1 -1
  123. package/dashboard/dist/assets/OperatorDashboard-BoeZIyup.js +2 -0
  124. package/dashboard/dist/assets/{OperatorDashboard-DctmZw10.js.map → OperatorDashboard-BoeZIyup.js.map} +1 -1
  125. package/dashboard/dist/assets/{PageHeader-CMd3f4bz.js → PageHeader-C4z1ExF7.js} +2 -2
  126. package/dashboard/dist/assets/{PageHeader-CMd3f4bz.js.map → PageHeader-C4z1ExF7.js.map} +1 -1
  127. package/dashboard/dist/assets/{PageHeaderWithStats-DHj8Mqox.js → PageHeaderWithStats-B_f_Sz_Q.js} +2 -2
  128. package/dashboard/dist/assets/{PageHeaderWithStats-DHj8Mqox.js.map → PageHeaderWithStats-B_f_Sz_Q.js.map} +1 -1
  129. package/dashboard/dist/assets/{PersonaDetailPage-Ubqm2rQH.js → PersonaDetailPage-CDktk1mi.js} +2 -2
  130. package/dashboard/dist/assets/{PersonaDetailPage-Ubqm2rQH.js.map → PersonaDetailPage-CDktk1mi.js.map} +1 -1
  131. package/dashboard/dist/assets/{PersonasPage-BpFL2rpo.js → PersonasPage-Bs097x07.js} +2 -2
  132. package/dashboard/dist/assets/{PersonasPage-BpFL2rpo.js.map → PersonasPage-Bs097x07.js.map} +1 -1
  133. package/dashboard/dist/assets/PortalPage-DjfjerpF.js +2 -0
  134. package/dashboard/dist/assets/{PortalPage-Dfc_Q02R.js.map → PortalPage-DjfjerpF.js.map} +1 -1
  135. package/dashboard/dist/assets/{ProcessDetailPage-liJspvQh.js → ProcessDetailPage-B30RatjD.js} +2 -2
  136. package/dashboard/dist/assets/{ProcessDetailPage-liJspvQh.js.map → ProcessDetailPage-B30RatjD.js.map} +1 -1
  137. package/dashboard/dist/assets/{ProcessesListPage-C3OM5ZUy.js → ProcessesListPage-DrvjzgWT.js} +2 -2
  138. package/dashboard/dist/assets/{ProcessesListPage-C3OM5ZUy.js.map → ProcessesListPage-DrvjzgWT.js.map} +1 -1
  139. package/dashboard/dist/assets/ResolverForm-Bl8Pibyz.js +8 -0
  140. package/dashboard/dist/assets/ResolverForm-Bl8Pibyz.js.map +1 -0
  141. package/dashboard/dist/assets/{RoleDetailPage-CrlYCh-R.js → RoleDetailPage-ClYgn-3d.js} +3 -3
  142. package/dashboard/dist/assets/{RoleDetailPage-CrlYCh-R.js.map → RoleDetailPage-ClYgn-3d.js.map} +1 -1
  143. package/dashboard/dist/assets/{RolePill-Di2CAHuD.js → RolePill-Bw_n7EpR.js} +2 -2
  144. package/dashboard/dist/assets/{RolePill-Di2CAHuD.js.map → RolePill-Bw_n7EpR.js.map} +1 -1
  145. package/dashboard/dist/assets/{RolesPage-BaazL_pt.js → RolesPage-B_3qWsvQ.js} +2 -2
  146. package/dashboard/dist/assets/{RolesPage-BaazL_pt.js.map → RolesPage-B_3qWsvQ.js.map} +1 -1
  147. package/dashboard/dist/assets/{RunAsSelector-DxuV0fa1.js → RunAsSelector-CbJT50Hp.js} +2 -2
  148. package/dashboard/dist/assets/{RunAsSelector-DxuV0fa1.js.map → RunAsSelector-CbJT50Hp.js.map} +1 -1
  149. package/dashboard/dist/assets/{ScanCodesPage-C0mvmYTq.js → ScanCodesPage-CIIDuh8w.js} +2 -2
  150. package/dashboard/dist/assets/{ScanCodesPage-C0mvmYTq.js.map → ScanCodesPage-CIIDuh8w.js.map} +1 -1
  151. package/dashboard/dist/assets/{ScanSchemeDetailPage-CiJgt5dZ.js → ScanSchemeDetailPage-Bd3P3eyh.js} +2 -2
  152. package/dashboard/dist/assets/{ScanSchemeDetailPage-CiJgt5dZ.js.map → ScanSchemeDetailPage-Bd3P3eyh.js.map} +1 -1
  153. package/dashboard/dist/assets/ScanStationPage-BiO34nov.js +2 -0
  154. package/dashboard/dist/assets/ScanStationPage-BiO34nov.js.map +1 -0
  155. package/dashboard/dist/assets/{ScopeBadge-CSItPVOH.js → ScopeBadge-CKgfwTy3.js} +2 -2
  156. package/dashboard/dist/assets/{ScopeBadge-CSItPVOH.js.map → ScopeBadge-CKgfwTy3.js.map} +1 -1
  157. package/dashboard/dist/assets/{StickyPagination-CqbjVY6T.js → StickyPagination-BNOuYZ1m.js} +2 -2
  158. package/dashboard/dist/assets/{StickyPagination-CqbjVY6T.js.map → StickyPagination-BNOuYZ1m.js.map} +1 -1
  159. package/dashboard/dist/assets/{StreamMessageDetail-wX3i0aqs.js → StreamMessageDetail-ALVUnfF1.js} +2 -2
  160. package/dashboard/dist/assets/{StreamMessageDetail-wX3i0aqs.js.map → StreamMessageDetail-ALVUnfF1.js.map} +1 -1
  161. package/dashboard/dist/assets/{SwimlaneTimeline-C50PsFoV.js → SwimlaneTimeline-B5b8-xqI.js} +2 -2
  162. package/dashboard/dist/assets/{SwimlaneTimeline-C50PsFoV.js.map → SwimlaneTimeline-B5b8-xqI.js.map} +1 -1
  163. package/dashboard/dist/assets/{TagInput-CAgdLN_b.js → TagInput-B6fMAqxd.js} +2 -2
  164. package/dashboard/dist/assets/{TagInput-CAgdLN_b.js.map → TagInput-B6fMAqxd.js.map} +1 -1
  165. package/dashboard/dist/assets/{TaskDetailPage-bbNSv4Ae.js → TaskDetailPage-CoV3WcTA.js} +2 -2
  166. package/dashboard/dist/assets/{TaskDetailPage-bbNSv4Ae.js.map → TaskDetailPage-CoV3WcTA.js.map} +1 -1
  167. package/dashboard/dist/assets/{TaskQueuePill-h0oorVMe.js → TaskQueuePill-BnDp_DE6.js} +2 -2
  168. package/dashboard/dist/assets/{TaskQueuePill-h0oorVMe.js.map → TaskQueuePill-BnDp_DE6.js.map} +1 -1
  169. package/dashboard/dist/assets/{TasksListPage-CDWnm_d9.js → TasksListPage-CkMmOYOE.js} +2 -2
  170. package/dashboard/dist/assets/{TasksListPage-CDWnm_d9.js.map → TasksListPage-CkMmOYOE.js.map} +1 -1
  171. package/dashboard/dist/assets/{TimeAgo-BLjqlfoX.js → TimeAgo-DVkFgECX.js} +2 -2
  172. package/dashboard/dist/assets/{TimeAgo-BLjqlfoX.js.map → TimeAgo-DVkFgECX.js.map} +1 -1
  173. package/dashboard/dist/assets/{TimestampCell-CnW0ENb5.js → TimestampCell-CfTc6Txy.js} +2 -2
  174. package/dashboard/dist/assets/{TimestampCell-CnW0ENb5.js.map → TimestampCell-CfTc6Txy.js.map} +1 -1
  175. package/dashboard/dist/assets/{ToolPill-CQCsVdzD.js → ToolPill-BRZWo4l8.js} +2 -2
  176. package/dashboard/dist/assets/{ToolPill-CQCsVdzD.js.map → ToolPill-BRZWo4l8.js.map} +1 -1
  177. package/dashboard/dist/assets/{ToolTestPanel-DkMgCB-k.js → ToolTestPanel-kvqZqM0A.js} +2 -2
  178. package/dashboard/dist/assets/{ToolTestPanel-DkMgCB-k.js.map → ToolTestPanel-kvqZqM0A.js.map} +1 -1
  179. package/dashboard/dist/assets/{TopicDetailPage-g3pljKKX.js → TopicDetailPage-CjkAAyy8.js} +4 -4
  180. package/dashboard/dist/assets/{TopicDetailPage-g3pljKKX.js.map → TopicDetailPage-CjkAAyy8.js.map} +1 -1
  181. package/dashboard/dist/assets/{TopicsPage-CVcCYdy-.js → TopicsPage-CJZnv50y.js} +2 -2
  182. package/dashboard/dist/assets/{TopicsPage-CVcCYdy-.js.map → TopicsPage-CJZnv50y.js.map} +1 -1
  183. package/dashboard/dist/assets/{UserName-D6zGIK2i.js → UserName-CPJPZnN1.js} +2 -2
  184. package/dashboard/dist/assets/{UserName-D6zGIK2i.js.map → UserName-CPJPZnN1.js.map} +1 -1
  185. package/dashboard/dist/assets/WorkflowExecutionPage-46UV5pJd.js +2 -0
  186. package/dashboard/dist/assets/{WorkflowExecutionPage-B8g81FCJ.js.map → WorkflowExecutionPage-46UV5pJd.js.map} +1 -1
  187. package/dashboard/dist/assets/{WorkflowPill-Dg7BDTyM.js → WorkflowPill-Cly9Ln_3.js} +2 -2
  188. package/dashboard/dist/assets/{WorkflowPill-Dg7BDTyM.js.map → WorkflowPill-Cly9Ln_3.js.map} +1 -1
  189. package/dashboard/dist/assets/{WorkflowsDashboard-nxT3bv6W.js → WorkflowsDashboard-DtxYdQbQ.js} +2 -2
  190. package/dashboard/dist/assets/{WorkflowsDashboard-nxT3bv6W.js.map → WorkflowsDashboard-DtxYdQbQ.js.map} +1 -1
  191. package/dashboard/dist/assets/{WorkflowsOverview-grI-KAI2.js → WorkflowsOverview-Cg4LSfck.js} +2 -2
  192. package/dashboard/dist/assets/{WorkflowsOverview-grI-KAI2.js.map → WorkflowsOverview-Cg4LSfck.js.map} +1 -1
  193. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BoZ-hfBE.js → YamlWorkflowDetailPage-Del-RCgi.js} +12 -12
  194. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BoZ-hfBE.js.map → YamlWorkflowDetailPage-Del-RCgi.js.map} +1 -1
  195. package/dashboard/dist/assets/{YamlWorkflowsPage-Bf04rwRv.js → YamlWorkflowsPage-BF6Jtx24.js} +2 -2
  196. package/dashboard/dist/assets/{YamlWorkflowsPage-Bf04rwRv.js.map → YamlWorkflowsPage-BF6Jtx24.js.map} +1 -1
  197. package/dashboard/dist/assets/{agents-Dphpdi8l.js → agents-wL-3dMOX.js} +2 -2
  198. package/dashboard/dist/assets/{agents-Dphpdi8l.js.map → agents-wL-3dMOX.js.map} +1 -1
  199. package/dashboard/dist/assets/{bots-DIFE298x.js → bots-D72EsztP.js} +2 -2
  200. package/dashboard/dist/assets/{bots-DIFE298x.js.map → bots-D72EsztP.js.map} +1 -1
  201. package/dashboard/dist/assets/{capabilities-D7Y7LClY.js → capabilities-DXFo6bRb.js} +2 -2
  202. package/dashboard/dist/assets/{capabilities-D7Y7LClY.js.map → capabilities-DXFo6bRb.js.map} +1 -1
  203. package/dashboard/dist/assets/{controlplane-D1TGcYsS.js → controlplane-gQdOgVtH.js} +2 -2
  204. package/dashboard/dist/assets/{controlplane-D1TGcYsS.js.map → controlplane-gQdOgVtH.js.map} +1 -1
  205. package/dashboard/dist/assets/escalation-columns-CSE5wfWQ.js +2 -0
  206. package/dashboard/dist/assets/escalation-columns-CSE5wfWQ.js.map +1 -0
  207. package/dashboard/dist/assets/escalation-items-BIgBWbaw.js +2 -0
  208. package/dashboard/dist/assets/escalation-items-BIgBWbaw.js.map +1 -0
  209. package/dashboard/dist/assets/{index-CeCtqujG.js → index-BaCDL5fh.js} +38 -38
  210. package/dashboard/dist/assets/index-BaCDL5fh.js.map +1 -0
  211. package/dashboard/dist/assets/{index-Dl_8hbYI.js → index-Btm8FtTQ.js} +2 -2
  212. package/dashboard/dist/assets/{index-Dl_8hbYI.js.map → index-Btm8FtTQ.js.map} +1 -1
  213. package/dashboard/dist/assets/index-BvH8T-jb.js +2 -0
  214. package/dashboard/dist/assets/{index-CxdjT1Yk.js.map → index-BvH8T-jb.js.map} +1 -1
  215. package/dashboard/dist/assets/{index-HLu98Sqh.js → index-C-QQ5rKF.js} +2 -2
  216. package/dashboard/dist/assets/{index-HLu98Sqh.js.map → index-C-QQ5rKF.js.map} +1 -1
  217. package/dashboard/dist/assets/{index-DpKOr311.js → index-C1iuZ4Q5.js} +2 -2
  218. package/dashboard/dist/assets/{index-DpKOr311.js.map → index-C1iuZ4Q5.js.map} +1 -1
  219. package/dashboard/dist/assets/{index-ChV7pBjM.js → index-CDxOQ84B.js} +2 -2
  220. package/dashboard/dist/assets/{index-ChV7pBjM.js.map → index-CDxOQ84B.js.map} +1 -1
  221. package/dashboard/dist/assets/index-CED9cLE2.js +3 -0
  222. package/dashboard/dist/assets/index-CED9cLE2.js.map +1 -0
  223. package/dashboard/dist/assets/index-CnU7Jjz3.css +1 -0
  224. package/dashboard/dist/assets/{index-DAvi5_2T.js → index-CptTEhWp.js} +2 -2
  225. package/dashboard/dist/assets/{index-DAvi5_2T.js.map → index-CptTEhWp.js.map} +1 -1
  226. package/dashboard/dist/assets/{index-ag2pCAPu.js → index-Cu0xdX2t.js} +2 -2
  227. package/dashboard/dist/assets/{index-ag2pCAPu.js.map → index-Cu0xdX2t.js.map} +1 -1
  228. package/dashboard/dist/assets/index-D1lfq5iZ.js +13 -0
  229. package/dashboard/dist/assets/{index-gkxPN7Vx.js.map → index-D1lfq5iZ.js.map} +1 -1
  230. package/dashboard/dist/assets/{index-Dy1wWu2e.js → index-czgLyfSo.js} +2 -2
  231. package/dashboard/dist/assets/{index-Dy1wWu2e.js.map → index-czgLyfSo.js.map} +1 -1
  232. package/dashboard/dist/assets/{index-DN-kX7hL.js → index-h1Useskt.js} +2 -2
  233. package/dashboard/dist/assets/{index-DN-kX7hL.js.map → index-h1Useskt.js.map} +1 -1
  234. package/dashboard/dist/assets/{index-BzYcUPdD.js → index-ujGmmwl6.js} +2 -2
  235. package/dashboard/dist/assets/{index-BzYcUPdD.js.map → index-ujGmmwl6.js.map} +1 -1
  236. package/dashboard/dist/assets/{knowledge-DnMs-SOJ.js → knowledge-150lsM68.js} +2 -2
  237. package/dashboard/dist/assets/{knowledge-DnMs-SOJ.js.map → knowledge-150lsM68.js.map} +1 -1
  238. package/dashboard/dist/assets/{mcp-CwW-K3r4.js → mcp-CRtY4v5W.js} +2 -2
  239. package/dashboard/dist/assets/{mcp-CwW-K3r4.js.map → mcp-CRtY4v5W.js.map} +1 -1
  240. package/dashboard/dist/assets/{mcp-query-BZt5e8_Q.js → mcp-query-CDsSMaYq.js} +2 -2
  241. package/dashboard/dist/assets/{mcp-query-BZt5e8_Q.js.map → mcp-query-CDsSMaYq.js.map} +1 -1
  242. package/dashboard/dist/assets/{personas-UA-Xn60f.js → personas-C2Wm4ng1.js} +2 -2
  243. package/dashboard/dist/assets/{personas-UA-Xn60f.js.map → personas-C2Wm4ng1.js.map} +1 -1
  244. package/dashboard/dist/assets/{pipelines-Cu3PARum.js → pipelines-BH0F9-jf.js} +2 -2
  245. package/dashboard/dist/assets/{pipelines-Cu3PARum.js.map → pipelines-BH0F9-jf.js.map} +1 -1
  246. package/dashboard/dist/assets/{tasks-BO6h8GJE.js → tasks-BkUVpaOa.js} +2 -2
  247. package/dashboard/dist/assets/{tasks-BO6h8GJE.js.map → tasks-BkUVpaOa.js.map} +1 -1
  248. package/dashboard/dist/assets/{topics-CWjCR-17.js → topics-Crl8HKHq.js} +2 -2
  249. package/dashboard/dist/assets/{topics-CWjCR-17.js.map → topics-Crl8HKHq.js.map} +1 -1
  250. package/dashboard/dist/assets/{useDebouncedValue-6t0jx1RB.js → useDebouncedValue-Dk3r5xo-.js} +2 -2
  251. package/dashboard/dist/assets/{useDebouncedValue-6t0jx1RB.js.map → useDebouncedValue-Dk3r5xo-.js.map} +1 -1
  252. package/dashboard/dist/assets/{useNamespace-CqpoQx6D.js → useNamespace-BXuyHKAs.js} +2 -2
  253. package/dashboard/dist/assets/{useNamespace-CqpoQx6D.js.map → useNamespace-BXuyHKAs.js.map} +1 -1
  254. package/dashboard/dist/assets/{useYamlActivityEvents-hmCQQwzB.js → useYamlActivityEvents-DSlkjQfb.js} +2 -2
  255. package/dashboard/dist/assets/{useYamlActivityEvents-hmCQQwzB.js.map → useYamlActivityEvents-DSlkjQfb.js.map} +1 -1
  256. package/dashboard/dist/assets/{vendor-icons-CeVtUKyP.js → vendor-icons-C_DQEeBy.js} +2 -2
  257. package/dashboard/dist/assets/{vendor-icons-CeVtUKyP.js.map → vendor-icons-C_DQEeBy.js.map} +1 -1
  258. package/dashboard/dist/assets/{yaml-workflows-ziUdZhKY.js → yaml-workflows-B230v5Za.js} +2 -2
  259. package/dashboard/dist/assets/{yaml-workflows-ziUdZhKY.js.map → yaml-workflows-B230v5Za.js.map} +1 -1
  260. package/dashboard/dist/index.html +3 -3
  261. package/docs/faceted-routing.md +14 -1
  262. package/docs/hitl/x-lt-embed.md +4 -1
  263. package/docs/hitl/x-lt-list-schema.md +19 -2
  264. package/docs/hitl/x-lt-roster.md +112 -0
  265. package/docs/hitl/x-lt-show-if.md +15 -0
  266. package/docs/hitl/x-lt-widget.md +1 -0
  267. package/docs/hitl-guide.md +3 -1
  268. package/docs/maintenance.md +3 -3
  269. package/docs/mcp.md +2 -0
  270. package/docs/scan-codes.md +4 -0
  271. package/docs/schema-enforcement.md +5 -0
  272. package/package.json +2 -2
  273. package/dashboard/dist/assets/AvailableEscalationsPage-IS28XBRe.js +0 -2
  274. package/dashboard/dist/assets/CountdownTimer-LG03dVgR.js +0 -2
  275. package/dashboard/dist/assets/CountdownTimer-LG03dVgR.js.map +0 -1
  276. package/dashboard/dist/assets/EscalationTimeline-d3nvshOk.js +0 -2
  277. package/dashboard/dist/assets/McpRunDetailPage-bMXNO-6V.js +0 -2
  278. package/dashboard/dist/assets/OperatorDashboard-DctmZw10.js +0 -2
  279. package/dashboard/dist/assets/PortalPage-Dfc_Q02R.js +0 -2
  280. package/dashboard/dist/assets/ResolverForm-B1_x8Blv.js +0 -8
  281. package/dashboard/dist/assets/ResolverForm-B1_x8Blv.js.map +0 -1
  282. package/dashboard/dist/assets/ScanStationPage-DzGaXc40.js +0 -2
  283. package/dashboard/dist/assets/ScanStationPage-DzGaXc40.js.map +0 -1
  284. package/dashboard/dist/assets/WorkflowExecutionPage-B8g81FCJ.js +0 -2
  285. package/dashboard/dist/assets/escalation-columns-cwZE6xyo.js +0 -2
  286. package/dashboard/dist/assets/escalation-columns-cwZE6xyo.js.map +0 -1
  287. package/dashboard/dist/assets/index-CeCtqujG.js.map +0 -1
  288. package/dashboard/dist/assets/index-CniRa65p.js +0 -3
  289. package/dashboard/dist/assets/index-CniRa65p.js.map +0 -1
  290. package/dashboard/dist/assets/index-CxdjT1Yk.js +0 -2
  291. package/dashboard/dist/assets/index-gkxPN7Vx.js +0 -13
  292. package/dashboard/dist/assets/index-m9A3UYst.css +0 -1
  293. package/dashboard/dist/assets/validate-BIRn4a6d.js +0 -2
  294. package/dashboard/dist/assets/validate-BIRn4a6d.js.map +0 -1
@@ -177,6 +177,10 @@ async function gateReciprocal(input, auth) {
177
177
  }
178
178
  return { reciprocal: { id: row.id, payload: input.payload } };
179
179
  }
180
+ /** The reciprocal of an add, stamped with who added it, as the container is. */
181
+ function completedBy(reciprocal, userId) {
182
+ return reciprocal ? { ...reciprocal, metadata: { resolved_by: userId } } : undefined;
183
+ }
180
184
  /** Validates the item payload against the container's versioned role form. */
181
185
  async function gatePayload(escalation, payload, userId) {
182
186
  if (payload === undefined)
@@ -245,7 +249,7 @@ async function accumulateItem(input, auth) {
245
249
  actor: initiator.actor,
246
250
  assertClaim: input.assertClaim ? auth.userId : undefined,
247
251
  resolvedBy: await (0, resolve_1.resolverIdentity)(auth),
248
- reciprocal: reciprocal.reciprocal,
252
+ reciprocal: completedBy(reciprocal.reciprocal, auth.userId),
249
253
  });
250
254
  return accumulateOutcomeResult(result, itemKey, escalation.workflow_id);
251
255
  }
@@ -291,7 +295,7 @@ async function accumulateItemBySignalKey(input, auth) {
291
295
  metadata: { ...metadata, resolved_by: auth.userId },
292
296
  actor: initiator.actor,
293
297
  resolvedBy: await (0, resolve_1.resolverIdentity)(auth),
294
- reciprocal: reciprocal.reciprocal,
298
+ reciprocal: completedBy(reciprocal.reciprocal, auth.userId),
295
299
  });
296
300
  return accumulateOutcomeResult(result, itemKey, escalation.workflow_id);
297
301
  }
@@ -344,7 +348,7 @@ async function accumulateItemByMetadata(input, auth) {
344
348
  actor: initiator.actor,
345
349
  resolvedBy,
346
350
  roles: allowedRoles ?? undefined,
347
- reciprocal: reciprocal.reciprocal,
351
+ reciprocal: completedBy(reciprocal.reciprocal, auth.userId),
348
352
  });
349
353
  if (result.outcome === 'not-found')
350
354
  return { status: 404, error: NO_ACCUMULATOR_FOR_FACET };
@@ -364,7 +368,7 @@ async function accumulateItemByMetadata(input, auth) {
364
368
  metadata: outcome,
365
369
  actor: initiator.actor,
366
370
  resolvedBy,
367
- reciprocal: reciprocal.reciprocal,
371
+ reciprocal: completedBy(reciprocal.reciprocal, auth.userId),
368
372
  });
369
373
  if (result.outcome === 'not-found') {
370
374
  return { status: 409, error: 'A concurrent change is already in progress for this escalation' };
@@ -175,12 +175,13 @@ async function resolveByMetadata(input, auth) {
175
175
  // writing; the payload validates here, then a second asserted pass claims
176
176
  // + resolves the same row with pending re-checked inside the statement.
177
177
  const enforcing = await (0, enforcement_cache_1.getEnforcingRoles)();
178
- let result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles, enforcing.size > 0 ? [...enforcing] : null, input.assertId ?? null, input.extraFacets, input.availability);
178
+ const outcome = { ...input.metadata, resolved_by: auth.userId };
179
+ let result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, outcome, writeAllRoles, writeSelfRoles, enforcing.size > 0 ? [...enforcing] : null, input.assertId ?? null, input.extraFacets, input.availability);
179
180
  if (result.outcome === 'validation_required' && result.row) {
180
181
  const report = await (0, resolver_validation_1.checkResolverPayload)(result.row, input.resolverPayload, undefined, resolveUserId);
181
182
  if (report)
182
183
  return (0, resolve_1.validationFailure)(report);
183
- result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, input.metadata, writeAllRoles, writeSelfRoles, null, result.row.id, input.extraFacets, input.availability);
184
+ result = await escalationService.resolveByMetadataAtomic(input.key, input.value, resolveUserId, input.resolverPayload, outcome, writeAllRoles, writeSelfRoles, null, result.row.id, input.extraFacets, input.availability);
184
185
  // The asserted row left pending between the two passes — a concurrent
185
186
  // resolution won the row; surface it as the conflict it is.
186
187
  if (result.outcome === 'not_found') {
@@ -199,7 +200,7 @@ async function resolveByMetadata(input, auth) {
199
200
  // Atomic conditional escalation (signal_key set) — SDK resolve atomically marks
200
201
  // resolved AND delivers the signal to the waiting condition(), resuming the workflow.
201
202
  if (result.signalKey) {
202
- const resolved = await escalationService.resolveEscalation(result.escalationId, input.resolverPayload);
203
+ const resolved = await escalationService.resolveEscalation(result.escalationId, input.resolverPayload, outcome);
203
204
  if (!resolved) {
204
205
  return { status: 409, error: 'Escalation not available for resolution' };
205
206
  }
@@ -1,7 +1,7 @@
1
1
  import type { LTApiAuth, LTApiResult } from '../../types/sdk';
2
2
  /**
3
3
  * Aggregate every related set the role's list schema declares, for one page
4
- * of host rows. One statement per (set, split) pair, whatever the page size.
4
+ * of host rows. One statement per (set, aggregate) pair, whatever the page size.
5
5
  */
6
6
  export declare function getRelatedAggregates(input: {
7
7
  role: string;
@@ -84,16 +84,24 @@ async function resolveQueryRoles(query, readScope) {
84
84
  const restricted = readScope.global ? [] : roles.filter((r) => !readScope.allRoles.includes(r));
85
85
  return { roles, roleLabels, restricted };
86
86
  }
87
- /** The split paths the declared columns ask of one set ('' when unsplit). */
88
- function splitsFor(relations, set) {
89
- const splits = new Set(relations.columns.filter((c) => c.related === set).map((c) => c.splitBy ?? ''));
90
- if (!splits.size)
91
- splits.add('');
92
- return [...splits];
87
+ /** The distinct aggregates the declared columns ask of one set; an unsplit count when none does. */
88
+ function asksFor(relations, set, source) {
89
+ const asks = new Map();
90
+ for (const col of relations.columns.filter((c) => c.related === set)) {
91
+ const key = (0, list_schema_1.relatedAggregateKey)(col, source);
92
+ if (asks.has(key))
93
+ continue;
94
+ asks.set(key, (0, list_schema_1.isExtremeAggregate)(col.aggregate)
95
+ ? { key, extreme: { fold: col.aggregate, path: (0, list_schema_1.relatedValuePath)(source, col.value) } }
96
+ : { key, ...(col.splitBy ? { splitBy: col.splitBy } : {}) });
97
+ }
98
+ if (!asks.size)
99
+ asks.set('', { key: '' });
100
+ return [...asks.values()];
93
101
  }
94
102
  /**
95
103
  * Aggregate every related set the role's list schema declares, for one page
96
- * of host rows. One statement per (set, split) pair, whatever the page size.
104
+ * of host rows. One statement per (set, aggregate) pair, whatever the page size.
97
105
  */
98
106
  async function getRelatedAggregates(input, auth) {
99
107
  try {
@@ -118,11 +126,13 @@ async function getRelatedAggregates(input, auth) {
118
126
  const result = { source: decl.source, aggregates: {} };
119
127
  if (decl.source === list_schema_1.RELATED_SOURCES.ITEMS) {
120
128
  const roles = new Set();
121
- for (const splitBy of splitsFor(relations, name)) {
122
- const read = await escalationService.aggregateRelatedItems({
123
- hostIds, scope, splitBy: splitBy || undefined,
124
- });
125
- result.aggregates[splitBy] = read.aggregates;
129
+ for (const ask of asksFor(relations, name, decl.source)) {
130
+ if (ask.extreme) {
131
+ result.aggregates[ask.key] = await escalationService.extremeRelatedItems({ hostIds, scope, ...ask.extreme });
132
+ continue;
133
+ }
134
+ const read = await escalationService.aggregateRelatedItems({ hostIds, scope, splitBy: ask.splitBy });
135
+ result.aggregates[ask.key] = read.aggregates;
126
136
  read.roles.forEach((r) => roles.add(r));
127
137
  }
128
138
  if (roles.size)
@@ -142,14 +152,14 @@ async function getRelatedAggregates(input, auth) {
142
152
  else
143
153
  unresolved.push(host.id);
144
154
  }
145
- for (const splitBy of splitsFor(relations, name)) {
146
- const agg = await escalationService.aggregateRelatedQuery({
147
- hosts: matched, roles: resolved.roles, status: decl.query.status ?? DEFAULT_STATUS, scope,
148
- splitBy: splitBy || undefined,
149
- });
155
+ for (const ask of asksFor(relations, name, decl.source)) {
156
+ const query = { hosts: matched, roles: resolved.roles, status: decl.query.status ?? DEFAULT_STATUS, scope };
157
+ const agg = ask.extreme
158
+ ? await escalationService.extremeRelatedQuery({ ...query, ...ask.extreme })
159
+ : await escalationService.aggregateRelatedQuery({ ...query, splitBy: ask.splitBy });
150
160
  for (const id of unresolved)
151
161
  agg[id] = { count: 0, unresolved: true };
152
- result.aggregates[splitBy] = agg;
162
+ result.aggregates[ask.key] = agg;
153
163
  }
154
164
  }
155
165
  data.sets[name] = result;
@@ -0,0 +1,15 @@
1
+ -- Migration 040: when a person last consented to a grant for an OAuth client
2
+ --
3
+ -- Registration needs no sign-in, so a client that never reaches consent is
4
+ -- deleted after the retention window. A client someone has consented to is
5
+ -- kept, so the app can always reconnect with the client id it holds.
6
+
7
+ ALTER TABLE lt_oauth_clients ADD COLUMN IF NOT EXISTS last_consent_at TIMESTAMPTZ;
8
+
9
+ -- Clients consented to before this migration: their newest grant.
10
+ UPDATE lt_oauth_clients c
11
+ SET last_consent_at = g.created_at
12
+ FROM (
13
+ SELECT client_id, MAX(created_at) AS created_at FROM lt_oauth_grants GROUP BY client_id
14
+ ) g
15
+ WHERE c.client_id = g.client_id AND c.last_consent_at IS NULL;
@@ -30,6 +30,10 @@ export declare const config: {
30
30
  JWT_SECRET: string;
31
31
  LT_SEED_SERVICE_KEYS: boolean;
32
32
  LT_OAUTH_ISSUER: string;
33
+ /** Spent, expired and revoked OAuth rows are deleted once older than this many days. */
34
+ OAUTH_RETENTION_DAYS: number;
35
+ /** Least time between one process's OAuth sweeps. A sweep runs on an OAuth write. */
36
+ OAUTH_SWEEP_SECONDS: number;
33
37
  HONEYCOMB_API_KEY: string;
34
38
  ROLE_ENFORCEMENT_CACHE_TTL_MS: number;
35
39
  LT_ANALYTICS_MAX_GROUPS: number;
@@ -45,6 +45,10 @@ exports.config = {
45
45
  LT_SEED_SERVICE_KEYS: process.env.LT_SEED_SERVICE_KEYS === 'true',
46
46
  // Enables the OAuth authorization server for /mcp when start() passes no auth.oauthServer.
47
47
  LT_OAUTH_ISSUER: process.env.LT_OAUTH_ISSUER || '',
48
+ /** Spent, expired and revoked OAuth rows are deleted once older than this many days. */
49
+ OAUTH_RETENTION_DAYS: positiveInt('OAUTH_RETENTION_DAYS', 7),
50
+ /** Least time between one process's OAuth sweeps. A sweep runs on an OAuth write. */
51
+ OAUTH_SWEEP_SECONDS: positiveInt('OAUTH_SWEEP_SECONDS', 600),
48
52
  HONEYCOMB_API_KEY: process.env.HONEYCOMB_API_KEY || '',
49
53
  // Resolver schema enforcement: TTL for the cached enforcing-role set and
50
54
  // latest role form schemas. Bounds cross-container staleness after a role
@@ -11,7 +11,7 @@ import type { LTMaintenanceConfig } from '../types/maintenance';
11
11
  * (preserves jdata, udata, jmark, hmark — exports always work)
12
12
  * 4. Hard-delete pruned jobs older than 180 days
13
13
  * 5. Delete OAuth server rows (expired codes and tokens, unused or revoked
14
- * grants, clients with no live grant) older than 7 days
14
+ * grants, clients never consented to) older than 7 days
15
15
  *
16
16
  * Conservative defaults — execution artifacts (activity inputs, stream
17
17
  * history) are needed for rich export data. Pruning too early strips
@@ -13,7 +13,7 @@ exports.defaultMaintenanceConfig = void 0;
13
13
  * (preserves jdata, udata, jmark, hmark — exports always work)
14
14
  * 4. Hard-delete pruned jobs older than 180 days
15
15
  * 5. Delete OAuth server rows (expired codes and tokens, unused or revoked
16
- * grants, clients with no live grant) older than 7 days
16
+ * grants, clients never consented to) older than 7 days
17
17
  *
18
18
  * Conservative defaults — execution artifacts (activity inputs, stream
19
19
  * history) are needed for rich export data. Pruning too early strips
@@ -1,3 +1,3 @@
1
- export { REFRESH_REUSE_GRACE_SECONDS, createSecret, hashSecret, pkceChallenge, registerClient, getClient, issueAuthorizationCode, exchangeAuthorizationCode, rotateRefreshToken, getLiveGrant, revokeByRefreshToken, revokeGrant, listGrants, pruneOAuthServer, type RevokedGrant, type OAuthPruneCounts, } from './store';
1
+ export { REFRESH_REUSE_GRACE_SECONDS, createSecret, hashSecret, pkceChallenge, registerClient, getClient, issueAuthorizationCode, exchangeAuthorizationCode, rotateRefreshToken, getLiveGrant, revokeByRefreshToken, revokeGrant, listGrants, pruneOAuthServer, sweepOAuthServer, type RevokedGrant, type OAuthPruneCounts, } from './store';
2
2
  export { signAccessToken, verifyAccessToken, accessTokenPrincipal, type AccessTokenTarget, type LTAccessTokenClaims, } from './access-token';
3
3
  export { MCP_SCOPES, PRESET_SCOPE, ACCESS_TOKEN_TYPE, OAUTH_PRINCIPAL_TYPE } from './constants';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OAUTH_PRINCIPAL_TYPE = exports.ACCESS_TOKEN_TYPE = exports.PRESET_SCOPE = exports.MCP_SCOPES = exports.accessTokenPrincipal = exports.verifyAccessToken = exports.signAccessToken = exports.pruneOAuthServer = exports.listGrants = exports.revokeGrant = exports.revokeByRefreshToken = exports.getLiveGrant = exports.rotateRefreshToken = exports.exchangeAuthorizationCode = exports.issueAuthorizationCode = exports.getClient = exports.registerClient = exports.pkceChallenge = exports.hashSecret = exports.createSecret = exports.REFRESH_REUSE_GRACE_SECONDS = void 0;
3
+ exports.OAUTH_PRINCIPAL_TYPE = exports.ACCESS_TOKEN_TYPE = exports.PRESET_SCOPE = exports.MCP_SCOPES = exports.accessTokenPrincipal = exports.verifyAccessToken = exports.signAccessToken = exports.sweepOAuthServer = exports.pruneOAuthServer = exports.listGrants = exports.revokeGrant = exports.revokeByRefreshToken = exports.getLiveGrant = exports.rotateRefreshToken = exports.exchangeAuthorizationCode = exports.issueAuthorizationCode = exports.getClient = exports.registerClient = exports.pkceChallenge = exports.hashSecret = exports.createSecret = exports.REFRESH_REUSE_GRACE_SECONDS = void 0;
4
4
  var store_1 = require("./store");
5
5
  Object.defineProperty(exports, "REFRESH_REUSE_GRACE_SECONDS", { enumerable: true, get: function () { return store_1.REFRESH_REUSE_GRACE_SECONDS; } });
6
6
  Object.defineProperty(exports, "createSecret", { enumerable: true, get: function () { return store_1.createSecret; } });
@@ -16,6 +16,7 @@ Object.defineProperty(exports, "revokeByRefreshToken", { enumerable: true, get:
16
16
  Object.defineProperty(exports, "revokeGrant", { enumerable: true, get: function () { return store_1.revokeGrant; } });
17
17
  Object.defineProperty(exports, "listGrants", { enumerable: true, get: function () { return store_1.listGrants; } });
18
18
  Object.defineProperty(exports, "pruneOAuthServer", { enumerable: true, get: function () { return store_1.pruneOAuthServer; } });
19
+ Object.defineProperty(exports, "sweepOAuthServer", { enumerable: true, get: function () { return store_1.sweepOAuthServer; } });
19
20
  var access_token_1 = require("./access-token");
20
21
  Object.defineProperty(exports, "signAccessToken", { enumerable: true, get: function () { return access_token_1.signAccessToken; } });
21
22
  Object.defineProperty(exports, "verifyAccessToken", { enumerable: true, get: function () { return access_token_1.verifyAccessToken; } });
@@ -1,7 +1,7 @@
1
1
  export declare const INSERT_CLIENT = "\n INSERT INTO lt_oauth_clients (client_id, client_name, redirect_uris)\n VALUES ($1, $2, $3)\n RETURNING client_id, client_name, redirect_uris, created_at";
2
2
  export declare const GET_CLIENT = "\n SELECT client_id, client_name, redirect_uris, created_at\n FROM lt_oauth_clients WHERE client_id = $1";
3
- /** Create the grant and its authorization code together. */
4
- export declare const ISSUE_CODE = "\n WITH g AS (\n INSERT INTO lt_oauth_grants (user_id, client_id, policy, scope)\n VALUES ($1, $2, $3, $4)\n RETURNING id\n )\n INSERT INTO lt_oauth_codes (code_hash, grant_id, redirect_uri, code_challenge, resource, expires_at)\n SELECT $5, g.id, $6, $7, $8, NOW() + make_interval(secs => $9) FROM g\n RETURNING grant_id";
3
+ /** Create the grant and its authorization code together, and stamp the client's consent. */
4
+ export declare const ISSUE_CODE = "\n WITH g AS (\n INSERT INTO lt_oauth_grants (user_id, client_id, policy, scope)\n VALUES ($1, $2, $3, $4)\n RETURNING id\n ),\n consented AS (\n UPDATE lt_oauth_clients SET last_consent_at = NOW() WHERE client_id = $2\n )\n INSERT INTO lt_oauth_codes (code_hash, grant_id, redirect_uri, code_challenge, resource, expires_at)\n SELECT $5, g.id, $6, $7, $8, NOW() + make_interval(secs => $9) FROM g\n RETURNING grant_id";
5
5
  /**
6
6
  * Consume a code and issue the first refresh token. The code must be unused,
7
7
  * unexpired, for this client and redirect URI, and match the PKCE challenge;
@@ -46,5 +46,8 @@ export declare const PRUNE_CODES = "\n DELETE FROM lt_oauth_codes WHERE expires
46
46
  * left with no refresh token and no code: never exchanged, or long unused.
47
47
  */
48
48
  export declare const PRUNE_GRANTS = "\n DELETE FROM lt_oauth_grants g\n WHERE g.revoked_at < NOW() - $1::interval\n OR (g.created_at < NOW() - $1::interval\n AND NOT EXISTS (SELECT 1 FROM lt_oauth_refresh_tokens t WHERE t.grant_id = g.id)\n AND NOT EXISTS (SELECT 1 FROM lt_oauth_codes c WHERE c.grant_id = g.id))";
49
- /** Clients registered longer ago than the window with no live grant. */
50
- export declare const PRUNE_CLIENTS = "\n DELETE FROM lt_oauth_clients c\n WHERE c.created_at < NOW() - $1::interval\n AND NOT EXISTS (SELECT 1 FROM lt_oauth_grants g WHERE g.client_id = c.client_id AND g.revoked_at IS NULL)";
49
+ /**
50
+ * Clients registered longer ago than the window that no one ever consented to.
51
+ * A client with a consent is kept: the app holds its client id to reconnect.
52
+ */
53
+ export declare const PRUNE_CLIENTS = "\n DELETE FROM lt_oauth_clients c\n WHERE c.last_consent_at IS NULL AND c.created_at < NOW() - $1::interval\n AND NOT EXISTS (SELECT 1 FROM lt_oauth_grants g WHERE g.client_id = c.client_id)";
@@ -17,12 +17,15 @@ exports.INSERT_CLIENT = `
17
17
  exports.GET_CLIENT = `
18
18
  SELECT client_id, client_name, redirect_uris, created_at
19
19
  FROM lt_oauth_clients WHERE client_id = $1`;
20
- /** Create the grant and its authorization code together. */
20
+ /** Create the grant and its authorization code together, and stamp the client's consent. */
21
21
  exports.ISSUE_CODE = `
22
22
  WITH g AS (
23
23
  INSERT INTO lt_oauth_grants (user_id, client_id, policy, scope)
24
24
  VALUES ($1, $2, $3, $4)
25
25
  RETURNING id
26
+ ),
27
+ consented AS (
28
+ UPDATE lt_oauth_clients SET last_consent_at = NOW() WHERE client_id = $2
26
29
  )
27
30
  INSERT INTO lt_oauth_codes (code_hash, grant_id, redirect_uri, code_challenge, resource, expires_at)
28
31
  SELECT $5, g.id, $6, $7, $8, NOW() + make_interval(secs => $9) FROM g
@@ -149,8 +152,11 @@ exports.PRUNE_GRANTS = `
149
152
  OR (g.created_at < NOW() - $1::interval
150
153
  AND NOT EXISTS (SELECT 1 FROM lt_oauth_refresh_tokens t WHERE t.grant_id = g.id)
151
154
  AND NOT EXISTS (SELECT 1 FROM lt_oauth_codes c WHERE c.grant_id = g.id))`;
152
- /** Clients registered longer ago than the window with no live grant. */
155
+ /**
156
+ * Clients registered longer ago than the window that no one ever consented to.
157
+ * A client with a consent is kept: the app holds its client id to reconnect.
158
+ */
153
159
  exports.PRUNE_CLIENTS = `
154
160
  DELETE FROM lt_oauth_clients c
155
- WHERE c.created_at < NOW() - $1::interval
156
- AND NOT EXISTS (SELECT 1 FROM lt_oauth_grants g WHERE g.client_id = c.client_id AND g.revoked_at IS NULL)`;
161
+ WHERE c.last_consent_at IS NULL AND c.created_at < NOW() - $1::interval
162
+ AND NOT EXISTS (SELECT 1 FROM lt_oauth_grants g WHERE g.client_id = c.client_id)`;
@@ -82,7 +82,14 @@ export interface OAuthPruneCounts {
82
82
  /**
83
83
  * Delete what the authorization server no longer needs, older than the
84
84
  * window (a Postgres interval): spent and expired refresh tokens and codes,
85
- * then grants with neither left or revoked, then clients with no live grant.
85
+ * then grants with neither left or revoked, then clients never consented to.
86
86
  * Each step reads what the one before it left.
87
87
  */
88
88
  export declare function pruneOAuthServer(olderThan: string): Promise<OAuthPruneCounts>;
89
+ /**
90
+ * Delete what the server no longer needs, older than OAUTH_RETENTION_DAYS.
91
+ * Each OAuth write calls it, and it runs at most once per OAUTH_SWEEP_SECONDS
92
+ * in a process, so the tables stay bounded with or without a maintenance
93
+ * schedule. A failed sweep is logged and never fails the write.
94
+ */
95
+ export declare function sweepOAuthServer(): Promise<void>;
@@ -47,8 +47,11 @@ exports.revokeByRefreshToken = revokeByRefreshToken;
47
47
  exports.revokeGrant = revokeGrant;
48
48
  exports.listGrants = listGrants;
49
49
  exports.pruneOAuthServer = pruneOAuthServer;
50
+ exports.sweepOAuthServer = sweepOAuthServer;
50
51
  const crypto = __importStar(require("crypto"));
51
52
  const db_1 = require("../../../lib/db");
53
+ const logger_1 = require("../../../lib/logger");
54
+ const config_1 = require("../../../modules/config");
52
55
  const uuid_1 = require("../../../lib/uuid");
53
56
  const sql_1 = require("./sql");
54
57
  /** A refresh token reused within this many seconds is treated as a client retry. */
@@ -69,6 +72,7 @@ async function registerClient(input) {
69
72
  const { rows } = await (0, db_1.getPool)().query(sql_1.INSERT_CLIENT, [
70
73
  `ltc_${createSecret()}`, input.clientName ?? null, input.redirectUris,
71
74
  ]);
75
+ void sweepOAuthServer();
72
76
  return rows[0];
73
77
  }
74
78
  /** A client id as registerClient issues it; anything else names no client and never reaches SQL. */
@@ -90,6 +94,7 @@ async function issueAuthorizationCode(input) {
90
94
  input.userId, input.clientId, input.policy, input.scope,
91
95
  hashSecret(code), input.redirectUri, input.codeChallenge, input.resource ?? null, input.ttlSeconds,
92
96
  ]);
97
+ void sweepOAuthServer();
93
98
  return { code, grantId: rows[0].grant_id };
94
99
  }
95
100
  /**
@@ -110,6 +115,7 @@ async function exchangeAuthorizationCode(input) {
110
115
  await pool.query(sql_1.REVOKE_ON_CODE_REUSE, [codeHash]);
111
116
  return null;
112
117
  }
118
+ void sweepOAuthServer();
113
119
  const { resource, ...snapshot } = rows[0];
114
120
  return { snapshot, resource, refreshToken };
115
121
  }
@@ -124,8 +130,10 @@ async function rotateRefreshToken(input) {
124
130
  const next = createSecret();
125
131
  const oldHash = hashSecret(input.refreshToken);
126
132
  const { rows } = await pool.query(sql_1.ROTATE_REFRESH_TOKEN, [oldHash, hashSecret(next), input.refreshTtlSeconds, input.clientId]);
127
- if (rows[0])
133
+ if (rows[0]) {
134
+ void sweepOAuthServer();
128
135
  return { snapshot: rows[0], refreshToken: next };
136
+ }
129
137
  const reuse = await pool.query(sql_1.REVOKE_ON_REFRESH_REUSE, [oldHash, exports.REFRESH_REUSE_GRACE_SECONDS]);
130
138
  return { snapshot: null, revoked: reuse.rows[0] ?? null };
131
139
  }
@@ -158,7 +166,7 @@ async function listGrants(userId) {
158
166
  /**
159
167
  * Delete what the authorization server no longer needs, older than the
160
168
  * window (a Postgres interval): spent and expired refresh tokens and codes,
161
- * then grants with neither left or revoked, then clients with no live grant.
169
+ * then grants with neither left or revoked, then clients never consented to.
162
170
  * Each step reads what the one before it left.
163
171
  */
164
172
  async function pruneOAuthServer(olderThan) {
@@ -169,3 +177,24 @@ async function pruneOAuthServer(olderThan) {
169
177
  const clients = (await pool.query(sql_1.PRUNE_CLIENTS, [olderThan])).rowCount ?? 0;
170
178
  return { refreshTokens, codes, grants, clients };
171
179
  }
180
+ let nextSweepAt = 0;
181
+ /**
182
+ * Delete what the server no longer needs, older than OAUTH_RETENTION_DAYS.
183
+ * Each OAuth write calls it, and it runs at most once per OAUTH_SWEEP_SECONDS
184
+ * in a process, so the tables stay bounded with or without a maintenance
185
+ * schedule. A failed sweep is logged and never fails the write.
186
+ */
187
+ async function sweepOAuthServer() {
188
+ const now = Date.now();
189
+ if (now < nextSweepAt)
190
+ return;
191
+ nextSweepAt = now + config_1.config.OAUTH_SWEEP_SECONDS * 1000;
192
+ try {
193
+ const counts = await pruneOAuthServer(`${config_1.config.OAUTH_RETENTION_DAYS} days`);
194
+ if (Object.values(counts).some(Boolean))
195
+ logger_1.loggerRegistry.info(`[lt-oauth] swept: ${JSON.stringify(counts)}`);
196
+ }
197
+ catch (err) {
198
+ logger_1.loggerRegistry.warn(`[lt-oauth] sweep failed: ${err?.message}`);
199
+ }
200
+ }
@@ -8,6 +8,8 @@ export interface AccumulateReciprocal {
8
8
  roles?: string[];
9
9
  /** Stored as the reciprocal entry's payload. */
10
10
  payload?: Record<string, any>;
11
+ /** Merge patch for the reciprocal row's metadata, same statement. */
12
+ metadata?: Record<string, any>;
11
13
  }
12
14
  export interface AccumulateItemInput {
13
15
  itemKey: string;
@@ -43,7 +45,7 @@ export interface AccumulateItemServiceOutcome {
43
45
  export interface RemoveItemInput {
44
46
  itemKey: string;
45
47
  actor?: string;
46
- reciprocal?: Omit<AccumulateReciprocal, 'payload'>;
48
+ reciprocal?: Omit<AccumulateReciprocal, 'payload' | 'metadata'>;
47
49
  }
48
50
  export interface RemoveItemServiceOutcome {
49
51
  outcome: RemoveAccumulatedItemOutcome;
@@ -13,7 +13,9 @@ exports.buildFacetOrder = buildFacetOrder;
13
13
  exports.buildGroupOrder = buildGroupOrder;
14
14
  // Top-level columns a caller may sort/range on directly. Anything else must be a
15
15
  // `metadata.<key>` facet. Keeps interpolation to a fixed, audited set.
16
- const ORDER_COLUMNS = new Set(['priority', 'created_at', 'updated_at', 'status', 'role']);
16
+ const ORDER_COLUMNS = new Set(['priority', 'created_at', 'updated_at', 'resolved_at', 'status', 'role']);
17
+ // Columns a pending row leaves empty: they sort after every value, either direction.
18
+ const NULLABLE_ORDER_COLUMNS = new Set(['resolved_at']);
17
19
  const RANGE_OPS = new Set(['<', '<=', '>', '>=', '=']);
18
20
  // Metadata keys are interpolated (as a JSON path), so they are strictly validated.
19
21
  // Exported so role config (priority_facet) validates keys against the same rule.
@@ -140,7 +142,7 @@ function buildFacetOrder(orderBy) {
140
142
  for (const o of orderBy) {
141
143
  const dir = o.direction === 'desc' ? 'DESC' : 'ASC';
142
144
  if (ORDER_COLUMNS.has(o.field)) {
143
- parts.push(`${o.field} ${dir}`);
145
+ parts.push(`${o.field} ${dir}${NULLABLE_ORDER_COLUMNS.has(o.field) ? ' NULLS LAST' : ''}`);
144
146
  }
145
147
  else if (o.field.startsWith('metadata.')) {
146
148
  const key = o.field.slice('metadata.'.length);
@@ -14,3 +14,4 @@ export { searchEscalationsGrouped } from './grouped-list';
14
14
  export type { EscalationGroup } from './grouped-list';
15
15
  export { aggregateRelatedItems, listRelatedItems, aggregateRelatedQuery, listRelatedQueryRows, readRelatedHosts, } from './related';
16
16
  export type { RelatedQueryHost } from './related';
17
+ export { extremeRelatedItems, extremeRelatedQuery } from './related-extremes';
@@ -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.readRelatedHosts = exports.listRelatedQueryRows = exports.aggregateRelatedQuery = exports.listRelatedItems = exports.aggregateRelatedItems = exports.searchEscalationsGrouped = exports.AnalyticsInputError = exports.resetAnalyticsCaches = exports.resolveEntitySystem = exports.timelineByFacet = exports.aggregateByFacets = exports.claimByFacets = exports.claimGroups = exports.countByFacets = exports.searchGroups = exports.searchByFacets = exports.ensureFacetReady = exports.ensureEscalationCompatView = exports.claimAssertedByMetadata = void 0;
17
+ exports.extremeRelatedQuery = exports.extremeRelatedItems = exports.readRelatedHosts = exports.listRelatedQueryRows = exports.aggregateRelatedQuery = exports.listRelatedItems = exports.aggregateRelatedItems = exports.searchEscalationsGrouped = exports.AnalyticsInputError = exports.resetAnalyticsCaches = exports.resolveEntitySystem = exports.timelineByFacet = exports.aggregateByFacets = exports.claimByFacets = exports.claimGroups = exports.countByFacets = exports.searchGroups = exports.searchByFacets = exports.ensureFacetReady = exports.ensureEscalationCompatView = exports.claimAssertedByMetadata = void 0;
18
18
  __exportStar(require("./types"), exports);
19
19
  __exportStar(require("./crud"), exports);
20
20
  var claim_asserted_1 = require("./claim-asserted");
@@ -48,3 +48,6 @@ Object.defineProperty(exports, "listRelatedItems", { enumerable: true, get: func
48
48
  Object.defineProperty(exports, "aggregateRelatedQuery", { enumerable: true, get: function () { return related_1.aggregateRelatedQuery; } });
49
49
  Object.defineProperty(exports, "listRelatedQueryRows", { enumerable: true, get: function () { return related_1.listRelatedQueryRows; } });
50
50
  Object.defineProperty(exports, "readRelatedHosts", { enumerable: true, get: function () { return related_1.readRelatedHosts; } });
51
+ var related_extremes_1 = require("./related-extremes");
52
+ Object.defineProperty(exports, "extremeRelatedItems", { enumerable: true, get: function () { return related_extremes_1.extremeRelatedItems; } });
53
+ Object.defineProperty(exports, "extremeRelatedQuery", { enumerable: true, get: function () { return related_extremes_1.extremeRelatedQuery; } });
@@ -0,0 +1,17 @@
1
+ import { type RelatedExtreme, type RelatedHostAggregate } from '../../shared/list-schema';
2
+ import { type RelatedQueryInput } from './related';
3
+ import type { ReadScopeInput } from './read-scope-sql';
4
+ type Aggregates = Record<string, RelatedHostAggregate>;
5
+ /** The least or greatest `payload.<path>` among each host's items. */
6
+ export declare function extremeRelatedItems(opts: {
7
+ hostIds: string[];
8
+ scope: ReadScopeInput;
9
+ fold: RelatedExtreme;
10
+ path: string;
11
+ }): Promise<Aggregates>;
12
+ /** The least or greatest `metadata.<key>` among the rows sharing each host's facets. */
13
+ export declare function extremeRelatedQuery(input: RelatedQueryInput & {
14
+ fold: RelatedExtreme;
15
+ path: string;
16
+ }): Promise<Aggregates>;
17
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // min/max over a related set: the least or greatest item payload value, or
4
+ // related row metadata value, per host. One statement per (set, value) pair,
5
+ // under the same read scope as the set's counts.
6
+ // ---------------------------------------------------------------------------
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.extremeRelatedItems = extremeRelatedItems;
9
+ exports.extremeRelatedQuery = extremeRelatedQuery;
10
+ const db_1 = require("../../lib/db");
11
+ const list_schema_1 = require("../../shared/list-schema");
12
+ const client_1 = require("./client");
13
+ const related_1 = require("./related");
14
+ const NUMBER_TEXT = '^-?[0-9]+(\\.[0-9]+)?([eE][-+]?[0-9]+)?$';
15
+ /**
16
+ * Per host: how many values, whether every one is numeric, and the extreme
17
+ * as a number and as text. `v` is the jsonb value; objects, arrays, nulls and
18
+ * blanks are not values. Text compares in code-unit order, as the dashboard folds.
19
+ */
20
+ function extremeSelect(fold, hostId, v, from) {
21
+ const fn = fold === list_schema_1.RELATED_AGGREGATES.MIN ? 'min' : 'max';
22
+ return `SELECT host_id, count(*)::int AS n, bool_and(t ~ '${NUMBER_TEXT}') AS numeric,
23
+ ${fn}(CASE WHEN t ~ '${NUMBER_TEXT}' THEN t::numeric END)::text AS num,
24
+ ${fn}(t COLLATE "C") AS txt
25
+ FROM (SELECT h_id AS host_id, x #>> '{}' AS t
26
+ FROM (SELECT ${hostId}::text AS h_id, ${v} AS x ${from}) raw
27
+ WHERE jsonb_typeof(x) IN ('string', 'number', 'boolean') AND x #>> '{}' <> '') vals
28
+ GROUP BY host_id`;
29
+ }
30
+ function foldExtremeRows(rows, hostIds) {
31
+ const out = {};
32
+ for (const id of hostIds)
33
+ out[id] = { count: 0 };
34
+ for (const row of rows) {
35
+ if (!out[row.host_id])
36
+ continue;
37
+ const value = row.numeric && row.num !== null ? Number(row.num) : row.txt;
38
+ out[row.host_id] = value === null ? { count: row.n } : { count: row.n, value };
39
+ }
40
+ return out;
41
+ }
42
+ /** The least or greatest `payload.<path>` among each host's items. */
43
+ async function extremeRelatedItems(opts) {
44
+ if (!opts.hostIds.length)
45
+ return {};
46
+ await (0, client_1.ensureEscalationCompatView)();
47
+ const params = [];
48
+ const cte = (0, related_1.itemEntriesCte)(params, opts.scope, opts.hostIds);
49
+ const pi = params.push(opts.path.slice(list_schema_1.SPLIT_PATHS.PAYLOAD.length).split('.'));
50
+ const v = `(CASE WHEN en.acc THEN en.entry->'payload' ELSE en.entry END) #> $${pi}::text[]`;
51
+ const { rows } = await (0, db_1.getPool)().query(`WITH ${cte} ${extremeSelect(opts.fold, 'en.host_id', v, 'FROM entries en')}`, params);
52
+ return foldExtremeRows(rows, opts.hostIds);
53
+ }
54
+ /** The least or greatest `metadata.<key>` among the rows sharing each host's facets. */
55
+ async function extremeRelatedQuery(input) {
56
+ const hostIds = input.hosts.map((h) => h.hostId);
57
+ if (!hostIds.length || !input.roles.length)
58
+ return foldExtremeRows([], hostIds);
59
+ await (0, client_1.ensureEscalationCompatView)();
60
+ const params = [];
61
+ const from = (0, related_1.querySetFrom)(params, input);
62
+ const ki = params.push(input.path.slice(list_schema_1.SPLIT_PATHS.METADATA.length));
63
+ const { rows } = await (0, db_1.getPool)().query(extremeSelect(input.fold, 'h.host_id', `e.metadata->$${ki}`, from), params);
64
+ return foldExtremeRows(rows, hostIds);
65
+ }
@@ -12,6 +12,8 @@ export interface RelatedQueryInput {
12
12
  scope: ReadScopeInput;
13
13
  }
14
14
  type Aggregates = Record<string, RelatedHostAggregate>;
15
+ /** The hosts and their item entries, read under the caller's scope. */
16
+ export declare function itemEntriesCte(params: unknown[], scope: ReadScopeInput, hostIds: string[]): string;
15
17
  /**
16
18
  * Count each host's items, optionally split by `payload.<path>` or
17
19
  * `reciprocal.metadata.<key>`. A reciprocal split counts only reciprocal rows
@@ -35,6 +37,8 @@ export declare function listRelatedItems(opts: {
35
37
  rows: RelatedRow[];
36
38
  total: number;
37
39
  }>;
40
+ /** The shared FROM/WHERE of a query set: hosts unnested beside their matches. */
41
+ export declare function querySetFrom(params: unknown[], input: RelatedQueryInput): string;
38
42
  /**
39
43
  * Count the rows that share each host's facets, grouped by role and an
40
44
  * optional `metadata.<key>` split. The GIN index serves each host's match.
@@ -7,8 +7,10 @@
7
7
  // the page size or the item count.
8
8
  // ---------------------------------------------------------------------------
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.itemEntriesCte = itemEntriesCte;
10
11
  exports.aggregateRelatedItems = aggregateRelatedItems;
11
12
  exports.listRelatedItems = listRelatedItems;
13
+ exports.querySetFrom = querySetFrom;
12
14
  exports.aggregateRelatedQuery = aggregateRelatedQuery;
13
15
  exports.listRelatedQueryRows = listRelatedQueryRows;
14
16
  exports.readRelatedHosts = readRelatedHosts;
@@ -23,6 +23,7 @@ exports.toValidationErrorBody = toValidationErrorBody;
23
23
  */
24
24
  const form_validation_1 = require("../../shared/form-validation");
25
25
  const submit_guard_1 = require("./submit-guard");
26
+ const roster_1 = require("./roster");
26
27
  const enforcement_cache_1 = require("../role/enforcement-cache");
27
28
  const knowledge_1 = require("../knowledge");
28
29
  const escalation_1 = require("../../types/escalation");
@@ -85,6 +86,12 @@ async function checkResolverPayload(escalation, resolverPayload, loadEnvelope, r
85
86
  payload: parseJsonOrNull(escalation.escalation_payload),
86
87
  ...(lookup ? { lookup } : {}),
87
88
  };
89
+ // Roster counts read the submitted form values (an `in` source may name a
90
+ // form field), then join the context so showIf and the guard see them.
91
+ const formValues = (0, form_validation_1.mapPayloadToForm)(resolverPayload, schema);
92
+ const roster = await (0, roster_1.loadRosterCounts)(schema, { ...ctx, resolver: formValues, input: formValues });
93
+ if (Object.keys(roster).length)
94
+ ctx.roster = roster;
88
95
  const violations = (0, form_validation_1.validateResolverPayload)(schema, resolverPayload, ctx);
89
96
  // The query precondition (x-lt-submit-guard): reject while the embedded query
90
97
  // still returns rows. Enforced on the same enforce_schema opt-in as the field
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Server-side roster counts: the API-first counterpart of the dashboard's
3
+ * roster widget. For each `x-lt-roster` field of a form schema, the member
4
+ * rows are read with the same query the widget runs and the shared
5
+ * `computeRoster` turns them into counts, so `roster.*` showIf conditions and
6
+ * a roster submit guard evaluate at resolve exactly as the form showed them.
7
+ *
8
+ * Member rows are read with `global: true`: a resolver who cannot see a
9
+ * member must never be told the roster is complete.
10
+ */
11
+ import { type RosterCounts, type ShowIfContext } from '../../shared/form-validation';
12
+ /** Each roster field's counts, keyed by field name; empty when the schema declares none. */
13
+ export declare function loadRosterCounts(schema: Record<string, unknown> | null | undefined, ctx: ShowIfContext): Promise<Record<string, RosterCounts>>;