@hotmeshio/long-tail 0.7.0 → 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.
Files changed (231) hide show
  1. package/build/api/escalations/create.js +30 -9
  2. package/build/api/roles.d.ts +32 -0
  3. package/build/api/roles.js +73 -0
  4. package/build/bin/ltc.js +23 -0
  5. package/build/lib/cli/commands/roles.d.ts +17 -0
  6. package/build/lib/cli/commands/roles.js +102 -0
  7. package/build/lib/db/schemas/017_role_schema_versions.sql +40 -0
  8. package/build/lib/db/schemas/018_role_upstreams.sql +21 -0
  9. package/build/routes/roles.js +20 -0
  10. package/build/scripts/ortho-populate.d.ts +80 -8
  11. package/build/scripts/ortho-populate.js +204 -52
  12. package/build/sdk/index.d.ts +2 -0
  13. package/build/sdk/index.js +2 -0
  14. package/build/services/interceptor/completion.js +17 -12
  15. package/build/services/orchestrator/condition.d.ts +25 -1
  16. package/build/services/orchestrator/condition.js +29 -1
  17. package/build/services/role/index.d.ts +15 -1
  18. package/build/services/role/index.js +45 -1
  19. package/build/services/role/sql.d.ts +23 -2
  20. package/build/services/role/sql.js +93 -16
  21. package/build/services/role/types.d.ts +41 -0
  22. package/build/system/mcp-servers/admin/index.js +3 -3
  23. package/build/system/mcp-servers/admin/schemas.d.ts +25 -2
  24. package/build/system/mcp-servers/admin/schemas.js +11 -2
  25. package/build/system/mcp-servers/admin/users.js +42 -0
  26. package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -0
  27. package/build/system/mcp-servers/human-queue-schemas.js +4 -0
  28. package/build/system/mcp-servers/human-queue.js +10 -2
  29. package/build/system/seed/tool-manifests-admin.d.ts +371 -0
  30. package/build/system/seed/tool-manifests-admin.js +3 -1
  31. package/build/tsconfig.tsbuildinfo +1 -1
  32. package/build/types/escalation.d.ts +16 -0
  33. package/build/types/escalation.js +17 -0
  34. package/dashboard/dist/assets/{AdminDashboard-Bg1HgukI.js → AdminDashboard-rZDVS2G_.js} +2 -2
  35. package/dashboard/dist/assets/{AdminDashboard-Bg1HgukI.js.map → AdminDashboard-rZDVS2G_.js.map} +1 -1
  36. package/dashboard/dist/assets/{AgentConfigPage-HBe_YDU_.js → AgentConfigPage-CbyyCjCT.js} +2 -2
  37. package/dashboard/dist/assets/{AgentConfigPage-HBe_YDU_.js.map → AgentConfigPage-CbyyCjCT.js.map} +1 -1
  38. package/dashboard/dist/assets/{AgentDetailPage-BsaiYbwL.js → AgentDetailPage-DMeHvidT.js} +2 -2
  39. package/dashboard/dist/assets/{AgentDetailPage-BsaiYbwL.js.map → AgentDetailPage-DMeHvidT.js.map} +1 -1
  40. package/dashboard/dist/assets/{AgentsPage-DhN2lIBh.js → AgentsPage-C8fKLxh-.js} +2 -2
  41. package/dashboard/dist/assets/{AgentsPage-DhN2lIBh.js.map → AgentsPage-C8fKLxh-.js.map} +1 -1
  42. package/dashboard/dist/assets/{AvailableEscalationsPage-_xcWvlIz.js → AvailableEscalationsPage-CUHcybKU.js} +2 -2
  43. package/dashboard/dist/assets/{AvailableEscalationsPage-_xcWvlIz.js.map → AvailableEscalationsPage-CUHcybKU.js.map} +1 -1
  44. package/dashboard/dist/assets/{BotPicker-DY6iUVC9.js → BotPicker-DGhniNcy.js} +2 -2
  45. package/dashboard/dist/assets/{BotPicker-DY6iUVC9.js.map → BotPicker-DGhniNcy.js.map} +1 -1
  46. package/dashboard/dist/assets/{CapabilitiesPage-w1owqsmN.js → CapabilitiesPage-ClfuW4ya.js} +2 -2
  47. package/dashboard/dist/assets/{CapabilitiesPage-w1owqsmN.js.map → CapabilitiesPage-ClfuW4ya.js.map} +1 -1
  48. package/dashboard/dist/assets/{CollapsibleSection-DIMGjlJ-.js → CollapsibleSection-DnECdKEY.js} +2 -2
  49. package/dashboard/dist/assets/{CollapsibleSection-DIMGjlJ-.js.map → CollapsibleSection-DnECdKEY.js.map} +1 -1
  50. package/dashboard/dist/assets/{CredentialsPage-D1OmMHux.js → CredentialsPage-DdZ-jyyc.js} +2 -2
  51. package/dashboard/dist/assets/{CredentialsPage-D1OmMHux.js.map → CredentialsPage-DdZ-jyyc.js.map} +1 -1
  52. package/dashboard/dist/assets/{CronLabel-CQy3DrUQ.js → CronLabel-CymxLvY0.js} +2 -2
  53. package/dashboard/dist/assets/{CronLabel-CQy3DrUQ.js.map → CronLabel-CymxLvY0.js.map} +1 -1
  54. package/dashboard/dist/assets/{CustomDurationPicker-BoBkl9tw.js → CustomDurationPicker-8JHvVWD2.js} +2 -2
  55. package/dashboard/dist/assets/{CustomDurationPicker-BoBkl9tw.js.map → CustomDurationPicker-8JHvVWD2.js.map} +1 -1
  56. package/dashboard/dist/assets/{DropZone-QXbiuU0b.js → DropZone-DEX0BBqt.js} +2 -2
  57. package/dashboard/dist/assets/{DropZone-QXbiuU0b.js.map → DropZone-DEX0BBqt.js.map} +1 -1
  58. package/dashboard/dist/assets/{ElapsedCell-Donjm9gT.js → ElapsedCell-BRZfw_Af.js} +2 -2
  59. package/dashboard/dist/assets/{ElapsedCell-Donjm9gT.js.map → ElapsedCell-BRZfw_Af.js.map} +1 -1
  60. package/dashboard/dist/assets/EscalationSchemaPage-Do2iPKUl.js +9 -0
  61. package/dashboard/dist/assets/EscalationSchemaPage-Do2iPKUl.js.map +1 -0
  62. package/dashboard/dist/assets/{EscalationsOverview-CvHIJ_GG.js → EscalationsOverview-CiQO3ntf.js} +2 -2
  63. package/dashboard/dist/assets/{EscalationsOverview-CvHIJ_GG.js.map → EscalationsOverview-CiQO3ntf.js.map} +1 -1
  64. package/dashboard/dist/assets/{EventTable-DpAXMmT3.js → EventTable-CLuAbba_.js} +2 -2
  65. package/dashboard/dist/assets/{EventTable-DpAXMmT3.js.map → EventTable-CLuAbba_.js.map} +1 -1
  66. package/dashboard/dist/assets/{GraphInvokePage-De1XVx5d.js → GraphInvokePage-BFRi6Nny.js} +2 -2
  67. package/dashboard/dist/assets/{GraphInvokePage-De1XVx5d.js.map → GraphInvokePage-BFRi6Nny.js.map} +1 -1
  68. package/dashboard/dist/assets/{HomePage-BYR5VXXw.js → HomePage-m1pnxilb.js} +2 -2
  69. package/dashboard/dist/assets/{HomePage-BYR5VXXw.js.map → HomePage-m1pnxilb.js.map} +1 -1
  70. package/dashboard/dist/assets/{ListToolbar-Bx7jGzPA.js → ListToolbar-D4Y-M7wN.js} +2 -2
  71. package/dashboard/dist/assets/{ListToolbar-Bx7jGzPA.js.map → ListToolbar-D4Y-M7wN.js.map} +1 -1
  72. package/dashboard/dist/assets/{McpOverview-DXRKcJN0.js → McpOverview-mfJpETsW.js} +2 -2
  73. package/dashboard/dist/assets/{McpOverview-DXRKcJN0.js.map → McpOverview-mfJpETsW.js.map} +1 -1
  74. package/dashboard/dist/assets/{McpQueryDetailPage-CmW15w5e.js → McpQueryDetailPage-Cb99vI7v.js} +2 -2
  75. package/dashboard/dist/assets/{McpQueryDetailPage-CmW15w5e.js.map → McpQueryDetailPage-Cb99vI7v.js.map} +1 -1
  76. package/dashboard/dist/assets/{McpQueryPage-BE6I011z.js → McpQueryPage-CKKOxks0.js} +2 -2
  77. package/dashboard/dist/assets/{McpQueryPage-BE6I011z.js.map → McpQueryPage-CKKOxks0.js.map} +1 -1
  78. package/dashboard/dist/assets/{McpRunDetailPage-Dsa3G3XQ.js → McpRunDetailPage-CyTbHs0J.js} +2 -2
  79. package/dashboard/dist/assets/{McpRunDetailPage-Dsa3G3XQ.js.map → McpRunDetailPage-CyTbHs0J.js.map} +1 -1
  80. package/dashboard/dist/assets/{McpRunsPage-uTbym66Y.js → McpRunsPage-MIwmK-Ky.js} +2 -2
  81. package/dashboard/dist/assets/{McpRunsPage-uTbym66Y.js.map → McpRunsPage-MIwmK-Ky.js.map} +1 -1
  82. package/dashboard/dist/assets/{NamespacePill-BGf0jHwE.js → NamespacePill-CwKdoyRM.js} +2 -2
  83. package/dashboard/dist/assets/{NamespacePill-BGf0jHwE.js.map → NamespacePill-CwKdoyRM.js.map} +1 -1
  84. package/dashboard/dist/assets/OperationsPage-DPesph0U.js +2 -0
  85. package/dashboard/dist/assets/OperationsPage-DPesph0U.js.map +1 -0
  86. package/dashboard/dist/assets/{OperatorDashboard-i8cxaDlk.js → OperatorDashboard-K1gzULr7.js} +2 -2
  87. package/dashboard/dist/assets/{OperatorDashboard-i8cxaDlk.js.map → OperatorDashboard-K1gzULr7.js.map} +1 -1
  88. package/dashboard/dist/assets/{PageHeader-BZJsEVOC.js → PageHeader-BhaWTWcM.js} +2 -2
  89. package/dashboard/dist/assets/{PageHeader-BZJsEVOC.js.map → PageHeader-BhaWTWcM.js.map} +1 -1
  90. package/dashboard/dist/assets/{PageHeaderWithStats-BWuD7s9c.js → PageHeaderWithStats-yex5WmHB.js} +2 -2
  91. package/dashboard/dist/assets/{PageHeaderWithStats-BWuD7s9c.js.map → PageHeaderWithStats-yex5WmHB.js.map} +1 -1
  92. package/dashboard/dist/assets/{ProcessDetailPage-CP7GZhgs.js → ProcessDetailPage-DBIrY6Vd.js} +2 -2
  93. package/dashboard/dist/assets/{ProcessDetailPage-CP7GZhgs.js.map → ProcessDetailPage-DBIrY6Vd.js.map} +1 -1
  94. package/dashboard/dist/assets/{ProcessesListPage-btsU6hBR.js → ProcessesListPage-CrLGDCAD.js} +2 -2
  95. package/dashboard/dist/assets/{ProcessesListPage-btsU6hBR.js.map → ProcessesListPage-CrLGDCAD.js.map} +1 -1
  96. package/dashboard/dist/assets/RoleDetailPage-7j6Mk780.js +11 -0
  97. package/dashboard/dist/assets/RoleDetailPage-7j6Mk780.js.map +1 -0
  98. package/dashboard/dist/assets/{RolePill-Pb9TKSQN.js → RolePill-Cl71eDUA.js} +2 -2
  99. package/dashboard/dist/assets/{RolePill-Pb9TKSQN.js.map → RolePill-Cl71eDUA.js.map} +1 -1
  100. package/dashboard/dist/assets/RolesPage-B0ZgjB2V.js +2 -0
  101. package/dashboard/dist/assets/{RolesPage-1DBoEMsb.js.map → RolesPage-B0ZgjB2V.js.map} +1 -1
  102. package/dashboard/dist/assets/{RunAsSelector-Cp6j3FEY.js → RunAsSelector-DA0h41ml.js} +2 -2
  103. package/dashboard/dist/assets/{RunAsSelector-Cp6j3FEY.js.map → RunAsSelector-DA0h41ml.js.map} +1 -1
  104. package/dashboard/dist/assets/{StickyPagination-Pr8kawv8.js → StickyPagination-t5B71hwd.js} +2 -2
  105. package/dashboard/dist/assets/{StickyPagination-Pr8kawv8.js.map → StickyPagination-t5B71hwd.js.map} +1 -1
  106. package/dashboard/dist/assets/{StreamMessageDetail-BqmQ-N3y.js → StreamMessageDetail-CYFnZsYA.js} +2 -2
  107. package/dashboard/dist/assets/{StreamMessageDetail-BqmQ-N3y.js.map → StreamMessageDetail-CYFnZsYA.js.map} +1 -1
  108. package/dashboard/dist/assets/{SwimlaneTimeline-y1YmXrsj.js → SwimlaneTimeline-CY18gBek.js} +2 -2
  109. package/dashboard/dist/assets/{SwimlaneTimeline-y1YmXrsj.js.map → SwimlaneTimeline-CY18gBek.js.map} +1 -1
  110. package/dashboard/dist/assets/{TagInput-Df4myuYF.js → TagInput-B_WQiuer.js} +2 -2
  111. package/dashboard/dist/assets/{TagInput-Df4myuYF.js.map → TagInput-B_WQiuer.js.map} +1 -1
  112. package/dashboard/dist/assets/{TaskDetailPage-CT5rXHK5.js → TaskDetailPage-B9owzuhV.js} +2 -2
  113. package/dashboard/dist/assets/{TaskDetailPage-CT5rXHK5.js.map → TaskDetailPage-B9owzuhV.js.map} +1 -1
  114. package/dashboard/dist/assets/{TaskQueuePill-YalYdCNo.js → TaskQueuePill-Lgn2obNU.js} +2 -2
  115. package/dashboard/dist/assets/{TaskQueuePill-YalYdCNo.js.map → TaskQueuePill-Lgn2obNU.js.map} +1 -1
  116. package/dashboard/dist/assets/{TasksListPage-DOy6JlKS.js → TasksListPage-Dlsu7cWG.js} +2 -2
  117. package/dashboard/dist/assets/{TasksListPage-DOy6JlKS.js.map → TasksListPage-Dlsu7cWG.js.map} +1 -1
  118. package/dashboard/dist/assets/{TimeAgo-BAQwZSpq.js → TimeAgo-CSzifQXB.js} +2 -2
  119. package/dashboard/dist/assets/{TimeAgo-BAQwZSpq.js.map → TimeAgo-CSzifQXB.js.map} +1 -1
  120. package/dashboard/dist/assets/{TimestampCell-BK_jufns.js → TimestampCell-CIxgN57O.js} +2 -2
  121. package/dashboard/dist/assets/{TimestampCell-BK_jufns.js.map → TimestampCell-CIxgN57O.js.map} +1 -1
  122. package/dashboard/dist/assets/{ToolPill-VGpT6Quy.js → ToolPill-uVqcbHPE.js} +2 -2
  123. package/dashboard/dist/assets/{ToolPill-VGpT6Quy.js.map → ToolPill-uVqcbHPE.js.map} +1 -1
  124. package/dashboard/dist/assets/{ToolTestPanel-5PMQyUCe.js → ToolTestPanel-Bw844EW3.js} +2 -2
  125. package/dashboard/dist/assets/{ToolTestPanel-5PMQyUCe.js.map → ToolTestPanel-Bw844EW3.js.map} +1 -1
  126. package/dashboard/dist/assets/{TopicDetailPage-Cn1ShfR8.js → TopicDetailPage-HuDx2oAW.js} +2 -2
  127. package/dashboard/dist/assets/{TopicDetailPage-Cn1ShfR8.js.map → TopicDetailPage-HuDx2oAW.js.map} +1 -1
  128. package/dashboard/dist/assets/{TopicsPage-D67tjS8L.js → TopicsPage-CAG0RhAK.js} +2 -2
  129. package/dashboard/dist/assets/{TopicsPage-D67tjS8L.js.map → TopicsPage-CAG0RhAK.js.map} +1 -1
  130. package/dashboard/dist/assets/{UserName-Dsu1Jk4N.js → UserName-CoGYeig8.js} +2 -2
  131. package/dashboard/dist/assets/{UserName-Dsu1Jk4N.js.map → UserName-CoGYeig8.js.map} +1 -1
  132. package/dashboard/dist/assets/{WorkflowExecutionPage-BwQoOmUE.js → WorkflowExecutionPage-Dhb9EWgI.js} +2 -2
  133. package/dashboard/dist/assets/{WorkflowExecutionPage-BwQoOmUE.js.map → WorkflowExecutionPage-Dhb9EWgI.js.map} +1 -1
  134. package/dashboard/dist/assets/{WorkflowPill-pPUqmx6Q.js → WorkflowPill-IDjTRfQ8.js} +2 -2
  135. package/dashboard/dist/assets/{WorkflowPill-pPUqmx6Q.js.map → WorkflowPill-IDjTRfQ8.js.map} +1 -1
  136. package/dashboard/dist/assets/{WorkflowsDashboard-D3z7COgH.js → WorkflowsDashboard-bJ6Zebfv.js} +2 -2
  137. package/dashboard/dist/assets/{WorkflowsDashboard-D3z7COgH.js.map → WorkflowsDashboard-bJ6Zebfv.js.map} +1 -1
  138. package/dashboard/dist/assets/{WorkflowsOverview-C15JcxyY.js → WorkflowsOverview-B8262fVx.js} +2 -2
  139. package/dashboard/dist/assets/{WorkflowsOverview-C15JcxyY.js.map → WorkflowsOverview-B8262fVx.js.map} +1 -1
  140. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BYq3t7E-.js → YamlWorkflowDetailPage-DhW3q2cO.js} +2 -2
  141. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BYq3t7E-.js.map → YamlWorkflowDetailPage-DhW3q2cO.js.map} +1 -1
  142. package/dashboard/dist/assets/{YamlWorkflowsPage-B44WgwE_.js → YamlWorkflowsPage-C8hBjSn_.js} +2 -2
  143. package/dashboard/dist/assets/{YamlWorkflowsPage-B44WgwE_.js.map → YamlWorkflowsPage-C8hBjSn_.js.map} +1 -1
  144. package/dashboard/dist/assets/{agents-BRmmK6Y9.js → agents-j2YErVp4.js} +2 -2
  145. package/dashboard/dist/assets/{agents-BRmmK6Y9.js.map → agents-j2YErVp4.js.map} +1 -1
  146. package/dashboard/dist/assets/{bots-BD75mzGG.js → bots-CD8Bl5pL.js} +2 -2
  147. package/dashboard/dist/assets/{bots-BD75mzGG.js.map → bots-CD8Bl5pL.js.map} +1 -1
  148. package/dashboard/dist/assets/{capabilities-DDgC_Kds.js → capabilities-Dz190EAa.js} +2 -2
  149. package/dashboard/dist/assets/{capabilities-DDgC_Kds.js.map → capabilities-Dz190EAa.js.map} +1 -1
  150. package/dashboard/dist/assets/{controlplane-BJh12lMv.js → controlplane-CkUHGPA3.js} +2 -2
  151. package/dashboard/dist/assets/{controlplane-BJh12lMv.js.map → controlplane-CkUHGPA3.js.map} +1 -1
  152. package/dashboard/dist/assets/{escalation-DIJt2sgn.js → escalation-BXTwi4J1.js} +2 -2
  153. package/dashboard/dist/assets/{escalation-DIJt2sgn.js.map → escalation-BXTwi4J1.js.map} +1 -1
  154. package/dashboard/dist/assets/{escalation-columns-zl6E94Up.js → escalation-columns-GxxwSbDk.js} +2 -2
  155. package/dashboard/dist/assets/{escalation-columns-zl6E94Up.js.map → escalation-columns-GxxwSbDk.js.map} +1 -1
  156. package/dashboard/dist/assets/{index-DU7YumVJ.js → index-BK3PVYcd.js} +2 -2
  157. package/dashboard/dist/assets/{index-DU7YumVJ.js.map → index-BK3PVYcd.js.map} +1 -1
  158. package/dashboard/dist/assets/{index-CknKy4RA.js → index-BkwsJ-0v.js} +2 -2
  159. package/dashboard/dist/assets/{index-CknKy4RA.js.map → index-BkwsJ-0v.js.map} +1 -1
  160. package/dashboard/dist/assets/{index-B0kmiSmt.js → index-CaMVn0kW.js} +2 -2
  161. package/dashboard/dist/assets/{index-B0kmiSmt.js.map → index-CaMVn0kW.js.map} +1 -1
  162. package/dashboard/dist/assets/{index-BUbzfEA9.js → index-CkL8bpdH.js} +2 -2
  163. package/dashboard/dist/assets/{index-BUbzfEA9.js.map → index-CkL8bpdH.js.map} +1 -1
  164. package/dashboard/dist/assets/index-CoGkgm9-.js +6 -0
  165. package/dashboard/dist/assets/{index-DccCwyZl.js.map → index-CoGkgm9-.js.map} +1 -1
  166. package/dashboard/dist/assets/{index-CtDf8vvQ.js → index-D-7W2AHB.js} +9 -9
  167. package/dashboard/dist/assets/index-D-7W2AHB.js.map +1 -0
  168. package/dashboard/dist/assets/{index-CV4lEd4I.js → index-DGKgXObc.js} +2 -2
  169. package/dashboard/dist/assets/{index-CV4lEd4I.js.map → index-DGKgXObc.js.map} +1 -1
  170. package/dashboard/dist/assets/{index-DVVIeBBk.js → index-DTrcYOM2.js} +2 -2
  171. package/dashboard/dist/assets/{index-DVVIeBBk.js.map → index-DTrcYOM2.js.map} +1 -1
  172. package/dashboard/dist/assets/{index-BhznDv-4.js → index-NfVWOFbF.js} +2 -2
  173. package/dashboard/dist/assets/{index-BhznDv-4.js.map → index-NfVWOFbF.js.map} +1 -1
  174. package/dashboard/dist/assets/index-Wny0bRgq.css +1 -0
  175. package/dashboard/dist/assets/{index-DrUkTcn-.js → index-YliQ4kW_.js} +2 -2
  176. package/dashboard/dist/assets/{index-DrUkTcn-.js.map → index-YliQ4kW_.js.map} +1 -1
  177. package/dashboard/dist/assets/{index-CpgV4Kkp.js → index-eLRh-2NW.js} +2 -2
  178. package/dashboard/dist/assets/{index-CpgV4Kkp.js.map → index-eLRh-2NW.js.map} +1 -1
  179. package/dashboard/dist/assets/{index-CDPBXB4e.js → index-n-lZY5S6.js} +2 -2
  180. package/dashboard/dist/assets/{index-CDPBXB4e.js.map → index-n-lZY5S6.js.map} +1 -1
  181. package/dashboard/dist/assets/{index-j-C7g_Ej.js → index-qT7eB_Hk.js} +2 -2
  182. package/dashboard/dist/assets/{index-j-C7g_Ej.js.map → index-qT7eB_Hk.js.map} +1 -1
  183. package/dashboard/dist/assets/{knowledge-pNDWACXB.js → knowledge-CSAbLtk7.js} +2 -2
  184. package/dashboard/dist/assets/{knowledge-pNDWACXB.js.map → knowledge-CSAbLtk7.js.map} +1 -1
  185. package/dashboard/dist/assets/{mcp-xddLBwUW.js → mcp-D0Ha3aEo.js} +2 -2
  186. package/dashboard/dist/assets/{mcp-xddLBwUW.js.map → mcp-D0Ha3aEo.js.map} +1 -1
  187. package/dashboard/dist/assets/{mcp-query-CmNjvxTG.js → mcp-query-BySmvmuS.js} +2 -2
  188. package/dashboard/dist/assets/{mcp-query-CmNjvxTG.js.map → mcp-query-BySmvmuS.js.map} +1 -1
  189. package/dashboard/dist/assets/{pipelines-B05sd2nB.js → pipelines-DiqWgxJd.js} +2 -2
  190. package/dashboard/dist/assets/{pipelines-B05sd2nB.js.map → pipelines-DiqWgxJd.js.map} +1 -1
  191. package/dashboard/dist/assets/roles-BdtnzLsL.js +2 -0
  192. package/dashboard/dist/assets/roles-BdtnzLsL.js.map +1 -0
  193. package/dashboard/dist/assets/{tasks-Bm1OKrJA.js → tasks-DYCUi5nZ.js} +2 -2
  194. package/dashboard/dist/assets/{tasks-Bm1OKrJA.js.map → tasks-DYCUi5nZ.js.map} +1 -1
  195. package/dashboard/dist/assets/{topics-BVIqtmd4.js → topics-CA7i8ymr.js} +2 -2
  196. package/dashboard/dist/assets/{topics-BVIqtmd4.js.map → topics-CA7i8ymr.js.map} +1 -1
  197. package/dashboard/dist/assets/{useEventHooks-_qCzU74s.js → useEventHooks-SfWdyc5L.js} +2 -2
  198. package/dashboard/dist/assets/{useEventHooks-_qCzU74s.js.map → useEventHooks-SfWdyc5L.js.map} +1 -1
  199. package/dashboard/dist/assets/{useNamespace-CDnCSFpG.js → useNamespace-CZUsk5Vv.js} +2 -2
  200. package/dashboard/dist/assets/{useNamespace-CDnCSFpG.js.map → useNamespace-CZUsk5Vv.js.map} +1 -1
  201. package/dashboard/dist/assets/{useYamlActivityEvents-zrHMhaFM.js → useYamlActivityEvents-DKQ5dREx.js} +2 -2
  202. package/dashboard/dist/assets/{useYamlActivityEvents-zrHMhaFM.js.map → useYamlActivityEvents-DKQ5dREx.js.map} +1 -1
  203. package/dashboard/dist/assets/{users-BPBMZg27.js → users-BxKf5dAX.js} +2 -2
  204. package/dashboard/dist/assets/{users-BPBMZg27.js.map → users-BxKf5dAX.js.map} +1 -1
  205. package/dashboard/dist/assets/{vendor-icons-C1zVP_dv.js → vendor-icons-CEWCGPeJ.js} +76 -76
  206. package/dashboard/dist/assets/vendor-icons-CEWCGPeJ.js.map +1 -0
  207. package/dashboard/dist/assets/{workflows-CT6nTL1I.js → workflows-DNPQ-j-7.js} +2 -2
  208. package/dashboard/dist/assets/{workflows-CT6nTL1I.js.map → workflows-DNPQ-j-7.js.map} +1 -1
  209. package/dashboard/dist/assets/{yaml-workflows-CdfjkOKU.js → yaml-workflows-CA8BrFxc.js} +2 -2
  210. package/dashboard/dist/assets/{yaml-workflows-CdfjkOKU.js.map → yaml-workflows-CA8BrFxc.js.map} +1 -1
  211. package/dashboard/dist/index.html +3 -3
  212. package/docs/api/http/roles.md +62 -0
  213. package/docs/api/mcp/admin.md +32 -1
  214. package/docs/api/mcp/human-queue.md +2 -0
  215. package/docs/api/sdk/roles.md +39 -0
  216. package/docs/cli.md +13 -0
  217. package/docs/dashboard.md +13 -6
  218. package/docs/data.md +34 -0
  219. package/docs/hitl-guide.md +25 -3
  220. package/package.json +2 -2
  221. package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js +0 -2
  222. package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js.map +0 -1
  223. package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js +0 -16
  224. package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js.map +0 -1
  225. package/dashboard/dist/assets/RolesPage-1DBoEMsb.js +0 -2
  226. package/dashboard/dist/assets/index-BRtKQtQZ.css +0 -1
  227. package/dashboard/dist/assets/index-CtDf8vvQ.js.map +0 -1
  228. package/dashboard/dist/assets/index-DccCwyZl.js +0 -6
  229. package/dashboard/dist/assets/roles-bRgaMcdy.js +0 -2
  230. package/dashboard/dist/assets/roles-bRgaMcdy.js.map +0 -1
  231. 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=5 HOLD_S=0 POLL_MS=500 npx ts-node scripts/ortho-populate.ts
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 — number of orders to run (default 20)
17
- * HOLD_S — seconds to hold each escalation before resolving (default 2)
18
- * POLL_MS — poll interval ms (default 1000)
19
- * BASE_URL server URL (default http://localhost:3000)
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
- // ── Claim: grab available ortho-stage escalations for this run ────────────────
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 (!ORTHO_ROLES.includes(esc.role))
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(8)} ${esc.id.slice(0, 8)}… [${totalClaimed} total]`);
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 (!ORTHO_ROLES.includes(esc.role))
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 < HOLD_S)
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(8)} ${esc.id.slice(0, 8)}… held ${heldS.toFixed(1)}s [${totalResolved} total]`);
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
- // ── Run all orders through all 8 stages ───────────────────────────────────────
128
- async function runOrders(batchTag, target) {
257
+ async function runOrders(batchTag, runId, plan, target) {
129
258
  let done = false;
130
- // Claim loop — runs continuously until done.
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
- const STALL_S = Math.max(30, HOLD_S * 6);
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 workflowIds = [];
184
- // Enqueue all orders.
185
- console.log(`[${ts()}] Enqueuing ${ORDERS} orders…`);
186
- for (let i = 0; i < ORDERS; i++) {
187
- const wfId = `${batchTag}-${i}`;
188
- try {
189
- await api('POST', '/api/workflows/orthoPipeline/invoke', {
190
- workflowId: wfId,
191
- data: { order_id: `ORD-${runId}-${i}`, item_type: 'insole-standard' },
192
- metadata: { source: 'populate', run_id: runId },
193
- });
194
- workflowIds.push(wfId);
195
- console.log(`[${ts()}] ${i + 1}/${ORDERS} → ${wfId}`);
196
- }
197
- catch (err) {
198
- console.error(`[${ts()}] ${i + 1}/${ORDERS} FAILED: ${err.message}`);
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}`);
@@ -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;
@@ -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,
@@ -29,18 +29,6 @@ async function handleCompletion(state, result) {
29
29
  ],
30
30
  }
31
31
  : result;
32
- // Publish workflow.completed event
33
- (0, publish_1.publishWorkflowEvent)({
34
- type: 'workflow.completed',
35
- source: 'interceptor',
36
- workflowId: state.workflowId,
37
- workflowName: state.workflowName,
38
- taskQueue: state.taskQueue,
39
- taskId: state.taskId,
40
- originId: state.envelope?.lt?.originId,
41
- status: 'completed',
42
- data: augmentedResult.data,
43
- });
44
32
  // Publish milestone event (non-durable side effect, fire-and-forget)
45
33
  if (augmentedResult.milestones?.length) {
46
34
  (0, publish_1.publishMilestoneEvent)({
@@ -82,6 +70,23 @@ async function handleCompletion(state, result) {
82
70
  taskQueue: state.taskQueue,
83
71
  });
84
72
  }
73
+ // Publish workflow.completed LAST — after every interrupting proxy call.
74
+ // This code is workflow code: each durable activity above interrupts the
75
+ // function on first dispatch and the whole leg replays, so a fire-and-forget
76
+ // publish placed before them fires once per replay (observed live as doubled
77
+ // workflow.completed events). Only the final, uninterrupted execution
78
+ // reaches this point, so the event publishes exactly once per completion.
79
+ (0, publish_1.publishWorkflowEvent)({
80
+ type: 'workflow.completed',
81
+ source: 'interceptor',
82
+ workflowId: state.workflowId,
83
+ workflowName: state.workflowName,
84
+ taskQueue: state.taskQueue,
85
+ taskId: state.taskId,
86
+ originId: state.envelope?.lt?.originId,
87
+ status: 'completed',
88
+ data: augmentedResult.data,
89
+ });
85
90
  return augmentedResult;
86
91
  }
87
92
  /**
@@ -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?: Types.ConditionQueueConfig): Promise<T | false | null>;
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
- return rows[0] ?? null;
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