@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.
- package/build/api/escalations/accumulate.js +8 -4
- package/build/api/escalations/metadata.js +4 -3
- package/build/api/escalations/related.d.ts +1 -1
- package/build/api/escalations/related.js +28 -18
- package/build/lib/db/schemas/040_oauth_client_consent.sql +15 -0
- package/build/modules/config.d.ts +4 -0
- package/build/modules/config.js +4 -0
- package/build/modules/maintenance.d.ts +1 -1
- package/build/modules/maintenance.js +1 -1
- package/build/services/auth/oauth-server/index.d.ts +1 -1
- package/build/services/auth/oauth-server/index.js +2 -1
- package/build/services/auth/oauth-server/sql.d.ts +7 -4
- package/build/services/auth/oauth-server/sql.js +10 -4
- package/build/services/auth/oauth-server/store.d.ts +8 -1
- package/build/services/auth/oauth-server/store.js +31 -2
- package/build/services/escalation/accumulate.d.ts +3 -1
- package/build/services/escalation/facet-sql.js +4 -2
- package/build/services/escalation/index.d.ts +1 -0
- package/build/services/escalation/index.js +4 -1
- package/build/services/escalation/related-extremes.d.ts +17 -0
- package/build/services/escalation/related-extremes.js +65 -0
- package/build/services/escalation/related.d.ts +4 -0
- package/build/services/escalation/related.js +2 -0
- package/build/services/escalation/resolver-validation.js +7 -0
- package/build/services/escalation/roster.d.ts +13 -0
- package/build/services/escalation/roster.js +37 -0
- package/build/services/escalation/submit-guard.d.ts +3 -0
- package/build/services/escalation/submit-guard.js +21 -2
- package/build/services/maintenance/index.js +1 -1
- package/build/services/mcp/server-tools.js +1 -1
- package/build/shared/form-validation/display-only-widgets.d.ts +1 -0
- package/build/shared/form-validation/display-only-widgets.js +1 -0
- package/build/shared/form-validation/index.d.ts +1 -0
- package/build/shared/form-validation/index.js +1 -0
- package/build/shared/form-validation/validate-resolver-payload.d.ts +1 -0
- package/build/shared/form-validation/validate-resolver-payload.js +2 -1
- package/build/shared/form-validation/x-lt-help.d.ts +1 -1
- package/build/shared/form-validation/x-lt-help.js +2 -1
- package/build/shared/form-validation/x-lt-roster.d.ts +92 -0
- package/build/shared/form-validation/x-lt-roster.js +234 -0
- package/build/shared/form-validation/x-lt-show-if.d.ts +8 -0
- package/build/shared/form-validation/x-lt-show-if.js +13 -0
- package/build/shared/form-validation/x-lt-submit-guard.d.ts +14 -1
- package/build/shared/form-validation/x-lt-submit-guard.js +4 -1
- package/build/shared/list-schema/aggregate.d.ts +19 -0
- package/build/shared/list-schema/aggregate.js +66 -0
- package/build/shared/list-schema/index.d.ts +2 -0
- package/build/shared/list-schema/index.js +7 -1
- package/build/shared/list-schema/types.d.ts +15 -2
- package/build/shared/list-schema/types.js +6 -1
- package/build/shared/list-schema/validate.js +28 -2
- package/build/tsconfig.tsbuildinfo +1 -1
- package/dashboard/dist/assets/{AdminDashboard-B3mkMIOT.js → AdminDashboard-DWVRAu63.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-B3mkMIOT.js.map → AdminDashboard-DWVRAu63.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-CQj2B678.js → AgentConfigPage-Exjtjbvp.js} +6 -6
- package/dashboard/dist/assets/{AgentConfigPage-CQj2B678.js.map → AgentConfigPage-Exjtjbvp.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-CzrD8JJI.js → AgentDetailPage-nDbZo6RB.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-CzrD8JJI.js.map → AgentDetailPage-nDbZo6RB.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-jg2OFwyW.js → AgentsPage-Dsk9iHps.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-jg2OFwyW.js.map → AgentsPage-Dsk9iHps.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-D1mCqj8Z.js +2 -0
- package/dashboard/dist/assets/{AvailableEscalationsPage-IS28XBRe.js.map → AvailableEscalationsPage-D1mCqj8Z.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-DvEJFpwS.js → BotPicker-u5760MWZ.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-DvEJFpwS.js.map → BotPicker-u5760MWZ.js.map} +1 -1
- package/dashboard/dist/assets/{BulkAssignModal-BoQCi-YC.js → BulkAssignModal-CtISXXmW.js} +2 -2
- package/dashboard/dist/assets/{BulkAssignModal-BoQCi-YC.js.map → BulkAssignModal-CtISXXmW.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-kwlrcb_t.js → CapabilitiesPage-BKIlrQfU.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-kwlrcb_t.js.map → CapabilitiesPage-BKIlrQfU.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-UMIV4KIj.js → CollapsibleSection-DlBVTFqc.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-UMIV4KIj.js.map → CollapsibleSection-DlBVTFqc.js.map} +1 -1
- package/dashboard/dist/assets/{ConfirmDeleteModal-C5puwMg7.js → ConfirmDeleteModal-Pabkchg_.js} +2 -2
- package/dashboard/dist/assets/{ConfirmDeleteModal-C5puwMg7.js.map → ConfirmDeleteModal-Pabkchg_.js.map} +1 -1
- package/dashboard/dist/assets/{ConnectedAppsPage-drZa65kP.js → ConnectedAppsPage-DG2YZGN3.js} +2 -2
- package/dashboard/dist/assets/{ConnectedAppsPage-drZa65kP.js.map → ConnectedAppsPage-DG2YZGN3.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-5t8S2qMi.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-5t8S2qMi.js.map +1 -0
- package/dashboard/dist/assets/{CredentialsPage-CaFQ6vLb.js → CredentialsPage-CPVbYQow.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-CaFQ6vLb.js.map → CredentialsPage-CPVbYQow.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-Co5j7tzW.js → CronLabel-C7UbTrO-.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-Co5j7tzW.js.map → CronLabel-C7UbTrO-.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-B1Tdfddc.js → DropZone-DCZpORx-.js} +2 -2
- package/dashboard/dist/assets/{DropZone-B1Tdfddc.js.map → DropZone-DCZpORx-.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-WJyE9G0T.js → ElapsedCell-Yz7-LbtE.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-WJyE9G0T.js.map → ElapsedCell-Yz7-LbtE.js.map} +1 -1
- package/dashboard/dist/assets/{EntityTimelinePanel-CDFFfX9d.js → EntityTimelinePanel-xgPRdKEq.js} +2 -2
- package/dashboard/dist/assets/{EntityTimelinePanel-CDFFfX9d.js.map → EntityTimelinePanel-xgPRdKEq.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-CMrUlaKI.js → EscalationListSchemaPage-CqCd-y6B.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-CMrUlaKI.js.map → EscalationListSchemaPage-CqCd-y6B.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-Dx2Lyfrz.js → EscalationSchemaPage-D8YVQNph.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-Dx2Lyfrz.js.map → EscalationSchemaPage-D8YVQNph.js.map} +1 -1
- package/dashboard/dist/assets/EscalationTimeline-D6jrDcBb.js +2 -0
- package/dashboard/dist/assets/{EscalationTimeline-d3nvshOk.js.map → EscalationTimeline-D6jrDcBb.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationTitleSelect-DWQieOM9.js → EscalationTitleSelect-CGGT-QTu.js} +2 -2
- package/dashboard/dist/assets/{EscalationTitleSelect-DWQieOM9.js.map → EscalationTitleSelect-CGGT-QTu.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-BX8Cpi0X.js → EscalationsOverview-B7O4ttP4.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-BX8Cpi0X.js.map → EscalationsOverview-B7O4ttP4.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-D1-ITVXS.js → EventTable-DJniXnhe.js} +2 -2
- package/dashboard/dist/assets/{EventTable-D1-ITVXS.js.map → EventTable-DJniXnhe.js.map} +1 -1
- package/dashboard/dist/assets/{FilterBar-ABC9ctin.js → FilterBar-f-LEKcHI.js} +2 -2
- package/dashboard/dist/assets/{FilterBar-ABC9ctin.js.map → FilterBar-f-LEKcHI.js.map} +1 -1
- package/dashboard/dist/assets/{FormattedValue-k27T4bG7.js → FormattedValue-CUtv1g3k.js} +2 -2
- package/dashboard/dist/assets/{FormattedValue-k27T4bG7.js.map → FormattedValue-CUtv1g3k.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-DKUErxWc.js → GraphInvokePage-CR0MtYv8.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-DKUErxWc.js.map → GraphInvokePage-CR0MtYv8.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-C6u9dIpR.js → HomePage-HVw7T5ER.js} +2 -2
- package/dashboard/dist/assets/{HomePage-C6u9dIpR.js.map → HomePage-HVw7T5ER.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-gA5rFzkz.js → ListToolbar-CTGK5BXW.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-gA5rFzkz.js.map → ListToolbar-CTGK5BXW.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-BOPo3J_i.js → McpOverview-B2bbKKu2.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-BOPo3J_i.js.map → McpOverview-B2bbKKu2.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-BRVVi0Zh.js → McpQueryDetailPage-DgLxwwJy.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-BRVVi0Zh.js.map → McpQueryDetailPage-DgLxwwJy.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-Dmbb6SAc.js → McpQueryPage-CSkczeJP.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-Dmbb6SAc.js.map → McpQueryPage-CSkczeJP.js.map} +1 -1
- package/dashboard/dist/assets/McpRunDetailPage-CrUsDPRD.js +2 -0
- package/dashboard/dist/assets/{McpRunDetailPage-bMXNO-6V.js.map → McpRunDetailPage-CrUsDPRD.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-Bi9AMWja.js → McpRunsPage-twxFdTtJ.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-Bi9AMWja.js.map → McpRunsPage-twxFdTtJ.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-DwM5pNA4.js → NamespacePill-CSZT2fA4.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-DwM5pNA4.js.map → NamespacePill-CSZT2fA4.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-CvR74ZUJ.js → OperationsPage-gOVMrL_a.js} +3 -3
- package/dashboard/dist/assets/{OperationsPage-CvR74ZUJ.js.map → OperationsPage-gOVMrL_a.js.map} +1 -1
- package/dashboard/dist/assets/OperatorDashboard-BoeZIyup.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-DctmZw10.js.map → OperatorDashboard-BoeZIyup.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-CMd3f4bz.js → PageHeader-C4z1ExF7.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-CMd3f4bz.js.map → PageHeader-C4z1ExF7.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-DHj8Mqox.js → PageHeaderWithStats-B_f_Sz_Q.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-DHj8Mqox.js.map → PageHeaderWithStats-B_f_Sz_Q.js.map} +1 -1
- package/dashboard/dist/assets/{PersonaDetailPage-Ubqm2rQH.js → PersonaDetailPage-CDktk1mi.js} +2 -2
- package/dashboard/dist/assets/{PersonaDetailPage-Ubqm2rQH.js.map → PersonaDetailPage-CDktk1mi.js.map} +1 -1
- package/dashboard/dist/assets/{PersonasPage-BpFL2rpo.js → PersonasPage-Bs097x07.js} +2 -2
- package/dashboard/dist/assets/{PersonasPage-BpFL2rpo.js.map → PersonasPage-Bs097x07.js.map} +1 -1
- package/dashboard/dist/assets/PortalPage-DjfjerpF.js +2 -0
- package/dashboard/dist/assets/{PortalPage-Dfc_Q02R.js.map → PortalPage-DjfjerpF.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-liJspvQh.js → ProcessDetailPage-B30RatjD.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-liJspvQh.js.map → ProcessDetailPage-B30RatjD.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-C3OM5ZUy.js → ProcessesListPage-DrvjzgWT.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-C3OM5ZUy.js.map → ProcessesListPage-DrvjzgWT.js.map} +1 -1
- package/dashboard/dist/assets/ResolverForm-Bl8Pibyz.js +8 -0
- package/dashboard/dist/assets/ResolverForm-Bl8Pibyz.js.map +1 -0
- package/dashboard/dist/assets/{RoleDetailPage-CrlYCh-R.js → RoleDetailPage-ClYgn-3d.js} +3 -3
- package/dashboard/dist/assets/{RoleDetailPage-CrlYCh-R.js.map → RoleDetailPage-ClYgn-3d.js.map} +1 -1
- package/dashboard/dist/assets/{RolePill-Di2CAHuD.js → RolePill-Bw_n7EpR.js} +2 -2
- package/dashboard/dist/assets/{RolePill-Di2CAHuD.js.map → RolePill-Bw_n7EpR.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-BaazL_pt.js → RolesPage-B_3qWsvQ.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-BaazL_pt.js.map → RolesPage-B_3qWsvQ.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-DxuV0fa1.js → RunAsSelector-CbJT50Hp.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-DxuV0fa1.js.map → RunAsSelector-CbJT50Hp.js.map} +1 -1
- package/dashboard/dist/assets/{ScanCodesPage-C0mvmYTq.js → ScanCodesPage-CIIDuh8w.js} +2 -2
- package/dashboard/dist/assets/{ScanCodesPage-C0mvmYTq.js.map → ScanCodesPage-CIIDuh8w.js.map} +1 -1
- package/dashboard/dist/assets/{ScanSchemeDetailPage-CiJgt5dZ.js → ScanSchemeDetailPage-Bd3P3eyh.js} +2 -2
- package/dashboard/dist/assets/{ScanSchemeDetailPage-CiJgt5dZ.js.map → ScanSchemeDetailPage-Bd3P3eyh.js.map} +1 -1
- package/dashboard/dist/assets/ScanStationPage-BiO34nov.js +2 -0
- package/dashboard/dist/assets/ScanStationPage-BiO34nov.js.map +1 -0
- package/dashboard/dist/assets/{ScopeBadge-CSItPVOH.js → ScopeBadge-CKgfwTy3.js} +2 -2
- package/dashboard/dist/assets/{ScopeBadge-CSItPVOH.js.map → ScopeBadge-CKgfwTy3.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-CqbjVY6T.js → StickyPagination-BNOuYZ1m.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-CqbjVY6T.js.map → StickyPagination-BNOuYZ1m.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-wX3i0aqs.js → StreamMessageDetail-ALVUnfF1.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-wX3i0aqs.js.map → StreamMessageDetail-ALVUnfF1.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-C50PsFoV.js → SwimlaneTimeline-B5b8-xqI.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-C50PsFoV.js.map → SwimlaneTimeline-B5b8-xqI.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-CAgdLN_b.js → TagInput-B6fMAqxd.js} +2 -2
- package/dashboard/dist/assets/{TagInput-CAgdLN_b.js.map → TagInput-B6fMAqxd.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-bbNSv4Ae.js → TaskDetailPage-CoV3WcTA.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-bbNSv4Ae.js.map → TaskDetailPage-CoV3WcTA.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-h0oorVMe.js → TaskQueuePill-BnDp_DE6.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-h0oorVMe.js.map → TaskQueuePill-BnDp_DE6.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CDWnm_d9.js → TasksListPage-CkMmOYOE.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CDWnm_d9.js.map → TasksListPage-CkMmOYOE.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-BLjqlfoX.js → TimeAgo-DVkFgECX.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-BLjqlfoX.js.map → TimeAgo-DVkFgECX.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-CnW0ENb5.js → TimestampCell-CfTc6Txy.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-CnW0ENb5.js.map → TimestampCell-CfTc6Txy.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-CQCsVdzD.js → ToolPill-BRZWo4l8.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-CQCsVdzD.js.map → ToolPill-BRZWo4l8.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-DkMgCB-k.js → ToolTestPanel-kvqZqM0A.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-DkMgCB-k.js.map → ToolTestPanel-kvqZqM0A.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-g3pljKKX.js → TopicDetailPage-CjkAAyy8.js} +4 -4
- package/dashboard/dist/assets/{TopicDetailPage-g3pljKKX.js.map → TopicDetailPage-CjkAAyy8.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-CVcCYdy-.js → TopicsPage-CJZnv50y.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-CVcCYdy-.js.map → TopicsPage-CJZnv50y.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-D6zGIK2i.js → UserName-CPJPZnN1.js} +2 -2
- package/dashboard/dist/assets/{UserName-D6zGIK2i.js.map → UserName-CPJPZnN1.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-46UV5pJd.js +2 -0
- package/dashboard/dist/assets/{WorkflowExecutionPage-B8g81FCJ.js.map → WorkflowExecutionPage-46UV5pJd.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-Dg7BDTyM.js → WorkflowPill-Cly9Ln_3.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-Dg7BDTyM.js.map → WorkflowPill-Cly9Ln_3.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-nxT3bv6W.js → WorkflowsDashboard-DtxYdQbQ.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-nxT3bv6W.js.map → WorkflowsDashboard-DtxYdQbQ.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-grI-KAI2.js → WorkflowsOverview-Cg4LSfck.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-grI-KAI2.js.map → WorkflowsOverview-Cg4LSfck.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-BoZ-hfBE.js → YamlWorkflowDetailPage-Del-RCgi.js} +12 -12
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-BoZ-hfBE.js.map → YamlWorkflowDetailPage-Del-RCgi.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-Bf04rwRv.js → YamlWorkflowsPage-BF6Jtx24.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-Bf04rwRv.js.map → YamlWorkflowsPage-BF6Jtx24.js.map} +1 -1
- package/dashboard/dist/assets/{agents-Dphpdi8l.js → agents-wL-3dMOX.js} +2 -2
- package/dashboard/dist/assets/{agents-Dphpdi8l.js.map → agents-wL-3dMOX.js.map} +1 -1
- package/dashboard/dist/assets/{bots-DIFE298x.js → bots-D72EsztP.js} +2 -2
- package/dashboard/dist/assets/{bots-DIFE298x.js.map → bots-D72EsztP.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-D7Y7LClY.js → capabilities-DXFo6bRb.js} +2 -2
- package/dashboard/dist/assets/{capabilities-D7Y7LClY.js.map → capabilities-DXFo6bRb.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-D1TGcYsS.js → controlplane-gQdOgVtH.js} +2 -2
- package/dashboard/dist/assets/{controlplane-D1TGcYsS.js.map → controlplane-gQdOgVtH.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-CSE5wfWQ.js +2 -0
- package/dashboard/dist/assets/escalation-columns-CSE5wfWQ.js.map +1 -0
- package/dashboard/dist/assets/escalation-items-BIgBWbaw.js +2 -0
- package/dashboard/dist/assets/escalation-items-BIgBWbaw.js.map +1 -0
- package/dashboard/dist/assets/{index-CeCtqujG.js → index-BaCDL5fh.js} +38 -38
- package/dashboard/dist/assets/index-BaCDL5fh.js.map +1 -0
- package/dashboard/dist/assets/{index-Dl_8hbYI.js → index-Btm8FtTQ.js} +2 -2
- package/dashboard/dist/assets/{index-Dl_8hbYI.js.map → index-Btm8FtTQ.js.map} +1 -1
- package/dashboard/dist/assets/index-BvH8T-jb.js +2 -0
- package/dashboard/dist/assets/{index-CxdjT1Yk.js.map → index-BvH8T-jb.js.map} +1 -1
- package/dashboard/dist/assets/{index-HLu98Sqh.js → index-C-QQ5rKF.js} +2 -2
- package/dashboard/dist/assets/{index-HLu98Sqh.js.map → index-C-QQ5rKF.js.map} +1 -1
- package/dashboard/dist/assets/{index-DpKOr311.js → index-C1iuZ4Q5.js} +2 -2
- package/dashboard/dist/assets/{index-DpKOr311.js.map → index-C1iuZ4Q5.js.map} +1 -1
- package/dashboard/dist/assets/{index-ChV7pBjM.js → index-CDxOQ84B.js} +2 -2
- package/dashboard/dist/assets/{index-ChV7pBjM.js.map → index-CDxOQ84B.js.map} +1 -1
- package/dashboard/dist/assets/index-CED9cLE2.js +3 -0
- package/dashboard/dist/assets/index-CED9cLE2.js.map +1 -0
- package/dashboard/dist/assets/index-CnU7Jjz3.css +1 -0
- package/dashboard/dist/assets/{index-DAvi5_2T.js → index-CptTEhWp.js} +2 -2
- package/dashboard/dist/assets/{index-DAvi5_2T.js.map → index-CptTEhWp.js.map} +1 -1
- package/dashboard/dist/assets/{index-ag2pCAPu.js → index-Cu0xdX2t.js} +2 -2
- package/dashboard/dist/assets/{index-ag2pCAPu.js.map → index-Cu0xdX2t.js.map} +1 -1
- package/dashboard/dist/assets/index-D1lfq5iZ.js +13 -0
- package/dashboard/dist/assets/{index-gkxPN7Vx.js.map → index-D1lfq5iZ.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dy1wWu2e.js → index-czgLyfSo.js} +2 -2
- package/dashboard/dist/assets/{index-Dy1wWu2e.js.map → index-czgLyfSo.js.map} +1 -1
- package/dashboard/dist/assets/{index-DN-kX7hL.js → index-h1Useskt.js} +2 -2
- package/dashboard/dist/assets/{index-DN-kX7hL.js.map → index-h1Useskt.js.map} +1 -1
- package/dashboard/dist/assets/{index-BzYcUPdD.js → index-ujGmmwl6.js} +2 -2
- package/dashboard/dist/assets/{index-BzYcUPdD.js.map → index-ujGmmwl6.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-DnMs-SOJ.js → knowledge-150lsM68.js} +2 -2
- package/dashboard/dist/assets/{knowledge-DnMs-SOJ.js.map → knowledge-150lsM68.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-CwW-K3r4.js → mcp-CRtY4v5W.js} +2 -2
- package/dashboard/dist/assets/{mcp-CwW-K3r4.js.map → mcp-CRtY4v5W.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-BZt5e8_Q.js → mcp-query-CDsSMaYq.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-BZt5e8_Q.js.map → mcp-query-CDsSMaYq.js.map} +1 -1
- package/dashboard/dist/assets/{personas-UA-Xn60f.js → personas-C2Wm4ng1.js} +2 -2
- package/dashboard/dist/assets/{personas-UA-Xn60f.js.map → personas-C2Wm4ng1.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-Cu3PARum.js → pipelines-BH0F9-jf.js} +2 -2
- package/dashboard/dist/assets/{pipelines-Cu3PARum.js.map → pipelines-BH0F9-jf.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-BO6h8GJE.js → tasks-BkUVpaOa.js} +2 -2
- package/dashboard/dist/assets/{tasks-BO6h8GJE.js.map → tasks-BkUVpaOa.js.map} +1 -1
- package/dashboard/dist/assets/{topics-CWjCR-17.js → topics-Crl8HKHq.js} +2 -2
- package/dashboard/dist/assets/{topics-CWjCR-17.js.map → topics-Crl8HKHq.js.map} +1 -1
- package/dashboard/dist/assets/{useDebouncedValue-6t0jx1RB.js → useDebouncedValue-Dk3r5xo-.js} +2 -2
- package/dashboard/dist/assets/{useDebouncedValue-6t0jx1RB.js.map → useDebouncedValue-Dk3r5xo-.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-CqpoQx6D.js → useNamespace-BXuyHKAs.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-CqpoQx6D.js.map → useNamespace-BXuyHKAs.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-hmCQQwzB.js → useYamlActivityEvents-DSlkjQfb.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-hmCQQwzB.js.map → useYamlActivityEvents-DSlkjQfb.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-CeVtUKyP.js → vendor-icons-C_DQEeBy.js} +2 -2
- package/dashboard/dist/assets/{vendor-icons-CeVtUKyP.js.map → vendor-icons-C_DQEeBy.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-ziUdZhKY.js → yaml-workflows-B230v5Za.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-ziUdZhKY.js.map → yaml-workflows-B230v5Za.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/faceted-routing.md +14 -1
- package/docs/hitl/x-lt-embed.md +4 -1
- package/docs/hitl/x-lt-list-schema.md +19 -2
- package/docs/hitl/x-lt-roster.md +112 -0
- package/docs/hitl/x-lt-show-if.md +15 -0
- package/docs/hitl/x-lt-widget.md +1 -0
- package/docs/hitl-guide.md +3 -1
- package/docs/maintenance.md +3 -3
- package/docs/mcp.md +2 -0
- package/docs/scan-codes.md +4 -0
- package/docs/schema-enforcement.md +5 -0
- package/package.json +2 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-IS28XBRe.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-LG03dVgR.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-LG03dVgR.js.map +0 -1
- package/dashboard/dist/assets/EscalationTimeline-d3nvshOk.js +0 -2
- package/dashboard/dist/assets/McpRunDetailPage-bMXNO-6V.js +0 -2
- package/dashboard/dist/assets/OperatorDashboard-DctmZw10.js +0 -2
- package/dashboard/dist/assets/PortalPage-Dfc_Q02R.js +0 -2
- package/dashboard/dist/assets/ResolverForm-B1_x8Blv.js +0 -8
- package/dashboard/dist/assets/ResolverForm-B1_x8Blv.js.map +0 -1
- package/dashboard/dist/assets/ScanStationPage-DzGaXc40.js +0 -2
- package/dashboard/dist/assets/ScanStationPage-DzGaXc40.js.map +0 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-B8g81FCJ.js +0 -2
- package/dashboard/dist/assets/escalation-columns-cwZE6xyo.js +0 -2
- package/dashboard/dist/assets/escalation-columns-cwZE6xyo.js.map +0 -1
- package/dashboard/dist/assets/index-CeCtqujG.js.map +0 -1
- package/dashboard/dist/assets/index-CniRa65p.js +0 -3
- package/dashboard/dist/assets/index-CniRa65p.js.map +0 -1
- package/dashboard/dist/assets/index-CxdjT1Yk.js +0 -2
- package/dashboard/dist/assets/index-gkxPN7Vx.js +0 -13
- package/dashboard/dist/assets/index-m9A3UYst.css +0 -1
- package/dashboard/dist/assets/validate-BIRn4a6d.js +0 -2
- 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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
88
|
-
function
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
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
|
|
122
|
-
|
|
123
|
-
hostIds, scope,
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
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[
|
|
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;
|
package/build/modules/config.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
/**
|
|
50
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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>>;
|