@straiffi/archon 1.8.15 → 1.8.16

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 (227) hide show
  1. package/dist/client/assets/ActivityView-p8gBUxYN.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-BRxGcSyq.js +1 -0
  3. package/dist/client/assets/VerificationDialog-B6l5TYOS.js +4 -0
  4. package/dist/client/assets/{arc-C83vRtlz.js → arc-D2Nn9xIj.js} +1 -1
  5. package/dist/client/assets/architecture-7EHR7CIX-C-4Cp4ml.js +1 -0
  6. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Dx7MJofM.js → architectureDiagram-3BPJPVTR-DnuKO1RY.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-GPEHLZMM-CSSJR8F_.js → blockDiagram-GPEHLZMM-CnQ7FwEp.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-AAUBKEIU-C4F9969r.js → c4Diagram-AAUBKEIU-BF1hFXwp.js} +1 -1
  9. package/dist/client/assets/channel-TJtf4cBJ.js +1 -0
  10. package/dist/client/assets/{chunk-2J33WTMH-23HW6i-y.js → chunk-2J33WTMH-UvCFvkZO.js} +1 -1
  11. package/dist/client/assets/{chunk-3OPIFGDE-C3ZmRDMb.js → chunk-3OPIFGDE-z1l5nBD0.js} +1 -1
  12. package/dist/client/assets/{chunk-4BX2VUAB-C5pU9ApX.js → chunk-4BX2VUAB-CtLS8D71.js} +1 -1
  13. package/dist/client/assets/{chunk-4EGX6M5U-CAjWPjND.js → chunk-4EGX6M5U-Btnm0QNe.js} +1 -1
  14. package/dist/client/assets/{chunk-55IACEB6-DhO4-AG4.js → chunk-55IACEB6-74CcJaub.js} +1 -1
  15. package/dist/client/assets/{chunk-5DO6E6H7-BDKA-zHS.js → chunk-5DO6E6H7-WLZ8L4ko.js} +1 -1
  16. package/dist/client/assets/{chunk-5ZQYHXKU-B_-UQvCg.js → chunk-5ZQYHXKU-Bis0Pw2_.js} +1 -1
  17. package/dist/client/assets/{chunk-727SXJPM-5Dua6u1B.js → chunk-727SXJPM-B3gUO8rD.js} +1 -1
  18. package/dist/client/assets/{chunk-AQP2D5EJ-DcpcaFRO.js → chunk-AQP2D5EJ-hPBzrrh5.js} +1 -1
  19. package/dist/client/assets/{chunk-BR22UD5L-12D2GWh4.js → chunk-BR22UD5L-Cou5beGe.js} +1 -1
  20. package/dist/client/assets/{chunk-BSJP7CBP-DMi77e84.js → chunk-BSJP7CBP-DJ57XcV5.js} +1 -1
  21. package/dist/client/assets/{chunk-CSCIHK7Q-CLCpOw52.js → chunk-CSCIHK7Q-BfOng_VY.js} +2 -2
  22. package/dist/client/assets/{chunk-FHYWG6QK-BhSajKiB.js → chunk-FHYWG6QK-Ba10e2kj.js} +1 -1
  23. package/dist/client/assets/{chunk-FMBD7UC4-Drmlc0wM.js → chunk-FMBD7UC4-CEo2wZMx.js} +1 -1
  24. package/dist/client/assets/{chunk-KSCS5N6A-uOZFe2JI.js → chunk-KSCS5N6A-B2Jg7PW5.js} +1 -1
  25. package/dist/client/assets/{chunk-L5ZTLDWV-DNnzSNoX.js → chunk-L5ZTLDWV-CUYujZxt.js} +1 -1
  26. package/dist/client/assets/{chunk-LZXEDZCA-CSA3Xnoh.js → chunk-LZXEDZCA-CH_eV8jH.js} +2 -2
  27. package/dist/client/assets/{chunk-MPE355IW-Cw8YmjTC.js → chunk-MPE355IW-MqEt-J8G.js} +1 -1
  28. package/dist/client/assets/{chunk-MZUSXYTE-jvxRCZ7x.js → chunk-MZUSXYTE-DdNi9lhx.js} +1 -1
  29. package/dist/client/assets/{chunk-N66VUXT2-00i4y0yD.js → chunk-N66VUXT2-CjemTW4E.js} +1 -1
  30. package/dist/client/assets/{chunk-ND2GUHAM-BQR5rD7w.js → chunk-ND2GUHAM-BVF5evM9.js} +1 -1
  31. package/dist/client/assets/{chunk-NZK2D7GU-DyIwcQYQ.js → chunk-NZK2D7GU-WfG-2AOS.js} +1 -1
  32. package/dist/client/assets/{chunk-O5CBEL6O-DfW60w5V.js → chunk-O5CBEL6O-CSF9ozqy.js} +1 -1
  33. package/dist/client/assets/{chunk-PUPMXCY4-DfWourL7.js → chunk-PUPMXCY4-ZvwTw0To.js} +1 -1
  34. package/dist/client/assets/chunk-QZHKN3VN-LI_RZca2.js +1 -0
  35. package/dist/client/assets/{chunk-UIBZB4QT-v-a5ZfN6.js → chunk-UIBZB4QT-BTY9be9s.js} +1 -1
  36. package/dist/client/assets/{chunk-WCWK7LTN-CtYzhvvb.js → chunk-WCWK7LTN-Db1n3n3-.js} +1 -1
  37. package/dist/client/assets/chunk-WU5MYG2G-DoVpb2oe.js +1 -0
  38. package/dist/client/assets/{chunk-XPW4576I-CvnAuXH2.js → chunk-XPW4576I-DObFPxjB.js} +1 -1
  39. package/dist/client/assets/classDiagram-4FO5ZUOK-CnKIRKWC.js +1 -0
  40. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-5ZgjUwTv.js +1 -0
  41. package/dist/client/assets/{cose-bilkent-S5V4N54A-fchD-dH2.js → cose-bilkent-S5V4N54A-MlOPiG6P.js} +1 -1
  42. package/dist/client/assets/{dagre-BM42HDAG-Ba7Pz-ZI.js → dagre-BM42HDAG-rekDIYnC.js} +1 -1
  43. package/dist/client/assets/{dagre-DSrzm-Gq.js → dagre-BWNHuDdf.js} +1 -1
  44. package/dist/client/assets/{diagram-2AECGRRQ-BxYfgpZ6.js → diagram-2AECGRRQ-Z4mXdhW0.js} +1 -1
  45. package/dist/client/assets/{diagram-5GNKFQAL-Bc4-O-UJ.js → diagram-5GNKFQAL-DqeVe5Vc.js} +1 -1
  46. package/dist/client/assets/{diagram-KO2AKTUF-Ds1Lmrzm.js → diagram-KO2AKTUF-ijppTuNc.js} +1 -1
  47. package/dist/client/assets/{diagram-LMA3HP47-VFQabxKS.js → diagram-LMA3HP47-CDGLx0-N.js} +1 -1
  48. package/dist/client/assets/{diagram-OG6HWLK6-Baff59Zc.js → diagram-OG6HWLK6-DDlnjzF8.js} +1 -1
  49. package/dist/client/assets/{erDiagram-TEJ5UH35-Da5zHjOK.js → erDiagram-TEJ5UH35-V_axe9oz.js} +1 -1
  50. package/dist/client/assets/eventmodeling-FCH6USID-Dqb6QKoI.js +1 -0
  51. package/dist/client/assets/{flowDiagram-I6XJVG4X-pdGMAuog.js → flowDiagram-I6XJVG4X-GH5q79jg.js} +1 -1
  52. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DjJGX0nm.js → ganttDiagram-6RSMTGT7-BMWVdLeQ.js} +1 -1
  53. package/dist/client/assets/gitGraph-WXDBUCRP-DYEsqh93.js +1 -0
  54. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-kErlwtcT.js → gitGraphDiagram-PVQCEYII-VrV8jMUg.js} +1 -1
  55. package/dist/client/assets/index-B7J4DYDu.js +275 -0
  56. package/dist/client/assets/index-CsZfTdMO.css +2 -0
  57. package/dist/client/assets/info-J43DQDTF-CfjXFZai.js +1 -0
  58. package/dist/client/assets/{infoDiagram-5YYISTIA-Bx78HhDi.js → infoDiagram-5YYISTIA-EGiVbp17.js} +1 -1
  59. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BP0YQCMl.js → ishikawaDiagram-YF4QCWOH-Dw3CiXbm.js} +1 -1
  60. package/dist/client/assets/{journeyDiagram-JHISSGLW-DUgrCOuD.js → journeyDiagram-JHISSGLW-2S631RY5.js} +1 -1
  61. package/dist/client/assets/{kanban-definition-UN3LZRKU-K0Zc5_M8.js → kanban-definition-UN3LZRKU-pc9vzUtB.js} +1 -1
  62. package/dist/client/assets/{line-Dtg-krjk.js → line-BBMBmEjI.js} +1 -1
  63. package/dist/client/assets/{linear-BOwW57yX.js → linear-DT4rSZvy.js} +1 -1
  64. package/dist/client/assets/{mermaid-parser.core-Dw5BI77W.js → mermaid-parser.core-ClNeolx0.js} +2 -2
  65. package/dist/client/assets/{mermaid.core-BE-qlWNj.js → mermaid.core-Q4IJbm5V.js} +3 -3
  66. package/dist/client/assets/{mindmap-definition-RKZ34NQL-1Piz4H9I.js → mindmap-definition-RKZ34NQL-DUTZxR8H.js} +1 -1
  67. package/dist/client/assets/{ordinal-CZ33lFpK.js → ordinal-k--hYEme.js} +1 -1
  68. package/dist/client/assets/packet-YPE3B663-Dv0HkRHU.js +1 -0
  69. package/dist/client/assets/pie-LRSECV5Y-S5zPzZFN.js +1 -0
  70. package/dist/client/assets/{pieDiagram-4H26LBE5-zlWdJ7lL.js → pieDiagram-4H26LBE5-RW5hNtJq.js} +1 -1
  71. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DRQY8Adp.js → quadrantDiagram-W4KKPZXB-CK60c7X9.js} +1 -1
  72. package/dist/client/assets/radar-GUYGQ44K-B7psLv22.js +1 -0
  73. package/dist/client/assets/{requirementDiagram-4Y6WPE33-B8_GJFxl.js → requirementDiagram-4Y6WPE33-Dz35FvcC.js} +1 -1
  74. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BaI0Njo8.js → sankeyDiagram-5OEKKPKP-ByyluoXu.js} +1 -1
  75. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DjqAXbRF.js → sequenceDiagram-3UESZ5HK-C-879UFe.js} +1 -1
  76. package/dist/client/assets/{stateDiagram-AJRCARHV-Ovr_3LeO.js → stateDiagram-AJRCARHV-B8neDlUu.js} +1 -1
  77. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-B4Mq8Abu.js +1 -0
  78. package/dist/client/assets/{timeline-definition-PNZ67QCA-DkczYKeZ.js → timeline-definition-PNZ67QCA-iAwrltn1.js} +1 -1
  79. package/dist/client/assets/treeView-BLDUP644-B8H0kT67.js +1 -0
  80. package/dist/client/assets/treemap-LRROVOQU-DPLhFHnh.js +1 -0
  81. package/dist/client/assets/{vennDiagram-CIIHVFJN-BZyo4ESi.js → vennDiagram-CIIHVFJN-DHN61m8d.js} +1 -1
  82. package/dist/client/assets/wardley-L42UT6IY-B5-Vb08V.js +1 -0
  83. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BEISrECX.js → wardleyDiagram-YWT4CUSO-XDVo7Jy1.js} +1 -1
  84. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CyNzGK7f.js → xychartDiagram-2RQKCTM6-Cb-HLpaN.js} +1 -1
  85. package/dist/client/index.html +2 -2
  86. package/dist/server/db.js +235 -0
  87. package/dist/server/db.js.map +1 -1
  88. package/dist/server/index.js +200 -45
  89. package/dist/server/index.js.map +1 -1
  90. package/dist/server/lib/agent.js +139 -90
  91. package/dist/server/lib/agent.js.map +1 -1
  92. package/dist/server/lib/agentProvider.js +1 -6
  93. package/dist/server/lib/agentProvider.js.map +1 -1
  94. package/dist/server/lib/buildExecutionEnvironment.js +517 -0
  95. package/dist/server/lib/buildExecutionEnvironment.js.map +1 -0
  96. package/dist/server/lib/buildOpenQuestionOperations.js +9 -3
  97. package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -1
  98. package/dist/server/lib/checkAutoFix.js +1 -0
  99. package/dist/server/lib/checkAutoFix.js.map +1 -1
  100. package/dist/server/lib/checkRepairDispatch.js +3 -1
  101. package/dist/server/lib/checkRepairDispatch.js.map +1 -1
  102. package/dist/server/lib/dockerSandboxCredentials.js +62 -0
  103. package/dist/server/lib/dockerSandboxCredentials.js.map +1 -0
  104. package/dist/server/lib/dockerSandboxEnvironmentOperations.js +54 -0
  105. package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -0
  106. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +631 -0
  107. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -0
  108. package/dist/server/lib/dockerSandboxExecutionProvider.js +196 -0
  109. package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -0
  110. package/dist/server/lib/dockerSandboxOpencodeSessionOperations.js +29 -0
  111. package/dist/server/lib/dockerSandboxOpencodeSessionOperations.js.map +1 -0
  112. package/dist/server/lib/dockerSandboxRuntimeController.js +187 -0
  113. package/dist/server/lib/dockerSandboxRuntimeController.js.map +1 -0
  114. package/dist/server/lib/executionEnvironment.js +22 -0
  115. package/dist/server/lib/executionEnvironment.js.map +1 -1
  116. package/dist/server/lib/executionEnvironmentContexts.js +144 -0
  117. package/dist/server/lib/executionEnvironmentContexts.js.map +1 -0
  118. package/dist/server/lib/executionEnvironmentLifecycle.js +63 -0
  119. package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -0
  120. package/dist/server/lib/executionEnvironmentReconciliation.js +782 -0
  121. package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -0
  122. package/dist/server/lib/executionEnvironmentRoutes.js +354 -0
  123. package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -0
  124. package/dist/server/lib/executionEnvironmentValidation.js +486 -0
  125. package/dist/server/lib/executionEnvironmentValidation.js.map +1 -0
  126. package/dist/server/lib/executionEnvironmentValidationOperations.js +635 -0
  127. package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -0
  128. package/dist/server/lib/executionEnvironments.js +726 -0
  129. package/dist/server/lib/executionEnvironments.js.map +1 -0
  130. package/dist/server/lib/executionReconciliation.js +77 -0
  131. package/dist/server/lib/executionReconciliation.js.map +1 -1
  132. package/dist/server/lib/executionRetry.js +3 -0
  133. package/dist/server/lib/executionRetry.js.map +1 -1
  134. package/dist/server/lib/executionRuns.js +1 -0
  135. package/dist/server/lib/executionRuns.js.map +1 -1
  136. package/dist/server/lib/executionTargets.js +107 -0
  137. package/dist/server/lib/executionTargets.js.map +1 -0
  138. package/dist/server/lib/executionTimeline.js +3 -0
  139. package/dist/server/lib/executionTimeline.js.map +1 -1
  140. package/dist/server/lib/hostExecutionEnvironment.js +47 -1
  141. package/dist/server/lib/hostExecutionEnvironment.js.map +1 -1
  142. package/dist/server/lib/mobileAccess.js +2 -2
  143. package/dist/server/lib/mobileAccess.js.map +1 -1
  144. package/dist/server/lib/models.js +11 -0
  145. package/dist/server/lib/models.js.map +1 -1
  146. package/dist/server/lib/opencodeErrors.js +31 -3
  147. package/dist/server/lib/opencodeErrors.js.map +1 -1
  148. package/dist/server/lib/opencodeSessionOperations.js +189 -9
  149. package/dist/server/lib/opencodeSessionOperations.js.map +1 -1
  150. package/dist/server/lib/opencodeTaskSessions.js +7 -12
  151. package/dist/server/lib/opencodeTaskSessions.js.map +1 -1
  152. package/dist/server/lib/postBuildChecks.js +2 -1
  153. package/dist/server/lib/postBuildChecks.js.map +1 -1
  154. package/dist/server/lib/projects.js +10 -0
  155. package/dist/server/lib/projects.js.map +1 -1
  156. package/dist/server/lib/specArtifact.js +1 -1
  157. package/dist/server/lib/specArtifact.js.map +1 -1
  158. package/dist/server/lib/specDraft.js +1 -5
  159. package/dist/server/lib/specDraft.js.map +1 -1
  160. package/dist/server/lib/specExport.js +1 -6
  161. package/dist/server/lib/specExport.js.map +1 -1
  162. package/dist/server/lib/specGraphFeature.js +3 -0
  163. package/dist/server/lib/specGraphFeature.js.map +1 -0
  164. package/dist/server/lib/specRepositoryEvidenceFormatting.js +2 -1
  165. package/dist/server/lib/specRepositoryEvidenceFormatting.js.map +1 -1
  166. package/dist/server/lib/specRepositoryImpact.js +4 -0
  167. package/dist/server/lib/specRepositoryImpact.js.map +1 -1
  168. package/dist/server/lib/specReviewContext.js +6 -20
  169. package/dist/server/lib/specReviewContext.js.map +1 -1
  170. package/dist/server/lib/specSnapshotEvidence.js +4 -0
  171. package/dist/server/lib/specSnapshotEvidence.js.map +1 -1
  172. package/dist/server/lib/specTicketMaterialization.js +4 -1
  173. package/dist/server/lib/specTicketMaterialization.js.map +1 -1
  174. package/dist/server/lib/ticketFollowUpOperations.js +8 -1
  175. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  176. package/dist/server/lib/ticketRunOperations.js +0 -3
  177. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  178. package/dist/server/lib/types.js +1 -0
  179. package/dist/server/lib/types.js.map +1 -1
  180. package/dist/server/lib/workspaceIdentity.js +3 -0
  181. package/dist/server/lib/workspaceIdentity.js.map +1 -1
  182. package/dist/server/workers/build.js +114 -8
  183. package/dist/server/workers/build.js.map +1 -1
  184. package/dist/server/workers/followUp.js +118 -3
  185. package/dist/server/workers/followUp.js.map +1 -1
  186. package/dist/server/workers/plan.js +34 -22
  187. package/dist/server/workers/plan.js.map +1 -1
  188. package/dist/server/workers/review.js +14 -13
  189. package/dist/server/workers/review.js.map +1 -1
  190. package/dist/server/workers/specReview.js +0 -1
  191. package/dist/server/workers/specReview.js.map +1 -1
  192. package/dist/server/workers/specSession.js +46 -122
  193. package/dist/server/workers/specSession.js.map +1 -1
  194. package/package.json +1 -1
  195. package/dist/client/assets/ActivityView-DpY9GZez.js +0 -1
  196. package/dist/client/assets/GitHubActionsDialog-BCr-hjvp.js +0 -1
  197. package/dist/client/assets/RepositoryGraphDialog-_hm-x1Dx.js +0 -1
  198. package/dist/client/assets/VerificationDialog-CsjDzpE1.js +0 -4
  199. package/dist/client/assets/architecture-7EHR7CIX-Dm8H7N6Z.js +0 -1
  200. package/dist/client/assets/channel-DVkhJzgx.js +0 -1
  201. package/dist/client/assets/chunk-QZHKN3VN-CtbsU1Bj.js +0 -1
  202. package/dist/client/assets/chunk-WU5MYG2G-CzMMPYdi.js +0 -1
  203. package/dist/client/assets/classDiagram-4FO5ZUOK-6oZ93s5p.js +0 -1
  204. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DYO_YnkU.js +0 -1
  205. package/dist/client/assets/eventmodeling-FCH6USID-BWhw0UJ8.js +0 -1
  206. package/dist/client/assets/gitGraph-WXDBUCRP-BSnxAa1J.js +0 -1
  207. package/dist/client/assets/index-CS4TzG41.css +0 -2
  208. package/dist/client/assets/index-D5TEfELZ.js +0 -275
  209. package/dist/client/assets/info-J43DQDTF-DcFwF1-h.js +0 -1
  210. package/dist/client/assets/packet-YPE3B663-1VZiO_9r.js +0 -1
  211. package/dist/client/assets/pie-LRSECV5Y-BX1-tQCu.js +0 -1
  212. package/dist/client/assets/radar-GUYGQ44K-B2HRrCR4.js +0 -1
  213. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-9pmeAorV.js +0 -1
  214. package/dist/client/assets/treeView-BLDUP644-CDfUVbUi.js +0 -1
  215. package/dist/client/assets/treemap-LRROVOQU-DUWqu8sK.js +0 -1
  216. package/dist/client/assets/wardley-L42UT6IY-BEFuPUCy.js +0 -1
  217. /package/dist/client/assets/{array-B2f2amhX.js → array-BNor45A1.js} +0 -0
  218. /package/dist/client/assets/{chunk-NNHCCRGN-DgKrp8T-.js → chunk-NNHCCRGN-CZB-V-Gv.js} +0 -0
  219. /package/dist/client/assets/{cytoscape.esm-BInZCgaJ.js → cytoscape.esm-B2lJcMSI.js} +0 -0
  220. /package/dist/client/assets/{defaultLocale-sPXJ5urm.js → defaultLocale-DPzUsThw.js} +0 -0
  221. /package/dist/client/assets/{dist-DfHDMDTt.js → dist-CERhmQhR.js} +0 -0
  222. /package/dist/client/assets/{graphlib-DacNv4Jd.js → graphlib-BHOW-eWz.js} +0 -0
  223. /package/dist/client/assets/{init-BVCnG1lj.js → init-C0L3woqb.js} +0 -0
  224. /package/dist/client/assets/{katex-BlkrXOwm.js → katex-CpjQsUQe.js} +0 -0
  225. /package/dist/client/assets/{path-Bw5AvBNC.js → path-sMK4d_s9.js} +0 -0
  226. /package/dist/client/assets/{rough.esm-DeLgKbOI.js → rough.esm-BoTisKeL.js} +0 -0
  227. /package/dist/client/assets/{src-CFGsdLiG.js → src-BRjoYtUW.js} +0 -0
@@ -0,0 +1,782 @@
1
+ import db from '../db.js';
2
+ import { randomUUID } from 'crypto';
3
+ import { createDockerSandboxAdapter, createDockerSandboxName, createSbxCommandRunner, DOCKER_SANDBOX_PROVIDER, QUALIFIED_DOCKER_SANDBOX_TEMPLATES, DockerSandboxCollisionFailure, DockerSandboxStopVerificationFailure, SbxCommandFailure, } from './dockerSandboxExecutionEnvironment.js';
4
+ import { appendExecutionEnvironmentEvent, claimExecutionEnvironmentsForCleanup, completeExecutionEnvironmentCleanup, failExecutionEnvironmentCleanup, getExecutionEnvironment, releaseExecutionEnvironmentLease, transitionExecutionEnvironment, } from './executionEnvironments.js';
5
+ import { getExecutionRun, transitionExecutionRun } from './executionRuns.js';
6
+ const VALIDATION_CLEANUP_TIMEOUT_MS = 30_000;
7
+ const VALIDATION_CLEANUP_REMEDIATION = 'Verify the persisted sandbox identity and stop it manually before retrying validation.';
8
+ export class ExecutionEnvironmentCleanupError extends Error {
9
+ constructor(message, options = {}) {
10
+ super(message, options);
11
+ this.name = 'ExecutionEnvironmentCleanupError';
12
+ }
13
+ }
14
+ export const createDefaultDockerSandboxAdapter = () => (createDockerSandboxAdapter(createSbxCommandRunner()));
15
+ export const resolveExpectedDockerSandbox = (environment, requestedAgent) => {
16
+ if (environment.provider_kind !== DOCKER_SANDBOX_PROVIDER) {
17
+ throw new Error(`Execution environment ${environment.id} is not a Docker Sandbox environment`);
18
+ }
19
+ if (!environment.agent_identity || !environment.template_identity) {
20
+ throw new Error(`Execution environment ${environment.id} has incomplete persisted sandbox ownership evidence`);
21
+ }
22
+ if (requestedAgent && environment.agent_identity !== requestedAgent) {
23
+ throw new Error(`Execution environment ${environment.id} is not owned by the requested agent template`);
24
+ }
25
+ return {
26
+ name: environment.external_id ?? createDockerSandboxName({
27
+ projectId: environment.project_id,
28
+ targetKey: environment.target_key,
29
+ environmentRevision: environment.environment_revision,
30
+ validationFingerprint: environment.environment_validation_fingerprint,
31
+ }),
32
+ agent: environment.agent_identity,
33
+ workspacePath: environment.target_workspace_path,
34
+ template: environment.template_identity,
35
+ };
36
+ };
37
+ const validationCleanupFailure = (error, timedOut) => {
38
+ if (timedOut || (error instanceof SbxCommandFailure && error.kind === 'timeout')) {
39
+ return { code: 'validation_cleanup_stop_timeout', message: 'Docker Sandbox cleanup timed out before stopped state was verified.' };
40
+ }
41
+ if (error instanceof DockerSandboxCollisionFailure) {
42
+ return { code: 'validation_cleanup_identity_mismatch', message: error.message };
43
+ }
44
+ if (error instanceof DockerSandboxStopVerificationFailure) {
45
+ return { code: 'validation_cleanup_unverified_running', message: error.message };
46
+ }
47
+ return {
48
+ code: 'validation_cleanup_provider_failure',
49
+ message: error instanceof Error ? error.message : 'Docker Sandbox cleanup could not verify stopped state.',
50
+ };
51
+ };
52
+ export const stopAndRecordEnvironmentValidationCleanup = async (input) => {
53
+ const expected = resolveExpectedDockerSandbox(input.environment, input.requestedAgent);
54
+ const controller = new AbortController();
55
+ let timedOut = false;
56
+ const timeoutFailure = new Promise((_resolve, reject) => {
57
+ const timeout = setTimeout(() => {
58
+ timedOut = true;
59
+ controller.abort();
60
+ reject(new Error('Docker Sandbox cleanup timed out.'));
61
+ }, VALIDATION_CLEANUP_TIMEOUT_MS);
62
+ timeout.unref?.();
63
+ controller.signal.addEventListener('abort', () => clearTimeout(timeout), { once: true });
64
+ });
65
+ try {
66
+ await Promise.race([
67
+ input.adapter.stop(expected, { signal: controller.signal, timeoutMs: VALIDATION_CLEANUP_TIMEOUT_MS }),
68
+ timeoutFailure,
69
+ ]);
70
+ const current = getExecutionEnvironment(input.environment.project_id, input.environment.id);
71
+ if (!current || current.lifecycle_state === 'retired' || current.lifecycle_state === 'failed') {
72
+ return;
73
+ }
74
+ if (current.lifecycle_state === 'stopped') {
75
+ appendExecutionEnvironmentEvent({
76
+ projectId: current.project_id,
77
+ environmentId: current.id,
78
+ eventType: 'environment_validation_cleanup_completed',
79
+ fromState: 'stopped',
80
+ toState: 'stopped',
81
+ message: 'Docker Sandbox stopped state was verified after validation.',
82
+ metadata: { executionRunId: input.executionRunId, sandboxName: expected.name },
83
+ });
84
+ return;
85
+ }
86
+ transitionExecutionEnvironment({
87
+ projectId: current.project_id,
88
+ environmentId: current.id,
89
+ state: 'stopped',
90
+ eventType: 'environment_validation_cleanup_completed',
91
+ message: 'Docker Sandbox stopped state was verified after validation.',
92
+ metadata: { executionRunId: input.executionRunId, sandboxName: expected.name },
93
+ });
94
+ }
95
+ catch (error) {
96
+ const failure = validationCleanupFailure(error, timedOut);
97
+ const current = getExecutionEnvironment(input.environment.project_id, input.environment.id);
98
+ if (!current || current.lifecycle_state === 'retired' || current.lifecycle_state === 'failed') {
99
+ return;
100
+ }
101
+ const metadata = {
102
+ executionRunId: input.executionRunId,
103
+ sandboxName: expected.name,
104
+ remediation: VALIDATION_CLEANUP_REMEDIATION,
105
+ };
106
+ if (current.lifecycle_state === 'unavailable') {
107
+ appendExecutionEnvironmentEvent({
108
+ projectId: current.project_id,
109
+ environmentId: current.id,
110
+ eventType: 'environment_validation_cleanup_failed',
111
+ fromState: 'unavailable',
112
+ toState: 'unavailable',
113
+ message: failure.message,
114
+ metadata: { ...metadata, failureCode: failure.code },
115
+ });
116
+ return;
117
+ }
118
+ transitionExecutionEnvironment({
119
+ projectId: current.project_id,
120
+ environmentId: current.id,
121
+ state: 'unavailable',
122
+ eventType: 'environment_validation_cleanup_failed',
123
+ failure: { code: failure.code, message: failure.message, details: { remediation: VALIDATION_CLEANUP_REMEDIATION } },
124
+ metadata,
125
+ });
126
+ }
127
+ finally {
128
+ controller.abort();
129
+ }
130
+ };
131
+ export const resetExecutionEnvironment = async (input) => {
132
+ const environment = getExecutionEnvironment(input.projectId, input.environmentId);
133
+ if (!environment) {
134
+ throw new ExecutionEnvironmentCleanupError(`Execution environment ${input.environmentId} was not found.`);
135
+ }
136
+ const activeBinding = db.prepare(`
137
+ SELECT execution_run_id
138
+ FROM execution_environment_run_bindings
139
+ WHERE project_id = ? AND environment_id = ? AND released_at IS NULL
140
+ LIMIT 1
141
+ `).get(input.projectId, input.environmentId);
142
+ if (activeBinding) {
143
+ throw new ExecutionEnvironmentCleanupError(`Execution environment ${input.environmentId} is still used by active run ${activeBinding.execution_run_id}.`);
144
+ }
145
+ if (environment.lifecycle_state === 'retired') {
146
+ return environment;
147
+ }
148
+ transitionExecutionEnvironment({
149
+ projectId: input.projectId,
150
+ environmentId: input.environmentId,
151
+ state: 'retiring',
152
+ eventType: 'environment_reset_started',
153
+ message: 'Docker Sandbox reset started by the user.',
154
+ });
155
+ const expected = resolveExpectedDockerSandbox(environment);
156
+ try {
157
+ const observed = await input.adapter.inspect(expected.name);
158
+ if (observed && !inspectionMatches(expected, observed)) {
159
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} does not match Archon's persisted external identity`);
160
+ }
161
+ if (observed?.state === 'running') {
162
+ await input.adapter.stop(expected);
163
+ }
164
+ const afterStop = await input.adapter.inspect(expected.name);
165
+ if (afterStop && !inspectionMatches(expected, afterStop)) {
166
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} was replaced during reset`);
167
+ }
168
+ if (afterStop) {
169
+ await input.adapter.remove(expected);
170
+ }
171
+ const remaining = await input.adapter.inspect(expected.name);
172
+ if (remaining) {
173
+ throw new ExecutionEnvironmentCleanupError(`Sandbox ${expected.name} still exists after reset.`);
174
+ }
175
+ transitionExecutionEnvironment({
176
+ projectId: input.projectId,
177
+ environmentId: input.environmentId,
178
+ state: 'cleanup_pending',
179
+ eventType: 'environment_reset_cleanup_started',
180
+ });
181
+ return transitionExecutionEnvironment({
182
+ projectId: input.projectId,
183
+ environmentId: input.environmentId,
184
+ state: 'retired',
185
+ eventType: 'environment_reset_completed',
186
+ message: 'Docker Sandbox was removed and verified absent.',
187
+ });
188
+ }
189
+ catch (error) {
190
+ const current = getExecutionEnvironment(input.projectId, input.environmentId);
191
+ if (current && (current.lifecycle_state === 'retiring' || current.lifecycle_state === 'cleanup_pending')) {
192
+ transitionExecutionEnvironment({
193
+ projectId: input.projectId,
194
+ environmentId: input.environmentId,
195
+ state: 'unavailable',
196
+ eventType: 'environment_reset_failed',
197
+ failure: {
198
+ code: error instanceof DockerSandboxCollisionFailure ? 'identity_mismatch' : 'reset_failed',
199
+ message: error instanceof Error ? error.message : 'Docker Sandbox reset failed.',
200
+ },
201
+ });
202
+ }
203
+ throw error;
204
+ }
205
+ };
206
+ export const stopAndRecordEnvironmentExecutionCleanup = async (input) => {
207
+ const expected = resolveExpectedDockerSandbox(input.environment);
208
+ try {
209
+ const before = await input.adapter.inspect(expected.name);
210
+ if (before && !inspectionMatches(expected, before)) {
211
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} does not match Archon's persisted external identity`);
212
+ }
213
+ if (before?.state === 'running') {
214
+ await input.adapter.stop(expected, { timeoutMs: VALIDATION_CLEANUP_TIMEOUT_MS });
215
+ }
216
+ const after = await input.adapter.inspect(expected.name);
217
+ if (after && !inspectionMatches(expected, after)) {
218
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} was replaced during execution cleanup`);
219
+ }
220
+ if (after?.state === 'running') {
221
+ throw new DockerSandboxStopVerificationFailure(`Sandbox ${expected.name} remained running`);
222
+ }
223
+ const current = getExecutionEnvironment(input.environment.project_id, input.environment.id);
224
+ if (!current || current.lifecycle_state === 'retired') {
225
+ return;
226
+ }
227
+ const metadata = { executionRunId: input.executionRunId, sandboxName: expected.name };
228
+ if (!after) {
229
+ if (current.lifecycle_state === 'unavailable' || current.lifecycle_state === 'failed') {
230
+ appendExecutionEnvironmentEvent({
231
+ projectId: current.project_id,
232
+ environmentId: current.id,
233
+ eventType: 'environment_execution_cleanup_absence_verified',
234
+ fromState: current.lifecycle_state,
235
+ toState: current.lifecycle_state,
236
+ message: 'Docker Sandbox absence was verified after execution interruption.',
237
+ metadata,
238
+ });
239
+ }
240
+ else {
241
+ transitionExecutionEnvironment({
242
+ projectId: current.project_id,
243
+ environmentId: current.id,
244
+ state: 'unavailable',
245
+ eventType: 'environment_execution_cleanup_absence_verified',
246
+ failure: { code: 'sandbox_missing', message: 'Docker Sandbox absence was verified after execution interruption.' },
247
+ metadata,
248
+ });
249
+ }
250
+ return;
251
+ }
252
+ if (current.lifecycle_state === 'stopped') {
253
+ appendExecutionEnvironmentEvent({
254
+ projectId: current.project_id,
255
+ environmentId: current.id,
256
+ eventType: 'environment_execution_cleanup_completed',
257
+ fromState: 'stopped',
258
+ toState: 'stopped',
259
+ message: 'Docker Sandbox stopped state was verified after execution interruption.',
260
+ metadata,
261
+ });
262
+ }
263
+ else if (current.lifecycle_state !== 'failed' && current.lifecycle_state !== 'unavailable') {
264
+ transitionExecutionEnvironment({
265
+ projectId: current.project_id,
266
+ environmentId: current.id,
267
+ state: 'stopped',
268
+ eventType: 'environment_execution_cleanup_completed',
269
+ message: 'Docker Sandbox stopped state was verified after execution interruption.',
270
+ metadata,
271
+ });
272
+ }
273
+ }
274
+ catch (error) {
275
+ const current = getExecutionEnvironment(input.environment.project_id, input.environment.id);
276
+ const message = error instanceof Error ? error.message : 'Docker Sandbox execution cleanup could not verify stopped state.';
277
+ if (current && current.lifecycle_state !== 'retired') {
278
+ const metadata = { executionRunId: input.executionRunId, sandboxName: expected.name };
279
+ if (current.lifecycle_state === 'failed' || current.lifecycle_state === 'unavailable') {
280
+ appendExecutionEnvironmentEvent({
281
+ projectId: current.project_id,
282
+ environmentId: current.id,
283
+ eventType: 'environment_execution_cleanup_failed',
284
+ fromState: current.lifecycle_state,
285
+ toState: current.lifecycle_state,
286
+ message,
287
+ metadata,
288
+ });
289
+ }
290
+ else {
291
+ transitionExecutionEnvironment({
292
+ projectId: current.project_id,
293
+ environmentId: current.id,
294
+ state: 'unavailable',
295
+ eventType: 'environment_execution_cleanup_failed',
296
+ failure: { code: 'execution_termination_unverified', message },
297
+ metadata,
298
+ });
299
+ }
300
+ }
301
+ throw new ExecutionEnvironmentCleanupError(`Unable to prove termination of execution run ${input.executionRunId} in sandbox ${expected.name}`, { cause: error });
302
+ }
303
+ };
304
+ const inspectionMatches = (expected, observed) => (expected.name === observed.name
305
+ && expected.agent === observed.agent
306
+ && expected.workspacePath === observed.workspacePath
307
+ && expected.template === observed.template);
308
+ const parseProductionAuthority = (binding) => {
309
+ if (!binding.authority_json) {
310
+ return null;
311
+ }
312
+ try {
313
+ const authority = JSON.parse(binding.authority_json);
314
+ if (authority?.schema_version !== 1
315
+ || authority.provider !== DOCKER_SANDBOX_PROVIDER
316
+ || authority.environment_id !== binding.environment_id
317
+ || authority.environment_revision !== binding.environment_revision
318
+ || authority.environment_validation_fingerprint !== binding.environment_validation_fingerprint
319
+ || (authority.agent !== 'opencode' && authority.agent !== 'claude')
320
+ || typeof authority.target?.workspace_path !== 'string'
321
+ || typeof authority.target?.key !== 'string') {
322
+ return null;
323
+ }
324
+ return authority;
325
+ }
326
+ catch {
327
+ return null;
328
+ }
329
+ };
330
+ const parseProductionPlacement = (binding) => {
331
+ if (!binding.placement_json) {
332
+ return null;
333
+ }
334
+ try {
335
+ const placement = JSON.parse(binding.placement_json);
336
+ if (placement?.provider !== DOCKER_SANDBOX_PROVIDER
337
+ || placement.environmentId !== binding.environment_id
338
+ || placement.environmentRevision !== binding.environment_revision
339
+ || placement.executionRunId !== binding.execution_run_id
340
+ || placement.scope?.kind !== 'exact-sandbox'
341
+ || typeof placement.scope.externalId !== 'string') {
342
+ return null;
343
+ }
344
+ return placement;
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ };
350
+ const interruptProductionRunAfterCleanupFailure = (binding, message) => {
351
+ const run = getExecutionRun(binding.project_id, binding.execution_run_id);
352
+ if (!run || !['queued', 'running', 'waiting'].includes(run.status)) {
353
+ return;
354
+ }
355
+ transitionExecutionRun({
356
+ projectId: binding.project_id,
357
+ runId: binding.execution_run_id,
358
+ status: 'interrupted',
359
+ domainStatus: 'interrupted',
360
+ failure: {
361
+ code: 'production_environment_termination_unverified',
362
+ message: `Startup could not verify termination of the production Docker Sandbox placement: ${message}`,
363
+ details: {
364
+ environmentId: binding.environment_id,
365
+ environmentRevision: binding.environment_revision,
366
+ placementRecorded: binding.placement_json !== null,
367
+ },
368
+ },
369
+ eventType: 'production_environment_reconciliation_failed',
370
+ eventMessage: 'The production environment lease was retained because termination could not be verified.',
371
+ eventMetadata: {
372
+ environmentId: binding.environment_id,
373
+ environmentRevision: binding.environment_revision,
374
+ leaseRetained: true,
375
+ placementRecorded: binding.placement_json !== null,
376
+ },
377
+ });
378
+ };
379
+ export const reconcileProductionExecutionEnvironmentBindings = async (adapter = createDefaultDockerSandboxAdapter()) => {
380
+ const bindings = db.prepare(`
381
+ SELECT *
382
+ FROM execution_environment_run_bindings
383
+ WHERE relationship = 'execution' AND released_at IS NULL
384
+ ORDER BY bound_at ASC, execution_run_id ASC
385
+ `).all();
386
+ const summary = { cleaned: 0, failed: 0 };
387
+ for (const binding of bindings) {
388
+ const environment = getExecutionEnvironment(binding.project_id, binding.environment_id);
389
+ const authority = parseProductionAuthority(binding);
390
+ const parsedPlacement = parseProductionPlacement(binding);
391
+ try {
392
+ if (!environment) {
393
+ throw new Error(`Execution environment ${binding.environment_id} is missing`);
394
+ }
395
+ if (!authority) {
396
+ throw new Error('The immutable execution authority is malformed or does not match its lease');
397
+ }
398
+ if (binding.placement_json !== null && !parsedPlacement) {
399
+ throw new Error('The immutable execution placement is malformed or does not match its lease');
400
+ }
401
+ const placement = parsedPlacement
402
+ && parsedPlacement.providerVersion === authority.provider_version
403
+ && parsedPlacement.targetKey === authority.target.key
404
+ ? parsedPlacement
405
+ : null;
406
+ if (parsedPlacement && !placement) {
407
+ throw new Error('The immutable execution placement does not match its authority');
408
+ }
409
+ const externalId = placement?.scope.kind === 'exact-sandbox'
410
+ ? placement.scope.externalId
411
+ : environment.external_id;
412
+ if (!externalId) {
413
+ throw new Error('No exact persisted sandbox identity is available');
414
+ }
415
+ if (placement?.scope.kind === 'exact-sandbox' && environment.external_id && externalId !== environment.external_id) {
416
+ throw new Error('The immutable placement and live sandbox identity disagree');
417
+ }
418
+ const expected = {
419
+ name: externalId,
420
+ agent: authority.agent,
421
+ workspacePath: authority.target.workspace_path,
422
+ template: QUALIFIED_DOCKER_SANDBOX_TEMPLATES[authority.agent],
423
+ };
424
+ const before = await adapter.inspect(expected.name);
425
+ if (before && !inspectionMatches(expected, before)) {
426
+ throw new DockerSandboxCollisionFailure(expected.name, 'The persisted production sandbox identity does not match provider inspection');
427
+ }
428
+ if (before?.state === 'running') {
429
+ await adapter.stop(expected, { timeoutMs: VALIDATION_CLEANUP_TIMEOUT_MS });
430
+ }
431
+ const after = await adapter.inspect(expected.name);
432
+ if (after && !inspectionMatches(expected, after)) {
433
+ throw new DockerSandboxCollisionFailure(expected.name, 'The production sandbox identity changed before termination was verified');
434
+ }
435
+ if (after?.state === 'running') {
436
+ throw new DockerSandboxStopVerificationFailure(`Sandbox ${expected.name} remained running`);
437
+ }
438
+ const state = after ? 'stopped' : 'absent';
439
+ const cleanupCompletion = state === 'stopped'
440
+ ? {
441
+ state,
442
+ eventType: 'production_environment_reconciliation_completed',
443
+ message: 'Production Docker Sandbox termination was positively verified before its lease was released.',
444
+ metadata: { executionRunId: binding.execution_run_id, sandboxName: externalId, providerState: state },
445
+ }
446
+ : {
447
+ state,
448
+ eventType: 'production_environment_reconciliation_sandbox_missing',
449
+ failure: { code: 'sandbox_missing', message: 'The exact production Docker Sandbox is absent.' },
450
+ metadata: { executionRunId: binding.execution_run_id, sandboxName: externalId, providerState: state },
451
+ };
452
+ if (placement) {
453
+ releaseExecutionEnvironmentLease({
454
+ projectId: binding.project_id,
455
+ environmentId: binding.environment_id,
456
+ executionRunId: binding.execution_run_id,
457
+ proof: { kind: 'placement_terminated', inspection: { identity: placement, state } },
458
+ cleanupCompletion,
459
+ });
460
+ }
461
+ else {
462
+ releaseExecutionEnvironmentLease({
463
+ projectId: binding.project_id,
464
+ environmentId: binding.environment_id,
465
+ executionRunId: binding.execution_run_id,
466
+ proof: { kind: 'exact_sandbox_stopped', externalId, state },
467
+ cleanupCompletion,
468
+ });
469
+ }
470
+ summary.cleaned += 1;
471
+ }
472
+ catch (error) {
473
+ const message = error instanceof Error ? error.message : 'Production environment termination could not be verified.';
474
+ const current = environment && getExecutionEnvironment(binding.project_id, binding.environment_id);
475
+ if (current && !['unavailable', 'retired'].includes(current.lifecycle_state)) {
476
+ const failureState = current.lifecycle_state === 'failed' ? 'cleanup_pending' : 'unavailable';
477
+ transitionExecutionEnvironment({
478
+ projectId: binding.project_id,
479
+ environmentId: binding.environment_id,
480
+ state: failureState,
481
+ eventType: 'production_environment_reconciliation_failed',
482
+ failure: { code: 'production_termination_unverified', message },
483
+ metadata: { executionRunId: binding.execution_run_id, leaseRetained: true },
484
+ });
485
+ }
486
+ else if (current) {
487
+ appendExecutionEnvironmentEvent({
488
+ projectId: binding.project_id,
489
+ environmentId: binding.environment_id,
490
+ eventType: 'production_environment_reconciliation_failed',
491
+ fromState: current.lifecycle_state,
492
+ toState: current.lifecycle_state,
493
+ message,
494
+ metadata: { executionRunId: binding.execution_run_id, leaseRetained: true },
495
+ });
496
+ }
497
+ interruptProductionRunAfterCleanupFailure(binding, message);
498
+ summary.failed += 1;
499
+ }
500
+ }
501
+ return summary;
502
+ };
503
+ const recordObservation = (environment, observed) => {
504
+ const targetState = observed.state === 'stopped' ? 'stopped' : 'ready';
505
+ const allowed = ((targetState === 'stopped' && ['preparing', 'ready', 'running'].includes(environment.lifecycle_state))
506
+ || (targetState === 'ready' && ['preparing', 'running', 'stopped'].includes(environment.lifecycle_state)));
507
+ if (environment.lifecycle_state !== targetState && allowed) {
508
+ transitionExecutionEnvironment({
509
+ projectId: environment.project_id,
510
+ environmentId: environment.id,
511
+ state: targetState,
512
+ eventType: 'environment_reconciled',
513
+ message: `Docker Sandbox was observed ${observed.state} during startup reconciliation.`,
514
+ metadata: { providerState: observed.state },
515
+ });
516
+ return;
517
+ }
518
+ appendExecutionEnvironmentEvent({
519
+ projectId: environment.project_id,
520
+ environmentId: environment.id,
521
+ eventType: 'environment_reconciled',
522
+ fromState: environment.lifecycle_state,
523
+ toState: environment.lifecycle_state,
524
+ message: `Docker Sandbox was observed ${observed.state} during startup reconciliation.`,
525
+ metadata: { providerState: observed.state },
526
+ });
527
+ };
528
+ export const reconcileExecutionEnvironments = async (adapter = createDefaultDockerSandboxAdapter()) => {
529
+ const rows = db.prepare(`
530
+ SELECT id, project_id
531
+ FROM execution_environments
532
+ WHERE lifecycle_state <> 'retired'
533
+ ORDER BY created_at ASC, id ASC
534
+ `).all();
535
+ const summary = { observed_running: 0, observed_stopped: 0, observed_missing: 0, unavailable: 0, deferred: 0 };
536
+ for (const row of rows) {
537
+ const environment = getExecutionEnvironment(row.project_id, row.id);
538
+ if (!environment || environment.provider_kind !== DOCKER_SANDBOX_PROVIDER) {
539
+ summary.deferred += 1;
540
+ continue;
541
+ }
542
+ try {
543
+ const expected = resolveExpectedDockerSandbox(environment);
544
+ const observed = await adapter.inspect(expected.name);
545
+ if (!observed) {
546
+ if (environment.lifecycle_state === 'failed') {
547
+ appendExecutionEnvironmentEvent({
548
+ projectId: environment.project_id,
549
+ environmentId: environment.id,
550
+ eventType: 'environment_reconciliation_missing',
551
+ fromState: environment.lifecycle_state,
552
+ toState: environment.lifecycle_state,
553
+ message: 'The persisted Docker Sandbox was not found during startup reconciliation.',
554
+ metadata: { failureCode: 'sandbox_missing' },
555
+ });
556
+ summary.observed_missing += 1;
557
+ continue;
558
+ }
559
+ transitionExecutionEnvironment({
560
+ projectId: environment.project_id,
561
+ environmentId: environment.id,
562
+ state: 'unavailable',
563
+ eventType: 'environment_reconciliation_missing',
564
+ failure: { code: 'sandbox_missing', message: 'The persisted Docker Sandbox was not found during startup reconciliation.' },
565
+ });
566
+ summary.observed_missing += 1;
567
+ continue;
568
+ }
569
+ if (!inspectionMatches(expected, observed)) {
570
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} does not match Archon's persisted external identity`);
571
+ }
572
+ recordObservation(environment, observed);
573
+ summary[observed.state === 'running' ? 'observed_running' : 'observed_stopped'] += 1;
574
+ }
575
+ catch (error) {
576
+ const current = getExecutionEnvironment(environment.project_id, environment.id);
577
+ const message = error instanceof Error ? error.message : 'Docker Sandbox reconciliation failed.';
578
+ if (current && !['failed', 'unavailable', 'retired'].includes(current.lifecycle_state)) {
579
+ transitionExecutionEnvironment({
580
+ projectId: current.project_id,
581
+ environmentId: current.id,
582
+ state: 'unavailable',
583
+ eventType: 'environment_reconciliation_unavailable',
584
+ failure: { code: error instanceof DockerSandboxCollisionFailure ? 'identity_mismatch' : 'provider_unavailable', message },
585
+ });
586
+ }
587
+ else if (current) {
588
+ appendExecutionEnvironmentEvent({
589
+ projectId: current.project_id,
590
+ environmentId: current.id,
591
+ eventType: 'environment_reconciliation_unavailable',
592
+ fromState: current.lifecycle_state,
593
+ toState: current.lifecycle_state,
594
+ message,
595
+ });
596
+ }
597
+ summary.unavailable += 1;
598
+ }
599
+ }
600
+ return summary;
601
+ };
602
+ const listOrphanedExecutionEnvironmentScopes = () => {
603
+ const rows = db.prepare(`
604
+ SELECT DISTINCT environment.project_id, environment.target_kind, environment.target_key
605
+ FROM execution_environments environment
606
+ INNER JOIN projects project
607
+ ON project.id = environment.project_id
608
+ AND project.deletion_started_at IS NULL
609
+ WHERE environment.lifecycle_state <> 'retired'
610
+ AND (
611
+ (
612
+ environment.target_kind = 'bundle-worktree'
613
+ AND NOT EXISTS (
614
+ SELECT 1
615
+ FROM worktree_bundles bundle
616
+ WHERE bundle.project_id = environment.project_id
617
+ AND environment.target_key = 'project:' || environment.project_id || ':bundle:' || bundle.id
618
+ )
619
+ )
620
+ OR (
621
+ environment.target_kind = 'branch-worktree'
622
+ AND NOT EXISTS (
623
+ SELECT 1
624
+ FROM tickets ticket
625
+ WHERE ticket.project_id = environment.project_id
626
+ AND ticket.worktree_bundle_id IS NULL
627
+ AND ticket.branch IS NOT NULL
628
+ AND environment.target_key = 'project:' || ticket.project_id || ':branch:' || ticket.branch
629
+ )
630
+ )
631
+ )
632
+ ORDER BY environment.project_id ASC, environment.target_kind ASC, environment.target_key ASC
633
+ `).all();
634
+ return rows.map(row => ({
635
+ projectId: row.project_id,
636
+ targetKind: row.target_kind,
637
+ targetKey: row.target_key,
638
+ }));
639
+ };
640
+ export const reconcileOrphanedExecutionEnvironments = async (adapter = createDefaultDockerSandboxAdapter()) => {
641
+ const scopes = listOrphanedExecutionEnvironmentScopes();
642
+ const summary = { scanned: scopes.length, cleaned: 0, failed: 0 };
643
+ for (const scope of scopes) {
644
+ try {
645
+ const result = await cleanupExecutionEnvironmentsForTarget({ ...scope, adapter });
646
+ summary.cleaned += result.cleaned;
647
+ }
648
+ catch {
649
+ summary.failed += 1;
650
+ }
651
+ }
652
+ return summary;
653
+ };
654
+ const getCleanupScopeQuery = (scope, alias = '') => {
655
+ const column = (name) => alias ? `${alias}.${name}` : name;
656
+ const conditions = [`${column('project_id')} = ?`];
657
+ const params = [scope.projectId];
658
+ if (scope.targetKind !== undefined) {
659
+ conditions.push(`${column('target_kind')} = ?`);
660
+ params.push(scope.targetKind);
661
+ }
662
+ if (scope.targetKey !== undefined) {
663
+ conditions.push(`${column('target_key')} = ?`);
664
+ params.push(scope.targetKey);
665
+ }
666
+ return { where: conditions.join(' AND '), params };
667
+ };
668
+ const assertNoActiveEnvironmentBindings = (scope) => {
669
+ const query = getCleanupScopeQuery(scope, 'environment');
670
+ const active = db.prepare(`
671
+ SELECT binding.environment_id, binding.execution_run_id
672
+ FROM execution_environment_run_bindings binding
673
+ JOIN execution_environments environment
674
+ ON environment.id = binding.environment_id AND environment.project_id = binding.project_id
675
+ WHERE ${query.where}
676
+ AND binding.released_at IS NULL
677
+ LIMIT 1
678
+ `).get(...query.params);
679
+ if (active) {
680
+ throw new ExecutionEnvironmentCleanupError(`Execution environment ${active.environment_id} is still used by active run ${active.execution_run_id}`);
681
+ }
682
+ };
683
+ const prepareEnvironmentsForCleanup = (scope, eventType, message) => {
684
+ const query = getCleanupScopeQuery(scope);
685
+ const rows = db.prepare(`
686
+ SELECT id FROM execution_environments
687
+ WHERE ${query.where} AND lifecycle_state NOT IN ('retiring', 'cleanup_pending', 'retired')
688
+ ORDER BY created_at ASC, id ASC
689
+ `).all(...query.params);
690
+ for (const row of rows) {
691
+ transitionExecutionEnvironment({
692
+ projectId: scope.projectId,
693
+ environmentId: row.id,
694
+ state: 'retiring',
695
+ eventType,
696
+ message,
697
+ });
698
+ }
699
+ };
700
+ const cleanupExecutionEnvironments = async (scope, claimId, adapter = createDefaultDockerSandboxAdapter(), options) => {
701
+ assertNoActiveEnvironmentBindings(scope);
702
+ prepareEnvironmentsForCleanup(scope, options.startedEventType, options.startedMessage);
703
+ const staleBefore = new Date().toISOString();
704
+ let cleaned = 0;
705
+ while (true) {
706
+ const claimed = claimExecutionEnvironmentsForCleanup({
707
+ projectId: scope.projectId,
708
+ targetKind: scope.targetKind,
709
+ targetKey: scope.targetKey,
710
+ claimId,
711
+ limit: 200,
712
+ staleBefore,
713
+ });
714
+ if (claimed.length === 0) {
715
+ break;
716
+ }
717
+ const failures = [];
718
+ for (const environment of claimed) {
719
+ try {
720
+ const expected = resolveExpectedDockerSandbox(environment);
721
+ const observed = await adapter.inspect(expected.name);
722
+ if (observed && !inspectionMatches(expected, observed)) {
723
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} does not match Archon's persisted external identity`);
724
+ }
725
+ if (observed?.state === 'running') {
726
+ await adapter.stop(expected);
727
+ }
728
+ if (observed) {
729
+ await adapter.remove(expected);
730
+ }
731
+ const remaining = await adapter.inspect(expected.name);
732
+ if (remaining && !inspectionMatches(expected, remaining)) {
733
+ throw new DockerSandboxCollisionFailure(expected.name, `Sandbox ${expected.name} was replaced before absence could be verified`);
734
+ }
735
+ if (remaining) {
736
+ throw new Error(`Sandbox ${expected.name} still exists after removal`);
737
+ }
738
+ completeExecutionEnvironmentCleanup({ projectId: scope.projectId, environmentId: environment.id, claimId });
739
+ }
740
+ catch (error) {
741
+ const failure = error instanceof Error ? error : new Error(String(error));
742
+ failExecutionEnvironmentCleanup({
743
+ projectId: scope.projectId,
744
+ environmentId: environment.id,
745
+ claimId,
746
+ failure: {
747
+ code: error instanceof DockerSandboxCollisionFailure ? 'identity_mismatch' : 'cleanup_failed',
748
+ message: failure.message,
749
+ },
750
+ });
751
+ failures.push(failure);
752
+ }
753
+ }
754
+ if (failures.length > 0) {
755
+ throw new ExecutionEnvironmentCleanupError(`Failed to clean ${failures.length} execution environment(s): ${failures[0]?.message ?? 'Unknown cleanup failure'}`, { cause: new AggregateError(failures) });
756
+ }
757
+ cleaned += claimed.length;
758
+ }
759
+ const query = getCleanupScopeQuery(scope);
760
+ const remaining = db.prepare(`
761
+ SELECT id FROM execution_environments
762
+ WHERE ${query.where} AND lifecycle_state <> 'retired'
763
+ LIMIT 1
764
+ `).get(...query.params);
765
+ if (remaining) {
766
+ throw new ExecutionEnvironmentCleanupError(`Execution environment ${remaining.id} could not be claimed for verified cleanup.`);
767
+ }
768
+ return { cleaned };
769
+ };
770
+ export const cleanupExecutionEnvironmentsForTarget = async (input) => cleanupExecutionEnvironments({
771
+ projectId: input.projectId,
772
+ targetKind: input.targetKind,
773
+ targetKey: input.targetKey,
774
+ }, randomUUID(), input.adapter, {
775
+ startedEventType: 'environment_target_deletion_started',
776
+ startedMessage: 'Environment cleanup started because its execution target is being deleted.',
777
+ });
778
+ export const cleanupProjectExecutionEnvironments = async (projectId, claimId, adapter = createDefaultDockerSandboxAdapter()) => cleanupExecutionEnvironments({ projectId }, claimId, adapter, {
779
+ startedEventType: 'environment_project_deletion_started',
780
+ startedMessage: 'Environment cleanup started for claimed project deletion.',
781
+ });
782
+ //# sourceMappingURL=executionEnvironmentReconciliation.js.map