@omnitron-dev/omnitron 0.3.3 → 0.4.6
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/README.md +296 -6
- package/bin/{pnpmfile-sdk.mjs → pnpmfile-sdk.js} +19 -1
- package/bin/sdk.d.ts +19 -0
- package/bin/sdk.js +932 -0
- package/dist/BUILD.json +2 -2
- package/dist/cli/omnitron.js +68 -20
- package/dist/cli/omnitron.js.map +1 -1
- package/dist/commands/infra.d.ts +8 -2
- package/dist/commands/infra.d.ts.map +1 -1
- package/dist/commands/infra.js +52 -59
- package/dist/commands/infra.js.map +1 -1
- package/dist/commands/node.d.ts.map +1 -1
- package/dist/commands/node.js +8 -1
- package/dist/commands/node.js.map +1 -1
- package/dist/commands/release-store.d.ts.map +1 -1
- package/dist/commands/release-store.js +6 -9
- package/dist/commands/release-store.js.map +1 -1
- package/dist/commands/release.d.ts +4 -1
- package/dist/commands/release.d.ts.map +1 -1
- package/dist/commands/release.js +125 -5
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/stack.d.ts +3 -6
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +51 -9
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +1 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/config/saved.d.ts +3 -0
- package/dist/config/saved.d.ts.map +1 -1
- package/dist/config/saved.js +1 -0
- package/dist/config/saved.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/daemon/daemon-client.d.ts +3 -15
- package/dist/daemon/daemon-client.d.ts.map +1 -1
- package/dist/daemon/daemon-client.js +4 -0
- package/dist/daemon/daemon-client.js.map +1 -1
- package/dist/daemon/daemon.d.ts +5 -11
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +58 -48
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/daemon.rpc-service.d.ts +3 -41
- package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
- package/dist/daemon/daemon.rpc-service.js +50 -117
- package/dist/daemon/daemon.rpc-service.js.map +1 -1
- package/dist/daemon/darwin-owner-evidence.js +1 -1
- package/dist/daemon/darwin-owner-evidence.js.map +1 -1
- package/dist/daemon/execution-capabilities.d.ts +1 -1
- package/dist/daemon/execution-capabilities.d.ts.map +1 -1
- package/dist/daemon/execution-capabilities.js +3 -3
- package/dist/daemon/execution-capabilities.js.map +1 -1
- package/dist/daemon/execution-client.d.ts +5 -1
- package/dist/daemon/execution-client.d.ts.map +1 -1
- package/dist/daemon/execution-client.js +45 -19
- package/dist/daemon/execution-client.js.map +1 -1
- package/dist/daemon/execution-host.d.ts +4 -11
- package/dist/daemon/execution-host.d.ts.map +1 -1
- package/dist/daemon/execution-host.js +3 -9
- package/dist/daemon/execution-host.js.map +1 -1
- package/dist/daemon/execution-owner.d.ts.map +1 -1
- package/dist/daemon/execution-owner.js +2 -16
- package/dist/daemon/execution-owner.js.map +1 -1
- package/dist/daemon/execution-shutdown.d.ts +1 -1
- package/dist/daemon/execution-shutdown.d.ts.map +1 -1
- package/dist/daemon/managed-owner-build.d.ts +4 -0
- package/dist/daemon/managed-owner-build.d.ts.map +1 -1
- package/dist/daemon/managed-owner-build.js +14 -2
- package/dist/daemon/managed-owner-build.js.map +1 -1
- package/dist/daemon/managed-runtime.js +1 -1
- package/dist/daemon/managed-runtime.js.map +1 -1
- package/dist/daemon/owner-reconciliation.d.ts.map +1 -1
- package/dist/daemon/owner-reconciliation.js +1 -2
- package/dist/daemon/owner-reconciliation.js.map +1 -1
- package/dist/daemon/startup-config.d.ts +1 -0
- package/dist/daemon/startup-config.d.ts.map +1 -1
- package/dist/daemon/system-guardian-cgroup.js +1 -1
- package/dist/daemon/system-guardian-cgroup.js.map +1 -1
- package/dist/infrastructure/container-runtime.d.ts +8 -0
- package/dist/infrastructure/container-runtime.d.ts.map +1 -1
- package/dist/infrastructure/container-runtime.js +5 -0
- package/dist/infrastructure/container-runtime.js.map +1 -1
- package/dist/infrastructure/existing-containers.d.ts +25 -0
- package/dist/infrastructure/existing-containers.d.ts.map +1 -0
- package/dist/infrastructure/existing-containers.js +183 -0
- package/dist/infrastructure/existing-containers.js.map +1 -0
- package/dist/infrastructure/infrastructure.service.d.ts +19 -1
- package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
- package/dist/infrastructure/infrastructure.service.js +136 -17
- package/dist/infrastructure/infrastructure.service.js.map +1 -1
- package/dist/infrastructure/presets/postgres.js +1 -1
- package/dist/infrastructure/presets/postgres.js.map +1 -1
- package/dist/infrastructure/presets/registry.d.ts.map +1 -1
- package/dist/infrastructure/presets/registry.js +11 -19
- package/dist/infrastructure/presets/registry.js.map +1 -1
- package/dist/infrastructure/stack-infra-manager.d.ts +11 -1
- package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
- package/dist/infrastructure/stack-infra-manager.js +79 -8
- package/dist/infrastructure/stack-infra-manager.js.map +1 -1
- package/dist/mcp/tool-groups/infra.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/infra.tools.js +4 -1
- package/dist/mcp/tool-groups/infra.tools.js.map +1 -1
- package/dist/monitoring/log-journal.d.ts.map +1 -1
- package/dist/monitoring/log-journal.js +1 -2
- package/dist/monitoring/log-journal.js.map +1 -1
- package/dist/operations.d.ts +1 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +1 -0
- package/dist/operations.js.map +1 -1
- package/dist/orchestrator/app-name.d.ts +33 -0
- package/dist/orchestrator/app-name.d.ts.map +1 -0
- package/dist/orchestrator/app-name.js +68 -0
- package/dist/orchestrator/app-name.js.map +1 -0
- package/dist/orchestrator/bootstrap-loader.d.ts +2 -0
- package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-loader.js +2 -1
- package/dist/orchestrator/bootstrap-loader.js.map +1 -1
- package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
- package/dist/orchestrator/classic-launcher.js +2 -1
- package/dist/orchestrator/classic-launcher.js.map +1 -1
- package/dist/orchestrator/execution-contract.d.ts +2 -3
- package/dist/orchestrator/execution-contract.d.ts.map +1 -1
- package/dist/orchestrator/file-watcher.d.ts +11 -23
- package/dist/orchestrator/file-watcher.d.ts.map +1 -1
- package/dist/orchestrator/file-watcher.js +57 -42
- package/dist/orchestrator/file-watcher.js.map +1 -1
- package/dist/orchestrator/orchestrator.service.d.ts +11 -31
- package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.service.js +127 -109
- package/dist/orchestrator/orchestrator.service.js.map +1 -1
- package/dist/project/bootstrap-metadata.d.ts +1 -1
- package/dist/project/bootstrap-metadata.d.ts.map +1 -1
- package/dist/project/bootstrap-metadata.js +3 -2
- package/dist/project/bootstrap-metadata.js.map +1 -1
- package/dist/project/node-stack-stop.d.ts +2 -1
- package/dist/project/node-stack-stop.d.ts.map +1 -1
- package/dist/project/node-stack-stop.js +5 -1
- package/dist/project/node-stack-stop.js.map +1 -1
- package/dist/project/stack-diagnosis.d.ts.map +1 -1
- package/dist/project/stack-diagnosis.js +2 -3
- package/dist/project/stack-diagnosis.js.map +1 -1
- package/dist/release/artifact-store.d.ts +42 -7
- package/dist/release/artifact-store.d.ts.map +1 -1
- package/dist/release/artifact-store.js +176 -62
- package/dist/release/artifact-store.js.map +1 -1
- package/dist/release/closure-library-path.d.ts +8 -2
- package/dist/release/closure-library-path.d.ts.map +1 -1
- package/dist/release/closure-library-path.js +72 -28
- package/dist/release/closure-library-path.js.map +1 -1
- package/dist/release/configured-store.d.ts +6 -0
- package/dist/release/configured-store.d.ts.map +1 -0
- package/dist/release/configured-store.js +54 -0
- package/dist/release/configured-store.js.map +1 -0
- package/dist/release/load.d.ts +3 -0
- package/dist/release/load.d.ts.map +1 -1
- package/dist/release/load.js +8 -2
- package/dist/release/load.js.map +1 -1
- package/dist/release/local-installation.d.ts +2 -0
- package/dist/release/local-installation.d.ts.map +1 -1
- package/dist/release/local-installation.js +1 -1
- package/dist/release/local-installation.js.map +1 -1
- package/dist/release/portable-archive.d.ts +3 -0
- package/dist/release/portable-archive.d.ts.map +1 -1
- package/dist/release/portable-archive.js +13 -7
- package/dist/release/portable-archive.js.map +1 -1
- package/dist/release/registry-framework.d.ts +25 -0
- package/dist/release/registry-framework.d.ts.map +1 -1
- package/dist/release/registry-framework.js +51 -3
- package/dist/release/registry-framework.js.map +1 -1
- package/dist/release/target-runtime.d.ts +4 -0
- package/dist/release/target-runtime.d.ts.map +1 -1
- package/dist/release/target-runtime.js +9 -0
- package/dist/release/target-runtime.js.map +1 -1
- package/dist/services/host-upgrade.d.ts +2 -0
- package/dist/services/host-upgrade.d.ts.map +1 -1
- package/dist/services/host-upgrade.js +6 -2
- package/dist/services/host-upgrade.js.map +1 -1
- package/dist/services/infrastructure.rpc-service.d.ts +14 -2
- package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
- package/dist/services/infrastructure.rpc-service.js +128 -17
- package/dist/services/infrastructure.rpc-service.js.map +1 -1
- package/dist/services/kernel-rollout.service.d.ts.map +1 -1
- package/dist/services/kernel-rollout.service.js +1 -2
- package/dist/services/kernel-rollout.service.js.map +1 -1
- package/dist/services/migration-process.d.ts +1 -0
- package/dist/services/migration-process.d.ts.map +1 -1
- package/dist/services/migration-process.js +4 -2
- package/dist/services/migration-process.js.map +1 -1
- package/dist/services/node-deploy-lease.d.ts +4 -1
- package/dist/services/node-deploy-lease.d.ts.map +1 -1
- package/dist/services/node-deploy-lease.js +29 -4
- package/dist/services/node-deploy-lease.js.map +1 -1
- package/dist/services/node-manager.service.d.ts +5 -25
- package/dist/services/node-manager.service.d.ts.map +1 -1
- package/dist/services/node-manager.service.js +150 -139
- package/dist/services/node-manager.service.js.map +1 -1
- package/dist/services/pipeline-definition.d.ts.map +1 -1
- package/dist/services/pipeline-definition.js +32 -6
- package/dist/services/pipeline-definition.js.map +1 -1
- package/dist/services/pipeline.service.d.ts +7 -1
- package/dist/services/pipeline.service.d.ts.map +1 -1
- package/dist/services/pipeline.service.js +106 -13
- package/dist/services/pipeline.service.js.map +1 -1
- package/dist/services/project.rpc-service.d.ts +6 -5
- package/dist/services/project.rpc-service.d.ts.map +1 -1
- package/dist/services/project.rpc-service.js +13 -0
- package/dist/services/project.rpc-service.js.map +1 -1
- package/dist/services/project.service.d.ts +30 -6
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +414 -66
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/release.rpc-service.d.ts +2 -1
- package/dist/services/release.rpc-service.d.ts.map +1 -1
- package/dist/services/release.rpc-service.js +19 -3
- package/dist/services/release.rpc-service.js.map +1 -1
- package/dist/services/release.service.d.ts +24 -11
- package/dist/services/release.service.d.ts.map +1 -1
- package/dist/services/release.service.js +162 -39
- package/dist/services/release.service.js.map +1 -1
- package/dist/services/remote-deployer.service.d.ts +1 -1
- package/dist/services/remote-deployer.service.d.ts.map +1 -1
- package/dist/services/remote-deployer.service.js +3 -0
- package/dist/services/remote-deployer.service.js.map +1 -1
- package/dist/services/secrets.service.d.ts +6 -0
- package/dist/services/secrets.service.d.ts.map +1 -1
- package/dist/services/secrets.service.js +95 -51
- package/dist/services/secrets.service.js.map +1 -1
- package/dist/shared/dto/backend.d.ts +3 -0
- package/dist/shared/dto/backend.d.ts.map +1 -1
- package/dist/shared/dto/backend.js.map +1 -1
- package/dist/shared/dto/nodes.d.ts +2 -0
- package/dist/shared/dto/nodes.d.ts.map +1 -1
- package/dist/shared/dto/pipelines.d.ts +54 -5
- package/dist/shared/dto/pipelines.d.ts.map +1 -1
- package/dist/shared/dto/pipelines.js +0 -7
- package/dist/shared/dto/pipelines.js.map +1 -1
- package/dist/shared/dto/project.d.ts +61 -1
- package/dist/shared/dto/project.d.ts.map +1 -1
- package/dist/shared/dto/releases.d.ts +8 -1
- package/dist/shared/dto/releases.d.ts.map +1 -1
- package/dist/shared/dto/services.d.ts +44 -18
- package/dist/shared/dto/services.d.ts.map +1 -1
- package/dist/shared/dto/services.js.map +1 -1
- package/dist/shared/release-reading.d.ts +6 -0
- package/dist/shared/release-reading.d.ts.map +1 -1
- package/dist/shared/release-reading.js +42 -0
- package/dist/shared/release-reading.js.map +1 -1
- package/dist/workers/system-worker-manager.d.ts +1 -2
- package/dist/workers/system-worker-manager.d.ts.map +1 -1
- package/dist/workers/system-worker-manager.js +5 -3
- package/dist/workers/system-worker-manager.js.map +1 -1
- package/package.json +20 -19
- package/webapp/dist/BUILD.json +2 -2
- package/webapp/dist/assets/{access-jWkHxGc7.js → access-B2gXuoMG.js} +1 -1
- package/webapp/dist/assets/{access-48hkC2-P.js → access-I0f72P9m.js} +1 -1
- package/webapp/dist/assets/{admin-data-table-BKSDI8sU.js → admin-data-table-CAEL2f-m.js} +1 -1
- package/webapp/dist/assets/{alert-uBG6r9NW.js → alert-BdJ2uaY0.js} +1 -1
- package/webapp/dist/assets/{alerts-CuPQ9YQJ.js → alerts-DeVkMXWo.js} +1 -1
- package/webapp/dist/assets/{apps-Drio028B.js → apps-9SuDvyGQ.js} +1 -1
- package/webapp/dist/assets/{audit-npSFJECe.js → audit-DT8cY7Ys.js} +1 -1
- package/webapp/dist/assets/backend-status.store-BbBE9cn8.js +4 -0
- package/webapp/dist/assets/{card-DkH32gMY.js → card-DaUEmUI5.js} +1 -1
- package/webapp/dist/assets/{chart-BpGXlCJc.js → chart-BKXP2ter.js} +1 -1
- package/webapp/dist/assets/{client-DFnuv2z3.js → client-CgyTwq9g.js} +1 -1
- package/webapp/dist/assets/{code-editor-DK6Tmmxv.js → code-editor-JVuQ4m-z.js} +1 -1
- package/webapp/dist/assets/{comparison-bars-BtC0dHjF.js → comparison-bars-Ds5Dt1oj.js} +1 -1
- package/webapp/dist/assets/{confirm-dialog-BbGsAz7_.js → confirm-dialog-BayExVbF.js} +1 -1
- package/webapp/dist/assets/{console-keyboard-D3YCA0yU.js → console-keyboard-uSLt92dB.js} +1 -1
- package/webapp/dist/assets/{containers-Bl8gAyZX.js → containers-DkXTgcrY.js} +1 -1
- package/webapp/dist/assets/{controls-C5W8gHU4.js → controls-CqeGPnaF.js} +1 -1
- package/webapp/dist/assets/{dashboard-UHlet3H9.js → dashboard-By5yPuOf.js} +1 -1
- package/webapp/dist/assets/{dashboard-builder-67PZFqhE.js → dashboard-builder-C0hvBwkd.js} +1 -1
- package/webapp/dist/assets/deploy-progress-B39cvvCE.js +1 -0
- package/webapp/dist/assets/deploy-release-dialog-CnzaO_xL.js +1 -0
- package/webapp/dist/assets/deployments-BXP943hA.js +1 -0
- package/webapp/dist/assets/{detail-B4TiBfxU.js → detail-B84RgiOb.js} +1 -1
- package/webapp/dist/assets/{detail-CYS81K6Y.js → detail-CVXDks9-.js} +1 -1
- package/webapp/dist/assets/detail-D9DixtaK.js +2 -0
- package/webapp/dist/assets/{empty-content-7jOFhG4C.js → empty-content-eyDOuOVp.js} +1 -1
- package/webapp/dist/assets/{error-boundary-CLLI8PWw.js → error-boundary-CQmDr_HS.js} +1 -1
- package/webapp/dist/assets/{event-fields-CiyqrRO8.js → event-fields-C7p2a2HY.js} +1 -1
- package/webapp/dist/assets/{files-DigwXndm.js → files-BsfGT3t6.js} +2 -2
- package/webapp/dist/assets/{icons-OboZgWSt.js → icons-B9Ct8PJ9.js} +1 -1
- package/webapp/dist/assets/index-BEfv96EF.js +1 -0
- package/webapp/dist/assets/{label-B1SF0RTL.js → label-avMhe08g.js} +1 -1
- package/webapp/dist/assets/{log-level-filter-Bcn31D1F.js → log-level-filter-oaUGdbpk.js} +1 -1
- package/webapp/dist/assets/{logs-BqHOufk8.js → logs-iC1OEDv3.js} +1 -1
- package/webapp/dist/assets/{metric-chart-M-kI-XOt.js → metric-chart-CSa_qv4F.js} +1 -1
- package/webapp/dist/assets/{metrics-QIQaF7iS.js → metrics-CKCmcGKw.js} +1 -1
- package/webapp/dist/assets/{mui-Cd711nOk.js → mui-B3fJdIMc.js} +1 -1
- package/webapp/dist/assets/{mui-CdB_p2lt.js → mui-Bdr6Ks5j.js} +1 -1
- package/webapp/dist/assets/{mui-BIz4LSVs.js → mui-CADnFGCx.js} +1 -1
- package/webapp/dist/assets/{mui-AnEgAVjz.js → mui-CMWGTMXv.js} +1 -1
- package/webapp/dist/assets/{mui-DyT2n1lx.js → mui-CN3dT7bb.js} +1 -1
- package/webapp/dist/assets/{mui-BwsIrivo.js → mui-CPEkFAPo.js} +1 -1
- package/webapp/dist/assets/{mui-B1jrje6Z.js → mui-D3qAWvfZ.js} +1 -1
- package/webapp/dist/assets/{mui-C_hromOc.js → mui-D7tBU80e.js} +1 -1
- package/webapp/dist/assets/{mui-y44PQOfG.js → mui-DaJavAoc.js} +1 -1
- package/webapp/dist/assets/{mui-BXSXx9c9.js → mui-SNRRJ_vi.js} +1 -1
- package/webapp/dist/assets/{nodes-BJPk02vv.js → nodes-DtLyXH0V.js} +1 -1
- package/webapp/dist/assets/{operation-status-Bm1x1wTz.js → operation-status-DHyYOT7G.js} +1 -1
- package/webapp/dist/assets/{page-breadcrumbs-fR7Jmpp4.js → page-breadcrumbs-3R8RZ3nA.js} +1 -1
- package/webapp/dist/assets/pipelines-Do2NAAqK.js +3 -0
- package/webapp/dist/assets/project.store-D5IR3fo9.js +1 -0
- package/webapp/dist/assets/{projects-YoBzcQ24.js → projects-BqlGbzeG.js} +1 -1
- package/webapp/dist/assets/{realtime.store-CBDO4Abj.js → realtime.store-C8D235-B.js} +1 -1
- package/webapp/dist/assets/release-bits-IehuBssK.js +1 -0
- package/webapp/dist/assets/release-build-panel-C48hP_9w.js +1 -0
- package/webapp/dist/assets/release-deployment-state-BfmxZP4Y.js +1 -0
- package/webapp/dist/assets/releases-Cm0RV5Xu.js +2 -0
- package/webapp/dist/assets/{search-input-C0MHI8vO.js → search-input-SWD1dXxD.js} +1 -1
- package/webapp/dist/assets/settings-DwuEPQGg.js +1 -0
- package/webapp/dist/assets/sign-in-CQ-WeS5A.js +1 -0
- package/webapp/dist/assets/{skeleton-BmWDJWF9.js → skeleton-BnVxAoeX.js} +1 -1
- package/webapp/dist/assets/{snackbar-DSPmjMyi.js → snackbar-CRX6eqRT.js} +1 -1
- package/webapp/dist/assets/{stack-health-status-BIv2C10x.js → stack-health-status-DfErRNb1.js} +1 -1
- package/webapp/dist/assets/{stack-start-notice-BVnMOwRQ.js → stack-start-notice-NkYDjR-f.js} +1 -1
- package/webapp/dist/assets/{stacks-IZ1mfizi.js → stacks-BOGCfW90.js} +1 -1
- package/webapp/dist/assets/{store-bqUBn2A4.js → store-DtTtbc9H.js} +1 -1
- package/webapp/dist/assets/{system-info-BFKNM-hf.js → system-info-Cw-xywgI.js} +1 -1
- package/webapp/dist/assets/{table-BOWT6Y9d.js → table-D-so_B1p.js} +1 -1
- package/webapp/dist/assets/{table-viewport-Bp4pq-c_.js → table-viewport-DdZ1yAKm.js} +1 -1
- package/webapp/dist/assets/{tabs-k5NPGLnJ.js → tabs-DGqBkXh_.js} +1 -1
- package/webapp/dist/assets/{terminal-QWBau-Us.js → terminal-Bbl0XNeB.js} +1 -1
- package/webapp/dist/assets/{topology-CPey5dP6.js → topology-DmrRy7nN.js} +1 -1
- package/webapp/dist/assets/{topology-metadata-dialog-BivuU2y6.js → topology-metadata-dialog-Dpfp4YF1.js} +1 -1
- package/webapp/dist/assets/{traces-By0rdfDC.js → traces-CKHsOabJ.js} +1 -1
- package/webapp/dist/assets/{use-stack-context-UkBsF-l7.js → use-stack-context-BuraYaeY.js} +1 -1
- package/webapp/dist/assets/webapp-D8h_eabt.js +35 -0
- package/webapp/dist/console-version.json +1 -1
- package/webapp/dist/index.html +34 -34
- package/webapp/dist/logo.svg +1 -80
- package/bin/sdk.d.mts +0 -8
- package/bin/sdk.mjs +0 -576
- package/dist/monitoring/process-lifetime.d.ts +0 -11
- package/dist/monitoring/process-lifetime.d.ts.map +0 -1
- package/dist/monitoring/process-lifetime.js +0 -45
- package/dist/monitoring/process-lifetime.js.map +0 -1
- package/dist/shared/host-boot-identity.d.ts +0 -12
- package/dist/shared/host-boot-identity.d.ts.map +0 -1
- package/dist/shared/host-boot-identity.js +0 -89
- package/dist/shared/host-boot-identity.js.map +0 -1
- package/webapp/dist/assets/deploy-release-dialog-BOXcD5Wo.js +0 -1
- package/webapp/dist/assets/deployments-qV20Ak0P.js +0 -1
- package/webapp/dist/assets/detail-B85-Wees.js +0 -2
- package/webapp/dist/assets/index-uq8Dkk4c.js +0 -1
- package/webapp/dist/assets/omnitron-logo-koMCbsCv.js +0 -1
- package/webapp/dist/assets/pipelines-T6Ch9LTw.js +0 -3
- package/webapp/dist/assets/project.store--G5pIF-J.js +0 -1
- package/webapp/dist/assets/release-bits-B2a_wbft.js +0 -1
- package/webapp/dist/assets/release-build-panel-BQmIdKW9.js +0 -1
- package/webapp/dist/assets/release-deployment-state-DRrEI2pP.js +0 -1
- package/webapp/dist/assets/release-wire-f2VuBoBF.js +0 -1
- package/webapp/dist/assets/releases-Bp3CDgmo.js +0 -2
- package/webapp/dist/assets/settings-B3egRvbI.js +0 -1
- package/webapp/dist/assets/sign-in-9q-Nfy83.js +0 -1
- package/webapp/dist/assets/webapp-D-UhjFhS.js +0 -35
- package/webapp/dist/favicon.png +0 -0
|
@@ -8,8 +8,12 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { readProjectStacks, resolveProjectStacks } from '../config/project-stacks.js';
|
|
11
|
+
import { readSavedDaemonConfig } from '../config/saved.js';
|
|
11
12
|
import { observeStackHealth } from '../project/stack-health.js';
|
|
12
13
|
import { LocalReleaseSelection } from '../project/local-release-selection.js';
|
|
14
|
+
import { assertResidentApplicationRuntime, assertSameApplicationRuntime } from './host-upgrade.js';
|
|
15
|
+
import { assertReleaseLoaderEnvironment, assertReleaseTarget, releaseOptionsForEnv } from '../release/target-runtime.js';
|
|
16
|
+
import { withClosureLibraryPath } from '../release/closure-library-path.js';
|
|
13
17
|
import { resolveAppPath } from '../config/app-path.js';
|
|
14
18
|
import { infrastructureOwnership, ownsStackInfrastructure } from '../project/infrastructure-ownership.js';
|
|
15
19
|
import { InstalledStackResume, resumeDestinations } from '../project/installed-stack-resume.js';
|
|
@@ -17,7 +21,7 @@ import { isDeepStrictEqual } from 'node:util';
|
|
|
17
21
|
import { diagnoseContainers } from '../project/stack-diagnosis.js';
|
|
18
22
|
import { diagnosisStatus } from '../shared/dto/stack-diagnosis.js';
|
|
19
23
|
import { buildInfraFromRequirements, requirementsFromDefinitions } from '../project/requirements-scanner.js';
|
|
20
|
-
import { randomUUID } from 'node:crypto';
|
|
24
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
21
25
|
import { NodeStackStop, stackDefinitionFingerprint } from '../project/node-stack-stop.js';
|
|
22
26
|
import { stackStopReview } from '../project/stack-stop-review.js';
|
|
23
27
|
import { inspectStackContainers, removeContainer } from '../infrastructure/container-runtime.js';
|
|
@@ -141,7 +145,7 @@ export class ProjectService extends EventEmitter {
|
|
|
141
145
|
recoveryRegistryDigest() { return this.registry.recoveryDigest(); }
|
|
142
146
|
/** Read-only bookkeeping; no timers are stopped and no Docker call runs. */
|
|
143
147
|
captureRecoveryState() {
|
|
144
|
-
if (this.rereading?.size || this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.size || this.stopsInFlight.size || this.nodeStackStop.busy || this.reconcilingStacks ||
|
|
148
|
+
if (this.incomingReleases?.size || this.rereading?.size || this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.size || this.stopsInFlight.size || this.nodeStackStop.busy || this.reconcilingStacks ||
|
|
145
149
|
[...this.stackStates.values()].some(state => state.status === 'starting' || state.status === 'stopping')) {
|
|
146
150
|
throw new RecoveryBusyError('Project ownership is changing; a recovery snapshot is not ready');
|
|
147
151
|
}
|
|
@@ -157,7 +161,7 @@ export class ProjectService extends EventEmitter {
|
|
|
157
161
|
}
|
|
158
162
|
/** Capture controllers' bookkeeping, retaining native application ownership in the execution host. */
|
|
159
163
|
async prepareHandoff() {
|
|
160
|
-
if (this.rereading?.size || this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.size || this.stopsInFlight.size || this.nodeStackStop.busy || this.reconcilingStacks || [...this.stackStates.values()].some(state => state.status === 'starting' || state.status === 'stopping')) {
|
|
164
|
+
if (this.incomingReleases?.size || this.rereading?.size || this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.size || this.stopsInFlight.size || this.nodeStackStop.busy || this.reconcilingStacks || [...this.stackStates.values()].some(state => state.status === 'starting' || state.status === 'stopping')) {
|
|
161
165
|
throw new Error('A stack lifecycle operation is active; finish it before transferring the backend');
|
|
162
166
|
}
|
|
163
167
|
this.handoffReconciling = this.resumeTimer !== null;
|
|
@@ -244,6 +248,7 @@ export class ProjectService extends EventEmitter {
|
|
|
244
248
|
this.daemonStateStore = daemonStateStore;
|
|
245
249
|
this.syncService = options.syncService;
|
|
246
250
|
this.secrets = options.secrets;
|
|
251
|
+
this.receiveRelease = options.receiveRelease;
|
|
247
252
|
this.audit = options.audit;
|
|
248
253
|
// T-7 — registry persistence routed through DaemonStateStore
|
|
249
254
|
// (SQLite, transactional, co-located with daemon-state-kv,
|
|
@@ -270,7 +275,18 @@ export class ProjectService extends EventEmitter {
|
|
|
270
275
|
execution: orchestrator,
|
|
271
276
|
definition: (scope, cached) => this.stopDefinition(scope, cached),
|
|
272
277
|
containers: scope => inspectStackContainers(scope),
|
|
273
|
-
disableResume:
|
|
278
|
+
disableResume: (scope, keep) => {
|
|
279
|
+
if (keep)
|
|
280
|
+
this.setInfrastructurePolicy(scope.project, scope.stack, null);
|
|
281
|
+
this.registry.setStackEnabled(scope.project, scope.stack, false);
|
|
282
|
+
},
|
|
283
|
+
holdInfrastructure: async (scope) => {
|
|
284
|
+
await this.drainHostedProvisioning(scope);
|
|
285
|
+
const hosted = this.hostedInfrastructure();
|
|
286
|
+
if (hosted?.representsStack(scope))
|
|
287
|
+
await hosted.holdForPreservation();
|
|
288
|
+
await this.infraManager.getInstance(scope.project, scope.stack)?.holdForPreservation();
|
|
289
|
+
},
|
|
274
290
|
retireInfrastructure: async (scope) => {
|
|
275
291
|
await this.drainHostedProvisioning(scope);
|
|
276
292
|
await this.hostedInfrastructure()?.retireStack(scope);
|
|
@@ -1315,15 +1331,15 @@ export class ProjectService extends EventEmitter {
|
|
|
1315
1331
|
continue;
|
|
1316
1332
|
for (const app of await this.launchEntries(scope.project, name, stack, config))
|
|
1317
1333
|
if (app.bootstrap)
|
|
1318
|
-
bootstraps.set(resolveAppPath(directory, app, app.bootstrap), app.name);
|
|
1334
|
+
bootstraps.set(resolveAppPath(directory, app, app.bootstrap), { app: app.name, ...(app.releaseRoot && { releaseRoot: app.releaseRoot }) });
|
|
1319
1335
|
}
|
|
1320
1336
|
const result = { refreshed: 0, failures: [] };
|
|
1321
|
-
for (const [bootstrap, app] of bootstraps) {
|
|
1337
|
+
for (const [bootstrap, { app, releaseRoot }] of bootstraps) {
|
|
1322
1338
|
try {
|
|
1323
1339
|
const remaining = Math.min(30_000, deadline - Date.now());
|
|
1324
1340
|
if (remaining <= 0)
|
|
1325
1341
|
throw new Error('Declaration refresh deadline exceeded; this application was not read');
|
|
1326
|
-
const { metadata: record } = await readBootstrapMetadata(bootstrap, false, remaining);
|
|
1342
|
+
const { metadata: record } = await readBootstrapMetadata(bootstrap, false, remaining, releaseRoot);
|
|
1327
1343
|
this.bootstrapMetadata.save(record);
|
|
1328
1344
|
this.bootstrapTopology.set(bootstrap, record.definition);
|
|
1329
1345
|
result.refreshed += 1;
|
|
@@ -1383,6 +1399,109 @@ export class ProjectService extends EventEmitter {
|
|
|
1383
1399
|
*/
|
|
1384
1400
|
rereading;
|
|
1385
1401
|
startsInFlight = new Map();
|
|
1402
|
+
infrastructurePolicyKey(project, stack) {
|
|
1403
|
+
const registration = this.registry.get(project);
|
|
1404
|
+
if (!registration?.registeredAt)
|
|
1405
|
+
throw Error('Infrastructure authority requires a project registration');
|
|
1406
|
+
return `lifecycle:infrastructure:${stackExecutionKey({ project, stack })}:${registration.registeredAt}`;
|
|
1407
|
+
}
|
|
1408
|
+
infrastructurePolicy(project, stack) {
|
|
1409
|
+
const value = this.daemonStateStore.kvGetSync(this.infrastructurePolicyKey(project, stack), { strict: true });
|
|
1410
|
+
if (value && (value.mode !== 'preserve' || (value.review !== null && !/^[a-f0-9]{64}$/.test(value.review))))
|
|
1411
|
+
throw Error('Invalid retained infrastructure authority');
|
|
1412
|
+
return value;
|
|
1413
|
+
}
|
|
1414
|
+
setInfrastructurePolicy(project, stack, review, reconcile = false) {
|
|
1415
|
+
this.daemonStateStore.kvUpdateSync(this.infrastructurePolicyKey(project, stack), () => reconcile ? null : { mode: 'preserve', review });
|
|
1416
|
+
}
|
|
1417
|
+
/** Read declarations without importing application code or executing startup hooks. */
|
|
1418
|
+
preservationDefinitions(project, stack, entries) {
|
|
1419
|
+
const root = this.registry.get(project)?.path;
|
|
1420
|
+
if (!root)
|
|
1421
|
+
throw Error('Project is not registered');
|
|
1422
|
+
const definitions = new Map();
|
|
1423
|
+
for (const entry of entries) {
|
|
1424
|
+
if (entry.releaseFrom)
|
|
1425
|
+
throw Error('Preservation requires a prepared release installation');
|
|
1426
|
+
if (!entry.bootstrap)
|
|
1427
|
+
continue;
|
|
1428
|
+
const bootstrap = resolveAppPath(root, entry, entry.bootstrap);
|
|
1429
|
+
const { metadata, declared, state, record } = this.readAppDeclaration(project, stack, entry.name, bootstrap);
|
|
1430
|
+
if (declared.malformed || !metadata || !record || state?.failure || metadataSourceChanged(record))
|
|
1431
|
+
throw Error('Current application declarations must be captured before reviewing infrastructure preservation');
|
|
1432
|
+
definitions.set(entry.name, withDeclaredConfig({ name: entry.name, version: '', processes: [], ...metadata }, declared));
|
|
1433
|
+
}
|
|
1434
|
+
return definitions;
|
|
1435
|
+
}
|
|
1436
|
+
/** Shared lifecycle preflight. No release installation, bootstrap import, provisioning or database effects. */
|
|
1437
|
+
async reviewStackStart(project, stackName, options = {}) {
|
|
1438
|
+
if (options.infrastructure !== undefined && options.infrastructure !== 'preserve')
|
|
1439
|
+
throw Error('Start review requires infrastructure=preserve');
|
|
1440
|
+
if (options.workingTree && options.release)
|
|
1441
|
+
throw Error('Select either a release or the working tree');
|
|
1442
|
+
const report = { protocol: 1, project, stack: stackName, infrastructure: 'preserve', checkedAt: new Date().toISOString(),
|
|
1443
|
+
digest: null, targets: [], deferred: ['Application startup and application health; framework setup and migrations are forbidden in preserve mode'] };
|
|
1444
|
+
try {
|
|
1445
|
+
const config = this.getLoadedConfig(project);
|
|
1446
|
+
if (!config)
|
|
1447
|
+
throw Error('Project configuration is not loaded');
|
|
1448
|
+
const stack = this.resolveStacks(config, project)[stackName];
|
|
1449
|
+
if (!stack)
|
|
1450
|
+
throw Error('Stack is not declared');
|
|
1451
|
+
this.assertStartAuthority({ project, stack: stackName }, stack);
|
|
1452
|
+
const local = stack.type === 'local' && !stack.managedOrigin;
|
|
1453
|
+
const prior = local ? this.localSelection(project, stackName).read().release : null;
|
|
1454
|
+
const id = options.release ?? (options.workingTree ? undefined : prior?.id);
|
|
1455
|
+
report.selection = id ? { release: id } : { workingTree: true };
|
|
1456
|
+
const release = id ? await this.admitRelease(project, stackName, stack, config, id) : null;
|
|
1457
|
+
if (!release && stack.release?.mode === 'required')
|
|
1458
|
+
throw Error('A verified release is required');
|
|
1459
|
+
if (release && prior && prior.id === id && prior.manifestSha256 !== release.manifestSha256)
|
|
1460
|
+
throw Error('Selected release changed');
|
|
1461
|
+
const installation = local && release ? await this.installedRelease(project, stackName, stack, config, release, false) : undefined;
|
|
1462
|
+
const entries = installation?.entries ?? this.resolveStackApps(stack, config);
|
|
1463
|
+
resolveStartupOrder(entries);
|
|
1464
|
+
const definitions = release?.remoteDefinitions ?? this.preservationDefinitions(project, stackName, entries);
|
|
1465
|
+
const environment = await this.stackEnvFor(project, stackName, stack, entries.map(entry => entry.name));
|
|
1466
|
+
const effective = mergeInferredInfrastructure(buildInfraFromRequirements(requirementsFromDefinitions(definitions)), installation?.infrastructure ?? mergeInfrastructure(config, stack));
|
|
1467
|
+
const normalized = normalizeInfraConfig(effective ?? {}, createDefaultRegistry());
|
|
1468
|
+
const requirements = { ...normalized };
|
|
1469
|
+
for (const definition of definitions.values())
|
|
1470
|
+
Object.assign(requirements, definition.omnitronConfig?.infrastructure);
|
|
1471
|
+
if (Object.entries(requirements).some(([name, requirement]) => {
|
|
1472
|
+
const binding = bindService(requirement, stack.serviceOverrides?.[name]);
|
|
1473
|
+
return binding.provisioning === 'bareMetal' || binding.provisioning === 'none';
|
|
1474
|
+
}))
|
|
1475
|
+
throw Error('This infrastructure resource has no verifiable native preservation identity');
|
|
1476
|
+
if (stack.type === 'local') {
|
|
1477
|
+
const infrastructure = await this.infraManager.reviewPreservation(project, stackName, stack, effective ?? {}, definitions, this.registry.get(project).path);
|
|
1478
|
+
report.targets.push({ node: 'local', infrastructure });
|
|
1479
|
+
}
|
|
1480
|
+
else {
|
|
1481
|
+
const services = await this.collectDeclaredServices(project, stack, config, { definitions });
|
|
1482
|
+
for (const { node } of resolveNodePlacements(stack, entries)) {
|
|
1483
|
+
const infrastructure = await this.reviewNodeInfrastructure(node, mergeInfrastructure(config, stack) ?? {}, services, { project, stack: stackName, overrides: await this.resolveOverrideSecrets(project, stack.serviceOverrides) }, this.registry.get(project).path, release?.staticsDir ?? undefined);
|
|
1484
|
+
report.targets.push({ node: stackNodeKey(node.host, node.port ?? 9700), infrastructure });
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
// Bind launch bytes as well as declarations; release installation verification binds the full payload closure.
|
|
1488
|
+
const launch = stack.type === 'local' ? entries.map(entry => {
|
|
1489
|
+
const script = entry.bootstrap ?? entry.script;
|
|
1490
|
+
return script ? [entry.name, createHash('sha256').update(fs.readFileSync(resolveAppPath(this.registry.get(project).path, entry, script))).digest('hex')] : [entry.name];
|
|
1491
|
+
}) : [];
|
|
1492
|
+
if (report.targets.length && report.targets.every(target => target.infrastructure?.digest))
|
|
1493
|
+
report.digest = stackDefinitionFingerprint([
|
|
1494
|
+
'omnitron/stack-start/preserve/v1', { project, stack: stackName }, this.registry.get(project).registeredAt,
|
|
1495
|
+
stack, entries, [...definitions], environment, release?.manifestSha256 ?? null, launch,
|
|
1496
|
+
report.targets.map(target => [target.node, target.infrastructure.digest]),
|
|
1497
|
+
]);
|
|
1498
|
+
}
|
|
1499
|
+
catch {
|
|
1500
|
+
// Source errors may contain credential values. Evidence contains only reviewed fingerprints and controlled diagnostics.
|
|
1501
|
+
report.targets.push({ node: 'preflight', reason: 'Complete launch and infrastructure preservation could not be verified. Check release preparation, current declarations and recipient capability.' });
|
|
1502
|
+
}
|
|
1503
|
+
return report;
|
|
1504
|
+
}
|
|
1386
1505
|
async startStack(projectName, stackName, opts) {
|
|
1387
1506
|
if (this.resumingInstalled)
|
|
1388
1507
|
throw new Error('An installed allocation is resuming');
|
|
@@ -1395,7 +1514,19 @@ export class ProjectService extends EventEmitter {
|
|
|
1395
1514
|
throw new Error('Project definitions are being updated; retry the start');
|
|
1396
1515
|
if (opts?.workingTree && opts.release)
|
|
1397
1516
|
throw new Error('Select either a release or the working tree');
|
|
1398
|
-
|
|
1517
|
+
if (opts?.infrastructure !== undefined && !['reconcile', 'preserve'].includes(opts.infrastructure))
|
|
1518
|
+
throw Error('Invalid infrastructure start mode');
|
|
1519
|
+
const policy = this.infrastructurePolicy(projectName, stackName);
|
|
1520
|
+
if (policy && opts?.infrastructure === 'reconcile' && (opts.source === 'boot' || opts.source === 'auto-resume'))
|
|
1521
|
+
throw Error('Automatic starts cannot expand preserved infrastructure authority');
|
|
1522
|
+
const mode = opts?.infrastructure ?? policy?.mode ?? 'reconcile';
|
|
1523
|
+
const review = opts?.review ?? (mode === 'preserve' ? policy?.review : undefined);
|
|
1524
|
+
if (mode === 'preserve' && (!review || !/^[a-f0-9]{64}$/.test(review)))
|
|
1525
|
+
throw Error('Preserved infrastructure requires a successful stack start review; inspect --dry-run --infrastructure preserve');
|
|
1526
|
+
if (opts?.review && mode !== 'preserve')
|
|
1527
|
+
throw Error('A preservation review cannot authorize reconciliation');
|
|
1528
|
+
const infrastructure = { mode, review: review ?? undefined, explicitReconcile: opts?.infrastructure === 'reconcile' };
|
|
1529
|
+
const selection = JSON.stringify([opts?.release, opts?.workingTree === true, opts?.reinstall === true, infrastructure]);
|
|
1399
1530
|
const running = this.startsInFlight.get(inFlightKey);
|
|
1400
1531
|
if (running) {
|
|
1401
1532
|
this.logger.info({ project: projectName, stack: stackName }, 'This stack is already being started — joining the run in progress');
|
|
@@ -1403,7 +1534,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1403
1534
|
throw new Error('Another stack start selected different launch code; retry after it completes');
|
|
1404
1535
|
return running.promise;
|
|
1405
1536
|
}
|
|
1406
|
-
const started = this.startStackOnce(projectName, stackName, opts?.source ?? 'unknown', opts?.allowDirty === true, opts?.release, opts?.reinstall === true, opts?.workingTree === true).finally(() => {
|
|
1537
|
+
const started = this.startStackOnce(projectName, stackName, opts?.source ?? 'unknown', opts?.allowDirty === true, opts?.release, opts?.reinstall === true, opts?.workingTree === true, infrastructure).finally(() => {
|
|
1407
1538
|
this.startsInFlight.delete(inFlightKey);
|
|
1408
1539
|
});
|
|
1409
1540
|
this.startsInFlight.set(inFlightKey, { selection, promise: started });
|
|
@@ -1434,10 +1565,10 @@ export class ProjectService extends EventEmitter {
|
|
|
1434
1565
|
* deploys nothing, and the reconciler re-asks a node that does not answer
|
|
1435
1566
|
* every few minutes — rows about looking, not about changing anything.
|
|
1436
1567
|
*/
|
|
1437
|
-
async startStackOnce(projectName, stackName, source, allowDirty, releaseId, reinstall = false, workingTree = false) {
|
|
1568
|
+
async startStackOnce(projectName, stackName, source, allowDirty, releaseId, reinstall = false, workingTree = false, infrastructure) {
|
|
1438
1569
|
const known = { attach: false };
|
|
1439
1570
|
try {
|
|
1440
|
-
return await this.runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall, workingTree);
|
|
1571
|
+
return await this.runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall, workingTree, infrastructure);
|
|
1441
1572
|
}
|
|
1442
1573
|
catch (err) {
|
|
1443
1574
|
if (!known.attach) {
|
|
@@ -1449,6 +1580,8 @@ export class ProjectService extends EventEmitter {
|
|
|
1449
1580
|
error: err,
|
|
1450
1581
|
details: {
|
|
1451
1582
|
source,
|
|
1583
|
+
...(infrastructure?.mode === 'preserve' ? { infrastructureMode: 'preserve', review: infrastructure.review }
|
|
1584
|
+
: infrastructure?.explicitReconcile ? { infrastructureMode: 'reconcile' } : {}),
|
|
1452
1585
|
...(known.type ? { type: known.type } : {}),
|
|
1453
1586
|
...((releaseId ?? known.release) ? { release: releaseId ?? known.release } : {}),
|
|
1454
1587
|
...(known.commit !== undefined ? { commit: known.commit } : {}),
|
|
@@ -1458,8 +1591,10 @@ export class ProjectService extends EventEmitter {
|
|
|
1458
1591
|
throw err;
|
|
1459
1592
|
}
|
|
1460
1593
|
}
|
|
1461
|
-
async runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall = false, workingTree = false) {
|
|
1462
|
-
const config = await this.loadProjectConfig(projectName);
|
|
1594
|
+
async runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall = false, workingTree = false, infrastructure) {
|
|
1595
|
+
const config = infrastructure?.mode === 'preserve' ? this.getLoadedConfig(projectName) : await this.loadProjectConfig(projectName);
|
|
1596
|
+
if (!config)
|
|
1597
|
+
throw Error('Project configuration must be loaded before preservation');
|
|
1463
1598
|
const stacks = this.resolveStacks(config, projectName);
|
|
1464
1599
|
const stackConfig = stacks[stackName];
|
|
1465
1600
|
if (!stackConfig)
|
|
@@ -1473,6 +1608,13 @@ export class ProjectService extends EventEmitter {
|
|
|
1473
1608
|
return this.attachRemoteStack(projectName, stackName, stackConfig, source);
|
|
1474
1609
|
}
|
|
1475
1610
|
this.assertStartAuthority({ project: projectName, stack: stackName }, stackConfig);
|
|
1611
|
+
let preservation;
|
|
1612
|
+
if (infrastructure?.mode === 'preserve') {
|
|
1613
|
+
preservation = await this.reviewStackStart(projectName, stackName, { infrastructure: 'preserve', ...(releaseId && { release: releaseId }), ...(workingTree && { workingTree: true }) });
|
|
1614
|
+
if (!preservation.digest || preservation.digest !== infrastructure.review)
|
|
1615
|
+
throw Error('Stack launch or native infrastructure changed since review; no startup effects were admitted');
|
|
1616
|
+
this.setInfrastructurePolicy(projectName, stackName, preservation.digest);
|
|
1617
|
+
}
|
|
1476
1618
|
// A release, when one is named — and a stack that takes nothing else.
|
|
1477
1619
|
const local = stackConfig.type === 'local' && !stackConfig.managedOrigin;
|
|
1478
1620
|
if ((workingTree || (releaseId && stackConfig.type === 'local')) && !local)
|
|
@@ -1483,6 +1625,8 @@ export class ProjectService extends EventEmitter {
|
|
|
1483
1625
|
let release = null;
|
|
1484
1626
|
if (requested) {
|
|
1485
1627
|
known.release = requested;
|
|
1628
|
+
if (this.receiveRelease && releaseId && source === 'operator')
|
|
1629
|
+
await this.fetchMissingRelease(releaseId);
|
|
1486
1630
|
release = await this.admitRelease(projectName, stackName, stackConfig, config, requested);
|
|
1487
1631
|
if (prior?.release?.id === requested && prior.release.manifestSha256 !== release.manifestSha256)
|
|
1488
1632
|
throw new Error('The selected release manifest changed on disk; restore its original bytes');
|
|
@@ -1564,8 +1708,8 @@ export class ProjectService extends EventEmitter {
|
|
|
1564
1708
|
if (mounted.length && (!local || !release || !ownsStackInfrastructure(stackConfig)))
|
|
1565
1709
|
throw Error('Artifact volumes require a selected release on an independently owned local stack');
|
|
1566
1710
|
const installation = mounted.length && release
|
|
1567
|
-
? await this.installedRelease(projectName, stackName, stackConfig, config, release,
|
|
1568
|
-
const entries = installation?.entries ?? (local && release ? await this.installedEntries(projectName, stackName, stackConfig, config, release,
|
|
1711
|
+
? await this.installedRelease(projectName, stackName, stackConfig, config, release, !preservation) : undefined;
|
|
1712
|
+
const entries = installation?.entries ?? (local && release ? await this.installedEntries(projectName, stackName, stackConfig, config, release, !preservation) : undefined);
|
|
1569
1713
|
let localMembershipChanged = false;
|
|
1570
1714
|
if (local) {
|
|
1571
1715
|
const target = entries ?? this.resolveStackApps(stackConfig, config);
|
|
@@ -1609,7 +1753,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1609
1753
|
// wrapper returns the info untouched.
|
|
1610
1754
|
const current = await this.withRemoteAppStatuses(projectName, this.toStackInfo(projectName, stackName, stackConfig));
|
|
1611
1755
|
const down = current.apps.filter((a) => a.status !== 'online');
|
|
1612
|
-
if (down.length === 0 && !current.notUp?.length) {
|
|
1756
|
+
if (down.length === 0 && !current.notUp?.length && !preservation && !infrastructure?.explicitReconcile) {
|
|
1613
1757
|
if (existing)
|
|
1614
1758
|
existing.status = 'running';
|
|
1615
1759
|
this.logger.warn({ project: projectName, stack: stackName }, 'Stack already running');
|
|
@@ -1657,20 +1801,22 @@ export class ProjectService extends EventEmitter {
|
|
|
1657
1801
|
if (changing && plan.phase !== 'stopped')
|
|
1658
1802
|
throw new LocalReleaseStopRequired('Artifact volume selection requires the owner stop fence');
|
|
1659
1803
|
return (await this.installedRelease(projectName, stackName, stackConfig, config, release, false)).infrastructure;
|
|
1660
|
-
} : undefined);
|
|
1804
|
+
} : undefined, preservation, infrastructure?.explicitReconcile);
|
|
1661
1805
|
}
|
|
1662
1806
|
else {
|
|
1663
|
-
await this.startLocalStack(projectName, stackName, stackConfig, config, undefined, async () => { await enable(); });
|
|
1807
|
+
await this.startLocalStack(projectName, stackName, stackConfig, config, undefined, async () => { await enable(); }, undefined, undefined, preservation, infrastructure?.explicitReconcile);
|
|
1664
1808
|
}
|
|
1665
1809
|
}
|
|
1666
1810
|
else {
|
|
1667
1811
|
this.rememberStackTargets(projectName, stackName, stackConfig, config);
|
|
1668
|
-
reach = await this.startRemoteStack(projectName, stackName, stackConfig, config, release, reinstall);
|
|
1812
|
+
reach = await this.startRemoteStack(projectName, stackName, stackConfig, config, release, reinstall, preservation, infrastructure?.explicitReconcile);
|
|
1669
1813
|
}
|
|
1670
1814
|
state.status = 'running';
|
|
1671
1815
|
state.startedAt = Date.now();
|
|
1672
1816
|
// Update registry with enabled stack
|
|
1673
1817
|
this.updateEnabledStacks(projectName, stackName, true);
|
|
1818
|
+
if (infrastructure?.explicitReconcile)
|
|
1819
|
+
this.setInfrastructurePolicy(projectName, stackName, null, true);
|
|
1674
1820
|
this.markPriorStopSuperseded(projectName, stackName);
|
|
1675
1821
|
const info = this.toStackInfo(projectName, stackName, stackConfig);
|
|
1676
1822
|
// What did not come up, said in the row. A boot at 12:14 UTC on
|
|
@@ -1722,6 +1868,8 @@ export class ProjectService extends EventEmitter {
|
|
|
1722
1868
|
apps: info.apps.length,
|
|
1723
1869
|
...(notUp.length > 0 ? { notUp: notUp.join(', ') } : {}),
|
|
1724
1870
|
source,
|
|
1871
|
+
...(preservation ? { infrastructureMode: 'preserve', review: preservation.digest }
|
|
1872
|
+
: infrastructure?.explicitReconcile ? { infrastructureMode: 'reconcile' } : {}),
|
|
1725
1873
|
...(reach ? { nodes: reach.nodes, reached: reach.reached } : {}),
|
|
1726
1874
|
...(reach && reach.skipped.length > 0 ? { skipped: reach.skipped } : {}),
|
|
1727
1875
|
...(reach && (reach.notReady?.length ?? 0) > 0 ? { notReady: reach.notReady } : {}),
|
|
@@ -1962,6 +2110,8 @@ export class ProjectService extends EventEmitter {
|
|
|
1962
2110
|
if (cluster)
|
|
1963
2111
|
await cluster.election.confirmStopAuthority();
|
|
1964
2112
|
this.updateEnabledStacks(projectName, stackName, false);
|
|
2113
|
+
if (keepInfrastructure)
|
|
2114
|
+
this.setInfrastructurePolicy(projectName, stackName, null);
|
|
1965
2115
|
progress.phase = 'stopping';
|
|
1966
2116
|
progress.intentCommitted = true;
|
|
1967
2117
|
persist();
|
|
@@ -1976,13 +2126,18 @@ export class ProjectService extends EventEmitter {
|
|
|
1976
2126
|
}
|
|
1977
2127
|
}
|
|
1978
2128
|
else if (localPlan && localInventory) {
|
|
1979
|
-
|
|
1980
|
-
if (keepInfrastructure) {
|
|
2129
|
+
const verifyRetained = async () => {
|
|
1981
2130
|
const retained = await this.infraManager.inspectStopContainers(projectName, stackName);
|
|
1982
|
-
if (!isDeepStrictEqual([...retained].sort((a, b) => a.id.localeCompare(b.id)), [...localInventory.containers].sort((a, b) => a.id.localeCompare(b.id))))
|
|
2131
|
+
if (!isDeepStrictEqual([...retained].sort((a, b) => a.id.localeCompare(b.id)), [...localInventory.containers].sort((a, b) => a.id.localeCompare(b.id))))
|
|
1983
2132
|
throw new Error('Retained stack infrastructure changed; inspect before confirming this application stop');
|
|
1984
|
-
|
|
2133
|
+
};
|
|
2134
|
+
if (keepInfrastructure) {
|
|
2135
|
+
await this.infraManager.getInstance(projectName, stackName)?.holdForPreservation();
|
|
2136
|
+
await verifyRetained();
|
|
1985
2137
|
}
|
|
2138
|
+
await this.orchestrator.stopStackExecution({ operationId: progress.id, plan: localPlan });
|
|
2139
|
+
if (keepInfrastructure)
|
|
2140
|
+
await verifyRetained();
|
|
1986
2141
|
else
|
|
1987
2142
|
await this.infraManager.teardownStack(projectName, stackName, localInventory.containers);
|
|
1988
2143
|
}
|
|
@@ -2112,6 +2267,32 @@ export class ProjectService extends EventEmitter {
|
|
|
2112
2267
|
const { releasesRoot } = await import('../release/load.js');
|
|
2113
2268
|
return releasesRoot();
|
|
2114
2269
|
}
|
|
2270
|
+
receiveRelease;
|
|
2271
|
+
incomingReleases = new Map();
|
|
2272
|
+
/** A failed or unfinished local build owns its directory; a pull must never replace it. */
|
|
2273
|
+
async fetchMissingRelease(id) {
|
|
2274
|
+
if (!this.receiveRelease)
|
|
2275
|
+
return;
|
|
2276
|
+
const { assertReleaseId } = await import('../release/load.js');
|
|
2277
|
+
assertReleaseId(id);
|
|
2278
|
+
const root = await this.releaseStore();
|
|
2279
|
+
try {
|
|
2280
|
+
await fs.promises.lstat(path.join(root, id));
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
catch (error) {
|
|
2284
|
+
if (error.code !== 'ENOENT')
|
|
2285
|
+
throw error;
|
|
2286
|
+
}
|
|
2287
|
+
let operation = this.incomingReleases.get(id);
|
|
2288
|
+
if (!operation) {
|
|
2289
|
+
if (this.ownershipFrozen)
|
|
2290
|
+
throw new RecoveryBusyError('Release transfer must wait for the backend handoff');
|
|
2291
|
+
operation = this.receiveRelease(id, root).finally(() => this.incomingReleases.delete(id));
|
|
2292
|
+
this.incomingReleases.set(id, operation);
|
|
2293
|
+
}
|
|
2294
|
+
await operation;
|
|
2295
|
+
}
|
|
2115
2296
|
/** Explicit disk inspection: never performed by polling and never alters launch evidence. */
|
|
2116
2297
|
async inspectInstalledArtifacts(project, stack, catalogProject) {
|
|
2117
2298
|
const { inspectInstalledArtifacts } = await import('../release/installed-artifacts.js');
|
|
@@ -2205,8 +2386,8 @@ export class ProjectService extends EventEmitter {
|
|
|
2205
2386
|
if (!stackConfig)
|
|
2206
2387
|
throw new Error(`Stack '${stackName}' not found in project '${projectName}'`);
|
|
2207
2388
|
this.assertStartAuthority({ project: projectName, stack: stackName }, stackConfig);
|
|
2208
|
-
if (prepare &&
|
|
2209
|
-
|
|
2389
|
+
if (prepare && this.receiveRelease)
|
|
2390
|
+
await this.fetchMissingRelease(releaseId);
|
|
2210
2391
|
const release = await this.admitRelease(projectName, stackName, stackConfig, config, releaseId, 'check');
|
|
2211
2392
|
let retiring = [];
|
|
2212
2393
|
if (stackConfig.type === 'local') {
|
|
@@ -2237,6 +2418,12 @@ export class ProjectService extends EventEmitter {
|
|
|
2237
2418
|
}
|
|
2238
2419
|
catch (err) {
|
|
2239
2420
|
const { LocalReleaseNotPreparedError } = await import('../release/local-installation.js');
|
|
2421
|
+
const { MissingReleaseError } = await import('../release/load.js');
|
|
2422
|
+
if (!prepare && this.receiveRelease && err instanceof MissingReleaseError) {
|
|
2423
|
+
return {
|
|
2424
|
+
ok: false, state: 'missing', because: `Release ${releaseId} is absent from this controller. Fetch it from the configured release store and check target admission.`,
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2240
2427
|
return { ok: false, because: err.message,
|
|
2241
2428
|
...(err instanceof LocalReleaseNotPreparedError && { state: 'unprepared' }),
|
|
2242
2429
|
...(err instanceof LocalReleaseStopRequired && { state: 'requires-stop',
|
|
@@ -3262,7 +3449,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3262
3449
|
// ===========================================================================
|
|
3263
3450
|
// Private — Stack Launchers
|
|
3264
3451
|
// ===========================================================================
|
|
3265
|
-
async startLocalStack(projectName, stackName, stackConfig, ecosystemConfig, selectedEntries, enable, verifyMigrations, verifyArtifactInfrastructure) {
|
|
3452
|
+
async startLocalStack(projectName, stackName, stackConfig, ecosystemConfig, selectedEntries, enable, verifyMigrations, verifyArtifactInfrastructure, preservation, reconcile = false) {
|
|
3266
3453
|
const ownsInfrastructure = ownsStackInfrastructure(stackConfig);
|
|
3267
3454
|
const stateKey = `${projectName}/${stackName}`;
|
|
3268
3455
|
const state = this.stackStates.get(stateKey);
|
|
@@ -3273,7 +3460,8 @@ export class ProjectService extends EventEmitter {
|
|
|
3273
3460
|
// start here (project/stack-env.ts).
|
|
3274
3461
|
const stackEnv = await this.stackEnvFor(projectName, stackName, stackConfig, appEntries.map((e) => e.name));
|
|
3275
3462
|
const project = this.registry.get(projectName);
|
|
3276
|
-
const appDefinitions =
|
|
3463
|
+
const appDefinitions = preservation ? this.preservationDefinitions(projectName, stackName, appEntries)
|
|
3464
|
+
: await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig, appEntries);
|
|
3277
3465
|
// 2. Provision per-stack infrastructure (built-in + app-declared services)
|
|
3278
3466
|
// Auto-detect core services (postgres, redis, minio) from app requirements,
|
|
3279
3467
|
// then merge with explicit infrastructure config (gateway, custom services).
|
|
@@ -3283,8 +3471,13 @@ export class ProjectService extends EventEmitter {
|
|
|
3283
3471
|
throw new Error(`Bootstrap declarations unavailable for ${entry.name}`);
|
|
3284
3472
|
}
|
|
3285
3473
|
const effectiveInfra = ownsInfrastructure ? mergeInferredInfrastructure(buildInfraFromRequirements(requirementsFromDefinitions(appDefinitions)), verifyArtifactInfrastructure ? await verifyArtifactInfrastructure() : mergeInfrastructure(ecosystemConfig, stackConfig)) : undefined;
|
|
3474
|
+
if (preservation) {
|
|
3475
|
+
const current = await this.reviewStackStart(projectName, stackName, { infrastructure: 'preserve', ...preservation.selection });
|
|
3476
|
+
if (!current.digest || current.digest !== preservation.digest)
|
|
3477
|
+
throw Error('Launch, release or native infrastructure changed immediately before start');
|
|
3478
|
+
}
|
|
3286
3479
|
// Refuse missing/tampered migrators before creating or changing infrastructure.
|
|
3287
|
-
if (effectiveInfra && project) {
|
|
3480
|
+
if (effectiveInfra && project && !preservation) {
|
|
3288
3481
|
const migrations = await localMigrationTargets(requestedMigrations(normalizeInfraConfig(effectiveInfra, createDefaultRegistry())), appEntries, project.path, verifyMigrations);
|
|
3289
3482
|
const resolveMigration = this.stackEntryBuilder(projectName, stackName, stackConfig, ecosystemConfig, appDefinitions, stackEnv);
|
|
3290
3483
|
validateMigrationDestinations(migrations.map(target => {
|
|
@@ -3294,7 +3487,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3294
3487
|
}
|
|
3295
3488
|
if (effectiveInfra) {
|
|
3296
3489
|
try {
|
|
3297
|
-
const infraService = await this.infraManager.provisionStack(projectName, stackName, stackConfig, effectiveInfra, appDefinitions, project?.path);
|
|
3490
|
+
const infraService = preservation ? await this.infraManager.preserveStack(projectName, stackName, stackConfig, effectiveInfra, preservation.targets.find(target => target.node === 'local').infrastructure.digest, appDefinitions, project?.path) : await this.infraManager.provisionStack(projectName, stackName, stackConfig, effectiveInfra, appDefinitions, project?.path, reconcile);
|
|
3298
3491
|
state.infraService = infraService;
|
|
3299
3492
|
const outcome = summariseProvisioning(infraService.getDesiredServices(), infraService.getState().services);
|
|
3300
3493
|
if (!outcome.ready && !outcome.empty) {
|
|
@@ -3318,7 +3511,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3318
3511
|
// `settings.allowDegradedInfra: true` (the field is namespaced under
|
|
3319
3512
|
// settings to remain forward-compatible with stack schema).
|
|
3320
3513
|
const allowDegraded = stackConfig.settings?.['allowDegradedInfra'] === true;
|
|
3321
|
-
if (!allowDegraded) {
|
|
3514
|
+
if (!allowDegraded || preservation) {
|
|
3322
3515
|
const cleanMsg = `Stack '${projectName}/${stackName}' infrastructure provisioning failed:\n` +
|
|
3323
3516
|
` ${reason}\n\n` +
|
|
3324
3517
|
`Apps were not started. Fix the underlying infrastructure issue (Docker socket, ` +
|
|
@@ -3337,7 +3530,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3337
3530
|
// Resolve once for both migrations and application startup. Inferred service
|
|
3338
3531
|
// requirements are not permission to connect to a database or run migrations.
|
|
3339
3532
|
const appConfigBuilder = this.stackEntryBuilder(projectName, stackName, stackConfig, ecosystemConfig, appDefinitions, stackEnv);
|
|
3340
|
-
if (effectiveInfra && project) {
|
|
3533
|
+
if (effectiveInfra && project && !preservation) {
|
|
3341
3534
|
await this.runStackMigrations(projectName, stackName, this.infraManager.getNormalizedServices(projectName, stackName) ?? {}, appEntries, project.path, appConfigBuilder, verifyMigrations);
|
|
3342
3535
|
}
|
|
3343
3536
|
await enable?.(appConfigBuilder);
|
|
@@ -3399,10 +3592,10 @@ export class ProjectService extends EventEmitter {
|
|
|
3399
3592
|
* and silent about which of its seven steps was running. See
|
|
3400
3593
|
* `deploy-phases.ts`.
|
|
3401
3594
|
*/
|
|
3402
|
-
async startRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, release = null, reinstall = false) {
|
|
3595
|
+
async startRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, release = null, reinstall = false, preservation, reconcile = false) {
|
|
3403
3596
|
const phases = reportPhases(this.logger, { project: projectName, stack: stackName });
|
|
3404
3597
|
try {
|
|
3405
|
-
return await withDeploymentProgress({ project: projectName, stack: stackName, operationId: randomUUID(), ...(release && { releaseId: release.id }) }, () => this.deployRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, phases, release, reinstall));
|
|
3598
|
+
return await withDeploymentProgress({ project: projectName, stack: stackName, operationId: randomUUID(), ...(release && { releaseId: release.id }) }, () => this.deployRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, phases, release, reinstall, preservation, reconcile));
|
|
3406
3599
|
}
|
|
3407
3600
|
finally {
|
|
3408
3601
|
// Every exit, including the refusals: a reporter that outlives its
|
|
@@ -3410,7 +3603,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3410
3603
|
phases.done();
|
|
3411
3604
|
}
|
|
3412
3605
|
}
|
|
3413
|
-
async deployRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, phases, release = null, reinstall = false) {
|
|
3606
|
+
async deployRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, phases, release = null, reinstall = false, preservation, reconcile = false) {
|
|
3414
3607
|
const nodes = stackConfig.nodes ?? [];
|
|
3415
3608
|
if (nodes.length === 0) {
|
|
3416
3609
|
throw new Error(`Remote stack '${stackName}' has no nodes configured`);
|
|
@@ -3446,8 +3639,9 @@ export class ProjectService extends EventEmitter {
|
|
|
3446
3639
|
const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
|
|
3447
3640
|
const migrationRequests = requestedMigrations({ ...declaredServices,
|
|
3448
3641
|
...normalizeInfraConfig(mergeInfrastructure(ecosystemConfig, stackConfig) ?? {}, createDefaultRegistry()) });
|
|
3449
|
-
|
|
3450
|
-
|
|
3642
|
+
if (!preservation)
|
|
3643
|
+
validateMigrationOwners(migrationRequests, appEntries);
|
|
3644
|
+
const placements = orderMigrationPlacements(preservation ? [] : migrationRequests, resolveNodePlacements(stackConfig, appEntries));
|
|
3451
3645
|
// What the stack gives its apps, vault references resolved — before any
|
|
3452
3646
|
// node is touched: a key the vault does not hold refuses the deployment
|
|
3453
3647
|
// here, not after a node was provisioned (project/stack-env.ts). A remote
|
|
@@ -3569,11 +3763,28 @@ export class ProjectService extends EventEmitter {
|
|
|
3569
3763
|
targets.set(`${node.host}:${node.port ?? 9700}`, await this.targetForStackNode(node));
|
|
3570
3764
|
}
|
|
3571
3765
|
phases.enter(`leasing ${nodes.length} node(s)`);
|
|
3766
|
+
const waited = new Set();
|
|
3572
3767
|
return withNodeLeases([...targets].map(([node, target]) => ({
|
|
3573
3768
|
node,
|
|
3574
3769
|
machine: `${target.host}:${target.sshPort ?? 22}`,
|
|
3575
3770
|
run: this.deployer.leaseRunner(target),
|
|
3576
3771
|
})), `${projectName}/${stackName}`, this.logger, async (leases) => {
|
|
3772
|
+
if (preservation) {
|
|
3773
|
+
const current = await this.reviewStackStart(projectName, stackName, { infrastructure: 'preserve', ...preservation.selection });
|
|
3774
|
+
if (!current.digest || current.digest !== preservation.digest)
|
|
3775
|
+
throw Error('Preserved stack changed while acquiring deployment authority');
|
|
3776
|
+
}
|
|
3777
|
+
if (release && leases.unreachable.size) {
|
|
3778
|
+
const missing = [...leases.unreachable].map(([node, reason]) => `${node}: ${reason}`).join(', ');
|
|
3779
|
+
throw Error('Release deployment requires every selected node to be reachable before activation: ' + missing);
|
|
3780
|
+
}
|
|
3781
|
+
if (waited.size && release) {
|
|
3782
|
+
const current = await this.admitRelease(projectName, stackName, stackConfig, ecosystemConfig, release.id);
|
|
3783
|
+
if (current.manifestSha256 !== release.manifestSha256)
|
|
3784
|
+
throw Error('Release changed while waiting for the target');
|
|
3785
|
+
}
|
|
3786
|
+
for (const node of waited)
|
|
3787
|
+
this.deployer.emitProgress(node, '*', 'pending', 0, 'Node lease acquired');
|
|
3577
3788
|
// Each node's platform is held at its gateway from before the first
|
|
3578
3789
|
// step that can stop an app until the apps answer through it again
|
|
3579
3790
|
// (`maintenance-lock.ts`). A node whose deployment throws or is
|
|
@@ -3603,11 +3814,11 @@ export class ProjectService extends EventEmitter {
|
|
|
3603
3814
|
this.logger.warn({ node: nodeKey, because: master.reason }, 'Provisioning this node without a master address — it will be pulled from, not dial in');
|
|
3604
3815
|
}
|
|
3605
3816
|
await leases.confirm(nodeKey, `provisioning ${node.host}`);
|
|
3606
|
-
const lock = await this.holdForDeployment(target, projectName, stackName, stackConfig, ecosystemConfig, release, phases);
|
|
3817
|
+
const lock = preservation ? null : await this.holdForDeployment(target, projectName, stackName, stackConfig, ecosystemConfig, release, phases);
|
|
3607
3818
|
if (lock)
|
|
3608
3819
|
locks.set(nodeKey, lock);
|
|
3609
3820
|
phases.enter(`provisioning ${node.host}`);
|
|
3610
|
-
const provisioned = await this.deployer.provisionSlaveNode(target, master.host, masterPort, projectName);
|
|
3821
|
+
const provisioned = preservation ? true : await this.deployer.provisionSlaveNode(target, master.host, masterPort, projectName);
|
|
3611
3822
|
if (!provisioned) {
|
|
3612
3823
|
skipped.push(nodeKey);
|
|
3613
3824
|
this.logger.error({ node: nodeKey }, 'Failed to provision slave — skipping');
|
|
@@ -3634,7 +3845,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3634
3845
|
// Sending only the stack's own block is why a remote deployment had
|
|
3635
3846
|
// databases and no gateway, and no onion address to reach it by.
|
|
3636
3847
|
const nodeInfra = mergeInfrastructure(ecosystemConfig, stackConfig);
|
|
3637
|
-
if (nodeInfra || Object.keys(declaredServices).length > 0) {
|
|
3848
|
+
if (preservation || nodeInfra || Object.keys(declaredServices).length > 0) {
|
|
3638
3849
|
await leases.confirm(nodeKey, `bringing up infrastructure on ${node.host}`);
|
|
3639
3850
|
phases.enter(`bringing up infrastructure on ${node.host}`);
|
|
3640
3851
|
const infrastructure = await this.provisionNodeInfrastructure(connector, node, (nodeInfra ?? {}), declaredServices,
|
|
@@ -3646,8 +3857,12 @@ export class ProjectService extends EventEmitter {
|
|
|
3646
3857
|
project: projectName,
|
|
3647
3858
|
stack: stackName,
|
|
3648
3859
|
overrides: await this.resolveOverrideSecrets(projectName, stackConfig.serviceOverrides),
|
|
3649
|
-
}, project?.path, release?.staticsDir ?? undefined)
|
|
3860
|
+
}, project?.path, release?.staticsDir ?? undefined, preservation ? { mode: 'preserve', review: preservation.targets.find(preservedTarget => preservedTarget.node === nodeKey)?.infrastructure?.digest ?? undefined }
|
|
3861
|
+
: reconcile ? { mode: 'reconcile' } : undefined);
|
|
3650
3862
|
if (!infrastructure.ready) {
|
|
3863
|
+
if (release || preservation) {
|
|
3864
|
+
throw new Error(`Deployment stopped before application installation on ${nodeKey}: ${infrastructure.detail}`);
|
|
3865
|
+
}
|
|
3651
3866
|
// Not fatal: a node whose infrastructure is incomplete can still
|
|
3652
3867
|
// be looked at, and stopping here would leave the fleet in a state
|
|
3653
3868
|
// no command describes. It is said at error level with the node —
|
|
@@ -3679,7 +3894,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3679
3894
|
// the rest stay as this stack declared them, because those are the
|
|
3680
3895
|
// master's to decide and the node merely carried them out.
|
|
3681
3896
|
phases.enter(`reading credentials from ${node.host}`);
|
|
3682
|
-
const nodeCredentials = await this.readNodeCredentials(connector, node, CREDENTIALED_SERVICES.filter(
|
|
3897
|
+
const nodeCredentials = await this.readNodeCredentials(connector, node, CREDENTIALED_SERVICES.filter(service => Boolean(nodeInfra?.[service] || nodeInfra?.services?.[service] || declaredServices[service])), { project: projectName, stack: stackName });
|
|
3683
3898
|
const deployedInfra = overlayCredentials(nodeInfra, nodeCredentials);
|
|
3684
3899
|
// Resolved as a LOCAL stack, because from the node's side these
|
|
3685
3900
|
// services are: containers on that machine, ports on its loopback. The
|
|
@@ -3712,7 +3927,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3712
3927
|
const results = await this.deployer.deployToStack([target], nodeArtifacts, projectName, {
|
|
3713
3928
|
apps: nodeApps,
|
|
3714
3929
|
appEnv,
|
|
3715
|
-
migrations: migrationRequests.filter(request => selectedNames.has(request.app)),
|
|
3930
|
+
migrations: preservation ? [] : migrationRequests.filter(request => selectedNames.has(request.app)),
|
|
3716
3931
|
stack: stackName,
|
|
3717
3932
|
// Past the node's record of what each app runs: ship and restart all.
|
|
3718
3933
|
...(reinstall ? { force: true } : {}),
|
|
@@ -3775,6 +3990,16 @@ export class ProjectService extends EventEmitter {
|
|
|
3775
3990
|
await lock.lift('failed').catch((err) => this.logger.error({ node: nodeKey, err: err.message }, 'Could not lift maintenance — it expires by itself'));
|
|
3776
3991
|
}
|
|
3777
3992
|
}
|
|
3993
|
+
}, undefined, {
|
|
3994
|
+
seconds: stackConfig.settings?.deploymentLeaseWaitSeconds ?? (release ? 300 : 0),
|
|
3995
|
+
onWait: (node, holder, remaining) => {
|
|
3996
|
+
waited.add(node);
|
|
3997
|
+
this.deployer.emitProgress(node, '*', 'waiting', 0, `Waiting for ${holder} on this node · ${remaining}s remaining`);
|
|
3998
|
+
},
|
|
3999
|
+
}).catch((error) => {
|
|
4000
|
+
for (const node of waited)
|
|
4001
|
+
this.deployer.emitProgress(node, '*', 'failed', 0, describeFailure(error));
|
|
4002
|
+
throw error;
|
|
3778
4003
|
});
|
|
3779
4004
|
}
|
|
3780
4005
|
/**
|
|
@@ -3953,12 +4178,12 @@ export class ProjectService extends EventEmitter {
|
|
|
3953
4178
|
*/
|
|
3954
4179
|
async readNodeCredentials(connector, node,
|
|
3955
4180
|
/** The services this stack declares — each must be answered for. */
|
|
3956
|
-
declared
|
|
4181
|
+
declared, scope) {
|
|
3957
4182
|
const out = {};
|
|
3958
|
-
for (const service of
|
|
4183
|
+
for (const service of declared) {
|
|
3959
4184
|
let info;
|
|
3960
4185
|
try {
|
|
3961
|
-
info = (await connector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronInfra', 'getConnectionInfo', [{ service }], { retryOnDisconnect: true }));
|
|
4186
|
+
info = (await connector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronInfra', 'getConnectionInfo', [{ service, ...scope }], { retryOnDisconnect: true }));
|
|
3962
4187
|
}
|
|
3963
4188
|
catch (err) {
|
|
3964
4189
|
throw new Error(`Could not read ${service}'s credentials from ${node.host}: ${err.message}. ` +
|
|
@@ -4122,7 +4347,7 @@ export class ProjectService extends EventEmitter {
|
|
|
4122
4347
|
/** Filled with each service whose directory was sent → that directory's absolute path here. */
|
|
4123
4348
|
shippedDirs,
|
|
4124
4349
|
/** The services the stack's apps require — each may name a directory of its own, as the stack's may. */
|
|
4125
|
-
services) {
|
|
4350
|
+
services, strict = false) {
|
|
4126
4351
|
const { readConfigDirectory } = await import('../infrastructure/config-payload.js');
|
|
4127
4352
|
const { shippedDirsOf } = await import('../infrastructure/shipped-config.js');
|
|
4128
4353
|
const fsp = await import('node:fs/promises');
|
|
@@ -4145,6 +4370,8 @@ export class ProjectService extends EventEmitter {
|
|
|
4145
4370
|
shippedDirs?.set(name, abs);
|
|
4146
4371
|
}
|
|
4147
4372
|
if (skipped.length > 0) {
|
|
4373
|
+
if (strict)
|
|
4374
|
+
throw Error('Complete service configuration is required for preservation');
|
|
4148
4375
|
// Said out loud: a file left behind changes what the service does,
|
|
4149
4376
|
// and silence here would make the node's behaviour unexplainable
|
|
4150
4377
|
// from the master.
|
|
@@ -4152,6 +4379,8 @@ export class ProjectService extends EventEmitter {
|
|
|
4152
4379
|
}
|
|
4153
4380
|
}
|
|
4154
4381
|
catch (err) {
|
|
4382
|
+
if (strict)
|
|
4383
|
+
throw Error('Service configuration cannot be verified for preservation', { cause: err });
|
|
4155
4384
|
// Not fatal. A stack whose gateway config is missing still gets its
|
|
4156
4385
|
// databases, and the gateway fails visibly rather than the whole
|
|
4157
4386
|
// provisioning stopping before anything was done.
|
|
@@ -4160,6 +4389,12 @@ export class ProjectService extends EventEmitter {
|
|
|
4160
4389
|
}
|
|
4161
4390
|
return payload;
|
|
4162
4391
|
}
|
|
4392
|
+
async reviewNodeInfrastructure(node, infrastructure, services, owner, projectRoot, releasedStatics) {
|
|
4393
|
+
const result = await this.provisionNodeInfrastructure(this.getSlaveConnector(), node, infrastructure, services, owner, projectRoot, releasedStatics, { mode: 'review' });
|
|
4394
|
+
if (!result.preservation || result.preservation.protocol !== 1 || result.preservation.mode !== 'preserve')
|
|
4395
|
+
throw Error('Recipient does not confirm infrastructure preservation capability; update or inspect the node before recovery');
|
|
4396
|
+
return result.preservation;
|
|
4397
|
+
}
|
|
4163
4398
|
async provisionNodeInfrastructure(connector, node, infrastructure, services, owner,
|
|
4164
4399
|
/**
|
|
4165
4400
|
* This project's root on the MASTER, for reading the config files a
|
|
@@ -4174,7 +4409,7 @@ export class ProjectService extends EventEmitter {
|
|
|
4174
4409
|
*/
|
|
4175
4410
|
projectRoot,
|
|
4176
4411
|
/** A release's static bundle, shipped as it was built — see `shipStackStatics`. */
|
|
4177
|
-
releasedStatics) {
|
|
4412
|
+
releasedStatics, authority) {
|
|
4178
4413
|
const host = node.host;
|
|
4179
4414
|
const port = node.port ?? 9700;
|
|
4180
4415
|
await connector.addSlave({ host, port, label: node.label });
|
|
@@ -4187,7 +4422,8 @@ export class ProjectService extends EventEmitter {
|
|
|
4187
4422
|
}
|
|
4188
4423
|
try {
|
|
4189
4424
|
const shippedDirs = new Map();
|
|
4190
|
-
const
|
|
4425
|
+
const preserving = authority?.mode === 'review' || authority?.mode === 'preserve';
|
|
4426
|
+
const configFiles = projectRoot ? await this.readStackConfigFiles(infrastructure, projectRoot, shippedDirs, services, preserving) : {};
|
|
4191
4427
|
// A service's bind mounts inside its shipped directory name the node's
|
|
4192
4428
|
// copy, not this machine's path — the stack's own services and the ones
|
|
4193
4429
|
// its apps require alike. See `shipped-config.ts`.
|
|
@@ -4200,16 +4436,31 @@ export class ProjectService extends EventEmitter {
|
|
|
4200
4436
|
// by SSH rather than inside the call: 32 MB is the wrong size for an
|
|
4201
4437
|
// RPC argument, which is held whole on both sides and blocks the call
|
|
4202
4438
|
// it rides on. Config files are 65 KB and ride along; a build does not.
|
|
4203
|
-
const shipped = projectRoot
|
|
4439
|
+
const shipped = projectRoot && !preserving
|
|
4204
4440
|
? await this.shipStackStatics(infrastructure, projectRoot, node, releasedStatics, owner)
|
|
4205
4441
|
: { dirs: {}, roots: {} };
|
|
4206
4442
|
const staticRoots = shipped.dirs;
|
|
4207
4443
|
const staticLinkRoots = shipped.roots;
|
|
4444
|
+
const staticDigests = {};
|
|
4445
|
+
if (preserving && projectRoot && this.staticDirOf(infrastructure, projectRoot)) {
|
|
4446
|
+
const { releaseTreeDigest } = await import('../release/runtime-provenance.js');
|
|
4447
|
+
staticDigests['gateway'] = await releaseTreeDigest(releasedStatics ?? this.staticDirOf(infrastructure, projectRoot), true);
|
|
4448
|
+
}
|
|
4208
4449
|
// Retry once if the connection turns out to be gone: the deployer just
|
|
4209
4450
|
// restarted this node's daemon, so the mesh connection the master holds
|
|
4210
4451
|
// was established to the process that exited. `provisionStack` is a
|
|
4211
4452
|
// reconciler, so applying it twice is applying it once.
|
|
4212
|
-
const report = (await connector.invokeOnSlave(host, port, 'OmnitronInfra', 'provisionStack', [{ config: configForNode, services: servicesForNode, ...(owner ?? {}), configFiles, staticRoots, staticLinkRoots
|
|
4453
|
+
const report = (await connector.invokeOnSlave(host, port, 'OmnitronInfra', authority?.mode === 'review' ? 'reviewStack' : 'provisionStack', [{ config: configForNode, services: servicesForNode, ...(owner ?? {}), configFiles, staticRoots, staticLinkRoots,
|
|
4454
|
+
...(preserving ? { infrastructure: 'preserve', staticDigests, ...(authority?.review && { review: authority.review }) }
|
|
4455
|
+
: authority?.mode === 'reconcile' ? { infrastructure: 'reconcile' } : {}) }], { retryOnDisconnect: true }));
|
|
4456
|
+
const preservation = authority?.mode === 'review' ? report : report?.preservation;
|
|
4457
|
+
if (preserving) {
|
|
4458
|
+
if (preservation?.protocol !== 1 || preservation.mode !== 'preserve' || !Array.isArray(preservation.checks) || !Array.isArray(preservation.containers))
|
|
4459
|
+
throw Error('Recipient does not implement the preservation protocol');
|
|
4460
|
+
if (authority?.mode === 'preserve' && (!preservation.digest || preservation.digest !== authority.review))
|
|
4461
|
+
throw Error('Recipient did not confirm the reviewed infrastructure identity');
|
|
4462
|
+
return { ready: preservation.digest !== null, detail: 'Native infrastructure preservation checked', preservation };
|
|
4463
|
+
}
|
|
4213
4464
|
this.logger.info({
|
|
4214
4465
|
node: `${host}:${port}`,
|
|
4215
4466
|
ready: report?.ready ?? false,
|
|
@@ -4226,6 +4477,8 @@ export class ProjectService extends EventEmitter {
|
|
|
4226
4477
|
// touched, and going on would put new backends behind the old frontend.
|
|
4227
4478
|
if (err instanceof FrontendNotDeliveredError)
|
|
4228
4479
|
throw err;
|
|
4480
|
+
if (authority?.mode === 'review' || authority?.mode === 'preserve')
|
|
4481
|
+
return { ready: false, detail: 'Recipient preservation capability or complete native identity could not be verified; no provisioning fallback is permitted' };
|
|
4229
4482
|
this.logger.error({ node: `${host}:${port}`, error: err.message }, 'Could not bring up this node\'s infrastructure');
|
|
4230
4483
|
return { ready: false, detail: `could not ask the node: ${err.message}` };
|
|
4231
4484
|
}
|
|
@@ -4389,7 +4642,7 @@ export class ProjectService extends EventEmitter {
|
|
|
4389
4642
|
if (entry.bootstrap && project) {
|
|
4390
4643
|
try {
|
|
4391
4644
|
const bootstrapAbsPath = resolveAppPath(project.path, entry, entry.bootstrap);
|
|
4392
|
-
const { definition: loaded, metadata } = await readBootstrapMetadata(bootstrapAbsPath);
|
|
4645
|
+
const { definition: loaded, metadata } = await readBootstrapMetadata(bootstrapAbsPath, false, 30_000, entry.releaseRoot);
|
|
4393
4646
|
this.bootstrapMetadata.save(metadata);
|
|
4394
4647
|
// Preserve only declarative data across backend handoffs, never executable hooks.
|
|
4395
4648
|
this.bootstrapTopology.set(bootstrapAbsPath, structuredClone({ ...(loaded.requires ? { requires: loaded.requires } : {}), ...(loaded.omnitronConfig ? { omnitronConfig: loaded.omnitronConfig } : {}) }));
|
|
@@ -4456,37 +4709,34 @@ export class ProjectService extends EventEmitter {
|
|
|
4456
4709
|
throw new Error('Project is not registered');
|
|
4457
4710
|
return new LocalReleaseSelection(this.daemonStateStore, { project, stack }, registration.registeredAt);
|
|
4458
4711
|
}
|
|
4459
|
-
async installedEntries(project, stack, definition, config, release, prepare = false,
|
|
4460
|
-
return (await this.installedRelease(project, stack, definition, config, release, prepare,
|
|
4712
|
+
async installedEntries(project, stack, definition, config, release, prepare = false, requiredApps, requiredFiles) {
|
|
4713
|
+
return (await this.installedRelease(project, stack, definition, config, release, prepare, requiredApps, requiredFiles)).entries;
|
|
4461
4714
|
}
|
|
4462
|
-
async installedRelease(project, stack, definition, config, release, prepare = false,
|
|
4715
|
+
async installedRelease(project, stack, definition, config, release, prepare = false, requiredApps, requiredFiles, admittedOwner) {
|
|
4463
4716
|
const root = this.registry.get(project)?.path;
|
|
4464
4717
|
if (!root)
|
|
4465
4718
|
throw new Error('Project is not registered');
|
|
4466
4719
|
const { inspectLocalReleaseInstallation, inspectLocalReleasePayloads } = await import('../release/local-installation.js');
|
|
4467
4720
|
const stackEntries = this.resolveStackApps(definition, config);
|
|
4468
4721
|
// Validate the complete stack before narrowing executable verification to
|
|
4469
|
-
//
|
|
4722
|
+
// requested executable owners. Other applications still own their declared environment.
|
|
4470
4723
|
const env = await this.stackEnvFor(project, stack, definition, stackEntries.map(entry => entry.name));
|
|
4471
|
-
const configured = stackEntries.filter(entry => !
|
|
4724
|
+
const configured = stackEntries.filter(entry => !requiredApps || requiredApps.includes(entry.name));
|
|
4472
4725
|
const entries = configured.map(entry => ({
|
|
4473
4726
|
...entry, env: { ...entry.env, ...env[entry.name] },
|
|
4474
4727
|
...(!entry.releaseFrom || entry.cwd !== undefined ? { cwd: resolveAppPath(root, entry) } : {}),
|
|
4475
4728
|
...(entry.bootstrap && { bootstrap: resolveAppPath(root, entry, entry.bootstrap) }),
|
|
4476
4729
|
}));
|
|
4477
|
-
const
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
const ownerPackage = await this.orchestrator.getExecutionOwnerPackage?.();
|
|
4481
|
-
if (!ownerPackage)
|
|
4482
|
-
throw new Error('Verified runtime origin requires an independently verified execution-owner package');
|
|
4730
|
+
const owner = admittedOwner ?? await this.orchestrator.getExecutionOwnerRuntime?.();
|
|
4731
|
+
assertResidentApplicationRuntime(owner);
|
|
4732
|
+
assertReleaseTarget(owner.target, owner.target);
|
|
4483
4733
|
const infrastructure = mergeInfrastructure(config, definition);
|
|
4484
4734
|
const payloads = artifactVolumeEntries(infrastructure, config.apps);
|
|
4485
4735
|
const roots = await inspectLocalReleasePayloads(release, payloads, prepare);
|
|
4486
4736
|
const resolved = await bindArtifactVolumes(infrastructure, roots);
|
|
4487
|
-
const installed = await inspectLocalReleaseInstallation(release, stack, entries, { prepare,
|
|
4488
|
-
|
|
4489
|
-
|
|
4737
|
+
const installed = await inspectLocalReleaseInstallation(release, stack, entries, { prepare,
|
|
4738
|
+
ownerPid: owner.execution.pid, ownerPackage: owner.root, runtime: owner.target,
|
|
4739
|
+
...(requiredApps && { requiredFiles: requiredFiles ?? Object.fromEntries(requiredApps.map(app => [app, [COMPILED_MIGRATION_ENTRY]])) }), });
|
|
4490
4740
|
const preparedArtifactVolumes = artifactVolumeBindings(infrastructure).map(([binding, value]) => {
|
|
4491
4741
|
const service = resolved?.services?.[value.service];
|
|
4492
4742
|
const mount = service && !('preset' in service) ? service.docker?.volumes?.[binding] : undefined;
|
|
@@ -4498,6 +4748,84 @@ export class ProjectService extends EventEmitter {
|
|
|
4498
4748
|
});
|
|
4499
4749
|
return { entries: installed, infrastructure: resolved, preparedArtifactVolumes };
|
|
4500
4750
|
}
|
|
4751
|
+
/** Read-only admission for an operation in an already selected, prepared release.
|
|
4752
|
+
* The returned fence repeats admission before each Xec attempt; it cannot install,
|
|
4753
|
+
* select a release, run setup, or silently adopt a different owner/registration. */
|
|
4754
|
+
async resolvePipelineRelease(target, options) {
|
|
4755
|
+
const { readInstalledReleaseReceipt } = await import('../release/installed-origin.js');
|
|
4756
|
+
const { frozenMember } = await import('../release/frozen-installation.js');
|
|
4757
|
+
const inspect = async () => {
|
|
4758
|
+
assertReleaseLoaderEnvironment(process.env);
|
|
4759
|
+
assertReleaseLoaderEnvironment(options.env);
|
|
4760
|
+
const scope = await this.resolveProjectStack({ project: target.project, stack: target.stack }, { readOnly: true });
|
|
4761
|
+
this.assertStackMutable(scope);
|
|
4762
|
+
const registered = this.registry.get(scope.project);
|
|
4763
|
+
const config = this.getLoadedConfig(scope.project);
|
|
4764
|
+
if (!registered || !config)
|
|
4765
|
+
throw Error('Release pipeline target requires a registered, loaded project');
|
|
4766
|
+
const registration = structuredClone(registered);
|
|
4767
|
+
const definition = this.resolveStacks(config, scope.project)[scope.stack];
|
|
4768
|
+
if (!definition || definition.type !== 'local' || definition.managedOrigin)
|
|
4769
|
+
throw Error('Release pipeline target requires an independently owned local stack; remote execution is unsupported');
|
|
4770
|
+
const fingerprint = () => createHash('sha256').update(JSON.stringify({
|
|
4771
|
+
definition: this.resolveStacks(this.getLoadedConfig(scope.project), scope.project)[scope.stack],
|
|
4772
|
+
config: this.getLoadedConfig(scope.project),
|
|
4773
|
+
})).digest('hex');
|
|
4774
|
+
const definitionFingerprint = fingerprint();
|
|
4775
|
+
const selection = this.localSelection(scope.project, scope.stack).read();
|
|
4776
|
+
if (!selection.release)
|
|
4777
|
+
throw Error('Release pipeline target has no selected immutable release');
|
|
4778
|
+
const owner = await this.orchestrator.getExecutionOwnerRuntime?.();
|
|
4779
|
+
assertResidentApplicationRuntime(owner);
|
|
4780
|
+
assertReleaseTarget(owner.target, owner.target);
|
|
4781
|
+
if (!owner.execution?.lifetime)
|
|
4782
|
+
throw Error('Release pipeline execution requires native owner lifetime evidence');
|
|
4783
|
+
const release = await this.admitRelease(scope.project, scope.stack, definition, config, selection.release.id, 'check');
|
|
4784
|
+
if (release.manifestSha256 !== selection.release.manifestSha256)
|
|
4785
|
+
throw Error('Selected release manifest changed on disk');
|
|
4786
|
+
const source = releaseArtifactOrigins(release.manifest, release.manifestSha256).find(item => item.app === target.app);
|
|
4787
|
+
const origin = source?.origin;
|
|
4788
|
+
const artifact = origin?.runtimeOrigin && { releaseId: origin.releaseId, manifestSha256: origin.manifestSha256,
|
|
4789
|
+
artifactSha256: origin.artifactSha256, producerApp: origin.runtimeOrigin.producerApp,
|
|
4790
|
+
projectCommit: origin.runtimeOrigin.projectCommit, frameworkCommit: origin.frameworkCommit };
|
|
4791
|
+
if (!artifact || !isDeepStrictEqual(artifact, target.artifact))
|
|
4792
|
+
throw Error('Selected release does not contain the pinned pipeline artifact');
|
|
4793
|
+
const installed = await this.installedRelease(scope.project, scope.stack, definition, config, release, false, [target.app], { [target.app]: [target.entry] }, owner);
|
|
4794
|
+
if (installed.entries.length !== 1 || !installed.entries[0]?.releaseRoot)
|
|
4795
|
+
throw Error('Pipeline application is absent from the selected stack');
|
|
4796
|
+
const entry = installed.entries[0], root = entry.releaseRoot;
|
|
4797
|
+
const file = await frozenMember(root, target.entry);
|
|
4798
|
+
const receipt = await readInstalledReleaseReceipt(root);
|
|
4799
|
+
const env = { ...Object.fromEntries(Object.entries(process.env).filter((item) => typeof item[1] === 'string')),
|
|
4800
|
+
...entry.env, ...options.env, NODE_OPTIONS: releaseOptionsForEnv(entry.env), NODE_PATH: '',
|
|
4801
|
+
PATH: path.dirname(owner.executable) + path.delimiter + '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin' };
|
|
4802
|
+
const cwd = options.cwd ?? entry.cwd ?? root;
|
|
4803
|
+
if (!path.isAbsolute(cwd) || await fs.promises.realpath(cwd) !== cwd || !(await fs.promises.stat(cwd)).isDirectory())
|
|
4804
|
+
throw Error('A release pipeline working directory must be an absolute physical directory');
|
|
4805
|
+
const command = { command: owner.executable, args: [file, ...options.args ?? []], shell: false,
|
|
4806
|
+
cwd,
|
|
4807
|
+
env: withClosureLibraryPath(env, { verifiedRoot: root }, {}) ?? env };
|
|
4808
|
+
const evidence = { artifact,
|
|
4809
|
+
consumer: origin.runtimeOrigin.consumer,
|
|
4810
|
+
installationRoot: root, treeSha256: receipt.treeSha256, entry: file, executable: owner.executable, cwd,
|
|
4811
|
+
environmentSha256: createHash('sha256').update(JSON.stringify(Object.entries(command.env).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))).digest('hex'), target: owner.target,
|
|
4812
|
+
owner: { id: owner.execution.ownerId, pid: owner.execution.pid, epoch: owner.execution.epoch,
|
|
4813
|
+
lifetime: owner.execution.lifetime, frameworkCommit: owner.commit } };
|
|
4814
|
+
const observed = await this.orchestrator.getExecutionOwnerRuntime?.();
|
|
4815
|
+
assertSameApplicationRuntime(owner, observed);
|
|
4816
|
+
assertReleaseTarget(owner.target, observed?.target);
|
|
4817
|
+
this.assertStackMutable(scope);
|
|
4818
|
+
if (!isDeepStrictEqual(registration, this.registry.get(scope.project)) ||
|
|
4819
|
+
!isDeepStrictEqual(selection, this.localSelection(scope.project, scope.stack).read()) || fingerprint() !== definitionFingerprint)
|
|
4820
|
+
throw Error('Pipeline registration, configuration or release selection changed during admission');
|
|
4821
|
+
return { command, evidence, registration, selection, scope, definition: definitionFingerprint };
|
|
4822
|
+
};
|
|
4823
|
+
const admitted = await inspect();
|
|
4824
|
+
return { command: admitted.command, evidence: admitted.evidence, verify: async () => {
|
|
4825
|
+
if (!isDeepStrictEqual(admitted, await inspect()))
|
|
4826
|
+
throw Error('Pipeline release, application environment or execution owner changed after admission');
|
|
4827
|
+
} };
|
|
4828
|
+
}
|
|
4501
4829
|
/** All consumers of executable declarations must see the same physical entry as the owner. */
|
|
4502
4830
|
async launchEntries(project, stack, definition, config, requiredMigrations, migrationFiles) {
|
|
4503
4831
|
const selected = definition.type === 'local' && !definition.managedOrigin ? this.localSelection(project, stack).read().release : null;
|
|
@@ -4866,6 +5194,7 @@ export class ProjectService extends EventEmitter {
|
|
|
4866
5194
|
const selectedRelease = config.type === 'local' && !config.managedOrigin ? this.localSelection(projectName, stackName).read().release : null;
|
|
4867
5195
|
return {
|
|
4868
5196
|
name: stackName,
|
|
5197
|
+
infrastructureMode: this.infrastructurePolicy(projectName, stackName)?.mode ?? 'reconcile',
|
|
4869
5198
|
type: config.type,
|
|
4870
5199
|
...(selectedRelease && { selectedRelease }),
|
|
4871
5200
|
status,
|
|
@@ -5034,7 +5363,26 @@ export const MASTER_PROJECT_SERVICE_PROVIDER = {
|
|
|
5034
5363
|
// deployed app is handed `<secret:…>` as a password. `audit` is here
|
|
5035
5364
|
// because two of the three callers of `startStack` never pass through the
|
|
5036
5365
|
// RPC layer that used to record it.
|
|
5037
|
-
new ProjectService(loggerModule.logger, orchestrator, daemonStateStore, {
|
|
5366
|
+
new ProjectService(loggerModule.logger, orchestrator, daemonStateStore, {
|
|
5367
|
+
secrets, audit, ...(readSavedDaemonConfig()?.releaseStore ? { receiveRelease: async (id, root) => {
|
|
5368
|
+
const { configuredReleaseStore, configuredStoreConfig } = await import('../release/configured-store.js');
|
|
5369
|
+
const { pullRelease } = await import('../release/artifact-store.js');
|
|
5370
|
+
await audit.record({ action: 'release.pull.requested', resourceType: 'release', resourceId: id }, true);
|
|
5371
|
+
const store = await configuredReleaseStore(configuredStoreConfig(), key => secrets.get(key));
|
|
5372
|
+
try {
|
|
5373
|
+
const result = await pullRelease(id, store, { root });
|
|
5374
|
+
await audit.record({ action: 'release.pull', resourceType: 'release', resourceId: id,
|
|
5375
|
+
details: { indexSha256: result.indexSha256, files: result.moved.files, bytes: result.moved.bytes } }, true);
|
|
5376
|
+
}
|
|
5377
|
+
catch (error) {
|
|
5378
|
+
await audit.record({ action: 'release.pull', resourceType: 'release', resourceId: id, outcome: 'failed', error });
|
|
5379
|
+
throw error;
|
|
5380
|
+
}
|
|
5381
|
+
finally {
|
|
5382
|
+
await store.close();
|
|
5383
|
+
}
|
|
5384
|
+
} } : {}),
|
|
5385
|
+
}),
|
|
5038
5386
|
};
|
|
5039
5387
|
/** A slave has its own encrypted vault even though it has no master database/audit service. */
|
|
5040
5388
|
export const SLAVE_PROJECT_SERVICE_PROVIDER = {
|