@straiffi/archon 1.8.15 → 1.8.17
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-XT8l-IRo.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-C1nmgELc.js +1 -0
- package/dist/client/assets/VerificationDialog-Cg5XNaPd.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-Dx7MJofM.js → architectureDiagram-3BPJPVTR-YDD_HNbm.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CSSJR8F_.js → blockDiagram-GPEHLZMM-BCgBIlJR.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C4F9969r.js → c4Diagram-AAUBKEIU-BLfk5bKl.js} +1 -1
- package/dist/client/assets/channel-3_WpUZue.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-23HW6i-y.js → chunk-2J33WTMH-DvwqUfn2.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-C3ZmRDMb.js → chunk-3OPIFGDE-D9R3JLGz.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-B_-UQvCg.js → chunk-5ZQYHXKU-CZeWOab2.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-5Dua6u1B.js → chunk-727SXJPM-YdTt6zCi.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DcpcaFRO.js → chunk-AQP2D5EJ-BFHbP_eb.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-CLCpOw52.js → chunk-CSCIHK7Q-C0BPXL2C.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-uOZFe2JI.js → chunk-KSCS5N6A-DqC4tO6g.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DNnzSNoX.js → chunk-L5ZTLDWV-BERUWsJ6.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-CSA3Xnoh.js → chunk-LZXEDZCA-BUHwhuWm.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-BQR5rD7w.js → chunk-ND2GUHAM-DCfmNJ41.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-DyIwcQYQ.js → chunk-NZK2D7GU-CHOkPepJ.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DfW60w5V.js → chunk-O5CBEL6O-ByMuL2EO.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-BGsB3vTj.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-CvnAuXH2.js → chunk-XPW4576I-DObFPxjB.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK--L8rxPgF.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-uW5z5Qib.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-Ba7Pz-ZI.js → dagre-BM42HDAG-CtmlVYvf.js} +1 -1
- package/dist/client/assets/{dagre-DSrzm-Gq.js → dagre-BWNHuDdf.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BxYfgpZ6.js → diagram-2AECGRRQ-DmQ2-tFR.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Bc4-O-UJ.js → diagram-5GNKFQAL-l49VXoBJ.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-Ds1Lmrzm.js → diagram-KO2AKTUF-0-U37RyJ.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-VFQabxKS.js → diagram-LMA3HP47-DupuMWgW.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-Baff59Zc.js → diagram-OG6HWLK6-D5DoIHW2.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Da5zHjOK.js → erDiagram-TEJ5UH35-DO-3-WYF.js} +1 -1
- package/dist/client/assets/eventmodeling-FCH6USID-Dqb6QKoI.js +1 -0
- package/dist/client/assets/{flowDiagram-I6XJVG4X-pdGMAuog.js → flowDiagram-I6XJVG4X-CG1viGSO.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DjJGX0nm.js → ganttDiagram-6RSMTGT7-CV-6ALMZ.js} +1 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-DYEsqh93.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-kErlwtcT.js → gitGraphDiagram-PVQCEYII-CHnyP-d3.js} +1 -1
- package/dist/client/assets/index-CsZfTdMO.css +2 -0
- package/dist/client/assets/index-DQ7z6ZWk.js +275 -0
- package/dist/client/assets/info-J43DQDTF-CfjXFZai.js +1 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-Bx78HhDi.js → infoDiagram-5YYISTIA-iKC-l4xs.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BP0YQCMl.js → ishikawaDiagram-YF4QCWOH-zi-y6jRI.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DUgrCOuD.js → journeyDiagram-JHISSGLW-Da4wMeC4.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-K0Zc5_M8.js → kanban-definition-UN3LZRKU-CZQ1yL5y.js} +1 -1
- package/dist/client/assets/{line-Dtg-krjk.js → line-B-FS1zIN.js} +1 -1
- package/dist/client/assets/{linear-BOwW57yX.js → linear-DT4rSZvy.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-Dw5BI77W.js → mermaid-parser.core-DPXl3axs.js} +2 -2
- package/dist/client/assets/{mermaid.core-BE-qlWNj.js → mermaid.core-DjqrtE_A.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-1Piz4H9I.js → mindmap-definition-RKZ34NQL-Day2-dLJ.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-zlWdJ7lL.js → pieDiagram-4H26LBE5-BmZiJzvi.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DRQY8Adp.js → quadrantDiagram-W4KKPZXB-DJgAcycV.js} +1 -1
- package/dist/client/assets/radar-GUYGQ44K-B7psLv22.js +1 -0
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-B8_GJFxl.js → requirementDiagram-4Y6WPE33-y94ek5ya.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BaI0Njo8.js → sankeyDiagram-5OEKKPKP-ChuulS6b.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DjqAXbRF.js → sequenceDiagram-3UESZ5HK-C7eiUu5p.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Ovr_3LeO.js → stateDiagram-AJRCARHV-Dy4QkKUV.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-HdcReOP7.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DkczYKeZ.js → timeline-definition-PNZ67QCA-CTtZLJWJ.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-BZyo4ESi.js → vennDiagram-CIIHVFJN-C5matxMS.js} +1 -1
- package/dist/client/assets/wardley-L42UT6IY-B5-Vb08V.js +1 -0
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BEISrECX.js → wardleyDiagram-YWT4CUSO-m_iOvfzY.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CyNzGK7f.js → xychartDiagram-2RQKCTM6-BmOxTt9K.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +235 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +200 -45
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +139 -90
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +1 -6
- package/dist/server/lib/agentProvider.js.map +1 -1
- package/dist/server/lib/buildExecutionEnvironment.js +535 -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/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 +725 -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 +375 -0
- package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -0
- package/dist/server/lib/executionEnvironmentValidation.js +540 -0
- package/dist/server/lib/executionEnvironmentValidation.js.map +1 -0
- package/dist/server/lib/executionEnvironmentValidationOperations.js +636 -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/hostExecutionEnvironment.js +47 -1
- package/dist/server/lib/hostExecutionEnvironment.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/opencodeSessionOperations.js +189 -9
- 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/projects.js +10 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/specArtifact.js +1 -1
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specDraft.js +1 -5
- package/dist/server/lib/specDraft.js.map +1 -1
- package/dist/server/lib/specExport.js +1 -6
- package/dist/server/lib/specExport.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/specSnapshotEvidence.js +4 -0
- package/dist/server/lib/specSnapshotEvidence.js.map +1 -1
- package/dist/server/lib/specTicketMaterialization.js +4 -1
- package/dist/server/lib/specTicketMaterialization.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 +115 -8
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +119 -3
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +34 -22
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +14 -13
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +0 -1
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +46 -122
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-DpY9GZez.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-BCr-hjvp.js +0 -1
- package/dist/client/assets/RepositoryGraphDialog-_hm-x1Dx.js +0 -1
- package/dist/client/assets/VerificationDialog-CsjDzpE1.js +0 -4
- package/dist/client/assets/architecture-7EHR7CIX-Dm8H7N6Z.js +0 -1
- package/dist/client/assets/channel-DVkhJzgx.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-CtbsU1Bj.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-CzMMPYdi.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-6oZ93s5p.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DYO_YnkU.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-CS4TzG41.css +0 -2
- package/dist/client/assets/index-D5TEfELZ.js +0 -275
- 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-9pmeAorV.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,726 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import db from '../db.js';
|
|
3
|
+
import { canonicalizeEnvironmentValidationFingerprintInput, createEnvironmentValidationFingerprint, } from './executionEnvironmentLifecycle.js';
|
|
4
|
+
const MAX_JSON_BYTES = 16 * 1024;
|
|
5
|
+
const MAX_EVENT_MESSAGE_LENGTH = 4_000;
|
|
6
|
+
const REUSABLE_STATES = ['ready', 'running', 'stopped'];
|
|
7
|
+
const CLEANUP_CLAIMABLE_STATES = ['retiring', 'cleanup_pending', 'failed', 'unavailable'];
|
|
8
|
+
const ALLOWED_TRANSITIONS = {
|
|
9
|
+
queued: new Set(['preparing', 'retiring', 'failed', 'unavailable']),
|
|
10
|
+
preparing: new Set(['ready', 'stopped', 'retiring', 'cleanup_pending', 'failed', 'unavailable']),
|
|
11
|
+
ready: new Set(['running', 'stopped', 'retiring', 'failed', 'unavailable']),
|
|
12
|
+
running: new Set(['ready', 'stopped', 'retiring', 'failed', 'unavailable']),
|
|
13
|
+
stopped: new Set(['preparing', 'ready', 'running', 'retiring', 'failed', 'unavailable']),
|
|
14
|
+
retiring: new Set(['cleanup_pending', 'retired', 'failed', 'unavailable']),
|
|
15
|
+
cleanup_pending: new Set(['retired', 'failed', 'unavailable']),
|
|
16
|
+
retired: new Set(),
|
|
17
|
+
failed: new Set(['preparing', 'retiring', 'cleanup_pending']),
|
|
18
|
+
unavailable: new Set(['preparing', 'retiring', 'cleanup_pending']),
|
|
19
|
+
};
|
|
20
|
+
const getEnvironmentStatement = db.prepare(`
|
|
21
|
+
SELECT * FROM execution_environments WHERE id = ? AND project_id = ?
|
|
22
|
+
`);
|
|
23
|
+
const projectExistsStatement = db.prepare(`
|
|
24
|
+
SELECT 1 FROM projects WHERE id = ? AND deletion_started_at IS NULL
|
|
25
|
+
`);
|
|
26
|
+
const insertEnvironmentStatement = db.prepare(`
|
|
27
|
+
INSERT INTO execution_environments (
|
|
28
|
+
id, project_id, provider_kind, provider_version, external_id, target_kind, target_key,
|
|
29
|
+
target_workspace_path, environment_revision, workspace_mode,
|
|
30
|
+
environment_validation_fingerprint, template_identity, template_digest, agent_identity,
|
|
31
|
+
compatible_agents_json, capabilities_json, lifecycle_state, created_at, updated_at
|
|
32
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'direct', ?, ?, ?, ?, ?, ?, 'queued', ?, ?)
|
|
33
|
+
`);
|
|
34
|
+
const updateEnvironmentStatement = db.prepare(`
|
|
35
|
+
UPDATE execution_environments
|
|
36
|
+
SET lifecycle_state = ?, external_id = ?, capabilities_json = ?, failure_code = ?,
|
|
37
|
+
failure_message = ?, failure_details_json = ?, preparation_started_at = ?, ready_at = ?,
|
|
38
|
+
last_validated_at = ?, last_used_at = ?, stopped_at = ?, retirement_started_at = ?,
|
|
39
|
+
cleanup_pending_at = ?, retired_at = ?, failed_at = ?, unavailable_at = ?, updated_at = ?
|
|
40
|
+
WHERE id = ? AND project_id = ? AND lifecycle_state = ?
|
|
41
|
+
`);
|
|
42
|
+
const insertEventStatement = db.prepare(`
|
|
43
|
+
INSERT INTO execution_environment_events (
|
|
44
|
+
id, project_id, environment_id, sequence, event_type, from_state, to_state, message,
|
|
45
|
+
metadata_schema_version, metadata_json, created_at
|
|
46
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
47
|
+
`);
|
|
48
|
+
const nextEventSequenceStatement = db.prepare(`
|
|
49
|
+
SELECT COALESCE(MAX(sequence), 0) + 1 AS sequence
|
|
50
|
+
FROM execution_environment_events WHERE environment_id = ?
|
|
51
|
+
`);
|
|
52
|
+
const getEventStatement = db.prepare(`
|
|
53
|
+
SELECT * FROM execution_environment_events WHERE id = ? AND project_id = ?
|
|
54
|
+
`);
|
|
55
|
+
const insertBindingStatement = db.prepare(`
|
|
56
|
+
INSERT INTO execution_environment_run_bindings (
|
|
57
|
+
environment_id, execution_run_id, project_id, relationship, environment_revision,
|
|
58
|
+
environment_validation_fingerprint, authority_json, bound_at
|
|
59
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
60
|
+
`);
|
|
61
|
+
const getBindingStatement = db.prepare(`
|
|
62
|
+
SELECT * FROM execution_environment_run_bindings
|
|
63
|
+
WHERE environment_id = ? AND execution_run_id = ? AND relationship = ?
|
|
64
|
+
`);
|
|
65
|
+
const releaseBindingStatement = db.prepare(`
|
|
66
|
+
UPDATE execution_environment_run_bindings
|
|
67
|
+
SET released_at = ?
|
|
68
|
+
WHERE environment_id = ? AND execution_run_id = ? AND project_id = ?
|
|
69
|
+
AND relationship = ? AND released_at IS NULL
|
|
70
|
+
`);
|
|
71
|
+
const capturePlacementStatement = db.prepare(`
|
|
72
|
+
UPDATE execution_environment_run_bindings
|
|
73
|
+
SET placement_json = ?
|
|
74
|
+
WHERE environment_id = ? AND execution_run_id = ? AND project_id = ?
|
|
75
|
+
AND relationship = 'execution' AND released_at IS NULL AND placement_json IS NULL
|
|
76
|
+
`);
|
|
77
|
+
const assertBoundedString = (value, name, maxBytes) => {
|
|
78
|
+
const bytes = Buffer.byteLength(value, 'utf8');
|
|
79
|
+
if (bytes < 1 || bytes > maxBytes) {
|
|
80
|
+
throw new Error(`${name} must contain between 1 and ${maxBytes} bytes`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const serializeBoundedObject = (value, name) => {
|
|
84
|
+
let serialized;
|
|
85
|
+
try {
|
|
86
|
+
serialized = JSON.stringify(value);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw new Error(`${name} must be JSON serializable`);
|
|
90
|
+
}
|
|
91
|
+
if (Buffer.byteLength(serialized, 'utf8') > MAX_JSON_BYTES) {
|
|
92
|
+
throw new Error(`${name} exceeds ${MAX_JSON_BYTES} bytes`);
|
|
93
|
+
}
|
|
94
|
+
return serialized;
|
|
95
|
+
};
|
|
96
|
+
const parseStoredObject = (value) => {
|
|
97
|
+
if (value === null || Buffer.byteLength(value, 'utf8') > MAX_JSON_BYTES) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(value);
|
|
102
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
103
|
+
? parsed
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const createDefaultAuthority = (environment) => {
|
|
111
|
+
if (environment.provider_kind !== 'docker_sandbox' || !environment.agent_identity) {
|
|
112
|
+
throw new Error('Execution bindings require an explicit Docker Sandbox authority snapshot');
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
schema_version: 1,
|
|
116
|
+
provider: 'docker_sandbox',
|
|
117
|
+
provider_version: environment.provider_version,
|
|
118
|
+
environment_id: environment.id,
|
|
119
|
+
environment_revision: environment.environment_revision,
|
|
120
|
+
environment_validation_fingerprint: environment.environment_validation_fingerprint,
|
|
121
|
+
target: {
|
|
122
|
+
kind: environment.target_kind,
|
|
123
|
+
key: environment.target_key,
|
|
124
|
+
workspace_path: environment.target_workspace_path,
|
|
125
|
+
},
|
|
126
|
+
capabilities: environment.capabilities,
|
|
127
|
+
degradation_reasons: environment.workspace_mode === 'direct' ? ['direct_workspace_writable'] : [],
|
|
128
|
+
agent: environment.agent_identity,
|
|
129
|
+
authentication_mode: 'validated_guest',
|
|
130
|
+
credential_delivery: 'sandbox_template',
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const assertAuthorityMatchesEnvironment = (authority, environment) => {
|
|
134
|
+
if (authority.schema_version !== 1
|
|
135
|
+
|| authority.provider !== 'docker_sandbox'
|
|
136
|
+
|| authority.provider_version !== environment.provider_version
|
|
137
|
+
|| authority.environment_id !== environment.id
|
|
138
|
+
|| authority.environment_revision !== environment.environment_revision
|
|
139
|
+
|| authority.environment_validation_fingerprint !== environment.environment_validation_fingerprint
|
|
140
|
+
|| authority.target.kind !== environment.target_kind
|
|
141
|
+
|| authority.target.key !== environment.target_key
|
|
142
|
+
|| authority.target.workspace_path !== environment.target_workspace_path
|
|
143
|
+
|| authority.agent !== environment.agent_identity
|
|
144
|
+
|| authority.authentication_mode !== 'validated_guest'
|
|
145
|
+
|| authority.credential_delivery !== 'sandbox_template') {
|
|
146
|
+
throw new Error('Execution environment authority does not match the durable environment identity');
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const parseCompatibleAgents = (value) => {
|
|
150
|
+
if (Buffer.byteLength(value, 'utf8') > 100) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(value);
|
|
155
|
+
if (!Array.isArray(parsed)
|
|
156
|
+
|| parsed.length === 0
|
|
157
|
+
|| parsed.some(agent => agent !== 'opencode' && agent !== 'claude')
|
|
158
|
+
|| new Set(parsed).size !== parsed.length
|
|
159
|
+
|| [...parsed].sort().some((agent, index) => agent !== parsed[index])) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const serializeEnvironment = (row) => {
|
|
169
|
+
const compatibleAgents = parseCompatibleAgents(row.compatible_agents_json);
|
|
170
|
+
const capabilities = parseStoredObject(row.capabilities_json);
|
|
171
|
+
if (compatibleAgents === null
|
|
172
|
+
|| capabilities === null
|
|
173
|
+
|| !/^[0-9a-f]{64}$/.test(row.environment_validation_fingerprint)) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
...row,
|
|
178
|
+
compatible_agents: compatibleAgents,
|
|
179
|
+
capabilities,
|
|
180
|
+
failure_details: parseStoredObject(row.failure_details_json),
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
const serializeEvent = (row) => ({
|
|
184
|
+
...row,
|
|
185
|
+
metadata: parseStoredObject(row.metadata_json),
|
|
186
|
+
});
|
|
187
|
+
const requireEnvironment = (projectId, environmentId) => {
|
|
188
|
+
const row = getEnvironmentStatement.get(environmentId, projectId);
|
|
189
|
+
if (!row) {
|
|
190
|
+
throw new Error(`Execution environment ${environmentId} was not found in project ${projectId}`);
|
|
191
|
+
}
|
|
192
|
+
const environment = serializeEnvironment(row);
|
|
193
|
+
if (!environment) {
|
|
194
|
+
throw new Error(`Execution environment ${environmentId} contains invalid durable identity data`);
|
|
195
|
+
}
|
|
196
|
+
return environment;
|
|
197
|
+
};
|
|
198
|
+
const appendEventInTransaction = (input) => {
|
|
199
|
+
assertBoundedString(input.eventType, 'Environment event type', 100);
|
|
200
|
+
const metadataSchemaVersion = input.metadataSchemaVersion ?? 1;
|
|
201
|
+
if (!Number.isSafeInteger(metadataSchemaVersion) || metadataSchemaVersion < 1) {
|
|
202
|
+
throw new Error('Environment event metadata schema version must be a positive integer');
|
|
203
|
+
}
|
|
204
|
+
const message = input.message ?? null;
|
|
205
|
+
if (message && message.length > MAX_EVENT_MESSAGE_LENGTH) {
|
|
206
|
+
throw new Error(`Environment event message exceeds ${MAX_EVENT_MESSAGE_LENGTH} characters`);
|
|
207
|
+
}
|
|
208
|
+
const id = randomUUID();
|
|
209
|
+
const sequence = nextEventSequenceStatement.get(input.environmentId).sequence;
|
|
210
|
+
insertEventStatement.run(id, input.projectId, input.environmentId, sequence, input.eventType, input.fromState, input.toState, message, metadataSchemaVersion, input.metadata ? serializeBoundedObject(input.metadata, 'Environment event metadata') : null, input.createdAt ?? new Date().toISOString());
|
|
211
|
+
return serializeEvent(getEventStatement.get(id, input.projectId));
|
|
212
|
+
};
|
|
213
|
+
const createEnvironmentInTransaction = (input) => {
|
|
214
|
+
if (!projectExistsStatement.get(input.projectId)) {
|
|
215
|
+
throw new Error(`Project ${input.projectId} was not found`);
|
|
216
|
+
}
|
|
217
|
+
const id = input.id ?? randomUUID();
|
|
218
|
+
assertBoundedString(id, 'Environment ID', 128);
|
|
219
|
+
assertBoundedString(input.providerKind, 'Provider kind', 100);
|
|
220
|
+
assertBoundedString(input.providerVersion, 'Provider version', 200);
|
|
221
|
+
assertBoundedString(input.target.kind, 'Target kind', 100);
|
|
222
|
+
assertBoundedString(input.target.key, 'Target key', 1_000);
|
|
223
|
+
assertBoundedString(input.target.workspacePath, 'Target workspace path', 4_096);
|
|
224
|
+
if (!Number.isSafeInteger(input.environmentRevision) || input.environmentRevision < 1) {
|
|
225
|
+
throw new Error('Environment revision must be a positive integer');
|
|
226
|
+
}
|
|
227
|
+
if (input.externalId) {
|
|
228
|
+
assertBoundedString(input.externalId, 'Provider external ID', 2_048);
|
|
229
|
+
}
|
|
230
|
+
const validation = canonicalizeEnvironmentValidationFingerprintInput(input.validation);
|
|
231
|
+
if (input.agentIdentity && !validation.compatibleAgents.includes(input.agentIdentity)) {
|
|
232
|
+
throw new Error('Agent identity must be included in the compatible agent set');
|
|
233
|
+
}
|
|
234
|
+
const capabilitiesJson = serializeBoundedObject(input.capabilities, 'Environment capabilities');
|
|
235
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
236
|
+
insertEnvironmentStatement.run(id, input.projectId, input.providerKind, input.providerVersion, input.externalId ?? null, input.target.kind, input.target.key, input.target.workspacePath, input.environmentRevision, createEnvironmentValidationFingerprint(validation), validation.template.kind === 'identity' ? validation.template.value : null, validation.template.kind === 'digest' ? validation.template.value : null, input.agentIdentity ?? null, JSON.stringify(validation.compatibleAgents), capabilitiesJson, createdAt, createdAt);
|
|
237
|
+
const environment = requireEnvironment(input.projectId, id);
|
|
238
|
+
const event = appendEventInTransaction({
|
|
239
|
+
projectId: input.projectId,
|
|
240
|
+
environmentId: id,
|
|
241
|
+
eventType: 'environment_created',
|
|
242
|
+
fromState: null,
|
|
243
|
+
toState: 'queued',
|
|
244
|
+
createdAt,
|
|
245
|
+
});
|
|
246
|
+
return { environment, event };
|
|
247
|
+
};
|
|
248
|
+
const stateTimestampUpdates = (environment, state, occurredAt) => ({
|
|
249
|
+
preparationStartedAt: state === 'preparing' ? occurredAt : environment.preparation_started_at,
|
|
250
|
+
readyAt: state === 'ready' ? occurredAt : environment.ready_at,
|
|
251
|
+
lastValidatedAt: environment.last_validated_at,
|
|
252
|
+
lastUsedAt: state === 'running' ? occurredAt : environment.last_used_at,
|
|
253
|
+
stoppedAt: state === 'stopped' ? occurredAt : environment.stopped_at,
|
|
254
|
+
retirementStartedAt: state === 'retiring' ? occurredAt : environment.retirement_started_at,
|
|
255
|
+
cleanupPendingAt: state === 'cleanup_pending' ? occurredAt : environment.cleanup_pending_at,
|
|
256
|
+
retiredAt: state === 'retired' ? occurredAt : environment.retired_at,
|
|
257
|
+
failedAt: state === 'failed' ? occurredAt : environment.failed_at,
|
|
258
|
+
unavailableAt: state === 'unavailable' ? occurredAt : environment.unavailable_at,
|
|
259
|
+
});
|
|
260
|
+
const transitionEnvironmentInTransaction = (input) => {
|
|
261
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
262
|
+
if (environment.lifecycle_state === input.state) {
|
|
263
|
+
return { environment, event: null };
|
|
264
|
+
}
|
|
265
|
+
if (!ALLOWED_TRANSITIONS[environment.lifecycle_state].has(input.state)) {
|
|
266
|
+
throw new Error(`Invalid execution environment transition: ${environment.lifecycle_state} -> ${input.state}`);
|
|
267
|
+
}
|
|
268
|
+
if (environment.external_id && input.externalId !== undefined && input.externalId !== environment.external_id) {
|
|
269
|
+
throw new Error('Provider external ID cannot be replaced');
|
|
270
|
+
}
|
|
271
|
+
if (input.failure && input.state !== 'failed' && input.state !== 'unavailable' && input.state !== 'cleanup_pending') {
|
|
272
|
+
throw new Error('Environment failure evidence requires a failed, unavailable, or cleanup-pending state');
|
|
273
|
+
}
|
|
274
|
+
const failure = input.failure ?? null;
|
|
275
|
+
if (failure) {
|
|
276
|
+
assertBoundedString(failure.code, 'Environment failure code', 100);
|
|
277
|
+
if (failure.message.length > MAX_EVENT_MESSAGE_LENGTH) {
|
|
278
|
+
throw new Error(`Environment failure message exceeds ${MAX_EVENT_MESSAGE_LENGTH} characters`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const occurredAt = input.occurredAt ?? new Date().toISOString();
|
|
282
|
+
const timestamps = stateTimestampUpdates(environment, input.state, occurredAt);
|
|
283
|
+
const capabilitiesJson = input.capabilities
|
|
284
|
+
? serializeBoundedObject(input.capabilities, 'Environment capabilities')
|
|
285
|
+
: JSON.stringify(environment.capabilities);
|
|
286
|
+
const result = updateEnvironmentStatement.run(input.state, input.externalId === undefined ? environment.external_id : input.externalId, capabilitiesJson, failure?.code ?? null, failure?.message ?? null, failure?.details ? serializeBoundedObject(failure.details, 'Environment failure details') : null, timestamps.preparationStartedAt, timestamps.readyAt, timestamps.lastValidatedAt, timestamps.lastUsedAt, timestamps.stoppedAt, timestamps.retirementStartedAt, timestamps.cleanupPendingAt, timestamps.retiredAt, timestamps.failedAt, timestamps.unavailableAt, occurredAt, input.environmentId, input.projectId, environment.lifecycle_state);
|
|
287
|
+
if (result.changes !== 1) {
|
|
288
|
+
throw new Error(`Execution environment ${input.environmentId} changed concurrently`);
|
|
289
|
+
}
|
|
290
|
+
const updated = requireEnvironment(input.projectId, input.environmentId);
|
|
291
|
+
const event = appendEventInTransaction({
|
|
292
|
+
projectId: input.projectId,
|
|
293
|
+
environmentId: input.environmentId,
|
|
294
|
+
eventType: input.eventType ?? `environment_${input.state}`,
|
|
295
|
+
fromState: environment.lifecycle_state,
|
|
296
|
+
toState: input.state,
|
|
297
|
+
message: input.message ?? failure?.message,
|
|
298
|
+
metadata: input.metadata,
|
|
299
|
+
metadataSchemaVersion: input.metadataSchemaVersion,
|
|
300
|
+
createdAt: occurredAt,
|
|
301
|
+
});
|
|
302
|
+
return { environment: updated, event };
|
|
303
|
+
};
|
|
304
|
+
const bindRunInTransaction = (input) => {
|
|
305
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
306
|
+
const authority = input.relationship === 'execution'
|
|
307
|
+
? input.authority ?? createDefaultAuthority(environment)
|
|
308
|
+
: null;
|
|
309
|
+
if (authority) {
|
|
310
|
+
assertAuthorityMatchesEnvironment(authority, environment);
|
|
311
|
+
}
|
|
312
|
+
const boundAt = input.boundAt ?? new Date().toISOString();
|
|
313
|
+
insertBindingStatement.run(input.environmentId, input.executionRunId, input.projectId, input.relationship, environment.environment_revision, environment.environment_validation_fingerprint, authority ? serializeBoundedObject(authority, 'Execution environment authority') : null, boundAt);
|
|
314
|
+
const binding = getBindingStatement.get(input.environmentId, input.executionRunId, input.relationship);
|
|
315
|
+
appendEventInTransaction({
|
|
316
|
+
projectId: input.projectId,
|
|
317
|
+
environmentId: input.environmentId,
|
|
318
|
+
eventType: 'execution_run_bound',
|
|
319
|
+
fromState: environment.lifecycle_state,
|
|
320
|
+
toState: environment.lifecycle_state,
|
|
321
|
+
metadata: { executionRunId: input.executionRunId, relationship: input.relationship },
|
|
322
|
+
createdAt: boundAt,
|
|
323
|
+
});
|
|
324
|
+
return binding;
|
|
325
|
+
};
|
|
326
|
+
const releaseRunInTransaction = (input) => {
|
|
327
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
328
|
+
const releasedAt = input.releasedAt ?? new Date().toISOString();
|
|
329
|
+
const result = releaseBindingStatement.run(releasedAt, input.environmentId, input.executionRunId, input.projectId, input.relationship);
|
|
330
|
+
if (result.changes !== 1) {
|
|
331
|
+
throw new Error('Execution environment run binding was not found or was already released');
|
|
332
|
+
}
|
|
333
|
+
const binding = getBindingStatement.get(input.environmentId, input.executionRunId, input.relationship);
|
|
334
|
+
appendEventInTransaction({
|
|
335
|
+
projectId: input.projectId,
|
|
336
|
+
environmentId: input.environmentId,
|
|
337
|
+
eventType: 'execution_run_released',
|
|
338
|
+
fromState: environment.lifecycle_state,
|
|
339
|
+
toState: environment.lifecycle_state,
|
|
340
|
+
metadata: { executionRunId: input.executionRunId, relationship: input.relationship },
|
|
341
|
+
createdAt: releasedAt,
|
|
342
|
+
});
|
|
343
|
+
return binding;
|
|
344
|
+
};
|
|
345
|
+
const isPromiseLike = (value) => {
|
|
346
|
+
if ((typeof value !== 'object' || value === null) && typeof value !== 'function') {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return typeof value.then === 'function';
|
|
350
|
+
};
|
|
351
|
+
export const withExecutionEnvironmentTransaction = (callback) => {
|
|
352
|
+
const transaction = db.transaction(() => {
|
|
353
|
+
const operations = {
|
|
354
|
+
createExecutionEnvironment: input => createEnvironmentInTransaction(input).environment,
|
|
355
|
+
transitionExecutionEnvironment: input => transitionEnvironmentInTransaction(input).environment,
|
|
356
|
+
bindExecutionRunToEnvironment: bindRunInTransaction,
|
|
357
|
+
releaseExecutionRunFromEnvironment: releaseRunInTransaction,
|
|
358
|
+
appendExecutionEnvironmentEvent: appendEventInTransaction,
|
|
359
|
+
};
|
|
360
|
+
const result = callback(operations);
|
|
361
|
+
if (isPromiseLike(result)) {
|
|
362
|
+
throw new Error('Execution environment transactions require a synchronous callback');
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
});
|
|
366
|
+
return transaction();
|
|
367
|
+
};
|
|
368
|
+
export const createExecutionEnvironment = (input) => (withExecutionEnvironmentTransaction(transaction => transaction.createExecutionEnvironment(input)));
|
|
369
|
+
export const transitionExecutionEnvironment = (input) => (withExecutionEnvironmentTransaction(transaction => transaction.transitionExecutionEnvironment(input)));
|
|
370
|
+
export const bindExecutionRunToEnvironment = (input) => (withExecutionEnvironmentTransaction(transaction => transaction.bindExecutionRunToEnvironment(input)));
|
|
371
|
+
export const acquireExecutionEnvironmentLease = (input) => {
|
|
372
|
+
try {
|
|
373
|
+
return db.transaction(() => {
|
|
374
|
+
const active = db.prepare(`
|
|
375
|
+
SELECT 1 FROM execution_environment_run_bindings
|
|
376
|
+
WHERE environment_id = ? AND released_at IS NULL
|
|
377
|
+
LIMIT 1
|
|
378
|
+
`).get(input.environmentId);
|
|
379
|
+
if (active) {
|
|
380
|
+
throw new Error(`Execution environment ${input.environmentId} is already leased`);
|
|
381
|
+
}
|
|
382
|
+
return bindRunInTransaction({
|
|
383
|
+
...input,
|
|
384
|
+
relationship: 'execution',
|
|
385
|
+
});
|
|
386
|
+
}).immediate();
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
if (error instanceof Error && error.message.includes('UNIQUE constraint failed')) {
|
|
390
|
+
throw new Error(`Execution environment ${input.environmentId} is already leased`);
|
|
391
|
+
}
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const placementMatchesBinding = (placement, binding) => (placement.provider === 'docker_sandbox'
|
|
396
|
+
&& placement.environmentId === binding.environment_id
|
|
397
|
+
&& placement.environmentRevision === binding.environment_revision
|
|
398
|
+
&& placement.executionRunId === binding.execution_run_id
|
|
399
|
+
&& placement.scope.kind === 'exact-sandbox');
|
|
400
|
+
export const captureExecutionEnvironmentPlacement = (input) => withExecutionEnvironmentTransaction(transaction => {
|
|
401
|
+
const binding = getBindingStatement.get(input.environmentId, input.executionRunId, 'execution');
|
|
402
|
+
if (!binding || binding.project_id !== input.projectId || binding.released_at !== null) {
|
|
403
|
+
throw new Error('Active execution environment lease was not found');
|
|
404
|
+
}
|
|
405
|
+
if (!placementMatchesBinding(input.placement, binding)) {
|
|
406
|
+
throw new Error('Execution placement does not match its durable lease');
|
|
407
|
+
}
|
|
408
|
+
if (binding.placement_json !== null) {
|
|
409
|
+
const existingPlacement = parseStoredObject(binding.placement_json);
|
|
410
|
+
if (existingPlacement && JSON.stringify(existingPlacement) === JSON.stringify(input.placement)) {
|
|
411
|
+
return binding;
|
|
412
|
+
}
|
|
413
|
+
throw new Error('Conflicting execution placement evidence was already captured');
|
|
414
|
+
}
|
|
415
|
+
const result = capturePlacementStatement.run(serializeBoundedObject(input.placement, 'Execution placement evidence'), input.environmentId, input.executionRunId, input.projectId);
|
|
416
|
+
if (result.changes !== 1) {
|
|
417
|
+
throw new Error('Execution placement evidence was already captured');
|
|
418
|
+
}
|
|
419
|
+
transaction.appendExecutionEnvironmentEvent({
|
|
420
|
+
projectId: input.projectId,
|
|
421
|
+
environmentId: input.environmentId,
|
|
422
|
+
eventType: 'execution_placement_captured',
|
|
423
|
+
fromState: requireEnvironment(input.projectId, input.environmentId).lifecycle_state,
|
|
424
|
+
toState: requireEnvironment(input.projectId, input.environmentId).lifecycle_state,
|
|
425
|
+
metadata: { executionRunId: input.executionRunId },
|
|
426
|
+
});
|
|
427
|
+
return getBindingStatement.get(input.environmentId, input.executionRunId, 'execution');
|
|
428
|
+
});
|
|
429
|
+
export const getExecutionEnvironmentRunBinding = (input) => getBindingStatement.get(input.environmentId, input.executionRunId, input.relationship) ?? null;
|
|
430
|
+
export const getExecutionEnvironmentBindingForRun = (projectId, executionRunId) => db.prepare(`
|
|
431
|
+
SELECT * FROM execution_environment_run_bindings
|
|
432
|
+
WHERE project_id = ? AND execution_run_id = ? AND relationship = 'execution'
|
|
433
|
+
ORDER BY bound_at DESC LIMIT 1
|
|
434
|
+
`).get(projectId, executionRunId) ?? null;
|
|
435
|
+
export const releaseExecutionEnvironmentLease = (input) => withExecutionEnvironmentTransaction(transaction => {
|
|
436
|
+
const binding = getExecutionEnvironmentRunBinding({
|
|
437
|
+
environmentId: input.environmentId,
|
|
438
|
+
executionRunId: input.executionRunId,
|
|
439
|
+
relationship: 'execution',
|
|
440
|
+
});
|
|
441
|
+
if (!binding || binding.project_id !== input.projectId || binding.released_at !== null) {
|
|
442
|
+
throw new Error('Active execution environment lease was not found');
|
|
443
|
+
}
|
|
444
|
+
if (input.proof.kind === 'terminal_local_execution') {
|
|
445
|
+
const run = db.prepare('SELECT status FROM execution_runs WHERE id = ? AND project_id = ?').get(input.executionRunId, input.projectId);
|
|
446
|
+
if (!run || ['queued', 'running', 'waiting'].includes(run.status)) {
|
|
447
|
+
throw new Error('Execution environment lease release requires a terminal execution run');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else if (input.proof.kind === 'placement_terminated') {
|
|
451
|
+
const placement = parseStoredObject(binding.placement_json);
|
|
452
|
+
if (!placement
|
|
453
|
+
|| !placementMatchesBinding(placement, binding)
|
|
454
|
+
|| input.proof.inspection.state === 'running'
|
|
455
|
+
|| JSON.stringify(input.proof.inspection.identity) !== JSON.stringify(placement)) {
|
|
456
|
+
throw new Error('Execution environment lease release requires exact terminated placement proof');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
461
|
+
if (!environment.external_id || environment.external_id !== input.proof.externalId) {
|
|
462
|
+
throw new Error('Execution environment lease release requires the exact stopped sandbox identity');
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (input.cleanupCompletion) {
|
|
466
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
467
|
+
const targetState = input.cleanupCompletion.state === 'stopped' ? 'stopped' : 'unavailable';
|
|
468
|
+
if (environment.lifecycle_state === targetState
|
|
469
|
+
|| ['failed', 'unavailable', 'retired'].includes(environment.lifecycle_state)) {
|
|
470
|
+
transaction.appendExecutionEnvironmentEvent({
|
|
471
|
+
projectId: input.projectId,
|
|
472
|
+
environmentId: input.environmentId,
|
|
473
|
+
eventType: input.cleanupCompletion.eventType,
|
|
474
|
+
fromState: environment.lifecycle_state,
|
|
475
|
+
toState: environment.lifecycle_state,
|
|
476
|
+
message: input.cleanupCompletion.message,
|
|
477
|
+
metadata: input.cleanupCompletion.metadata,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
transaction.transitionExecutionEnvironment({
|
|
482
|
+
projectId: input.projectId,
|
|
483
|
+
environmentId: input.environmentId,
|
|
484
|
+
state: targetState,
|
|
485
|
+
eventType: input.cleanupCompletion.eventType,
|
|
486
|
+
message: input.cleanupCompletion.message,
|
|
487
|
+
metadata: input.cleanupCompletion.metadata,
|
|
488
|
+
failure: input.cleanupCompletion.failure,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return releaseRunInTransaction({
|
|
493
|
+
projectId: input.projectId,
|
|
494
|
+
environmentId: input.environmentId,
|
|
495
|
+
executionRunId: input.executionRunId,
|
|
496
|
+
relationship: 'execution',
|
|
497
|
+
releasedAt: input.releasedAt,
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
export const releaseExecutionRunFromEnvironment = (input) => (withExecutionEnvironmentTransaction(transaction => transaction.releaseExecutionRunFromEnvironment(input)));
|
|
501
|
+
export const appendExecutionEnvironmentEvent = (input) => (withExecutionEnvironmentTransaction(transaction => transaction.appendExecutionEnvironmentEvent(input)));
|
|
502
|
+
export const retireExecutionEnvironment = (input) => transitionExecutionEnvironment({ ...input, state: 'retiring', eventType: input.eventType ?? 'environment_retirement_started' });
|
|
503
|
+
export const getExecutionEnvironment = (projectId, environmentId) => {
|
|
504
|
+
const row = getEnvironmentStatement.get(environmentId, projectId);
|
|
505
|
+
return row ? serializeEnvironment(row) : null;
|
|
506
|
+
};
|
|
507
|
+
export const listExecutionEnvironments = (projectId) => {
|
|
508
|
+
const rows = db.prepare(`
|
|
509
|
+
SELECT * FROM execution_environments
|
|
510
|
+
WHERE project_id = ?
|
|
511
|
+
ORDER BY environment_revision DESC, created_at DESC, id DESC
|
|
512
|
+
`).all(projectId);
|
|
513
|
+
return rows.flatMap(row => {
|
|
514
|
+
const environment = serializeEnvironment(row);
|
|
515
|
+
return environment ? [environment] : [];
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
export const listExecutionEnvironmentEvents = (projectId, environmentId) => (db.prepare(`
|
|
519
|
+
SELECT * FROM execution_environment_events
|
|
520
|
+
WHERE project_id = ? AND environment_id = ?
|
|
521
|
+
ORDER BY sequence ASC
|
|
522
|
+
`).all(projectId, environmentId).map(row => serializeEvent(row)));
|
|
523
|
+
export const findReusableExecutionEnvironment = (input) => {
|
|
524
|
+
const validation = canonicalizeEnvironmentValidationFingerprintInput(input.validation);
|
|
525
|
+
if (input.agentIdentity && !validation.compatibleAgents.includes(input.agentIdentity)) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
const rows = db.prepare(`
|
|
529
|
+
SELECT * FROM execution_environments
|
|
530
|
+
WHERE project_id = ?
|
|
531
|
+
AND provider_kind = ?
|
|
532
|
+
AND provider_version = ?
|
|
533
|
+
AND target_kind = ?
|
|
534
|
+
AND target_key = ?
|
|
535
|
+
AND target_workspace_path = ?
|
|
536
|
+
AND environment_revision = ?
|
|
537
|
+
AND workspace_mode = 'direct'
|
|
538
|
+
AND environment_validation_fingerprint = ?
|
|
539
|
+
AND template_identity IS ?
|
|
540
|
+
AND template_digest IS ?
|
|
541
|
+
AND compatible_agents_json = ?
|
|
542
|
+
AND (agent_identity IS NULL OR agent_identity IS ?)
|
|
543
|
+
AND lifecycle_state IN (${REUSABLE_STATES.map(() => '?').join(', ')})
|
|
544
|
+
ORDER BY last_validated_at DESC, created_at DESC, id DESC
|
|
545
|
+
`).all(input.projectId, input.providerKind, input.providerVersion, input.target.kind, input.target.key, input.target.workspacePath, input.environmentRevision, createEnvironmentValidationFingerprint(validation), validation.template.kind === 'identity' ? validation.template.value : null, validation.template.kind === 'digest' ? validation.template.value : null, JSON.stringify(validation.compatibleAgents), input.agentIdentity ?? null, ...REUSABLE_STATES);
|
|
546
|
+
for (const row of rows) {
|
|
547
|
+
const environment = serializeEnvironment(row);
|
|
548
|
+
if (environment) {
|
|
549
|
+
return environment;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
};
|
|
554
|
+
export const claimExecutionEnvironmentPreparation = (input) => {
|
|
555
|
+
const transaction = db.transaction(() => {
|
|
556
|
+
const validation = canonicalizeEnvironmentValidationFingerprintInput(input.validation);
|
|
557
|
+
if (input.agentIdentity && !validation.compatibleAgents.includes(input.agentIdentity)) {
|
|
558
|
+
throw new Error('Agent identity must be included in the compatible agent set');
|
|
559
|
+
}
|
|
560
|
+
const fingerprint = createEnvironmentValidationFingerprint(validation);
|
|
561
|
+
const identityValues = [
|
|
562
|
+
input.projectId,
|
|
563
|
+
input.providerKind,
|
|
564
|
+
input.providerVersion,
|
|
565
|
+
input.target.kind,
|
|
566
|
+
input.target.key,
|
|
567
|
+
input.target.workspacePath,
|
|
568
|
+
fingerprint,
|
|
569
|
+
validation.template.kind === 'identity' ? validation.template.value : null,
|
|
570
|
+
validation.template.kind === 'digest' ? validation.template.value : null,
|
|
571
|
+
JSON.stringify(validation.compatibleAgents),
|
|
572
|
+
input.agentIdentity ?? null,
|
|
573
|
+
];
|
|
574
|
+
const exactRows = db.prepare(`
|
|
575
|
+
SELECT * FROM execution_environments
|
|
576
|
+
WHERE project_id = ? AND provider_kind = ? AND provider_version = ?
|
|
577
|
+
AND target_kind = ? AND target_key = ? AND target_workspace_path = ?
|
|
578
|
+
AND workspace_mode = 'direct' AND environment_validation_fingerprint = ?
|
|
579
|
+
AND template_identity IS ? AND template_digest IS ?
|
|
580
|
+
AND compatible_agents_json = ? AND agent_identity IS ?
|
|
581
|
+
ORDER BY environment_revision DESC, created_at DESC, id DESC
|
|
582
|
+
`).all(...identityValues);
|
|
583
|
+
for (const row of exactRows) {
|
|
584
|
+
const environment = serializeEnvironment(row);
|
|
585
|
+
if (!environment) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (REUSABLE_STATES.includes(environment.lifecycle_state)) {
|
|
589
|
+
return { kind: 'reusable', environment };
|
|
590
|
+
}
|
|
591
|
+
if (environment.lifecycle_state === 'queued' || environment.lifecycle_state === 'preparing') {
|
|
592
|
+
return { kind: 'preparing', environment };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const revision = db.prepare(`
|
|
596
|
+
SELECT COALESCE(MAX(environment_revision), 0) + 1 AS revision
|
|
597
|
+
FROM execution_environments
|
|
598
|
+
WHERE project_id = ? AND provider_kind = ? AND target_kind = ? AND target_key = ?
|
|
599
|
+
`).get(input.projectId, input.providerKind, input.target.kind, input.target.key).revision;
|
|
600
|
+
const externalId = input.resolveExternalId(revision, fingerprint);
|
|
601
|
+
const environment = createEnvironmentInTransaction({
|
|
602
|
+
...input,
|
|
603
|
+
environmentRevision: revision,
|
|
604
|
+
externalId,
|
|
605
|
+
}).environment;
|
|
606
|
+
const preparing = transitionEnvironmentInTransaction({
|
|
607
|
+
projectId: input.projectId,
|
|
608
|
+
environmentId: environment.id,
|
|
609
|
+
state: 'preparing',
|
|
610
|
+
eventType: 'environment_preparing',
|
|
611
|
+
}).environment;
|
|
612
|
+
return { kind: 'created', environment: preparing };
|
|
613
|
+
});
|
|
614
|
+
return transaction.immediate();
|
|
615
|
+
};
|
|
616
|
+
export const markExecutionEnvironmentValidated = (input) => withExecutionEnvironmentTransaction(() => {
|
|
617
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
618
|
+
const validatedAt = input.validatedAt ?? new Date().toISOString();
|
|
619
|
+
const result = db.prepare(`
|
|
620
|
+
UPDATE execution_environments
|
|
621
|
+
SET last_validated_at = ?, updated_at = ?
|
|
622
|
+
WHERE id = ? AND project_id = ?
|
|
623
|
+
`).run(validatedAt, validatedAt, input.environmentId, input.projectId);
|
|
624
|
+
if (result.changes !== 1) {
|
|
625
|
+
throw new Error(`Execution environment ${input.environmentId} changed concurrently`);
|
|
626
|
+
}
|
|
627
|
+
return { ...environment, last_validated_at: validatedAt, updated_at: validatedAt };
|
|
628
|
+
});
|
|
629
|
+
export const claimExecutionEnvironmentsForCleanup = (input = {}) => withExecutionEnvironmentTransaction(transaction => {
|
|
630
|
+
const limit = input.limit ?? 50;
|
|
631
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200) {
|
|
632
|
+
throw new Error('Environment cleanup claim limit must be between 1 and 200');
|
|
633
|
+
}
|
|
634
|
+
const claimId = input.claimId ?? randomUUID();
|
|
635
|
+
assertBoundedString(claimId, 'Environment cleanup claim ID', 128);
|
|
636
|
+
const claimedAt = input.claimedAt ?? new Date().toISOString();
|
|
637
|
+
const rows = db.prepare(`
|
|
638
|
+
SELECT * FROM execution_environments
|
|
639
|
+
WHERE lifecycle_state IN (${CLEANUP_CLAIMABLE_STATES.map(() => '?').join(', ')})
|
|
640
|
+
AND (? IS NULL OR project_id = ?)
|
|
641
|
+
AND (? IS NULL OR target_kind = ?)
|
|
642
|
+
AND (? IS NULL OR target_key = ?)
|
|
643
|
+
AND (cleanup_claim_id IS NULL OR (? IS NOT NULL AND cleanup_claimed_at < ?))
|
|
644
|
+
ORDER BY updated_at ASC, id ASC
|
|
645
|
+
LIMIT ?
|
|
646
|
+
`).all(...CLEANUP_CLAIMABLE_STATES, input.projectId ?? null, input.projectId ?? null, input.targetKind ?? null, input.targetKind ?? null, input.targetKey ?? null, input.targetKey ?? null, input.staleBefore ?? null, input.staleBefore ?? null, limit);
|
|
647
|
+
const claimed = [];
|
|
648
|
+
for (const row of rows) {
|
|
649
|
+
const result = db.prepare(`
|
|
650
|
+
UPDATE execution_environments
|
|
651
|
+
SET lifecycle_state = 'cleanup_pending', cleanup_claim_id = ?, cleanup_claimed_at = ?,
|
|
652
|
+
cleanup_attempts = cleanup_attempts + 1,
|
|
653
|
+
cleanup_pending_at = COALESCE(cleanup_pending_at, ?), updated_at = ?
|
|
654
|
+
WHERE id = ? AND project_id = ?
|
|
655
|
+
AND lifecycle_state = ?
|
|
656
|
+
AND (cleanup_claim_id IS NULL OR (? IS NOT NULL AND cleanup_claimed_at < ?))
|
|
657
|
+
`).run(claimId, claimedAt, claimedAt, claimedAt, row.id, row.project_id, row.lifecycle_state, input.staleBefore ?? null, input.staleBefore ?? null);
|
|
658
|
+
if (result.changes !== 1) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
transaction.appendExecutionEnvironmentEvent({
|
|
662
|
+
projectId: row.project_id,
|
|
663
|
+
environmentId: row.id,
|
|
664
|
+
eventType: 'environment_cleanup_claimed',
|
|
665
|
+
fromState: row.lifecycle_state,
|
|
666
|
+
toState: 'cleanup_pending',
|
|
667
|
+
metadata: { claimId },
|
|
668
|
+
createdAt: claimedAt,
|
|
669
|
+
});
|
|
670
|
+
claimed.push(requireEnvironment(row.project_id, row.id));
|
|
671
|
+
}
|
|
672
|
+
return claimed;
|
|
673
|
+
});
|
|
674
|
+
export const completeExecutionEnvironmentCleanup = (input) => withExecutionEnvironmentTransaction(transaction => {
|
|
675
|
+
const environment = requireEnvironment(input.projectId, input.environmentId);
|
|
676
|
+
if (environment.lifecycle_state !== 'cleanup_pending' || environment.cleanup_claim_id !== input.claimId) {
|
|
677
|
+
throw new Error(`Execution environment ${input.environmentId} cleanup claim is not owned by ${input.claimId}`);
|
|
678
|
+
}
|
|
679
|
+
const completedAt = input.completedAt ?? new Date().toISOString();
|
|
680
|
+
const result = db.prepare(`
|
|
681
|
+
UPDATE execution_environments
|
|
682
|
+
SET lifecycle_state = 'retired', cleanup_claim_id = NULL, cleanup_claimed_at = NULL,
|
|
683
|
+
retired_at = ?, updated_at = ?
|
|
684
|
+
WHERE id = ? AND project_id = ? AND lifecycle_state = 'cleanup_pending' AND cleanup_claim_id = ?
|
|
685
|
+
`).run(completedAt, completedAt, input.environmentId, input.projectId, input.claimId);
|
|
686
|
+
if (result.changes !== 1) {
|
|
687
|
+
throw new Error(`Execution environment ${input.environmentId} cleanup claim was lost`);
|
|
688
|
+
}
|
|
689
|
+
transaction.appendExecutionEnvironmentEvent({
|
|
690
|
+
projectId: input.projectId,
|
|
691
|
+
environmentId: input.environmentId,
|
|
692
|
+
eventType: 'environment_cleanup_completed',
|
|
693
|
+
fromState: 'cleanup_pending',
|
|
694
|
+
toState: 'retired',
|
|
695
|
+
createdAt: completedAt,
|
|
696
|
+
});
|
|
697
|
+
return requireEnvironment(input.projectId, input.environmentId);
|
|
698
|
+
});
|
|
699
|
+
export const failExecutionEnvironmentCleanup = (input) => withExecutionEnvironmentTransaction(transaction => {
|
|
700
|
+
assertBoundedString(input.failure.code, 'Environment cleanup failure code', 100);
|
|
701
|
+
if (input.failure.message.length > MAX_EVENT_MESSAGE_LENGTH) {
|
|
702
|
+
throw new Error(`Environment cleanup failure message exceeds ${MAX_EVENT_MESSAGE_LENGTH} characters`);
|
|
703
|
+
}
|
|
704
|
+
const failedAt = input.failedAt ?? new Date().toISOString();
|
|
705
|
+
const result = db.prepare(`
|
|
706
|
+
UPDATE execution_environments
|
|
707
|
+
SET cleanup_claim_id = NULL, cleanup_claimed_at = NULL, failure_code = ?, failure_message = ?,
|
|
708
|
+
failure_details_json = ?, updated_at = ?
|
|
709
|
+
WHERE id = ? AND project_id = ? AND lifecycle_state = 'cleanup_pending' AND cleanup_claim_id = ?
|
|
710
|
+
`).run(input.failure.code, input.failure.message, input.failure.details ? serializeBoundedObject(input.failure.details, 'Environment cleanup failure details') : null, failedAt, input.environmentId, input.projectId, input.claimId);
|
|
711
|
+
if (result.changes !== 1) {
|
|
712
|
+
throw new Error(`Execution environment ${input.environmentId} cleanup claim was lost`);
|
|
713
|
+
}
|
|
714
|
+
transaction.appendExecutionEnvironmentEvent({
|
|
715
|
+
projectId: input.projectId,
|
|
716
|
+
environmentId: input.environmentId,
|
|
717
|
+
eventType: 'environment_cleanup_failed',
|
|
718
|
+
fromState: 'cleanup_pending',
|
|
719
|
+
toState: 'cleanup_pending',
|
|
720
|
+
message: input.failure.message,
|
|
721
|
+
metadata: { failureCode: input.failure.code },
|
|
722
|
+
createdAt: failedAt,
|
|
723
|
+
});
|
|
724
|
+
return requireEnvironment(input.projectId, input.environmentId);
|
|
725
|
+
});
|
|
726
|
+
//# sourceMappingURL=executionEnvironments.js.map
|