@hotmeshio/long-tail 0.7.1 → 0.7.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/build/api/escalations/create.js +30 -9
- package/build/api/roles.d.ts +32 -0
- package/build/api/roles.js +73 -0
- package/build/bin/ltc.js +23 -0
- package/build/lib/cli/commands/roles.d.ts +17 -0
- package/build/lib/cli/commands/roles.js +102 -0
- package/build/lib/db/schemas/017_role_schema_versions.sql +40 -0
- package/build/lib/db/schemas/018_role_upstreams.sql +21 -0
- package/build/routes/roles.js +20 -0
- package/build/scripts/ortho-populate.d.ts +80 -8
- package/build/scripts/ortho-populate.js +204 -52
- package/build/sdk/index.d.ts +2 -0
- package/build/sdk/index.js +2 -0
- package/build/services/orchestrator/condition.d.ts +25 -1
- package/build/services/orchestrator/condition.js +29 -1
- package/build/services/role/index.d.ts +15 -1
- package/build/services/role/index.js +45 -1
- package/build/services/role/sql.d.ts +23 -2
- package/build/services/role/sql.js +93 -16
- package/build/services/role/types.d.ts +41 -0
- package/build/system/mcp-servers/admin/index.js +3 -3
- package/build/system/mcp-servers/admin/schemas.d.ts +25 -2
- package/build/system/mcp-servers/admin/schemas.js +11 -2
- package/build/system/mcp-servers/admin/users.js +42 -0
- package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -0
- package/build/system/mcp-servers/human-queue-schemas.js +4 -0
- package/build/system/mcp-servers/human-queue.js +10 -2
- package/build/system/seed/tool-manifests-admin.d.ts +371 -0
- package/build/system/seed/tool-manifests-admin.js +3 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/escalation.d.ts +16 -0
- package/build/types/escalation.js +17 -0
- package/dashboard/dist/assets/{AdminDashboard-Bg1HgukI.js → AdminDashboard-rZDVS2G_.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-Bg1HgukI.js.map → AdminDashboard-rZDVS2G_.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-HBe_YDU_.js → AgentConfigPage-CbyyCjCT.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-HBe_YDU_.js.map → AgentConfigPage-CbyyCjCT.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-BsaiYbwL.js → AgentDetailPage-DMeHvidT.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-BsaiYbwL.js.map → AgentDetailPage-DMeHvidT.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-DhN2lIBh.js → AgentsPage-C8fKLxh-.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DhN2lIBh.js.map → AgentsPage-C8fKLxh-.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-_xcWvlIz.js → AvailableEscalationsPage-CUHcybKU.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-_xcWvlIz.js.map → AvailableEscalationsPage-CUHcybKU.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-DY6iUVC9.js → BotPicker-DGhniNcy.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-DY6iUVC9.js.map → BotPicker-DGhniNcy.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-w1owqsmN.js → CapabilitiesPage-ClfuW4ya.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-w1owqsmN.js.map → CapabilitiesPage-ClfuW4ya.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-DIMGjlJ-.js → CollapsibleSection-DnECdKEY.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-DIMGjlJ-.js.map → CollapsibleSection-DnECdKEY.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-D1OmMHux.js → CredentialsPage-DdZ-jyyc.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-D1OmMHux.js.map → CredentialsPage-DdZ-jyyc.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-CQy3DrUQ.js → CronLabel-CymxLvY0.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-CQy3DrUQ.js.map → CronLabel-CymxLvY0.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-BoBkl9tw.js → CustomDurationPicker-8JHvVWD2.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-BoBkl9tw.js.map → CustomDurationPicker-8JHvVWD2.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-QXbiuU0b.js → DropZone-DEX0BBqt.js} +2 -2
- package/dashboard/dist/assets/{DropZone-QXbiuU0b.js.map → DropZone-DEX0BBqt.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-Donjm9gT.js → ElapsedCell-BRZfw_Af.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-Donjm9gT.js.map → ElapsedCell-BRZfw_Af.js.map} +1 -1
- package/dashboard/dist/assets/EscalationSchemaPage-Do2iPKUl.js +9 -0
- package/dashboard/dist/assets/EscalationSchemaPage-Do2iPKUl.js.map +1 -0
- package/dashboard/dist/assets/{EscalationsOverview-CvHIJ_GG.js → EscalationsOverview-CiQO3ntf.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-CvHIJ_GG.js.map → EscalationsOverview-CiQO3ntf.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-DpAXMmT3.js → EventTable-CLuAbba_.js} +2 -2
- package/dashboard/dist/assets/{EventTable-DpAXMmT3.js.map → EventTable-CLuAbba_.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-De1XVx5d.js → GraphInvokePage-BFRi6Nny.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-De1XVx5d.js.map → GraphInvokePage-BFRi6Nny.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-BYR5VXXw.js → HomePage-m1pnxilb.js} +2 -2
- package/dashboard/dist/assets/{HomePage-BYR5VXXw.js.map → HomePage-m1pnxilb.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-Bx7jGzPA.js → ListToolbar-D4Y-M7wN.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-Bx7jGzPA.js.map → ListToolbar-D4Y-M7wN.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-DXRKcJN0.js → McpOverview-mfJpETsW.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-DXRKcJN0.js.map → McpOverview-mfJpETsW.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-CmW15w5e.js → McpQueryDetailPage-Cb99vI7v.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-CmW15w5e.js.map → McpQueryDetailPage-Cb99vI7v.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-BE6I011z.js → McpQueryPage-CKKOxks0.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-BE6I011z.js.map → McpQueryPage-CKKOxks0.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-Dsa3G3XQ.js → McpRunDetailPage-CyTbHs0J.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-Dsa3G3XQ.js.map → McpRunDetailPage-CyTbHs0J.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-uTbym66Y.js → McpRunsPage-MIwmK-Ky.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-uTbym66Y.js.map → McpRunsPage-MIwmK-Ky.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-BGf0jHwE.js → NamespacePill-CwKdoyRM.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-BGf0jHwE.js.map → NamespacePill-CwKdoyRM.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-DPesph0U.js +2 -0
- package/dashboard/dist/assets/OperationsPage-DPesph0U.js.map +1 -0
- package/dashboard/dist/assets/{OperatorDashboard-i8cxaDlk.js → OperatorDashboard-K1gzULr7.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-i8cxaDlk.js.map → OperatorDashboard-K1gzULr7.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-BZJsEVOC.js → PageHeader-BhaWTWcM.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-BZJsEVOC.js.map → PageHeader-BhaWTWcM.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-BWuD7s9c.js → PageHeaderWithStats-yex5WmHB.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-BWuD7s9c.js.map → PageHeaderWithStats-yex5WmHB.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-CP7GZhgs.js → ProcessDetailPage-DBIrY6Vd.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-CP7GZhgs.js.map → ProcessDetailPage-DBIrY6Vd.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-btsU6hBR.js → ProcessesListPage-CrLGDCAD.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-btsU6hBR.js.map → ProcessesListPage-CrLGDCAD.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-7j6Mk780.js +11 -0
- package/dashboard/dist/assets/RoleDetailPage-7j6Mk780.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-Pb9TKSQN.js → RolePill-Cl71eDUA.js} +2 -2
- package/dashboard/dist/assets/{RolePill-Pb9TKSQN.js.map → RolePill-Cl71eDUA.js.map} +1 -1
- package/dashboard/dist/assets/RolesPage-B0ZgjB2V.js +2 -0
- package/dashboard/dist/assets/{RolesPage-1DBoEMsb.js.map → RolesPage-B0ZgjB2V.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-Cp6j3FEY.js → RunAsSelector-DA0h41ml.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-Cp6j3FEY.js.map → RunAsSelector-DA0h41ml.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-Pr8kawv8.js → StickyPagination-t5B71hwd.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-Pr8kawv8.js.map → StickyPagination-t5B71hwd.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-BqmQ-N3y.js → StreamMessageDetail-CYFnZsYA.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-BqmQ-N3y.js.map → StreamMessageDetail-CYFnZsYA.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-y1YmXrsj.js → SwimlaneTimeline-CY18gBek.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-y1YmXrsj.js.map → SwimlaneTimeline-CY18gBek.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-Df4myuYF.js → TagInput-B_WQiuer.js} +2 -2
- package/dashboard/dist/assets/{TagInput-Df4myuYF.js.map → TagInput-B_WQiuer.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-CT5rXHK5.js → TaskDetailPage-B9owzuhV.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CT5rXHK5.js.map → TaskDetailPage-B9owzuhV.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-YalYdCNo.js → TaskQueuePill-Lgn2obNU.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-YalYdCNo.js.map → TaskQueuePill-Lgn2obNU.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-DOy6JlKS.js → TasksListPage-Dlsu7cWG.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-DOy6JlKS.js.map → TasksListPage-Dlsu7cWG.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-BAQwZSpq.js → TimeAgo-CSzifQXB.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-BAQwZSpq.js.map → TimeAgo-CSzifQXB.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-BK_jufns.js → TimestampCell-CIxgN57O.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-BK_jufns.js.map → TimestampCell-CIxgN57O.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-VGpT6Quy.js → ToolPill-uVqcbHPE.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-VGpT6Quy.js.map → ToolPill-uVqcbHPE.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-5PMQyUCe.js → ToolTestPanel-Bw844EW3.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-5PMQyUCe.js.map → ToolTestPanel-Bw844EW3.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-Cn1ShfR8.js → TopicDetailPage-HuDx2oAW.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-Cn1ShfR8.js.map → TopicDetailPage-HuDx2oAW.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-D67tjS8L.js → TopicsPage-CAG0RhAK.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-D67tjS8L.js.map → TopicsPage-CAG0RhAK.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-Dsu1Jk4N.js → UserName-CoGYeig8.js} +2 -2
- package/dashboard/dist/assets/{UserName-Dsu1Jk4N.js.map → UserName-CoGYeig8.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-BwQoOmUE.js → WorkflowExecutionPage-Dhb9EWgI.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-BwQoOmUE.js.map → WorkflowExecutionPage-Dhb9EWgI.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-pPUqmx6Q.js → WorkflowPill-IDjTRfQ8.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-pPUqmx6Q.js.map → WorkflowPill-IDjTRfQ8.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-D3z7COgH.js → WorkflowsDashboard-bJ6Zebfv.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-D3z7COgH.js.map → WorkflowsDashboard-bJ6Zebfv.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-C15JcxyY.js → WorkflowsOverview-B8262fVx.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-C15JcxyY.js.map → WorkflowsOverview-B8262fVx.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-BYq3t7E-.js → YamlWorkflowDetailPage-DhW3q2cO.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-BYq3t7E-.js.map → YamlWorkflowDetailPage-DhW3q2cO.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-B44WgwE_.js → YamlWorkflowsPage-C8hBjSn_.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-B44WgwE_.js.map → YamlWorkflowsPage-C8hBjSn_.js.map} +1 -1
- package/dashboard/dist/assets/{agents-BRmmK6Y9.js → agents-j2YErVp4.js} +2 -2
- package/dashboard/dist/assets/{agents-BRmmK6Y9.js.map → agents-j2YErVp4.js.map} +1 -1
- package/dashboard/dist/assets/{bots-BD75mzGG.js → bots-CD8Bl5pL.js} +2 -2
- package/dashboard/dist/assets/{bots-BD75mzGG.js.map → bots-CD8Bl5pL.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-DDgC_Kds.js → capabilities-Dz190EAa.js} +2 -2
- package/dashboard/dist/assets/{capabilities-DDgC_Kds.js.map → capabilities-Dz190EAa.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-BJh12lMv.js → controlplane-CkUHGPA3.js} +2 -2
- package/dashboard/dist/assets/{controlplane-BJh12lMv.js.map → controlplane-CkUHGPA3.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-DIJt2sgn.js → escalation-BXTwi4J1.js} +2 -2
- package/dashboard/dist/assets/{escalation-DIJt2sgn.js.map → escalation-BXTwi4J1.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-zl6E94Up.js → escalation-columns-GxxwSbDk.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-zl6E94Up.js.map → escalation-columns-GxxwSbDk.js.map} +1 -1
- package/dashboard/dist/assets/{index-DU7YumVJ.js → index-BK3PVYcd.js} +2 -2
- package/dashboard/dist/assets/{index-DU7YumVJ.js.map → index-BK3PVYcd.js.map} +1 -1
- package/dashboard/dist/assets/{index-CknKy4RA.js → index-BkwsJ-0v.js} +2 -2
- package/dashboard/dist/assets/{index-CknKy4RA.js.map → index-BkwsJ-0v.js.map} +1 -1
- package/dashboard/dist/assets/{index-B0kmiSmt.js → index-CaMVn0kW.js} +2 -2
- package/dashboard/dist/assets/{index-B0kmiSmt.js.map → index-CaMVn0kW.js.map} +1 -1
- package/dashboard/dist/assets/{index-BUbzfEA9.js → index-CkL8bpdH.js} +2 -2
- package/dashboard/dist/assets/{index-BUbzfEA9.js.map → index-CkL8bpdH.js.map} +1 -1
- package/dashboard/dist/assets/index-CoGkgm9-.js +6 -0
- package/dashboard/dist/assets/{index-DccCwyZl.js.map → index-CoGkgm9-.js.map} +1 -1
- package/dashboard/dist/assets/{index-CtDf8vvQ.js → index-D-7W2AHB.js} +9 -9
- package/dashboard/dist/assets/index-D-7W2AHB.js.map +1 -0
- package/dashboard/dist/assets/{index-CV4lEd4I.js → index-DGKgXObc.js} +2 -2
- package/dashboard/dist/assets/{index-CV4lEd4I.js.map → index-DGKgXObc.js.map} +1 -1
- package/dashboard/dist/assets/{index-DVVIeBBk.js → index-DTrcYOM2.js} +2 -2
- package/dashboard/dist/assets/{index-DVVIeBBk.js.map → index-DTrcYOM2.js.map} +1 -1
- package/dashboard/dist/assets/{index-BhznDv-4.js → index-NfVWOFbF.js} +2 -2
- package/dashboard/dist/assets/{index-BhznDv-4.js.map → index-NfVWOFbF.js.map} +1 -1
- package/dashboard/dist/assets/index-Wny0bRgq.css +1 -0
- package/dashboard/dist/assets/{index-DrUkTcn-.js → index-YliQ4kW_.js} +2 -2
- package/dashboard/dist/assets/{index-DrUkTcn-.js.map → index-YliQ4kW_.js.map} +1 -1
- package/dashboard/dist/assets/{index-CpgV4Kkp.js → index-eLRh-2NW.js} +2 -2
- package/dashboard/dist/assets/{index-CpgV4Kkp.js.map → index-eLRh-2NW.js.map} +1 -1
- package/dashboard/dist/assets/{index-CDPBXB4e.js → index-n-lZY5S6.js} +2 -2
- package/dashboard/dist/assets/{index-CDPBXB4e.js.map → index-n-lZY5S6.js.map} +1 -1
- package/dashboard/dist/assets/{index-j-C7g_Ej.js → index-qT7eB_Hk.js} +2 -2
- package/dashboard/dist/assets/{index-j-C7g_Ej.js.map → index-qT7eB_Hk.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-pNDWACXB.js → knowledge-CSAbLtk7.js} +2 -2
- package/dashboard/dist/assets/{knowledge-pNDWACXB.js.map → knowledge-CSAbLtk7.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-xddLBwUW.js → mcp-D0Ha3aEo.js} +2 -2
- package/dashboard/dist/assets/{mcp-xddLBwUW.js.map → mcp-D0Ha3aEo.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CmNjvxTG.js → mcp-query-BySmvmuS.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CmNjvxTG.js.map → mcp-query-BySmvmuS.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-B05sd2nB.js → pipelines-DiqWgxJd.js} +2 -2
- package/dashboard/dist/assets/{pipelines-B05sd2nB.js.map → pipelines-DiqWgxJd.js.map} +1 -1
- package/dashboard/dist/assets/roles-BdtnzLsL.js +2 -0
- package/dashboard/dist/assets/roles-BdtnzLsL.js.map +1 -0
- package/dashboard/dist/assets/{tasks-Bm1OKrJA.js → tasks-DYCUi5nZ.js} +2 -2
- package/dashboard/dist/assets/{tasks-Bm1OKrJA.js.map → tasks-DYCUi5nZ.js.map} +1 -1
- package/dashboard/dist/assets/{topics-BVIqtmd4.js → topics-CA7i8ymr.js} +2 -2
- package/dashboard/dist/assets/{topics-BVIqtmd4.js.map → topics-CA7i8ymr.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-_qCzU74s.js → useEventHooks-SfWdyc5L.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-_qCzU74s.js.map → useEventHooks-SfWdyc5L.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-CDnCSFpG.js → useNamespace-CZUsk5Vv.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-CDnCSFpG.js.map → useNamespace-CZUsk5Vv.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-zrHMhaFM.js → useYamlActivityEvents-DKQ5dREx.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-zrHMhaFM.js.map → useYamlActivityEvents-DKQ5dREx.js.map} +1 -1
- package/dashboard/dist/assets/{users-BPBMZg27.js → users-BxKf5dAX.js} +2 -2
- package/dashboard/dist/assets/{users-BPBMZg27.js.map → users-BxKf5dAX.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-C1zVP_dv.js → vendor-icons-CEWCGPeJ.js} +76 -76
- package/dashboard/dist/assets/vendor-icons-CEWCGPeJ.js.map +1 -0
- package/dashboard/dist/assets/{workflows-CT6nTL1I.js → workflows-DNPQ-j-7.js} +2 -2
- package/dashboard/dist/assets/{workflows-CT6nTL1I.js.map → workflows-DNPQ-j-7.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-CdfjkOKU.js → yaml-workflows-CA8BrFxc.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-CdfjkOKU.js.map → yaml-workflows-CA8BrFxc.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/http/roles.md +62 -0
- package/docs/api/mcp/admin.md +32 -1
- package/docs/api/mcp/human-queue.md +2 -0
- package/docs/api/sdk/roles.md +39 -0
- package/docs/cli.md +13 -0
- package/docs/dashboard.md +13 -6
- package/docs/data.md +34 -0
- package/docs/hitl-guide.md +25 -3
- package/package.json +1 -1
- package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js +0 -2
- package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js.map +0 -1
- package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js +0 -16
- package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-1DBoEMsb.js +0 -2
- package/dashboard/dist/assets/index-BRtKQtQZ.css +0 -1
- package/dashboard/dist/assets/index-CtDf8vvQ.js.map +0 -1
- package/dashboard/dist/assets/index-DccCwyZl.js +0 -6
- package/dashboard/dist/assets/roles-bRgaMcdy.js +0 -2
- package/dashboard/dist/assets/roles-bRgaMcdy.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-C1zVP_dv.js.map +0 -1
|
@@ -3,20 +3,58 @@
|
|
|
3
3
|
* ortho-populate — Populate the Operations dashboard with ortho pipeline history.
|
|
4
4
|
*
|
|
5
5
|
* Adapted from the boilerplate's 08-day-resolver.ts pattern:
|
|
6
|
-
* - concurrent claim loop + resolve loop + watchdog via Promise.all
|
|
6
|
+
* - concurrent enqueue + claim loop + resolve loop + watchdog via Promise.all
|
|
7
7
|
* - hold time read from esc.claimed_at (API), not tracked in memory
|
|
8
8
|
* - simple resolve payload { approved: true, station: role }
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* npx ts-node scripts/ortho-populate.ts
|
|
12
12
|
* ORDERS=50 HOLD_S=3 npx ts-node scripts/ortho-populate.ts
|
|
13
|
-
* ORDERS=
|
|
13
|
+
* ORDERS=10 HOLD_S=25 npx ts-node scripts/ortho-populate.ts
|
|
14
|
+
* ORDERS=5 HOLD_S=0 QUEUE_S=0 SHOES_PCT=0 POLL_MS=500 npx ts-node scripts/ortho-populate.ts
|
|
14
15
|
*
|
|
15
16
|
* Env vars:
|
|
16
|
-
* ORDERS
|
|
17
|
-
* HOLD_S
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* ORDERS — number of orders to run (default 20)
|
|
18
|
+
* HOLD_S — average seconds to hold each claimed escalation before
|
|
19
|
+
* resolving (default 2)
|
|
20
|
+
* QUEUE_S — average seconds an escalation waits in the queue before being
|
|
21
|
+
* claimed (default: HOLD_S). This is what builds a visible
|
|
22
|
+
* backlog: on the Operations chart, queued-but-unclaimed and
|
|
23
|
+
* claimed-and-worked are two different bands — with QUEUE_S=0
|
|
24
|
+
* everything is claimed the moment it lands and pending always
|
|
25
|
+
* equals active.
|
|
26
|
+
* SHOES_PCT — percent of orders that include shoes (default 50). A shoe
|
|
27
|
+
* order runs the side-quest sequence (ordering → inserting)
|
|
28
|
+
* alongside its manufacturing line — see below.
|
|
29
|
+
* ARRIVAL_S — average seconds between order arrivals (default: HOLD_S).
|
|
30
|
+
* Orders trickle in at this rate instead of landing as one
|
|
31
|
+
* wave, so the chart shows throughput, not a step function.
|
|
32
|
+
* POLL_MS — poll interval ms (default 1000)
|
|
33
|
+
* BASE_URL — server URL (default http://localhost:3000)
|
|
34
|
+
*
|
|
35
|
+
* All durations are per-item averages, not fixed delays: each escalation gets
|
|
36
|
+
* its own wait and hold spread across 0.4×–1.6× of the configured value (a
|
|
37
|
+
* stable hash of its id — no state to track between polls). That staggering is
|
|
38
|
+
* what makes the Operations chart read like a real floor: at any moment a
|
|
39
|
+
* station holds a mix of waiting and active items, claims burn the backlog
|
|
40
|
+
* down gradually, and resolves feed the next station a trickle instead of a
|
|
41
|
+
* wave.
|
|
42
|
+
*
|
|
43
|
+
* ── The shoe side-quest ──────────────────────────────────────────────────────
|
|
44
|
+
* A shoe order exercises the cross-sequence model (ordering → inserting feeds
|
|
45
|
+
* ship via upstream_roles — its own Operations sequence, not a bend in the
|
|
46
|
+
* main line). Timing tells the physical story, scaled to the manufacturing
|
|
47
|
+
* duration (MFG ≈ 8 stages × (QUEUE_S + HOLD_S)):
|
|
48
|
+
*
|
|
49
|
+
* arrival ordering escalation created with the order
|
|
50
|
+
* + ~½·QUEUE claimed quickly — the shoes are on order, work is underway
|
|
51
|
+
* + ~35% MFG shoes arrive → ordering resolves → inserting created
|
|
52
|
+
* + ~35% MFG inserting sits AVAILABLE (you cannot stuff inserts until the
|
|
53
|
+
* insole is made) — a long sky band on its station
|
|
54
|
+
* + HOLD claimed and resolved just ahead of shipping
|
|
55
|
+
*
|
|
56
|
+
* The script provisions the side-quest roles idempotently on start (ordering,
|
|
57
|
+
* inserting under it, ship fed by inserting).
|
|
20
58
|
*/
|
|
21
59
|
try {
|
|
22
60
|
require('dotenv/config');
|
|
@@ -25,8 +63,18 @@ catch { }
|
|
|
25
63
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
26
64
|
const ORDERS = parseInt(process.env.ORDERS || '20', 10);
|
|
27
65
|
const HOLD_S = parseFloat(process.env.HOLD_S || '2');
|
|
66
|
+
const QUEUE_S = parseFloat(process.env.QUEUE_S ?? String(HOLD_S));
|
|
67
|
+
const SHOES_PCT = parseInt(process.env.SHOES_PCT || '50', 10);
|
|
68
|
+
const ARRIVAL_S = parseFloat(process.env.ARRIVAL_S ?? String(HOLD_S));
|
|
28
69
|
const POLL_MS = parseInt(process.env.POLL_MS || '1000', 10);
|
|
29
70
|
const ORTHO_ROLES = ['design', 'review', 'print', 'grind', 'glue', 'finish', 'qa', 'ship'];
|
|
71
|
+
const SIDE_ROLES = ['ordering', 'inserting'];
|
|
72
|
+
const ALL_ROLES = [...ORTHO_ROLES, ...SIDE_ROLES];
|
|
73
|
+
// Average end-to-end manufacturing time — the side-quest paces derive from it.
|
|
74
|
+
const MFG_S = ORTHO_ROLES.length * (QUEUE_S + HOLD_S);
|
|
75
|
+
const TRANSIT_S = 0.35 * MFG_S; // shoes underway with the vendor (held)
|
|
76
|
+
const INSERT_QUEUE_S = 0.35 * MFG_S; // inserting waits available for inserts
|
|
77
|
+
const ORDERING_QUEUE_S = 0.5 * QUEUE_S; // shoes get ordered promptly
|
|
30
78
|
let token = '';
|
|
31
79
|
let userId = '';
|
|
32
80
|
let totalClaimed = 0;
|
|
@@ -34,6 +82,35 @@ let totalResolved = 0;
|
|
|
34
82
|
function ts() { return new Date().toISOString().slice(11, 19); }
|
|
35
83
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
36
84
|
function ageSeconds(iso) { return (Date.now() - new Date(iso).getTime()) / 1000; }
|
|
85
|
+
// Deterministic per-item jitter — FNV-1a hash of the escalation id into [0, 1).
|
|
86
|
+
// Stable across polls, so each item has one fixed wait and one fixed hold
|
|
87
|
+
// without any bookkeeping.
|
|
88
|
+
function jitter(id, salt) {
|
|
89
|
+
let h = 2166136261;
|
|
90
|
+
for (const c of salt + id) {
|
|
91
|
+
h ^= c.charCodeAt(0);
|
|
92
|
+
h = Math.imul(h, 16777619);
|
|
93
|
+
}
|
|
94
|
+
return ((h >>> 0) % 1000) / 1000;
|
|
95
|
+
}
|
|
96
|
+
/** Spread a base duration across 0.4×–1.6× so a stage's items trickle instead of toggling together. */
|
|
97
|
+
function spread(base, r) { return base * (0.4 + 1.2 * r); }
|
|
98
|
+
/** Evenly distribute SHOES_PCT across the order sequence (deterministic). */
|
|
99
|
+
function hasShoes(i) {
|
|
100
|
+
return Math.floor(((i + 1) * SHOES_PCT) / 100) > Math.floor((i * SHOES_PCT) / 100);
|
|
101
|
+
}
|
|
102
|
+
/** Queue wait before this escalation may be claimed. */
|
|
103
|
+
function claimWaitFor(esc) {
|
|
104
|
+
const base = esc.role === 'ordering' ? ORDERING_QUEUE_S
|
|
105
|
+
: esc.role === 'inserting' ? INSERT_QUEUE_S
|
|
106
|
+
: QUEUE_S;
|
|
107
|
+
return spread(base, jitter(esc.id, 'queue'));
|
|
108
|
+
}
|
|
109
|
+
/** Hold time after claim before this escalation may be resolved. */
|
|
110
|
+
function holdFor(esc) {
|
|
111
|
+
const base = esc.role === 'ordering' ? TRANSIT_S : HOLD_S;
|
|
112
|
+
return spread(base, jitter(esc.id, 'hold'));
|
|
113
|
+
}
|
|
37
114
|
// ── HTTP helper (mirrors boilerplate 07-shared) ───────────────────────────────
|
|
38
115
|
async function api(method, path, body) {
|
|
39
116
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
@@ -60,22 +137,66 @@ async function login() {
|
|
|
60
137
|
}
|
|
61
138
|
console.log(`[${ts()}] Logged in (userId: ${userId.slice(0, 8)}…)`);
|
|
62
139
|
}
|
|
63
|
-
// ──
|
|
140
|
+
// ── Side-quest role provisioning (idempotent) ─────────────────────────────────
|
|
141
|
+
async function ensureSideQuestRoles() {
|
|
142
|
+
for (const role of SIDE_ROLES) {
|
|
143
|
+
await api('POST', '/api/roles', { role }).catch(() => { });
|
|
144
|
+
}
|
|
145
|
+
await api('PATCH', '/api/roles/ordering', {
|
|
146
|
+
title: 'Ordering', ops_visible: true, target_per_hour: 22,
|
|
147
|
+
});
|
|
148
|
+
await api('PATCH', '/api/roles/inserting', {
|
|
149
|
+
title: 'Inserting', ops_visible: true, parent_role: 'ordering', target_per_hour: 22,
|
|
150
|
+
});
|
|
151
|
+
// ship draws from inserting across sequences — union, never clobber.
|
|
152
|
+
const details = await api('GET', '/api/roles/details');
|
|
153
|
+
const ship = (details?.roles ?? []).find((r) => r.role === 'ship');
|
|
154
|
+
const upstreams = ship?.upstream_roles ?? [];
|
|
155
|
+
if (!upstreams.includes('inserting')) {
|
|
156
|
+
await api('PATCH', '/api/roles/ship', { upstream_roles: [...upstreams, 'inserting'] });
|
|
157
|
+
}
|
|
158
|
+
console.log(`[${ts()}] Side-quest roles ready (ordering → inserting ⇒ ship)`);
|
|
159
|
+
}
|
|
160
|
+
// ── Side-quest escalations (standalone rows — no workflow behind them) ────────
|
|
161
|
+
async function createSideEscalation(role, workflowId, orderId, runId, description) {
|
|
162
|
+
try {
|
|
163
|
+
await api('POST', '/api/escalations', {
|
|
164
|
+
type: 'ortho-stage',
|
|
165
|
+
subtype: role,
|
|
166
|
+
role,
|
|
167
|
+
description,
|
|
168
|
+
workflow_id: workflowId,
|
|
169
|
+
envelope: JSON.stringify({ order_id: orderId }),
|
|
170
|
+
metadata: { order_id: orderId, source: 'populate', run_id: runId },
|
|
171
|
+
});
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.error(`[${ts()}] [${role}] create error: ${String(err.message).slice(0, 80)}`);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ── Claim: grab available escalations for this run once their wait elapses ────
|
|
64
180
|
async function claimBatch(batchTag) {
|
|
65
181
|
try {
|
|
66
182
|
const resp = await api('GET', '/api/escalations/available?type=ortho-stage&limit=100&sort_by=created_at&order=asc');
|
|
67
183
|
const escalations = resp?.escalations ?? [];
|
|
68
184
|
let claimed = 0;
|
|
69
185
|
for (const esc of escalations) {
|
|
70
|
-
if (!
|
|
186
|
+
if (!ALL_ROLES.includes(esc.role))
|
|
71
187
|
continue;
|
|
72
188
|
if (!String(esc.workflow_id ?? '').includes(batchTag))
|
|
73
189
|
continue;
|
|
190
|
+
// Let the item sit in the queue first — this is the unclaimed backlog the
|
|
191
|
+
// Operations chart renders as the "waiting" band above the active band.
|
|
192
|
+
// Each item gets its own wait so the backlog burns down gradually.
|
|
193
|
+
if (esc.created_at && ageSeconds(esc.created_at) < claimWaitFor(esc))
|
|
194
|
+
continue;
|
|
74
195
|
try {
|
|
75
196
|
await api('POST', `/api/escalations/${esc.id}/claim`, { durationMinutes: 600 });
|
|
76
197
|
totalClaimed++;
|
|
77
198
|
claimed++;
|
|
78
|
-
console.log(`[${ts()}] [claim] ${esc.role.padEnd(
|
|
199
|
+
console.log(`[${ts()}] [claim] ${esc.role.padEnd(9)} ${esc.id.slice(0, 8)}… [${totalClaimed} total]`);
|
|
79
200
|
}
|
|
80
201
|
catch (err) {
|
|
81
202
|
const msg = String(err.message).slice(0, 80);
|
|
@@ -92,18 +213,21 @@ async function claimBatch(batchTag) {
|
|
|
92
213
|
}
|
|
93
214
|
}
|
|
94
215
|
// ── Resolve: drain all claimed+held escalations for this run ─────────────────
|
|
95
|
-
async function resolveBatch(batchTag) {
|
|
216
|
+
async function resolveBatch(batchTag, runId) {
|
|
96
217
|
try {
|
|
97
218
|
const resp = await api('GET', `/api/escalations?type=ortho-stage&status=pending&assigned_to=${userId}&limit=100&sort_by=created_at&order=asc`);
|
|
98
219
|
const escalations = resp?.escalations ?? [];
|
|
99
220
|
let resolved = 0;
|
|
100
221
|
for (const esc of escalations) {
|
|
101
|
-
if (!
|
|
222
|
+
if (!ALL_ROLES.includes(esc.role))
|
|
102
223
|
continue;
|
|
103
224
|
if (!String(esc.workflow_id ?? '').includes(batchTag))
|
|
104
225
|
continue;
|
|
226
|
+
// Per-item hold: some resolvers are quick, some slow — resolves trickle
|
|
227
|
+
// out and feed the next station a steady stream instead of a wave.
|
|
228
|
+
// ordering holds for the vendor transit time instead of a work hold.
|
|
105
229
|
const heldS = esc.claimed_at ? ageSeconds(esc.claimed_at) : 0;
|
|
106
|
-
if (heldS <
|
|
230
|
+
if (heldS < holdFor(esc))
|
|
107
231
|
continue;
|
|
108
232
|
try {
|
|
109
233
|
await api('POST', `/api/escalations/${esc.id}/resolve`, {
|
|
@@ -111,7 +235,13 @@ async function resolveBatch(batchTag) {
|
|
|
111
235
|
});
|
|
112
236
|
totalResolved++;
|
|
113
237
|
resolved++;
|
|
114
|
-
console.log(`[${ts()}] [resolve] ${esc.role.padEnd(
|
|
238
|
+
console.log(`[${ts()}] [resolve] ${esc.role.padEnd(9)} ${esc.id.slice(0, 8)}… held ${heldS.toFixed(1)}s [${totalResolved} total]`);
|
|
239
|
+
// Shoes arrived — the side quest advances to inserting, which sits
|
|
240
|
+
// available until the manufactured inserts catch up with it.
|
|
241
|
+
if (esc.role === 'ordering') {
|
|
242
|
+
const orderId = esc.metadata?.order_id ?? '';
|
|
243
|
+
await createSideEscalation('inserting', esc.workflow_id, orderId, runId, `Stuff inserts into shoes for ${orderId}`);
|
|
244
|
+
}
|
|
115
245
|
}
|
|
116
246
|
catch (err) {
|
|
117
247
|
console.error(`[${ts()}] [resolve] ${esc.id.slice(0, 8)}… error: ${err.message.slice(0, 60)}`);
|
|
@@ -124,10 +254,38 @@ async function resolveBatch(batchTag) {
|
|
|
124
254
|
return 0;
|
|
125
255
|
}
|
|
126
256
|
}
|
|
127
|
-
|
|
128
|
-
async function runOrders(batchTag, target) {
|
|
257
|
+
async function runOrders(batchTag, runId, plan, target) {
|
|
129
258
|
let done = false;
|
|
130
|
-
|
|
259
|
+
const workflowIds = [];
|
|
260
|
+
// Enqueue loop — orders arrive at ARRIVAL_S pace (jittered), simulating
|
|
261
|
+
// intake throughput instead of one opening wave. A shoe order launches its
|
|
262
|
+
// ordering escalation the moment it lands.
|
|
263
|
+
const enqueueLoop = async () => {
|
|
264
|
+
for (const order of plan) {
|
|
265
|
+
if (done)
|
|
266
|
+
break;
|
|
267
|
+
try {
|
|
268
|
+
await api('POST', '/api/workflows/orthoPipeline/invoke', {
|
|
269
|
+
workflowId: order.wfId,
|
|
270
|
+
data: { order_id: order.orderId, item_type: 'insole-standard' },
|
|
271
|
+
metadata: { source: 'populate', run_id: runId, shoes: order.shoes },
|
|
272
|
+
});
|
|
273
|
+
workflowIds.push(order.wfId);
|
|
274
|
+
console.log(`[${ts()}] [arrive] ${order.index + 1}/${plan.length} → ${order.wfId}${order.shoes ? ' +shoes' : ''}`);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
target.value -= 8 + (order.shoes ? 2 : 0);
|
|
278
|
+
console.error(`[${ts()}] [arrive] ${order.index + 1}/${plan.length} FAILED: ${err.message}`);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (order.shoes) {
|
|
282
|
+
const ok = await createSideEscalation('ordering', `${order.wfId}-shoes`, order.orderId, runId, `Order shoes for ${order.orderId}`);
|
|
283
|
+
if (!ok)
|
|
284
|
+
target.value -= 2;
|
|
285
|
+
}
|
|
286
|
+
await sleep(spread(ARRIVAL_S, jitter(order.wfId, 'arrive')) * 1000);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
131
289
|
const claimLoop = async () => {
|
|
132
290
|
while (!done) {
|
|
133
291
|
await claimBatch(batchTag);
|
|
@@ -135,22 +293,24 @@ async function runOrders(batchTag, target) {
|
|
|
135
293
|
await sleep(POLL_MS);
|
|
136
294
|
}
|
|
137
295
|
};
|
|
138
|
-
// Resolve loop — drains all held escalations each poll.
|
|
139
296
|
const resolveLoop = async () => {
|
|
140
297
|
while (!done) {
|
|
141
|
-
await resolveBatch(batchTag);
|
|
298
|
+
await resolveBatch(batchTag, runId);
|
|
142
299
|
if (!done)
|
|
143
300
|
await sleep(POLL_MS);
|
|
144
301
|
}
|
|
145
302
|
};
|
|
146
|
-
// Watchdog — stops when target hit or stalled too long.
|
|
147
|
-
|
|
303
|
+
// Watchdog — stops when target hit or stalled too long. Stages spend
|
|
304
|
+
// QUEUE_S unclaimed + HOLD_S claimed (the side quest waits longer but
|
|
305
|
+
// overlaps the main line, which keeps resolutions flowing), so the quiet
|
|
306
|
+
// window scales with both.
|
|
307
|
+
const STALL_S = Math.max(30, (HOLD_S + QUEUE_S) * 8);
|
|
148
308
|
const watchdog = async () => {
|
|
149
309
|
let lastResolved = totalResolved;
|
|
150
310
|
let stalledFor = 0;
|
|
151
311
|
while (!done) {
|
|
152
312
|
await sleep(1000);
|
|
153
|
-
if (totalResolved >= target) {
|
|
313
|
+
if (totalResolved >= target.value) {
|
|
154
314
|
done = true;
|
|
155
315
|
break;
|
|
156
316
|
}
|
|
@@ -161,51 +321,42 @@ async function runOrders(batchTag, target) {
|
|
|
161
321
|
else {
|
|
162
322
|
stalledFor++;
|
|
163
323
|
if (stalledFor % 10 === 0) {
|
|
164
|
-
console.log(`[${ts()}] … ${totalResolved}/${target} resolved — waiting (${stalledFor}s quiet)`);
|
|
324
|
+
console.log(`[${ts()}] … ${totalResolved}/${target.value} resolved — waiting (${stalledFor}s quiet)`);
|
|
165
325
|
}
|
|
166
326
|
if (stalledFor >= STALL_S) {
|
|
167
|
-
console.warn(`[${ts()}] ⚠ stalled ${stalledFor}s with ${totalResolved}/${target} — stopping`);
|
|
327
|
+
console.warn(`[${ts()}] ⚠ stalled ${stalledFor}s with ${totalResolved}/${target.value} — stopping`);
|
|
168
328
|
done = true;
|
|
169
329
|
break;
|
|
170
330
|
}
|
|
171
331
|
}
|
|
172
332
|
}
|
|
173
333
|
};
|
|
174
|
-
await Promise.all([claimLoop(), resolveLoop(), watchdog()]);
|
|
334
|
+
await Promise.all([enqueueLoop(), claimLoop(), resolveLoop(), watchdog()]);
|
|
335
|
+
return workflowIds;
|
|
175
336
|
}
|
|
176
337
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
177
338
|
async function main() {
|
|
178
|
-
console.log(`\n Ortho Populate ── ${ORDERS} orders × 8 stages (hold=${HOLD_S}s, poll=${POLL_MS}ms)`);
|
|
179
|
-
console.log(` Server: ${BASE_URL}\n`);
|
|
180
|
-
await login();
|
|
181
339
|
const runId = Math.floor(Date.now() / 1000).toString();
|
|
182
340
|
const batchTag = `ortho-pop-${runId}`;
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
if (workflowIds.length === 0) {
|
|
202
|
-
console.error('Nothing enqueued — aborting');
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
const target = workflowIds.length * 8;
|
|
206
|
-
console.log(`\n[${ts()}] Running — target: ${target} resolutions (${workflowIds.length} orders × 8 stages)\n`);
|
|
341
|
+
const plan = Array.from({ length: ORDERS }, (_, i) => ({
|
|
342
|
+
index: i,
|
|
343
|
+
wfId: `${batchTag}-${i}`,
|
|
344
|
+
orderId: `ORD-${runId}-${i}`,
|
|
345
|
+
shoes: hasShoes(i),
|
|
346
|
+
}));
|
|
347
|
+
const shoeCount = plan.filter((o) => o.shoes).length;
|
|
348
|
+
// Each order resolves 8 manufacturing stages; a shoe order adds
|
|
349
|
+
// ordering + inserting on the side-quest sequence.
|
|
350
|
+
const target = { value: ORDERS * 8 + shoeCount * 2 };
|
|
351
|
+
console.log(`\n Ortho Populate ── ${ORDERS} orders × 8 stages, ${shoeCount} with shoes (+2 each)`);
|
|
352
|
+
console.log(` queue=${QUEUE_S}s hold=${HOLD_S}s arrival=${ARRIVAL_S}s shoes=${SHOES_PCT}% poll=${POLL_MS}ms`);
|
|
353
|
+
console.log(` Side quest: transit≈${TRANSIT_S.toFixed(0)}s, inserting waits≈${INSERT_QUEUE_S.toFixed(0)}s (MFG≈${MFG_S.toFixed(0)}s)`);
|
|
354
|
+
console.log(` Server: ${BASE_URL}\n`);
|
|
355
|
+
await login();
|
|
356
|
+
await ensureSideQuestRoles();
|
|
357
|
+
console.log(`\n[${ts()}] Running — target: ${target.value} resolutions\n`);
|
|
207
358
|
const startMs = Date.now();
|
|
208
|
-
await runOrders(batchTag, target);
|
|
359
|
+
const workflowIds = await runOrders(batchTag, runId, plan, target);
|
|
209
360
|
const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
|
|
210
361
|
await sleep(2000);
|
|
211
362
|
// Count completed workflows.
|
|
@@ -220,6 +371,7 @@ async function main() {
|
|
|
220
371
|
}
|
|
221
372
|
console.log(`\n[${ts()}] Done in ${elapsed}s`);
|
|
222
373
|
console.log(` Orders submitted: ${workflowIds.length}`);
|
|
374
|
+
console.log(` Orders with shoes: ${shoeCount}`);
|
|
223
375
|
console.log(` Orders completed: ${completed}`);
|
|
224
376
|
console.log(` Claimed: ${totalClaimed}`);
|
|
225
377
|
console.log(` Resolved: ${totalResolved}`);
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -273,6 +273,8 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
273
273
|
create: typeof rolesApi.createRole;
|
|
274
274
|
update: typeof rolesApi.updateRole;
|
|
275
275
|
delete: typeof rolesApi.deleteRole;
|
|
276
|
+
getSchema: typeof rolesApi.getRoleSchema;
|
|
277
|
+
listSchemaVersions: typeof rolesApi.listRoleSchemaVersions;
|
|
276
278
|
getEscalationChains: typeof rolesApi.getEscalationChains;
|
|
277
279
|
addEscalationChain: typeof rolesApi.addEscalationChain;
|
|
278
280
|
removeEscalationChain: typeof rolesApi.removeEscalationChain;
|
package/build/sdk/index.js
CHANGED
|
@@ -196,6 +196,8 @@ function createClient(options = {}) {
|
|
|
196
196
|
create: rolesApi.createRole,
|
|
197
197
|
update: rolesApi.updateRole,
|
|
198
198
|
delete: rolesApi.deleteRole,
|
|
199
|
+
getSchema: rolesApi.getRoleSchema,
|
|
200
|
+
listSchemaVersions: rolesApi.listRoleSchemaVersions,
|
|
199
201
|
getEscalationChains: rolesApi.getEscalationChains,
|
|
200
202
|
addEscalationChain: rolesApi.addEscalationChain,
|
|
201
203
|
removeEscalationChain: rolesApi.removeEscalationChain,
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { Types } from '@hotmeshio/hotmesh';
|
|
2
|
+
/**
|
|
3
|
+
* HotMesh's escalation config plus long-tail sugar. `schemaVersion` pins the
|
|
4
|
+
* role-schema version (lt_role_schemas) the resolver form should render; it is
|
|
5
|
+
* folded into `metadata.schema_version` before the config reaches the engine,
|
|
6
|
+
* so the pin rides the row's GIN-indexed metadata like any other facet. Omit
|
|
7
|
+
* it and the resolver UI simply uses the role's latest schema.
|
|
8
|
+
*/
|
|
9
|
+
export type ConditionEscalationConfig = Types.ConditionQueueConfig & {
|
|
10
|
+
schemaVersion?: number;
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* Wait for a signal and resolve the associated escalation automatically.
|
|
4
14
|
*
|
|
@@ -23,6 +33,20 @@ import type { Types } from '@hotmeshio/hotmesh';
|
|
|
23
33
|
* });
|
|
24
34
|
* ```
|
|
25
35
|
*
|
|
36
|
+
* **Pin the role schema version (optional).** Role form/metadata schemas are
|
|
37
|
+
* versioned (lt_role_schemas). Set `schemaVersion` when this wait depends on a
|
|
38
|
+
* specific shape — e.g. a review form that gained a field the workflow expects
|
|
39
|
+
* back. The resolver UI renders exactly that version for this escalation;
|
|
40
|
+
* without it, the role's latest schema always applies.
|
|
41
|
+
*
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const decision = await conditionLT<{ approved: boolean; lotNumber: string }>(signalId, {
|
|
44
|
+
* role: 'reviewer',
|
|
45
|
+
* description: instructions,
|
|
46
|
+
* schemaVersion: 3, // render role schema v3 for this row
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
26
50
|
* **With an SLA (hotmesh 0.25.1+) — add `timeout` to the config.** The same
|
|
27
51
|
* single wait arms a resume timer alongside the escalation row: when the timer
|
|
28
52
|
* fires first, this helper returns `false`, the row transitions to
|
|
@@ -56,4 +80,4 @@ import type { Types } from '@hotmeshio/hotmesh';
|
|
|
56
80
|
* const decision = await conditionLT<{ approved: boolean }>(signalId);
|
|
57
81
|
* ```
|
|
58
82
|
*/
|
|
59
|
-
export declare function conditionLT<T = Record<string, any>>(signalId: string, escalation?:
|
|
83
|
+
export declare function conditionLT<T = Record<string, any>>(signalId: string, escalation?: ConditionEscalationConfig): Promise<T | false | null>;
|
|
@@ -35,8 +35,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.conditionLT = conditionLT;
|
|
37
37
|
const hotmesh_1 = require("@hotmeshio/hotmesh");
|
|
38
|
+
const escalation_1 = require("../../types/escalation");
|
|
38
39
|
const interceptorActivities = __importStar(require("../interceptor/activities"));
|
|
39
40
|
const LT_ACTIVITY_QUEUE = 'lt-interceptor';
|
|
41
|
+
/** Fold the schemaVersion sugar into metadata; pass everything else through. */
|
|
42
|
+
function toEngineConfig(escalation) {
|
|
43
|
+
if (!escalation || escalation.schemaVersion == null)
|
|
44
|
+
return escalation;
|
|
45
|
+
const { schemaVersion, ...config } = escalation;
|
|
46
|
+
return {
|
|
47
|
+
...config,
|
|
48
|
+
metadata: {
|
|
49
|
+
...config.metadata,
|
|
50
|
+
[escalation_1.ESCALATION_METADATA_KEYS.SCHEMA_VERSION]: schemaVersion,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
40
54
|
/**
|
|
41
55
|
* Wait for a signal and resolve the associated escalation automatically.
|
|
42
56
|
*
|
|
@@ -61,6 +75,20 @@ const LT_ACTIVITY_QUEUE = 'lt-interceptor';
|
|
|
61
75
|
* });
|
|
62
76
|
* ```
|
|
63
77
|
*
|
|
78
|
+
* **Pin the role schema version (optional).** Role form/metadata schemas are
|
|
79
|
+
* versioned (lt_role_schemas). Set `schemaVersion` when this wait depends on a
|
|
80
|
+
* specific shape — e.g. a review form that gained a field the workflow expects
|
|
81
|
+
* back. The resolver UI renders exactly that version for this escalation;
|
|
82
|
+
* without it, the role's latest schema always applies.
|
|
83
|
+
*
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const decision = await conditionLT<{ approved: boolean; lotNumber: string }>(signalId, {
|
|
86
|
+
* role: 'reviewer',
|
|
87
|
+
* description: instructions,
|
|
88
|
+
* schemaVersion: 3, // render role schema v3 for this row
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
64
92
|
* **With an SLA (hotmesh 0.25.1+) — add `timeout` to the config.** The same
|
|
65
93
|
* single wait arms a resume timer alongside the escalation row: when the timer
|
|
66
94
|
* fires first, this helper returns `false`, the row transitions to
|
|
@@ -95,7 +123,7 @@ const LT_ACTIVITY_QUEUE = 'lt-interceptor';
|
|
|
95
123
|
* ```
|
|
96
124
|
*/
|
|
97
125
|
async function conditionLT(signalId, escalation) {
|
|
98
|
-
const raw = await hotmesh_1.Durable.workflow.condition(signalId, escalation);
|
|
126
|
+
const raw = await hotmesh_1.Durable.workflow.condition(signalId, toEngineConfig(escalation));
|
|
99
127
|
// false = timeout, null = escalation was cancelled — propagate both as-is
|
|
100
128
|
if (raw === null || raw === false)
|
|
101
129
|
return raw;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EscalationChain, RoleDetail, UpdateRoleInput } from './types';
|
|
1
|
+
import type { EscalationChain, RoleDetail, RoleSchemaVersion, RoleSchemaVersionSummary, UpdateRoleInput } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Get the roles a given source role can escalate to.
|
|
4
4
|
*/
|
|
@@ -46,6 +46,20 @@ export declare function createRole(role: string): Promise<boolean>;
|
|
|
46
46
|
* resets to {}). One atomic UPDATE — see UPDATE_ROLE_METADATA.
|
|
47
47
|
*/
|
|
48
48
|
export declare function updateRoleMetadata(role: string, input: UpdateRoleInput): Promise<RoleDetail | null>;
|
|
49
|
+
/** Roles this station draws input from across other sequences. */
|
|
50
|
+
export declare function getRoleUpstreams(role: string): Promise<string[]>;
|
|
51
|
+
/**
|
|
52
|
+
* List the schema version history for a role (newest first). Schemas are
|
|
53
|
+
* elided — presence flags only; fetch a full snapshot via getRoleSchema.
|
|
54
|
+
*/
|
|
55
|
+
export declare function listRoleSchemaVersions(role: string): Promise<RoleSchemaVersionSummary[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Fetch a role's schema pair. With a version, reads the immutable snapshot
|
|
58
|
+
* from lt_role_schemas; without one, reads the live (latest) columns — a role
|
|
59
|
+
* that has never versioned its schema still answers with version null.
|
|
60
|
+
* Returns null when the role (or the requested version) does not exist.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getRoleSchema(role: string, version?: number): Promise<RoleSchemaVersion | null>;
|
|
49
63
|
/**
|
|
50
64
|
* Fetch form_schema for a role (used as default escalation form when workflow
|
|
51
65
|
* config does not specify a resolver_schema).
|
|
@@ -10,6 +10,9 @@ exports.listDistinctRoles = listDistinctRoles;
|
|
|
10
10
|
exports.listRolesWithDetails = listRolesWithDetails;
|
|
11
11
|
exports.createRole = createRole;
|
|
12
12
|
exports.updateRoleMetadata = updateRoleMetadata;
|
|
13
|
+
exports.getRoleUpstreams = getRoleUpstreams;
|
|
14
|
+
exports.listRoleSchemaVersions = listRoleSchemaVersions;
|
|
15
|
+
exports.getRoleSchema = getRoleSchema;
|
|
13
16
|
exports.getRoleFormSchema = getRoleFormSchema;
|
|
14
17
|
exports.getRoleMetadataSchema = getRoleMetadataSchema;
|
|
15
18
|
exports.deleteRole = deleteRole;
|
|
@@ -123,6 +126,7 @@ async function createRole(role) {
|
|
|
123
126
|
async function updateRoleMetadata(role, input) {
|
|
124
127
|
const pool = (0, db_1.getPool)();
|
|
125
128
|
const provided = (key) => input[key] !== undefined;
|
|
129
|
+
const upstreams = [...new Set(input.upstream_roles ?? [])].sort();
|
|
126
130
|
const { rows } = await pool.query(sql_1.UPDATE_ROLE_METADATA, [
|
|
127
131
|
role,
|
|
128
132
|
provided('title'), input.title ?? null,
|
|
@@ -135,8 +139,48 @@ async function updateRoleMetadata(role, input) {
|
|
|
135
139
|
provided('sla_minutes'), input.sla_minutes ?? null,
|
|
136
140
|
provided('target_per_hour'), input.target_per_hour ?? null,
|
|
137
141
|
provided('worker_count'), input.worker_count ?? null,
|
|
142
|
+
input.change_summary ?? null,
|
|
143
|
+
provided('upstream_roles'), upstreams,
|
|
138
144
|
]);
|
|
139
|
-
|
|
145
|
+
if (!rows[0])
|
|
146
|
+
return null;
|
|
147
|
+
// The statement's CTEs share one snapshot, so the returned row cannot see
|
|
148
|
+
// the upstream sync it just performed — echo the write, or read when the
|
|
149
|
+
// field wasn't touched.
|
|
150
|
+
const upstream_roles = provided('upstream_roles')
|
|
151
|
+
? upstreams
|
|
152
|
+
: await getRoleUpstreams(role);
|
|
153
|
+
return { ...rows[0], upstream_roles };
|
|
154
|
+
}
|
|
155
|
+
/** Roles this station draws input from across other sequences. */
|
|
156
|
+
async function getRoleUpstreams(role) {
|
|
157
|
+
const pool = (0, db_1.getPool)();
|
|
158
|
+
const { rows } = await pool.query(sql_1.GET_ROLE_UPSTREAMS, [role]);
|
|
159
|
+
return rows.map((r) => r.upstream_role);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* List the schema version history for a role (newest first). Schemas are
|
|
163
|
+
* elided — presence flags only; fetch a full snapshot via getRoleSchema.
|
|
164
|
+
*/
|
|
165
|
+
async function listRoleSchemaVersions(role) {
|
|
166
|
+
const pool = (0, db_1.getPool)();
|
|
167
|
+
const { rows } = await pool.query(sql_1.LIST_ROLE_SCHEMA_VERSIONS, [role]);
|
|
168
|
+
return rows;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Fetch a role's schema pair. With a version, reads the immutable snapshot
|
|
172
|
+
* from lt_role_schemas; without one, reads the live (latest) columns — a role
|
|
173
|
+
* that has never versioned its schema still answers with version null.
|
|
174
|
+
* Returns null when the role (or the requested version) does not exist.
|
|
175
|
+
*/
|
|
176
|
+
async function getRoleSchema(role, version) {
|
|
177
|
+
const pool = (0, db_1.getPool)();
|
|
178
|
+
const { rows } = version != null
|
|
179
|
+
? await pool.query(sql_1.GET_ROLE_SCHEMA_VERSION, [role, version])
|
|
180
|
+
: await pool.query(sql_1.GET_ROLE_SCHEMA_CURRENT, [role]);
|
|
181
|
+
if (!rows[0])
|
|
182
|
+
return null;
|
|
183
|
+
return { ...rows[0], role };
|
|
140
184
|
}
|
|
141
185
|
/**
|
|
142
186
|
* Fetch form_schema for a role (used as default escalation form when workflow
|
|
@@ -21,11 +21,32 @@ export declare const DELETE_ROLE = "\n DELETE FROM lt_roles WHERE role = $1";
|
|
|
21
21
|
* true the paired value is written (null clears; properties resets to '{}').
|
|
22
22
|
* This is what lets the dashboard's per-tab saves and single-field MCP calls
|
|
23
23
|
* coexist on the same row without read-modify-write.
|
|
24
|
+
*
|
|
25
|
+
* Schema versioning rides in the same statement: when a provided form_schema
|
|
26
|
+
* or metadata_schema actually differs from the stored value (IS DISTINCT FROM,
|
|
27
|
+
* evaluated against the pre-update row), current_schema_version advances and
|
|
28
|
+
* the post-update schema pair is snapshotted into lt_role_schemas. A no-change
|
|
29
|
+
* save leaves the version alone and the snapshot INSERT conflicts away.
|
|
30
|
+
* $22 = optional change summary recorded on the snapshot.
|
|
31
|
+
*
|
|
32
|
+
* Upstream inputs ($23 = provided sentinel, $24 = replacement set) sync in the
|
|
33
|
+
* same statement with replace semantics. The two CTEs deliberately avoid
|
|
34
|
+
* touching the same row: the DELETE only removes rows leaving the set, and the
|
|
35
|
+
* INSERT's ON CONFLICT DO NOTHING skips rows that stay (delete+insert of the
|
|
36
|
+
* SAME key in one statement would silently drop it — CTEs share a snapshot).
|
|
24
37
|
*/
|
|
25
|
-
export declare const UPDATE_ROLE_METADATA = "\n UPDATE lt_roles SET\n
|
|
38
|
+
export declare const UPDATE_ROLE_METADATA = "\n WITH updated AS (\n UPDATE lt_roles SET\n title = CASE WHEN $2::boolean THEN $3 ELSE title END,\n description = CASE WHEN $4::boolean THEN $5 ELSE description END,\n form_schema = CASE WHEN $6::boolean THEN $7::jsonb ELSE form_schema END,\n metadata_schema = CASE WHEN $8::boolean THEN $9::jsonb ELSE metadata_schema END,\n properties = CASE WHEN $10::boolean THEN COALESCE($11::jsonb, '{}'::jsonb) ELSE properties END,\n ops_visible = CASE WHEN $12::boolean THEN $13::boolean ELSE ops_visible END,\n parent_role = CASE WHEN $14::boolean THEN $15 ELSE parent_role END,\n sla_minutes = CASE WHEN $16::boolean THEN $17::numeric ELSE sla_minutes END,\n target_per_hour = CASE WHEN $18::boolean THEN $19::numeric ELSE target_per_hour END,\n worker_count = CASE WHEN $20::boolean THEN $21::int ELSE worker_count END,\n current_schema_version = CASE\n WHEN ($6::boolean AND $7::jsonb IS DISTINCT FROM form_schema)\n OR ($8::boolean AND $9::jsonb IS DISTINCT FROM metadata_schema)\n THEN COALESCE(current_schema_version, 0) + 1\n ELSE current_schema_version END\n WHERE role = $1\n RETURNING\n role, title, description, form_schema, metadata_schema, properties,\n ops_visible, parent_role, sla_minutes, target_per_hour, worker_count,\n current_schema_version\n ), snapshot AS (\n INSERT INTO lt_role_schemas (role, version, form_schema, metadata_schema, change_summary)\n SELECT role, current_schema_version, form_schema, metadata_schema, $22\n FROM updated\n WHERE ($6::boolean OR $8::boolean) AND current_schema_version IS NOT NULL\n ON CONFLICT (role, version) DO NOTHING\n ), upstream_prune AS (\n DELETE FROM lt_role_upstreams\n WHERE $23::boolean AND role = $1 AND upstream_role <> ALL($24::text[])\n ), upstream_add AS (\n INSERT INTO lt_role_upstreams (role, upstream_role)\n SELECT $1, u FROM unnest($24::text[]) AS u\n WHERE $23::boolean\n ON CONFLICT DO NOTHING\n )\n SELECT * FROM updated";
|
|
26
39
|
export declare const GET_ROLE_FORM_SCHEMA = "\n SELECT form_schema FROM lt_roles WHERE role = $1";
|
|
27
40
|
export declare const GET_ROLE_METADATA_SCHEMA = "\n SELECT metadata_schema FROM lt_roles WHERE role = $1";
|
|
28
|
-
export declare const
|
|
41
|
+
export declare const GET_ROLE_UPSTREAMS = "\n SELECT upstream_role FROM lt_role_upstreams WHERE role = $1 ORDER BY upstream_role";
|
|
42
|
+
export declare const LIST_ROLE_SCHEMA_VERSIONS = "\n SELECT\n s.version,\n s.form_schema IS NOT NULL AS has_form_schema,\n s.metadata_schema IS NOT NULL AS has_metadata_schema,\n s.change_summary,\n s.created_at,\n s.version = r.current_schema_version AS is_current\n FROM lt_role_schemas s\n JOIN lt_roles r ON r.role = s.role\n WHERE s.role = $1\n ORDER BY s.version DESC";
|
|
43
|
+
export declare const GET_ROLE_SCHEMA_VERSION = "\n SELECT s.role, s.version, s.form_schema, s.metadata_schema, s.change_summary,\n s.created_at, r.current_schema_version AS latest_version\n FROM lt_role_schemas s\n JOIN lt_roles r ON r.role = s.role\n WHERE s.role = $1 AND s.version = $2";
|
|
44
|
+
/**
|
|
45
|
+
* Latest schema resolves from the live lt_roles columns (a role that has never
|
|
46
|
+
* versioned its schema still answers, with version NULL).
|
|
47
|
+
*/
|
|
48
|
+
export declare const GET_ROLE_SCHEMA_CURRENT = "\n SELECT r.role, r.current_schema_version AS version, r.form_schema,\n r.metadata_schema, s.change_summary, s.created_at,\n r.current_schema_version AS latest_version\n FROM lt_roles r\n LEFT JOIN lt_role_schemas s\n ON s.role = r.role AND s.version = r.current_schema_version\n WHERE r.role = $1";
|
|
49
|
+
export declare const LIST_ROLES_WITH_DETAILS = "\n WITH\n user_counts AS (\n SELECT role, COUNT(DISTINCT user_id)::int AS cnt\n FROM lt_user_roles\n GROUP BY role\n ),\n chain_counts AS (\n SELECT role, COUNT(*)::int AS cnt\n FROM (\n SELECT source_role AS role FROM lt_config_role_escalations\n UNION ALL\n SELECT target_role AS role FROM lt_config_role_escalations\n ) c\n GROUP BY role\n ),\n workflow_counts AS (\n SELECT role, COUNT(*)::int AS cnt\n FROM lt_config_roles\n GROUP BY role\n ),\n upstreams AS (\n SELECT role, array_agg(upstream_role ORDER BY upstream_role) AS ups\n FROM lt_role_upstreams\n GROUP BY role\n )\n SELECT\n r.role,\n r.title,\n r.description,\n r.form_schema,\n r.metadata_schema,\n r.properties,\n r.ops_visible,\n r.parent_role,\n r.sla_minutes,\n r.target_per_hour,\n r.worker_count,\n r.current_schema_version,\n COALESCE(up.ups, '{}') AS upstream_roles,\n COALESCE(uc.cnt, 0) AS user_count,\n COALESCE(cc.cnt, 0) AS chain_count,\n COALESCE(wc.cnt, 0) AS workflow_count\n FROM lt_roles r\n LEFT JOIN user_counts uc ON uc.role = r.role\n LEFT JOIN chain_counts cc ON cc.role = r.role\n LEFT JOIN workflow_counts wc ON wc.role = r.role\n LEFT JOIN upstreams up ON up.role = r.role\n ORDER BY r.role";
|
|
29
50
|
export declare const COUNT_USER_ROLE_REFS = "\n SELECT COUNT(*)::int AS cnt FROM lt_user_roles WHERE role = $1";
|
|
30
51
|
export declare const COUNT_CHAIN_REFS = "\n SELECT COUNT(*)::int AS cnt\n FROM lt_config_role_escalations\n WHERE source_role = $1 OR target_role = $1";
|
|
31
52
|
export declare const COUNT_WORKFLOW_REFS = "\n SELECT COUNT(*)::int AS cnt FROM lt_config_roles WHERE role = $1";
|