@hotmeshio/long-tail 0.5.12 → 0.6.2
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/README.md +1 -0
- package/build/api/escalations/bulk.js +9 -1
- package/build/api/escalations/cancel.js +5 -7
- package/build/api/escalations/claim.js +18 -15
- package/build/api/escalations/create.d.ts +6 -0
- package/build/api/escalations/create.js +11 -8
- package/build/api/escalations/facets.d.ts +18 -0
- package/build/api/escalations/facets.js +115 -0
- package/build/api/escalations/helpers.d.ts +70 -1
- package/build/api/escalations/helpers.js +101 -7
- package/build/api/escalations/index.d.ts +3 -2
- package/build/api/escalations/index.js +7 -1
- package/build/api/escalations/list.d.ts +21 -2
- package/build/api/escalations/list.js +109 -10
- package/build/api/escalations/metadata.js +22 -27
- package/build/api/escalations/resolve.d.ts +19 -1
- package/build/api/escalations/resolve.js +111 -26
- package/build/api/escalations/single.js +8 -8
- package/build/api/users.d.ts +6 -0
- package/build/api/users.js +29 -1
- package/build/bin/ltc.js +40 -0
- package/build/lib/cli/commands/escalations.d.ts +36 -0
- package/build/lib/cli/commands/escalations.js +98 -0
- package/build/lib/db/schemas/001_schema.sql +10 -5
- package/build/lib/db/schemas/012_lt_tasks_workflow_id_unique.sql +20 -0
- package/build/lib/db/schemas/013_role_scope.sql +37 -0
- package/build/modules/version.d.ts +10 -5
- package/build/modules/version.js +27 -11
- package/build/routes/escalations/facets.d.ts +7 -0
- package/build/routes/escalations/facets.js +81 -0
- package/build/routes/escalations/index.js +3 -0
- package/build/routes/escalations/list.js +46 -0
- package/build/routes/escalations/resolve.js +22 -4
- package/build/routes/users.js +9 -5
- package/build/sdk/index.d.ts +33 -2
- package/build/sdk/index.js +7 -0
- package/build/services/escalation/crud.d.ts +56 -6
- package/build/services/escalation/crud.js +102 -31
- package/build/services/escalation/facet-sql.d.ts +18 -0
- package/build/services/escalation/facet-sql.js +118 -0
- package/build/services/escalation/facets.d.ts +45 -0
- package/build/services/escalation/facets.js +218 -0
- package/build/services/escalation/index.d.ts +1 -0
- package/build/services/escalation/index.js +8 -1
- package/build/services/escalation/queries.d.ts +45 -0
- package/build/services/escalation/queries.js +107 -6
- package/build/services/escalation/sql.d.ts +10 -4
- package/build/services/escalation/sql.js +36 -8
- package/build/services/interceptor/activities/escalation.d.ts +2 -0
- package/build/services/interceptor/activities/escalation.js +1 -1
- package/build/services/interceptor/lifecycle.js +4 -0
- package/build/services/mcp/server-tools.js +10 -4
- package/build/services/orchestrator/condition.js +6 -2
- package/build/services/role/index.js +2 -3
- package/build/services/role/sql.d.ts +8 -0
- package/build/services/role/sql.js +17 -1
- package/build/services/task/crud.js +22 -14
- package/build/services/task/process.js +3 -21
- package/build/services/task/sql.d.ts +24 -1
- package/build/services/task/sql.js +65 -1
- package/build/services/user/crud.d.ts +1 -0
- package/build/services/user/crud.js +29 -11
- package/build/services/user/index.d.ts +2 -1
- package/build/services/user/index.js +12 -1
- package/build/services/user/rbac.d.ts +18 -0
- package/build/services/user/rbac.js +27 -0
- package/build/services/user/roles.d.ts +7 -2
- package/build/services/user/roles.js +21 -5
- package/build/services/user/scope.d.ts +22 -0
- package/build/services/user/scope.js +50 -0
- package/build/services/user/seed-admin.js +34 -17
- package/build/services/user/sql.d.ts +18 -7
- package/build/services/user/sql.js +38 -13
- package/build/services/user/sso-provision.js +41 -24
- package/build/services/user/types.d.ts +9 -5
- package/build/system/mcp-servers/admin/escalations.js +65 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +325 -0
- package/build/system/mcp-servers/admin/schemas.js +63 -2
- package/build/system/mcp-servers/admin/users.js +28 -2
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/envelope.d.ts +7 -0
- package/build/types/facets.d.ts +60 -0
- package/build/types/facets.js +10 -0
- package/build/types/index.d.ts +2 -1
- package/build/types/user.d.ts +11 -0
- package/dashboard/dist/assets/AdminDashboard-C5gfVEeL.js +2 -0
- package/dashboard/dist/assets/{AdminDashboard-LTiYLuzq.js.map → AdminDashboard-C5gfVEeL.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js → AgentConfigPage-CWEnuiGN.js} +6 -6
- package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js.map → AgentConfigPage-CWEnuiGN.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-BSUnDvGp.js → AgentDetailPage-B6IE27iK.js} +3 -3
- package/dashboard/dist/assets/AgentDetailPage-B6IE27iK.js.map +1 -0
- package/dashboard/dist/assets/AgentsPage-Blhh2RsK.js +2 -0
- package/dashboard/dist/assets/{AgentsPage-DpS0cCYe.js.map → AgentsPage-Blhh2RsK.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js.map +1 -0
- package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js → BotPicker-DSozotGk.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js.map → BotPicker-DSozotGk.js.map} +1 -1
- package/dashboard/dist/assets/CapabilitiesPage-CrS_DTA1.js +2 -0
- package/dashboard/dist/assets/CapabilitiesPage-CrS_DTA1.js.map +1 -0
- package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js → CollapsibleSection-1i6bOQ1L.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js.map → CollapsibleSection-1i6bOQ1L.js.map} +1 -1
- package/dashboard/dist/assets/CopyableId-blg-gZvT.js +2 -0
- package/dashboard/dist/assets/CopyableId-blg-gZvT.js.map +1 -0
- package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js → CredentialsPage-BxJio06E.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js.map → CredentialsPage-BxJio06E.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js → CronLabel-BAfIHHz4.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js.map → CronLabel-BAfIHHz4.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js → CustomDurationPicker-BLF0NFJK.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js.map → CustomDurationPicker-BLF0NFJK.js.map} +1 -1
- package/dashboard/dist/assets/{DataTable-D9yuBv0w.js → DataTable-ZWgVQg7F.js} +2 -2
- package/dashboard/dist/assets/DataTable-ZWgVQg7F.js.map +1 -0
- package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js → DropZone-CEx_wk-g.js} +2 -2
- package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js.map → DropZone-CEx_wk-g.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js → ElapsedCell-nKGJvznH.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js.map → ElapsedCell-nKGJvznH.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js → EscalationsOverview-bNNVA6R2.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js.map → EscalationsOverview-bNNVA6R2.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-CeKfFk3m.js → EventTable-BCuDdqlU.js} +2 -2
- package/dashboard/dist/assets/{EventTable-CeKfFk3m.js.map → EventTable-BCuDdqlU.js.map} +1 -1
- package/dashboard/dist/assets/FilterBar-BQ2JGg8U.js +2 -0
- package/dashboard/dist/assets/FilterBar-BQ2JGg8U.js.map +1 -0
- package/dashboard/dist/assets/GraphInvokePage-BPAvqHls.js +2 -0
- package/dashboard/dist/assets/GraphInvokePage-BPAvqHls.js.map +1 -0
- package/dashboard/dist/assets/HomePage-BZ1YTrjR.js +2 -0
- package/dashboard/dist/assets/HomePage-BZ1YTrjR.js.map +1 -0
- package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js → ListToolbar-DIJZfEgu.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js.map → ListToolbar-DIJZfEgu.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js → McpOverview-DCfSCfPg.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js.map → McpOverview-DCfSCfPg.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js → McpQueryDetailPage-mb7rmtNe.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js.map → McpQueryDetailPage-mb7rmtNe.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js → McpQueryPage-D9b1T0I3.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js.map → McpQueryPage-D9b1T0I3.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js → McpRunDetailPage-Bz2UyuNm.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js.map → McpRunDetailPage-Bz2UyuNm.js.map} +1 -1
- package/dashboard/dist/assets/McpRunsPage-Dw7w_ZO-.js +2 -0
- package/dashboard/dist/assets/McpRunsPage-Dw7w_ZO-.js.map +1 -0
- package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js → NamespacePill-D27vqDXy.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js.map → NamespacePill-D27vqDXy.js.map} +1 -1
- package/dashboard/dist/assets/OperatorDashboard-CTzSDW3N.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-g9o7I925.js.map → OperatorDashboard-CTzSDW3N.js.map} +1 -1
- package/dashboard/dist/assets/PageHeader-B_1zTx2q.js +2 -0
- package/dashboard/dist/assets/PageHeader-B_1zTx2q.js.map +1 -0
- package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js → PageHeaderWithStats-D-cPg7UD.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js.map → PageHeaderWithStats-D-cPg7UD.js.map} +1 -1
- package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js +2 -0
- package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js.map +1 -0
- package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js → ProcessDetailPage-olG2CS-h.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js.map → ProcessDetailPage-olG2CS-h.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js → ProcessesListPage-CUZSL06Y.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js.map → ProcessesListPage-CUZSL06Y.js.map} +1 -1
- package/dashboard/dist/assets/RolePill-B-V-31lC.js +2 -0
- package/dashboard/dist/assets/RolePill-B-V-31lC.js.map +1 -0
- package/dashboard/dist/assets/RolesPage-DLnaW6iD.js +2 -0
- package/dashboard/dist/assets/RolesPage-DLnaW6iD.js.map +1 -0
- package/dashboard/dist/assets/RowActions-BA9L9djf.js +2 -0
- package/dashboard/dist/assets/RowActions-BA9L9djf.js.map +1 -0
- package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js → RunAsSelector-BoLHF4fy.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js.map → RunAsSelector-BoLHF4fy.js.map} +1 -1
- package/dashboard/dist/assets/StickyPagination-BEMY1L-H.js +2 -0
- package/dashboard/dist/assets/StickyPagination-BEMY1L-H.js.map +1 -0
- package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js → StreamMessageDetail-CVRVWXcU.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js.map → StreamMessageDetail-CVRVWXcU.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js → SwimlaneTimeline-DDpU0-Jn.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js.map → SwimlaneTimeline-DDpU0-Jn.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-CZSeyesi.js → TagInput-BHVfG307.js} +2 -2
- package/dashboard/dist/assets/{TagInput-CZSeyesi.js.map → TagInput-BHVfG307.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js → TaskDetailPage-B_xQtJMg.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js.map → TaskDetailPage-B_xQtJMg.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js → TaskQueuePill-CGlxXqWt.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js.map → TaskQueuePill-CGlxXqWt.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js → TasksListPage-CsrdIOKk.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js.map → TasksListPage-CsrdIOKk.js.map} +1 -1
- package/dashboard/dist/assets/TimeAgo-DmicaxjT.js +2 -0
- package/dashboard/dist/assets/{TimeAgo-CUIYqSt_.js.map → TimeAgo-DmicaxjT.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell--tnreI1i.js → TimestampCell-DeK89eRV.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell--tnreI1i.js.map → TimestampCell-DeK89eRV.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-O09jsTQM.js → ToolPill-NV7PxSpA.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-O09jsTQM.js.map → ToolPill-NV7PxSpA.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js → ToolTestPanel-D3s2n9-9.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js.map → ToolTestPanel-D3s2n9-9.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-rh_dsTJI.js → TopicDetailPage-qyv7UWT_.js} +3 -3
- package/dashboard/dist/assets/TopicDetailPage-qyv7UWT_.js.map +1 -0
- package/dashboard/dist/assets/TopicsPage-CEUfMoed.js +2 -0
- package/dashboard/dist/assets/TopicsPage-CEUfMoed.js.map +1 -0
- package/dashboard/dist/assets/{UserName-C__c8BN0.js → UserName-Bn_BxI8K.js} +2 -2
- package/dashboard/dist/assets/{UserName-C__c8BN0.js.map → UserName-Bn_BxI8K.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js → WorkflowExecutionPage-DAk2RqpB.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js.map → WorkflowExecutionPage-DAk2RqpB.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js → WorkflowPill-DDR0B6DN.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js.map → WorkflowPill-DDR0B6DN.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowsDashboard-DfBgM80t.js +2 -0
- package/dashboard/dist/assets/WorkflowsDashboard-DfBgM80t.js.map +1 -0
- package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js → WorkflowsOverview--Dkoo9Jq.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js.map → WorkflowsOverview--Dkoo9Jq.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js → YamlWorkflowDetailPage-B6HYbfFj.js} +25 -25
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js.map → YamlWorkflowDetailPage-B6HYbfFj.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js → YamlWorkflowsPage-CJE-PxC0.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js.map → YamlWorkflowsPage-CJE-PxC0.js.map} +1 -1
- package/dashboard/dist/assets/{agents-xstNkF6c.js → agents-DUciUZpt.js} +2 -2
- package/dashboard/dist/assets/{agents-xstNkF6c.js.map → agents-DUciUZpt.js.map} +1 -1
- package/dashboard/dist/assets/{bots-BztAI0s_.js → bots-BnyqCfuC.js} +2 -2
- package/dashboard/dist/assets/{bots-BztAI0s_.js.map → bots-BnyqCfuC.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-CEDwtxNL.js → capabilities-CkTWWT0q.js} +2 -2
- package/dashboard/dist/assets/{capabilities-CEDwtxNL.js.map → capabilities-CkTWWT0q.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-BlGEsRKe.js → controlplane-D9HaFZsC.js} +2 -2
- package/dashboard/dist/assets/{controlplane-BlGEsRKe.js.map → controlplane-D9HaFZsC.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-iI4QKlP-.js → escalation-De9JnTKI.js} +2 -2
- package/dashboard/dist/assets/{escalation-iI4QKlP-.js.map → escalation-De9JnTKI.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-Df7Pluzg.js +2 -0
- package/dashboard/dist/assets/escalation-columns-Df7Pluzg.js.map +1 -0
- package/dashboard/dist/assets/index-7hqtQI43.js +2 -0
- package/dashboard/dist/assets/index-7hqtQI43.js.map +1 -0
- package/dashboard/dist/assets/{index-CAqEy2Db.js → index-B07xLlyz.js} +2 -2
- package/dashboard/dist/assets/{index-CAqEy2Db.js.map → index-B07xLlyz.js.map} +1 -1
- package/dashboard/dist/assets/{index-D1xVY4JR.js → index-B1kc6itM.js} +24 -24
- package/dashboard/dist/assets/index-B1kc6itM.js.map +1 -0
- package/dashboard/dist/assets/index-BAhkWcOq.js +9 -0
- package/dashboard/dist/assets/index-BAhkWcOq.js.map +1 -0
- package/dashboard/dist/assets/{index-Dsi5o7dx.js → index-BsIaUbKD.js} +2 -2
- package/dashboard/dist/assets/{index-Dsi5o7dx.js.map → index-BsIaUbKD.js.map} +1 -1
- package/dashboard/dist/assets/index-BxdiKgnH.js +6 -0
- package/dashboard/dist/assets/index-BxdiKgnH.js.map +1 -0
- package/dashboard/dist/assets/{index-B7VC0vjf.js → index-BzMsh6Ui.js} +2 -2
- package/dashboard/dist/assets/{index-B7VC0vjf.js.map → index-BzMsh6Ui.js.map} +1 -1
- package/dashboard/dist/assets/index-CN0efCE9.js +6 -0
- package/dashboard/dist/assets/index-CN0efCE9.js.map +1 -0
- package/dashboard/dist/assets/{index-B5qsp8lb.js → index-CgD9fA9P.js} +2 -2
- package/dashboard/dist/assets/{index-B5qsp8lb.js.map → index-CgD9fA9P.js.map} +1 -1
- package/dashboard/dist/assets/{index-CUfgt3mV.js → index-Cqjf-yVP.js} +2 -2
- package/dashboard/dist/assets/{index-CUfgt3mV.js.map → index-Cqjf-yVP.js.map} +1 -1
- package/dashboard/dist/assets/{index-DjJ6VCuc.js → index-DNugmBs7.js} +2 -2
- package/dashboard/dist/assets/{index-DjJ6VCuc.js.map → index-DNugmBs7.js.map} +1 -1
- package/dashboard/dist/assets/{index-BUmnJ4eT.js → index-DjxTV3cn.js} +2 -2
- package/dashboard/dist/assets/{index-BUmnJ4eT.js.map → index-DjxTV3cn.js.map} +1 -1
- package/dashboard/dist/assets/index-DpAS4-DA.js +2 -0
- package/dashboard/dist/assets/index-DpAS4-DA.js.map +1 -0
- package/dashboard/dist/assets/index-nO0cim3_.css +1 -0
- package/dashboard/dist/assets/{knowledge-BKg0cr39.js → knowledge-D4SvdNv5.js} +2 -2
- package/dashboard/dist/assets/{knowledge-BKg0cr39.js.map → knowledge-D4SvdNv5.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-CVdHpKlz.js → mcp-DVxt4T9J.js} +2 -2
- package/dashboard/dist/assets/{mcp-CVdHpKlz.js.map → mcp-DVxt4T9J.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js → mcp-query-CLP9EnnU.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js.map → mcp-query-CLP9EnnU.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-BOEv7P6O.js → pipelines-C_x0LTBN.js} +2 -2
- package/dashboard/dist/assets/{pipelines-BOEv7P6O.js.map → pipelines-C_x0LTBN.js.map} +1 -1
- package/dashboard/dist/assets/{roles-c5cV2QYQ.js → roles-QaGFZQ58.js} +2 -2
- package/dashboard/dist/assets/{roles-c5cV2QYQ.js.map → roles-QaGFZQ58.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-JV3BjeEF.js → tasks-1eTuP02d.js} +2 -2
- package/dashboard/dist/assets/{tasks-JV3BjeEF.js.map → tasks-1eTuP02d.js.map} +1 -1
- package/dashboard/dist/assets/{topics-BDaPnhnQ.js → topics-B3p8K0PW.js} +2 -2
- package/dashboard/dist/assets/{topics-BDaPnhnQ.js.map → topics-B3p8K0PW.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js → useEventHooks-BNZky4xM.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js.map → useEventHooks-BNZky4xM.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js → useNamespace-Dx81_JM0.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js.map → useNamespace-Dx81_JM0.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js → useYamlActivityEvents-D5T3TbE7.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js.map → useYamlActivityEvents-D5T3TbE7.js.map} +1 -1
- package/dashboard/dist/assets/users-BEMysNdR.js +2 -0
- package/dashboard/dist/assets/users-BEMysNdR.js.map +1 -0
- package/dashboard/dist/assets/{vendor-icons-D5ycKPA2.js → vendor-icons--Ivmp-RX.js} +132 -112
- package/dashboard/dist/assets/vendor-icons--Ivmp-RX.js.map +1 -0
- package/dashboard/dist/assets/{workflows-DF-9Voyi.js → workflows-C4rjvQbI.js} +2 -2
- package/dashboard/dist/assets/{workflows-DF-9Voyi.js.map → workflows-C4rjvQbI.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js → yaml-workflows-4rJ5v5rJ.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js.map → yaml-workflows-4rJ5v5rJ.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/http/escalations.md +131 -10
- package/docs/api/http/roles.md +44 -9
- package/docs/api/http/users.md +15 -6
- package/docs/api/mcp/admin.md +8 -2
- package/docs/api/mcp/human-queue.md +34 -2
- package/docs/api/sdk/escalations.md +155 -2
- package/docs/api/sdk/roles.md +11 -0
- package/docs/api/sdk/users.md +26 -1
- package/docs/auth.md +1 -1
- package/docs/dashboard.md +5 -4
- package/docs/data.md +9 -2
- package/docs/faceted-routing.md +221 -0
- package/docs/hitl-guide.md +50 -0
- package/docs/iam.md +31 -0
- package/docs/mcp.md +8 -0
- package/package.json +2 -2
- package/dashboard/dist/assets/AdminDashboard-LTiYLuzq.js +0 -2
- package/dashboard/dist/assets/AgentDetailPage-BSUnDvGp.js.map +0 -1
- package/dashboard/dist/assets/AgentsPage-DpS0cCYe.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js.map +0 -1
- package/dashboard/dist/assets/CapabilitiesPage-DLIleX2L.js +0 -2
- package/dashboard/dist/assets/CapabilitiesPage-DLIleX2L.js.map +0 -1
- package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js +0 -2
- package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js.map +0 -1
- package/dashboard/dist/assets/DataTable-D9yuBv0w.js.map +0 -1
- package/dashboard/dist/assets/FilterBar-Ck4K4rzu.js +0 -2
- package/dashboard/dist/assets/FilterBar-Ck4K4rzu.js.map +0 -1
- package/dashboard/dist/assets/GraphInvokePage-BZBPBCSq.js +0 -2
- package/dashboard/dist/assets/GraphInvokePage-BZBPBCSq.js.map +0 -1
- package/dashboard/dist/assets/HomePage-dM7U2tl6.js +0 -2
- package/dashboard/dist/assets/HomePage-dM7U2tl6.js.map +0 -1
- package/dashboard/dist/assets/McpRunsPage-CPMYWgm_.js +0 -2
- package/dashboard/dist/assets/McpRunsPage-CPMYWgm_.js.map +0 -1
- package/dashboard/dist/assets/OperatorDashboard-g9o7I925.js +0 -2
- package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js +0 -2
- package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js.map +0 -1
- package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js +0 -2
- package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js.map +0 -1
- package/dashboard/dist/assets/RolePill-Du-aNnjR.js +0 -2
- package/dashboard/dist/assets/RolePill-Du-aNnjR.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-B2Kdf-L9.js +0 -2
- package/dashboard/dist/assets/RolesPage-B2Kdf-L9.js.map +0 -1
- package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js +0 -2
- package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js.map +0 -1
- package/dashboard/dist/assets/StickyPagination-BWhFSr2d.js +0 -2
- package/dashboard/dist/assets/StickyPagination-BWhFSr2d.js.map +0 -1
- package/dashboard/dist/assets/TimeAgo-CUIYqSt_.js +0 -2
- package/dashboard/dist/assets/TopicDetailPage-rh_dsTJI.js.map +0 -1
- package/dashboard/dist/assets/TopicsPage-CD_40_v-.js +0 -2
- package/dashboard/dist/assets/TopicsPage-CD_40_v-.js.map +0 -1
- package/dashboard/dist/assets/WorkflowsDashboard-BgI-iOP6.js +0 -2
- package/dashboard/dist/assets/WorkflowsDashboard-BgI-iOP6.js.map +0 -1
- package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js +0 -2
- package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js.map +0 -1
- package/dashboard/dist/assets/index-CMviPjXQ.js +0 -9
- package/dashboard/dist/assets/index-CMviPjXQ.js.map +0 -1
- package/dashboard/dist/assets/index-CP0JEQhZ.js +0 -6
- package/dashboard/dist/assets/index-CP0JEQhZ.js.map +0 -1
- package/dashboard/dist/assets/index-ClvYSNZd.css +0 -1
- package/dashboard/dist/assets/index-D1xVY4JR.js.map +0 -1
- package/dashboard/dist/assets/index-DPLy-jwO.js +0 -2
- package/dashboard/dist/assets/index-DPLy-jwO.js.map +0 -1
- package/dashboard/dist/assets/index-DzVYjoex.js +0 -6
- package/dashboard/dist/assets/index-DzVYjoex.js.map +0 -1
- package/dashboard/dist/assets/index-FuhiJuHL.js +0 -2
- package/dashboard/dist/assets/index-FuhiJuHL.js.map +0 -1
- package/dashboard/dist/assets/users-Pn85K_3C.js +0 -2
- package/dashboard/dist/assets/users-Pn85K_3C.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-D5ycKPA2.js.map +0 -1
package/build/routes/users.js
CHANGED
|
@@ -41,13 +41,14 @@ const router = (0, express_1.Router)();
|
|
|
41
41
|
/**
|
|
42
42
|
* GET /api/users
|
|
43
43
|
* List users with optional filters.
|
|
44
|
-
* Query: ?role=reviewer&roleType=admin&status=active&limit=50&offset=0
|
|
44
|
+
* Query: ?role=reviewer&roleType=admin&status=active&search=ada&limit=50&offset=0
|
|
45
45
|
*/
|
|
46
46
|
router.get('/', async (req, res) => {
|
|
47
47
|
const result = await api.listUsers({
|
|
48
48
|
role: req.query.role,
|
|
49
49
|
roleType: req.query.roleType,
|
|
50
50
|
status: req.query.status,
|
|
51
|
+
search: req.query.search,
|
|
51
52
|
limit: req.query.limit ? parseInt(req.query.limit, 10) : undefined,
|
|
52
53
|
offset: req.query.offset ? parseInt(req.query.offset, 10) : undefined,
|
|
53
54
|
});
|
|
@@ -99,15 +100,18 @@ router.get('/:id/roles', async (req, res) => {
|
|
|
99
100
|
/**
|
|
100
101
|
* POST /api/users/:id/roles
|
|
101
102
|
* Add a role to a user.
|
|
102
|
-
* Body: { role, type } — type must be superadmin,
|
|
103
|
+
* Body: { role, type, read_scope?, write_scope? } — type must be superadmin,
|
|
104
|
+
* admin, or member. read_scope (self|all) and write_scope (none|self|all) refine
|
|
105
|
+
* a member's work surface; they default to all/all and are ignored for admin/superadmin.
|
|
103
106
|
*
|
|
104
|
-
* Scoping rules:
|
|
107
|
+
* Scoping rules (management tier):
|
|
105
108
|
* - superadmin: can assign any role/type
|
|
106
109
|
* - engineer: can assign up to admin type, but never superadmin type
|
|
107
110
|
* - role/admin (non-builder): can only assign member/admin for roles they hold
|
|
111
|
+
* A caller who may assign a role may set any work-surface scope on it.
|
|
108
112
|
*/
|
|
109
113
|
router.post('/:id/roles', auth_1.requireAdmin, async (req, res) => {
|
|
110
|
-
const { role, type } = req.body || {};
|
|
114
|
+
const { role, type, read_scope, write_scope } = req.body || {};
|
|
111
115
|
const userId = req.auth.userId;
|
|
112
116
|
// Superadmin bypasses all scoping
|
|
113
117
|
const { isSuperAdmin } = await Promise.resolve().then(() => __importStar(require('../services/user/rbac')));
|
|
@@ -129,7 +133,7 @@ router.post('/:id/roles', auth_1.requireAdmin, async (req, res) => {
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
|
-
const result = await api.addUserRole({ id: req.params.id, role, type });
|
|
136
|
+
const result = await api.addUserRole({ id: req.params.id, role, type, read_scope, write_scope });
|
|
133
137
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
134
138
|
});
|
|
135
139
|
/**
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -82,6 +82,12 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
82
82
|
envelope?: string;
|
|
83
83
|
metadata?: Record<string, any>;
|
|
84
84
|
escalation_payload?: string;
|
|
85
|
+
origin_id?: string;
|
|
86
|
+
parent_id?: string;
|
|
87
|
+
task_id?: string;
|
|
88
|
+
workflow_id?: string;
|
|
89
|
+
task_queue?: string;
|
|
90
|
+
workflow_type?: string;
|
|
85
91
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
86
92
|
list: (input: {
|
|
87
93
|
status?: string;
|
|
@@ -96,7 +102,7 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
96
102
|
sort_by?: string;
|
|
97
103
|
order?: string;
|
|
98
104
|
search?: string;
|
|
99
|
-
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
105
|
+
} & import("../api/escalations/list").FacetInput, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
100
106
|
listAvailable: (input: {
|
|
101
107
|
role?: string;
|
|
102
108
|
type?: string;
|
|
@@ -107,8 +113,9 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
107
113
|
sort_by?: string;
|
|
108
114
|
order?: string;
|
|
109
115
|
search?: string;
|
|
110
|
-
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
116
|
+
} & import("../api/escalations/list").FacetInput, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
111
117
|
listTypes: typeof escalationsApi.listDistinctTypes;
|
|
118
|
+
listFacetKeys: (input: unknown, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
112
119
|
getStats: (input: {
|
|
113
120
|
period?: string;
|
|
114
121
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
@@ -131,6 +138,17 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
131
138
|
resolve: (input: {
|
|
132
139
|
id: string;
|
|
133
140
|
resolverPayload: Record<string, any>;
|
|
141
|
+
metadata?: Record<string, any>;
|
|
142
|
+
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
143
|
+
resolveBySignalKey: (input: {
|
|
144
|
+
signalKey: string;
|
|
145
|
+
resolverPayload: Record<string, any>;
|
|
146
|
+
metadata?: Record<string, any>;
|
|
147
|
+
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
148
|
+
resolveByIds: (input: {
|
|
149
|
+
ids: string[];
|
|
150
|
+
resolverPayload: Record<string, any>;
|
|
151
|
+
metadata?: Record<string, any>;
|
|
134
152
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
135
153
|
releaseExpired: typeof escalationsApi.releaseExpiredClaims;
|
|
136
154
|
updatePriority: (input: {
|
|
@@ -176,6 +194,19 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
176
194
|
assignee?: string;
|
|
177
195
|
metadata?: Record<string, any>;
|
|
178
196
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
197
|
+
searchByFacets: (input: import("..").FacetQuery, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
198
|
+
claimGroups: (input: {
|
|
199
|
+
query: import("..").FacetQuery;
|
|
200
|
+
limit?: number;
|
|
201
|
+
durationMinutes?: number;
|
|
202
|
+
sizeFacet?: string;
|
|
203
|
+
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
204
|
+
claimByFacets: (input: {
|
|
205
|
+
query: import("..").FacetQuery;
|
|
206
|
+
limit?: number;
|
|
207
|
+
durationMinutes?: number;
|
|
208
|
+
allOrNone?: boolean;
|
|
209
|
+
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
179
210
|
};
|
|
180
211
|
workflows: {
|
|
181
212
|
invoke: (input: {
|
package/build/sdk/index.js
CHANGED
|
@@ -117,6 +117,7 @@ function createClient(options = {}) {
|
|
|
117
117
|
list: bindAuth(escalationsApi.listEscalations, auth),
|
|
118
118
|
listAvailable: bindAuth(escalationsApi.listAvailableEscalations, auth),
|
|
119
119
|
listTypes: escalationsApi.listDistinctTypes,
|
|
120
|
+
listFacetKeys: bindAuth(escalationsApi.listFacetKeys, auth),
|
|
120
121
|
getStats: bindAuth(escalationsApi.getEscalationStats, auth),
|
|
121
122
|
get: bindAuth(escalationsApi.getEscalation, auth),
|
|
122
123
|
getByWorkflowId: escalationsApi.getEscalationsByWorkflowId,
|
|
@@ -124,6 +125,8 @@ function createClient(options = {}) {
|
|
|
124
125
|
claim: bindAuth(escalationsApi.claimEscalation, auth),
|
|
125
126
|
release: bindAuth(escalationsApi.releaseEscalation, auth),
|
|
126
127
|
resolve: bindAuth(escalationsApi.resolveEscalation, auth),
|
|
128
|
+
resolveBySignalKey: bindAuth(escalationsApi.resolveBySignalKey, auth),
|
|
129
|
+
resolveByIds: bindAuth(escalationsApi.resolveByIds, auth),
|
|
127
130
|
releaseExpired: escalationsApi.releaseExpiredClaims,
|
|
128
131
|
updatePriority: bindAuth(escalationsApi.updatePriority, auth),
|
|
129
132
|
bulkClaim: bindAuth(escalationsApi.bulkClaim, auth),
|
|
@@ -133,6 +136,10 @@ function createClient(options = {}) {
|
|
|
133
136
|
findByMetadata: bindAuth(escalationsApi.findByMetadata, auth),
|
|
134
137
|
claimByMetadata: bindAuth(escalationsApi.claimByMetadata, auth),
|
|
135
138
|
resolveByMetadata: bindAuth(escalationsApi.resolveByMetadata, auth),
|
|
139
|
+
// Faceted-routing surface (pond search + batch claim), RBAC-scoped to the caller's role.
|
|
140
|
+
searchByFacets: bindAuth(escalationsApi.searchByFacets, auth),
|
|
141
|
+
claimGroups: bindAuth(escalationsApi.claimGroups, auth),
|
|
142
|
+
claimByFacets: bindAuth(escalationsApi.claimByFacets, auth),
|
|
136
143
|
},
|
|
137
144
|
// ── Workflows ──────────────────────────────────────────────────────────
|
|
138
145
|
workflows: {
|
|
@@ -12,8 +12,29 @@ export declare function claimEscalation(id: string, userId: string, durationMinu
|
|
|
12
12
|
* orchestrator (api/escalations/resolve.ts); service-created rows have no
|
|
13
13
|
* signal_key, so this never delivers a signal itself. Returns null when the
|
|
14
14
|
* row is missing or already terminal.
|
|
15
|
+
*
|
|
16
|
+
* `metadata` (optional) is merged — not replaced — into the resolved row's
|
|
17
|
+
* GIN-indexed `metadata` in the same atomic UPDATE, and only on the winning
|
|
18
|
+
* resolve. It records "what actually happened" (outcome, duration, measured
|
|
19
|
+
* results) into the `@>`-queryable surface alongside the creation metadata
|
|
20
|
+
* ("what was intended"). Distinct from `resolverPayload`, which is delivered to
|
|
21
|
+
* the waiting workflow as `condition()`'s return value and is not GIN-indexed.
|
|
15
22
|
*/
|
|
16
|
-
export declare function resolveEscalation(id: string, resolverPayload: Record<string, any>): Promise<LTEscalationRecord | null>;
|
|
23
|
+
export declare function resolveEscalation(id: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>): Promise<LTEscalationRecord | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a SET of escalations by id in ONE guarded statement
|
|
26
|
+
* (`UPDATE … WHERE id = ANY($ids) AND status='pending'`). Returns the rows that
|
|
27
|
+
* won the resolve (already-terminal ids are excluded by the guard), and publishes
|
|
28
|
+
* a resolved event per winner.
|
|
29
|
+
*
|
|
30
|
+
* Use this over a per-row `resolveEscalation` loop when every row takes the SAME
|
|
31
|
+
* resolverPayload and the rows are woken collectively (or are bookkeeping rows with
|
|
32
|
+
* no signal_key). NOTE: unlike the single `resolve`, this does NOT deliver per-row
|
|
33
|
+
* signals — `resolveMany` is UPDATE-only. Do not use it for signal_key adverts whose
|
|
34
|
+
* resolution must wake a parked workflow; those require the per-row `resolveEscalation`
|
|
35
|
+
* (which delivers the signal).
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveEscalationsByIds(ids: string[], resolverPayload: Record<string, any>, metadata?: Record<string, any>): Promise<LTEscalationRecord[]>;
|
|
17
38
|
/**
|
|
18
39
|
* Look up an efficient (atomic) escalation by its `signal_key` — the signal id
|
|
19
40
|
* passed to `conditionLT(signalId, config)` / `condition(signalId, config)`.
|
|
@@ -30,8 +51,11 @@ export declare function getEscalationBySignalKey(signalKey: string): Promise<LTE
|
|
|
30
51
|
* is delegated to `resolveEscalation`, whose `client.resolve` uses FOR UPDATE +
|
|
31
52
|
* `WHERE status = 'pending'` so exactly one concurrent caller commits. No status
|
|
32
53
|
* pre-check (that would be a TOCTOU window) — the atomic resolve is the arbiter.
|
|
54
|
+
*
|
|
55
|
+
* `metadata` (optional) records the resolution outcome into the row's
|
|
56
|
+
* GIN-indexed metadata atomically — see {@link resolveEscalation}.
|
|
33
57
|
*/
|
|
34
|
-
export declare function resolveEscalationBySignalKey(signalKey: string, resolverPayload: Record<string, any>): Promise<LTEscalationRecord | null>;
|
|
58
|
+
export declare function resolveEscalationBySignalKey(signalKey: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>): Promise<LTEscalationRecord | null>;
|
|
35
59
|
/**
|
|
36
60
|
* Mark an escalation cancelled — used when the tied workflow has terminated and
|
|
37
61
|
* can never receive the resolution signal. The escalation is removed from the
|
|
@@ -49,6 +73,16 @@ export declare function updateEscalationsPriority(ids: string[], priority: 1 | 2
|
|
|
49
73
|
* Used for permission validation before bulk operations.
|
|
50
74
|
*/
|
|
51
75
|
export declare function getEscalationRoles(ids: string[]): Promise<string[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Fetch (id, role, assigned_to) for a set of escalation IDs — the minimal fields a
|
|
78
|
+
* write-scope gate needs to authorize a BULK action per item: write_all by role,
|
|
79
|
+
* write_self by ownership (assigned_to = caller). One query, no N+1.
|
|
80
|
+
*/
|
|
81
|
+
export declare function getEscalationScopeRows(ids: string[]): Promise<{
|
|
82
|
+
id: string;
|
|
83
|
+
role: string;
|
|
84
|
+
assigned_to: string | null;
|
|
85
|
+
}[]>;
|
|
52
86
|
/**
|
|
53
87
|
* Release a single escalation claim back to the available pool.
|
|
54
88
|
* Only the assigned user (or superadmin via route) may release.
|
|
@@ -79,7 +113,6 @@ export declare function getEscalationsByWorkflowId(workflowId: string): Promise<
|
|
|
79
113
|
* This single UPDATE bypasses the SDK entirely and closes that gap.
|
|
80
114
|
*/
|
|
81
115
|
export declare function cancelEscalationsByWorkflowId(workflowId: string): Promise<number>;
|
|
82
|
-
export declare function updateEscalationMetadata(id: string, patch: Record<string, any>): Promise<LTEscalationRecord | null>;
|
|
83
116
|
export declare function enrichEscalationRouting(id: string, metadataPatch: Record<string, any>, workflowFields: {
|
|
84
117
|
workflowType?: string;
|
|
85
118
|
workflowId?: string;
|
|
@@ -87,7 +120,20 @@ export declare function enrichEscalationRouting(id: string, metadataPatch: Recor
|
|
|
87
120
|
taskId?: string;
|
|
88
121
|
}): Promise<LTEscalationRecord | null>;
|
|
89
122
|
export declare function getEscalationsByOriginId(originId: string): Promise<LTEscalationRecord[]>;
|
|
90
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Find escalations by a metadata key/value, RBAC-scoped IN SQL.
|
|
125
|
+
*
|
|
126
|
+
* `allowedRoles` flows into BOTH the page query and the count: `undefined` =
|
|
127
|
+
* global (no role filter); a `string[]` filters `role = ANY(...)` (an empty array
|
|
128
|
+
* matches nothing). Filtering in SQL — never client-side over a fetched page — is
|
|
129
|
+
* what keeps `total` correct and pagination intact (a client-side filter shrinks a
|
|
130
|
+
* page and reports the wrong total).
|
|
131
|
+
*/
|
|
132
|
+
export declare function findByMetadata(key: string, value: string, status?: string, limit?: number, offset?: number, scope?: {
|
|
133
|
+
allRoles?: string[];
|
|
134
|
+
selfRoles?: string[];
|
|
135
|
+
meUserId?: string;
|
|
136
|
+
}): Promise<{
|
|
91
137
|
escalations: LTEscalationRecord[];
|
|
92
138
|
total: number;
|
|
93
139
|
}>;
|
|
@@ -97,7 +143,11 @@ export declare function findByMetadata(key: string, value: string, status?: stri
|
|
|
97
143
|
* match zero rows. Returns `{ escalation, isExtension, candidatesExist }` or
|
|
98
144
|
* null when nothing was claimed.
|
|
99
145
|
*
|
|
100
|
-
* @param allowedRoles — roles the caller can claim (null =
|
|
146
|
+
* @param allowedRoles — write_all roles the caller can claim (null = global / no
|
|
147
|
+
* filter). The SDK applies a flat `role = ANY` filter and cannot express the
|
|
148
|
+
* write_self ownership predicate, so self-scope members are intentionally
|
|
149
|
+
* excluded from claim-by-metadata (their items are pre-claimed and resolved
|
|
150
|
+
* by id). Self-scope claim-by-metadata would require a HotMesh SDK predicate.
|
|
101
151
|
*/
|
|
102
152
|
export declare function claimByMetadata(key: string, value: string, userId: string, durationMinutes?: number, metadata?: Record<string, any>, allowedRoles?: string[] | null): Promise<(ClaimResult & {
|
|
103
153
|
candidatesExist: number;
|
|
@@ -131,4 +181,4 @@ export interface ResolveByMetadataResult {
|
|
|
131
181
|
* business logic over the shared table, so it runs as one atomic statement on
|
|
132
182
|
* `hmsh_escalations` rather than through the generic SDK resolve.
|
|
133
183
|
*/
|
|
134
|
-
export declare function resolveByMetadataAtomic(key: string, value: string, userId: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>,
|
|
184
|
+
export declare function resolveByMetadataAtomic(key: string, value: string, userId: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>, writeAllRoles?: string[] | null, writeSelfRoles?: string[] | null): Promise<ResolveByMetadataResult>;
|
|
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createEscalation = createEscalation;
|
|
4
4
|
exports.claimEscalation = claimEscalation;
|
|
5
5
|
exports.resolveEscalation = resolveEscalation;
|
|
6
|
+
exports.resolveEscalationsByIds = resolveEscalationsByIds;
|
|
6
7
|
exports.getEscalationBySignalKey = getEscalationBySignalKey;
|
|
7
8
|
exports.resolveEscalationBySignalKey = resolveEscalationBySignalKey;
|
|
8
9
|
exports.cancelEscalation = cancelEscalation;
|
|
9
10
|
exports.updateEscalationsPriority = updateEscalationsPriority;
|
|
10
11
|
exports.getEscalationRoles = getEscalationRoles;
|
|
12
|
+
exports.getEscalationScopeRows = getEscalationScopeRows;
|
|
11
13
|
exports.releaseEscalation = releaseEscalation;
|
|
12
14
|
exports.releaseExpiredClaims = releaseExpiredClaims;
|
|
13
15
|
exports.escalateToRole = escalateToRole;
|
|
@@ -15,7 +17,6 @@ exports.getEscalation = getEscalation;
|
|
|
15
17
|
exports.getEscalationsByTaskId = getEscalationsByTaskId;
|
|
16
18
|
exports.getEscalationsByWorkflowId = getEscalationsByWorkflowId;
|
|
17
19
|
exports.cancelEscalationsByWorkflowId = cancelEscalationsByWorkflowId;
|
|
18
|
-
exports.updateEscalationMetadata = updateEscalationMetadata;
|
|
19
20
|
exports.enrichEscalationRouting = enrichEscalationRouting;
|
|
20
21
|
exports.getEscalationsByOriginId = getEscalationsByOriginId;
|
|
21
22
|
exports.findByMetadata = findByMetadata;
|
|
@@ -24,6 +25,7 @@ exports.resolveByMetadataAtomic = resolveByMetadataAtomic;
|
|
|
24
25
|
const db_1 = require("../../lib/db");
|
|
25
26
|
const publish_1 = require("../../lib/events/publish");
|
|
26
27
|
const client_1 = require("./client");
|
|
28
|
+
const queries_1 = require("./queries");
|
|
27
29
|
const map_1 = require("./map");
|
|
28
30
|
const sql_1 = require("./sql");
|
|
29
31
|
// All escalation state lives in `public.hmsh_escalations` (HotMesh 0.22.3),
|
|
@@ -94,10 +96,17 @@ async function claimEscalation(id, userId, durationMinutes = 30) {
|
|
|
94
96
|
* orchestrator (api/escalations/resolve.ts); service-created rows have no
|
|
95
97
|
* signal_key, so this never delivers a signal itself. Returns null when the
|
|
96
98
|
* row is missing or already terminal.
|
|
99
|
+
*
|
|
100
|
+
* `metadata` (optional) is merged — not replaced — into the resolved row's
|
|
101
|
+
* GIN-indexed `metadata` in the same atomic UPDATE, and only on the winning
|
|
102
|
+
* resolve. It records "what actually happened" (outcome, duration, measured
|
|
103
|
+
* results) into the `@>`-queryable surface alongside the creation metadata
|
|
104
|
+
* ("what was intended"). Distinct from `resolverPayload`, which is delivered to
|
|
105
|
+
* the waiting workflow as `condition()`'s return value and is not GIN-indexed.
|
|
97
106
|
*/
|
|
98
|
-
async function resolveEscalation(id, resolverPayload) {
|
|
107
|
+
async function resolveEscalation(id, resolverPayload, metadata) {
|
|
99
108
|
const client = await (0, client_1.escalations)();
|
|
100
|
-
const result = await client.resolve({ id, resolverPayload });
|
|
109
|
+
const result = await client.resolve({ id, resolverPayload, metadata });
|
|
101
110
|
if (!result.ok)
|
|
102
111
|
return null;
|
|
103
112
|
const escalation = (0, map_1.toEscalationRecord)(result.entry);
|
|
@@ -113,6 +122,39 @@ async function resolveEscalation(id, resolverPayload) {
|
|
|
113
122
|
});
|
|
114
123
|
return escalation;
|
|
115
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a SET of escalations by id in ONE guarded statement
|
|
127
|
+
* (`UPDATE … WHERE id = ANY($ids) AND status='pending'`). Returns the rows that
|
|
128
|
+
* won the resolve (already-terminal ids are excluded by the guard), and publishes
|
|
129
|
+
* a resolved event per winner.
|
|
130
|
+
*
|
|
131
|
+
* Use this over a per-row `resolveEscalation` loop when every row takes the SAME
|
|
132
|
+
* resolverPayload and the rows are woken collectively (or are bookkeeping rows with
|
|
133
|
+
* no signal_key). NOTE: unlike the single `resolve`, this does NOT deliver per-row
|
|
134
|
+
* signals — `resolveMany` is UPDATE-only. Do not use it for signal_key adverts whose
|
|
135
|
+
* resolution must wake a parked workflow; those require the per-row `resolveEscalation`
|
|
136
|
+
* (which delivers the signal).
|
|
137
|
+
*/
|
|
138
|
+
async function resolveEscalationsByIds(ids, resolverPayload, metadata) {
|
|
139
|
+
if (ids.length === 0)
|
|
140
|
+
return [];
|
|
141
|
+
const client = await (0, client_1.escalations)();
|
|
142
|
+
const rows = await client.resolveMany({ ids, resolverPayload, metadata });
|
|
143
|
+
const records = (0, map_1.toEscalationRecords)(rows);
|
|
144
|
+
for (const escalation of records) {
|
|
145
|
+
(0, publish_1.publishEscalationEvent)({
|
|
146
|
+
type: 'escalation.resolved',
|
|
147
|
+
source: 'service',
|
|
148
|
+
workflowId: escalation.workflow_id || '',
|
|
149
|
+
workflowName: escalation.workflow_type || '',
|
|
150
|
+
taskQueue: escalation.task_queue || '',
|
|
151
|
+
escalationId: escalation.id,
|
|
152
|
+
status: 'resolved',
|
|
153
|
+
data: {},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return records;
|
|
157
|
+
}
|
|
116
158
|
/**
|
|
117
159
|
* Look up an efficient (atomic) escalation by its `signal_key` — the signal id
|
|
118
160
|
* passed to `conditionLT(signalId, config)` / `condition(signalId, config)`.
|
|
@@ -133,12 +175,15 @@ async function getEscalationBySignalKey(signalKey) {
|
|
|
133
175
|
* is delegated to `resolveEscalation`, whose `client.resolve` uses FOR UPDATE +
|
|
134
176
|
* `WHERE status = 'pending'` so exactly one concurrent caller commits. No status
|
|
135
177
|
* pre-check (that would be a TOCTOU window) — the atomic resolve is the arbiter.
|
|
178
|
+
*
|
|
179
|
+
* `metadata` (optional) records the resolution outcome into the row's
|
|
180
|
+
* GIN-indexed metadata atomically — see {@link resolveEscalation}.
|
|
136
181
|
*/
|
|
137
|
-
async function resolveEscalationBySignalKey(signalKey, resolverPayload) {
|
|
182
|
+
async function resolveEscalationBySignalKey(signalKey, resolverPayload, metadata) {
|
|
138
183
|
const escalation = await getEscalationBySignalKey(signalKey);
|
|
139
184
|
if (!escalation)
|
|
140
185
|
return null;
|
|
141
|
-
return resolveEscalation(escalation.id, resolverPayload);
|
|
186
|
+
return resolveEscalation(escalation.id, resolverPayload, metadata);
|
|
142
187
|
}
|
|
143
188
|
/**
|
|
144
189
|
* Mark an escalation cancelled — used when the tied workflow has terminated and
|
|
@@ -185,6 +230,22 @@ async function getEscalationRoles(ids) {
|
|
|
185
230
|
const rows = await client.list({ ids, limit: LOOKUP_LIMIT });
|
|
186
231
|
return [...new Set(rows.map(r => r.role).filter((r) => !!r))];
|
|
187
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Fetch (id, role, assigned_to) for a set of escalation IDs — the minimal fields a
|
|
235
|
+
* write-scope gate needs to authorize a BULK action per item: write_all by role,
|
|
236
|
+
* write_self by ownership (assigned_to = caller). One query, no N+1.
|
|
237
|
+
*/
|
|
238
|
+
async function getEscalationScopeRows(ids) {
|
|
239
|
+
if (ids.length === 0)
|
|
240
|
+
return [];
|
|
241
|
+
const client = await (0, client_1.escalations)();
|
|
242
|
+
const rows = await client.list({ ids, limit: LOOKUP_LIMIT });
|
|
243
|
+
return rows.map((r) => ({
|
|
244
|
+
id: r.id,
|
|
245
|
+
role: r.role,
|
|
246
|
+
assigned_to: r.assigned_to ?? null,
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
188
249
|
/**
|
|
189
250
|
* Release a single escalation claim back to the available pool.
|
|
190
251
|
* Only the assigned user (or superadmin via route) may release.
|
|
@@ -284,11 +345,6 @@ async function cancelEscalationsByWorkflowId(workflowId) {
|
|
|
284
345
|
}
|
|
285
346
|
return rows.length;
|
|
286
347
|
}
|
|
287
|
-
async function updateEscalationMetadata(id, patch) {
|
|
288
|
-
const client = await (0, client_1.escalations)();
|
|
289
|
-
const entry = await client.update({ id, metadata: patch });
|
|
290
|
-
return entry ? (0, map_1.toEscalationRecord)(entry) : null;
|
|
291
|
-
}
|
|
292
348
|
async function enrichEscalationRouting(id, metadataPatch, workflowFields) {
|
|
293
349
|
const client = await (0, client_1.escalations)();
|
|
294
350
|
const entry = await client.update({
|
|
@@ -312,23 +368,31 @@ async function getEscalationsByOriginId(originId) {
|
|
|
312
368
|
return (0, map_1.toEscalationRecords)(rows);
|
|
313
369
|
}
|
|
314
370
|
// --- Metadata candidate key lookups -----------------------------------------
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Find escalations by a metadata key/value, RBAC-scoped IN SQL.
|
|
373
|
+
*
|
|
374
|
+
* `allowedRoles` flows into BOTH the page query and the count: `undefined` =
|
|
375
|
+
* global (no role filter); a `string[]` filters `role = ANY(...)` (an empty array
|
|
376
|
+
* matches nothing). Filtering in SQL — never client-side over a fetched page — is
|
|
377
|
+
* what keeps `total` correct and pagination intact (a client-side filter shrinks a
|
|
378
|
+
* page and reports the wrong total).
|
|
379
|
+
*/
|
|
380
|
+
async function findByMetadata(key, value, status, limit = 50, offset = 0, scope) {
|
|
381
|
+
// Route through the scoped list query so BOTH the role-scope filter
|
|
382
|
+
// (role ∈ allRoles OR (role ∈ selfRoles AND assigned_to = me)) AND the count
|
|
383
|
+
// run in SQL — never a client-side filter over a fetched page. allRoles travel
|
|
384
|
+
// as the flat `roles` filter; selfRoles + meUserId engage the raw-SQL scoped path.
|
|
385
|
+
return (0, queries_1.listEscalations)({
|
|
386
|
+
metadata: { [key]: value },
|
|
387
|
+
status: status,
|
|
388
|
+
visibleRoles: scope?.allRoles,
|
|
389
|
+
selfRoles: scope?.selfRoles,
|
|
390
|
+
meUserId: scope?.meUserId,
|
|
391
|
+
sort_by: 'priority',
|
|
392
|
+
order: 'asc',
|
|
393
|
+
limit,
|
|
394
|
+
offset,
|
|
395
|
+
});
|
|
332
396
|
}
|
|
333
397
|
/**
|
|
334
398
|
* Atomic claim by metadata with inline RBAC and optional metadata merge.
|
|
@@ -336,7 +400,11 @@ async function findByMetadata(key, value, status, limit = 50, offset = 0) {
|
|
|
336
400
|
* match zero rows. Returns `{ escalation, isExtension, candidatesExist }` or
|
|
337
401
|
* null when nothing was claimed.
|
|
338
402
|
*
|
|
339
|
-
* @param allowedRoles — roles the caller can claim (null =
|
|
403
|
+
* @param allowedRoles — write_all roles the caller can claim (null = global / no
|
|
404
|
+
* filter). The SDK applies a flat `role = ANY` filter and cannot express the
|
|
405
|
+
* write_self ownership predicate, so self-scope members are intentionally
|
|
406
|
+
* excluded from claim-by-metadata (their items are pre-claimed and resolved
|
|
407
|
+
* by id). Self-scope claim-by-metadata would require a HotMesh SDK predicate.
|
|
340
408
|
*/
|
|
341
409
|
async function claimByMetadata(key, value, userId, durationMinutes = 30, metadata, allowedRoles) {
|
|
342
410
|
const client = await (0, client_1.escalations)();
|
|
@@ -377,14 +445,17 @@ async function claimByMetadata(key, value, userId, durationMinutes = 30, metadat
|
|
|
377
445
|
* business logic over the shared table, so it runs as one atomic statement on
|
|
378
446
|
* `hmsh_escalations` rather than through the generic SDK resolve.
|
|
379
447
|
*/
|
|
380
|
-
async function resolveByMetadataAtomic(key, value, userId, resolverPayload, metadata,
|
|
448
|
+
async function resolveByMetadataAtomic(key, value, userId, resolverPayload, metadata, writeAllRoles, writeSelfRoles) {
|
|
381
449
|
await (0, client_1.ensureEscalationCompatView)();
|
|
382
450
|
const pool = (0, db_1.getPool)();
|
|
383
451
|
const filter = JSON.stringify({ [key]: value });
|
|
384
452
|
const payloadJson = JSON.stringify(resolverPayload);
|
|
385
453
|
const metaPatch = metadata ? JSON.stringify(metadata) : null;
|
|
386
|
-
|
|
387
|
-
|
|
454
|
+
// null write_all roles = global (no filter). A scoped caller passes its arrays
|
|
455
|
+
// (possibly empty); the write_self branch matches only rows assigned to userId.
|
|
456
|
+
const allRoles = writeAllRoles ?? null;
|
|
457
|
+
const selfRoles = writeSelfRoles ?? null;
|
|
458
|
+
const { rows } = await pool.query(sql_1.RESOLVE_BY_METADATA_ATOMIC, [filter, userId, payloadJson, metaPatch, allRoles, selfRoles]);
|
|
388
459
|
if (rows.length === 0)
|
|
389
460
|
return { outcome: 'not_found' };
|
|
390
461
|
const row = rows[0];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FacetOrder, FacetQuery } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Build the WHERE clause for a faceted query, pushing bound params into `params`.
|
|
4
|
+
* Every value is parameterized; only validated column names / metadata keys and
|
|
5
|
+
* a fixed operator set are interpolated. Returns `TRUE` when empty.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildFacetWhere(q: FacetQuery, params: unknown[]): string;
|
|
8
|
+
/** Build an injection-safe ORDER BY mixing top-level columns and metadata facets. */
|
|
9
|
+
export declare function buildFacetOrder(orderBy?: FacetOrder[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Group rank for the all-or-nothing claim: each sort key aggregated across the
|
|
12
|
+
* group's members — `min` for ascending, `max` for descending — so a faceted
|
|
13
|
+
* order over members yields a stable order over orders. Defaults to the natural
|
|
14
|
+
* priority/FIFO order.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildGroupOrder(orderBy?: FacetOrder[]): string;
|
|
17
|
+
/** Lazily-ensured index for the group aggregation (origin_id over the pending pool). */
|
|
18
|
+
export declare const ENSURE_ORIGIN_INDEX = "CREATE INDEX IF NOT EXISTS idx_hmsh_escalations_origin_pending\n ON public.hmsh_escalations (origin_id) WHERE status = 'pending'";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Faceted routing SQL — composable, injection-safe predicate + ORDER BY builders
|
|
4
|
+
// over the escalation queue. Reads go through the `public.lt_escalations` view
|
|
5
|
+
// (adds the computed `available` flag); atomic claims run on the shared
|
|
6
|
+
// `public.hmsh_escalations` table, the same raw-SQL-on-the-shared-table pattern
|
|
7
|
+
// as services/escalation/sql.ts. Metadata facets use the GIN index (`@>`).
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ENSURE_ORIGIN_INDEX = void 0;
|
|
11
|
+
exports.buildFacetWhere = buildFacetWhere;
|
|
12
|
+
exports.buildFacetOrder = buildFacetOrder;
|
|
13
|
+
exports.buildGroupOrder = buildGroupOrder;
|
|
14
|
+
// Top-level columns a caller may sort/range on directly. Anything else must be a
|
|
15
|
+
// `metadata.<key>` facet. Keeps interpolation to a fixed, audited set.
|
|
16
|
+
const ORDER_COLUMNS = new Set(['priority', 'created_at', 'updated_at', 'status', 'role']);
|
|
17
|
+
const RANGE_OPS = new Set(['<', '<=', '>', '>=', '=']);
|
|
18
|
+
// Metadata keys are interpolated (as a JSON path), so they are strictly validated.
|
|
19
|
+
const FACET_KEY = /^[a-zA-Z0-9_]+$/;
|
|
20
|
+
/** A metadata extraction expression for a validated key (text or numeric). */
|
|
21
|
+
function metaExpr(key, numeric = false) {
|
|
22
|
+
return numeric ? `(metadata->>'${key}')::numeric` : `(metadata->>'${key}')`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build the WHERE clause for a faceted query, pushing bound params into `params`.
|
|
26
|
+
* Every value is parameterized; only validated column names / metadata keys and
|
|
27
|
+
* a fixed operator set are interpolated. Returns `TRUE` when empty.
|
|
28
|
+
*/
|
|
29
|
+
function buildFacetWhere(q, params) {
|
|
30
|
+
const clauses = [];
|
|
31
|
+
if (q.role) {
|
|
32
|
+
params.push(q.role);
|
|
33
|
+
clauses.push(`role = $${params.length}`);
|
|
34
|
+
}
|
|
35
|
+
else if (q.roles && q.roles.length) {
|
|
36
|
+
params.push(q.roles);
|
|
37
|
+
clauses.push(`role = ANY($${params.length}::text[])`);
|
|
38
|
+
}
|
|
39
|
+
if (q.status) {
|
|
40
|
+
params.push(q.status);
|
|
41
|
+
clauses.push(`status = $${params.length}`);
|
|
42
|
+
}
|
|
43
|
+
if (q.facets && Object.keys(q.facets).length) {
|
|
44
|
+
params.push(JSON.stringify(q.facets));
|
|
45
|
+
clauses.push(`metadata @> $${params.length}::jsonb`); // GIN-served
|
|
46
|
+
}
|
|
47
|
+
if (q.block && q.block.length) {
|
|
48
|
+
params.push(q.block.map((b) => JSON.stringify(b)));
|
|
49
|
+
clauses.push(`NOT (metadata @> ANY($${params.length}::jsonb[]))`);
|
|
50
|
+
}
|
|
51
|
+
for (const r of q.range ?? []) {
|
|
52
|
+
if (!FACET_KEY.test(r.facet) || !RANGE_OPS.has(r.op))
|
|
53
|
+
continue;
|
|
54
|
+
params.push(r.value);
|
|
55
|
+
clauses.push(`${metaExpr(r.facet, true)} ${r.op} $${params.length}`);
|
|
56
|
+
}
|
|
57
|
+
for (const key of q.exists ?? []) {
|
|
58
|
+
if (!FACET_KEY.test(key))
|
|
59
|
+
continue;
|
|
60
|
+
clauses.push(`metadata ? '${key}'`);
|
|
61
|
+
}
|
|
62
|
+
if (q.available === true) {
|
|
63
|
+
clauses.push(`(assigned_to IS NULL OR assigned_until IS NULL OR assigned_until <= NOW())`);
|
|
64
|
+
}
|
|
65
|
+
else if (q.available === false) {
|
|
66
|
+
clauses.push(`(assigned_to IS NOT NULL AND assigned_until > NOW())`);
|
|
67
|
+
}
|
|
68
|
+
return clauses.length ? clauses.join('\n AND ') : 'TRUE';
|
|
69
|
+
}
|
|
70
|
+
const DEFAULT_ORDER = 'priority ASC, created_at ASC';
|
|
71
|
+
/** Build an injection-safe ORDER BY mixing top-level columns and metadata facets. */
|
|
72
|
+
function buildFacetOrder(orderBy) {
|
|
73
|
+
if (!orderBy || !orderBy.length)
|
|
74
|
+
return DEFAULT_ORDER;
|
|
75
|
+
const parts = [];
|
|
76
|
+
for (const o of orderBy) {
|
|
77
|
+
const dir = o.direction === 'desc' ? 'DESC' : 'ASC';
|
|
78
|
+
if (ORDER_COLUMNS.has(o.field)) {
|
|
79
|
+
parts.push(`${o.field} ${dir}`);
|
|
80
|
+
}
|
|
81
|
+
else if (o.field.startsWith('metadata.')) {
|
|
82
|
+
const key = o.field.slice('metadata.'.length);
|
|
83
|
+
if (!FACET_KEY.test(key))
|
|
84
|
+
continue;
|
|
85
|
+
parts.push(`${metaExpr(key, o.numeric)} ${dir} NULLS LAST`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return parts.length ? parts.join(', ') : DEFAULT_ORDER;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Group rank for the all-or-nothing claim: each sort key aggregated across the
|
|
92
|
+
* group's members — `min` for ascending, `max` for descending — so a faceted
|
|
93
|
+
* order over members yields a stable order over orders. Defaults to the natural
|
|
94
|
+
* priority/FIFO order.
|
|
95
|
+
*/
|
|
96
|
+
function buildGroupOrder(orderBy) {
|
|
97
|
+
if (!orderBy || !orderBy.length)
|
|
98
|
+
return 'min(priority) ASC, min(created_at) ASC';
|
|
99
|
+
const parts = [];
|
|
100
|
+
for (const o of orderBy) {
|
|
101
|
+
const desc = o.direction === 'desc';
|
|
102
|
+
const agg = desc ? 'max' : 'min';
|
|
103
|
+
const dir = desc ? 'DESC' : 'ASC';
|
|
104
|
+
if (ORDER_COLUMNS.has(o.field)) {
|
|
105
|
+
parts.push(`${agg}(${o.field}) ${dir}`);
|
|
106
|
+
}
|
|
107
|
+
else if (o.field.startsWith('metadata.')) {
|
|
108
|
+
const key = o.field.slice('metadata.'.length);
|
|
109
|
+
if (!FACET_KEY.test(key))
|
|
110
|
+
continue;
|
|
111
|
+
parts.push(`${agg}(${metaExpr(key, o.numeric)}) ${dir} NULLS LAST`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return parts.length ? parts.join(', ') : 'min(priority) ASC, min(created_at) ASC';
|
|
115
|
+
}
|
|
116
|
+
/** Lazily-ensured index for the group aggregation (origin_id over the pending pool). */
|
|
117
|
+
exports.ENSURE_ORIGIN_INDEX = `CREATE INDEX IF NOT EXISTS idx_hmsh_escalations_origin_pending
|
|
118
|
+
ON public.hmsh_escalations (origin_id) WHERE status = 'pending'`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ClaimedGroup, FacetQuery, GroupSummary, LTEscalationRecord } from '../../types';
|
|
2
|
+
/** Ensure the compat view + the origin_id index exist before the first query. */
|
|
3
|
+
export declare function ensureFacetReady(): Promise<void>;
|
|
4
|
+
/** Item-level faceted search — filter/sort over top-level columns and metadata facets. */
|
|
5
|
+
export declare function searchByFacets(query: FacetQuery): Promise<{
|
|
6
|
+
escalations: LTEscalationRecord[];
|
|
7
|
+
total: number;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Order-level view of the pond — what a batched dispatcher reads to page by
|
|
11
|
+
* capacity. Each row is an order (origin) with its unit count, availability and
|
|
12
|
+
* completeness, so the app grabs only as many orders as it has free consumers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function searchGroups(query: FacetQuery, opts?: {
|
|
15
|
+
sizeFacet?: string;
|
|
16
|
+
limit?: number;
|
|
17
|
+
offset?: number;
|
|
18
|
+
}): Promise<GroupSummary[]>;
|
|
19
|
+
/** Aggregate count — capacity / in-flight soft-limit checks. */
|
|
20
|
+
export declare function countByFacets(query: FacetQuery): Promise<number>;
|
|
21
|
+
/**
|
|
22
|
+
* Pull a *page* of complete orders — the dispatcher primitive. Claims up to
|
|
23
|
+
* `limit` eligible orders (each all-or-nothing) in rank order, skipping any a
|
|
24
|
+
* competing dispatcher holds. Page `limit` = how many consumers/printers are
|
|
25
|
+
* free, so a dependent grabs exactly as much as it can distribute. `limit=1`
|
|
26
|
+
* claims a single order.
|
|
27
|
+
*/
|
|
28
|
+
export declare function claimGroups(query: FacetQuery, consumer: string, opts?: {
|
|
29
|
+
limit?: number;
|
|
30
|
+
durationMinutes?: number;
|
|
31
|
+
sizeFacet?: string;
|
|
32
|
+
}): Promise<ClaimedGroup[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Batch-claim *individual* escalations matching a facet query — the single-row
|
|
35
|
+
* sibling of `claimGroups`. Locks up to `limit` available rows in rank order with
|
|
36
|
+
* `FOR UPDATE SKIP LOCKED`, so concurrent claimers take disjoint sets without
|
|
37
|
+
* contention. With `allOrNone`, the claim commits only when the full `limit` was
|
|
38
|
+
* acquired (otherwise it rolls back and returns `[]`) — the all-or-none lock a
|
|
39
|
+
* dispatcher wants over a counted set it anticipated. Returns the claimed records.
|
|
40
|
+
*/
|
|
41
|
+
export declare function claimByFacets(query: FacetQuery, consumer: string, opts?: {
|
|
42
|
+
limit?: number;
|
|
43
|
+
durationMinutes?: number;
|
|
44
|
+
allOrNone?: boolean;
|
|
45
|
+
}): Promise<LTEscalationRecord[]>;
|