@hotmeshio/long-tail 0.8.3 → 0.8.5

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 (217) hide show
  1. package/build/api/escalations/create.js +19 -60
  2. package/build/api/escalations/index.d.ts +1 -1
  3. package/build/api/escalations/index.js +2 -1
  4. package/build/api/escalations/resolve.d.ts +28 -0
  5. package/build/api/escalations/resolve.js +110 -4
  6. package/build/api/escalations/single.js +8 -4
  7. package/build/api/roles.d.ts +1 -0
  8. package/build/lib/db/index.js +12 -1
  9. package/build/lib/db/migrate.js +27 -0
  10. package/build/modules/defaults.d.ts +2 -0
  11. package/build/modules/defaults.js +3 -1
  12. package/build/routes/escalations/resolve.js +18 -0
  13. package/build/scripts/twin-farm.d.ts +12 -13
  14. package/build/scripts/twin-farm.js +61 -75
  15. package/build/sdk/index.d.ts +8 -0
  16. package/build/sdk/index.js +1 -0
  17. package/build/services/escalation/crud.d.ts +36 -0
  18. package/build/services/escalation/crud.js +53 -0
  19. package/build/services/escalation/queries.d.ts +13 -0
  20. package/build/services/escalation/queries.js +27 -0
  21. package/build/services/maintenance/index.js +22 -0
  22. package/build/services/orchestrator/condition.d.ts +18 -12
  23. package/build/services/orchestrator/condition.js +11 -8
  24. package/build/services/role/index.d.ts +1 -2
  25. package/build/services/role/index.js +1 -2
  26. package/build/services/role/schema-validator.d.ts +9 -0
  27. package/build/services/role/schema-validator.js +42 -0
  28. package/build/services/role/sql.d.ts +1 -1
  29. package/build/services/role/sql.js +2 -2
  30. package/build/services/role/types.d.ts +8 -6
  31. package/build/system/mcp-servers/admin/schemas.js +4 -2
  32. package/build/system/mcp-servers/human-queue-schemas.js +6 -2
  33. package/build/system/mcp-servers/human-queue.js +11 -3
  34. package/build/tsconfig.tsbuildinfo +1 -1
  35. package/build/types/config.d.ts +2 -0
  36. package/build/types/maintenance.d.ts +6 -1
  37. package/build/types/startup.d.ts +5 -1
  38. package/dashboard/dist/assets/{AdminDashboard-Dhr7sUq6.js → AdminDashboard-RrSvb8d-.js} +2 -2
  39. package/dashboard/dist/assets/{AdminDashboard-Dhr7sUq6.js.map → AdminDashboard-RrSvb8d-.js.map} +1 -1
  40. package/dashboard/dist/assets/{AgentConfigPage-CUPm8FjH.js → AgentConfigPage-5RMdI0ku.js} +2 -2
  41. package/dashboard/dist/assets/{AgentConfigPage-CUPm8FjH.js.map → AgentConfigPage-5RMdI0ku.js.map} +1 -1
  42. package/dashboard/dist/assets/{AgentDetailPage-C9Tmmhhc.js → AgentDetailPage-C_-sIBcr.js} +2 -2
  43. package/dashboard/dist/assets/{AgentDetailPage-C9Tmmhhc.js.map → AgentDetailPage-C_-sIBcr.js.map} +1 -1
  44. package/dashboard/dist/assets/{AgentsPage-Pj47ZJ2U.js → AgentsPage-CxNNtSsf.js} +2 -2
  45. package/dashboard/dist/assets/{AgentsPage-Pj47ZJ2U.js.map → AgentsPage-CxNNtSsf.js.map} +1 -1
  46. package/dashboard/dist/assets/{AvailableEscalationsPage-BxakNqha.js → AvailableEscalationsPage-BcbhA6NE.js} +2 -2
  47. package/dashboard/dist/assets/{AvailableEscalationsPage-BxakNqha.js.map → AvailableEscalationsPage-BcbhA6NE.js.map} +1 -1
  48. package/dashboard/dist/assets/{BotPicker-CCIo5ic0.js → BotPicker-BQVz6bye.js} +2 -2
  49. package/dashboard/dist/assets/{BotPicker-CCIo5ic0.js.map → BotPicker-BQVz6bye.js.map} +1 -1
  50. package/dashboard/dist/assets/{CapabilitiesPage-XFVq4iPX.js → CapabilitiesPage-DlZ8SgO3.js} +2 -2
  51. package/dashboard/dist/assets/{CapabilitiesPage-XFVq4iPX.js.map → CapabilitiesPage-DlZ8SgO3.js.map} +1 -1
  52. package/dashboard/dist/assets/{CollapsibleSection-CBKICsCu.js → CollapsibleSection-lo8Qx4zb.js} +2 -2
  53. package/dashboard/dist/assets/{CollapsibleSection-CBKICsCu.js.map → CollapsibleSection-lo8Qx4zb.js.map} +1 -1
  54. package/dashboard/dist/assets/{CredentialsPage-BN_d7ybc.js → CredentialsPage-DJ9DyXsO.js} +2 -2
  55. package/dashboard/dist/assets/{CredentialsPage-BN_d7ybc.js.map → CredentialsPage-DJ9DyXsO.js.map} +1 -1
  56. package/dashboard/dist/assets/{CronLabel-4V_EXMUg.js → CronLabel-o0dbisw1.js} +2 -2
  57. package/dashboard/dist/assets/{CronLabel-4V_EXMUg.js.map → CronLabel-o0dbisw1.js.map} +1 -1
  58. package/dashboard/dist/assets/{CustomDurationPicker-BdurQj-B.js → CustomDurationPicker-CGiwKSVJ.js} +2 -2
  59. package/dashboard/dist/assets/{CustomDurationPicker-BdurQj-B.js.map → CustomDurationPicker-CGiwKSVJ.js.map} +1 -1
  60. package/dashboard/dist/assets/{ElapsedCell-CO8XMSJX.js → ElapsedCell-N22STQcg.js} +2 -2
  61. package/dashboard/dist/assets/{ElapsedCell-CO8XMSJX.js.map → ElapsedCell-N22STQcg.js.map} +1 -1
  62. package/dashboard/dist/assets/EscalationSchemaPage-Zsmewko2.js +9 -0
  63. package/dashboard/dist/assets/EscalationSchemaPage-Zsmewko2.js.map +1 -0
  64. package/dashboard/dist/assets/{EscalationsOverview-qlo0mxxa.js → EscalationsOverview-CE2p70Ct.js} +2 -2
  65. package/dashboard/dist/assets/{EscalationsOverview-qlo0mxxa.js.map → EscalationsOverview-CE2p70Ct.js.map} +1 -1
  66. package/dashboard/dist/assets/{EventTable-D9RjAT_a.js → EventTable-B3Gj7Ayg.js} +2 -2
  67. package/dashboard/dist/assets/{EventTable-D9RjAT_a.js.map → EventTable-B3Gj7Ayg.js.map} +1 -1
  68. package/dashboard/dist/assets/{GraphInvokePage-CU3r2tzN.js → GraphInvokePage-DywWVSdj.js} +2 -2
  69. package/dashboard/dist/assets/{GraphInvokePage-CU3r2tzN.js.map → GraphInvokePage-DywWVSdj.js.map} +1 -1
  70. package/dashboard/dist/assets/{HomePage-B7pb4-Gx.js → HomePage-CxtBiULd.js} +2 -2
  71. package/dashboard/dist/assets/{HomePage-B7pb4-Gx.js.map → HomePage-CxtBiULd.js.map} +1 -1
  72. package/dashboard/dist/assets/{ListToolbar-C_64Y22Z.js → ListToolbar-Ok2O0wmC.js} +2 -2
  73. package/dashboard/dist/assets/{ListToolbar-C_64Y22Z.js.map → ListToolbar-Ok2O0wmC.js.map} +1 -1
  74. package/dashboard/dist/assets/{McpOverview-D-4eSOTc.js → McpOverview-CTCYZXIp.js} +2 -2
  75. package/dashboard/dist/assets/{McpOverview-D-4eSOTc.js.map → McpOverview-CTCYZXIp.js.map} +1 -1
  76. package/dashboard/dist/assets/{McpQueryDetailPage-BYwDb8LC.js → McpQueryDetailPage-BOaXf0bZ.js} +2 -2
  77. package/dashboard/dist/assets/{McpQueryDetailPage-BYwDb8LC.js.map → McpQueryDetailPage-BOaXf0bZ.js.map} +1 -1
  78. package/dashboard/dist/assets/{McpQueryPage-CIijB5t2.js → McpQueryPage-BbYojoHu.js} +2 -2
  79. package/dashboard/dist/assets/{McpQueryPage-CIijB5t2.js.map → McpQueryPage-BbYojoHu.js.map} +1 -1
  80. package/dashboard/dist/assets/{McpRunDetailPage-CQmhfgju.js → McpRunDetailPage-CXGCVmPq.js} +2 -2
  81. package/dashboard/dist/assets/{McpRunDetailPage-CQmhfgju.js.map → McpRunDetailPage-CXGCVmPq.js.map} +1 -1
  82. package/dashboard/dist/assets/{McpRunsPage-CdDyeOdi.js → McpRunsPage-Df1GqxUg.js} +2 -2
  83. package/dashboard/dist/assets/{McpRunsPage-CdDyeOdi.js.map → McpRunsPage-Df1GqxUg.js.map} +1 -1
  84. package/dashboard/dist/assets/OperationsPage-DgZYi-BC.js +2 -0
  85. package/dashboard/dist/assets/OperationsPage-DgZYi-BC.js.map +1 -0
  86. package/dashboard/dist/assets/{OperatorDashboard-DFORmS46.js → OperatorDashboard-DIR7XDeD.js} +2 -2
  87. package/dashboard/dist/assets/{OperatorDashboard-DFORmS46.js.map → OperatorDashboard-DIR7XDeD.js.map} +1 -1
  88. package/dashboard/dist/assets/{ProcessDetailPage-DwypVFE2.js → ProcessDetailPage-BuSi654J.js} +2 -2
  89. package/dashboard/dist/assets/{ProcessDetailPage-DwypVFE2.js.map → ProcessDetailPage-BuSi654J.js.map} +1 -1
  90. package/dashboard/dist/assets/{ProcessesListPage-tNLvALwT.js → ProcessesListPage-Dhik7XpW.js} +2 -2
  91. package/dashboard/dist/assets/{ProcessesListPage-tNLvALwT.js.map → ProcessesListPage-Dhik7XpW.js.map} +1 -1
  92. package/dashboard/dist/assets/RoleDetailPage-BfzqQxMU.js +8 -0
  93. package/dashboard/dist/assets/RoleDetailPage-BfzqQxMU.js.map +1 -0
  94. package/dashboard/dist/assets/{RolesPage-txueySPI.js → RolesPage-BRicD5mx.js} +2 -2
  95. package/dashboard/dist/assets/{RolesPage-txueySPI.js.map → RolesPage-BRicD5mx.js.map} +1 -1
  96. package/dashboard/dist/assets/{RunAsSelector-Bvefon9B.js → RunAsSelector-DipyBf_T.js} +2 -2
  97. package/dashboard/dist/assets/{RunAsSelector-Bvefon9B.js.map → RunAsSelector-DipyBf_T.js.map} +1 -1
  98. package/dashboard/dist/assets/{StickyPagination-CeQCzVIA.js → StickyPagination-CD4W450h.js} +2 -2
  99. package/dashboard/dist/assets/{StickyPagination-CeQCzVIA.js.map → StickyPagination-CD4W450h.js.map} +1 -1
  100. package/dashboard/dist/assets/{StreamMessageDetail-6wEm6o6t.js → StreamMessageDetail-BbDdWDUY.js} +2 -2
  101. package/dashboard/dist/assets/{StreamMessageDetail-6wEm6o6t.js.map → StreamMessageDetail-BbDdWDUY.js.map} +1 -1
  102. package/dashboard/dist/assets/{SwimlaneTimeline-C97Rldf5.js → SwimlaneTimeline-Bktv2vi5.js} +2 -2
  103. package/dashboard/dist/assets/{SwimlaneTimeline-C97Rldf5.js.map → SwimlaneTimeline-Bktv2vi5.js.map} +1 -1
  104. package/dashboard/dist/assets/{TaskDetailPage-B09mLOCE.js → TaskDetailPage-pSU3xuG9.js} +2 -2
  105. package/dashboard/dist/assets/{TaskDetailPage-B09mLOCE.js.map → TaskDetailPage-pSU3xuG9.js.map} +1 -1
  106. package/dashboard/dist/assets/{TasksListPage-CQhiSGkN.js → TasksListPage-M6ok-Nfd.js} +2 -2
  107. package/dashboard/dist/assets/{TasksListPage-CQhiSGkN.js.map → TasksListPage-M6ok-Nfd.js.map} +1 -1
  108. package/dashboard/dist/assets/{TimeAgo-BkyeBcMF.js → TimeAgo-DVWhRNbi.js} +2 -2
  109. package/dashboard/dist/assets/{TimeAgo-BkyeBcMF.js.map → TimeAgo-DVWhRNbi.js.map} +1 -1
  110. package/dashboard/dist/assets/{TimestampCell-DVp_UdEB.js → TimestampCell-rG7pmIkW.js} +2 -2
  111. package/dashboard/dist/assets/{TimestampCell-DVp_UdEB.js.map → TimestampCell-rG7pmIkW.js.map} +1 -1
  112. package/dashboard/dist/assets/{ToolTestPanel-CZOuRP_i.js → ToolTestPanel-CLcMA8OE.js} +2 -2
  113. package/dashboard/dist/assets/{ToolTestPanel-CZOuRP_i.js.map → ToolTestPanel-CLcMA8OE.js.map} +1 -1
  114. package/dashboard/dist/assets/{TopicDetailPage-0mvQde8R.js → TopicDetailPage-C9gvoWYu.js} +2 -2
  115. package/dashboard/dist/assets/{TopicDetailPage-0mvQde8R.js.map → TopicDetailPage-C9gvoWYu.js.map} +1 -1
  116. package/dashboard/dist/assets/{TopicsPage-DkvsHLLe.js → TopicsPage-CIGi3TdT.js} +2 -2
  117. package/dashboard/dist/assets/{TopicsPage-DkvsHLLe.js.map → TopicsPage-CIGi3TdT.js.map} +1 -1
  118. package/dashboard/dist/assets/{UserName-EaD_Tz9y.js → UserName-CifbOahk.js} +2 -2
  119. package/dashboard/dist/assets/{UserName-EaD_Tz9y.js.map → UserName-CifbOahk.js.map} +1 -1
  120. package/dashboard/dist/assets/{WorkflowExecutionPage-CjMSQ9Bb.js → WorkflowExecutionPage-BR0oVx_p.js} +2 -2
  121. package/dashboard/dist/assets/{WorkflowExecutionPage-CjMSQ9Bb.js.map → WorkflowExecutionPage-BR0oVx_p.js.map} +1 -1
  122. package/dashboard/dist/assets/{WorkflowsDashboard-BgUz_jO6.js → WorkflowsDashboard-CSYrYQsO.js} +2 -2
  123. package/dashboard/dist/assets/{WorkflowsDashboard-BgUz_jO6.js.map → WorkflowsDashboard-CSYrYQsO.js.map} +1 -1
  124. package/dashboard/dist/assets/{WorkflowsOverview-BzUjUfAz.js → WorkflowsOverview-CaXWYZuw.js} +2 -2
  125. package/dashboard/dist/assets/{WorkflowsOverview-BzUjUfAz.js.map → WorkflowsOverview-CaXWYZuw.js.map} +1 -1
  126. package/dashboard/dist/assets/{YamlWorkflowDetailPage-C48Ui513.js → YamlWorkflowDetailPage-CRBXahFJ.js} +2 -2
  127. package/dashboard/dist/assets/{YamlWorkflowDetailPage-C48Ui513.js.map → YamlWorkflowDetailPage-CRBXahFJ.js.map} +1 -1
  128. package/dashboard/dist/assets/{YamlWorkflowsPage-CQ-dk84N.js → YamlWorkflowsPage-D5zc8Lta.js} +2 -2
  129. package/dashboard/dist/assets/{YamlWorkflowsPage-CQ-dk84N.js.map → YamlWorkflowsPage-D5zc8Lta.js.map} +1 -1
  130. package/dashboard/dist/assets/{agents-O1GlQE9p.js → agents-D7vYXeLj.js} +2 -2
  131. package/dashboard/dist/assets/{agents-O1GlQE9p.js.map → agents-D7vYXeLj.js.map} +1 -1
  132. package/dashboard/dist/assets/{bots-DB5Z7MEk.js → bots-CKaYN2fL.js} +2 -2
  133. package/dashboard/dist/assets/{bots-DB5Z7MEk.js.map → bots-CKaYN2fL.js.map} +1 -1
  134. package/dashboard/dist/assets/{capabilities-Du9EfNdT.js → capabilities-BknM6oFm.js} +2 -2
  135. package/dashboard/dist/assets/{capabilities-Du9EfNdT.js.map → capabilities-BknM6oFm.js.map} +1 -1
  136. package/dashboard/dist/assets/{controlplane-ku4V2nVk.js → controlplane-BodNGCr-.js} +2 -2
  137. package/dashboard/dist/assets/{controlplane-ku4V2nVk.js.map → controlplane-BodNGCr-.js.map} +1 -1
  138. package/dashboard/dist/assets/{escalation-oVVz7Lw2.js → escalation-CH_XK2XM.js} +2 -2
  139. package/dashboard/dist/assets/{escalation-oVVz7Lw2.js.map → escalation-CH_XK2XM.js.map} +1 -1
  140. package/dashboard/dist/assets/{escalation-columns-C-xaR_Qg.js → escalation-columns-CcbLKRI4.js} +2 -2
  141. package/dashboard/dist/assets/{escalation-columns-C-xaR_Qg.js.map → escalation-columns-CcbLKRI4.js.map} +1 -1
  142. package/dashboard/dist/assets/index--xNp5rx6.css +1 -0
  143. package/dashboard/dist/assets/{index-aHhMmZ0W.js → index-B-LQ8fsJ.js} +2 -2
  144. package/dashboard/dist/assets/{index-aHhMmZ0W.js.map → index-B-LQ8fsJ.js.map} +1 -1
  145. package/dashboard/dist/assets/{index-CyXilW90.js → index-BH67e1Ah.js} +2 -2
  146. package/dashboard/dist/assets/{index-CyXilW90.js.map → index-BH67e1Ah.js.map} +1 -1
  147. package/dashboard/dist/assets/{index-MbEPoyTe.js → index-B_aFcGv9.js} +4 -4
  148. package/dashboard/dist/assets/{index-MbEPoyTe.js.map → index-B_aFcGv9.js.map} +1 -1
  149. package/dashboard/dist/assets/{index-DsKK1fCc.js → index-Bwofyfi8.js} +2 -2
  150. package/dashboard/dist/assets/{index-DsKK1fCc.js.map → index-Bwofyfi8.js.map} +1 -1
  151. package/dashboard/dist/assets/{index-61yKjTkk.js → index-C3QKGY5C.js} +2 -2
  152. package/dashboard/dist/assets/{index-61yKjTkk.js.map → index-C3QKGY5C.js.map} +1 -1
  153. package/dashboard/dist/assets/{index-BwXtXaw8.js → index-CJpywoA8.js} +2 -2
  154. package/dashboard/dist/assets/{index-BwXtXaw8.js.map → index-CJpywoA8.js.map} +1 -1
  155. package/dashboard/dist/assets/index-CV8soXIM.js +5 -0
  156. package/dashboard/dist/assets/index-CV8soXIM.js.map +1 -0
  157. package/dashboard/dist/assets/{index-CxlsLEr1.js → index-CdN-Z8-r.js} +2 -2
  158. package/dashboard/dist/assets/{index-CxlsLEr1.js.map → index-CdN-Z8-r.js.map} +1 -1
  159. package/dashboard/dist/assets/{index-DcZDyLHC.js → index-D7NHjvA4.js} +2 -2
  160. package/dashboard/dist/assets/{index-DcZDyLHC.js.map → index-D7NHjvA4.js.map} +1 -1
  161. package/dashboard/dist/assets/{index-DkZnXWTA.js → index-DHhz71yv.js} +2 -2
  162. package/dashboard/dist/assets/{index-DkZnXWTA.js.map → index-DHhz71yv.js.map} +1 -1
  163. package/dashboard/dist/assets/{index-DgAmkmtv.js → index-Gt2Rebs9.js} +2 -2
  164. package/dashboard/dist/assets/{index-DgAmkmtv.js.map → index-Gt2Rebs9.js.map} +1 -1
  165. package/dashboard/dist/assets/index-IIq5Tkqs.js +6 -0
  166. package/dashboard/dist/assets/index-IIq5Tkqs.js.map +1 -0
  167. package/dashboard/dist/assets/{index-Bcgsf2yD.js → index-_gh2K9DU.js} +2 -2
  168. package/dashboard/dist/assets/{index-Bcgsf2yD.js.map → index-_gh2K9DU.js.map} +1 -1
  169. package/dashboard/dist/assets/{knowledge-CTjJVvIG.js → knowledge-CE6gjN3G.js} +2 -2
  170. package/dashboard/dist/assets/{knowledge-CTjJVvIG.js.map → knowledge-CE6gjN3G.js.map} +1 -1
  171. package/dashboard/dist/assets/{mcp-CsHkygRS.js → mcp-B4F-T-7R.js} +2 -2
  172. package/dashboard/dist/assets/{mcp-CsHkygRS.js.map → mcp-B4F-T-7R.js.map} +1 -1
  173. package/dashboard/dist/assets/{mcp-query-BCt4tpjO.js → mcp-query-CYOarL1K.js} +2 -2
  174. package/dashboard/dist/assets/{mcp-query-BCt4tpjO.js.map → mcp-query-CYOarL1K.js.map} +1 -1
  175. package/dashboard/dist/assets/{pipelines-BpGmuS5V.js → pipelines-B4gB9qMx.js} +2 -2
  176. package/dashboard/dist/assets/{pipelines-BpGmuS5V.js.map → pipelines-B4gB9qMx.js.map} +1 -1
  177. package/dashboard/dist/assets/{roles-lHLgLwOY.js → roles-LZf98ePg.js} +2 -2
  178. package/dashboard/dist/assets/{roles-lHLgLwOY.js.map → roles-LZf98ePg.js.map} +1 -1
  179. package/dashboard/dist/assets/{tasks-BHfimh-E.js → tasks-GG8c_U9w.js} +2 -2
  180. package/dashboard/dist/assets/{tasks-BHfimh-E.js.map → tasks-GG8c_U9w.js.map} +1 -1
  181. package/dashboard/dist/assets/{topics-BW2cS79q.js → topics-C7-6oOgA.js} +2 -2
  182. package/dashboard/dist/assets/{topics-BW2cS79q.js.map → topics-C7-6oOgA.js.map} +1 -1
  183. package/dashboard/dist/assets/{useEventHooks-CeOIM1Ix.js → useEventHooks-82IiMmOc.js} +2 -2
  184. package/dashboard/dist/assets/{useEventHooks-CeOIM1Ix.js.map → useEventHooks-82IiMmOc.js.map} +1 -1
  185. package/dashboard/dist/assets/{useNamespace-CEVLPGGY.js → useNamespace-DQEqD2LA.js} +2 -2
  186. package/dashboard/dist/assets/{useNamespace-CEVLPGGY.js.map → useNamespace-DQEqD2LA.js.map} +1 -1
  187. package/dashboard/dist/assets/{useYamlActivityEvents-DdRNcy7s.js → useYamlActivityEvents-Bm0-ewLx.js} +2 -2
  188. package/dashboard/dist/assets/{useYamlActivityEvents-DdRNcy7s.js.map → useYamlActivityEvents-Bm0-ewLx.js.map} +1 -1
  189. package/dashboard/dist/assets/{users-CAih5Fty.js → users-DHx2ium7.js} +2 -2
  190. package/dashboard/dist/assets/{users-CAih5Fty.js.map → users-DHx2ium7.js.map} +1 -1
  191. package/dashboard/dist/assets/{workflows-C_gCwB9q.js → workflows-qIbkeQ_5.js} +2 -2
  192. package/dashboard/dist/assets/{workflows-C_gCwB9q.js.map → workflows-qIbkeQ_5.js.map} +1 -1
  193. package/dashboard/dist/assets/{yaml-workflows-CPabf8Sf.js → yaml-workflows-FibyeHpo.js} +2 -2
  194. package/dashboard/dist/assets/{yaml-workflows-CPabf8Sf.js.map → yaml-workflows-FibyeHpo.js.map} +1 -1
  195. package/dashboard/dist/index.html +2 -2
  196. package/docs/api/http/escalations.md +28 -4
  197. package/docs/api/http/roles.md +1 -1
  198. package/docs/api/http/workflows.md +1 -1
  199. package/docs/api/sdk/escalations.md +40 -5
  200. package/docs/api/sdk/workflows.md +1 -1
  201. package/docs/dashboard.md +3 -3
  202. package/docs/data.md +2 -2
  203. package/docs/hitl-guide.md +114 -6
  204. package/docs/maintenance.md +6 -3
  205. package/docs/oauth-and-delegation.md +1 -1
  206. package/package.json +2 -2
  207. package/dashboard/dist/assets/EscalationSchemaPage-hzFtuyha.js +0 -9
  208. package/dashboard/dist/assets/EscalationSchemaPage-hzFtuyha.js.map +0 -1
  209. package/dashboard/dist/assets/OperationsPage-Cz_0K8tj.js +0 -2
  210. package/dashboard/dist/assets/OperationsPage-Cz_0K8tj.js.map +0 -1
  211. package/dashboard/dist/assets/RoleDetailPage-BPsrUqIa.js +0 -11
  212. package/dashboard/dist/assets/RoleDetailPage-BPsrUqIa.js.map +0 -1
  213. package/dashboard/dist/assets/index-BdqKh0UK.css +0 -1
  214. package/dashboard/dist/assets/index-CAzFU1rn.js +0 -9
  215. package/dashboard/dist/assets/index-CAzFU1rn.js.map +0 -1
  216. package/dashboard/dist/assets/index-D0bDVU2a.js +0 -6
  217. 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({
@@ -3,7 +3,7 @@ export { listEscalations, listAvailableEscalations, listDistinctTypes, listFacet
3
3
  export { getEscalation, getEscalationsByWorkflowId, escalateToRole } from './single';
4
4
  export { claimEscalation, releaseEscalation } from './claim';
5
5
  export { releaseExpiredClaims, updatePriority, bulkClaim, bulkAssign, bulkEscalate, bulkTriage } from './bulk';
6
- export { resolveEscalation, resolveBySignalKey, resolveByIds } from './resolve';
6
+ export { resolveEscalation, resolveBySignalKey, resolveByIds, resolveAllOrNone } from './resolve';
7
7
  export { findByMetadata, claimByMetadata, resolveByMetadata } from './metadata';
8
8
  export { cancelSingleEscalation, bulkCancel } from './cancel';
9
9
  export { searchByFacets, claimGroups, claimByFacets } from './facets';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.claimByFacets = exports.claimGroups = exports.searchByFacets = exports.bulkCancel = exports.cancelSingleEscalation = exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveByIds = exports.resolveBySignalKey = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getStationMetrics = exports.getEscalationStats = exports.listFacetKeys = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
3
+ exports.claimByFacets = exports.claimGroups = exports.searchByFacets = exports.bulkCancel = exports.cancelSingleEscalation = exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveAllOrNone = exports.resolveByIds = exports.resolveBySignalKey = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getStationMetrics = exports.getEscalationStats = exports.listFacetKeys = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
4
4
  var create_1 = require("./create");
5
5
  Object.defineProperty(exports, "createEscalation", { enumerable: true, get: function () { return create_1.createEscalation; } });
6
6
  var list_1 = require("./list");
@@ -28,6 +28,7 @@ var resolve_1 = require("./resolve");
28
28
  Object.defineProperty(exports, "resolveEscalation", { enumerable: true, get: function () { return resolve_1.resolveEscalation; } });
29
29
  Object.defineProperty(exports, "resolveBySignalKey", { enumerable: true, get: function () { return resolve_1.resolveBySignalKey; } });
30
30
  Object.defineProperty(exports, "resolveByIds", { enumerable: true, get: function () { return resolve_1.resolveByIds; } });
31
+ Object.defineProperty(exports, "resolveAllOrNone", { enumerable: true, get: function () { return resolve_1.resolveAllOrNone; } });
31
32
  var metadata_1 = require("./metadata");
32
33
  Object.defineProperty(exports, "findByMetadata", { enumerable: true, get: function () { return metadata_1.findByMetadata; } });
33
34
  Object.defineProperty(exports, "claimByMetadata", { enumerable: true, get: function () { return metadata_1.claimByMetadata; } });
@@ -44,3 +44,31 @@ export declare function resolveByIds(input: {
44
44
  resolverPayload: Record<string, any>;
45
45
  metadata?: Record<string, any>;
46
46
  }, auth: LTApiAuth): Promise<LTApiResult>;
47
+ /**
48
+ * Atomic all-or-none bulk resolve with PER-ROW payloads — the gang-handoff
49
+ * sibling of {@link resolveByIds}. Every listed escalation must be pending and
50
+ * writable by the caller, or NOTHING resolves. Unlike resolveByIds, rows
51
+ * backing a live `condition()` waiter are first-class: each is woken with its
52
+ * own payload inside the single atomic statement (same wake contract as
53
+ * single resolve).
54
+ *
55
+ * RBAC matches resolveByIds: per-item write scope, 404 non-disclosure when any
56
+ * id is missing or out of scope, nothing resolved. `requireClaimed` adds a
57
+ * SQL-level assertion that every row is currently assigned to the caller —
58
+ * for claim-then-resolve flows (a broker resolving its claimed gang), this
59
+ * closes the window where another principal re-claims a member mid-flight.
60
+ *
61
+ * Rows whose resolution requires a non-atomic path (legacy signal routing via
62
+ * `metadata.signal_id` / `metadata.signal_routing`, which need a separate
63
+ * workflow signal) block the batch with 409 — bulk-resolving them would strand
64
+ * their workflows. Re-run/triage rows (workflow_type without any signal shape)
65
+ * resolve as bookkeeping acknowledgements, matching resolveByIds semantics.
66
+ */
67
+ export declare function resolveAllOrNone(input: {
68
+ items: Array<{
69
+ id: string;
70
+ resolverPayload: Record<string, any>;
71
+ }>;
72
+ metadata?: Record<string, any>;
73
+ requireClaimed?: boolean;
74
+ }, auth: LTApiAuth): Promise<LTApiResult>;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.resolveEscalation = resolveEscalation;
37
37
  exports.resolveBySignalKey = resolveBySignalKey;
38
38
  exports.resolveByIds = resolveByIds;
39
+ exports.resolveAllOrNone = resolveAllOrNone;
39
40
  const escalationService = __importStar(require("../../services/escalation"));
40
41
  const taskService = __importStar(require("../../services/task"));
41
42
  const escalation_strategy_1 = require("../../services/escalation-strategy");
@@ -60,7 +61,7 @@ const helpers_1 = require("./helpers");
60
61
  */
61
62
  async function resolveEscalation(input, auth) {
62
63
  try {
63
- const { id, resolverPayload, metadata } = input;
64
+ const { id, metadata, resolverPayload } = input;
64
65
  if (!resolverPayload) {
65
66
  return { status: 400, error: 'resolverPayload is required' };
66
67
  }
@@ -82,6 +83,9 @@ async function resolveEscalation(input, auth) {
82
83
  const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
83
84
  if (denied)
84
85
  return denied;
86
+ // The resolver payload is stored exactly as submitted — its shape is the
87
+ // workflow's contract, formed by the caller (the React app maps the form to
88
+ // the payload via x-lt-bind before submitting). No server-side transform.
85
89
  // `metadata` (the outcome patch) is never written separately — it rides WITH the
86
90
  // resolverPayload to whichever path performs the resolve, so it merges inside the
87
91
  // single status='pending'-guarded UPDATE (HotMesh `resolve(metadata)`). Paths that
@@ -133,7 +137,7 @@ async function resolveEscalation(input, auth) {
133
137
  */
134
138
  async function resolveBySignalKey(input, auth) {
135
139
  try {
136
- const { signalKey, resolverPayload, metadata } = input;
140
+ const { signalKey, metadata, resolverPayload } = input;
137
141
  if (!signalKey)
138
142
  return { status: 400, error: 'signalKey is required' };
139
143
  if (!resolverPayload)
@@ -147,6 +151,8 @@ async function resolveBySignalKey(input, auth) {
147
151
  const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
148
152
  if (denied)
149
153
  return { status: 404, error: 'Escalation not found' };
154
+ // The payload is delivered as the signal the parked workflow's condition()
155
+ // receives — stored exactly as submitted (the caller formed its shape).
150
156
  return resolveViaSignalKey(escalation, resolverPayload, metadata);
151
157
  }
152
158
  catch (err) {
@@ -166,12 +172,15 @@ async function resolveBySignalKey(input, auth) {
166
172
  */
167
173
  async function resolveByIds(input, auth) {
168
174
  try {
169
- const { ids, resolverPayload, metadata } = input;
175
+ const { ids, metadata } = input;
170
176
  if (!Array.isArray(ids) || ids.length === 0) {
171
177
  return { status: 400, error: 'ids must be a non-empty array' };
172
178
  }
173
- if (!resolverPayload)
179
+ if (!input.resolverPayload)
174
180
  return { status: 400, error: 'resolverPayload is required' };
181
+ // One pre-shaped payload for a homogeneous group of bookkeeping rows, stored
182
+ // as submitted — the caller forms the payload's shape.
183
+ const resolverPayload = input.resolverPayload;
175
184
  // Bulk resolve ("ack") is a write verb, gated PER ITEM: write_all roles may
176
185
  // resolve any item in the role; write_self roles may resolve only items assigned
177
186
  // to the caller (same rule single-resolve applies via assertWriteAccess). Global
@@ -194,6 +203,103 @@ async function resolveByIds(input, auth) {
194
203
  return { status: 500, error: err.message };
195
204
  }
196
205
  }
206
+ /**
207
+ * Atomic all-or-none bulk resolve with PER-ROW payloads — the gang-handoff
208
+ * sibling of {@link resolveByIds}. Every listed escalation must be pending and
209
+ * writable by the caller, or NOTHING resolves. Unlike resolveByIds, rows
210
+ * backing a live `condition()` waiter are first-class: each is woken with its
211
+ * own payload inside the single atomic statement (same wake contract as
212
+ * single resolve).
213
+ *
214
+ * RBAC matches resolveByIds: per-item write scope, 404 non-disclosure when any
215
+ * id is missing or out of scope, nothing resolved. `requireClaimed` adds a
216
+ * SQL-level assertion that every row is currently assigned to the caller —
217
+ * for claim-then-resolve flows (a broker resolving its claimed gang), this
218
+ * closes the window where another principal re-claims a member mid-flight.
219
+ *
220
+ * Rows whose resolution requires a non-atomic path (legacy signal routing via
221
+ * `metadata.signal_id` / `metadata.signal_routing`, which need a separate
222
+ * workflow signal) block the batch with 409 — bulk-resolving them would strand
223
+ * their workflows. Re-run/triage rows (workflow_type without any signal shape)
224
+ * resolve as bookkeeping acknowledgements, matching resolveByIds semantics.
225
+ */
226
+ async function resolveAllOrNone(input, auth) {
227
+ try {
228
+ const { items, metadata, requireClaimed } = input;
229
+ if (!Array.isArray(items) || items.length === 0) {
230
+ return { status: 400, error: 'items must be a non-empty array' };
231
+ }
232
+ if (items.length > defaults_1.ESCALATION_BULK_RESOLVE_MAX) {
233
+ return { status: 400, error: `items exceeds the maximum of ${defaults_1.ESCALATION_BULK_RESOLVE_MAX}` };
234
+ }
235
+ const wellFormed = items.every((i) => i && typeof i.id === 'string' && i.id.length > 0
236
+ && i.resolverPayload && typeof i.resolverPayload === 'object');
237
+ if (!wellFormed) {
238
+ return { status: 400, error: 'every item requires an id and a resolverPayload object' };
239
+ }
240
+ const ids = items.map((i) => i.id);
241
+ if (new Set(ids).size !== ids.length) {
242
+ return { status: 400, error: 'items must not repeat ids' };
243
+ }
244
+ // One indexed read serves every pre-flight gate below (scope, path shape,
245
+ // redaction schema). Role and signal routing are immutable per escalation,
246
+ // so none of these checks race the guarded statement that follows — and
247
+ // row STATE (pending, assignee) is re-asserted inside that statement.
248
+ const rows = await escalationService.getEscalationsByIds(ids);
249
+ const rowById = new Map(rows.map((r) => [r.id, r]));
250
+ // Per-item write scope, same rule as resolveByIds: write_all roles may
251
+ // resolve any item in the role; write_self only items assigned to the
252
+ // caller. A missing id or any out-of-scope item → 404 (non-disclosure),
253
+ // nothing resolved. Global bypasses; the store then reports precise
254
+ // per-row blockers (including not-found) via the 409 below.
255
+ const writeScope = await (0, helpers_1.getEscalationWriteScope)(auth.userId);
256
+ if (!writeScope.global) {
257
+ const allSet = new Set(writeScope.allRoles);
258
+ const selfSet = new Set(writeScope.selfRoles);
259
+ const writable = (r) => (!!r.role && allSet.has(r.role)) ||
260
+ (!!r.role && selfSet.has(r.role) && r.assigned_to === auth.userId);
261
+ if (rows.length !== ids.length || !rows.every(writable)) {
262
+ return { status: 404, error: 'One or more escalations not found' };
263
+ }
264
+ }
265
+ // Fail loud on rows the atomic statement cannot wake: legacy signal shapes
266
+ // resume their workflow via a separate handle.signal, so bulk-resolving the
267
+ // row would mark it settled while the workflow stays parked forever.
268
+ const unsupported = rows.filter((r) => {
269
+ const meta = (r.metadata ?? {});
270
+ return !r.signal_key && (meta.signal_id || meta.signal_routing?.signalId);
271
+ });
272
+ if (unsupported.length > 0) {
273
+ const failed = unsupported.map((r) => ({ id: r.id, reason: 'unsupported-resolution-path' }));
274
+ // error rides inside data too: routes serialize `data ?? { error }`, and
275
+ // the 409 body must carry BOTH the message and the blocking ids
276
+ const error = 'One or more escalations require the single-resolve path (legacy signal routing)';
277
+ return { status: 409, error, data: { error, failedIds: failed.map((f) => f.id), failed } };
278
+ }
279
+ // Per-row password redaction against each row's own form schema — the
280
+ // payload enters the signal store and the audit record; plaintext never.
281
+ const serviceItems = await Promise.all(items.map(async (item) => ({
282
+ id: item.id,
283
+ resolverPayload: await redactPasswords(item.resolverPayload, rowById.get(item.id)?.metadata?.form_schema),
284
+ })));
285
+ const result = await escalationService.resolveEscalationsAllOrNone(serviceItems, metadata, requireClaimed ? auth.userId : undefined);
286
+ if (!result.ok) {
287
+ const error = 'One or more escalations blocked the batch; nothing was resolved';
288
+ return {
289
+ status: 409,
290
+ error,
291
+ data: { error, failedIds: result.failed.map((f) => f.id), failed: result.failed },
292
+ };
293
+ }
294
+ return {
295
+ status: 200,
296
+ data: { resolved: result.escalations.length, escalationIds: result.escalations.map((e) => e.id) },
297
+ };
298
+ }
299
+ catch (err) {
300
+ return { status: 500, error: err.message };
301
+ }
302
+ }
197
303
  // ── Resolution paths ─────────────────────────────────────────────────────
198
304
  /** Path A: lightweight conditionLT signal — inject $escalation_id and signal the running workflow. */
199
305
  async function resolveViaConditionSignal(escalation, resolverPayload, metadata) {
@@ -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;
@@ -8,7 +8,18 @@ const config_1 = require("../../modules/config");
8
8
  let pool = null;
9
9
  function getPool() {
10
10
  if (!pool) {
11
- pool = new pg_1.Pool(config_1.postgres_options);
11
+ pool = new pg_1.Pool({
12
+ ...config_1.postgres_options,
13
+ // Long-tail's shared tables live in public. Postgres's default
14
+ // search_path is `"$user", public`: if any schema shares the connecting
15
+ // role's name (e.g. a HotMesh app schema named after the DB user), every
16
+ // unqualified statement — including CREATE TABLE in migrations — silently
17
+ // resolves there instead of public, forking the data across two schemas.
18
+ // Pinning the session search_path makes resolution independent of role
19
+ // and schema naming. HotMesh connections (getConnection) are unaffected:
20
+ // the engine fully qualifies its app schemas.
21
+ options: '-c search_path=public',
22
+ });
12
23
  }
13
24
  return pool;
14
25
  }
@@ -47,7 +47,34 @@ async function migrate() {
47
47
  // Uses a dedicated client so the lock is held for the entire sequence.
48
48
  const client = await pool.connect();
49
49
  try {
50
+ // Migrations define the shared public tables, so resolution is pinned on
51
+ // this client even if a future caller wires a pool without the pool-level
52
+ // search_path option. Without this, a schema named after the DB role
53
+ // (default search_path `"$user", public`) captures every unqualified
54
+ // CREATE TABLE below.
55
+ await client.query('SET search_path TO public');
50
56
  await client.query('SELECT pg_advisory_lock(8675309)');
57
+ // Fail loudly on a pre-existing split: an lt_migrations table in any other
58
+ // schema means a prior boot ran with `"$user"` shadowing public and forked
59
+ // the tables. Booting through it would either fork the data further or
60
+ // re-run every migration against empty public tables while the live rows
61
+ // sit in the shadow schema. An operator must consolidate first (move the
62
+ // live lt_* tables to public, archive the stale copies).
63
+ const drift = await client.query(`
64
+ SELECT n.nspname
65
+ FROM pg_class c
66
+ JOIN pg_namespace n ON n.oid = c.relnamespace
67
+ WHERE c.relname = 'lt_migrations' AND c.relkind = 'r' AND n.nspname <> 'public'
68
+ `);
69
+ if (drift.rows.length > 0) {
70
+ const schemas = drift.rows.map((r) => r.nspname).join(', ');
71
+ throw new Error(`[migrate] lt_migrations exists in non-public schema(s): ${schemas}. ` +
72
+ 'Long-tail tables must live in the public schema. This usually means the ' +
73
+ 'database user shares a name with another schema, so the default ' +
74
+ 'search_path ("$user", public) captured earlier migrations. Consolidate ' +
75
+ 'the schemas (ALTER TABLE ... SET SCHEMA public for the live lt_* tables, ' +
76
+ 'archive stale public copies) before starting the app.');
77
+ }
51
78
  // ensure migration tracking table
52
79
  await client.query(`
53
80
  CREATE TABLE IF NOT EXISTS lt_migrations (
@@ -49,6 +49,8 @@ export declare const QUERY_LIMIT_MAX: number;
49
49
  export declare const YAML_LIST_LIMIT: number;
50
50
  /** Default page size for YAML workflow version history. */
51
51
  export declare const YAML_VERSION_LIMIT: number;
52
+ /** Maximum items per atomic bulk resolve (POST /escalations/resolve-all-or-none). */
53
+ export declare const ESCALATION_BULK_RESOLVE_MAX: number;
52
54
  /**
53
55
  * Claim duration presets shown in the dashboard (minutes).
54
56
  * Override via JSON array, e.g. LT_CLAIM_DURATION_OPTIONS='[15,30,60,480]'
@@ -9,7 +9,7 @@
9
9
  * Grouped by domain so related knobs live together.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.YAML_LINE_WIDTH = exports.STOP_WORDS = exports.CLAIM_DURATION_OPTIONS = exports.YAML_VERSION_LIMIT = exports.YAML_LIST_LIMIT = exports.QUERY_LIMIT_MAX = exports.QUERY_LIMIT_DEFAULT = exports.CONFIG_CACHE_TTL_MS = exports.MCP_TOOL_TIMEOUT_MS = exports.WORKFLOW_SYNC_TIMEOUT_MS = exports.JOB_EXPIRE_SECS = exports.WORKFLOW_EXPIRE_SECS = exports.TOOL_ARG_LIMIT_CAP = exports.TOOL_ROUNDS_MCP_QUERY = exports.TOOL_ROUNDS_TRIAGE = exports.TOOL_RESULT_MAX_CHARS = exports.LLM_MAX_INPUT_CHARS = exports.LLM_MAX_ARRAY_ITEMS = exports.LLM_MAX_TOKENS_VISION = exports.LLM_MAX_TOKENS_JSON = exports.LLM_MAX_TOKENS_DEFAULT = exports.LLM_BASE_URL = exports.LLM_MODEL_SECONDARY = exports.LLM_MODEL_PRIMARY = void 0;
12
+ exports.YAML_LINE_WIDTH = exports.STOP_WORDS = exports.CLAIM_DURATION_OPTIONS = exports.ESCALATION_BULK_RESOLVE_MAX = exports.YAML_VERSION_LIMIT = exports.YAML_LIST_LIMIT = exports.QUERY_LIMIT_MAX = exports.QUERY_LIMIT_DEFAULT = exports.CONFIG_CACHE_TTL_MS = exports.MCP_TOOL_TIMEOUT_MS = exports.WORKFLOW_SYNC_TIMEOUT_MS = exports.JOB_EXPIRE_SECS = exports.WORKFLOW_EXPIRE_SECS = exports.TOOL_ARG_LIMIT_CAP = exports.TOOL_ROUNDS_MCP_QUERY = exports.TOOL_ROUNDS_TRIAGE = exports.TOOL_RESULT_MAX_CHARS = exports.LLM_MAX_INPUT_CHARS = exports.LLM_MAX_ARRAY_ITEMS = exports.LLM_MAX_TOKENS_VISION = exports.LLM_MAX_TOKENS_JSON = exports.LLM_MAX_TOKENS_DEFAULT = exports.LLM_BASE_URL = exports.LLM_MODEL_SECONDARY = exports.LLM_MODEL_PRIMARY = void 0;
13
13
  function envInt(key, fallback) {
14
14
  const v = process.env[key];
15
15
  if (v === undefined || v === '')
@@ -67,6 +67,8 @@ exports.YAML_LIST_LIMIT = envInt('LT_YAML_LIST_LIMIT', 50);
67
67
  /** Default page size for YAML workflow version history. */
68
68
  exports.YAML_VERSION_LIMIT = envInt('LT_YAML_VERSION_LIMIT', 20);
69
69
  // ── Escalation ──────────────────────────────────────────────────────
70
+ /** Maximum items per atomic bulk resolve (POST /escalations/resolve-all-or-none). */
71
+ exports.ESCALATION_BULK_RESOLVE_MAX = envInt('LT_ESCALATION_BULK_RESOLVE_MAX', 100);
70
72
  /**
71
73
  * Claim duration presets shown in the dashboard (minutes).
72
74
  * Override via JSON array, e.g. LT_CLAIM_DURATION_OPTIONS='[15,30,60,480]'
@@ -66,6 +66,24 @@ function registerResolveRoutes(router) {
66
66
  }, req.auth);
67
67
  res.status(result.status).json(result.data ?? { error: result.error });
68
68
  });
69
+ /**
70
+ * POST /api/escalations/resolve-all-or-none
71
+ * Atomic bulk resolve with per-row payloads: every listed escalation resolves
72
+ * with its own resolverPayload in one statement, or nothing resolves. Rows
73
+ * backing a live condition() waiter are woken with their own payload (same
74
+ * wake contract as /:id/resolve). 409 returns { error, failedIds, failed }
75
+ * naming exactly the rows that blocked the batch. Max 100 items per call.
76
+ * Body: { items: Array<{ id: string, resolverPayload: Record<string, any> }>,
77
+ * metadata?: Record<string, any>, requireClaimed?: boolean }
78
+ */
79
+ router.post('/resolve-all-or-none', async (req, res) => {
80
+ const result = await api.resolveAllOrNone({
81
+ items: req.body?.items,
82
+ metadata: req.body?.metadata,
83
+ requireClaimed: req.body?.requireClaimed,
84
+ }, req.auth);
85
+ res.status(result.status).json(result.data ?? { error: result.error });
86
+ });
69
87
  /**
70
88
  * POST /api/escalations/:id/resolve
71
89
  * Resolve a pending escalation with a human-provided payload. Routes by
@@ -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