@hotmeshio/long-tail 0.7.1 → 0.7.3

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 (229) 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/orchestrator/condition.d.ts +25 -1
  15. package/build/services/orchestrator/condition.js +29 -1
  16. package/build/services/role/index.d.ts +15 -1
  17. package/build/services/role/index.js +45 -1
  18. package/build/services/role/sql.d.ts +23 -2
  19. package/build/services/role/sql.js +93 -16
  20. package/build/services/role/types.d.ts +41 -0
  21. package/build/system/mcp-servers/admin/index.js +3 -3
  22. package/build/system/mcp-servers/admin/schemas.d.ts +25 -2
  23. package/build/system/mcp-servers/admin/schemas.js +11 -2
  24. package/build/system/mcp-servers/admin/users.js +42 -0
  25. package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -0
  26. package/build/system/mcp-servers/human-queue-schemas.js +4 -0
  27. package/build/system/mcp-servers/human-queue.js +10 -2
  28. package/build/system/seed/tool-manifests-admin.d.ts +371 -0
  29. package/build/system/seed/tool-manifests-admin.js +3 -1
  30. package/build/tsconfig.tsbuildinfo +1 -1
  31. package/build/types/escalation.d.ts +16 -0
  32. package/build/types/escalation.js +17 -0
  33. package/dashboard/dist/assets/{AdminDashboard-Bg1HgukI.js → AdminDashboard-C9eE1VEp.js} +2 -2
  34. package/dashboard/dist/assets/{AdminDashboard-Bg1HgukI.js.map → AdminDashboard-C9eE1VEp.js.map} +1 -1
  35. package/dashboard/dist/assets/{AgentConfigPage-HBe_YDU_.js → AgentConfigPage-Ba8QfXM2.js} +2 -2
  36. package/dashboard/dist/assets/{AgentConfigPage-HBe_YDU_.js.map → AgentConfigPage-Ba8QfXM2.js.map} +1 -1
  37. package/dashboard/dist/assets/{AgentDetailPage-BsaiYbwL.js → AgentDetailPage-DVmec0CP.js} +2 -2
  38. package/dashboard/dist/assets/{AgentDetailPage-BsaiYbwL.js.map → AgentDetailPage-DVmec0CP.js.map} +1 -1
  39. package/dashboard/dist/assets/{AgentsPage-DhN2lIBh.js → AgentsPage-DNiCYMh3.js} +2 -2
  40. package/dashboard/dist/assets/{AgentsPage-DhN2lIBh.js.map → AgentsPage-DNiCYMh3.js.map} +1 -1
  41. package/dashboard/dist/assets/{AvailableEscalationsPage-_xcWvlIz.js → AvailableEscalationsPage-gDIF8ocC.js} +2 -2
  42. package/dashboard/dist/assets/{AvailableEscalationsPage-_xcWvlIz.js.map → AvailableEscalationsPage-gDIF8ocC.js.map} +1 -1
  43. package/dashboard/dist/assets/{BotPicker-DY6iUVC9.js → BotPicker-3wBcBavT.js} +2 -2
  44. package/dashboard/dist/assets/{BotPicker-DY6iUVC9.js.map → BotPicker-3wBcBavT.js.map} +1 -1
  45. package/dashboard/dist/assets/{CapabilitiesPage-w1owqsmN.js → CapabilitiesPage-WoPkND2y.js} +2 -2
  46. package/dashboard/dist/assets/{CapabilitiesPage-w1owqsmN.js.map → CapabilitiesPage-WoPkND2y.js.map} +1 -1
  47. package/dashboard/dist/assets/{CollapsibleSection-DIMGjlJ-.js → CollapsibleSection-Cx99mqNN.js} +2 -2
  48. package/dashboard/dist/assets/{CollapsibleSection-DIMGjlJ-.js.map → CollapsibleSection-Cx99mqNN.js.map} +1 -1
  49. package/dashboard/dist/assets/{CredentialsPage-D1OmMHux.js → CredentialsPage-DbNaoPbF.js} +2 -2
  50. package/dashboard/dist/assets/{CredentialsPage-D1OmMHux.js.map → CredentialsPage-DbNaoPbF.js.map} +1 -1
  51. package/dashboard/dist/assets/{CronLabel-CQy3DrUQ.js → CronLabel-DLW4_Zg4.js} +2 -2
  52. package/dashboard/dist/assets/{CronLabel-CQy3DrUQ.js.map → CronLabel-DLW4_Zg4.js.map} +1 -1
  53. package/dashboard/dist/assets/{CustomDurationPicker-BoBkl9tw.js → CustomDurationPicker-BRUFjkiu.js} +2 -2
  54. package/dashboard/dist/assets/{CustomDurationPicker-BoBkl9tw.js.map → CustomDurationPicker-BRUFjkiu.js.map} +1 -1
  55. package/dashboard/dist/assets/{DropZone-QXbiuU0b.js → DropZone-DEX0BBqt.js} +2 -2
  56. package/dashboard/dist/assets/{DropZone-QXbiuU0b.js.map → DropZone-DEX0BBqt.js.map} +1 -1
  57. package/dashboard/dist/assets/{ElapsedCell-Donjm9gT.js → ElapsedCell-BtmDQaym.js} +2 -2
  58. package/dashboard/dist/assets/{ElapsedCell-Donjm9gT.js.map → ElapsedCell-BtmDQaym.js.map} +1 -1
  59. package/dashboard/dist/assets/EscalationSchemaPage-Bm4yBEGN.js +9 -0
  60. package/dashboard/dist/assets/EscalationSchemaPage-Bm4yBEGN.js.map +1 -0
  61. package/dashboard/dist/assets/{EscalationsOverview-CvHIJ_GG.js → EscalationsOverview-B3xZwTQA.js} +2 -2
  62. package/dashboard/dist/assets/{EscalationsOverview-CvHIJ_GG.js.map → EscalationsOverview-B3xZwTQA.js.map} +1 -1
  63. package/dashboard/dist/assets/{EventTable-DpAXMmT3.js → EventTable-Bh9FKXLI.js} +2 -2
  64. package/dashboard/dist/assets/{EventTable-DpAXMmT3.js.map → EventTable-Bh9FKXLI.js.map} +1 -1
  65. package/dashboard/dist/assets/{GraphInvokePage-De1XVx5d.js → GraphInvokePage-ov_ANlaE.js} +2 -2
  66. package/dashboard/dist/assets/{GraphInvokePage-De1XVx5d.js.map → GraphInvokePage-ov_ANlaE.js.map} +1 -1
  67. package/dashboard/dist/assets/{HomePage-BYR5VXXw.js → HomePage-D0DV6izb.js} +2 -2
  68. package/dashboard/dist/assets/{HomePage-BYR5VXXw.js.map → HomePage-D0DV6izb.js.map} +1 -1
  69. package/dashboard/dist/assets/{ListToolbar-Bx7jGzPA.js → ListToolbar-FpcvqNkN.js} +2 -2
  70. package/dashboard/dist/assets/{ListToolbar-Bx7jGzPA.js.map → ListToolbar-FpcvqNkN.js.map} +1 -1
  71. package/dashboard/dist/assets/{McpOverview-DXRKcJN0.js → McpOverview-DcuRiAff.js} +2 -2
  72. package/dashboard/dist/assets/{McpOverview-DXRKcJN0.js.map → McpOverview-DcuRiAff.js.map} +1 -1
  73. package/dashboard/dist/assets/{McpQueryDetailPage-CmW15w5e.js → McpQueryDetailPage-BcYl67KP.js} +2 -2
  74. package/dashboard/dist/assets/{McpQueryDetailPage-CmW15w5e.js.map → McpQueryDetailPage-BcYl67KP.js.map} +1 -1
  75. package/dashboard/dist/assets/{McpQueryPage-BE6I011z.js → McpQueryPage-C3GDzqZM.js} +2 -2
  76. package/dashboard/dist/assets/{McpQueryPage-BE6I011z.js.map → McpQueryPage-C3GDzqZM.js.map} +1 -1
  77. package/dashboard/dist/assets/{McpRunDetailPage-Dsa3G3XQ.js → McpRunDetailPage-SzOyhDBW.js} +2 -2
  78. package/dashboard/dist/assets/{McpRunDetailPage-Dsa3G3XQ.js.map → McpRunDetailPage-SzOyhDBW.js.map} +1 -1
  79. package/dashboard/dist/assets/{McpRunsPage-uTbym66Y.js → McpRunsPage-BwgB6mRV.js} +2 -2
  80. package/dashboard/dist/assets/{McpRunsPage-uTbym66Y.js.map → McpRunsPage-BwgB6mRV.js.map} +1 -1
  81. package/dashboard/dist/assets/{NamespacePill-BGf0jHwE.js → NamespacePill-CwKdoyRM.js} +2 -2
  82. package/dashboard/dist/assets/{NamespacePill-BGf0jHwE.js.map → NamespacePill-CwKdoyRM.js.map} +1 -1
  83. package/dashboard/dist/assets/OperationsPage-BCr21p8I.js +2 -0
  84. package/dashboard/dist/assets/OperationsPage-BCr21p8I.js.map +1 -0
  85. package/dashboard/dist/assets/{OperatorDashboard-i8cxaDlk.js → OperatorDashboard-DSXh-yYw.js} +2 -2
  86. package/dashboard/dist/assets/{OperatorDashboard-i8cxaDlk.js.map → OperatorDashboard-DSXh-yYw.js.map} +1 -1
  87. package/dashboard/dist/assets/{PageHeader-BZJsEVOC.js → PageHeader-BhaWTWcM.js} +2 -2
  88. package/dashboard/dist/assets/{PageHeader-BZJsEVOC.js.map → PageHeader-BhaWTWcM.js.map} +1 -1
  89. package/dashboard/dist/assets/{PageHeaderWithStats-BWuD7s9c.js → PageHeaderWithStats-yex5WmHB.js} +2 -2
  90. package/dashboard/dist/assets/{PageHeaderWithStats-BWuD7s9c.js.map → PageHeaderWithStats-yex5WmHB.js.map} +1 -1
  91. package/dashboard/dist/assets/{ProcessDetailPage-CP7GZhgs.js → ProcessDetailPage-iIB1rwEt.js} +2 -2
  92. package/dashboard/dist/assets/{ProcessDetailPage-CP7GZhgs.js.map → ProcessDetailPage-iIB1rwEt.js.map} +1 -1
  93. package/dashboard/dist/assets/{ProcessesListPage-btsU6hBR.js → ProcessesListPage-BrqWR2zr.js} +2 -2
  94. package/dashboard/dist/assets/{ProcessesListPage-btsU6hBR.js.map → ProcessesListPage-BrqWR2zr.js.map} +1 -1
  95. package/dashboard/dist/assets/RoleDetailPage-DS37tJhq.js +11 -0
  96. package/dashboard/dist/assets/RoleDetailPage-DS37tJhq.js.map +1 -0
  97. package/dashboard/dist/assets/{RolePill-Pb9TKSQN.js → RolePill-Cl71eDUA.js} +2 -2
  98. package/dashboard/dist/assets/{RolePill-Pb9TKSQN.js.map → RolePill-Cl71eDUA.js.map} +1 -1
  99. package/dashboard/dist/assets/{RolesPage-1DBoEMsb.js → RolesPage-Bzp9FhAl.js} +2 -2
  100. package/dashboard/dist/assets/{RolesPage-1DBoEMsb.js.map → RolesPage-Bzp9FhAl.js.map} +1 -1
  101. package/dashboard/dist/assets/{RunAsSelector-Cp6j3FEY.js → RunAsSelector-DXbHRxQs.js} +2 -2
  102. package/dashboard/dist/assets/{RunAsSelector-Cp6j3FEY.js.map → RunAsSelector-DXbHRxQs.js.map} +1 -1
  103. package/dashboard/dist/assets/{StickyPagination-Pr8kawv8.js → StickyPagination-D8XuMSWD.js} +2 -2
  104. package/dashboard/dist/assets/{StickyPagination-Pr8kawv8.js.map → StickyPagination-D8XuMSWD.js.map} +1 -1
  105. package/dashboard/dist/assets/{StreamMessageDetail-BqmQ-N3y.js → StreamMessageDetail-D5v95pmz.js} +2 -2
  106. package/dashboard/dist/assets/{StreamMessageDetail-BqmQ-N3y.js.map → StreamMessageDetail-D5v95pmz.js.map} +1 -1
  107. package/dashboard/dist/assets/{SwimlaneTimeline-y1YmXrsj.js → SwimlaneTimeline-CUEf2R5R.js} +2 -2
  108. package/dashboard/dist/assets/{SwimlaneTimeline-y1YmXrsj.js.map → SwimlaneTimeline-CUEf2R5R.js.map} +1 -1
  109. package/dashboard/dist/assets/{TagInput-Df4myuYF.js → TagInput-B_WQiuer.js} +2 -2
  110. package/dashboard/dist/assets/{TagInput-Df4myuYF.js.map → TagInput-B_WQiuer.js.map} +1 -1
  111. package/dashboard/dist/assets/{TaskDetailPage-CT5rXHK5.js → TaskDetailPage-BJvKNwEe.js} +2 -2
  112. package/dashboard/dist/assets/{TaskDetailPage-CT5rXHK5.js.map → TaskDetailPage-BJvKNwEe.js.map} +1 -1
  113. package/dashboard/dist/assets/{TaskQueuePill-YalYdCNo.js → TaskQueuePill-Lgn2obNU.js} +2 -2
  114. package/dashboard/dist/assets/{TaskQueuePill-YalYdCNo.js.map → TaskQueuePill-Lgn2obNU.js.map} +1 -1
  115. package/dashboard/dist/assets/{TasksListPage-DOy6JlKS.js → TasksListPage-CM7BWh_h.js} +2 -2
  116. package/dashboard/dist/assets/{TasksListPage-DOy6JlKS.js.map → TasksListPage-CM7BWh_h.js.map} +1 -1
  117. package/dashboard/dist/assets/{TimeAgo-BAQwZSpq.js → TimeAgo-D1K7xKq5.js} +2 -2
  118. package/dashboard/dist/assets/{TimeAgo-BAQwZSpq.js.map → TimeAgo-D1K7xKq5.js.map} +1 -1
  119. package/dashboard/dist/assets/{TimestampCell-BK_jufns.js → TimestampCell-7G0echgP.js} +2 -2
  120. package/dashboard/dist/assets/{TimestampCell-BK_jufns.js.map → TimestampCell-7G0echgP.js.map} +1 -1
  121. package/dashboard/dist/assets/{ToolPill-VGpT6Quy.js → ToolPill-uVqcbHPE.js} +2 -2
  122. package/dashboard/dist/assets/{ToolPill-VGpT6Quy.js.map → ToolPill-uVqcbHPE.js.map} +1 -1
  123. package/dashboard/dist/assets/{ToolTestPanel-5PMQyUCe.js → ToolTestPanel-LxuJvURK.js} +2 -2
  124. package/dashboard/dist/assets/{ToolTestPanel-5PMQyUCe.js.map → ToolTestPanel-LxuJvURK.js.map} +1 -1
  125. package/dashboard/dist/assets/{TopicDetailPage-Cn1ShfR8.js → TopicDetailPage-JvAX6rrR.js} +2 -2
  126. package/dashboard/dist/assets/{TopicDetailPage-Cn1ShfR8.js.map → TopicDetailPage-JvAX6rrR.js.map} +1 -1
  127. package/dashboard/dist/assets/{TopicsPage-D67tjS8L.js → TopicsPage-tFT2AcTz.js} +2 -2
  128. package/dashboard/dist/assets/{TopicsPage-D67tjS8L.js.map → TopicsPage-tFT2AcTz.js.map} +1 -1
  129. package/dashboard/dist/assets/{UserName-Dsu1Jk4N.js → UserName-Djz0nmaZ.js} +2 -2
  130. package/dashboard/dist/assets/{UserName-Dsu1Jk4N.js.map → UserName-Djz0nmaZ.js.map} +1 -1
  131. package/dashboard/dist/assets/{WorkflowExecutionPage-BwQoOmUE.js → WorkflowExecutionPage-f53hilbI.js} +2 -2
  132. package/dashboard/dist/assets/{WorkflowExecutionPage-BwQoOmUE.js.map → WorkflowExecutionPage-f53hilbI.js.map} +1 -1
  133. package/dashboard/dist/assets/{WorkflowPill-pPUqmx6Q.js → WorkflowPill-IDjTRfQ8.js} +2 -2
  134. package/dashboard/dist/assets/{WorkflowPill-pPUqmx6Q.js.map → WorkflowPill-IDjTRfQ8.js.map} +1 -1
  135. package/dashboard/dist/assets/{WorkflowsDashboard-D3z7COgH.js → WorkflowsDashboard-DM9Xe665.js} +2 -2
  136. package/dashboard/dist/assets/{WorkflowsDashboard-D3z7COgH.js.map → WorkflowsDashboard-DM9Xe665.js.map} +1 -1
  137. package/dashboard/dist/assets/{WorkflowsOverview-C15JcxyY.js → WorkflowsOverview-CUGSrx9N.js} +2 -2
  138. package/dashboard/dist/assets/{WorkflowsOverview-C15JcxyY.js.map → WorkflowsOverview-CUGSrx9N.js.map} +1 -1
  139. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BYq3t7E-.js → YamlWorkflowDetailPage-B1_yqxAw.js} +2 -2
  140. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BYq3t7E-.js.map → YamlWorkflowDetailPage-B1_yqxAw.js.map} +1 -1
  141. package/dashboard/dist/assets/{YamlWorkflowsPage-B44WgwE_.js → YamlWorkflowsPage-NtGvqiu7.js} +2 -2
  142. package/dashboard/dist/assets/{YamlWorkflowsPage-B44WgwE_.js.map → YamlWorkflowsPage-NtGvqiu7.js.map} +1 -1
  143. package/dashboard/dist/assets/{agents-BRmmK6Y9.js → agents-tML6AjDj.js} +2 -2
  144. package/dashboard/dist/assets/{agents-BRmmK6Y9.js.map → agents-tML6AjDj.js.map} +1 -1
  145. package/dashboard/dist/assets/{bots-BD75mzGG.js → bots-Cb0WTwhY.js} +2 -2
  146. package/dashboard/dist/assets/{bots-BD75mzGG.js.map → bots-Cb0WTwhY.js.map} +1 -1
  147. package/dashboard/dist/assets/{capabilities-DDgC_Kds.js → capabilities-BGZogsi_.js} +2 -2
  148. package/dashboard/dist/assets/{capabilities-DDgC_Kds.js.map → capabilities-BGZogsi_.js.map} +1 -1
  149. package/dashboard/dist/assets/{controlplane-BJh12lMv.js → controlplane-B7VJTlZD.js} +2 -2
  150. package/dashboard/dist/assets/{controlplane-BJh12lMv.js.map → controlplane-B7VJTlZD.js.map} +1 -1
  151. package/dashboard/dist/assets/{escalation-DIJt2sgn.js → escalation-Bl-SQV_u.js} +2 -2
  152. package/dashboard/dist/assets/{escalation-DIJt2sgn.js.map → escalation-Bl-SQV_u.js.map} +1 -1
  153. package/dashboard/dist/assets/{escalation-columns-zl6E94Up.js → escalation-columns-BlJOswRx.js} +2 -2
  154. package/dashboard/dist/assets/{escalation-columns-zl6E94Up.js.map → escalation-columns-BlJOswRx.js.map} +1 -1
  155. package/dashboard/dist/assets/{index-BhznDv-4.js → index--WeZDfeN.js} +2 -2
  156. package/dashboard/dist/assets/{index-BhznDv-4.js.map → index--WeZDfeN.js.map} +1 -1
  157. package/dashboard/dist/assets/{index-DU7YumVJ.js → index-0o71lRuY.js} +2 -2
  158. package/dashboard/dist/assets/{index-DU7YumVJ.js.map → index-0o71lRuY.js.map} +1 -1
  159. package/dashboard/dist/assets/{index-CV4lEd4I.js → index-3A_5CL81.js} +2 -2
  160. package/dashboard/dist/assets/{index-CV4lEd4I.js.map → index-3A_5CL81.js.map} +1 -1
  161. package/dashboard/dist/assets/{index-DVVIeBBk.js → index-BorpZbZA.js} +2 -2
  162. package/dashboard/dist/assets/{index-DVVIeBBk.js.map → index-BorpZbZA.js.map} +1 -1
  163. package/dashboard/dist/assets/{index-CknKy4RA.js → index-CBv3UxF2.js} +2 -2
  164. package/dashboard/dist/assets/{index-CknKy4RA.js.map → index-CBv3UxF2.js.map} +1 -1
  165. package/dashboard/dist/assets/{index-BUbzfEA9.js → index-D0-8byNu.js} +2 -2
  166. package/dashboard/dist/assets/{index-BUbzfEA9.js.map → index-D0-8byNu.js.map} +1 -1
  167. package/dashboard/dist/assets/{index-j-C7g_Ej.js → index-DcbEIXit.js} +2 -2
  168. package/dashboard/dist/assets/{index-j-C7g_Ej.js.map → index-DcbEIXit.js.map} +1 -1
  169. package/dashboard/dist/assets/{index-DrUkTcn-.js → index-DfDfJivY.js} +2 -2
  170. package/dashboard/dist/assets/{index-DrUkTcn-.js.map → index-DfDfJivY.js.map} +1 -1
  171. package/dashboard/dist/assets/{index-CtDf8vvQ.js → index-Dgw1KKpq.js} +9 -9
  172. package/dashboard/dist/assets/index-Dgw1KKpq.js.map +1 -0
  173. package/dashboard/dist/assets/index-Il_g7kw5.js +6 -0
  174. package/dashboard/dist/assets/{index-DccCwyZl.js.map → index-Il_g7kw5.js.map} +1 -1
  175. package/dashboard/dist/assets/{index-B0kmiSmt.js → index-SCrD3l-K.js} +2 -2
  176. package/dashboard/dist/assets/{index-B0kmiSmt.js.map → index-SCrD3l-K.js.map} +1 -1
  177. package/dashboard/dist/assets/{index-CDPBXB4e.js → index-TiKh2Ctj.js} +2 -2
  178. package/dashboard/dist/assets/{index-CDPBXB4e.js.map → index-TiKh2Ctj.js.map} +1 -1
  179. package/dashboard/dist/assets/index-Wny0bRgq.css +1 -0
  180. package/dashboard/dist/assets/{index-CpgV4Kkp.js → index-e97M6GoI.js} +2 -2
  181. package/dashboard/dist/assets/{index-CpgV4Kkp.js.map → index-e97M6GoI.js.map} +1 -1
  182. package/dashboard/dist/assets/{knowledge-pNDWACXB.js → knowledge-CDzw4E6q.js} +2 -2
  183. package/dashboard/dist/assets/{knowledge-pNDWACXB.js.map → knowledge-CDzw4E6q.js.map} +1 -1
  184. package/dashboard/dist/assets/{mcp-xddLBwUW.js → mcp-D_-NQtnG.js} +2 -2
  185. package/dashboard/dist/assets/{mcp-xddLBwUW.js.map → mcp-D_-NQtnG.js.map} +1 -1
  186. package/dashboard/dist/assets/{mcp-query-CmNjvxTG.js → mcp-query-BqZXdf9A.js} +2 -2
  187. package/dashboard/dist/assets/{mcp-query-CmNjvxTG.js.map → mcp-query-BqZXdf9A.js.map} +1 -1
  188. package/dashboard/dist/assets/{pipelines-B05sd2nB.js → pipelines-CNLhgj8b.js} +2 -2
  189. package/dashboard/dist/assets/{pipelines-B05sd2nB.js.map → pipelines-CNLhgj8b.js.map} +1 -1
  190. package/dashboard/dist/assets/roles-CFD2eYap.js +2 -0
  191. package/dashboard/dist/assets/roles-CFD2eYap.js.map +1 -0
  192. package/dashboard/dist/assets/{tasks-Bm1OKrJA.js → tasks-DcSvF636.js} +2 -2
  193. package/dashboard/dist/assets/{tasks-Bm1OKrJA.js.map → tasks-DcSvF636.js.map} +1 -1
  194. package/dashboard/dist/assets/{topics-BVIqtmd4.js → topics-DI8TFSZv.js} +2 -2
  195. package/dashboard/dist/assets/{topics-BVIqtmd4.js.map → topics-DI8TFSZv.js.map} +1 -1
  196. package/dashboard/dist/assets/{useEventHooks-_qCzU74s.js → useEventHooks-DcIgMDh3.js} +2 -2
  197. package/dashboard/dist/assets/{useEventHooks-_qCzU74s.js.map → useEventHooks-DcIgMDh3.js.map} +1 -1
  198. package/dashboard/dist/assets/{useNamespace-CDnCSFpG.js → useNamespace-C88ZxGoX.js} +2 -2
  199. package/dashboard/dist/assets/{useNamespace-CDnCSFpG.js.map → useNamespace-C88ZxGoX.js.map} +1 -1
  200. package/dashboard/dist/assets/{useYamlActivityEvents-zrHMhaFM.js → useYamlActivityEvents-D2-yVjJ7.js} +2 -2
  201. package/dashboard/dist/assets/{useYamlActivityEvents-zrHMhaFM.js.map → useYamlActivityEvents-D2-yVjJ7.js.map} +1 -1
  202. package/dashboard/dist/assets/{users-BPBMZg27.js → users-C0WV36X8.js} +2 -2
  203. package/dashboard/dist/assets/{users-BPBMZg27.js.map → users-C0WV36X8.js.map} +1 -1
  204. package/dashboard/dist/assets/{vendor-icons-C1zVP_dv.js → vendor-icons-CEWCGPeJ.js} +76 -76
  205. package/dashboard/dist/assets/vendor-icons-CEWCGPeJ.js.map +1 -0
  206. package/dashboard/dist/assets/{workflows-CT6nTL1I.js → workflows-CkPv7xk-.js} +2 -2
  207. package/dashboard/dist/assets/{workflows-CT6nTL1I.js.map → workflows-CkPv7xk-.js.map} +1 -1
  208. package/dashboard/dist/assets/{yaml-workflows-CdfjkOKU.js → yaml-workflows-BjrMQLTK.js} +2 -2
  209. package/dashboard/dist/assets/{yaml-workflows-CdfjkOKU.js.map → yaml-workflows-BjrMQLTK.js.map} +1 -1
  210. package/dashboard/dist/index.html +3 -3
  211. package/docs/api/http/roles.md +62 -0
  212. package/docs/api/mcp/admin.md +32 -1
  213. package/docs/api/mcp/human-queue.md +2 -0
  214. package/docs/api/sdk/roles.md +39 -0
  215. package/docs/cli.md +13 -0
  216. package/docs/dashboard.md +66 -17
  217. package/docs/data.md +34 -0
  218. package/docs/hitl-guide.md +25 -3
  219. package/package.json +2 -2
  220. package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js +0 -2
  221. package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js.map +0 -1
  222. package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js +0 -16
  223. package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js.map +0 -1
  224. package/dashboard/dist/assets/index-BRtKQtQZ.css +0 -1
  225. package/dashboard/dist/assets/index-CtDf8vvQ.js.map +0 -1
  226. package/dashboard/dist/assets/index-DccCwyZl.js +0 -6
  227. package/dashboard/dist/assets/roles-bRgaMcdy.js +0 -2
  228. package/dashboard/dist/assets/roles-bRgaMcdy.js.map +0 -1
  229. package/dashboard/dist/assets/vendor-icons-C1zVP_dv.js.map +0 -1
@@ -724,7 +724,7 @@ Create a new role. Lowercase alphanumeric with hyphens/underscores.
724
724
 
725
725
  ### update_role
726
726
 
727
- Update a role's metadata: display name, description, form schema, metadata schema, free properties bag, operations visibility, process parent, and the typed operational targets (SLA minutes, throughput goal, worker count). Only provided fields are changed.
727
+ Update a role's metadata: display name, description, form schema, metadata schema, free properties bag, operations visibility, process parent, and the typed operational targets (SLA minutes, throughput goal, worker count). Only provided fields are changed. A change to `form_schema` or `metadata_schema` snapshots the new pair into the role's schema version history and advances its current version.
728
728
 
729
729
  | | |
730
730
  |---|---|
@@ -745,6 +745,37 @@ Update a role's metadata: display name, description, form schema, metadata schem
745
745
  | `sla_minutes` | `number \| null` | No | Target resolution time in minutes |
746
746
  | `target_per_hour` | `number \| null` | No | Throughput goal (items resolved per hour) |
747
747
  | `worker_count` | `number \| null` | No | Station capacity (staff or machines) |
748
+ | `upstream_roles` | `string[] \| null` | No | Replace the set of roles this station draws input from across other Operations sequences (omitted = preserve; `null` or `[]` = clear) |
749
+ | `change_summary` | `string` | No | Label recorded on the schema version snapshot when this update changes a schema field |
750
+
751
+ ### get_role_schema
752
+
753
+ Fetch a role's `form_schema` + `metadata_schema` pair. With `version`, reads that immutable snapshot from the version history (the one an escalation pinned via `metadata.schema_version`); without it, reads the live (latest) schema and its current version number.
754
+
755
+ | | |
756
+ |---|---|
757
+ | Read-safe | Yes |
758
+
759
+ **Parameters:**
760
+
761
+ | Field | Type | Required | Description |
762
+ |-------|------|----------|-------------|
763
+ | role | string | Yes | Role whose schema to fetch |
764
+ | version | number | No | Version pin (positive integer) |
765
+
766
+ ### list_role_schema_versions
767
+
768
+ List a role's schema version history, newest first. Each entry carries the version, presence flags for the two schemas, the change summary, and whether it is the current version.
769
+
770
+ | | |
771
+ |---|---|
772
+ | Read-safe | Yes |
773
+
774
+ **Parameters:**
775
+
776
+ | Field | Type | Required | Description |
777
+ |-------|------|----------|-------------|
778
+ | role | string | Yes | Role whose history to list |
748
779
 
749
780
  ### add_escalation_chain
750
781
 
@@ -33,6 +33,7 @@ Create a new escalation for human review. Returns the escalation ID.
33
33
  | type | string | No | Escalation type classification (default: "mcp") |
34
34
  | subtype | string | No | Escalation subtype (default: "tool_call") |
35
35
  | priority | number | No | Priority: 1 (highest) to 4 (lowest) (default: 2) |
36
+ | schema_version | number | No | Pin the role schema version the resolver form renders (defaults to latest) |
36
37
 
37
38
  ### check_resolution
38
39
 
@@ -116,6 +117,7 @@ Create an escalation and pause the workflow until a human responds. Returns a si
116
117
  | type | string | No | Escalation type classification (default: "mcp") |
117
118
  | subtype | string | No | Escalation subtype (default: "wait_for_human") |
118
119
  | priority | number | No | Priority: 1 (highest) to 4 (lowest) (default: 1) |
120
+ | schema_version | number | No | Pin the role schema version the resolver form renders (defaults to latest; an embedded `form_schema` takes precedence) |
119
121
 
120
122
  ## Scope and One-Time Users
121
123
 
@@ -269,7 +269,46 @@ const result = await lt.roles.update({
269
269
  | `sla_minutes` | `number \| null` | No | SLA target in minutes |
270
270
  | `target_per_hour` | `number \| null` | No | Throughput target (items per hour) |
271
271
  | `worker_count` | `number \| null` | No | Station capacity |
272
+ | `upstream_roles` | `string[] \| null` | No | Replace the set of roles this station draws input from across other Operations sequences (omitted = preserve; `null` or `[]` = clear). Distinct from `parent_role`, which places the role in its own sequence |
273
+ | `change_summary` | `string` | No | Label recorded on the schema version snapshot when this update changes a schema field |
274
+
275
+ When the update changes `form_schema` or `metadata_schema`, the new pair is snapshotted into the role's version history and `current_schema_version` advances.
272
276
 
273
277
  **Returns:** `LTApiResult<RoleDetail>` — the updated role.
274
278
 
275
279
  **Auth:** Role manager (admin type, superadmin, or engineer)
280
+
281
+ ## getSchema
282
+
283
+ Fetch a role's `form_schema` + `metadata_schema` pair. With `version`, reads that immutable snapshot from the version history (the one an escalation pins via `conditionLT`'s `schemaVersion`); without it, reads the live (latest) schema and its current version number. A missing version is a 404 — it never falls back to a different version.
284
+
285
+ ```typescript
286
+ const latest = await lt.roles.getSchema({ role: 'reviewer' });
287
+ const pinned = await lt.roles.getSchema({ role: 'reviewer', version: 3 });
288
+ ```
289
+
290
+ **Parameters:**
291
+
292
+ | Field | Type | Required | Description |
293
+ |-------|------|----------|-------------|
294
+ | `role` | `string` | Yes | Role whose schema to fetch |
295
+ | `version` | `number` | No | Version pin (positive integer) |
296
+
297
+ **Returns:** `LTApiResult<RoleSchemaVersion>` — `{ role, version, form_schema, metadata_schema, change_summary, created_at, latest_version }`.
298
+
299
+ ## listSchemaVersions
300
+
301
+ List a role's schema version history, newest first. Each entry carries the version number, presence flags for the two schemas, the change summary, and whether it is the role's current version.
302
+
303
+ ```typescript
304
+ const result = await lt.roles.listSchemaVersions({ role: 'reviewer' });
305
+ // result.data.versions → [{ version, has_form_schema, has_metadata_schema, change_summary, created_at, is_current }, …]
306
+ ```
307
+
308
+ **Parameters:**
309
+
310
+ | Field | Type | Required | Description |
311
+ |-------|------|----------|-------------|
312
+ | `role` | `string` | Yes | Role whose history to list |
313
+
314
+ **Returns:** `LTApiResult<{ versions: RoleSchemaVersionSummary[] }>`
package/docs/cli.md CHANGED
@@ -127,6 +127,19 @@ ltc users list
127
127
  ltc users get USER_ID
128
128
  ```
129
129
 
130
+ ### Roles
131
+
132
+ Roles are the queue-backed work surfaces where paused workflows hand off to people. Role schemas are versioned — every schema save adds an immutable snapshot, and escalations can pin one via `schemaVersion`.
133
+
134
+ ```bash
135
+ ltc roles list # Roles with schema version, member and workflow counts
136
+ ltc roles schema reviewer # Latest form/metadata schema (with current version)
137
+ ltc roles schema reviewer --version 3 # Immutable v3 snapshot
138
+ ltc roles schema-versions reviewer # Version history, newest first
139
+ ltc roles save-schema reviewer --file schema.json --summary "Added lotNumber"
140
+ # Save the escalation form schema (a change creates the next version)
141
+ ```
142
+
130
143
  ### Compiler
131
144
 
132
145
  ```bash
package/docs/dashboard.md CHANGED
@@ -28,7 +28,7 @@ The sidebar organizes pages into five groups.
28
28
  | Page | Route | Purpose |
29
29
  |------|-------|---------|
30
30
  | **Recent Activity** | `/` | Live event stream and business process overview. |
31
- | **Operations** | `/operations` | COO shop-floor view — pace chart of actual-vs-target flow across all ops-visible roles, station table with live metrics, and the station detail panel. Visible to any user with ops or builder access. |
31
+ | **Pace Board** | `/operations` | COO shop-floor view — pace chart of actual-vs-target flow across every station role, station table with live metrics, and the station detail panel. Visible to any user with ops or builder access. |
32
32
  | **Capabilities** | `/capabilities` | Browse MCP servers grouped by capability category. |
33
33
  | **Agent Automations** | `/agents` | Autonomous event-driven automations. Configure subscriptions, schedules, and knowledge domains. |
34
34
  | **Topics** | `/topics` | Topic catalog — browse all known event topics with descriptions, schemas, and subscriber counts. |
@@ -45,7 +45,7 @@ The sidebar organizes pages into five groups.
45
45
  | Page | Route | Purpose |
46
46
  |------|-------|---------|
47
47
  | **Accounts** | `/admin/users` | User accounts and service accounts (bots). Create, edit, assign roles, manage API keys. |
48
- | **Roles & Permissions** | `/admin/roles` | Define roles and escalation chains. Roles control escalation visibility and invocation access. |
48
+ | **Roles** | `/admin/roles` | Define roles — the queues, forms, and membership that connect workflows to people. Escalation chains, capacity settings, and versioned schemas live here. |
49
49
  | **DB Maintenance** | `/admin/maintenance` | Database housekeeping — vacuum, reindex, table statistics. |
50
50
  | **Task Queues** | `/admin/controlplane` | Active task queues, connected workers, queue depth, and worker health. |
51
51
 
@@ -188,7 +188,7 @@ Lists all durable workflow runs across the system.
188
188
 
189
189
  User Accounts and Service Accounts live on the same page, separated by a tab toggle.
190
190
 
191
- - **User Accounts** — human operators. Create users, assign display names, and grant roles. Roles determine which escalations a user can see and claim, and which workflows they can invoke from the dashboard. A `member` grant carries a work-surface scope (read/write breadth) chosen from the Scope picker; see [Roles](#roles).
191
+ - **User Accounts** — human operators. Create users, assign display names, and grant roles. Roles determine which escalations a user can see and claim, and which workflows they can invoke from the dashboard. A `member` grant carries a work-surface scope (read/write breadth) chosen from the Scope picker; the five named profiles are documented under [Role Detail](#role-detail).
192
192
  - **Service Accounts** — programmatic callers (bots, CI pipelines, external systems). Each service account has an API key for authentication. Assign roles to control access just like human users. Service accounts with the `reviewer` role can claim and resolve escalations programmatically.
193
193
  - **Role assignment** — both account types participate in the same role system. Click any account to edit roles, change display name, or manage credentials.
194
194
 
@@ -196,15 +196,57 @@ User Accounts and Service Accounts live on the same page, separated by a tab tog
196
196
 
197
197
  ### Roles
198
198
 
199
- Define roles and configure escalation chains that control how work flows between teams.
199
+ Roles are the system's central organizer and gatekeeper: every hand-off between the digital side (running workflows) and the outside world crosses a role. When a workflow needs something only a person or external actor can provide, it raises an escalation into exactly one role's queue and pauses. Membership in the role grants access to that queue and every escalation it contains; each member's scopes determine which specific combinations of activities they may perform via those escalations — which items appear, which they can claim, resolve, or forward. Resolving an item resumes the workflow exactly where it paused.
200
200
 
201
- - **Role list** all roles in the system. Each row shows the role key, title, description, user count, chain count, workflow count, and an OPS badge if `ops_visible` is set.
202
- - **Role detail** — click a role to open its detail panel: identity fields (title, description), escalation chains, and the capacity settings (`sla_minutes`, `target_per_hour`, `worker_count`). Edit these inline and save via `PATCH /api/roles/:role`.
203
- - **Create Role** — add a new role. Roles referenced in workflow configs are auto-created, but you can also create them here for organizational clarity.
204
- - **Scope picker** — when granting a role at `member` type, a Scope picker offers the five named work-surface profiles: full worker (`all`/`all`, default), see-all-act-own (`all`/`self`), own-items-only (`self`/`self`), read-only auditor (`all`/`none`), and read-only own (`self`/`none`). `admin` and `superadmin` grants show no Scope picker — they always work the whole queue. The picker enforces **write ⊆ read**, so a write breadth wider than the read breadth cannot be selected.
205
- - **Escalation chains** — define source → target role mappings. When a reviewer escalates, the chain determines which roles receive the escalation next. Chains are directional (reviewer → engineer → admin) and support multiple targets per source.
201
+ Because roles carry the queue, the form schema, the membership, and the capacity targets in one place, they are also the unit everything else is built on: [Accounts](#accounts) grant them, escalation views filter by them, and the [Pace Board](#pace-board) renders them as stations.
206
202
 
207
- **API:** `GET /api/roles` lists roles. `POST /api/roles` creates. `PATCH /api/roles/:role` updates metadata and capacity fields. `GET /api/roles/details` returns full `RoleDetail` shapes. `GET /api/roles/escalation-chains` lists chains. `POST /api/roles/escalation-chains` adds a chain.
203
+ **Master list** (`/admin/roles`) one row per role:
204
+
205
+ - **Ops dot** — a green dot marks roles that appear as stations on the [Pace Board](#pace-board).
206
+ - **Role / Label / Description** — the role key and its human-facing identity.
207
+ - **Preceded By** — the role's prior step (`parent_role`), linked. Prior steps compose the Pace Board's sequences.
208
+ - **Escalates To** — the chain targets this role can forward work to.
209
+ - **Member Count** — how many accounts hold the role.
210
+ - **Capacity** — SLA (minutes), Target (per hour), and Staff side by side. These drive the Pace Board's computed metrics.
211
+
212
+ Search filters by key, label, or description. **+ Add Role** creates a role here; roles referenced in workflow configs are also auto-created at startup. Click any row to open [Role Detail](#role-detail).
213
+
214
+ **API:** `GET /api/roles` lists roles. `POST /api/roles` creates. `GET /api/roles/details` returns full `RoleDetail` shapes. `GET /api/roles/escalation-chains` lists chains.
215
+
216
+ ### Role Detail
217
+
218
+ Accessible at `/admin/roles/:role`. One page per role — a header plus three columns covering identity, routing and people, and schemas.
219
+
220
+ **Header** — the role key with its prior step, the capacity set, and the **Ops** toggle that shows the role as a station on the [Pace Board](#pace-board). The capacity set holds `sla_minutes`, `target_per_hour`, and `worker_count`; enter any two and the header hints the derived third (`throughput = workers / (sla / 60)`). Save applies header and identity edits via `PATCH /api/roles/:role`.
221
+
222
+ **Identity** — display name and description, shown on role rows and station labels.
223
+
224
+ **Sequence placement** — appears when the Ops toggle is on:
225
+
226
+ - **Prior Step** — places the role in one Pace Board sequence; a role with no prior step starts its own.
227
+ - **Upstream Inputs** — the roles this station also draws from in other sequences. Mixin-like, many allowed, saved live. Rendered on the Pace Board as a merge glyph on the station, never a bend in the line: an upstream is an input, not a descendant.
228
+
229
+ **Escalation Targets** — the chain out of this role, saved live. When a member escalates an item, these targets receive it. Chains are directional and support multiple targets per source.
230
+
231
+ **Members** — who holds the role: admins manage it; members work its queue according to their read/write work-surface scope (read = which items appear; write = which they can claim and resolve). A `member` grant carries a scope chosen from five named profiles: full worker (`all`/`all`, default), see-all-act-own (`all`/`self`), own-items-only (`self`/`self`), read-only auditor (`all`/`none`), and read-only own (`self`/`none`). The picker enforces **write ⊆ read**. `admin` and `superadmin` grants always work the whole queue.
232
+
233
+ **Escalation Schema** — shows the schema version currently in use and links to the [Escalation Schema](#escalation-schema) editor page.
234
+
235
+ **Metadata Schema** — JSON Schema that validates each escalation's `metadata` at creation time. Its keys appear in faceted search autocomplete.
236
+
237
+ **Properties** — a free JSON bag for custom per-role values.
238
+
239
+ **API:** `PATCH /api/roles/:role` updates identity, capacity, placement, and schemas. `POST /api/roles/escalation-chains` adds a chain target; `DELETE /api/roles/escalation-chains` removes one.
240
+
241
+ ### Escalation Schema
242
+
243
+ Accessible at `/admin/roles/:role/schema`. The versioned form behind a role's escalations — the form a person completes to resolve items in the role's queue.
244
+
245
+ - **Editor** — form fields as JSON Schema in a full-width editor, with an optional change summary recorded on the version the save creates. **Save Version** writes only the schema: every save that changes it appends an immutable snapshot and advances the current version.
246
+ - **Version rail** — the full history with the current version marked. Expand any version to view its snapshot or load it into the editor as the base for the next save.
247
+ - **Pinning** — workflows pin a version via `schemaVersion` in the `conditionLT` config, so their resolver form keeps that exact shape for the life of the run. Escalations without a pin render the latest version.
248
+
249
+ **API:** `GET /api/roles/:role/schema` fetches the latest or a pinned version. `GET /api/roles/:role/schema/versions` lists the history. `PATCH /api/roles/:role` with `form_schema` (+ optional `change_summary`) saves a new version.
208
250
 
209
251
  ### DB Maintenance
210
252
 
@@ -262,16 +304,23 @@ Accessible at `/escalations`. A statistics dashboard for escalation health acros
262
304
  - **Summary cards** — open (pending), claimed (in progress), created (new), and resolved counts for the selected window.
263
305
  - **Role breakdown table** — groups escalations by role so you can see which teams have the most pending work. Useful for identifying bottlenecks and rebalancing workload.
264
306
 
265
- ### Operations
307
+ ### Pace Board
308
+
309
+ Accessible at `/operations`. The COO shop-floor view of the roles system: actual-vs-target flow across every station, rendered as a pace chart with a station table and detail panel below.
310
+
311
+ The board is [Roles](#roles) end-to-end. Every station is a role with its **Ops** toggle on; sequences are composed from each role's Prior Step (`parent_role`); cross-sequence feeds come from Upstream Inputs; the red target line comes from each role's capacity settings. Configuring the board *is* configuring roles — the **Configure** button in the header goes straight there.
266
312
 
267
- Accessible at `/operations`. The COO shop-floor view shows actual-vs-target flow across every ops-visible role as a pace chart, with a station table and detail panel below.
313
+ Execution is a graph; this page tells its story as **sequences**. Each station role with no prior step (or whose prior step is outside the station set) starts a sequence, followed by its `parent_role` descendants in dependency order. The longest sequence leads. The table is always the ground truth of the queues; the SVG is the narrative line drawn through them.
268
314
 
269
- - **Period selector** — `15m`, `1h`, `24h`, `7d`, `30d`. Controls the lookback window for resolved counts, percentile metrics, and the throughput metrics.
270
- - **Pace chart** — connects all ops-visible stations in process dependency order and plots absolute counts for the selected window: a straight red target polyline (`target_per_hour × window hours`) against a smooth actual (resolved) curve with a light area fill. Station circles are colored by pace ratio (green ≥ 100%, amber ≥ 60%, red below), with a three-number strip per station: pending, active, resolved.
271
- - **Station table** — one row per ops-visible role with PENDING, ACTIVE, RESOLVED, P99 WAIT, P99 WORK, and a PRESSURE mini-bar. Stations with in-arrears items show a sub-row with a link to oldest-first queue view.
272
- - **Station detail panel** — opens on row or circle click. Shows the role's identity, an independent period toggle, and full metric breakdown (wait/work percentiles, SLA target, worker count, links to queue).
315
+ - **Sequence picker** — when more than one sequence exists, tabs appear above the chart, one per sequence, named by its origin role with the station count alongside. The active sequence is deep-linked (`?fragment=<origin role>`) and each switch is a browser-history entry, so a shared URL opens the same sequence and back/forward walks between them.
316
+ - **Period selector** — `15m`, `1h`, `24h`, `7d`, `30d`. Controls the lookback window for resolved counts, percentile metrics, and throughput.
317
+ - **Pace chart** — connects the active sequence's stations in process dependency order and plots absolute counts for the selected window: a straight red target polyline (`target_per_hour × window hours`) against a smooth actual (resolved) curve with a light area fill. The queue splits into two stacked bands — claimed-and-worked (indigo) and waiting-unclaimed (sky). Station circles are colored by pace ratio (green ≥ 100%, amber ≥ 60%, red below); a station with items past SLA carries a dashed red ring.
318
+ - **Merge affordance** — a station that declares upstream inputs shows a small dashed merge glyph at its floor position. It is deliberately a symbol, never a bend in the line: the upstream is an input, not a descendant. Hover names the feeding roles; click jumps to their sequence.
319
+ - **Station table** — one row per station: ROLE (with a merge icon marking cross-sequence inputs), TARGET/H, then PENDING, ACTIVE, RESOLVED in column bands carrying the chart's hues, P99 WAIT, P99 WORK, and a TREND mini-bar. TREND shows the live backlog-to-target ratio while the queue has items; when the queue is idle it shows the period's throughput efficiency, marked with `↩`. Stations with past-SLA items show a sub-row linking to the oldest-first queue view.
320
+ - **Station detail panel** — opens on row or circle click. Shows the role's identity, an independent period toggle, and the full metric breakdown (wait/work percentiles, SLA target, worker count, links to the queue).
321
+ - **Live updates** — escalation events refresh the metrics push-driven and debounced; the header's refresh button forces a reload.
273
322
 
274
- Roles appear on this view when `ops_visible = true` is set. The capacity settings (`sla_minutes`, `target_per_hour`, `worker_count`) drive the computed metrics. Set these via `PATCH /api/roles/:role` or the Roles admin page. See [Operations](operations.md) for the full concept doc.
323
+ A role joins the board via the **Ops** toggle on its [Role Detail](#role-detail) page; the capacity settings (`sla_minutes`, `target_per_hour`, `worker_count`) drive the computed metrics. See [Operations](operations.md) for the full concept doc.
275
324
 
276
325
  **API:** `GET /api/escalations/station-metrics?period=24h`
277
326
 
package/docs/data.md CHANGED
@@ -11,10 +11,44 @@ Canonical role registry. Roles referenced by other tables are seeded here.
11
11
  | Column | Type | Default | Description |
12
12
  |--------|------|---------|-------------|
13
13
  | `role` | `TEXT` | — | Primary key |
14
+ | `title` | `TEXT` | — | Display name |
15
+ | `description` | `TEXT` | — | Short human-readable purpose |
16
+ | `form_schema` | `JSONB` | — | Live (latest) JSON Schema for the escalation resolve form |
17
+ | `metadata_schema` | `JSONB` | — | Live (latest) JSON Schema validating `lt_escalations.metadata` at creation |
18
+ | `current_schema_version` | `INTEGER` | — | Version of the live schema pair; advances on every schema change (null until the role first carries a schema) |
19
+ | `properties` | `JSONB` | `'{}'` | Free user-owned bag (icons, colors, tags) |
20
+ | `ops_visible` | `BOOLEAN` | `false` | Show as a station on the Operations view |
21
+ | `parent_role` | `TEXT` | — | FK to `lt_roles(role)` — the single prior step placing this role in one Operations sequence (cross-sequence inputs live in `lt_role_upstreams`) |
22
+ | `sla_minutes` | `NUMERIC` | — | Target resolution time |
23
+ | `target_per_hour` | `NUMERIC` | — | Throughput target |
24
+ | `worker_count` | `NUMERIC` | — | Station capacity |
14
25
  | `created_at` | `TIMESTAMPTZ NOT NULL` | `NOW()` | Row creation time |
15
26
 
16
27
  **Seeds:** `reviewer`, `engineer`, `admin`, `superadmin`.
17
28
 
29
+ ### lt_role_upstreams
30
+
31
+ The graph edges that don't fit the line. `lt_roles.parent_role` places a role in one Operations sequence (its single "prior step"); this table carries the remaining edges — the roles a station draws input from that live in other sequences (mixin-like, many allowed). The Operations chart renders them as a merge affordance on the station rather than bending the sequence. Replaced as a set through the same atomic `PATCH /api/roles/:role` statement (`upstream_roles`).
32
+
33
+ | Column | Type | Default | Description |
34
+ |--------|------|---------|-------------|
35
+ | `role` | `TEXT NOT NULL` | — | FK to `lt_roles(role)` `ON DELETE CASCADE`; part of the primary key |
36
+ | `upstream_role` | `TEXT NOT NULL` | — | FK to `lt_roles(role)` `ON DELETE CASCADE`; part of the primary key. `CHECK (role <> upstream_role)` |
37
+ | `created_at` | `TIMESTAMPTZ NOT NULL` | `NOW()` | When the edge was declared |
38
+
39
+ ### lt_role_schemas
40
+
41
+ Immutable version history of each role's schema pair. Every change to a role's `form_schema` or `metadata_schema` (via `PATCH /api/roles/:role`, the SDK, or the `update_role` MCP tool) appends the next `(role, version)` snapshot in the same atomic statement that updates `lt_roles`. Escalations pin a version via `metadata.schema_version` (`conditionLT`'s `schemaVersion` field) so the resolver form they render stays exactly what their author specified; unpinned escalations use the live columns on `lt_roles`.
42
+
43
+ | Column | Type | Default | Description |
44
+ |--------|------|---------|-------------|
45
+ | `role` | `TEXT NOT NULL` | — | FK to `lt_roles(role)` `ON DELETE CASCADE`; part of the primary key |
46
+ | `version` | `INTEGER NOT NULL` | — | Monotonic per role; part of the primary key |
47
+ | `form_schema` | `JSONB` | — | Snapshot of the form schema at this version |
48
+ | `metadata_schema` | `JSONB` | — | Snapshot of the metadata schema at this version |
49
+ | `change_summary` | `TEXT` | — | Optional label supplied with the change |
50
+ | `created_at` | `TIMESTAMPTZ NOT NULL` | `NOW()` | When this version was created |
51
+
18
52
  ### lt_tasks
19
53
 
20
54
  Tracks every workflow execution. Created by the LT interceptor when a workflow starts; updated when it completes or fails.
@@ -160,10 +160,32 @@ export async function reviewWorkflow(envelope: LTEnvelope) {
160
160
  }
161
161
  ```
162
162
 
163
- ### Per-Escalation vs Workflow-Level Schema
163
+ ### Which Schema Renders the Form
164
164
 
165
- - **Workflow config `resolver_schema`**: Default form for all escalations of this workflow type. Set in the workflow registry.
166
- - **`metadata.form_schema`**: Per-escalation override. Takes precedence over workflow config. Use when different escalation points in the same workflow need different forms.
165
+ The resolver form resolves in order, most specific first:
166
+
167
+ 1. **`metadata.form_schema`** — a full JSON Schema embedded on the escalation row. Use when different escalation points in the same workflow need different forms.
168
+ 2. **`metadata.schema_version`** — a pin to a specific version of the role's schema (set via `schemaVersion` in the `conditionLT` config). The form renders exactly that snapshot, even after the role's schema changes.
169
+ 3. **Workflow config `resolver_schema`** — default form for all escalations of this workflow type. Set in the workflow registry.
170
+ 4. **The role's latest `form_schema`** — the fallback every role provides.
171
+
172
+ ### Versioned Role Schemas
173
+
174
+ Every save that changes a role's `form_schema` or `metadata_schema` appends an immutable snapshot to the version history (`lt_role_schemas`) and advances the role's current version. Escalations that need a guaranteed shape pin one:
175
+
176
+ ```typescript
177
+ const decision = await conditionLT<{ approved: boolean; lotNumber: string }>(signalId, {
178
+ role: 'reviewer',
179
+ description: instructions,
180
+ schemaVersion: 3, // this row renders role schema v3, always
181
+ });
182
+ ```
183
+
184
+ The pin travels as `metadata.schema_version` on the row (GIN-indexed, queryable like any facet). A pin that names a missing version fails at creation with a 400 — it never falls through to a different version. Without a pin, the role's latest schema always applies: workflow authors who don't care get the current form automatically; authors who depend on a specific field (say, a form that gained `lotNumber` in v3 and the workflow reads it back) pin the version and the round trip stays aligned.
185
+
186
+ `metadata.schema_version` also selects which `metadata_schema` validates the creation-time metadata bag on `POST /api/escalations`.
187
+
188
+ Inspect versions via `GET /api/roles/:role/schema?version=N`, `GET /api/roles/:role/schema/versions`, `lt.roles.getSchema` / `lt.roles.listSchemaVersions`, `ltc roles schema <role> --version N`, or the `get_role_schema` / `list_role_schema_versions` admin MCP tools. Save a new version by writing only the schema — `PATCH /api/roles/:role` with `form_schema` (+ optional `change_summary`), `lt.roles.update`, `ltc roles save-schema <role> --file schema.json`, or the `update_role` MCP tool. The dashboard edits it on the role's Escalation Schema page (`/admin/roles/:role/schema`), with the version history and snapshot viewer alongside.
167
189
 
168
190
  ---
169
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -71,7 +71,7 @@
71
71
  "@anthropic-ai/sdk": "^0.92.0",
72
72
  "@aws-sdk/client-s3": "^3.1017.0",
73
73
  "@aws-sdk/s3-request-presigner": "^3.1045.0",
74
- "@hotmeshio/hotmesh": "^0.25.2",
74
+ "@hotmeshio/hotmesh": "^0.25.4",
75
75
  "@modelcontextprotocol/sdk": "^1.27.1",
76
76
  "@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
77
77
  "@opentelemetry/resources": "^2.5.1",
@@ -1,2 +0,0 @@
1
- import{a as R,j as e}from"./vendor-query-B2UbickB.js";import{b as le}from"./roles-bRgaMcdy.js";import{K as ee}from"./index-CtDf8vvQ.js";import{i as oe}from"./useEventHooks-_qCzU74s.js";import{P as ie}from"./PageHeader-BZJsEVOC.js";import{X as ce,ae as D,au as xe,av as de}from"./vendor-icons-C1zVP_dv.js";import{L}from"./vendor-react-CXumBFUA.js";const te=800,se=270,A=40,pe=76,B=24,ue=72,z=te-A-pe,ne=se-B-ue,w=B+ne,X=A+z,N="0.5s cubic-bezier(0.4, 0, 0.2, 1)";function he(t){return t.length===0?"":t.map((n,l)=>`${l===0?"M":"L"} ${n.x.toFixed(1)} ${n.y.toFixed(1)}`).join(" ")}function Q(t){if(t.length===0)return"";if(t.length===1)return`M ${t[0].x} ${t[0].y}`;const n=[];for(let l=0;l<t.length-1;l++){const i=t[Math.max(0,l-1)],o=t[l],x=t[l+1],a=t[Math.min(t.length-1,l+2)],c=(o.x+(x.x-i.x)/6).toFixed(1),u=(o.y+(x.y-i.y)/6).toFixed(1),r=(x.x-(a.x-o.x)/6).toFixed(1),d=(x.y-(a.y-o.y)/6).toFixed(1);l===0&&n.push(`M ${o.x.toFixed(1)} ${o.y.toFixed(1)}`),n.push(`C ${c} ${u}, ${r} ${d}, ${x.x.toFixed(1)} ${x.y.toFixed(1)}`)}return n.join(" ")}function me(t){return t>=1e4?`${Math.round(t/1e3)}k`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function V(t){return t.length>0?t[t.length-1]:void 0}function fe(t){return t==null?{stroke:"#cbd5e1",fill:"#f8fafc"}:t>=1?{stroke:"#10b981",fill:"#ecfdf5"}:t>=.6?{stroke:"#f59e0b",fill:"#fffbeb"}:{stroke:"#ef4444",fill:"#fef2f2"}}const I="#6366f1",ye=13;function je(t){const n=[...t].sort((i,o)=>i.y-o.y);let l=-1/0;return n.map(i=>{const o=Math.max(i.y+3,l+ye);return l=o,{...i,labelY:o}})}function ge({stations:t,selectedRole:n,onSelect:l,periodHours:i}){const[o,x]=R.useState(null),a=t.length;if(a===0)return null;const c=s=>a===1?A+z/2:A+s/(a-1)*z,u=new Map(t.map((s,h)=>[s.role,c(h)])),r=t.map((s,h)=>{var W,P,v,F;const b=s.target_per_hour??null,q=b!=null?b*i:null;return{idx:h,x:c(h),expected:q,actual:((W=s.metric)==null?void 0:W.resolved)??0,active:((P=s.metric)==null?void 0:P.claimed)??0,pending:((v=s.metric)==null?void 0:v.pending)??0,inArrears:((F=s.metric)==null?void 0:F.in_arrears)??0}}),d=r.some(s=>s.expected!=null),y=Math.max(1,...r.map(s=>s.expected??0),...r.map(s=>s.actual),...r.map(s=>s.expected!=null?s.active:0))*1.15,m=s=>B+ne*(1-Math.max(0,Math.min(s,y))/y),f=r.filter(s=>s.expected!=null),S=f.map(s=>({...s,y:m(s.expected)})),j=f.map(s=>({...s,y:m(s.actual)})),k=f.map(s=>({...s,y:m(s.active)})),C=he(S),_=Q(j),E=Q(k),M=V(j),p=V(S),$=V(k),U=j.length>=2&&M?`${_} L ${M.x.toFixed(1)} ${w} L ${j[0].x.toFixed(1)} ${w} Z`:"",re=je([p?{key:"target",text:"target",color:"#ef4444",y:p.y}:null,M?{key:"actual",text:"actual",color:"#475569",y:M.y}:null,$?{key:"active",text:"active",color:I,y:$.y}:null].filter(Boolean));return e.jsxs("svg",{viewBox:`0 0 ${te} ${se}`,className:"w-full h-full",preserveAspectRatio:"xMidYMid meet",style:{display:"block"},children:[t.map(s=>{if(!s.parent_role)return null;const h=u.get(s.parent_role);if(h==null)return null;const b=c(t.findIndex(q=>q.role===s.role));return e.jsx("line",{x1:h,y1:w,x2:b,y2:w,stroke:"#e2e8f0",strokeWidth:.75},`dep-${s.role}`)}),U&&e.jsx("path",{d:U,fill:"rgb(71 85 105 / 0.05)",style:{transition:`d ${N}`}}),C&&e.jsx("path",{d:C,fill:"none",stroke:"#ef4444",strokeWidth:.5,strokeLinejoin:"round",opacity:.9,style:{transition:`d ${N}`}}),S.map(s=>e.jsx("text",{x:s.x,y:s.y-3.5,textAnchor:"middle",fontSize:6.5,fill:"#ef4444",fontFamily:"ui-monospace, monospace",fontWeight:"500",opacity:.7,style:{transition:`y ${N}`},children:Math.round(s.expected)},`tval-${s.idx}`)),E&&e.jsx("path",{d:E,fill:"none",stroke:I,strokeWidth:.75,strokeDasharray:"2 3",strokeLinecap:"round",opacity:.5,style:{transition:`d ${N}`}}),_&&e.jsx("path",{d:_,fill:"none",stroke:"#475569",strokeWidth:1,strokeLinejoin:"round",strokeLinecap:"round",opacity:.7,style:{transition:`d ${N}`}}),k.map(s=>{if(s.pending<=0&&s.active<=0)return null;const h=Math.max(2.5,Math.min(5,2.5+s.active/10));return e.jsxs("g",{transform:`translate(${s.x} ${s.y})`,style:{transition:`transform ${N}`},children:[e.jsx("circle",{r:h,fill:I,opacity:.85}),s.pending>0&&e.jsx("text",{y:-h-4,textAnchor:"middle",fontSize:7.5,fill:"#64748b",fontFamily:"ui-monospace, monospace",fontWeight:"600",children:s.pending}),s.active>0&&e.jsx("text",{y:h+9,textAnchor:"middle",fontSize:7.5,fill:I,fontFamily:"ui-monospace, monospace",fontWeight:"600",children:s.active})]},`active-${s.idx}`)}),r.map(s=>{const h=t[s.idx],b=s.x,q=s.expected!=null?m(s.actual):w,W=s.expected&&s.expected>0?s.actual/s.expected:null,{stroke:P}=fe(s.expected!=null?W:null),v=Math.max(3,Math.min(5.5,3+s.actual/120)),F=h.role===n,G=o===s.idx,Y=s.expected==null?"idle · set a target rate to plot pace":`${s.actual} done · target ${Math.round(s.expected)}`+(s.pending>0?` · ${s.pending} in queue`:"")+(s.active>0?` · ${s.active} active`:""),O=Y.length*5.5+20,J=Math.max(A+O/2+4,Math.min(X-O/2-4,b)),K=q-v-10;return e.jsxs("g",{onClick:()=>l(h.role),onMouseEnter:()=>x(s.idx),onMouseLeave:()=>x(null),style:{cursor:"pointer"},children:[e.jsxs("g",{transform:`translate(${b} ${q})`,style:{transition:`transform ${N}`},children:[e.jsx("circle",{r:22,fill:"transparent"}),s.inArrears>0&&e.jsx("circle",{r:v+4,fill:"none",stroke:"#ef4444",strokeWidth:1,strokeDasharray:"3 2",opacity:.6}),e.jsx("circle",{r:v,fill:P,opacity:.9,style:{transition:`r ${N}`}}),s.expected!=null&&e.jsx("text",{y:-v-5,textAnchor:"middle",fontSize:9.5,fill:"#334155",fontFamily:"ui-monospace, monospace",fontWeight:"500",children:me(s.actual)}),G&&!F&&e.jsx("circle",{r:v+3,fill:"none",stroke:P,strokeWidth:1,opacity:.4}),F&&e.jsx("circle",{r:v+4,fill:"none",stroke:"#6366f1",strokeWidth:2})]}),e.jsx("text",{x:b,y:w+14,textAnchor:"middle",fontSize:9.5,fill:"#475569",fontFamily:"ui-sans-serif, sans-serif",fontWeight:"500",children:h.title??h.role}),e.jsx("text",{x:b,y:w+26,textAnchor:"middle",fontSize:8,fill:"#94a3b8",fontFamily:"ui-monospace, monospace",children:h.role}),G&&e.jsxs("g",{children:[e.jsx("rect",{x:J-O/2,y:K-16,width:O,height:19,rx:3,fill:"#0f172a",opacity:.88}),e.jsx("text",{x:J,y:K-3,textAnchor:"middle",fontSize:8.5,fill:"white",fontFamily:"ui-monospace, monospace",children:Y})]})]},h.role)}),!d&&e.jsx("text",{x:A+z/2,y:w+48,textAnchor:"middle",fontSize:8.5,fill:"#94a3b8",fontFamily:"ui-sans-serif, sans-serif",children:"Set target_per_hour on each role in Admin → Roles to enable the pace chart"}),re.map(s=>e.jsx("text",{x:X+6,y:s.labelY,fontSize:8,fill:s.color,fontFamily:"ui-sans-serif, sans-serif",fontWeight:s.key==="actual"?700:500,opacity:s.key==="active"?.85:1,style:{transition:`y ${N}`},children:s.text},s.key))]})}const ae=["15m","1h","24h","7d","30d"],be=t=>ae.includes(t);function T(t){if(t==null)return"—";if(t<1){const n=Math.round(t*60);return n<=0?"< 1s":`${n}s`}return t<60?`${t.toFixed(0)}m`:`${(t/60).toFixed(1)}h`}function H({label:t,value:n}){return e.jsxs("div",{className:"flex items-center justify-between py-1",children:[e.jsx("span",{className:"text-[10px] text-text-tertiary uppercase tracking-wider",children:t}),e.jsx("span",{className:"text-xs font-mono font-medium text-text-primary tabular-nums",children:n})]})}function ve({period:t,onChange:n}){return e.jsx("div",{className:"flex items-center gap-1 mb-4",children:ae.map(l=>e.jsx("button",{onClick:()=>n(l),className:`px-2 py-0.5 text-[10px] font-mono rounded transition-colors ${t===l?"bg-accent/10 text-accent font-semibold":"text-text-quaternary hover:text-text-secondary"}`,children:l},l))})}function Ne({role:t,globalPeriod:n,onClose:l}){const[i,o]=R.useState(be(n)?n:"24h"),{data:x}=ee(i),a=x==null?void 0:x.stations.find(d=>d.role===t.role),c=t.sla_minutes??void 0,u=t.target_per_hour??void 0,r=t.worker_count??void 0;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-start justify-between mb-1",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-baseline gap-1.5 flex-wrap",children:[e.jsx("span",{className:"text-sm font-mono font-bold text-text-primary",children:t.role}),t.title&&e.jsx("span",{className:"text-[10px] text-text-secondary",children:t.title})]}),t.description&&e.jsx("p",{className:"text-[10px] text-text-tertiary mt-0.5 line-clamp-2 leading-relaxed",children:t.description})]}),e.jsx("button",{onClick:l,className:"text-text-quaternary hover:text-text-secondary transition-colors mt-0.5 shrink-0 ml-2",children:e.jsx(ce,{className:"w-4 h-4"})})]}),e.jsxs(L,{to:`/admin/roles/${encodeURIComponent(t.role)}`,className:"flex items-center gap-1 text-[10px] text-accent hover:underline mb-4",children:[e.jsx(D,{className:"w-2.5 h-2.5"}),"Edit in Roles"]}),e.jsx(ve,{period:i,onChange:o}),(u||c||r)&&e.jsxs("div",{className:"flex items-center gap-4 mb-4 text-[10px]",children:[u&&e.jsxs("div",{children:[e.jsxs("div",{className:"font-mono font-semibold text-text-primary",children:[u,"/h"]}),e.jsx("div",{className:"text-text-quaternary uppercase tracking-wider text-[8.5px]",children:"target"})]}),c&&e.jsxs("div",{children:[e.jsxs("div",{className:"font-mono font-semibold text-text-primary",children:[c,"m"]}),e.jsx("div",{className:"text-text-quaternary uppercase tracking-wider text-[8.5px]",children:"SLA"})]}),r&&e.jsxs("div",{children:[e.jsx("div",{className:"font-mono font-semibold text-text-primary",children:r}),e.jsx("div",{className:"text-text-quaternary uppercase tracking-wider text-[8.5px]",children:"workers"})]})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-x-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[9px] text-text-quaternary uppercase tracking-wider mb-1.5",children:"Wait (queue)"}),e.jsx(H,{label:"P99",value:T((a==null?void 0:a.wait.p99)??null)}),e.jsx(H,{label:"avg",value:T((a==null?void 0:a.wait.avg)??null)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[9px] text-text-quaternary uppercase tracking-wider mb-1.5",children:"Work (proc.)"}),e.jsx(H,{label:"P99",value:T((a==null?void 0:a.work.p99)??null)}),e.jsx(H,{label:"avg",value:T((a==null?void 0:a.work.avg)??null)})]})]}),e.jsxs("div",{className:"border-t border-surface-border/40 pt-2 mt-3 space-y-1.5",children:[c&&e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[10px] text-text-tertiary",children:"SLA target"}),e.jsxs("span",{className:"text-xs font-mono text-text-secondary",children:[c,"m"]})]}),a&&a.in_arrears>0&&e.jsxs(L,{to:`/escalations/available?roles=${encodeURIComponent(JSON.stringify([a.role]))}&sort_by=created_at&order=asc`,className:"flex items-center justify-between group",children:[e.jsxs("span",{className:"text-[10px] text-red-500",children:[a.in_arrears," past SLA"]}),e.jsx(D,{className:"w-3 h-3 text-red-400 group-hover:text-red-500 transition-colors"})]})]}),e.jsx("div",{className:"mt-4 border-t border-surface-border/40 pt-3",children:e.jsxs(L,{to:`/escalations/available?roles=${encodeURIComponent(JSON.stringify([t.role]))}`,className:"flex items-center gap-1.5 text-[10px] text-accent hover:underline",children:["View full queue",e.jsx(D,{className:"w-2.5 h-2.5"})]})})]})}function ke({allMetrics:t,orderedRoles:n,period:l}){const i=t.reduce((r,d)=>r+d.pending,0),o=t.reduce((r,d)=>r+d.resolved,0),x=t.reduce((r,d)=>r+d.in_arrears,0),a=new Map(t.map(r=>[r.role,r])),c=t.filter(r=>r.in_arrears>0).length,u=t.filter(r=>r.pending>0).length;return e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-[9px] text-text-quaternary uppercase tracking-wider mb-3",children:["Pipeline · ",l]}),e.jsx("div",{className:"mb-4",children:c>0?e.jsxs("p",{className:"text-[11px] text-red-400",children:[c," station",c>1?"s":""," past SLA"]}):u>0?e.jsxs("p",{className:"text-[11px] text-amber-400",children:[u," station",u>1?"s":""," with backlog"]}):o>0?e.jsx("p",{className:"text-[11px] text-emerald-500",children:"Flowing — queue clear"}):e.jsx("p",{className:"text-[11px] text-text-quaternary",children:"Quiet — awaiting work"})}),e.jsxs("div",{className:"space-y-1.5 mb-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[9px] text-text-quaternary uppercase tracking-wider",children:"Pending"}),e.jsx("span",{className:`text-xs font-mono tabular-nums ${i>0?"text-text-primary font-semibold":"text-text-quaternary"}`,children:i})]}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("span",{className:"text-[9px] text-text-quaternary uppercase tracking-wider",children:["Resolved · ",l]}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-text-secondary",children:o})]}),x>0&&e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[9px] text-text-quaternary uppercase tracking-wider",children:"Past SLA"}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-red-500 font-semibold",children:x})]})]}),e.jsx("div",{className:"border-t border-surface-border/40 pt-2 mb-1",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider flex-1",children:"Station"}),e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0",children:"pend"}),e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0",children:"actv"}),e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0",children:"res"})]})}),e.jsx("div",{className:"space-y-0.5",children:n.map(r=>{const d=a.get(r.role),g=(d==null?void 0:d.pending)??0,y=(d==null?void 0:d.claimed)??0,m=(d==null?void 0:d.resolved)??0,f=(d==null?void 0:d.in_arrears)??0,S=r.title||r.role,j=f>0,k=g>0;return e.jsxs("div",{className:"flex items-center gap-2 py-0.5",children:[e.jsxs("span",{className:`text-[10px] font-mono flex-1 truncate ${j?"text-red-400":k?"text-text-primary":"text-text-quaternary"}`,children:[S,j&&e.jsx("span",{className:"ml-1 text-[8px] text-red-400 font-semibold",children:"SLA"})]}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${j?"text-red-400":k?"text-text-primary font-semibold":"text-text-quaternary"}`,children:g>0?g:"—"}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${y>0?"text-accent":"text-text-quaternary"}`,children:y>0?y:"—"}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${m>0?"text-text-secondary":"text-text-quaternary"}`,children:m>0?m:"—"})]},r.role)})}),e.jsx("p",{className:"text-[9px] text-text-quaternary mt-5",children:"Select a station for queue detail."})]})}function $e({role:t,allMetrics:n,orderedRoles:l,globalPeriod:i,onClose:o}){return e.jsx("div",{className:"w-[280px] shrink-0 px-6 py-8 flex flex-col overflow-y-auto min-h-0",children:t?e.jsx(Ne,{role:t,globalPeriod:i,onClose:o}):e.jsx(ke,{allMetrics:n,orderedRoles:l,period:i})})}const we=["15m","1h","24h","7d","30d"],Se={"15m":.25,"1h":1,"24h":24,"7d":168,"30d":720};function Me(t){const n=t.filter(a=>a.ops_visible),l=new Map(n.map(a=>[a.role,a])),i=[],o=new Set,x=n.filter(a=>!a.parent_role||!l.has(a.parent_role)).map(a=>({role:a,depth:0}));for(;x.length>0;){const a=x.shift();o.has(a.role.role)||(o.add(a.role.role),i.push(a),n.filter(c=>c.parent_role===a.role.role&&!o.has(c.role)).forEach(c=>x.push({role:c,depth:a.depth+1})))}return n.filter(a=>!o.has(a.role)).forEach(a=>i.push({role:a,depth:0})),i}function Z(t){if(t==null)return"—";if(t<1){const n=Math.round(t*60);return n<=0?"< 1s":`${n}s`}return t<60?`${t.toFixed(0)}m`:`${(t/60).toFixed(1)}h`}function Re(t,n,l,i){if(!n)return{pct:null,color:"bg-surface-border",historical:!1};if(t>0){const c=t/n,u=Math.round(c*100),r=c>1?"bg-amber-400":c<.2?"bg-orange-400":"bg-emerald-400";return{pct:u,color:r,historical:!1}}if(i===0||l==null)return{pct:null,color:"bg-surface-border",historical:!0};const o=l/100,x=Math.round(l),a=o>1?"bg-amber-400":"bg-surface-border";return{pct:x,color:a,historical:!0}}function _e({role:t,metric:n,selected:l,onClick:i}){const o=(n==null?void 0:n.pending)??0,x=(n==null?void 0:n.claimed)??0,a=(n==null?void 0:n.resolved)??0,c=(n==null?void 0:n.in_arrears)??0,u=t.target_per_hour??null,{pct:r,color:d,historical:g}=Re(o,u,(n==null?void 0:n.throughput_pct)??null,a),y=r!=null?Math.min(100,r):0;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{role:"button",tabIndex:0,onClick:i,onKeyDown:m=>{m.key==="Enter"&&i()},className:`grid items-center gap-4 py-3.5 cursor-pointer transition-colors ${l?"border-l-2 border-accent":"pl-0.5"}`,style:{gridTemplateColumns:"1fr 64px 56px 56px 60px 72px 72px 104px"},children:[e.jsxs("div",{className:"flex items-center gap-1.5 min-w-0",children:[e.jsx("span",{className:"font-mono font-bold text-text-primary truncate text-[11px]",children:t.role}),t.title&&e.jsx("span",{className:"text-[10px] text-text-tertiary truncate",children:t.title})]}),e.jsx("span",{className:`text-xs font-mono tabular-nums text-right ${u!=null?"text-text-secondary":"text-text-quaternary"}`,children:u??"—"}),e.jsx("span",{className:`text-xs font-mono tabular-nums text-right ${o>0?"text-text-primary font-semibold":"text-text-quaternary"}`,children:o}),e.jsx("span",{className:`text-xs font-mono tabular-nums text-right ${x>0?"text-accent font-semibold":"text-text-quaternary"}`,children:x}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-right text-text-quaternary",children:a}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-right text-text-secondary",children:Z((n==null?void 0:n.wait.p99)??null)}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-right text-text-secondary",children:Z((n==null?void 0:n.work.p99)??null)}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-12 h-1.5 bg-surface-sunken rounded-full overflow-hidden shrink-0",children:e.jsx("div",{className:`h-full rounded-full ${d}`,style:{width:`${y}%`}})}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums ${r!=null&&r>100?"text-amber-500 font-semibold":"text-text-quaternary"}`,title:g?"Throughput efficiency for the selected period":void 0,children:r!=null?`${r}%${g?"↩":""}`:"—"})]})]}),c>0&&e.jsxs("div",{className:"flex items-center gap-1.5 pb-2 pl-1",children:[e.jsx(de,{className:"w-3 h-3 text-red-400 shrink-0"}),e.jsxs(L,{to:`/escalations/available?roles=${encodeURIComponent(JSON.stringify([t.role]))}&sort_by=created_at&order=asc`,className:"text-[10px] text-red-400 hover:text-red-500 hover:underline",onClick:m=>m.stopPropagation(),children:[c," past SLA — view oldest first →"]})]})]})}function qe(){const t=["ROLE","TARGET/H","PENDING","ACTIVE","RESOLVED","P99 WAIT","P99 WORK","TREND"];return e.jsx("div",{className:"grid items-center gap-4 py-1.5 border-b border-surface-border mb-0.5",style:{gridTemplateColumns:"1fr 64px 56px 56px 60px 72px 72px 104px"},children:t.map((n,l)=>e.jsx("span",{className:`text-[9px] font-semibold uppercase tracking-wider text-text-quaternary ${l>0&&l<7?"text-right":""}`,children:n},n))})}function Oe(){var M;const[t,n]=R.useState("24h"),[l,i]=R.useState(null),[o,x]=R.useState(!1);oe();const{data:a,isLoading:c,refetch:u}=le(),{data:r,isLoading:d,refetch:g}=ee(t),y=(a==null?void 0:a.roles)??[],m=(r==null?void 0:r.stations)??[],f=R.useMemo(()=>Me(y),[y]),S=R.useMemo(()=>f.map(({role:p})=>({role:p.role,title:p.title,parent_role:p.parent_role,target_per_hour:p.target_per_hour??null,metric:m.find($=>$.role===p.role)})),[f,m]),j=((M=f.find(({role:p})=>p.role===l))==null?void 0:M.role)??null,k=c||d,C=async()=>{x(!0),await Promise.all([u(),g()]),x(!1)},_=p=>i($=>$===p?null:p),E=e.jsx("div",{className:"flex items-center gap-0.5",children:we.map(p=>e.jsx("button",{onClick:()=>n(p),className:`px-2.5 py-1 text-[10px] font-mono rounded transition-colors ${t===p?"bg-accent/10 text-accent font-semibold":"text-text-quaternary hover:text-text-secondary"}`,children:p},p))});return e.jsxs("div",{className:"flex flex-col flex-1 min-h-0",children:[e.jsx(ie,{title:"Operations",docsHash:"#docs:dashboard.md:operations",center:E,actions:e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{onClick:C,disabled:o,className:"text-text-quaternary hover:text-text-secondary transition-colors disabled:opacity-40",children:e.jsx(xe,{className:`w-3.5 h-3.5 ${o?"animate-spin":""}`})}),e.jsx(L,{to:"/admin/roles",className:"btn-secondary text-xs",children:"Configure"})]})}),k?e.jsxs("div",{className:"animate-pulse space-y-6 mt-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-64 bg-surface-sunken rounded w-full"}),e.jsx("div",{className:"space-y-3",children:[1,2,3].map(p=>e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-full"},p))})]}):f.length===0?e.jsxs("div",{className:"mt-8",children:[e.jsx("p",{className:"text-sm text-text-secondary mb-2",children:"Stations appear here once roles are marked visible in Operations."}),e.jsxs("p",{className:"text-xs text-text-tertiary",children:["Go to"," ",e.jsx(L,{to:"/admin/roles",className:"text-accent hover:underline",children:"Roles"})," ","and enable ",e.jsx("strong",{children:"Visible in Operations"})," on roles that represent pipeline stations."]})]}):e.jsxs("div",{className:"flex flex-col flex-1 min-h-0",children:[e.jsxs("div",{className:"flex-1 min-h-0 flex items-stretch overflow-hidden",children:[e.jsx("div",{className:"flex-1 min-w-0 min-h-0 flex flex-col justify-center overflow-hidden p-4",children:e.jsx(ge,{stations:S,selectedRole:l,onSelect:_,periodHours:Se[t]})}),e.jsx("div",{className:"w-px bg-surface-border shrink-0 self-stretch"}),e.jsx($e,{role:j,allMetrics:m,orderedRoles:f.map(p=>p.role),globalPeriod:t,onClose:()=>i(null)})]}),e.jsxs("div",{className:"h-[30vh] flex-none flex flex-col border-t border-surface-border overflow-hidden",children:[e.jsx(qe,{}),e.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto divide-y divide-surface-border/30",children:f.map(({role:p})=>e.jsx(_e,{role:p,metric:m.find($=>$.role===p.role),selected:l===p.role,onClick:()=>_(p.role)},p.role))})]})]})]})}export{Oe as OperationsPage};
2
- //# sourceMappingURL=OperationsPage-CAmiFHI6.js.map