@hotmeshio/long-tail 0.8.3 → 0.8.4

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 (193) hide show
  1. package/build/api/escalations/create.js +19 -60
  2. package/build/api/escalations/resolve.js +12 -4
  3. package/build/api/escalations/single.js +8 -4
  4. package/build/api/roles.d.ts +1 -0
  5. package/build/scripts/twin-farm.d.ts +12 -13
  6. package/build/scripts/twin-farm.js +61 -75
  7. package/build/services/escalation/queries.d.ts +13 -0
  8. package/build/services/escalation/queries.js +27 -0
  9. package/build/services/orchestrator/condition.d.ts +18 -12
  10. package/build/services/orchestrator/condition.js +11 -8
  11. package/build/services/role/index.d.ts +1 -2
  12. package/build/services/role/index.js +1 -2
  13. package/build/services/role/schema-validator.d.ts +9 -0
  14. package/build/services/role/schema-validator.js +42 -0
  15. package/build/services/role/sql.d.ts +1 -1
  16. package/build/services/role/sql.js +2 -2
  17. package/build/services/role/types.d.ts +8 -6
  18. package/build/system/mcp-servers/admin/schemas.js +4 -2
  19. package/build/system/mcp-servers/human-queue-schemas.js +6 -2
  20. package/build/system/mcp-servers/human-queue.js +11 -3
  21. package/build/tsconfig.tsbuildinfo +1 -1
  22. package/build/types/config.d.ts +2 -0
  23. package/build/types/startup.d.ts +5 -1
  24. package/dashboard/dist/assets/{AdminDashboard-Dhr7sUq6.js → AdminDashboard-B-JptT54.js} +2 -2
  25. package/dashboard/dist/assets/{AdminDashboard-Dhr7sUq6.js.map → AdminDashboard-B-JptT54.js.map} +1 -1
  26. package/dashboard/dist/assets/{AgentConfigPage-CUPm8FjH.js → AgentConfigPage-B1Jc6iJk.js} +2 -2
  27. package/dashboard/dist/assets/{AgentConfigPage-CUPm8FjH.js.map → AgentConfigPage-B1Jc6iJk.js.map} +1 -1
  28. package/dashboard/dist/assets/{AgentDetailPage-C9Tmmhhc.js → AgentDetailPage-CWpclCOY.js} +2 -2
  29. package/dashboard/dist/assets/{AgentDetailPage-C9Tmmhhc.js.map → AgentDetailPage-CWpclCOY.js.map} +1 -1
  30. package/dashboard/dist/assets/{AgentsPage-Pj47ZJ2U.js → AgentsPage-DyAf5GA2.js} +2 -2
  31. package/dashboard/dist/assets/{AgentsPage-Pj47ZJ2U.js.map → AgentsPage-DyAf5GA2.js.map} +1 -1
  32. package/dashboard/dist/assets/{AvailableEscalationsPage-BxakNqha.js → AvailableEscalationsPage-DDwAgklw.js} +2 -2
  33. package/dashboard/dist/assets/{AvailableEscalationsPage-BxakNqha.js.map → AvailableEscalationsPage-DDwAgklw.js.map} +1 -1
  34. package/dashboard/dist/assets/{BotPicker-CCIo5ic0.js → BotPicker-B1RdaNuY.js} +2 -2
  35. package/dashboard/dist/assets/{BotPicker-CCIo5ic0.js.map → BotPicker-B1RdaNuY.js.map} +1 -1
  36. package/dashboard/dist/assets/{CapabilitiesPage-XFVq4iPX.js → CapabilitiesPage-CxHDnyjX.js} +2 -2
  37. package/dashboard/dist/assets/{CapabilitiesPage-XFVq4iPX.js.map → CapabilitiesPage-CxHDnyjX.js.map} +1 -1
  38. package/dashboard/dist/assets/{CollapsibleSection-CBKICsCu.js → CollapsibleSection-Ds0e8cCO.js} +2 -2
  39. package/dashboard/dist/assets/{CollapsibleSection-CBKICsCu.js.map → CollapsibleSection-Ds0e8cCO.js.map} +1 -1
  40. package/dashboard/dist/assets/{CredentialsPage-BN_d7ybc.js → CredentialsPage-D8UVO-3A.js} +2 -2
  41. package/dashboard/dist/assets/{CredentialsPage-BN_d7ybc.js.map → CredentialsPage-D8UVO-3A.js.map} +1 -1
  42. package/dashboard/dist/assets/{CronLabel-4V_EXMUg.js → CronLabel-ZesYXxCH.js} +2 -2
  43. package/dashboard/dist/assets/{CronLabel-4V_EXMUg.js.map → CronLabel-ZesYXxCH.js.map} +1 -1
  44. package/dashboard/dist/assets/{CustomDurationPicker-BdurQj-B.js → CustomDurationPicker-OKlwH5a_.js} +2 -2
  45. package/dashboard/dist/assets/{CustomDurationPicker-BdurQj-B.js.map → CustomDurationPicker-OKlwH5a_.js.map} +1 -1
  46. package/dashboard/dist/assets/{ElapsedCell-CO8XMSJX.js → ElapsedCell-CO8S6vdb.js} +2 -2
  47. package/dashboard/dist/assets/{ElapsedCell-CO8XMSJX.js.map → ElapsedCell-CO8S6vdb.js.map} +1 -1
  48. package/dashboard/dist/assets/{EscalationSchemaPage-hzFtuyha.js → EscalationSchemaPage-Br5sD8-N.js} +2 -2
  49. package/dashboard/dist/assets/{EscalationSchemaPage-hzFtuyha.js.map → EscalationSchemaPage-Br5sD8-N.js.map} +1 -1
  50. package/dashboard/dist/assets/{EscalationsOverview-qlo0mxxa.js → EscalationsOverview-DMpRvVql.js} +2 -2
  51. package/dashboard/dist/assets/{EscalationsOverview-qlo0mxxa.js.map → EscalationsOverview-DMpRvVql.js.map} +1 -1
  52. package/dashboard/dist/assets/{EventTable-D9RjAT_a.js → EventTable-BARJeiS2.js} +2 -2
  53. package/dashboard/dist/assets/{EventTable-D9RjAT_a.js.map → EventTable-BARJeiS2.js.map} +1 -1
  54. package/dashboard/dist/assets/{GraphInvokePage-CU3r2tzN.js → GraphInvokePage-CTuul-av.js} +2 -2
  55. package/dashboard/dist/assets/{GraphInvokePage-CU3r2tzN.js.map → GraphInvokePage-CTuul-av.js.map} +1 -1
  56. package/dashboard/dist/assets/{HomePage-B7pb4-Gx.js → HomePage-BKFpOaHX.js} +2 -2
  57. package/dashboard/dist/assets/{HomePage-B7pb4-Gx.js.map → HomePage-BKFpOaHX.js.map} +1 -1
  58. package/dashboard/dist/assets/{ListToolbar-C_64Y22Z.js → ListToolbar-Dz2_neVF.js} +2 -2
  59. package/dashboard/dist/assets/{ListToolbar-C_64Y22Z.js.map → ListToolbar-Dz2_neVF.js.map} +1 -1
  60. package/dashboard/dist/assets/{McpOverview-D-4eSOTc.js → McpOverview-Do-vaEqV.js} +2 -2
  61. package/dashboard/dist/assets/{McpOverview-D-4eSOTc.js.map → McpOverview-Do-vaEqV.js.map} +1 -1
  62. package/dashboard/dist/assets/{McpQueryDetailPage-BYwDb8LC.js → McpQueryDetailPage-HwDGNSbN.js} +2 -2
  63. package/dashboard/dist/assets/{McpQueryDetailPage-BYwDb8LC.js.map → McpQueryDetailPage-HwDGNSbN.js.map} +1 -1
  64. package/dashboard/dist/assets/{McpQueryPage-CIijB5t2.js → McpQueryPage-DxMUpQ7R.js} +2 -2
  65. package/dashboard/dist/assets/{McpQueryPage-CIijB5t2.js.map → McpQueryPage-DxMUpQ7R.js.map} +1 -1
  66. package/dashboard/dist/assets/{McpRunDetailPage-CQmhfgju.js → McpRunDetailPage-BzzZIXIk.js} +2 -2
  67. package/dashboard/dist/assets/{McpRunDetailPage-CQmhfgju.js.map → McpRunDetailPage-BzzZIXIk.js.map} +1 -1
  68. package/dashboard/dist/assets/{McpRunsPage-CdDyeOdi.js → McpRunsPage-D3J42d2i.js} +2 -2
  69. package/dashboard/dist/assets/{McpRunsPage-CdDyeOdi.js.map → McpRunsPage-D3J42d2i.js.map} +1 -1
  70. package/dashboard/dist/assets/{OperationsPage-Cz_0K8tj.js → OperationsPage-BaM-QDhy.js} +2 -2
  71. package/dashboard/dist/assets/{OperationsPage-Cz_0K8tj.js.map → OperationsPage-BaM-QDhy.js.map} +1 -1
  72. package/dashboard/dist/assets/{OperatorDashboard-DFORmS46.js → OperatorDashboard-BqJn8wEZ.js} +2 -2
  73. package/dashboard/dist/assets/{OperatorDashboard-DFORmS46.js.map → OperatorDashboard-BqJn8wEZ.js.map} +1 -1
  74. package/dashboard/dist/assets/{ProcessDetailPage-DwypVFE2.js → ProcessDetailPage-C8sT2xuE.js} +2 -2
  75. package/dashboard/dist/assets/{ProcessDetailPage-DwypVFE2.js.map → ProcessDetailPage-C8sT2xuE.js.map} +1 -1
  76. package/dashboard/dist/assets/{ProcessesListPage-tNLvALwT.js → ProcessesListPage-eA8buUgd.js} +2 -2
  77. package/dashboard/dist/assets/{ProcessesListPage-tNLvALwT.js.map → ProcessesListPage-eA8buUgd.js.map} +1 -1
  78. package/dashboard/dist/assets/{RoleDetailPage-BPsrUqIa.js → RoleDetailPage-aR-ror5v.js} +2 -2
  79. package/dashboard/dist/assets/{RoleDetailPage-BPsrUqIa.js.map → RoleDetailPage-aR-ror5v.js.map} +1 -1
  80. package/dashboard/dist/assets/{RolesPage-txueySPI.js → RolesPage-BQNQxsMY.js} +2 -2
  81. package/dashboard/dist/assets/{RolesPage-txueySPI.js.map → RolesPage-BQNQxsMY.js.map} +1 -1
  82. package/dashboard/dist/assets/{RunAsSelector-Bvefon9B.js → RunAsSelector-mhusrlYE.js} +2 -2
  83. package/dashboard/dist/assets/{RunAsSelector-Bvefon9B.js.map → RunAsSelector-mhusrlYE.js.map} +1 -1
  84. package/dashboard/dist/assets/{StickyPagination-CeQCzVIA.js → StickyPagination-CqT9Q1I6.js} +2 -2
  85. package/dashboard/dist/assets/{StickyPagination-CeQCzVIA.js.map → StickyPagination-CqT9Q1I6.js.map} +1 -1
  86. package/dashboard/dist/assets/{StreamMessageDetail-6wEm6o6t.js → StreamMessageDetail-jOJaoupp.js} +2 -2
  87. package/dashboard/dist/assets/{StreamMessageDetail-6wEm6o6t.js.map → StreamMessageDetail-jOJaoupp.js.map} +1 -1
  88. package/dashboard/dist/assets/{SwimlaneTimeline-C97Rldf5.js → SwimlaneTimeline-BrfmWgAj.js} +2 -2
  89. package/dashboard/dist/assets/{SwimlaneTimeline-C97Rldf5.js.map → SwimlaneTimeline-BrfmWgAj.js.map} +1 -1
  90. package/dashboard/dist/assets/{TaskDetailPage-B09mLOCE.js → TaskDetailPage-Cus_JHIC.js} +2 -2
  91. package/dashboard/dist/assets/{TaskDetailPage-B09mLOCE.js.map → TaskDetailPage-Cus_JHIC.js.map} +1 -1
  92. package/dashboard/dist/assets/{TasksListPage-CQhiSGkN.js → TasksListPage-B7GK8AoX.js} +2 -2
  93. package/dashboard/dist/assets/{TasksListPage-CQhiSGkN.js.map → TasksListPage-B7GK8AoX.js.map} +1 -1
  94. package/dashboard/dist/assets/{TimeAgo-BkyeBcMF.js → TimeAgo-C606LzT3.js} +2 -2
  95. package/dashboard/dist/assets/{TimeAgo-BkyeBcMF.js.map → TimeAgo-C606LzT3.js.map} +1 -1
  96. package/dashboard/dist/assets/{TimestampCell-DVp_UdEB.js → TimestampCell-hfh6zvH-.js} +2 -2
  97. package/dashboard/dist/assets/{TimestampCell-DVp_UdEB.js.map → TimestampCell-hfh6zvH-.js.map} +1 -1
  98. package/dashboard/dist/assets/{ToolTestPanel-CZOuRP_i.js → ToolTestPanel-CsPsyvQq.js} +2 -2
  99. package/dashboard/dist/assets/{ToolTestPanel-CZOuRP_i.js.map → ToolTestPanel-CsPsyvQq.js.map} +1 -1
  100. package/dashboard/dist/assets/{TopicDetailPage-0mvQde8R.js → TopicDetailPage-Diz6r7zr.js} +2 -2
  101. package/dashboard/dist/assets/{TopicDetailPage-0mvQde8R.js.map → TopicDetailPage-Diz6r7zr.js.map} +1 -1
  102. package/dashboard/dist/assets/{TopicsPage-DkvsHLLe.js → TopicsPage-B6fn7fFh.js} +2 -2
  103. package/dashboard/dist/assets/{TopicsPage-DkvsHLLe.js.map → TopicsPage-B6fn7fFh.js.map} +1 -1
  104. package/dashboard/dist/assets/{UserName-EaD_Tz9y.js → UserName-Cc-5QH5q.js} +2 -2
  105. package/dashboard/dist/assets/{UserName-EaD_Tz9y.js.map → UserName-Cc-5QH5q.js.map} +1 -1
  106. package/dashboard/dist/assets/{WorkflowExecutionPage-CjMSQ9Bb.js → WorkflowExecutionPage-BFq7UdhA.js} +2 -2
  107. package/dashboard/dist/assets/{WorkflowExecutionPage-CjMSQ9Bb.js.map → WorkflowExecutionPage-BFq7UdhA.js.map} +1 -1
  108. package/dashboard/dist/assets/{WorkflowsDashboard-BgUz_jO6.js → WorkflowsDashboard-CHCM6hUa.js} +2 -2
  109. package/dashboard/dist/assets/{WorkflowsDashboard-BgUz_jO6.js.map → WorkflowsDashboard-CHCM6hUa.js.map} +1 -1
  110. package/dashboard/dist/assets/{WorkflowsOverview-BzUjUfAz.js → WorkflowsOverview-WeOvl8-_.js} +2 -2
  111. package/dashboard/dist/assets/{WorkflowsOverview-BzUjUfAz.js.map → WorkflowsOverview-WeOvl8-_.js.map} +1 -1
  112. package/dashboard/dist/assets/{YamlWorkflowDetailPage-C48Ui513.js → YamlWorkflowDetailPage-6GfhVDpv.js} +2 -2
  113. package/dashboard/dist/assets/{YamlWorkflowDetailPage-C48Ui513.js.map → YamlWorkflowDetailPage-6GfhVDpv.js.map} +1 -1
  114. package/dashboard/dist/assets/{YamlWorkflowsPage-CQ-dk84N.js → YamlWorkflowsPage-DqcY3ocP.js} +2 -2
  115. package/dashboard/dist/assets/{YamlWorkflowsPage-CQ-dk84N.js.map → YamlWorkflowsPage-DqcY3ocP.js.map} +1 -1
  116. package/dashboard/dist/assets/{agents-O1GlQE9p.js → agents-DlzvjNxE.js} +2 -2
  117. package/dashboard/dist/assets/{agents-O1GlQE9p.js.map → agents-DlzvjNxE.js.map} +1 -1
  118. package/dashboard/dist/assets/{bots-DB5Z7MEk.js → bots-CWMN-KUI.js} +2 -2
  119. package/dashboard/dist/assets/{bots-DB5Z7MEk.js.map → bots-CWMN-KUI.js.map} +1 -1
  120. package/dashboard/dist/assets/{capabilities-Du9EfNdT.js → capabilities-_WSZ7cxO.js} +2 -2
  121. package/dashboard/dist/assets/{capabilities-Du9EfNdT.js.map → capabilities-_WSZ7cxO.js.map} +1 -1
  122. package/dashboard/dist/assets/{controlplane-ku4V2nVk.js → controlplane-_k9nWZy3.js} +2 -2
  123. package/dashboard/dist/assets/{controlplane-ku4V2nVk.js.map → controlplane-_k9nWZy3.js.map} +1 -1
  124. package/dashboard/dist/assets/{escalation-oVVz7Lw2.js → escalation-CK8ElYzD.js} +2 -2
  125. package/dashboard/dist/assets/{escalation-oVVz7Lw2.js.map → escalation-CK8ElYzD.js.map} +1 -1
  126. package/dashboard/dist/assets/{escalation-columns-C-xaR_Qg.js → escalation-columns-Dyrf6Tdp.js} +2 -2
  127. package/dashboard/dist/assets/{escalation-columns-C-xaR_Qg.js.map → escalation-columns-Dyrf6Tdp.js.map} +1 -1
  128. package/dashboard/dist/assets/{index-DsKK1fCc.js → index-BiApvBEz.js} +2 -2
  129. package/dashboard/dist/assets/{index-DsKK1fCc.js.map → index-BiApvBEz.js.map} +1 -1
  130. package/dashboard/dist/assets/{index-61yKjTkk.js → index-C3h0qGzG.js} +2 -2
  131. package/dashboard/dist/assets/{index-61yKjTkk.js.map → index-C3h0qGzG.js.map} +1 -1
  132. package/dashboard/dist/assets/{index-CxlsLEr1.js → index-C5Yc3TT7.js} +2 -2
  133. package/dashboard/dist/assets/{index-CxlsLEr1.js.map → index-C5Yc3TT7.js.map} +1 -1
  134. package/dashboard/dist/assets/{index-Bcgsf2yD.js → index-C7VXLKJs.js} +2 -2
  135. package/dashboard/dist/assets/{index-Bcgsf2yD.js.map → index-C7VXLKJs.js.map} +1 -1
  136. package/dashboard/dist/assets/{index-DkZnXWTA.js → index-D8GcHxNJ.js} +2 -2
  137. package/dashboard/dist/assets/{index-DkZnXWTA.js.map → index-D8GcHxNJ.js.map} +1 -1
  138. package/dashboard/dist/assets/{index-DcZDyLHC.js → index-Dc-Ye2wk.js} +2 -2
  139. package/dashboard/dist/assets/{index-DcZDyLHC.js.map → index-Dc-Ye2wk.js.map} +1 -1
  140. package/dashboard/dist/assets/{index-CyXilW90.js → index-Dd4Si1Rl.js} +2 -2
  141. package/dashboard/dist/assets/{index-CyXilW90.js.map → index-Dd4Si1Rl.js.map} +1 -1
  142. package/dashboard/dist/assets/{index-MbEPoyTe.js → index-DwWP-Z_c.js} +3 -3
  143. package/dashboard/dist/assets/{index-MbEPoyTe.js.map → index-DwWP-Z_c.js.map} +1 -1
  144. package/dashboard/dist/assets/index-Ex5tEte8.js +5 -0
  145. package/dashboard/dist/assets/index-Ex5tEte8.js.map +1 -0
  146. package/dashboard/dist/assets/index-OV1JoB_P.js +6 -0
  147. package/dashboard/dist/assets/index-OV1JoB_P.js.map +1 -0
  148. package/dashboard/dist/assets/{index-BwXtXaw8.js → index-ZBj6Xgim.js} +2 -2
  149. package/dashboard/dist/assets/{index-BwXtXaw8.js.map → index-ZBj6Xgim.js.map} +1 -1
  150. package/dashboard/dist/assets/{index-DgAmkmtv.js → index-kKufOPLo.js} +2 -2
  151. package/dashboard/dist/assets/{index-DgAmkmtv.js.map → index-kKufOPLo.js.map} +1 -1
  152. package/dashboard/dist/assets/{index-aHhMmZ0W.js → index-uUYXmvFf.js} +2 -2
  153. package/dashboard/dist/assets/{index-aHhMmZ0W.js.map → index-uUYXmvFf.js.map} +1 -1
  154. package/dashboard/dist/assets/{knowledge-CTjJVvIG.js → knowledge-fYoEF4Lx.js} +2 -2
  155. package/dashboard/dist/assets/{knowledge-CTjJVvIG.js.map → knowledge-fYoEF4Lx.js.map} +1 -1
  156. package/dashboard/dist/assets/{mcp-CsHkygRS.js → mcp-1lzlgtS8.js} +2 -2
  157. package/dashboard/dist/assets/{mcp-CsHkygRS.js.map → mcp-1lzlgtS8.js.map} +1 -1
  158. package/dashboard/dist/assets/{mcp-query-BCt4tpjO.js → mcp-query-nlrAz7m_.js} +2 -2
  159. package/dashboard/dist/assets/{mcp-query-BCt4tpjO.js.map → mcp-query-nlrAz7m_.js.map} +1 -1
  160. package/dashboard/dist/assets/{pipelines-BpGmuS5V.js → pipelines-Ihi7EKXi.js} +2 -2
  161. package/dashboard/dist/assets/{pipelines-BpGmuS5V.js.map → pipelines-Ihi7EKXi.js.map} +1 -1
  162. package/dashboard/dist/assets/{roles-lHLgLwOY.js → roles-D4Wn7kE9.js} +2 -2
  163. package/dashboard/dist/assets/{roles-lHLgLwOY.js.map → roles-D4Wn7kE9.js.map} +1 -1
  164. package/dashboard/dist/assets/{tasks-BHfimh-E.js → tasks-Crk9Y3C4.js} +2 -2
  165. package/dashboard/dist/assets/{tasks-BHfimh-E.js.map → tasks-Crk9Y3C4.js.map} +1 -1
  166. package/dashboard/dist/assets/{topics-BW2cS79q.js → topics-CtfcCyw9.js} +2 -2
  167. package/dashboard/dist/assets/{topics-BW2cS79q.js.map → topics-CtfcCyw9.js.map} +1 -1
  168. package/dashboard/dist/assets/{useEventHooks-CeOIM1Ix.js → useEventHooks-Dn_vN2xE.js} +2 -2
  169. package/dashboard/dist/assets/{useEventHooks-CeOIM1Ix.js.map → useEventHooks-Dn_vN2xE.js.map} +1 -1
  170. package/dashboard/dist/assets/{useNamespace-CEVLPGGY.js → useNamespace-Cfs3vKWk.js} +2 -2
  171. package/dashboard/dist/assets/{useNamespace-CEVLPGGY.js.map → useNamespace-Cfs3vKWk.js.map} +1 -1
  172. package/dashboard/dist/assets/{useYamlActivityEvents-DdRNcy7s.js → useYamlActivityEvents-ymobxH47.js} +2 -2
  173. package/dashboard/dist/assets/{useYamlActivityEvents-DdRNcy7s.js.map → useYamlActivityEvents-ymobxH47.js.map} +1 -1
  174. package/dashboard/dist/assets/{users-CAih5Fty.js → users-DngGIiC_.js} +2 -2
  175. package/dashboard/dist/assets/{users-CAih5Fty.js.map → users-DngGIiC_.js.map} +1 -1
  176. package/dashboard/dist/assets/{workflows-C_gCwB9q.js → workflows-DuUspMAl.js} +2 -2
  177. package/dashboard/dist/assets/{workflows-C_gCwB9q.js.map → workflows-DuUspMAl.js.map} +1 -1
  178. package/dashboard/dist/assets/{yaml-workflows-CPabf8Sf.js → yaml-workflows-DXQCiOsP.js} +2 -2
  179. package/dashboard/dist/assets/{yaml-workflows-CPabf8Sf.js.map → yaml-workflows-DXQCiOsP.js.map} +1 -1
  180. package/dashboard/dist/index.html +1 -1
  181. package/docs/api/http/escalations.md +8 -4
  182. package/docs/api/http/roles.md +1 -1
  183. package/docs/api/http/workflows.md +1 -1
  184. package/docs/api/sdk/escalations.md +3 -3
  185. package/docs/api/sdk/workflows.md +1 -1
  186. package/docs/dashboard.md +2 -2
  187. package/docs/data.md +1 -1
  188. package/docs/hitl-guide.md +2 -3
  189. package/package.json +1 -1
  190. package/dashboard/dist/assets/index-CAzFU1rn.js +0 -9
  191. package/dashboard/dist/assets/index-CAzFU1rn.js.map +0 -1
  192. package/dashboard/dist/assets/index-D0bDVU2a.js +0 -6
  193. package/dashboard/dist/assets/index-D0bDVU2a.js.map +0 -1
@@ -32,34 +32,13 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.createEscalation = createEscalation;
40
- const ajv_1 = __importDefault(require("ajv"));
41
37
  const escalationService = __importStar(require("../../services/escalation"));
42
38
  const roleService = __importStar(require("../../services/role"));
39
+ const schema_validator_1 = require("../../services/role/schema-validator");
43
40
  const escalation_1 = require("../../types/escalation");
44
41
  const helpers_1 = require("./helpers");
45
- // strict: false — role metadata_schemas use the project's house extension
46
- // keywords (x-lt-widget etc., the same style as seeded form_schemas); Ajv 8's
47
- // strict mode throws at compile on any unknown keyword, which would turn every
48
- // metadata-bearing create for that role into a 500.
49
- const ajv = new ajv_1.default({ allErrors: true, strict: false });
50
- // Compiled validators cached per role+version, keyed by the schema's serialized
51
- // form so an admin's schema edit invalidates the stale validator on the next
52
- // create. Pinned versions are immutable, but the same key logic covers both.
53
- const validatorCache = new Map();
54
- function compileRoleValidator(cacheId, schema) {
55
- const key = JSON.stringify(schema);
56
- const cached = validatorCache.get(cacheId);
57
- if (cached && cached.key === key)
58
- return cached.validate;
59
- const validate = ajv.compile(schema);
60
- validatorCache.set(cacheId, { key, validate });
61
- return validate;
62
- }
63
42
  // ── Create ────────────────────────────────────────────────────────────────
64
43
  /**
65
44
  * Create a standalone escalation (not tied to a workflow).
@@ -94,46 +73,26 @@ async function createEscalation(input, auth) {
94
73
  if (denied) {
95
74
  return { status: 403, error: `You must have write access to the "${role}" role or be a superadmin to create escalations for it` };
96
75
  }
97
- // Validate metadata against the role's declared schema (if any). A
98
- // metadata.schema_version pin selects that immutable snapshot; a pin that
99
- // names a missing version is a 400, never a silent fall-through to latest.
100
- if (input.metadata) {
101
- const pinned = input.metadata[escalation_1.ESCALATION_METADATA_KEYS.SCHEMA_VERSION];
102
- if (pinned !== undefined && (!Number.isInteger(pinned) || pinned < 1)) {
103
- return { status: 400, error: `metadata.${escalation_1.ESCALATION_METADATA_KEYS.SCHEMA_VERSION} must be a positive integer` };
104
- }
105
- let schema;
106
- if (pinned !== undefined) {
107
- const snapshot = await roleService.getRoleSchema(role, pinned);
108
- if (!snapshot) {
109
- return { status: 400, error: `Schema version ${pinned} does not exist for role "${role}"` };
110
- }
111
- schema = snapshot.metadata_schema;
76
+ // Validate the caller-supplied metadata against the role's metadata_schema
77
+ // (the queryable-facet contract). An escalation may carry a form-version pin
78
+ // (metadata.schema_version) chosen by the workflow author; that is a system
79
+ // key, not caller data, so it is excluded from validation. No version is
80
+ // stamped here — omitting it means the resolve UI renders the role's latest
81
+ // form at fetch time.
82
+ const metadata = input.metadata ?? {};
83
+ const metadataSchema = await roleService.getRoleMetadataSchema(role);
84
+ if (metadataSchema) {
85
+ let validate;
86
+ try {
87
+ validate = (0, schema_validator_1.compileSchemaValidator)(`${role}:meta`, metadataSchema);
112
88
  }
113
- else {
114
- schema = await roleService.getRoleMetadataSchema(role);
89
+ catch (compileErr) {
90
+ // A broken stored schema is the role admin's bug, not the creator's.
91
+ return { status: 422, error: `metadata_schema for role "${role}" is not a valid JSON Schema: ${compileErr.message}` };
115
92
  }
116
- if (schema) {
117
- let validate;
118
- try {
119
- validate = compileRoleValidator(`${role}@${pinned ?? 'live'}`, schema);
120
- }
121
- catch (compileErr) {
122
- // A broken stored schema is the role admin's bug, not the creator's —
123
- // name it explicitly instead of surfacing an opaque 500.
124
- return {
125
- status: 422,
126
- error: `metadata_schema for role "${role}" is not a valid JSON Schema: ${compileErr.message}`,
127
- };
128
- }
129
- // The pin is a system key, not caller data — strip it so schemas with
130
- // additionalProperties: false still validate the caller's bag.
131
- const { [escalation_1.ESCALATION_METADATA_KEYS.SCHEMA_VERSION]: _pin, ...callerMetadata } = input.metadata;
132
- const valid = validate(callerMetadata);
133
- if (!valid) {
134
- const msgs = (validate.errors ?? []).map((e) => `${e.instancePath || '(root)'} ${e.message}`).join('; ');
135
- return { status: 400, error: `metadata does not match the role schema: ${msgs}` };
136
- }
93
+ const { [escalation_1.ESCALATION_METADATA_KEYS.SCHEMA_VERSION]: _pin, ...callerMetadata } = metadata;
94
+ if (!validate(callerMetadata)) {
95
+ return { status: 400, error: `metadata does not match the role schema: ${(0, schema_validator_1.formatValidationErrors)(validate)}` };
137
96
  }
138
97
  }
139
98
  const escalation = await escalationService.createEscalation({
@@ -60,7 +60,7 @@ const helpers_1 = require("./helpers");
60
60
  */
61
61
  async function resolveEscalation(input, auth) {
62
62
  try {
63
- const { id, resolverPayload, metadata } = input;
63
+ const { id, metadata, resolverPayload } = input;
64
64
  if (!resolverPayload) {
65
65
  return { status: 400, error: 'resolverPayload is required' };
66
66
  }
@@ -82,6 +82,9 @@ async function resolveEscalation(input, auth) {
82
82
  const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
83
83
  if (denied)
84
84
  return denied;
85
+ // The resolver payload is stored exactly as submitted — its shape is the
86
+ // workflow's contract, formed by the caller (the React app maps the form to
87
+ // the payload via x-lt-bind before submitting). No server-side transform.
85
88
  // `metadata` (the outcome patch) is never written separately — it rides WITH the
86
89
  // resolverPayload to whichever path performs the resolve, so it merges inside the
87
90
  // single status='pending'-guarded UPDATE (HotMesh `resolve(metadata)`). Paths that
@@ -133,7 +136,7 @@ async function resolveEscalation(input, auth) {
133
136
  */
134
137
  async function resolveBySignalKey(input, auth) {
135
138
  try {
136
- const { signalKey, resolverPayload, metadata } = input;
139
+ const { signalKey, metadata, resolverPayload } = input;
137
140
  if (!signalKey)
138
141
  return { status: 400, error: 'signalKey is required' };
139
142
  if (!resolverPayload)
@@ -147,6 +150,8 @@ async function resolveBySignalKey(input, auth) {
147
150
  const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
148
151
  if (denied)
149
152
  return { status: 404, error: 'Escalation not found' };
153
+ // The payload is delivered as the signal the parked workflow's condition()
154
+ // receives — stored exactly as submitted (the caller formed its shape).
150
155
  return resolveViaSignalKey(escalation, resolverPayload, metadata);
151
156
  }
152
157
  catch (err) {
@@ -166,12 +171,15 @@ async function resolveBySignalKey(input, auth) {
166
171
  */
167
172
  async function resolveByIds(input, auth) {
168
173
  try {
169
- const { ids, resolverPayload, metadata } = input;
174
+ const { ids, metadata } = input;
170
175
  if (!Array.isArray(ids) || ids.length === 0) {
171
176
  return { status: 400, error: 'ids must be a non-empty array' };
172
177
  }
173
- if (!resolverPayload)
178
+ if (!input.resolverPayload)
174
179
  return { status: 400, error: 'resolverPayload is required' };
180
+ // One pre-shaped payload for a homogeneous group of bookkeeping rows, stored
181
+ // as submitted — the caller forms the payload's shape.
182
+ const resolverPayload = input.resolverPayload;
175
183
  // Bulk resolve ("ack") is a write verb, gated PER ITEM: write_all roles may
176
184
  // resolve any item in the role; write_self roles may resolve only items assigned
177
185
  // to the caller (same rule single-resolve applies via assertWriteAccess). Global
@@ -51,14 +51,18 @@ const helpers_1 = require("./helpers");
51
51
  */
52
52
  async function getEscalation(input, auth) {
53
53
  try {
54
- const escalation = await escalationService.getEscalation(input.id);
55
- if (!escalation) {
54
+ // One query: the escalation plus its role's form, resolved to the version the
55
+ // row pinned (or the role's latest) and embedded as `form_schema`. The resolve
56
+ // UI renders from this without a second schema call, and the form is never
57
+ // stored on the row — it is JOINed in from the roles tables.
58
+ const detail = await escalationService.getEscalationWithFormSchema(input.id);
59
+ if (!detail) {
56
60
  return { status: 404, error: 'Escalation not found' };
57
61
  }
58
- const denied = await (0, helpers_1.assertReadAccess)(auth.userId, escalation);
62
+ const denied = await (0, helpers_1.assertReadAccess)(auth.userId, detail.escalation);
59
63
  if (denied)
60
64
  return denied;
61
- return { status: 200, data: escalation };
65
+ return { status: 200, data: { ...detail.escalation, form_schema: detail.form_schema } };
62
66
  }
63
67
  catch (err) {
64
68
  return { status: 500, error: err.message };
@@ -83,6 +83,7 @@ export declare function updateRole(input: {
83
83
  role: string;
84
84
  title?: string | null;
85
85
  description?: string | null;
86
+ /** The escalation resolve FORM schema (the JIT UI). Fields may carry x-lt-bind. */
86
87
  form_schema?: Record<string, any> | null;
87
88
  metadata_schema?: Record<string, any> | null;
88
89
  properties?: Record<string, any> | null;
@@ -3,16 +3,19 @@
3
3
  *
4
4
  * Spins up the whole DIGITAL side of the physical/digital twin farm from one
5
5
  * command and plays every human part automatically, so a single run exercises
6
- * the full lifecycle: register → advertise → dispatch → print → settle, plus the
7
- * failure detour (cancel service back in the pool). The PHYSICAL side is the
8
- * app's farm-manager backend `mock` by default (self-contained), or `http`
9
- * against a real print-farm-manager host once you wire the office up.
6
+ * the full lifecycle: onboard (register + bind) → advertise → dispatch → print →
7
+ * reconcilesettle, plus the failure detours (autonomous fail inspect, and
8
+ * filament runout change resume). The PHYSICAL side is the app's Bambu
9
+ * backend `BAMBU_BACKEND=mock` by default (a deterministic in-repo simulation),
10
+ * or `http` against a real Farm Manager Server once the office link is wired.
11
+ *
12
+ * Faults are injected deterministically via each order unit's `simOutcome` (the
13
+ * mock honors it), so the service escalations fire on demand — no racing.
10
14
  *
11
15
  * Usage:
12
16
  * npm run twin:farm
13
17
  * FLEET=6 ORDERS=10 UNITS=2 npm run twin:farm
14
- * FAIL_PCT=25 npm run twin:farm # rehearse dead-machineservice
15
- * FARM_MANAGER_URL=http://192.168.1.50:4000 npm run twin:farm # preflight the office link
18
+ * FAIL_PCT=30 FAIL_MODE=filament_runout npm run twin:farm # fault service recover
16
19
  * FLEET=4 ORDERS=4 KEEP=1 npm run twin:farm # leave twins running to poke in the UI
17
20
  *
18
21
  * Env vars:
@@ -24,17 +27,13 @@
24
27
  * FILAMENTS comma-separated filament pool the fleet + orders draw from
25
28
  * (default "pla,petg")
26
29
  * ARRIVAL_S average seconds between order arrivals (default 2)
27
- * FAIL_PCT percent of print jobs to cancel mid-print, exercising the
28
- * service path (default 0). Best-effort: it races the
29
- * physical side, so raise MOCK_PRINT_SECONDS on the app for
30
- * a wider window (see README).
31
- * POLL_MS poll interval ms (default 750)
30
+ * FAIL_PCT percent of orders with one faulted unit (default 0)
31
+ * FAIL_MODE the injected fault: filament_runout | failed (default filament_runout)
32
+ * POLL_MS servicer/registrar poll interval ms (default 750)
32
33
  * BROKER_IDLE idle broker ticks before it self-terminates (default 40)
33
34
  * RUN_BROKER launch a broker (default 1; set 0 if one already runs)
34
35
  * KEEP 1 = leave twins advertising at the end (default 0 = retire
35
36
  * them with a power-down so nothing lingers between runs)
36
- * FARM_MANAGER_URL optional: GET this before running and report reachability,
37
- * a preflight for the office laptop → farm-manager-host link
38
37
  *
39
38
  * Capabilities: each twin is registered with a capability profile (xl/pdac/soft).
40
39
  * By default the fleet is fully capable and only filament routes; set FLEET_CAPS
@@ -4,16 +4,19 @@
4
4
  *
5
5
  * Spins up the whole DIGITAL side of the physical/digital twin farm from one
6
6
  * command and plays every human part automatically, so a single run exercises
7
- * the full lifecycle: register → advertise → dispatch → print → settle, plus the
8
- * failure detour (cancel service back in the pool). The PHYSICAL side is the
9
- * app's farm-manager backend `mock` by default (self-contained), or `http`
10
- * against a real print-farm-manager host once you wire the office up.
7
+ * the full lifecycle: onboard (register + bind) → advertise → dispatch → print →
8
+ * reconcilesettle, plus the failure detours (autonomous fail inspect, and
9
+ * filament runout change resume). The PHYSICAL side is the app's Bambu
10
+ * backend `BAMBU_BACKEND=mock` by default (a deterministic in-repo simulation),
11
+ * or `http` against a real Farm Manager Server once the office link is wired.
12
+ *
13
+ * Faults are injected deterministically via each order unit's `simOutcome` (the
14
+ * mock honors it), so the service escalations fire on demand — no racing.
11
15
  *
12
16
  * Usage:
13
17
  * npm run twin:farm
14
18
  * FLEET=6 ORDERS=10 UNITS=2 npm run twin:farm
15
- * FAIL_PCT=25 npm run twin:farm # rehearse dead-machineservice
16
- * FARM_MANAGER_URL=http://192.168.1.50:4000 npm run twin:farm # preflight the office link
19
+ * FAIL_PCT=30 FAIL_MODE=filament_runout npm run twin:farm # fault service recover
17
20
  * FLEET=4 ORDERS=4 KEEP=1 npm run twin:farm # leave twins running to poke in the UI
18
21
  *
19
22
  * Env vars:
@@ -25,17 +28,13 @@
25
28
  * FILAMENTS comma-separated filament pool the fleet + orders draw from
26
29
  * (default "pla,petg")
27
30
  * ARRIVAL_S average seconds between order arrivals (default 2)
28
- * FAIL_PCT percent of print jobs to cancel mid-print, exercising the
29
- * service path (default 0). Best-effort: it races the
30
- * physical side, so raise MOCK_PRINT_SECONDS on the app for
31
- * a wider window (see README).
32
- * POLL_MS poll interval ms (default 750)
31
+ * FAIL_PCT percent of orders with one faulted unit (default 0)
32
+ * FAIL_MODE the injected fault: filament_runout | failed (default filament_runout)
33
+ * POLL_MS servicer/registrar poll interval ms (default 750)
33
34
  * BROKER_IDLE idle broker ticks before it self-terminates (default 40)
34
35
  * RUN_BROKER launch a broker (default 1; set 0 if one already runs)
35
36
  * KEEP 1 = leave twins advertising at the end (default 0 = retire
36
37
  * them with a power-down so nothing lingers between runs)
37
- * FARM_MANAGER_URL optional: GET this before running and report reachability,
38
- * a preflight for the office laptop → farm-manager-host link
39
38
  *
40
39
  * Capabilities: each twin is registered with a capability profile (xl/pdac/soft).
41
40
  * By default the fleet is fully capable and only filament routes; set FLEET_CAPS
@@ -55,17 +54,17 @@ const UNITS = parseInt(process.env.UNITS || '2', 10);
55
54
  const FILAMENTS = (process.env.FILAMENTS || 'pla,petg').split(',').map((f) => f.trim()).filter(Boolean);
56
55
  const ARRIVAL_S = parseFloat(process.env.ARRIVAL_S || '2');
57
56
  const FAIL_PCT = parseInt(process.env.FAIL_PCT || '0', 10);
57
+ const FAIL_MODE = (process.env.FAIL_MODE || 'filament_runout');
58
58
  const POLL_MS = parseInt(process.env.POLL_MS || '750', 10);
59
59
  const BROKER_IDLE = parseInt(process.env.BROKER_IDLE || '40', 10);
60
60
  const RUN_BROKER = process.env.RUN_BROKER !== '0';
61
61
  const KEEP = process.env.KEEP === '1';
62
- const FARM_MANAGER_URL = process.env.FARM_MANAGER_URL || '';
63
62
  const CAPABILITY_KEYS = ['xl', 'pdac', 'soft'];
64
63
  const MODELS = ['Fab-X1', 'Fab-X1C', 'Fab-P2', 'Fab-Mini'];
65
64
  let token = '';
66
65
  let userId = '';
67
66
  let done = false;
68
- const stats = { registered: 0, dispatched: 0, printed: 0, cancelled: 0, serviced: 0, settled: 0 };
67
+ const stats = { registered: 0, dispatched: 0, printed: 0, faulted: 0, serviced: 0, settled: 0 };
69
68
  function ts() { return new Date().toISOString().slice(11, 19); }
70
69
  function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
71
70
  function ageSeconds(iso) { return (Date.now() - new Date(iso).getTime()) / 1000; }
@@ -115,8 +114,9 @@ async function login() {
115
114
  */
116
115
  const TWIN_ROLE_CONFIG = [
117
116
  { role: 'print-jobs', title: 'Print Jobs', parent_role: null, sla_minutes: 5, target_per_hour: 30, priority_threshold_minutes: 5, description: 'Order demand — one print-job escalation per unit, claimed as a set.' },
118
- { role: 'printer-fleet', title: 'Printer Fleet', parent_role: 'print-jobs', sla_minutes: 5, target_per_hour: 30, priority_threshold_minutes: 5, description: 'Twin availability adverts and in-flight print rows — the machines at work.', upstream_roles: ['print-servicer'] },
119
- { role: 'print-servicer', title: 'Print Servicer', parent_role: null, sla_minutes: 10, target_per_hour: 10, priority_threshold_minutes: 10, description: 'Register unboxed machines and restore ones that fell offline.' },
117
+ { role: 'printer-fleet', title: 'Printer Fleet', parent_role: 'print-jobs', sla_minutes: 5, target_per_hour: 30, priority_threshold_minutes: 5, description: 'Twin availability adverts and in-flight print rows — the machines at work.', upstream_roles: ['print-onboarder', 'print-servicer'] },
118
+ { role: 'print-onboarder', title: 'Print Onboarder', parent_role: null, sla_minutes: 15, target_per_hour: 5, priority_threshold_minutes: 15, description: 'Register + bind a newly unboxed machine.' },
119
+ { role: 'print-servicer', title: 'Print Servicer', parent_role: null, sla_minutes: 10, target_per_hour: 10, priority_threshold_minutes: 10, description: 'Reload filament, inspect failures, restore offline machines.' },
120
120
  ];
121
121
  async function ensureTwinRoles() {
122
122
  const details = await api('GET', '/api/roles/details').catch(() => ({ roles: [] }));
@@ -160,6 +160,10 @@ function planFleet(batchTag) {
160
160
  return { printerId, wfId: printerId, filament, caps, model: MODELS[i % MODELS.length], serialNumber: `SN-${batchTag.slice(-6)}-${i}` };
161
161
  });
162
162
  }
163
+ /** Evenly distribute FAIL_PCT across the order sequence (deterministic). */
164
+ function hasFault(i) {
165
+ return Math.floor(((i + 1) * FAIL_PCT) / 100) > Math.floor((i * FAIL_PCT) / 100);
166
+ }
163
167
  function planOrders(batchTag, fleet) {
164
168
  return Array.from({ length: ORDERS }, (_, i) => {
165
169
  const filament = FILAMENTS[i % FILAMENTS.length];
@@ -171,7 +175,8 @@ function planOrders(batchTag, fleet) {
171
175
  const cap = CAPABILITY_KEYS[i % CAPABILITY_KEYS.length];
172
176
  const capable = pool.filter((t) => t.caps[cap]).length;
173
177
  const require = (i % 2 === 0 && capable >= units) ? { [cap]: true } : {};
174
- return { index: i, wfId: `${batchTag}-o${i}`, orderId: `TWIN-${batchTag.slice(-6)}-${i}`, filament, require, units };
178
+ const fault = hasFault(i) ? { unit: 0, mode: FAIL_MODE } : undefined;
179
+ return { index: i, wfId: `${batchTag}-o${i}`, orderId: `TWIN-${batchTag.slice(-6)}-${i}`, filament, require, units, fault };
175
180
  });
176
181
  }
177
182
  // ── Launch twins + broker + orders ────────────────────────────────────────────
@@ -198,7 +203,10 @@ async function placeOrder(spec) {
198
203
  orderId: spec.orderId,
199
204
  filament: spec.filament,
200
205
  require: spec.require,
201
- units: Array.from({ length: spec.units }, (_, u) => ({ gcodeUrl: `https://example.com/${spec.orderId}-u${u}.gcode` })),
206
+ units: Array.from({ length: spec.units }, (_, u) => ({
207
+ gcodeUrl: `https://example.com/${spec.orderId}-u${u}.gcode`,
208
+ ...(spec.fault && spec.fault.unit === u ? { simOutcome: spec.fault.mode } : {}),
209
+ })),
202
210
  operatorId: userId,
203
211
  },
204
212
  metadata: { source: 'twin-farm' },
@@ -209,7 +217,7 @@ async function placeOrder(spec) {
209
217
  async function registerLoop(fleet) {
210
218
  while (!done) {
211
219
  try {
212
- const resp = await api('GET', '/api/escalations/available?role=print-servicer&subtype=registering&limit=100');
220
+ const resp = await api('GET', '/api/escalations/available?role=print-onboarder&subtype=registering&limit=100');
213
221
  for (const esc of resp?.escalations ?? []) {
214
222
  const spec = fleet.get(String(esc.metadata?.printerId ?? ''));
215
223
  if (!spec)
@@ -238,17 +246,31 @@ async function registerLoop(fleet) {
238
246
  await sleep(POLL_MS);
239
247
  }
240
248
  }
241
- /** Resolve service escalations the servicer realigning a downed machine. */
249
+ /** The servicer's resolution for each escalation subtype the twin raises. Reloads
250
+ * the SAME filament the machine had (from the escalation metadata) so a service
251
+ * visit restores the machine rather than repurposing it. */
252
+ function servicePayload(esc) {
253
+ const subtype = esc.subtype;
254
+ if (subtype === 'filament_change')
255
+ return { filamentLoaded: esc.metadata?.filament || 'pla', notes: 'Reloaded by twin-farm' };
256
+ if (subtype === 'failure_inspect')
257
+ return { action: 'reset', notes: 'Inspected by twin-farm' };
258
+ if (subtype === 'offline_investigate')
259
+ return { action: 'checked', notes: 'Reconnected by twin-farm' };
260
+ return { action: 'restored', notes: 'Auto-serviced by twin-farm' };
261
+ }
262
+ /** Resolve every servicer escalation (except registration) — filament changes,
263
+ * failure inspections, offline investigations — the way a technician would. */
242
264
  async function serviceLoop() {
243
265
  while (!done) {
244
266
  try {
245
- const resp = await api('GET', '/api/escalations/available?role=print-servicer&subtype=service&limit=100');
267
+ const resp = await api('GET', '/api/escalations/available?role=print-servicer&limit=100');
246
268
  for (const esc of resp?.escalations ?? []) {
247
269
  await api('POST', `/api/escalations/${esc.id}/resolve`, {
248
- resolverPayload: { action: 'restored', notes: 'Auto-serviced by twin-farm' },
270
+ resolverPayload: servicePayload(esc),
249
271
  }).then(() => {
250
272
  stats.serviced++;
251
- console.log(`[${ts()}] [service] ${esc.metadata?.printerId ?? esc.id.slice(0, 8)} restored [${stats.serviced}]`);
273
+ console.log(`[${ts()}] [service] ${esc.subtype.padEnd(18)} ${esc.metadata?.printerId ?? esc.id.slice(0, 8)} [${stats.serviced}]`);
252
274
  }).catch(() => { });
253
275
  }
254
276
  }
@@ -259,35 +281,6 @@ async function serviceLoop() {
259
281
  await sleep(POLL_MS);
260
282
  }
261
283
  }
262
- /** Cancel a deterministic fraction of in-flight prints — the power-outage /
263
- * dead-machine drill. Best-effort: it races the physical side's resolve. */
264
- async function failLoop() {
265
- if (FAIL_PCT <= 0)
266
- return;
267
- const acted = new Set();
268
- while (!done) {
269
- try {
270
- const resp = await api('GET', '/api/escalations/available?role=printer-fleet&subtype=printing&limit=100');
271
- for (const esc of resp?.escalations ?? []) {
272
- const jobId = String(esc.metadata?.jobId ?? esc.id);
273
- if (acted.has(jobId))
274
- continue;
275
- if (jitter(jobId, 'fail') * 100 >= FAIL_PCT)
276
- continue;
277
- acted.add(jobId);
278
- await api('POST', `/api/escalations/${esc.id}/cancel`).then(() => {
279
- stats.cancelled++;
280
- console.log(`[${ts()}] [FAIL] cancelled print ${jobId} — machine went dark [${stats.cancelled}]`);
281
- }).catch(() => { });
282
- }
283
- }
284
- catch (err) {
285
- logPollErr('fail', err);
286
- }
287
- if (!done)
288
- await sleep(Math.min(POLL_MS, 300));
289
- }
290
- }
291
284
  let lastPollErr = '';
292
285
  function logPollErr(loop, err) {
293
286
  const msg = `[${loop}] ${String(err?.message).slice(0, 80)}`;
@@ -316,7 +309,11 @@ async function orderLoop(orders) {
316
309
  async function settlementWatch(orders) {
317
310
  const settled = new Set();
318
311
  let stalled = 0;
319
- const STALL_S = Math.max(40, ORDERS * (ARRIVAL_S + 8));
312
+ // Poll-driven twins add latency per order (poll pickup + print + report), and
313
+ // a fault adds a service round-trip, so the broker needs generous time to
314
+ // converge (it retries placement every tick via release-on-skip). This is a
315
+ // no-progress watchdog, not a deadline — steady settling keeps resetting it.
316
+ const STALL_S = Math.max(60, ORDERS * (ARRIVAL_S + 14));
320
317
  while (!done) {
321
318
  await sleep(1000);
322
319
  let progressed = false;
@@ -334,8 +331,9 @@ async function settlementWatch(orders) {
334
331
  const ok = outcomes.filter((o) => o.outcome === 'success').length;
335
332
  const bad = outcomes.length - ok;
336
333
  stats.printed += ok;
334
+ stats.faulted += bad;
337
335
  stats.dispatched += outcomes.length;
338
- console.log(`[${ts()}] [settle] ${order.orderId} — ${ok} printed${bad ? `, ${bad} cancelled` : ''} [${stats.settled}/${ORDERS}]`);
336
+ console.log(`[${ts()}] [settle] ${order.orderId} — ${ok} printed${bad ? `, ${bad} faulted` : ''} [${stats.settled}/${ORDERS}]`);
339
337
  }
340
338
  }
341
339
  catch { /* 202 still running */ }
@@ -360,6 +358,10 @@ async function retireTwins(fleet) {
360
358
  console.log(`[${ts()}] KEEP=1 — leaving ${fleet.length} twins advertising`);
361
359
  return;
362
360
  }
361
+ if (stats.settled < ORDERS) {
362
+ console.log(`[${ts()}] ${stats.settled}/${ORDERS} settled — leaving twins up so the broker can finish (rerun with KEEP=1 to keep them)`);
363
+ return;
364
+ }
363
365
  await sleep(2000); // let any last twin re-advertise after a service visit
364
366
  let retired = 0;
365
367
  for (let sweep = 0; sweep < 4 && retired < fleet.length; sweep++) {
@@ -379,21 +381,6 @@ async function retireTwins(fleet) {
379
381
  }
380
382
  console.log(`[${ts()}] Retired ${retired} idle twin${retired === 1 ? '' : 's'}`);
381
383
  }
382
- // ── Optional office-link preflight ────────────────────────────────────────────
383
- async function preflightFarmManager() {
384
- if (!FARM_MANAGER_URL)
385
- return;
386
- process.stdout.write(`[${ts()}] Preflight → ${FARM_MANAGER_URL} … `);
387
- try {
388
- const ctrl = new AbortController();
389
- const t = setTimeout(() => ctrl.abort(), 4000);
390
- const res = await fetch(FARM_MANAGER_URL, { signal: ctrl.signal }).finally(() => clearTimeout(t));
391
- console.log(`reachable (HTTP ${res.status}). Set FARM_MANAGER_BACKEND=http + FARM_MANAGER_BASE_URL on the app to dispatch for real.`);
392
- }
393
- catch (err) {
394
- console.log(`UNREACHABLE (${String(err?.message).slice(0, 60)}). Check the office wifi link — see the README connectivity walkthrough.`);
395
- }
396
- }
397
384
  // ── Main ──────────────────────────────────────────────────────────────────────
398
385
  async function main() {
399
386
  const runId = Math.floor(Date.now() / 1000).toString();
@@ -401,17 +388,16 @@ async function main() {
401
388
  const fleet = planFleet(batchTag);
402
389
  const fleetById = new Map(fleet.map((t) => [t.printerId, t]));
403
390
  console.log(`\n Twin Farm ── ${FLEET} printers, ${ORDERS} orders (≤${UNITS} units), filaments [${FILAMENTS.join(', ')}]`);
404
- console.log(` arrival=${ARRIVAL_S}s poll=${POLL_MS}ms fail=${FAIL_PCT}% broker=${RUN_BROKER ? `on (idle≤${BROKER_IDLE})` : 'external'} keep=${KEEP}`);
391
+ console.log(` arrival=${ARRIVAL_S}s fail=${FAIL_PCT}% (${FAIL_MODE}) broker=${RUN_BROKER ? `on (idle≤${BROKER_IDLE})` : 'external'} keep=${KEEP}`);
405
392
  console.log(` Server: ${BASE_URL}\n`);
406
393
  await login();
407
394
  await ensureTwinRoles();
408
- await preflightFarmManager();
409
- // 1. Launch the fleet — each twin raises a registration escalation and parks.
395
+ // 1. Launch the fleet — each twin onboards (register → bind) then advertises.
410
396
  console.log(`\n[${ts()}] Launching ${FLEET} twins …`);
411
397
  for (const spec of fleet)
412
398
  await startTwin(spec);
413
399
  // 2. Register + service loops start now so twins come online as they appear.
414
- const humanLoops = [registerLoop(fleetById), serviceLoop(), failLoop()];
400
+ const humanLoops = [registerLoop(fleetById), serviceLoop()];
415
401
  // 3. Broker (once the humans are watching).
416
402
  if (RUN_BROKER) {
417
403
  await startBroker(batchTag);
@@ -433,7 +419,7 @@ async function main() {
433
419
  console.log(`\n[${ts()}] Done in ${elapsed}s`);
434
420
  console.log(` Registered: ${stats.registered}/${FLEET}`);
435
421
  console.log(` Orders: ${stats.settled}/${ORDERS} settled`);
436
- console.log(` Units: ${stats.printed} printed${stats.cancelled ? `, ${stats.cancelled} cancelled` : ''} (${stats.dispatched} dispatched)`);
422
+ console.log(` Units: ${stats.printed} printed${stats.faulted ? `, ${stats.faulted} faulted` : ''} (${stats.dispatched} dispatched)`);
437
423
  console.log(` Serviced: ${stats.serviced}`);
438
424
  console.log(`\n Dashboard: ${BASE_URL}/escalations\n`);
439
425
  process.exit(0);
@@ -29,6 +29,19 @@ export declare function searchEscalationsFaceted(opts: {
29
29
  escalations: LTEscalationRecord[];
30
30
  total: number;
31
31
  }>;
32
+ /**
33
+ * Single-query escalation read for the resolve UI: the escalation row PLUS the
34
+ * role's escalation form, resolved to the version the escalation pinned
35
+ * (`metadata.schema_version`) or the role's latest when unpinned, JOINed from the
36
+ * roles tables and returned as `form_schema`. The form is NOT stored on the
37
+ * escalation — it rides in from lt_role_schemas/lt_roles on this one query, so a
38
+ * versioned JIT UI costs no per-row space and one SQL call to render. The version
39
+ * cast is guarded, so a non-numeric pin simply falls through to the latest form.
40
+ */
41
+ export declare function getEscalationWithFormSchema(id: string): Promise<{
42
+ escalation: LTEscalationRecord;
43
+ form_schema: Record<string, any> | null;
44
+ } | null>;
32
45
  /**
33
46
  * Distinct top-level metadata facet KEYS visible to the caller — feeds the faceted
34
47
  * UI's key autocomplete so it only ever offers facets that ACTUALLY exist in the
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.searchEscalationsFaceted = searchEscalationsFaceted;
4
+ exports.getEscalationWithFormSchema = getEscalationWithFormSchema;
4
5
  exports.listFacetKeys = listFacetKeys;
5
6
  exports.getEscalationStats = getEscalationStats;
6
7
  exports.listDistinctTypes = listDistinctTypes;
@@ -143,6 +144,32 @@ async function searchEscalationsFaceted(opts) {
143
144
  ]);
144
145
  return { escalations: (0, map_1.toEscalationRecords)(rows.rows), total: countRows.rows[0]?.total ?? 0 };
145
146
  }
147
+ /**
148
+ * Single-query escalation read for the resolve UI: the escalation row PLUS the
149
+ * role's escalation form, resolved to the version the escalation pinned
150
+ * (`metadata.schema_version`) or the role's latest when unpinned, JOINed from the
151
+ * roles tables and returned as `form_schema`. The form is NOT stored on the
152
+ * escalation — it rides in from lt_role_schemas/lt_roles on this one query, so a
153
+ * versioned JIT UI costs no per-row space and one SQL call to render. The version
154
+ * cast is guarded, so a non-numeric pin simply falls through to the latest form.
155
+ */
156
+ async function getEscalationWithFormSchema(id) {
157
+ await (0, client_1.ensureEscalationCompatView)();
158
+ const pool = (0, db_1.getPool)();
159
+ const { rows } = await pool.query(`SELECT e.*, COALESCE(ps.form_schema, r.form_schema) AS resolved_form_schema
160
+ FROM public.lt_escalations e
161
+ LEFT JOIN public.lt_roles r ON r.role = e.role
162
+ LEFT JOIN public.lt_role_schemas ps
163
+ ON ps.role = e.role
164
+ AND ps.version = CASE WHEN e.metadata->>'schema_version' ~ '^[0-9]+$'
165
+ THEN (e.metadata->>'schema_version')::int END
166
+ WHERE e.id = $1`, [id]);
167
+ if (!rows[0])
168
+ return null;
169
+ const { resolved_form_schema, ...row } = rows[0];
170
+ const [escalation] = (0, map_1.toEscalationRecords)([row]);
171
+ return { escalation, form_schema: resolved_form_schema ?? null };
172
+ }
146
173
  /**
147
174
  * Distinct top-level metadata facet KEYS visible to the caller — feeds the faceted
148
175
  * UI's key autocomplete so it only ever offers facets that ACTUALLY exist in the
@@ -1,10 +1,13 @@
1
1
  import type { Types } from '@hotmeshio/hotmesh';
2
2
  /**
3
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.
4
+ * role form version (lt_role_schemas) the resolve UI renders; it is a
5
+ * compile-time LITERAL the workflow author sets, folded into
6
+ * `metadata.schema_version` before the config reaches the engine, so the pin
7
+ * rides the row's GIN-indexed metadata like any other facet. Folding is a pure
8
+ * transform — no query, no activity — so a pinned `conditionLT` costs exactly
9
+ * what an unpinned one does. Omit it and the resolve UI renders the role's latest
10
+ * form when the escalation is fetched.
8
11
  */
9
12
  export type ConditionEscalationConfig = Types.ConditionQueueConfig & {
10
13
  schemaVersion?: number;
@@ -33,17 +36,20 @@ export type ConditionEscalationConfig = Types.ConditionQueueConfig & {
33
36
  * });
34
37
  * ```
35
38
  *
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.
39
+ * **Pin the role form version — a compile-time literal.** A role's escalation
40
+ * form is versioned (lt_role_schemas); its first form is version 1. Pass the
41
+ * version as a literal, paired with the generic `<T>` payload type, both bumped
42
+ * together when the form evolves. The literal folds into metadata for the same
43
+ * cost as an unpinned wait. Omit it and the resolve UI renders the role's latest
44
+ * form when the escalation is fetched.
41
45
  *
42
46
  * ```typescript
43
- * const decision = await conditionLT<{ approved: boolean; lotNumber: string }>(signalId, {
44
- * role: 'reviewer',
47
+ * import { INTAKE_SCHEMA_VERSION, type IntakeResolverV1 } from './forms';
48
+ *
49
+ * const decision = await conditionLT<IntakeResolverV1>(signalId, {
50
+ * role: INTAKE_ROLE,
45
51
  * description: instructions,
46
- * schemaVersion: 3, // render role schema v3 for this row
52
+ * schemaVersion: INTAKE_SCHEMA_VERSION, // the form version this code is written for
47
53
  * });
48
54
  * ```
49
55
  *