@straiffi/archon 1.8.14 → 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.
- package/dist/client/assets/ActivityView-p8gBUxYN.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-BRxGcSyq.js +1 -0
- package/dist/client/assets/VerificationDialog-B6l5TYOS.js +4 -0
- package/dist/client/assets/{arc-C83vRtlz.js → arc-D2Nn9xIj.js} +1 -1
- package/dist/client/assets/architecture-7EHR7CIX-C-4Cp4ml.js +1 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DFbki8mi.js → architectureDiagram-3BPJPVTR-DnuKO1RY.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BZFehG8P.js → blockDiagram-GPEHLZMM-CnQ7FwEp.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-FVDXRu1t.js → c4Diagram-AAUBKEIU-BF1hFXwp.js} +1 -1
- package/dist/client/assets/channel-TJtf4cBJ.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-4tuVZaT0.js → chunk-2J33WTMH-UvCFvkZO.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DSn14S35.js → chunk-3OPIFGDE-z1l5nBD0.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-C5pU9ApX.js → chunk-4BX2VUAB-CtLS8D71.js} +1 -1
- package/dist/client/assets/{chunk-4EGX6M5U-CAjWPjND.js → chunk-4EGX6M5U-Btnm0QNe.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-DhO4-AG4.js → chunk-55IACEB6-74CcJaub.js} +1 -1
- package/dist/client/assets/{chunk-5DO6E6H7-BDKA-zHS.js → chunk-5DO6E6H7-WLZ8L4ko.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-Cwx1KNjg.js → chunk-5ZQYHXKU-Bis0Pw2_.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Odza4hes.js → chunk-727SXJPM-B3gUO8rD.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-BmVoQT7M.js → chunk-AQP2D5EJ-hPBzrrh5.js} +1 -1
- package/dist/client/assets/{chunk-BR22UD5L-12D2GWh4.js → chunk-BR22UD5L-Cou5beGe.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-DMi77e84.js → chunk-BSJP7CBP-DJ57XcV5.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-Bg7Ktx0B.js → chunk-CSCIHK7Q-BfOng_VY.js} +2 -2
- package/dist/client/assets/{chunk-FHYWG6QK-BhSajKiB.js → chunk-FHYWG6QK-Ba10e2kj.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Drmlc0wM.js → chunk-FMBD7UC4-CEo2wZMx.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-2tnhar2x.js → chunk-KSCS5N6A-B2Jg7PW5.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-dAyg8qMJ.js → chunk-L5ZTLDWV-CUYujZxt.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-0gElxrlR.js → chunk-LZXEDZCA-CH_eV8jH.js} +2 -2
- package/dist/client/assets/{chunk-MPE355IW-Cw8YmjTC.js → chunk-MPE355IW-MqEt-J8G.js} +1 -1
- package/dist/client/assets/{chunk-MZUSXYTE-jvxRCZ7x.js → chunk-MZUSXYTE-DdNi9lhx.js} +1 -1
- package/dist/client/assets/{chunk-N66VUXT2-00i4y0yD.js → chunk-N66VUXT2-CjemTW4E.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-Bu-8Lz91.js → chunk-ND2GUHAM-BVF5evM9.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-CInZFdzd.js → chunk-NZK2D7GU-WfG-2AOS.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-rAzpvdNR.js → chunk-O5CBEL6O-CSF9ozqy.js} +1 -1
- package/dist/client/assets/{chunk-PUPMXCY4-DfWourL7.js → chunk-PUPMXCY4-ZvwTw0To.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-LI_RZca2.js +1 -0
- package/dist/client/assets/{chunk-UIBZB4QT-v-a5ZfN6.js → chunk-UIBZB4QT-BTY9be9s.js} +1 -1
- package/dist/client/assets/{chunk-WCWK7LTN-CtYzhvvb.js → chunk-WCWK7LTN-Db1n3n3-.js} +1 -1
- package/dist/client/assets/chunk-WU5MYG2G-DoVpb2oe.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-CvnAuXH2.js → chunk-XPW4576I-DObFPxjB.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CnKIRKWC.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-5ZgjUwTv.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-fchD-dH2.js → cose-bilkent-S5V4N54A-MlOPiG6P.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Dgua1iuh.js → dagre-BM42HDAG-rekDIYnC.js} +1 -1
- package/dist/client/assets/{dagre-DSrzm-Gq.js → dagre-BWNHuDdf.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-odacwlwu.js → diagram-2AECGRRQ-Z4mXdhW0.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-BiKjFmJ1.js → diagram-5GNKFQAL-DqeVe5Vc.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CmYcvFri.js → diagram-KO2AKTUF-ijppTuNc.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-ncx0RBuX.js → diagram-LMA3HP47-CDGLx0-N.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DbchKIzw.js → diagram-OG6HWLK6-DDlnjzF8.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-CMnpvRyQ.js → erDiagram-TEJ5UH35-V_axe9oz.js} +1 -1
- package/dist/client/assets/eventmodeling-FCH6USID-Dqb6QKoI.js +1 -0
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Bi02_PK5.js → flowDiagram-I6XJVG4X-GH5q79jg.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-BiS992US.js → ganttDiagram-6RSMTGT7-BMWVdLeQ.js} +1 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-DYEsqh93.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Cc1hrTsT.js → gitGraphDiagram-PVQCEYII-VrV8jMUg.js} +1 -1
- package/dist/client/assets/index-B7J4DYDu.js +275 -0
- package/dist/client/assets/index-CsZfTdMO.css +2 -0
- package/dist/client/assets/info-J43DQDTF-CfjXFZai.js +1 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-BKVfqJCe.js → infoDiagram-5YYISTIA-EGiVbp17.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-B-IbFOty.js → ishikawaDiagram-YF4QCWOH-Dw3CiXbm.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DAZBgAQW.js → journeyDiagram-JHISSGLW-2S631RY5.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Bkr_fXev.js → kanban-definition-UN3LZRKU-pc9vzUtB.js} +1 -1
- package/dist/client/assets/{line-CROuuXDO.js → line-BBMBmEjI.js} +1 -1
- package/dist/client/assets/{linear-BOwW57yX.js → linear-DT4rSZvy.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-DrBw_Rsc.js → mermaid-parser.core-ClNeolx0.js} +2 -2
- package/dist/client/assets/{mermaid.core-pPd6OYYj.js → mermaid.core-Q4IJbm5V.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BXSd3TZU.js → mindmap-definition-RKZ34NQL-DUTZxR8H.js} +1 -1
- package/dist/client/assets/{ordinal-CZ33lFpK.js → ordinal-k--hYEme.js} +1 -1
- package/dist/client/assets/packet-YPE3B663-Dv0HkRHU.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-S5zPzZFN.js +1 -0
- package/dist/client/assets/{pieDiagram-4H26LBE5-DoT7PIZC.js → pieDiagram-4H26LBE5-RW5hNtJq.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-ycZkLowm.js → quadrantDiagram-W4KKPZXB-CK60c7X9.js} +1 -1
- package/dist/client/assets/radar-GUYGQ44K-B7psLv22.js +1 -0
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BPkO1yLu.js → requirementDiagram-4Y6WPE33-Dz35FvcC.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-D7AvhhCw.js → sankeyDiagram-5OEKKPKP-ByyluoXu.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-C982swnH.js → sequenceDiagram-3UESZ5HK-C-879UFe.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-xvJ3u8lc.js → stateDiagram-AJRCARHV-B8neDlUu.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-B4Mq8Abu.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-D89qjzb5.js → timeline-definition-PNZ67QCA-iAwrltn1.js} +1 -1
- package/dist/client/assets/treeView-BLDUP644-B8H0kT67.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-DPLhFHnh.js +1 -0
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CoKeC__u.js → vennDiagram-CIIHVFJN-DHN61m8d.js} +1 -1
- package/dist/client/assets/wardley-L42UT6IY-B5-Vb08V.js +1 -0
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DKhjzvSE.js → wardleyDiagram-YWT4CUSO-XDVo7Jy1.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DFn9ass1.js → xychartDiagram-2RQKCTM6-Cb-HLpaN.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +237 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +211 -87
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +198 -100
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +14 -9
- package/dist/server/lib/agentProvider.js.map +1 -1
- package/dist/server/lib/agentRuns.js +20 -9
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/buildExecutionEnvironment.js +517 -0
- package/dist/server/lib/buildExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/buildOpenQuestionOperations.js +9 -3
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -1
- package/dist/server/lib/chatOperations.js +30 -1
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/checkAutoFix.js +1 -0
- package/dist/server/lib/checkAutoFix.js.map +1 -1
- package/dist/server/lib/checkRepairDispatch.js +3 -1
- package/dist/server/lib/checkRepairDispatch.js.map +1 -1
- package/dist/server/lib/dockerSandboxCredentials.js +62 -0
- package/dist/server/lib/dockerSandboxCredentials.js.map +1 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js +54 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -0
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js +631 -0
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/dockerSandboxExecutionProvider.js +196 -0
- package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -0
- package/dist/server/lib/dockerSandboxOpencodeSessionOperations.js +29 -0
- package/dist/server/lib/dockerSandboxOpencodeSessionOperations.js.map +1 -0
- package/dist/server/lib/dockerSandboxRuntimeController.js +187 -0
- package/dist/server/lib/dockerSandboxRuntimeController.js.map +1 -0
- package/dist/server/lib/executionEnvironment.js +22 -0
- package/dist/server/lib/executionEnvironment.js.map +1 -1
- package/dist/server/lib/executionEnvironmentContexts.js +144 -0
- package/dist/server/lib/executionEnvironmentContexts.js.map +1 -0
- package/dist/server/lib/executionEnvironmentLifecycle.js +63 -0
- package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -0
- package/dist/server/lib/executionEnvironmentReconciliation.js +782 -0
- package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -0
- package/dist/server/lib/executionEnvironmentRoutes.js +354 -0
- package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -0
- package/dist/server/lib/executionEnvironmentValidation.js +486 -0
- package/dist/server/lib/executionEnvironmentValidation.js.map +1 -0
- package/dist/server/lib/executionEnvironmentValidationOperations.js +635 -0
- package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -0
- package/dist/server/lib/executionEnvironments.js +726 -0
- package/dist/server/lib/executionEnvironments.js.map +1 -0
- package/dist/server/lib/executionReconciliation.js +77 -0
- package/dist/server/lib/executionReconciliation.js.map +1 -1
- package/dist/server/lib/executionRetry.js +3 -0
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRuns.js +1 -0
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTargets.js +107 -0
- package/dist/server/lib/executionTargets.js.map +1 -0
- package/dist/server/lib/executionTimeline.js +3 -0
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/git.js +32 -1
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/hostExecutionEnvironment.js +47 -1
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/lightweightPrompt.js +1 -0
- package/dist/server/lib/lightweightPrompt.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +2 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/models.js +11 -0
- package/dist/server/lib/models.js.map +1 -1
- package/dist/server/lib/opencodeErrors.js +31 -3
- package/dist/server/lib/opencodeErrors.js.map +1 -1
- package/dist/server/lib/opencodeExecutionReliability.js +52 -0
- package/dist/server/lib/opencodeExecutionReliability.js.map +1 -0
- package/dist/server/lib/opencodeSessionOperations.js +210 -20
- package/dist/server/lib/opencodeSessionOperations.js.map +1 -1
- package/dist/server/lib/opencodeTaskSessions.js +7 -12
- package/dist/server/lib/opencodeTaskSessions.js.map +1 -1
- package/dist/server/lib/postBuildChecks.js +2 -1
- package/dist/server/lib/postBuildChecks.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +1 -0
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projects.js +10 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/specArtifact.js +20 -1
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specBundleGroups.js +151 -0
- package/dist/server/lib/specBundleGroups.js.map +1 -0
- package/dist/server/lib/specDraft.js +1 -5
- package/dist/server/lib/specDraft.js.map +1 -1
- package/dist/server/lib/specExport.js +2 -6
- package/dist/server/lib/specExport.js.map +1 -1
- package/dist/server/lib/specFormatting.js +13 -0
- package/dist/server/lib/specFormatting.js.map +1 -1
- package/dist/server/lib/specGraphFeature.js +3 -0
- package/dist/server/lib/specGraphFeature.js.map +1 -0
- package/dist/server/lib/specRepositoryEvidenceFormatting.js +2 -1
- package/dist/server/lib/specRepositoryEvidenceFormatting.js.map +1 -1
- package/dist/server/lib/specRepositoryImpact.js +4 -0
- package/dist/server/lib/specRepositoryImpact.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +6 -20
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/specReviewValidation.js +22 -1
- package/dist/server/lib/specReviewValidation.js.map +1 -1
- package/dist/server/lib/specReviews.js +1 -1
- package/dist/server/lib/specSections.js +8 -1
- package/dist/server/lib/specSections.js.map +1 -1
- package/dist/server/lib/specSnapshotEvidence.js +4 -0
- package/dist/server/lib/specSnapshotEvidence.js.map +1 -1
- package/dist/server/lib/specTicketMaterialization.js +122 -0
- package/dist/server/lib/specTicketMaterialization.js.map +1 -0
- package/dist/server/lib/specs.js +27 -5
- package/dist/server/lib/specs.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +8 -1
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +0 -3
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/types.js +1 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/lib/workspaceIdentity.js +3 -0
- package/dist/server/lib/workspaceIdentity.js.map +1 -1
- package/dist/server/workers/build.js +114 -8
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +143 -7
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +57 -24
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +43 -23
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +7 -2
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +109 -153
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-DYDYyqIN.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-jhi4GOyV.js +0 -1
- package/dist/client/assets/RepositoryGraphDialog-BZV0vHu6.js +0 -1
- package/dist/client/assets/VerificationDialog-Sc6e8AKH.js +0 -4
- package/dist/client/assets/architecture-7EHR7CIX-Dm8H7N6Z.js +0 -1
- package/dist/client/assets/channel-D1kOpezG.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-CtbsU1Bj.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-DOOw2vq7.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C253nwny.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DFgDA23g.js +0 -1
- package/dist/client/assets/eventmodeling-FCH6USID-BWhw0UJ8.js +0 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-BSnxAa1J.js +0 -1
- package/dist/client/assets/index-B1gdJKwK.js +0 -275
- package/dist/client/assets/index-CCQwWUKF.css +0 -2
- package/dist/client/assets/info-J43DQDTF-DcFwF1-h.js +0 -1
- package/dist/client/assets/packet-YPE3B663-1VZiO_9r.js +0 -1
- package/dist/client/assets/pie-LRSECV5Y-BX1-tQCu.js +0 -1
- package/dist/client/assets/radar-GUYGQ44K-B2HRrCR4.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BKQG8NCn.js +0 -1
- package/dist/client/assets/treeView-BLDUP644-CDfUVbUi.js +0 -1
- package/dist/client/assets/treemap-LRROVOQU-DUWqu8sK.js +0 -1
- package/dist/client/assets/wardley-L42UT6IY-BEFuPUCy.js +0 -1
- /package/dist/client/assets/{array-B2f2amhX.js → array-BNor45A1.js} +0 -0
- /package/dist/client/assets/{chunk-NNHCCRGN-DgKrp8T-.js → chunk-NNHCCRGN-CZB-V-Gv.js} +0 -0
- /package/dist/client/assets/{cytoscape.esm-BInZCgaJ.js → cytoscape.esm-B2lJcMSI.js} +0 -0
- /package/dist/client/assets/{defaultLocale-sPXJ5urm.js → defaultLocale-DPzUsThw.js} +0 -0
- /package/dist/client/assets/{dist-DfHDMDTt.js → dist-CERhmQhR.js} +0 -0
- /package/dist/client/assets/{graphlib-DacNv4Jd.js → graphlib-BHOW-eWz.js} +0 -0
- /package/dist/client/assets/{init-BVCnG1lj.js → init-C0L3woqb.js} +0 -0
- /package/dist/client/assets/{katex-BlkrXOwm.js → katex-CpjQsUQe.js} +0 -0
- /package/dist/client/assets/{path-Bw5AvBNC.js → path-sMK4d_s9.js} +0 -0
- /package/dist/client/assets/{rough.esm-DeLgKbOI.js → rough.esm-BoTisKeL.js} +0 -0
- /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
|