@hotmeshio/long-tail 0.7.4 → 0.8.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/roles.d.ts +4 -0
- package/build/api/roles.js +7 -1
- package/build/api/workflows/config.d.ts +7 -1
- package/build/api/workflows/config.js +7 -1
- package/build/api/workflows/discovery.js +1 -4
- package/build/lib/db/schemas/019_role_priority.sql +28 -0
- package/build/lib/db/schemas/020_workflow_certified.sql +34 -0
- package/build/modules/ltconfig.d.ts +3 -2
- package/build/modules/ltconfig.js +4 -4
- package/build/routes/roles.js +1 -1
- package/build/routes/workflows/config.js +1 -0
- package/build/scripts/ortho-populate.d.ts +7 -1
- package/build/scripts/ortho-populate.js +21 -6
- package/build/services/config/cache.js +1 -0
- package/build/services/config/read.js +2 -0
- package/build/services/config/sql.d.ts +2 -2
- package/build/services/config/sql.js +6 -5
- package/build/services/config/write.js +13 -0
- package/build/services/escalation/bulk.d.ts +3 -1
- package/build/services/escalation/bulk.js +3 -1
- package/build/services/escalation/crud.d.ts +4 -3
- package/build/services/escalation/crud.js +4 -3
- package/build/services/escalation/facet-sql.d.ts +1 -0
- package/build/services/escalation/facet-sql.js +7 -6
- package/build/services/escalation/queries.js +27 -5
- package/build/services/escalation/sql.d.ts +19 -2
- package/build/services/escalation/sql.js +32 -9
- package/build/services/escalation/types.d.ts +7 -1
- package/build/services/role/index.js +2 -0
- package/build/services/role/sql.d.ts +4 -4
- package/build/services/role/sql.js +12 -6
- package/build/services/role/types.d.ts +14 -0
- package/build/start/workers.js +1 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +9 -0
- package/build/system/mcp-servers/admin/schemas.js +6 -1
- package/build/system/mcp-servers/admin/users.js +4 -1
- package/build/system/mcp-servers/admin/workflow-config.js +1 -0
- package/build/system/seed/tool-manifests-admin.d.ts +152 -0
- package/build/system/seed/tool-manifests-admin.js +1 -1
- package/build/types/config.d.ts +16 -0
- package/build/types/startup.d.ts +11 -1
- package/dashboard/dist/assets/{AdminDashboard-C9eE1VEp.js → AdminDashboard-9JLg41f-.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-C9eE1VEp.js.map → AdminDashboard-9JLg41f-.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-Ba8QfXM2.js → AgentConfigPage-D5P1hcOc.js} +6 -6
- package/dashboard/dist/assets/{AgentConfigPage-Ba8QfXM2.js.map → AgentConfigPage-D5P1hcOc.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-DVmec0CP.js → AgentDetailPage-DrijeAV8.js} +4 -4
- package/dashboard/dist/assets/{AgentDetailPage-DVmec0CP.js.map → AgentDetailPage-DrijeAV8.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-DNiCYMh3.js → AgentsPage-BW5d0JOt.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DNiCYMh3.js.map → AgentsPage-BW5d0JOt.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-gDIF8ocC.js → AvailableEscalationsPage-CoZ4Lufr.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-gDIF8ocC.js.map → AvailableEscalationsPage-CoZ4Lufr.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-3wBcBavT.js → BotPicker-xGUCxJo-.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-3wBcBavT.js.map → BotPicker-xGUCxJo-.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-WoPkND2y.js → CapabilitiesPage-ChaRJiyg.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-WoPkND2y.js.map → CapabilitiesPage-ChaRJiyg.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-Cx99mqNN.js → CollapsibleSection-CXMPecjj.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-Cx99mqNN.js.map → CollapsibleSection-CXMPecjj.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-DbNaoPbF.js → CredentialsPage-BbdfZh1D.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-DbNaoPbF.js.map → CredentialsPage-BbdfZh1D.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DLW4_Zg4.js → CronLabel-CJUL50eO.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DLW4_Zg4.js.map → CronLabel-CJUL50eO.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-BRUFjkiu.js → CustomDurationPicker-id6Tq64R.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-BRUFjkiu.js.map → CustomDurationPicker-id6Tq64R.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-DEX0BBqt.js → DropZone-CZDmNBn-.js} +2 -2
- package/dashboard/dist/assets/{DropZone-DEX0BBqt.js.map → DropZone-CZDmNBn-.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-BtmDQaym.js → ElapsedCell-B8TrnyT5.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-BtmDQaym.js.map → ElapsedCell-B8TrnyT5.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-Bm4yBEGN.js → EscalationSchemaPage-C6UuZgCD.js} +2 -2
- package/dashboard/dist/assets/{EscalationSchemaPage-Bm4yBEGN.js.map → EscalationSchemaPage-C6UuZgCD.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-B3xZwTQA.js → EscalationsOverview-CjLyujJ3.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-B3xZwTQA.js.map → EscalationsOverview-CjLyujJ3.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-Bh9FKXLI.js → EventTable-D_rNmyGG.js} +2 -2
- package/dashboard/dist/assets/{EventTable-Bh9FKXLI.js.map → EventTable-D_rNmyGG.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-ov_ANlaE.js → GraphInvokePage-BMyrTnI6.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-ov_ANlaE.js.map → GraphInvokePage-BMyrTnI6.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-D0DV6izb.js → HomePage-MBv6pwk4.js} +2 -2
- package/dashboard/dist/assets/{HomePage-D0DV6izb.js.map → HomePage-MBv6pwk4.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-FpcvqNkN.js → ListToolbar--FkPpYDY.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-FpcvqNkN.js.map → ListToolbar--FkPpYDY.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-DcuRiAff.js → McpOverview-CtAFXv-T.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-DcuRiAff.js.map → McpOverview-CtAFXv-T.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-BcYl67KP.js → McpQueryDetailPage-5Jzz2q2g.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-BcYl67KP.js.map → McpQueryDetailPage-5Jzz2q2g.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-C3GDzqZM.js → McpQueryPage-DveeRF5I.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-C3GDzqZM.js.map → McpQueryPage-DveeRF5I.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-SzOyhDBW.js → McpRunDetailPage-BYIxBcA7.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-SzOyhDBW.js.map → McpRunDetailPage-BYIxBcA7.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-BwgB6mRV.js → McpRunsPage-Cgke6ask.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-BwgB6mRV.js.map → McpRunsPage-Cgke6ask.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-CwKdoyRM.js → NamespacePill-DlqBTvFR.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-CwKdoyRM.js.map → NamespacePill-DlqBTvFR.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-D2i7xrAI.js +2 -0
- package/dashboard/dist/assets/OperationsPage-D2i7xrAI.js.map +1 -0
- package/dashboard/dist/assets/{OperatorDashboard-DSXh-yYw.js → OperatorDashboard-DMQwodfo.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-DSXh-yYw.js.map → OperatorDashboard-DMQwodfo.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-BhaWTWcM.js → PageHeader-CFta2VaS.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-BhaWTWcM.js.map → PageHeader-CFta2VaS.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-yex5WmHB.js → PageHeaderWithStats-CcVAji-7.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-yex5WmHB.js.map → PageHeaderWithStats-CcVAji-7.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-iIB1rwEt.js → ProcessDetailPage-Dodrk5-z.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-iIB1rwEt.js.map → ProcessDetailPage-Dodrk5-z.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-BrqWR2zr.js → ProcessesListPage-BVtkWVLY.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-BrqWR2zr.js.map → ProcessesListPage-BVtkWVLY.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-8NldZI49.js +11 -0
- package/dashboard/dist/assets/RoleDetailPage-8NldZI49.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-Cl71eDUA.js → RolePill-CXEyPMqb.js} +2 -2
- package/dashboard/dist/assets/{RolePill-Cl71eDUA.js.map → RolePill-CXEyPMqb.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-Bzp9FhAl.js → RolesPage-BJBjtFNY.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-Bzp9FhAl.js.map → RolesPage-BJBjtFNY.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-DXbHRxQs.js → RunAsSelector-Cc1i017Z.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-DXbHRxQs.js.map → RunAsSelector-Cc1i017Z.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-D8XuMSWD.js → StickyPagination-GQKFFTjL.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-D8XuMSWD.js.map → StickyPagination-GQKFFTjL.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-D5v95pmz.js → StreamMessageDetail-BYzfjWe_.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-D5v95pmz.js.map → StreamMessageDetail-BYzfjWe_.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-CUEf2R5R.js → SwimlaneTimeline-DLVmRrd-.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-CUEf2R5R.js.map → SwimlaneTimeline-DLVmRrd-.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-B_WQiuer.js → TagInput-CQg-TrV9.js} +2 -2
- package/dashboard/dist/assets/{TagInput-B_WQiuer.js.map → TagInput-CQg-TrV9.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-BJvKNwEe.js → TaskDetailPage-HzQpzp6D.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-BJvKNwEe.js.map → TaskDetailPage-HzQpzp6D.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-Lgn2obNU.js → TaskQueuePill-DyvtL8X4.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-Lgn2obNU.js.map → TaskQueuePill-DyvtL8X4.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CM7BWh_h.js → TasksListPage-DpqevgTz.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CM7BWh_h.js.map → TasksListPage-DpqevgTz.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-D1K7xKq5.js → TimeAgo-CftB8aJE.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-D1K7xKq5.js.map → TimeAgo-CftB8aJE.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-7G0echgP.js → TimestampCell-BSKj1jqT.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-7G0echgP.js.map → TimestampCell-BSKj1jqT.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-uVqcbHPE.js → ToolPill-miuRcj1Q.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-uVqcbHPE.js.map → ToolPill-miuRcj1Q.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-LxuJvURK.js → ToolTestPanel-DVQICZUQ.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-LxuJvURK.js.map → ToolTestPanel-DVQICZUQ.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-JvAX6rrR.js → TopicDetailPage-B2LAQDEg.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-JvAX6rrR.js.map → TopicDetailPage-B2LAQDEg.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-tFT2AcTz.js → TopicsPage-SwPAnN2C.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-tFT2AcTz.js.map → TopicsPage-SwPAnN2C.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-Djz0nmaZ.js → UserName-Ce9gvlT5.js} +2 -2
- package/dashboard/dist/assets/{UserName-Djz0nmaZ.js.map → UserName-Ce9gvlT5.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-f53hilbI.js → WorkflowExecutionPage-BI9NzeaH.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-f53hilbI.js.map → WorkflowExecutionPage-BI9NzeaH.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-IDjTRfQ8.js → WorkflowPill-BuNkWUVM.js} +2 -2
- package/dashboard/dist/assets/WorkflowPill-BuNkWUVM.js.map +1 -0
- package/dashboard/dist/assets/{WorkflowsDashboard-DM9Xe665.js → WorkflowsDashboard-CvWSgQdE.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-DM9Xe665.js.map → WorkflowsDashboard-CvWSgQdE.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-CUGSrx9N.js → WorkflowsOverview-CjBWlwlH.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CUGSrx9N.js.map → WorkflowsOverview-CjBWlwlH.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-B1_yqxAw.js → YamlWorkflowDetailPage-Ds5o1UK9.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-B1_yqxAw.js.map → YamlWorkflowDetailPage-Ds5o1UK9.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-NtGvqiu7.js → YamlWorkflowsPage-ChwvZS4Z.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-NtGvqiu7.js.map → YamlWorkflowsPage-ChwvZS4Z.js.map} +1 -1
- package/dashboard/dist/assets/{agents-tML6AjDj.js → agents-A1U43853.js} +2 -2
- package/dashboard/dist/assets/{agents-tML6AjDj.js.map → agents-A1U43853.js.map} +1 -1
- package/dashboard/dist/assets/{bots-Cb0WTwhY.js → bots-CyzN6Uxi.js} +2 -2
- package/dashboard/dist/assets/{bots-Cb0WTwhY.js.map → bots-CyzN6Uxi.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-BGZogsi_.js → capabilities-BE1X5FVI.js} +2 -2
- package/dashboard/dist/assets/{capabilities-BGZogsi_.js.map → capabilities-BE1X5FVI.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-B7VJTlZD.js → controlplane-BrwK_MT8.js} +2 -2
- package/dashboard/dist/assets/{controlplane-B7VJTlZD.js.map → controlplane-BrwK_MT8.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-Bl-SQV_u.js → escalation-DDdt7UdU.js} +2 -2
- package/dashboard/dist/assets/{escalation-Bl-SQV_u.js.map → escalation-DDdt7UdU.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-BlJOswRx.js → escalation-columns-D_zFY1zF.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-BlJOswRx.js.map → escalation-columns-D_zFY1zF.js.map} +1 -1
- package/dashboard/dist/assets/{index-DcbEIXit.js → index-6MALVAEY.js} +2 -2
- package/dashboard/dist/assets/{index-DcbEIXit.js.map → index-6MALVAEY.js.map} +1 -1
- package/dashboard/dist/assets/{index-CBv3UxF2.js → index-BNWKksZ6.js} +2 -2
- package/dashboard/dist/assets/{index-CBv3UxF2.js.map → index-BNWKksZ6.js.map} +1 -1
- package/dashboard/dist/assets/{index-DfDfJivY.js → index-BtrNxebh.js} +2 -2
- package/dashboard/dist/assets/{index-DfDfJivY.js.map → index-BtrNxebh.js.map} +1 -1
- package/dashboard/dist/assets/{index-BorpZbZA.js → index-C-rJQ-vB.js} +2 -2
- package/dashboard/dist/assets/{index-BorpZbZA.js.map → index-C-rJQ-vB.js.map} +1 -1
- package/dashboard/dist/assets/{index-Il_g7kw5.js → index-CGUBekEU.js} +2 -2
- package/dashboard/dist/assets/{index-Il_g7kw5.js.map → index-CGUBekEU.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dgw1KKpq.js → index-CKXSKsVZ.js} +4 -4
- package/dashboard/dist/assets/{index-Dgw1KKpq.js.map → index-CKXSKsVZ.js.map} +1 -1
- package/dashboard/dist/assets/{index-Wny0bRgq.css → index-CZ6-l8_0.css} +1 -1
- package/dashboard/dist/assets/{index--WeZDfeN.js → index-D2dDPGNk.js} +2 -2
- package/dashboard/dist/assets/{index--WeZDfeN.js.map → index-D2dDPGNk.js.map} +1 -1
- package/dashboard/dist/assets/{index-0o71lRuY.js → index-D4U--3Xo.js} +2 -2
- package/dashboard/dist/assets/{index-0o71lRuY.js.map → index-D4U--3Xo.js.map} +1 -1
- package/dashboard/dist/assets/{index-e97M6GoI.js → index-D4gAsCGh.js} +2 -2
- package/dashboard/dist/assets/{index-e97M6GoI.js.map → index-D4gAsCGh.js.map} +1 -1
- package/dashboard/dist/assets/{index-SCrD3l-K.js → index-DEXouwBQ.js} +2 -2
- package/dashboard/dist/assets/{index-SCrD3l-K.js.map → index-DEXouwBQ.js.map} +1 -1
- package/dashboard/dist/assets/{index-3A_5CL81.js → index-NnGsWGNS.js} +2 -2
- package/dashboard/dist/assets/{index-3A_5CL81.js.map → index-NnGsWGNS.js.map} +1 -1
- package/dashboard/dist/assets/index-U27KBbTV.js +9 -0
- package/dashboard/dist/assets/index-U27KBbTV.js.map +1 -0
- package/dashboard/dist/assets/index-fwHQSJCp.js +6 -0
- package/dashboard/dist/assets/index-fwHQSJCp.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-CDzw4E6q.js → knowledge-B3ZIRTeu.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CDzw4E6q.js.map → knowledge-B3ZIRTeu.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-D_-NQtnG.js → mcp-BJjWiHdc.js} +2 -2
- package/dashboard/dist/assets/{mcp-D_-NQtnG.js.map → mcp-BJjWiHdc.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-BqZXdf9A.js → mcp-query-ksjBztwq.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-BqZXdf9A.js.map → mcp-query-ksjBztwq.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-CNLhgj8b.js → pipelines-DT1astmP.js} +2 -2
- package/dashboard/dist/assets/{pipelines-CNLhgj8b.js.map → pipelines-DT1astmP.js.map} +1 -1
- package/dashboard/dist/assets/{roles-CFD2eYap.js → roles-UboiQxXT.js} +2 -2
- package/dashboard/dist/assets/roles-UboiQxXT.js.map +1 -0
- package/dashboard/dist/assets/{tasks-DcSvF636.js → tasks-aL0K8WW3.js} +2 -2
- package/dashboard/dist/assets/{tasks-DcSvF636.js.map → tasks-aL0K8WW3.js.map} +1 -1
- package/dashboard/dist/assets/{topics-DI8TFSZv.js → topics-CZO06VJl.js} +2 -2
- package/dashboard/dist/assets/{topics-DI8TFSZv.js.map → topics-CZO06VJl.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-DcIgMDh3.js → useEventHooks-DlWkui_n.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-DcIgMDh3.js.map → useEventHooks-DlWkui_n.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-C88ZxGoX.js → useNamespace-BBzCmCIN.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-C88ZxGoX.js.map → useNamespace-BBzCmCIN.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-D2-yVjJ7.js → useYamlActivityEvents-ZS6wRSSc.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-D2-yVjJ7.js.map → useYamlActivityEvents-ZS6wRSSc.js.map} +1 -1
- package/dashboard/dist/assets/{users-C0WV36X8.js → users-Dzh04pRB.js} +2 -2
- package/dashboard/dist/assets/{users-C0WV36X8.js.map → users-Dzh04pRB.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-CEWCGPeJ.js → vendor-icons-BaQqUm-e.js} +2 -2
- package/dashboard/dist/assets/{vendor-icons-CEWCGPeJ.js.map → vendor-icons-BaQqUm-e.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-CkPv7xk-.js → workflows-BTkHs6VT.js} +2 -2
- package/dashboard/dist/assets/workflows-BTkHs6VT.js.map +1 -0
- package/dashboard/dist/assets/{yaml-workflows-BjrMQLTK.js → yaml-workflows-Df8u3lgx.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-BjrMQLTK.js.map → yaml-workflows-Df8u3lgx.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/http/escalations.md +6 -6
- package/docs/api/http/roles.md +6 -0
- package/docs/api/http/workflows.md +2 -1
- package/docs/api/mcp/admin.md +15 -7
- package/docs/api/sdk/escalations.md +5 -3
- package/docs/api/sdk/roles.md +7 -1
- package/docs/api/sdk/workflows.md +3 -2
- package/docs/architecture.md +2 -2
- package/docs/dashboard.md +11 -9
- package/docs/data.md +2 -0
- package/docs/iam.md +3 -3
- package/docs/operations.md +16 -3
- package/docs/workflows.md +7 -5
- package/package.json +2 -2
- package/dashboard/dist/assets/OperationsPage-BCr21p8I.js +0 -2
- package/dashboard/dist/assets/OperationsPage-BCr21p8I.js.map +0 -1
- package/dashboard/dist/assets/RoleDetailPage-DS37tJhq.js +0 -11
- package/dashboard/dist/assets/RoleDetailPage-DS37tJhq.js.map +0 -1
- package/dashboard/dist/assets/WorkflowPill-IDjTRfQ8.js.map +0 -1
- package/dashboard/dist/assets/index-D0-8byNu.js +0 -6
- package/dashboard/dist/assets/index-D0-8byNu.js.map +0 -1
- package/dashboard/dist/assets/index-TiKh2Ctj.js +0 -9
- package/dashboard/dist/assets/index-TiKh2Ctj.js.map +0 -1
- package/dashboard/dist/assets/roles-CFD2eYap.js.map +0 -1
- package/dashboard/dist/assets/workflows-CkPv7xk-.js.map +0 -1
package/build/api/roles.d.ts
CHANGED
|
@@ -91,6 +91,10 @@ export declare function updateRole(input: {
|
|
|
91
91
|
sla_minutes?: number | null;
|
|
92
92
|
target_per_hour?: number | null;
|
|
93
93
|
worker_count?: number | null;
|
|
94
|
+
/** Max age (minutes) before a pending unclaimed item counts as priority on the Pace Board. Falls back to sla_minutes. */
|
|
95
|
+
priority_threshold_minutes?: number | null;
|
|
96
|
+
/** Escalation metadata key holding the age origin (ISO 8601 UTC timestamp). Falls back to created_at. */
|
|
97
|
+
priority_facet?: string | null;
|
|
94
98
|
/** Replace the upstream-input set (omitted = preserve; null or [] = clear). */
|
|
95
99
|
upstream_roles?: string[] | null;
|
|
96
100
|
/** Recorded on the schema snapshot when this update changes a schema field. */
|
package/build/api/roles.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.getRoleSchema = getRoleSchema;
|
|
|
46
46
|
exports.listRoleSchemaVersions = listRoleSchemaVersions;
|
|
47
47
|
exports.deleteRole = deleteRole;
|
|
48
48
|
const roleService = __importStar(require("../services/role"));
|
|
49
|
+
const facet_sql_1 = require("../services/escalation/facet-sql");
|
|
49
50
|
/**
|
|
50
51
|
* List all distinct role names in the system.
|
|
51
52
|
*
|
|
@@ -207,7 +208,7 @@ async function updateRole(input) {
|
|
|
207
208
|
if (!input.role) {
|
|
208
209
|
return { status: 400, error: 'role is required' };
|
|
209
210
|
}
|
|
210
|
-
for (const field of ['sla_minutes', 'target_per_hour', 'worker_count']) {
|
|
211
|
+
for (const field of ['sla_minutes', 'target_per_hour', 'worker_count', 'priority_threshold_minutes']) {
|
|
211
212
|
const value = input[field];
|
|
212
213
|
if (value !== undefined && value !== null && (typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {
|
|
213
214
|
return { status: 400, error: `${field} must be a non-negative number or null` };
|
|
@@ -216,6 +217,9 @@ async function updateRole(input) {
|
|
|
216
217
|
if (input.worker_count != null && !Number.isInteger(input.worker_count)) {
|
|
217
218
|
return { status: 400, error: 'worker_count must be a whole number' };
|
|
218
219
|
}
|
|
220
|
+
if (input.priority_facet != null && (typeof input.priority_facet !== 'string' || !facet_sql_1.FACET_KEY.test(input.priority_facet))) {
|
|
221
|
+
return { status: 400, error: 'priority_facet must be a metadata key (letters, numbers, underscores) or null' };
|
|
222
|
+
}
|
|
219
223
|
if (input.ops_visible !== undefined && typeof input.ops_visible !== 'boolean') {
|
|
220
224
|
return { status: 400, error: 'ops_visible must be a boolean' };
|
|
221
225
|
}
|
|
@@ -246,6 +250,8 @@ async function updateRole(input) {
|
|
|
246
250
|
sla_minutes: input.sla_minutes,
|
|
247
251
|
target_per_hour: input.target_per_hour,
|
|
248
252
|
worker_count: input.worker_count,
|
|
253
|
+
priority_threshold_minutes: input.priority_threshold_minutes,
|
|
254
|
+
priority_facet: input.priority_facet,
|
|
249
255
|
upstream_roles: input.upstream_roles,
|
|
250
256
|
change_summary: input.change_summary,
|
|
251
257
|
});
|
|
@@ -22,11 +22,16 @@ export declare function getWorkflowConfig(input: {
|
|
|
22
22
|
*
|
|
23
23
|
* @param input.type — workflow type name
|
|
24
24
|
* @param input.invocable — whether the workflow can be started via the API
|
|
25
|
+
* @param input.certified — explicit HITL certification (interceptor treatment).
|
|
26
|
+
* Omitted → derived from roles/consumes presence, preserving the behavior of
|
|
27
|
+
* callers that predate the explicit flag.
|
|
25
28
|
* @param input.task_queue — HotMesh task queue
|
|
26
29
|
* @param input.default_role — default escalation role
|
|
27
30
|
* @param input.description — human-readable description
|
|
28
31
|
* @param input.execute_as — service account for proxy invocation
|
|
29
|
-
* @param input.roles —
|
|
32
|
+
* @param input.roles — interceptor default for who resolves interceptor-raised
|
|
33
|
+
* escalations (deprecated as formalization — the escalation's role carries
|
|
34
|
+
* the versioned schema and takes precedence)
|
|
30
35
|
* @param input.invocation_roles — roles that can invoke this workflow
|
|
31
36
|
* @param input.consumes — workflow types whose data this workflow consumes
|
|
32
37
|
* @param input.tool_tags — MCP tool tags for discovery
|
|
@@ -38,6 +43,7 @@ export declare function getWorkflowConfig(input: {
|
|
|
38
43
|
export declare function upsertWorkflowConfig(input: {
|
|
39
44
|
type: string;
|
|
40
45
|
invocable?: boolean;
|
|
46
|
+
certified?: boolean;
|
|
41
47
|
task_queue?: string | null;
|
|
42
48
|
default_role?: string;
|
|
43
49
|
description?: string | null;
|
|
@@ -80,11 +80,16 @@ async function getWorkflowConfig(input) {
|
|
|
80
80
|
*
|
|
81
81
|
* @param input.type — workflow type name
|
|
82
82
|
* @param input.invocable — whether the workflow can be started via the API
|
|
83
|
+
* @param input.certified — explicit HITL certification (interceptor treatment).
|
|
84
|
+
* Omitted → derived from roles/consumes presence, preserving the behavior of
|
|
85
|
+
* callers that predate the explicit flag.
|
|
83
86
|
* @param input.task_queue — HotMesh task queue
|
|
84
87
|
* @param input.default_role — default escalation role
|
|
85
88
|
* @param input.description — human-readable description
|
|
86
89
|
* @param input.execute_as — service account for proxy invocation
|
|
87
|
-
* @param input.roles —
|
|
90
|
+
* @param input.roles — interceptor default for who resolves interceptor-raised
|
|
91
|
+
* escalations (deprecated as formalization — the escalation's role carries
|
|
92
|
+
* the versioned schema and takes precedence)
|
|
88
93
|
* @param input.invocation_roles — roles that can invoke this workflow
|
|
89
94
|
* @param input.consumes — workflow types whose data this workflow consumes
|
|
90
95
|
* @param input.tool_tags — MCP tool tags for discovery
|
|
@@ -103,6 +108,7 @@ async function upsertWorkflowConfig(input) {
|
|
|
103
108
|
const config = await configService.upsertWorkflowConfig({
|
|
104
109
|
workflow_type: input.type,
|
|
105
110
|
invocable: input.invocable ?? false,
|
|
111
|
+
certified: input.certified,
|
|
106
112
|
task_queue: input.task_queue ?? null,
|
|
107
113
|
default_role: input.default_role ?? 'reviewer',
|
|
108
114
|
description: input.description ?? null,
|
|
@@ -105,10 +105,7 @@ async function listDiscoveredWorkflows(input) {
|
|
|
105
105
|
.map((workflowType) => {
|
|
106
106
|
const config = configMap.get(workflowType);
|
|
107
107
|
const worker = activeWorkers.get(workflowType);
|
|
108
|
-
const
|
|
109
|
-
((config.roles?.length ?? 0) > 0 ||
|
|
110
|
-
(config.consumes?.length ?? 0) > 0));
|
|
111
|
-
const tier = !config ? 'durable' : hasCertification ? 'certified' : 'configured';
|
|
108
|
+
const tier = !config ? 'durable' : config.certified ? 'certified' : 'registered';
|
|
112
109
|
return {
|
|
113
110
|
workflow_type: workflowType,
|
|
114
111
|
task_queue: config?.task_queue ?? worker?.taskQueue ?? null,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- Migration 019: Role priority threshold
|
|
2
|
+
--
|
|
3
|
+
-- Per-role tuning for the Pace Board priority count: pending, unclaimed
|
|
4
|
+
-- escalations whose age exceeds the threshold appear as a count the floor
|
|
5
|
+
-- pulls to the front of the rack. The signal is deliberately boolean per
|
|
6
|
+
-- item (past threshold or not) — a count to rebalance by, not a queue to
|
|
7
|
+
-- keep re-sorting.
|
|
8
|
+
--
|
|
9
|
+
-- priority_threshold_minutes — max age before an item counts as priority.
|
|
10
|
+
-- Falls back to sla_minutes when NULL.
|
|
11
|
+
-- priority_facet — lt_escalations.metadata key holding the age
|
|
12
|
+
-- origin as an ISO 8601 UTC timestamp (e.g.
|
|
13
|
+
-- the order's authorized date). Falls back to
|
|
14
|
+
-- created_at when NULL. When set, items
|
|
15
|
+
-- missing the key or holding an unparseable
|
|
16
|
+
-- value are not counted.
|
|
17
|
+
--
|
|
18
|
+
-- With both NULL the count still works for any role with sla_minutes set
|
|
19
|
+
-- (age from created_at) — one signal, no parallel SLA pattern.
|
|
20
|
+
--
|
|
21
|
+
-- No new lt_escalations index: the count rides the bounded pending-only scan
|
|
22
|
+
-- in STATION_LIVE_COUNTS_SQL. The GIN metadata index only serves containment
|
|
23
|
+
-- (@>), not age comparisons, and the pending working set is bounded by real
|
|
24
|
+
-- backlog depth.
|
|
25
|
+
|
|
26
|
+
ALTER TABLE lt_roles
|
|
27
|
+
ADD COLUMN IF NOT EXISTS priority_threshold_minutes NUMERIC,
|
|
28
|
+
ADD COLUMN IF NOT EXISTS priority_facet TEXT;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- Migration 020: Explicit workflow certification
|
|
2
|
+
--
|
|
3
|
+
-- Certification becomes a declared property of the workflow registration,
|
|
4
|
+
-- stored on the row. Before this, the tier was derived: a registered workflow
|
|
5
|
+
-- counted as certified whenever it had escalation roles or consumes — so
|
|
6
|
+
-- demoting a certified workflow to plain registered meant deleting its
|
|
7
|
+
-- escalation roles and provider references. With the explicit flag, the three
|
|
8
|
+
-- tiers are direct states:
|
|
9
|
+
--
|
|
10
|
+
-- default — no row in lt_config_workflows (ad-hoc durable workflow)
|
|
11
|
+
-- registered — row exists, certified = false (invocation + entry schema)
|
|
12
|
+
-- certified — row exists, certified = true (interceptor: task tracking,
|
|
13
|
+
-- escalation handling, re-run detection)
|
|
14
|
+
--
|
|
15
|
+
-- Escalation formalization (who resolves, with what schema) belongs to the
|
|
16
|
+
-- escalation and its role — roles carry the versioned escalation schema and
|
|
17
|
+
-- take precedence. The workflow-level roles/resolver_schema remain as
|
|
18
|
+
-- interceptor defaults only.
|
|
19
|
+
--
|
|
20
|
+
-- The backfill runs once (schema_migrations tracked): rows that were
|
|
21
|
+
-- implicitly certified under the derived rule keep their tier.
|
|
22
|
+
|
|
23
|
+
ALTER TABLE lt_config_workflows
|
|
24
|
+
ADD COLUMN IF NOT EXISTS certified BOOLEAN NOT NULL DEFAULT false;
|
|
25
|
+
|
|
26
|
+
UPDATE lt_config_workflows w
|
|
27
|
+
SET certified = true
|
|
28
|
+
WHERE certified = false
|
|
29
|
+
AND (
|
|
30
|
+
cardinality(w.consumes) > 0
|
|
31
|
+
OR EXISTS (
|
|
32
|
+
SELECT 1 FROM lt_config_roles r WHERE r.workflow_type = w.workflow_type
|
|
33
|
+
)
|
|
34
|
+
);
|
|
@@ -19,8 +19,9 @@ declare class LTConfigCache {
|
|
|
19
19
|
*
|
|
20
20
|
* The interceptor uses this to decide whether to wrap the workflow
|
|
21
21
|
* with task tracking, escalation handling, and re-run detection.
|
|
22
|
-
*
|
|
23
|
-
* return null so the interceptor
|
|
22
|
+
* Certification is the explicit `certified` flag on the registration —
|
|
23
|
+
* registered-but-not-certified workflows return null so the interceptor
|
|
24
|
+
* skips them.
|
|
24
25
|
*/
|
|
25
26
|
getResolvedConfig(name: string): Promise<LTResolvedConfig | null>;
|
|
26
27
|
/**
|
|
@@ -106,15 +106,15 @@ class LTConfigCache {
|
|
|
106
106
|
*
|
|
107
107
|
* The interceptor uses this to decide whether to wrap the workflow
|
|
108
108
|
* with task tracking, escalation handling, and re-run detection.
|
|
109
|
-
*
|
|
110
|
-
* return null so the interceptor
|
|
109
|
+
* Certification is the explicit `certified` flag on the registration —
|
|
110
|
+
* registered-but-not-certified workflows return null so the interceptor
|
|
111
|
+
* skips them.
|
|
111
112
|
*/
|
|
112
113
|
async getResolvedConfig(name) {
|
|
113
114
|
const config = await this.get(name);
|
|
114
115
|
if (!config)
|
|
115
116
|
return null;
|
|
116
|
-
|
|
117
|
-
return isCertified ? config : null;
|
|
117
|
+
return config.certified ? config : null;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* Get the config for any workflow REGISTERED in lt_config_workflows (a row
|
package/build/routes/roles.js
CHANGED
|
@@ -95,7 +95,7 @@ router.delete('/escalation-chains', auth_1.requireRoleManager, async (req, res)
|
|
|
95
95
|
/**
|
|
96
96
|
* PATCH /api/roles/:role
|
|
97
97
|
* Update role metadata. Requires admin.
|
|
98
|
-
* Body: { title?, description?, form_schema?, metadata_schema?, properties?, ops_visible?, parent_role?, sla_minutes?, target_per_hour?, worker_count? }
|
|
98
|
+
* Body: { title?, description?, form_schema?, metadata_schema?, properties?, ops_visible?, parent_role?, sla_minutes?, target_per_hour?, worker_count?, priority_threshold_minutes?, priority_facet? }
|
|
99
99
|
*/
|
|
100
100
|
router.patch('/:role', auth_1.requireRoleManager, async (req, res) => {
|
|
101
101
|
// URL is the resource identity — a `role` key in the body must not redirect
|
|
@@ -63,6 +63,7 @@ router.put('/:type/config', auth_1.requireAdmin, async (req, res) => {
|
|
|
63
63
|
const result = await api.upsertWorkflowConfig({
|
|
64
64
|
type: req.params.type,
|
|
65
65
|
invocable: req.body.invocable,
|
|
66
|
+
certified: req.body.certified,
|
|
66
67
|
task_queue: req.body.task_queue,
|
|
67
68
|
default_role: req.body.default_role,
|
|
68
69
|
description: req.body.description,
|
|
@@ -39,6 +39,12 @@
|
|
|
39
39
|
* down gradually, and resolves feed the next station a trickle instead of a
|
|
40
40
|
* wave.
|
|
41
41
|
*
|
|
42
|
+
* Every escalation this run creates carries `metadata.authorized_at` — an ISO
|
|
43
|
+
* 8601 UTC timestamp stamped when the order arrives, identical across all of
|
|
44
|
+
* that order's stages (main line and shoe side-quest). Point a role's
|
|
45
|
+
* `priority_facet` at `authorized_at` with a threshold (e.g. 1 minute) and the
|
|
46
|
+
* Pace Board priority count lights up as orders age through the run.
|
|
47
|
+
*
|
|
42
48
|
* ── The shoe side-quest ──────────────────────────────────────────────────────
|
|
43
49
|
* A shoe order exercises the cross-sequence model (ordering → inserting feeds
|
|
44
50
|
* ship via upstream_roles — its own Operations sequence, not a bend in the
|
|
@@ -94,7 +100,7 @@ declare function holdFor(esc: {
|
|
|
94
100
|
declare function api(method: string, path: string, body?: unknown): Promise<any>;
|
|
95
101
|
declare function login(): Promise<void>;
|
|
96
102
|
declare function ensureSideQuestRoles(): Promise<void>;
|
|
97
|
-
declare function createSideEscalation(role: string, workflowId: string, orderId: string, runId: string, description: string): Promise<boolean>;
|
|
103
|
+
declare function createSideEscalation(role: string, workflowId: string, orderId: string, runId: string, description: string, authorizedAt: string): Promise<boolean>;
|
|
98
104
|
declare function claimBatch(batchTag: string): Promise<number>;
|
|
99
105
|
declare function resolveBatch(batchTag: string, runId: string): Promise<number>;
|
|
100
106
|
interface PlannedOrder {
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
* down gradually, and resolves feed the next station a trickle instead of a
|
|
41
41
|
* wave.
|
|
42
42
|
*
|
|
43
|
+
* Every escalation this run creates carries `metadata.authorized_at` — an ISO
|
|
44
|
+
* 8601 UTC timestamp stamped when the order arrives, identical across all of
|
|
45
|
+
* that order's stages (main line and shoe side-quest). Point a role's
|
|
46
|
+
* `priority_facet` at `authorized_at` with a threshold (e.g. 1 minute) and the
|
|
47
|
+
* Pace Board priority count lights up as orders age through the run.
|
|
48
|
+
*
|
|
43
49
|
* ── The shoe side-quest ──────────────────────────────────────────────────────
|
|
44
50
|
* A shoe order exercises the cross-sequence model (ordering → inserting feeds
|
|
45
51
|
* ship via upstream_roles — its own Operations sequence, not a bend in the
|
|
@@ -158,7 +164,7 @@ async function ensureSideQuestRoles() {
|
|
|
158
164
|
console.log(`[${ts()}] Side-quest roles ready (ordering → inserting ⇒ ship)`);
|
|
159
165
|
}
|
|
160
166
|
// ── Side-quest escalations (standalone rows — no workflow behind them) ────────
|
|
161
|
-
async function createSideEscalation(role, workflowId, orderId, runId, description) {
|
|
167
|
+
async function createSideEscalation(role, workflowId, orderId, runId, description, authorizedAt) {
|
|
162
168
|
try {
|
|
163
169
|
await api('POST', '/api/escalations', {
|
|
164
170
|
type: 'ortho-stage',
|
|
@@ -167,7 +173,7 @@ async function createSideEscalation(role, workflowId, orderId, runId, descriptio
|
|
|
167
173
|
description,
|
|
168
174
|
workflow_id: workflowId,
|
|
169
175
|
envelope: JSON.stringify({ order_id: orderId }),
|
|
170
|
-
metadata: { order_id: orderId, source: 'populate', run_id: runId },
|
|
176
|
+
metadata: { order_id: orderId, source: 'populate', run_id: runId, authorized_at: authorizedAt },
|
|
171
177
|
});
|
|
172
178
|
return true;
|
|
173
179
|
}
|
|
@@ -237,10 +243,11 @@ async function resolveBatch(batchTag, runId) {
|
|
|
237
243
|
resolved++;
|
|
238
244
|
console.log(`[${ts()}] [resolve] ${esc.role.padEnd(9)} ${esc.id.slice(0, 8)}… held ${heldS.toFixed(1)}s [${totalResolved} total]`);
|
|
239
245
|
// Shoes arrived — the side quest advances to inserting, which sits
|
|
240
|
-
// available until the manufactured inserts catch up with it.
|
|
246
|
+
// available until the manufactured inserts catch up with it. The
|
|
247
|
+
// order's authorized_at rides along from the ordering escalation.
|
|
241
248
|
if (esc.role === 'ordering') {
|
|
242
249
|
const orderId = esc.metadata?.order_id ?? '';
|
|
243
|
-
await createSideEscalation('inserting', esc.workflow_id, orderId, runId, `Stuff inserts into shoes for ${orderId}
|
|
250
|
+
await createSideEscalation('inserting', esc.workflow_id, orderId, runId, `Stuff inserts into shoes for ${orderId}`, esc.metadata?.authorized_at ?? new Date().toISOString());
|
|
244
251
|
}
|
|
245
252
|
}
|
|
246
253
|
catch (err) {
|
|
@@ -264,10 +271,18 @@ async function runOrders(batchTag, runId, plan, target) {
|
|
|
264
271
|
for (const order of plan) {
|
|
265
272
|
if (done)
|
|
266
273
|
break;
|
|
274
|
+
// The order's authorization moment — stamped once at arrival and carried
|
|
275
|
+
// on every stage escalation via the workflow's metadata passthrough, so
|
|
276
|
+
// an `authorized_at` priority facet measures journey age.
|
|
277
|
+
const authorizedAt = new Date().toISOString();
|
|
267
278
|
try {
|
|
268
279
|
await api('POST', '/api/workflows/orthoPipeline/invoke', {
|
|
269
280
|
workflowId: order.wfId,
|
|
270
|
-
data: {
|
|
281
|
+
data: {
|
|
282
|
+
order_id: order.orderId,
|
|
283
|
+
item_type: 'insole-standard',
|
|
284
|
+
metadata: { source: 'populate', run_id: runId, authorized_at: authorizedAt },
|
|
285
|
+
},
|
|
271
286
|
metadata: { source: 'populate', run_id: runId, shoes: order.shoes },
|
|
272
287
|
});
|
|
273
288
|
workflowIds.push(order.wfId);
|
|
@@ -279,7 +294,7 @@ async function runOrders(batchTag, runId, plan, target) {
|
|
|
279
294
|
continue;
|
|
280
295
|
}
|
|
281
296
|
if (order.shoes) {
|
|
282
|
-
const ok = await createSideEscalation('ordering', `${order.wfId}-shoes`, order.orderId, runId, `Order shoes for ${order.orderId}
|
|
297
|
+
const ok = await createSideEscalation('ordering', `${order.wfId}-shoes`, order.orderId, runId, `Order shoes for ${order.orderId}`, authorizedAt);
|
|
283
298
|
if (!ok)
|
|
284
299
|
target.value -= 2;
|
|
285
300
|
}
|
|
@@ -17,6 +17,7 @@ async function getWorkflowConfig(workflowType) {
|
|
|
17
17
|
return {
|
|
18
18
|
workflow_type: wf.workflow_type,
|
|
19
19
|
invocable: wf.invocable,
|
|
20
|
+
certified: wf.certified ?? false,
|
|
20
21
|
task_queue: wf.task_queue,
|
|
21
22
|
default_role: wf.default_role,
|
|
22
23
|
description: wf.description,
|
|
@@ -53,6 +54,7 @@ async function listWorkflowConfigs() {
|
|
|
53
54
|
return wfResult.rows.map((wf) => ({
|
|
54
55
|
workflow_type: wf.workflow_type,
|
|
55
56
|
invocable: wf.invocable,
|
|
57
|
+
certified: wf.certified ?? false,
|
|
56
58
|
task_queue: wf.task_queue,
|
|
57
59
|
default_role: wf.default_role,
|
|
58
60
|
description: wf.description,
|
|
@@ -5,13 +5,13 @@ export declare const LIST_ALL_WORKFLOWS = "SELECT * FROM lt_config_workflows ORD
|
|
|
5
5
|
export declare const LIST_ALL_ROLES = "SELECT * FROM lt_config_roles ORDER BY workflow_type, role";
|
|
6
6
|
export declare const LIST_ALL_INVOCATION_ROLES = "SELECT * FROM lt_config_invocation_roles ORDER BY workflow_type, role";
|
|
7
7
|
export declare const ENSURE_ROLE_EXISTS = "INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING";
|
|
8
|
-
export declare const UPSERT_WORKFLOW = "INSERT INTO lt_config_workflows\n (workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as)\nVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\nON CONFLICT (workflow_type) DO UPDATE SET\n invocable = EXCLUDED.invocable,\n task_queue = EXCLUDED.task_queue,\n default_role = EXCLUDED.default_role,\n description = EXCLUDED.description,\n consumes = EXCLUDED.consumes,\n envelope_schema = EXCLUDED.envelope_schema,\n resolver_schema = EXCLUDED.resolver_schema,\n cron_schedule = EXCLUDED.cron_schedule,\n tool_tags = EXCLUDED.tool_tags,\n execute_as = EXCLUDED.execute_as";
|
|
8
|
+
export declare const UPSERT_WORKFLOW = "INSERT INTO lt_config_workflows\n (workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as, certified)\nVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\nON CONFLICT (workflow_type) DO UPDATE SET\n invocable = EXCLUDED.invocable,\n task_queue = EXCLUDED.task_queue,\n default_role = EXCLUDED.default_role,\n description = EXCLUDED.description,\n consumes = EXCLUDED.consumes,\n envelope_schema = EXCLUDED.envelope_schema,\n resolver_schema = EXCLUDED.resolver_schema,\n cron_schedule = EXCLUDED.cron_schedule,\n tool_tags = EXCLUDED.tool_tags,\n execute_as = EXCLUDED.execute_as,\n certified = EXCLUDED.certified";
|
|
9
9
|
export declare const DELETE_CONFIG_ROLES = "DELETE FROM lt_config_roles WHERE workflow_type = $1";
|
|
10
10
|
export declare const INSERT_CONFIG_ROLE = "INSERT INTO lt_config_roles (workflow_type, role) VALUES ($1, $2)";
|
|
11
11
|
export declare const DELETE_INVOCATION_ROLES = "DELETE FROM lt_config_invocation_roles WHERE workflow_type = $1";
|
|
12
12
|
export declare const INSERT_INVOCATION_ROLE = "INSERT INTO lt_config_invocation_roles (workflow_type, role) VALUES ($1, $2)";
|
|
13
13
|
export declare const DELETE_WORKFLOW = "DELETE FROM lt_config_workflows WHERE workflow_type = $1";
|
|
14
|
-
export declare const SEED_WORKFLOW_CONFIG = "INSERT INTO lt_config_workflows\n (workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as)\nVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\nON CONFLICT (workflow_type) DO NOTHING";
|
|
14
|
+
export declare const SEED_WORKFLOW_CONFIG = "INSERT INTO lt_config_workflows\n (workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as, certified)\nVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\nON CONFLICT (workflow_type) DO NOTHING";
|
|
15
15
|
export declare const SEED_CONFIG_ROLE = "INSERT INTO lt_config_roles (workflow_type, role) VALUES ($1, $2)\nON CONFLICT (workflow_type, role) DO NOTHING";
|
|
16
16
|
export declare const SEED_INVOCATION_ROLE = "INSERT INTO lt_config_invocation_roles (workflow_type, role) VALUES ($1, $2)\nON CONFLICT (workflow_type, role) DO NOTHING";
|
|
17
17
|
export declare const GET_PROVIDER_DATA = "SELECT workflow_type, data, completed_at\n FROM lt_tasks\n WHERE origin_id = $1\n AND workflow_type = ANY($2)\n AND status = 'completed'\n ORDER BY completed_at DESC";
|
|
@@ -23,8 +23,8 @@ exports.ENSURE_ROLE_EXISTS = `\
|
|
|
23
23
|
INSERT INTO lt_roles (role) VALUES ($1) ON CONFLICT DO NOTHING`;
|
|
24
24
|
exports.UPSERT_WORKFLOW = `\
|
|
25
25
|
INSERT INTO lt_config_workflows
|
|
26
|
-
(workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as)
|
|
27
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
26
|
+
(workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as, certified)
|
|
27
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
28
28
|
ON CONFLICT (workflow_type) DO UPDATE SET
|
|
29
29
|
invocable = EXCLUDED.invocable,
|
|
30
30
|
task_queue = EXCLUDED.task_queue,
|
|
@@ -35,7 +35,8 @@ ON CONFLICT (workflow_type) DO UPDATE SET
|
|
|
35
35
|
resolver_schema = EXCLUDED.resolver_schema,
|
|
36
36
|
cron_schedule = EXCLUDED.cron_schedule,
|
|
37
37
|
tool_tags = EXCLUDED.tool_tags,
|
|
38
|
-
execute_as = EXCLUDED.execute_as
|
|
38
|
+
execute_as = EXCLUDED.execute_as,
|
|
39
|
+
certified = EXCLUDED.certified`;
|
|
39
40
|
exports.DELETE_CONFIG_ROLES = `\
|
|
40
41
|
DELETE FROM lt_config_roles WHERE workflow_type = $1`;
|
|
41
42
|
exports.INSERT_CONFIG_ROLE = `\
|
|
@@ -51,8 +52,8 @@ DELETE FROM lt_config_workflows WHERE workflow_type = $1`;
|
|
|
51
52
|
// ------------------------------------------------------------------ //
|
|
52
53
|
exports.SEED_WORKFLOW_CONFIG = `\
|
|
53
54
|
INSERT INTO lt_config_workflows
|
|
54
|
-
(workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as)
|
|
55
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
55
|
+
(workflow_type, invocable, task_queue, default_role, description, consumes, envelope_schema, resolver_schema, cron_schedule, tool_tags, execute_as, certified)
|
|
56
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
56
57
|
ON CONFLICT (workflow_type) DO NOTHING`;
|
|
57
58
|
exports.SEED_CONFIG_ROLE = `\
|
|
58
59
|
INSERT INTO lt_config_roles (workflow_type, role) VALUES ($1, $2)
|
|
@@ -7,6 +7,15 @@ const db_1 = require("../../lib/db");
|
|
|
7
7
|
const read_1 = require("./read");
|
|
8
8
|
const logger_1 = require("../../lib/logger");
|
|
9
9
|
const sql_1 = require("./sql");
|
|
10
|
+
/**
|
|
11
|
+
* Certification fallback for writers that omit the explicit flag (older SDKs,
|
|
12
|
+
* seed blocks without `certified`): derived from roles/consumes presence —
|
|
13
|
+
* the rule that governed the tier before the flag existed.
|
|
14
|
+
*/
|
|
15
|
+
function resolveCertified(config) {
|
|
16
|
+
return (config.certified ??
|
|
17
|
+
((config.roles?.length ?? 0) > 0 || (config.consumes?.length ?? 0) > 0));
|
|
18
|
+
}
|
|
10
19
|
async function upsertWorkflowConfig(config) {
|
|
11
20
|
const pool = (0, db_1.getPool)();
|
|
12
21
|
const client = await pool.connect();
|
|
@@ -34,6 +43,7 @@ async function upsertWorkflowConfig(config) {
|
|
|
34
43
|
config.cron_schedule ?? null,
|
|
35
44
|
config.tool_tags || [],
|
|
36
45
|
config.execute_as ?? null,
|
|
46
|
+
resolveCertified(config),
|
|
37
47
|
]);
|
|
38
48
|
// Replace roles
|
|
39
49
|
await client.query(sql_1.DELETE_CONFIG_ROLES, [config.workflow_type]);
|
|
@@ -90,6 +100,7 @@ async function seedWorkflowConfig(config) {
|
|
|
90
100
|
config.cron_schedule ?? null,
|
|
91
101
|
config.tool_tags || [],
|
|
92
102
|
config.execute_as ?? null,
|
|
103
|
+
resolveCertified(config),
|
|
93
104
|
]);
|
|
94
105
|
const inserted = (rowCount ?? 0) > 0;
|
|
95
106
|
if (inserted) {
|
|
@@ -110,6 +121,8 @@ async function seedWorkflowConfig(config) {
|
|
|
110
121
|
drifts.push('description');
|
|
111
122
|
if (config.invocable !== existing.invocable)
|
|
112
123
|
drifts.push('invocable');
|
|
124
|
+
if (resolveCertified(config) !== existing.certified)
|
|
125
|
+
drifts.push('certified');
|
|
113
126
|
if (config.default_role !== existing.default_role)
|
|
114
127
|
drifts.push('default_role');
|
|
115
128
|
if (JSON.stringify(config.envelope_schema) !== JSON.stringify(existing.envelope_schema))
|
|
@@ -32,6 +32,8 @@ export declare function bulkCancelEscalations(ids: string[]): Promise<{
|
|
|
32
32
|
/**
|
|
33
33
|
* Bulk resolve escalations for AI triage.
|
|
34
34
|
* Returns full records so the caller can start triage workflows. No signal is
|
|
35
|
-
* delivered — the triage workflow takes over handling.
|
|
35
|
+
* delivered — the triage workflow takes over handling. Rows backing a live
|
|
36
|
+
* `condition()` waiter (`signal_key` set) are skipped by the store and stay
|
|
37
|
+
* `pending`; only the returned rows enter triage.
|
|
36
38
|
*/
|
|
37
39
|
export declare function bulkResolveForTriage(ids: string[], hint?: string): Promise<LTEscalationRecord[]>;
|
|
@@ -65,7 +65,9 @@ async function bulkCancelEscalations(ids) {
|
|
|
65
65
|
/**
|
|
66
66
|
* Bulk resolve escalations for AI triage.
|
|
67
67
|
* Returns full records so the caller can start triage workflows. No signal is
|
|
68
|
-
* delivered — the triage workflow takes over handling.
|
|
68
|
+
* delivered — the triage workflow takes over handling. Rows backing a live
|
|
69
|
+
* `condition()` waiter (`signal_key` set) are skipped by the store and stay
|
|
70
|
+
* `pending`; only the returned rows enter triage.
|
|
69
71
|
*/
|
|
70
72
|
async function bulkResolveForTriage(ids, hint) {
|
|
71
73
|
if (ids.length === 0)
|
|
@@ -30,9 +30,10 @@ export declare function resolveEscalation(id: string, resolverPayload: Record<st
|
|
|
30
30
|
* Use this over a per-row `resolveEscalation` loop when every row takes the SAME
|
|
31
31
|
* resolverPayload and the rows are woken collectively (or are bookkeeping rows with
|
|
32
32
|
* no signal_key). NOTE: unlike the single `resolve`, this does NOT deliver per-row
|
|
33
|
-
* signals — `resolveMany` is UPDATE-only
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* signals — `resolveMany` is UPDATE-only, and the store enforces it: rows with
|
|
34
|
+
* `signal_key` set (live `condition()` waiters) are skipped, stay `pending`, and
|
|
35
|
+
* drop out of the return set. Waking a parked workflow requires the per-row
|
|
36
|
+
* `resolveEscalation` (which delivers the signal).
|
|
36
37
|
*/
|
|
37
38
|
export declare function resolveEscalationsByIds(ids: string[], resolverPayload: Record<string, any>, metadata?: Record<string, any>): Promise<LTEscalationRecord[]>;
|
|
38
39
|
/**
|
|
@@ -140,9 +140,10 @@ async function resolveEscalation(id, resolverPayload, metadata) {
|
|
|
140
140
|
* Use this over a per-row `resolveEscalation` loop when every row takes the SAME
|
|
141
141
|
* resolverPayload and the rows are woken collectively (or are bookkeeping rows with
|
|
142
142
|
* no signal_key). NOTE: unlike the single `resolve`, this does NOT deliver per-row
|
|
143
|
-
* signals — `resolveMany` is UPDATE-only
|
|
144
|
-
*
|
|
145
|
-
*
|
|
143
|
+
* signals — `resolveMany` is UPDATE-only, and the store enforces it: rows with
|
|
144
|
+
* `signal_key` set (live `condition()` waiters) are skipped, stay `pending`, and
|
|
145
|
+
* drop out of the return set. Waking a parked workflow requires the per-row
|
|
146
|
+
* `resolveEscalation` (which delivers the signal).
|
|
146
147
|
*/
|
|
147
148
|
async function resolveEscalationsByIds(ids, resolverPayload, metadata) {
|
|
148
149
|
if (ids.length === 0)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FacetOrder, FacetQuery } from '../../types';
|
|
2
|
+
export declare const FACET_KEY: RegExp;
|
|
2
3
|
/**
|
|
3
4
|
* Build the WHERE clause for a faceted query, pushing bound params into `params`.
|
|
4
5
|
* Every value is parameterized; only validated column names / metadata keys and
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// as services/escalation/sql.ts. Metadata facets use the GIN index (`@>`).
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.ENSURE_ORIGIN_INDEX = void 0;
|
|
10
|
+
exports.ENSURE_ORIGIN_INDEX = exports.FACET_KEY = void 0;
|
|
11
11
|
exports.buildFacetWhere = buildFacetWhere;
|
|
12
12
|
exports.buildFacetOrder = buildFacetOrder;
|
|
13
13
|
exports.buildGroupOrder = buildGroupOrder;
|
|
@@ -16,7 +16,8 @@ exports.buildGroupOrder = buildGroupOrder;
|
|
|
16
16
|
const ORDER_COLUMNS = new Set(['priority', 'created_at', 'updated_at', 'status', 'role']);
|
|
17
17
|
const RANGE_OPS = new Set(['<', '<=', '>', '>=', '=']);
|
|
18
18
|
// Metadata keys are interpolated (as a JSON path), so they are strictly validated.
|
|
19
|
-
|
|
19
|
+
// Exported so role config (priority_facet) validates keys against the same rule.
|
|
20
|
+
exports.FACET_KEY = /^[a-zA-Z0-9_]+$/;
|
|
20
21
|
/** A metadata extraction expression for a validated key (text or numeric). */
|
|
21
22
|
function metaExpr(key, numeric = false) {
|
|
22
23
|
return numeric ? `(metadata->>'${key}')::numeric` : `(metadata->>'${key}')`;
|
|
@@ -49,13 +50,13 @@ function buildFacetWhere(q, params) {
|
|
|
49
50
|
clauses.push(`NOT (metadata @> ANY($${params.length}::jsonb[]))`);
|
|
50
51
|
}
|
|
51
52
|
for (const r of q.range ?? []) {
|
|
52
|
-
if (!FACET_KEY.test(r.facet) || !RANGE_OPS.has(r.op))
|
|
53
|
+
if (!exports.FACET_KEY.test(r.facet) || !RANGE_OPS.has(r.op))
|
|
53
54
|
continue;
|
|
54
55
|
params.push(r.value);
|
|
55
56
|
clauses.push(`${metaExpr(r.facet, true)} ${r.op} $${params.length}`);
|
|
56
57
|
}
|
|
57
58
|
for (const key of q.exists ?? []) {
|
|
58
|
-
if (!FACET_KEY.test(key))
|
|
59
|
+
if (!exports.FACET_KEY.test(key))
|
|
59
60
|
continue;
|
|
60
61
|
clauses.push(`metadata ? '${key}'`);
|
|
61
62
|
}
|
|
@@ -80,7 +81,7 @@ function buildFacetOrder(orderBy) {
|
|
|
80
81
|
}
|
|
81
82
|
else if (o.field.startsWith('metadata.')) {
|
|
82
83
|
const key = o.field.slice('metadata.'.length);
|
|
83
|
-
if (!FACET_KEY.test(key))
|
|
84
|
+
if (!exports.FACET_KEY.test(key))
|
|
84
85
|
continue;
|
|
85
86
|
parts.push(`${metaExpr(key, o.numeric)} ${dir} NULLS LAST`);
|
|
86
87
|
}
|
|
@@ -106,7 +107,7 @@ function buildGroupOrder(orderBy) {
|
|
|
106
107
|
}
|
|
107
108
|
else if (o.field.startsWith('metadata.')) {
|
|
108
109
|
const key = o.field.slice('metadata.'.length);
|
|
109
|
-
if (!FACET_KEY.test(key))
|
|
110
|
+
if (!exports.FACET_KEY.test(key))
|
|
110
111
|
continue;
|
|
111
112
|
parts.push(`${agg}(${metaExpr(key, o.numeric)}) ${dir} NULLS LAST`);
|
|
112
113
|
}
|
|
@@ -212,6 +212,22 @@ async function listDistinctTypes() {
|
|
|
212
212
|
const client = await (0, client_1.escalations)();
|
|
213
213
|
return client.listDistinctTypes();
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Narrow the caller's explicit role filter WITHIN their read scope. The SDK
|
|
217
|
+
* store gives `roles[]` precedence over `role` when both are set, so passing
|
|
218
|
+
* both would silently DROP the explicit filter for scoped (non-global)
|
|
219
|
+
* callers — a role-filtered list would return the scope's rows instead.
|
|
220
|
+
* Intersecting first keeps the plain path's semantics identical to the
|
|
221
|
+
* faceted path's: a role filter can only narrow scope, never widen past it,
|
|
222
|
+
* and an out-of-scope role yields an empty result, not someone else's rows.
|
|
223
|
+
*/
|
|
224
|
+
function narrowRoleWithinScope(role, visibleRoles) {
|
|
225
|
+
if (!role || !visibleRoles)
|
|
226
|
+
return { role, roles: visibleRoles, empty: false };
|
|
227
|
+
if (!visibleRoles.includes(role))
|
|
228
|
+
return { empty: true };
|
|
229
|
+
return { role, roles: undefined, empty: false };
|
|
230
|
+
}
|
|
215
231
|
async function listEscalations(filters) {
|
|
216
232
|
// Active claim semantics: assigned_to-active and `claimed` both mean "held now".
|
|
217
233
|
const heldNow = !!(filters.assigned_to || filters.claimed);
|
|
@@ -240,15 +256,18 @@ async function listEscalations(filters) {
|
|
|
240
256
|
order: filters.order,
|
|
241
257
|
});
|
|
242
258
|
}
|
|
259
|
+
const scoped = narrowRoleWithinScope(filters.role, filters.visibleRoles);
|
|
260
|
+
if (scoped.empty)
|
|
261
|
+
return { escalations: [], total: 0 };
|
|
243
262
|
const client = await (0, client_1.escalations)();
|
|
244
263
|
// Shared filter — passed to both list() and count() so totals stay in sync.
|
|
245
264
|
const where = {
|
|
246
265
|
status: filters.status,
|
|
247
|
-
role:
|
|
266
|
+
role: scoped.role,
|
|
248
267
|
type: filters.type,
|
|
249
268
|
subtype: filters.subtype,
|
|
250
269
|
priority: filters.priority,
|
|
251
|
-
roles:
|
|
270
|
+
roles: scoped.roles,
|
|
252
271
|
};
|
|
253
272
|
if (filters.assigned_to)
|
|
254
273
|
where.assignedTo = filters.assigned_to;
|
|
@@ -292,15 +311,18 @@ async function listAvailableEscalations(filters) {
|
|
|
292
311
|
order: filters.order,
|
|
293
312
|
});
|
|
294
313
|
}
|
|
314
|
+
const scoped = narrowRoleWithinScope(filters.role, filters.visibleRoles);
|
|
315
|
+
if (scoped.empty)
|
|
316
|
+
return { escalations: [], total: 0 };
|
|
295
317
|
const client = await (0, client_1.escalations)();
|
|
296
318
|
const where = {
|
|
297
319
|
status: 'pending',
|
|
298
320
|
available: true,
|
|
299
|
-
role:
|
|
321
|
+
role: scoped.role,
|
|
300
322
|
type: filters.type,
|
|
301
323
|
subtype: filters.subtype,
|
|
302
324
|
priority: filters.priority,
|
|
303
|
-
roles:
|
|
325
|
+
roles: scoped.roles,
|
|
304
326
|
};
|
|
305
327
|
const [rows, total] = await Promise.all([
|
|
306
328
|
client.list({
|
|
@@ -359,7 +381,7 @@ async function getStationMetrics(visibleRoles, period) {
|
|
|
359
381
|
pending: Number(c.pending ?? 0),
|
|
360
382
|
claimed: Number(c.claimed ?? 0),
|
|
361
383
|
resolved: Number(p?.resolved ?? 0),
|
|
362
|
-
|
|
384
|
+
priority_count: Number(c.priority_count ?? 0),
|
|
363
385
|
throughput_pct: p?.throughput_pct != null ? Number(p.throughput_pct) : null,
|
|
364
386
|
wait: {
|
|
365
387
|
p99: toNum(p?.p99_wait_min),
|