@omnitron-dev/omnitron 0.3.0 → 0.3.2
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 +579 -0
- package/data/operations/managed-kernel.mjs +302 -0
- package/data/operations/stage-kernel.mjs +125 -0
- package/dist/BUILD.json +4 -3
- package/dist/cli/exit-after-output.d.ts +3 -0
- package/dist/cli/exit-after-output.d.ts.map +1 -0
- package/dist/cli/exit-after-output.js +11 -0
- package/dist/cli/exit-after-output.js.map +1 -0
- package/dist/cli/installed.d.ts +3 -0
- package/dist/cli/installed.d.ts.map +1 -0
- package/dist/cli/installed.js +17 -0
- package/dist/cli/installed.js.map +1 -0
- package/dist/cli/omnitron.js +455 -99
- package/dist/cli/omnitron.js.map +1 -1
- package/dist/cli/output-command.d.ts +13 -0
- package/dist/cli/output-command.d.ts.map +1 -0
- package/dist/cli/output-command.js +48 -0
- package/dist/cli/output-command.js.map +1 -0
- package/dist/cluster/cluster.rpc-service.d.ts +9 -77
- package/dist/cluster/cluster.rpc-service.d.ts.map +1 -1
- package/dist/cluster/cluster.rpc-service.js +20 -107
- package/dist/cluster/cluster.rpc-service.js.map +1 -1
- package/dist/cluster/enrollment.d.ts +9 -0
- package/dist/cluster/enrollment.d.ts.map +1 -0
- package/dist/cluster/enrollment.js +49 -0
- package/dist/cluster/enrollment.js.map +1 -0
- package/dist/cluster/index.d.ts +1 -1
- package/dist/cluster/index.d.ts.map +1 -1
- package/dist/cluster/leader-election.d.ts +39 -211
- package/dist/cluster/leader-election.d.ts.map +1 -1
- package/dist/cluster/leader-election.js +235 -512
- package/dist/cluster/leader-election.js.map +1 -1
- package/dist/cluster/mesh-link.d.ts +18 -51
- package/dist/cluster/mesh-link.d.ts.map +1 -1
- package/dist/cluster/mesh-link.js +66 -78
- package/dist/cluster/mesh-link.js.map +1 -1
- package/dist/cluster/mesh.d.ts +3 -1
- package/dist/cluster/mesh.d.ts.map +1 -1
- package/dist/cluster/mesh.js +76 -60
- package/dist/cluster/mesh.js.map +1 -1
- package/dist/cluster/slave-connector.d.ts +42 -6
- package/dist/cluster/slave-connector.d.ts.map +1 -1
- package/dist/cluster/slave-connector.js +349 -196
- package/dist/cluster/slave-connector.js.map +1 -1
- package/dist/cluster/standalone-infrastructure.d.ts +3 -0
- package/dist/cluster/standalone-infrastructure.d.ts.map +1 -0
- package/dist/cluster/standalone-infrastructure.js +25 -0
- package/dist/cluster/standalone-infrastructure.js.map +1 -0
- package/dist/cluster/stop-authority.d.ts +45 -0
- package/dist/cluster/stop-authority.d.ts.map +1 -0
- package/dist/cluster/stop-authority.js +112 -0
- package/dist/cluster/stop-authority.js.map +1 -0
- package/dist/commands/access.d.ts +9 -0
- package/dist/commands/access.d.ts.map +1 -0
- package/dist/commands/access.js +45 -0
- package/dist/commands/access.js.map +1 -0
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +9 -4
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/backend.d.ts +17 -0
- package/dist/commands/backend.d.ts.map +1 -0
- package/dist/commands/backend.js +264 -0
- package/dist/commands/backend.js.map +1 -0
- package/dist/commands/backup.d.ts +5 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +17 -2
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cluster.d.ts.map +1 -1
- package/dist/commands/cluster.js +33 -42
- package/dist/commands/cluster.js.map +1 -1
- package/dist/commands/daemon-cmd.d.ts.map +1 -1
- package/dist/commands/daemon-cmd.js +14 -3
- package/dist/commands/daemon-cmd.js.map +1 -1
- package/dist/commands/deploy.d.ts +4 -4
- package/dist/commands/deploy.js +4 -4
- package/dist/commands/discover.d.ts +2 -2
- package/dist/commands/discover.js +2 -2
- package/dist/commands/doctor.d.ts +11 -29
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +47 -45
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/files.d.ts +19 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/files.js +226 -0
- package/dist/commands/files.js.map +1 -0
- package/dist/commands/fleet-asking.d.ts +2 -2
- package/dist/commands/fleet-asking.js +2 -2
- package/dist/commands/fleet-recover.d.ts +2 -0
- package/dist/commands/fleet-recover.d.ts.map +1 -0
- package/dist/commands/fleet-recover.js +30 -0
- package/dist/commands/fleet-recover.js.map +1 -0
- package/dist/commands/fleet-rollout.d.ts +25 -0
- package/dist/commands/fleet-rollout.d.ts.map +1 -0
- package/dist/commands/fleet-rollout.js +155 -0
- package/dist/commands/fleet-rollout.js.map +1 -0
- package/dist/commands/fleet.d.ts.map +1 -1
- package/dist/commands/fleet.js +1 -0
- package/dist/commands/fleet.js.map +1 -1
- package/dist/commands/health-check.d.ts.map +1 -1
- package/dist/commands/health-check.js +13 -6
- package/dist/commands/health-check.js.map +1 -1
- package/dist/commands/host-ingress.d.ts +3 -0
- package/dist/commands/host-ingress.d.ts.map +1 -0
- package/dist/commands/host-ingress.js +31 -0
- package/dist/commands/host-ingress.js.map +1 -0
- package/dist/commands/infra.d.ts +7 -4
- package/dist/commands/infra.d.ts.map +1 -1
- package/dist/commands/infra.js +152 -60
- package/dist/commands/infra.js.map +1 -1
- package/dist/commands/logs.d.ts +7 -24
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +117 -41
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +7 -1
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/migrate.d.ts +8 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +64 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/node-reconcile.d.ts +8 -0
- package/dist/commands/node-reconcile.d.ts.map +1 -0
- package/dist/commands/node-reconcile.js +49 -0
- package/dist/commands/node-reconcile.js.map +1 -0
- package/dist/commands/node.d.ts +1 -21
- package/dist/commands/node.d.ts.map +1 -1
- package/dist/commands/node.js +35 -27
- package/dist/commands/node.js.map +1 -1
- package/dist/commands/output.d.ts +3 -9
- package/dist/commands/output.d.ts.map +1 -1
- package/dist/commands/output.js +34 -25
- package/dist/commands/output.js.map +1 -1
- package/dist/commands/pipeline.d.ts +11 -2
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +82 -15
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/project.d.ts +10 -2
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +69 -3
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/release-store.js +1 -1
- package/dist/commands/release-store.js.map +1 -1
- package/dist/commands/release.d.ts +5 -12
- package/dist/commands/release.d.ts.map +1 -1
- package/dist/commands/release.js +144 -36
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/remote.d.ts +3 -3
- package/dist/commands/remote.js +3 -3
- package/dist/commands/scale.d.ts +1 -4
- package/dist/commands/scale.d.ts.map +1 -1
- package/dist/commands/scale.js +39 -19
- package/dist/commands/scale.js.map +1 -1
- package/dist/commands/secret-input.d.ts +3 -0
- package/dist/commands/secret-input.d.ts.map +1 -0
- package/dist/commands/secret-input.js +36 -0
- package/dist/commands/secret-input.js.map +1 -0
- package/dist/commands/secret.d.ts +4 -2
- package/dist/commands/secret.d.ts.map +1 -1
- package/dist/commands/secret.js +12 -2
- package/dist/commands/secret.js.map +1 -1
- package/dist/commands/service-resident.d.ts +25 -0
- package/dist/commands/service-resident.d.ts.map +1 -0
- package/dist/commands/service-resident.js +236 -0
- package/dist/commands/service-resident.js.map +1 -0
- package/dist/commands/service-units.d.ts +13 -1
- package/dist/commands/service-units.d.ts.map +1 -1
- package/dist/commands/service-units.js +38 -16
- package/dist/commands/service-units.js.map +1 -1
- package/dist/commands/service.d.ts +2 -24
- package/dist/commands/service.d.ts.map +1 -1
- package/dist/commands/service.js +89 -7
- package/dist/commands/service.js.map +1 -1
- package/dist/commands/stack.d.ts +11 -6
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +212 -38
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/start.d.ts +0 -21
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +33 -43
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.d.ts +1 -5
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +5 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/stop.d.ts +0 -22
- package/dist/commands/stop.d.ts.map +1 -1
- package/dist/commands/stop.js +26 -86
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/system.d.ts +4 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/system.js +41 -0
- package/dist/commands/system.js.map +1 -0
- package/dist/commands/terminal.d.ts +7 -0
- package/dist/commands/terminal.d.ts.map +1 -0
- package/dist/commands/terminal.js +184 -0
- package/dist/commands/terminal.js.map +1 -0
- package/dist/commands/topology.d.ts +3 -0
- package/dist/commands/topology.d.ts.map +1 -0
- package/dist/commands/topology.js +107 -0
- package/dist/commands/topology.js.map +1 -0
- package/dist/commands/tor.d.ts +1 -1
- package/dist/commands/tor.js +2 -2
- package/dist/commands/up.d.ts +4 -54
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +41 -98
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/webapp.d.ts +0 -9
- package/dist/commands/webapp.d.ts.map +1 -1
- package/dist/commands/webapp.js +116 -69
- package/dist/commands/webapp.js.map +1 -1
- package/dist/config/app-path.d.ts +4 -0
- package/dist/config/app-path.d.ts.map +1 -0
- package/dist/config/app-path.js +6 -0
- package/dist/config/app-path.js.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +9 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/define-ecosystem.d.ts.map +1 -1
- package/dist/config/define-ecosystem.js +12 -0
- package/dist/config/define-ecosystem.js.map +1 -1
- package/dist/config/index.d.ts +3 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/loader-child.d.ts +2 -0
- package/dist/config/loader-child.d.ts.map +1 -0
- package/dist/config/loader-child.js +56 -0
- package/dist/config/loader-child.js.map +1 -0
- package/dist/config/loader.d.ts +7 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +14 -27
- package/dist/config/loader.js.map +1 -1
- package/dist/config/saved.d.ts +67 -0
- package/dist/config/saved.d.ts.map +1 -0
- package/dist/config/saved.js +81 -0
- package/dist/config/saved.js.map +1 -0
- package/dist/config/types.d.ts +105 -58
- package/dist/config/types.d.ts.map +1 -1
- package/dist/daemon/backend-candidate.d.ts +32 -0
- package/dist/daemon/backend-candidate.d.ts.map +1 -0
- package/dist/daemon/backend-candidate.js +129 -0
- package/dist/daemon/backend-candidate.js.map +1 -0
- package/dist/daemon/backend-child.d.ts +5 -0
- package/dist/daemon/backend-child.d.ts.map +1 -0
- package/dist/daemon/backend-child.js +13 -0
- package/dist/daemon/backend-child.js.map +1 -0
- package/dist/daemon/backend-control.d.ts +43 -0
- package/dist/daemon/backend-control.d.ts.map +1 -0
- package/dist/daemon/backend-control.js +155 -0
- package/dist/daemon/backend-control.js.map +1 -0
- package/dist/daemon/backend-crash-recovery.d.ts +4 -0
- package/dist/daemon/backend-crash-recovery.d.ts.map +1 -0
- package/dist/daemon/backend-crash-recovery.js +136 -0
- package/dist/daemon/backend-crash-recovery.js.map +1 -0
- package/dist/daemon/backend-handoff-types.d.ts +27 -0
- package/dist/daemon/backend-handoff-types.d.ts.map +1 -0
- package/dist/daemon/backend-handoff-types.js +33 -0
- package/dist/daemon/backend-handoff-types.js.map +1 -0
- package/dist/daemon/backend-recovery-journal.d.ts +30 -0
- package/dist/daemon/backend-recovery-journal.d.ts.map +1 -0
- package/dist/daemon/backend-recovery-journal.js +67 -0
- package/dist/daemon/backend-recovery-journal.js.map +1 -0
- package/dist/daemon/backend-recovery-monitor.d.ts +35 -0
- package/dist/daemon/backend-recovery-monitor.d.ts.map +1 -0
- package/dist/daemon/backend-recovery-monitor.js +113 -0
- package/dist/daemon/backend-recovery-monitor.js.map +1 -0
- package/dist/daemon/backend-supervisor-entry.d.ts +2 -0
- package/dist/daemon/backend-supervisor-entry.d.ts.map +1 -0
- package/dist/daemon/backend-supervisor-entry.js +3 -0
- package/dist/daemon/backend-supervisor-entry.js.map +1 -0
- package/dist/daemon/backend-supervisor-main.d.ts +2 -0
- package/dist/daemon/backend-supervisor-main.d.ts.map +1 -0
- package/dist/daemon/backend-supervisor-main.js +45 -0
- package/dist/daemon/backend-supervisor-main.js.map +1 -0
- package/dist/daemon/backend-supervisor.d.ts +61 -0
- package/dist/daemon/backend-supervisor.d.ts.map +1 -0
- package/dist/daemon/backend-supervisor.js +339 -0
- package/dist/daemon/backend-supervisor.js.map +1 -0
- package/dist/daemon/backend-upgrade.d.ts +41 -0
- package/dist/daemon/backend-upgrade.d.ts.map +1 -0
- package/dist/daemon/backend-upgrade.js +444 -0
- package/dist/daemon/backend-upgrade.js.map +1 -0
- package/dist/daemon/backend.rpc-service.d.ts +11 -0
- package/dist/daemon/backend.rpc-service.d.ts.map +1 -0
- package/dist/daemon/backend.rpc-service.js +44 -0
- package/dist/daemon/backend.rpc-service.js.map +1 -0
- package/dist/daemon/daemon-client.d.ts +12 -79
- package/dist/daemon/daemon-client.d.ts.map +1 -1
- package/dist/daemon/daemon-client.js +28 -270
- package/dist/daemon/daemon-client.js.map +1 -1
- package/dist/daemon/daemon-entry.d.ts +0 -7
- package/dist/daemon/daemon-entry.d.ts.map +1 -1
- package/dist/daemon/daemon-entry.js +2 -115
- package/dist/daemon/daemon-entry.js.map +1 -1
- package/dist/daemon/daemon-main.d.ts +9 -0
- package/dist/daemon/daemon-main.d.ts.map +1 -0
- package/dist/daemon/daemon-main.js +105 -0
- package/dist/daemon/daemon-main.js.map +1 -0
- package/dist/daemon/daemon-scheduler.d.ts +8 -5
- package/dist/daemon/daemon-scheduler.d.ts.map +1 -1
- package/dist/daemon/daemon-scheduler.js +45 -73
- package/dist/daemon/daemon-scheduler.js.map +1 -1
- package/dist/daemon/daemon-state-store.service.d.ts +5 -39
- package/dist/daemon/daemon-state-store.service.d.ts.map +1 -1
- package/dist/daemon/daemon-state-store.service.js +30 -17
- package/dist/daemon/daemon-state-store.service.js.map +1 -1
- package/dist/daemon/daemon.d.ts +50 -79
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +769 -442
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/daemon.module.d.ts +13 -11
- package/dist/daemon/daemon.module.d.ts.map +1 -1
- package/dist/daemon/daemon.module.js +139 -106
- package/dist/daemon/daemon.module.js.map +1 -1
- package/dist/daemon/daemon.rpc-service.d.ts +11 -19
- package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
- package/dist/daemon/daemon.rpc-service.js +89 -28
- package/dist/daemon/daemon.rpc-service.js.map +1 -1
- package/dist/daemon/darwin-owner-evidence.d.ts +57 -0
- package/dist/daemon/darwin-owner-evidence.d.ts.map +1 -0
- package/dist/daemon/darwin-owner-evidence.js +131 -0
- package/dist/daemon/darwin-owner-evidence.js.map +1 -0
- package/dist/daemon/database-startup.d.ts +7 -0
- package/dist/daemon/database-startup.d.ts.map +1 -0
- package/dist/daemon/database-startup.js +40 -0
- package/dist/daemon/database-startup.js.map +1 -0
- package/dist/daemon/entry-guard.d.ts +2 -0
- package/dist/daemon/entry-guard.d.ts.map +1 -0
- package/dist/daemon/entry-guard.js +34 -0
- package/dist/daemon/entry-guard.js.map +1 -0
- package/dist/daemon/execution-capabilities.d.ts +7 -0
- package/dist/daemon/execution-capabilities.d.ts.map +1 -0
- package/dist/daemon/execution-capabilities.js +23 -0
- package/dist/daemon/execution-capabilities.js.map +1 -0
- package/dist/daemon/execution-client.d.ts +88 -0
- package/dist/daemon/execution-client.d.ts.map +1 -0
- package/dist/daemon/execution-client.js +433 -0
- package/dist/daemon/execution-client.js.map +1 -0
- package/dist/daemon/execution-config.d.ts +7 -0
- package/dist/daemon/execution-config.d.ts.map +1 -0
- package/dist/daemon/execution-config.js +61 -0
- package/dist/daemon/execution-config.js.map +1 -0
- package/dist/daemon/execution-entry.d.ts +2 -0
- package/dist/daemon/execution-entry.d.ts.map +1 -0
- package/dist/daemon/execution-entry.js +3 -0
- package/dist/daemon/execution-entry.js.map +1 -0
- package/dist/daemon/execution-host.d.ts +133 -0
- package/dist/daemon/execution-host.d.ts.map +1 -0
- package/dist/daemon/execution-host.js +308 -0
- package/dist/daemon/execution-host.js.map +1 -0
- package/dist/daemon/execution-journal.d.ts +17 -0
- package/dist/daemon/execution-journal.d.ts.map +1 -0
- package/dist/daemon/execution-journal.js +75 -0
- package/dist/daemon/execution-journal.js.map +1 -0
- package/dist/daemon/execution-launcher.d.ts +12 -0
- package/dist/daemon/execution-launcher.d.ts.map +1 -0
- package/dist/daemon/execution-launcher.js +110 -0
- package/dist/daemon/execution-launcher.js.map +1 -0
- package/dist/daemon/execution-main.d.ts +2 -0
- package/dist/daemon/execution-main.d.ts.map +1 -0
- package/dist/daemon/execution-main.js +48 -0
- package/dist/daemon/execution-main.js.map +1 -0
- package/dist/daemon/execution-owner.d.ts +16 -0
- package/dist/daemon/execution-owner.d.ts.map +1 -0
- package/dist/daemon/execution-owner.js +97 -0
- package/dist/daemon/execution-owner.js.map +1 -0
- package/dist/daemon/execution-platform.d.ts +3 -0
- package/dist/daemon/execution-platform.d.ts.map +1 -0
- package/dist/daemon/execution-platform.js +23 -0
- package/dist/daemon/execution-platform.js.map +1 -0
- package/dist/daemon/execution-shutdown.d.ts +13 -0
- package/dist/daemon/execution-shutdown.d.ts.map +1 -0
- package/dist/daemon/execution-shutdown.js +30 -0
- package/dist/daemon/execution-shutdown.js.map +1 -0
- package/dist/daemon/managed-entry.d.ts +2 -0
- package/dist/daemon/managed-entry.d.ts.map +1 -0
- package/dist/daemon/managed-entry.js +3 -0
- package/dist/daemon/managed-entry.js.map +1 -0
- package/dist/daemon/managed-main.d.ts +2 -0
- package/dist/daemon/managed-main.d.ts.map +1 -0
- package/dist/daemon/managed-main.js +29 -0
- package/dist/daemon/managed-main.js.map +1 -0
- package/dist/daemon/managed-owner-build.d.ts +17 -0
- package/dist/daemon/managed-owner-build.d.ts.map +1 -0
- package/dist/daemon/managed-owner-build.js +108 -0
- package/dist/daemon/managed-owner-build.js.map +1 -0
- package/dist/daemon/managed-owner-entry.d.ts +2 -0
- package/dist/daemon/managed-owner-entry.d.ts.map +1 -0
- package/dist/daemon/managed-owner-entry.js +3 -0
- package/dist/daemon/managed-owner-entry.js.map +1 -0
- package/dist/daemon/managed-owner-main.d.ts +2 -0
- package/dist/daemon/managed-owner-main.d.ts.map +1 -0
- package/dist/daemon/managed-owner-main.js +53 -0
- package/dist/daemon/managed-owner-main.js.map +1 -0
- package/dist/daemon/managed-runtime.d.ts +39 -0
- package/dist/daemon/managed-runtime.d.ts.map +1 -0
- package/dist/daemon/managed-runtime.js +137 -0
- package/dist/daemon/managed-runtime.js.map +1 -0
- package/dist/daemon/network-publication.d.ts +5 -0
- package/dist/daemon/network-publication.d.ts.map +1 -0
- package/dist/daemon/network-publication.js +25 -0
- package/dist/daemon/network-publication.js.map +1 -0
- package/dist/daemon/owner-reconciliation.d.ts +58 -0
- package/dist/daemon/owner-reconciliation.d.ts.map +1 -0
- package/dist/daemon/owner-reconciliation.js +265 -0
- package/dist/daemon/owner-reconciliation.js.map +1 -0
- package/dist/daemon/pid-manager.d.ts +2 -0
- package/dist/daemon/pid-manager.d.ts.map +1 -1
- package/dist/daemon/pid-manager.js +5 -0
- package/dist/daemon/pid-manager.js.map +1 -1
- package/dist/daemon/private-control-client.d.ts +8 -0
- package/dist/daemon/private-control-client.d.ts.map +1 -0
- package/dist/daemon/private-control-client.js +41 -0
- package/dist/daemon/private-control-client.js.map +1 -0
- package/dist/daemon/recovery-state.d.ts +20 -0
- package/dist/daemon/recovery-state.d.ts.map +1 -0
- package/dist/daemon/recovery-state.js +53 -0
- package/dist/daemon/recovery-state.js.map +1 -0
- package/dist/daemon/rpc-connection.d.ts +29 -0
- package/dist/daemon/rpc-connection.d.ts.map +1 -0
- package/dist/daemon/rpc-connection.js +137 -0
- package/dist/daemon/rpc-connection.js.map +1 -0
- package/dist/daemon/runtime-installation.d.ts +58 -0
- package/dist/daemon/runtime-installation.d.ts.map +1 -0
- package/dist/daemon/runtime-installation.js +346 -0
- package/dist/daemon/runtime-installation.js.map +1 -0
- package/dist/daemon/runtime-launcher.d.ts +6 -0
- package/dist/daemon/runtime-launcher.d.ts.map +1 -0
- package/dist/daemon/runtime-launcher.js +45 -0
- package/dist/daemon/runtime-launcher.js.map +1 -0
- package/dist/daemon/schema-transition.d.ts +43 -0
- package/dist/daemon/schema-transition.d.ts.map +1 -0
- package/dist/daemon/schema-transition.js +239 -0
- package/dist/daemon/schema-transition.js.map +1 -0
- package/dist/daemon/spawn-daemon.d.ts +2 -0
- package/dist/daemon/spawn-daemon.d.ts.map +1 -1
- package/dist/daemon/spawn-daemon.js +4 -0
- package/dist/daemon/spawn-daemon.js.map +1 -1
- package/dist/daemon/startup-config.d.ts +77 -0
- package/dist/daemon/startup-config.d.ts.map +1 -0
- package/dist/daemon/startup-config.js +31 -0
- package/dist/daemon/startup-config.js.map +1 -0
- package/dist/daemon/startup-failure-journal.d.ts +3 -0
- package/dist/daemon/startup-failure-journal.d.ts.map +1 -0
- package/dist/daemon/startup-failure-journal.js +29 -0
- package/dist/daemon/startup-failure-journal.js.map +1 -0
- package/dist/daemon/state-store.d.ts +16 -24
- package/dist/daemon/state-store.d.ts.map +1 -1
- package/dist/daemon/state-store.js +51 -9
- package/dist/daemon/state-store.js.map +1 -1
- package/dist/daemon/supervisor-transition.d.ts +101 -0
- package/dist/daemon/supervisor-transition.d.ts.map +1 -0
- package/dist/daemon/supervisor-transition.js +503 -0
- package/dist/daemon/supervisor-transition.js.map +1 -0
- package/dist/daemon/system-guardian-cgroup.d.ts +16 -0
- package/dist/daemon/system-guardian-cgroup.d.ts.map +1 -0
- package/dist/daemon/system-guardian-cgroup.js +202 -0
- package/dist/daemon/system-guardian-cgroup.js.map +1 -0
- package/dist/daemon/system-guardian-unit.d.ts +9 -0
- package/dist/daemon/system-guardian-unit.d.ts.map +1 -0
- package/dist/daemon/system-guardian-unit.js +41 -0
- package/dist/daemon/system-guardian-unit.js.map +1 -0
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/connection.js +2 -6
- package/dist/database/connection.js.map +1 -1
- package/dist/database/log-durability.d.ts +5 -0
- package/dist/database/log-durability.d.ts.map +1 -0
- package/dist/database/log-durability.js +28 -0
- package/dist/database/log-durability.js.map +1 -0
- package/dist/database/migrate.js +2 -15
- package/dist/database/migrate.js.map +1 -1
- package/dist/database/migration-registry-compatibility.d.ts +13 -0
- package/dist/database/migration-registry-compatibility.d.ts.map +1 -0
- package/dist/database/migration-registry-compatibility.js +108 -0
- package/dist/database/migration-registry-compatibility.js.map +1 -0
- package/dist/database/migration-runner.d.ts +8 -42
- package/dist/database/migration-runner.d.ts.map +1 -1
- package/dist/database/migration-runner.js +114 -123
- package/dist/database/migration-runner.js.map +1 -1
- package/dist/database/migrations/014_replication_receipts.d.ts +5 -0
- package/dist/database/migrations/014_replication_receipts.d.ts.map +1 -0
- package/dist/database/migrations/014_replication_receipts.js +6 -0
- package/dist/database/migrations/014_replication_receipts.js.map +1 -0
- package/dist/database/migrations/015_persistent_metrics.d.ts +5 -0
- package/dist/database/migrations/015_persistent_metrics.d.ts.map +1 -0
- package/dist/database/migrations/015_persistent_metrics.js +7 -0
- package/dist/database/migrations/015_persistent_metrics.js.map +1 -0
- package/dist/database/migrations/016_cluster_lease.d.ts +4 -0
- package/dist/database/migrations/016_cluster_lease.d.ts.map +1 -0
- package/dist/database/migrations/016_cluster_lease.js +6 -0
- package/dist/database/migrations/016_cluster_lease.js.map +1 -0
- package/dist/database/migrations/index.d.ts +1 -1
- package/dist/database/migrations/index.d.ts.map +1 -1
- package/dist/database/migrations/index.js +22 -13
- package/dist/database/migrations/index.js.map +1 -1
- package/dist/database/schema-compatibility.d.ts +34 -0
- package/dist/database/schema-compatibility.d.ts.map +1 -0
- package/dist/database/schema-compatibility.js +109 -0
- package/dist/database/schema-compatibility.js.map +1 -0
- package/dist/database/schema-expansion-policy.d.ts +10 -0
- package/dist/database/schema-expansion-policy.d.ts.map +1 -0
- package/dist/database/schema-expansion-policy.js +21 -0
- package/dist/database/schema-expansion-policy.js.map +1 -0
- package/dist/database/schema-transition-plan.d.ts +53 -0
- package/dist/database/schema-transition-plan.d.ts.map +1 -0
- package/dist/database/schema-transition-plan.js +102 -0
- package/dist/database/schema-transition-plan.js.map +1 -0
- package/dist/database/schema-worker.d.ts +2 -0
- package/dist/database/schema-worker.d.ts.map +1 -0
- package/dist/database/schema-worker.js +55 -0
- package/dist/database/schema-worker.js.map +1 -0
- package/dist/database/schema.d.ts +6 -1
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/slave-migrations/001_replication_evidence.d.ts +4 -0
- package/dist/database/slave-migrations/001_replication_evidence.d.ts.map +1 -0
- package/dist/database/slave-migrations/001_replication_evidence.js +89 -0
- package/dist/database/slave-migrations/001_replication_evidence.js.map +1 -0
- package/dist/database/slave-migrations/index.d.ts +6 -0
- package/dist/database/slave-migrations/index.d.ts.map +1 -0
- package/dist/database/slave-migrations/index.js +10 -0
- package/dist/database/slave-migrations/index.js.map +1 -0
- package/dist/diagnostics/index.d.ts +16 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +22 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/execution/command-failure.d.ts +7 -0
- package/dist/execution/command-failure.d.ts.map +1 -0
- package/dist/execution/command-failure.js +29 -0
- package/dist/execution/command-failure.js.map +1 -0
- package/dist/execution/data-channel.d.ts +1 -1
- package/dist/execution/data-channel.js +1 -1
- package/dist/execution/execution.service.d.ts +16 -4
- package/dist/execution/execution.service.d.ts.map +1 -1
- package/dist/execution/execution.service.js +133 -74
- package/dist/execution/execution.service.js.map +1 -1
- package/dist/files/archive-codec.d.ts +57 -0
- package/dist/files/archive-codec.d.ts.map +1 -0
- package/dist/files/archive-codec.js +454 -0
- package/dist/files/archive-codec.js.map +1 -0
- package/dist/files/archive-node.d.ts +27 -0
- package/dist/files/archive-node.d.ts.map +1 -0
- package/dist/files/archive-node.js +379 -0
- package/dist/files/archive-node.js.map +1 -0
- package/dist/files/archive-publication.d.ts +26 -0
- package/dist/files/archive-publication.d.ts.map +1 -0
- package/dist/files/archive-publication.js +224 -0
- package/dist/files/archive-publication.js.map +1 -0
- package/dist/files/file-config.d.ts +7 -0
- package/dist/files/file-config.d.ts.map +1 -0
- package/dist/files/file-config.js +11 -0
- package/dist/files/file-config.js.map +1 -0
- package/dist/files/file-node.d.ts +65 -0
- package/dist/files/file-node.d.ts.map +1 -0
- package/dist/files/file-node.js +863 -0
- package/dist/files/file-node.js.map +1 -0
- package/dist/files/file-node.rpc-service.d.ts +12 -0
- package/dist/files/file-node.rpc-service.d.ts.map +1 -0
- package/dist/files/file-node.rpc-service.js +42 -0
- package/dist/files/file-node.rpc-service.js.map +1 -0
- package/dist/files/files.rpc-service.d.ts +107 -0
- package/dist/files/files.rpc-service.d.ts.map +1 -0
- package/dist/files/files.rpc-service.js +769 -0
- package/dist/files/files.rpc-service.js.map +1 -0
- package/dist/files/structural-policy.d.ts +3 -0
- package/dist/files/structural-policy.d.ts.map +1 -0
- package/dist/files/structural-policy.js +27 -0
- package/dist/files/structural-policy.js.map +1 -0
- package/dist/health/contracts.d.ts +30 -0
- package/dist/health/contracts.d.ts.map +1 -0
- package/dist/health/contracts.js +58 -0
- package/dist/health/contracts.js.map +1 -0
- package/dist/health/heartbeat.d.ts +11 -0
- package/dist/health/heartbeat.d.ts.map +1 -0
- package/dist/health/heartbeat.js +32 -0
- package/dist/health/heartbeat.js.map +1 -0
- package/dist/health/index.d.ts +4 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +2 -0
- package/dist/health/index.js.map +1 -0
- package/dist/health/readiness.d.ts +15 -0
- package/dist/health/readiness.d.ts.map +1 -0
- package/dist/health/readiness.js +170 -0
- package/dist/health/readiness.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/infrastructure/apt-packages.d.ts +34 -0
- package/dist/infrastructure/apt-packages.d.ts.map +1 -0
- package/dist/infrastructure/apt-packages.js +232 -0
- package/dist/infrastructure/apt-packages.js.map +1 -0
- package/dist/infrastructure/bare-metal-plan.d.ts +10 -1
- package/dist/infrastructure/bare-metal-plan.d.ts.map +1 -1
- package/dist/infrastructure/bare-metal-plan.js +22 -1
- package/dist/infrastructure/bare-metal-plan.js.map +1 -1
- package/dist/infrastructure/bare-metal-runner.d.ts.map +1 -1
- package/dist/infrastructure/bare-metal-runner.js +13 -2
- package/dist/infrastructure/bare-metal-runner.js.map +1 -1
- package/dist/infrastructure/config-normalizer.d.ts +4 -3
- package/dist/infrastructure/config-normalizer.d.ts.map +1 -1
- package/dist/infrastructure/config-normalizer.js +11 -3
- package/dist/infrastructure/config-normalizer.js.map +1 -1
- package/dist/infrastructure/config-payload.d.ts +4 -4
- package/dist/infrastructure/config-payload.js +4 -4
- package/dist/infrastructure/container-runtime.d.ts +39 -13
- package/dist/infrastructure/container-runtime.d.ts.map +1 -1
- package/dist/infrastructure/container-runtime.js +276 -84
- package/dist/infrastructure/container-runtime.js.map +1 -1
- package/dist/infrastructure/gateway-redis-db.d.ts +2 -2
- package/dist/infrastructure/gateway-redis-db.js +2 -2
- package/dist/infrastructure/health-recovery.d.ts +19 -0
- package/dist/infrastructure/health-recovery.d.ts.map +1 -0
- package/dist/infrastructure/health-recovery.js +39 -0
- package/dist/infrastructure/health-recovery.js.map +1 -0
- package/dist/infrastructure/host-ingress-config.d.ts +12 -0
- package/dist/infrastructure/host-ingress-config.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-config.js +200 -0
- package/dist/infrastructure/host-ingress-config.js.map +1 -0
- package/dist/infrastructure/host-ingress-lock.d.ts +3 -0
- package/dist/infrastructure/host-ingress-lock.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-lock.js +2 -0
- package/dist/infrastructure/host-ingress-lock.js.map +1 -0
- package/dist/infrastructure/host-ingress-runtime.d.ts +25 -0
- package/dist/infrastructure/host-ingress-runtime.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-runtime.js +114 -0
- package/dist/infrastructure/host-ingress-runtime.js.map +1 -0
- package/dist/infrastructure/host-ingress-takeover.d.ts +18 -0
- package/dist/infrastructure/host-ingress-takeover.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-takeover.js +142 -0
- package/dist/infrastructure/host-ingress-takeover.js.map +1 -0
- package/dist/infrastructure/host-ingress-types.d.ts +175 -0
- package/dist/infrastructure/host-ingress-types.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress-types.js +2 -0
- package/dist/infrastructure/host-ingress-types.js.map +1 -0
- package/dist/infrastructure/host-ingress.service.d.ts +47 -0
- package/dist/infrastructure/host-ingress.service.d.ts.map +1 -0
- package/dist/infrastructure/host-ingress.service.js +514 -0
- package/dist/infrastructure/host-ingress.service.js.map +1 -0
- package/dist/infrastructure/host-inspection.d.ts +1 -0
- package/dist/infrastructure/host-inspection.d.ts.map +1 -1
- package/dist/infrastructure/host-inspection.js +3 -2
- package/dist/infrastructure/host-inspection.js.map +1 -1
- package/dist/infrastructure/infrastructure.service.d.ts +101 -34
- package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
- package/dist/infrastructure/infrastructure.service.js +631 -205
- package/dist/infrastructure/infrastructure.service.js.map +1 -1
- package/dist/infrastructure/merge-config.d.ts +10 -0
- package/dist/infrastructure/merge-config.d.ts.map +1 -0
- package/dist/infrastructure/merge-config.js +27 -0
- package/dist/infrastructure/merge-config.js.map +1 -0
- package/dist/infrastructure/orphan-containers.d.ts +8 -7
- package/dist/infrastructure/orphan-containers.d.ts.map +1 -1
- package/dist/infrastructure/orphan-containers.js +7 -7
- package/dist/infrastructure/orphan-containers.js.map +1 -1
- package/dist/infrastructure/owned-container.d.ts +4 -0
- package/dist/infrastructure/owned-container.d.ts.map +1 -0
- package/dist/infrastructure/owned-container.js +13 -0
- package/dist/infrastructure/owned-container.js.map +1 -0
- package/dist/infrastructure/phantom-endpoint-janitor.d.ts +4 -1
- package/dist/infrastructure/phantom-endpoint-janitor.d.ts.map +1 -1
- package/dist/infrastructure/phantom-endpoint-janitor.js +11 -2
- package/dist/infrastructure/phantom-endpoint-janitor.js.map +1 -1
- package/dist/infrastructure/presets/minio.d.ts.map +1 -1
- package/dist/infrastructure/presets/minio.js +15 -25
- package/dist/infrastructure/presets/minio.js.map +1 -1
- package/dist/infrastructure/presets/postgres.d.ts.map +1 -1
- package/dist/infrastructure/presets/postgres.js +35 -31
- package/dist/infrastructure/presets/postgres.js.map +1 -1
- package/dist/infrastructure/presets/tor.d.ts +1 -2
- package/dist/infrastructure/presets/tor.d.ts.map +1 -1
- package/dist/infrastructure/presets/tor.js +7 -13
- package/dist/infrastructure/presets/tor.js.map +1 -1
- package/dist/infrastructure/provisioning-outcome.d.ts +8 -1
- package/dist/infrastructure/provisioning-outcome.d.ts.map +1 -1
- package/dist/infrastructure/provisioning-outcome.js +42 -9
- package/dist/infrastructure/provisioning-outcome.js.map +1 -1
- package/dist/infrastructure/service-binding.js +1 -1
- package/dist/infrastructure/service-resolver.d.ts +11 -5
- package/dist/infrastructure/service-resolver.d.ts.map +1 -1
- package/dist/infrastructure/service-resolver.js +109 -54
- package/dist/infrastructure/service-resolver.js.map +1 -1
- package/dist/infrastructure/shipped-config.js +4 -4
- package/dist/infrastructure/stack-infra-manager.d.ts +35 -5
- package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
- package/dist/infrastructure/stack-infra-manager.js +227 -57
- package/dist/infrastructure/stack-infra-manager.js.map +1 -1
- package/dist/infrastructure/types.d.ts +68 -9
- package/dist/infrastructure/types.d.ts.map +1 -1
- package/dist/mcp/mcp-bridge.js +1 -1
- package/dist/mcp/mcp-bridge.js.map +1 -1
- package/dist/mcp/monitoring-reading.d.ts +18 -0
- package/dist/mcp/monitoring-reading.d.ts.map +1 -0
- package/dist/mcp/monitoring-reading.js +84 -0
- package/dist/mcp/monitoring-reading.js.map +1 -0
- package/dist/mcp/tool-groups/apps.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/apps.tools.js +6 -4
- package/dist/mcp/tool-groups/apps.tools.js.map +1 -1
- package/dist/mcp/tool-groups/management.tools.d.ts +2 -0
- package/dist/mcp/tool-groups/management.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/management.tools.js +17 -38
- package/dist/mcp/tool-groups/management.tools.js.map +1 -1
- package/dist/mcp/tool-groups/monitoring.tools.d.ts.map +1 -1
- package/dist/mcp/tool-groups/monitoring.tools.js +45 -2
- package/dist/mcp/tool-groups/monitoring.tools.js.map +1 -1
- package/dist/mcp/unavailable-tools.js +2 -2
- package/dist/mcp/unavailable-tools.js.map +1 -1
- package/dist/migration/configuration.d.ts +8 -0
- package/dist/migration/configuration.d.ts.map +1 -0
- package/dist/migration/configuration.js +16 -0
- package/dist/migration/configuration.js.map +1 -0
- package/dist/migration/index.d.ts +25 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/index.js +27 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/migration/operation.d.ts +31 -0
- package/dist/migration/operation.d.ts.map +1 -0
- package/dist/migration/operation.js +67 -0
- package/dist/migration/operation.js.map +1 -0
- package/dist/migration/workspace.d.ts +28 -0
- package/dist/migration/workspace.d.ts.map +1 -0
- package/dist/migration/workspace.js +91 -0
- package/dist/migration/workspace.js.map +1 -0
- package/dist/monitoring/app-health.indicator.d.ts +1 -1
- package/dist/monitoring/app-health.indicator.d.ts.map +1 -1
- package/dist/monitoring/docker-health.indicator.d.ts +4 -2
- package/dist/monitoring/docker-health.indicator.d.ts.map +1 -1
- package/dist/monitoring/docker-health.indicator.js +66 -5
- package/dist/monitoring/docker-health.indicator.js.map +1 -1
- package/dist/monitoring/event-loop-watch.d.ts.map +1 -1
- package/dist/monitoring/event-loop-watch.js.map +1 -1
- package/dist/monitoring/infrastructure-health.d.ts +15 -0
- package/dist/monitoring/infrastructure-health.d.ts.map +1 -0
- package/dist/monitoring/infrastructure-health.js +2 -0
- package/dist/monitoring/infrastructure-health.js.map +1 -0
- package/dist/monitoring/log-entry.d.ts +7 -0
- package/dist/monitoring/log-entry.d.ts.map +1 -0
- package/dist/monitoring/log-entry.js +16 -0
- package/dist/monitoring/log-entry.js.map +1 -0
- package/dist/monitoring/log-journal.d.ts +40 -0
- package/dist/monitoring/log-journal.d.ts.map +1 -0
- package/dist/monitoring/log-journal.js +158 -0
- package/dist/monitoring/log-journal.js.map +1 -0
- package/dist/monitoring/log-line.d.ts +22 -17
- package/dist/monitoring/log-line.d.ts.map +1 -1
- package/dist/monitoring/log-line.js +73 -62
- package/dist/monitoring/log-line.js.map +1 -1
- package/dist/monitoring/log-manager.d.ts +37 -40
- package/dist/monitoring/log-manager.d.ts.map +1 -1
- package/dist/monitoring/log-manager.js +167 -66
- package/dist/monitoring/log-manager.js.map +1 -1
- package/dist/monitoring/log-sink-health.indicator.d.ts +10 -0
- package/dist/monitoring/log-sink-health.indicator.d.ts.map +1 -0
- package/dist/monitoring/log-sink-health.indicator.js +21 -0
- package/dist/monitoring/log-sink-health.indicator.js.map +1 -0
- package/dist/monitoring/metrics-storage.indicator.d.ts +9 -0
- package/dist/monitoring/metrics-storage.indicator.d.ts.map +1 -0
- package/dist/monitoring/metrics-storage.indicator.js +18 -0
- package/dist/monitoring/metrics-storage.indicator.js.map +1 -0
- package/dist/monitoring/process-lifetime.d.ts +11 -0
- package/dist/monitoring/process-lifetime.d.ts.map +1 -0
- package/dist/monitoring/process-lifetime.js +45 -0
- package/dist/monitoring/process-lifetime.js.map +1 -0
- package/dist/monitoring/runtime-diagnostics.d.ts +47 -0
- package/dist/monitoring/runtime-diagnostics.d.ts.map +1 -0
- package/dist/monitoring/runtime-diagnostics.js +88 -0
- package/dist/monitoring/runtime-diagnostics.js.map +1 -0
- package/dist/observability/metrics-bridge.d.ts +5 -1
- package/dist/observability/metrics-bridge.d.ts.map +1 -1
- package/dist/observability/metrics-bridge.js +11 -0
- package/dist/observability/metrics-bridge.js.map +1 -1
- package/dist/operations.d.ts +17 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +13 -0
- package/dist/operations.js.map +1 -0
- package/dist/orchestrator/app-handle.d.ts +27 -9
- package/dist/orchestrator/app-handle.d.ts.map +1 -1
- package/dist/orchestrator/app-handle.js +42 -11
- package/dist/orchestrator/app-handle.js.map +1 -1
- package/dist/orchestrator/application-runtime-child.d.ts +2 -0
- package/dist/orchestrator/application-runtime-child.d.ts.map +1 -0
- package/dist/orchestrator/application-runtime-child.js +15 -0
- package/dist/orchestrator/application-runtime-child.js.map +1 -0
- package/dist/orchestrator/application-runtime-resolution.d.ts +4 -0
- package/dist/orchestrator/application-runtime-resolution.d.ts.map +1 -0
- package/dist/orchestrator/application-runtime-resolution.js +62 -0
- package/dist/orchestrator/application-runtime-resolution.js.map +1 -0
- package/dist/orchestrator/application-runtime.d.ts +3 -0
- package/dist/orchestrator/application-runtime.d.ts.map +1 -0
- package/dist/orchestrator/application-runtime.js +20 -0
- package/dist/orchestrator/application-runtime.js.map +1 -0
- package/dist/orchestrator/application-topology.d.ts +20 -0
- package/dist/orchestrator/application-topology.d.ts.map +1 -0
- package/dist/orchestrator/application-topology.js +90 -0
- package/dist/orchestrator/application-topology.js.map +1 -0
- package/dist/orchestrator/bootstrap-loader-child.d.ts +1 -1
- package/dist/orchestrator/bootstrap-loader-child.js +7 -16
- package/dist/orchestrator/bootstrap-loader-child.js.map +1 -1
- package/dist/orchestrator/bootstrap-loader.d.ts +10 -20
- package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-loader.js +24 -207
- package/dist/orchestrator/bootstrap-loader.js.map +1 -1
- package/dist/orchestrator/bootstrap-process.d.ts +6 -6
- package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
- package/dist/orchestrator/bootstrap-process.js +40 -40
- package/dist/orchestrator/bootstrap-process.js.map +1 -1
- package/dist/orchestrator/build-service.d.ts +8 -14
- package/dist/orchestrator/build-service.d.ts.map +1 -1
- package/dist/orchestrator/build-service.js +144 -158
- package/dist/orchestrator/build-service.js.map +1 -1
- package/dist/orchestrator/call-exposed-service.d.ts +8 -0
- package/dist/orchestrator/call-exposed-service.d.ts.map +1 -1
- package/dist/orchestrator/call-exposed-service.js +1 -1
- package/dist/orchestrator/classic-launcher.d.ts +3 -2
- package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
- package/dist/orchestrator/classic-launcher.js +32 -23
- package/dist/orchestrator/classic-launcher.js.map +1 -1
- package/dist/orchestrator/dependency-resolver.d.ts.map +1 -1
- package/dist/orchestrator/dependency-resolver.js +7 -38
- package/dist/orchestrator/dependency-resolver.js.map +1 -1
- package/dist/orchestrator/execution-contract.d.ts +67 -0
- package/dist/orchestrator/execution-contract.d.ts.map +1 -0
- package/dist/orchestrator/execution-contract.js +2 -0
- package/dist/orchestrator/execution-contract.js.map +1 -0
- package/dist/orchestrator/file-watcher.d.ts +2 -1
- package/dist/orchestrator/file-watcher.d.ts.map +1 -1
- package/dist/orchestrator/file-watcher.js +23 -5
- package/dist/orchestrator/file-watcher.js.map +1 -1
- package/dist/orchestrator/http-routes.d.ts +4 -0
- package/dist/orchestrator/http-routes.d.ts.map +1 -0
- package/dist/orchestrator/http-routes.js +19 -0
- package/dist/orchestrator/http-routes.js.map +1 -0
- package/dist/orchestrator/installed-execution.d.ts +7 -0
- package/dist/orchestrator/installed-execution.d.ts.map +1 -0
- package/dist/orchestrator/installed-execution.js +25 -0
- package/dist/orchestrator/installed-execution.js.map +1 -0
- package/dist/orchestrator/module-worker-process.d.ts.map +1 -1
- package/dist/orchestrator/module-worker-process.js +2 -1
- package/dist/orchestrator/module-worker-process.js.map +1 -1
- package/dist/orchestrator/orchestrator.service.d.ts +105 -80
- package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.service.js +1084 -571
- package/dist/orchestrator/orchestrator.service.js.map +1 -1
- package/dist/orchestrator/process-janitor.d.ts +3 -0
- package/dist/orchestrator/process-janitor.d.ts.map +1 -1
- package/dist/orchestrator/process-janitor.js +5 -1
- package/dist/orchestrator/process-janitor.js.map +1 -1
- package/dist/orchestrator/runtime-package-resolution.d.ts +8 -0
- package/dist/orchestrator/runtime-package-resolution.d.ts.map +1 -0
- package/dist/orchestrator/runtime-package-resolution.js +56 -0
- package/dist/orchestrator/runtime-package-resolution.js.map +1 -0
- package/dist/orchestrator/service-router.d.ts +4 -108
- package/dist/orchestrator/service-router.d.ts.map +1 -1
- package/dist/orchestrator/service-router.js +51 -237
- package/dist/orchestrator/service-router.js.map +1 -1
- package/dist/orchestrator/topology-query.d.ts +1 -1
- package/dist/orchestrator/topology-query.js +1 -1
- package/dist/orchestrator/ts-compiler.d.ts.map +1 -1
- package/dist/orchestrator/ts-compiler.js +9 -17
- package/dist/orchestrator/ts-compiler.js.map +1 -1
- package/dist/project/artifact-builder.d.ts +17 -7
- package/dist/project/artifact-builder.d.ts.map +1 -1
- package/dist/project/artifact-builder.js +91 -58
- package/dist/project/artifact-builder.js.map +1 -1
- package/dist/project/bootstrap-metadata.d.ts +42 -0
- package/dist/project/bootstrap-metadata.d.ts.map +1 -0
- package/dist/project/bootstrap-metadata.js +76 -0
- package/dist/project/bootstrap-metadata.js.map +1 -0
- package/dist/project/build-decision.d.ts +1 -1
- package/dist/project/build-decision.js +1 -1
- package/dist/project/config-resolver.js +4 -4
- package/dist/project/config-resolver.js.map +1 -1
- package/dist/project/configuration-catalog.d.ts +8 -0
- package/dist/project/configuration-catalog.d.ts.map +1 -0
- package/dist/project/configuration-catalog.js +29 -0
- package/dist/project/configuration-catalog.js.map +1 -0
- package/dist/project/declared-config.d.ts +4 -2
- package/dist/project/declared-config.d.ts.map +1 -1
- package/dist/project/declared-config.js +10 -7
- package/dist/project/declared-config.js.map +1 -1
- package/dist/project/dependency-topology.d.ts +10 -0
- package/dist/project/dependency-topology.d.ts.map +1 -0
- package/dist/project/dependency-topology.js +55 -0
- package/dist/project/dependency-topology.js.map +1 -0
- package/dist/project/deploy-phases.d.ts +1 -1
- package/dist/project/deploy-phases.js +1 -1
- package/dist/project/exec-in-group.d.ts +1 -1
- package/dist/project/exec-in-group.js +1 -1
- package/dist/project/infrastructure-ownership.d.ts +5 -0
- package/dist/project/infrastructure-ownership.d.ts.map +1 -0
- package/dist/project/infrastructure-ownership.js +26 -0
- package/dist/project/infrastructure-ownership.js.map +1 -0
- package/dist/project/installed-stack-resume.d.ts +62 -0
- package/dist/project/installed-stack-resume.d.ts.map +1 -0
- package/dist/project/installed-stack-resume.js +261 -0
- package/dist/project/installed-stack-resume.js.map +1 -0
- package/dist/project/launch-failure.d.ts +3 -0
- package/dist/project/launch-failure.d.ts.map +1 -0
- package/dist/project/launch-failure.js +32 -0
- package/dist/project/launch-failure.js.map +1 -0
- package/dist/project/local-release-selection.d.ts +18 -0
- package/dist/project/local-release-selection.d.ts.map +1 -0
- package/dist/project/local-release-selection.js +36 -0
- package/dist/project/local-release-selection.js.map +1 -0
- package/dist/project/node-app-config.d.ts +10 -3
- package/dist/project/node-app-config.d.ts.map +1 -1
- package/dist/project/node-app-config.js +22 -30
- package/dist/project/node-app-config.js.map +1 -1
- package/dist/project/node-app-health.d.ts +1 -1
- package/dist/project/node-app-health.js +1 -1
- package/dist/project/node-stack-stop.d.ts +83 -0
- package/dist/project/node-stack-stop.d.ts.map +1 -0
- package/dist/project/node-stack-stop.js +219 -0
- package/dist/project/node-stack-stop.js.map +1 -0
- package/dist/project/operator-account.d.ts +9 -3
- package/dist/project/operator-account.d.ts.map +1 -1
- package/dist/project/operator-account.js +9 -9
- package/dist/project/operator-account.js.map +1 -1
- package/dist/project/public-stack.d.ts +4 -0
- package/dist/project/public-stack.d.ts.map +1 -0
- package/dist/project/public-stack.js +29 -0
- package/dist/project/public-stack.js.map +1 -0
- package/dist/project/registry.d.ts +18 -27
- package/dist/project/registry.d.ts.map +1 -1
- package/dist/project/registry.js +248 -77
- package/dist/project/registry.js.map +1 -1
- package/dist/project/requirements-scanner.d.ts +4 -8
- package/dist/project/requirements-scanner.d.ts.map +1 -1
- package/dist/project/requirements-scanner.js +39 -146
- package/dist/project/requirements-scanner.js.map +1 -1
- package/dist/project/stack-diagnosis.d.ts +18 -0
- package/dist/project/stack-diagnosis.d.ts.map +1 -0
- package/dist/project/stack-diagnosis.js +94 -0
- package/dist/project/stack-diagnosis.js.map +1 -0
- package/dist/project/stack-env.d.ts +1 -1
- package/dist/project/stack-env.js +1 -1
- package/dist/project/stack-health.d.ts +12 -0
- package/dist/project/stack-health.d.ts.map +1 -0
- package/dist/project/stack-health.js +27 -0
- package/dist/project/stack-health.js.map +1 -0
- package/dist/project/stack-observation.d.ts +8 -0
- package/dist/project/stack-observation.d.ts.map +1 -0
- package/dist/project/stack-observation.js +126 -0
- package/dist/project/stack-observation.js.map +1 -0
- package/dist/project/stack-placement.d.ts +11 -0
- package/dist/project/stack-placement.d.ts.map +1 -0
- package/dist/project/stack-placement.js +57 -0
- package/dist/project/stack-placement.js.map +1 -0
- package/dist/project/stack-stop-review.d.ts +14 -0
- package/dist/project/stack-stop-review.d.ts.map +1 -0
- package/dist/project/stack-stop-review.js +22 -0
- package/dist/project/stack-stop-review.js.map +1 -0
- package/dist/project/stack-targets.d.ts +24 -0
- package/dist/project/stack-targets.d.ts.map +1 -0
- package/dist/project/stack-targets.js +67 -0
- package/dist/project/stack-targets.js.map +1 -0
- package/dist/project/stand-containers.d.ts +9 -2
- package/dist/project/stand-containers.d.ts.map +1 -1
- package/dist/project/stand-containers.js +17 -17
- package/dist/project/stand-containers.js.map +1 -1
- package/dist/project/topology-control.d.ts +41 -0
- package/dist/project/topology-control.d.ts.map +1 -0
- package/dist/project/topology-control.js +133 -0
- package/dist/project/topology-control.js.map +1 -0
- package/dist/project/topology-plan.d.ts +11 -0
- package/dist/project/topology-plan.d.ts.map +1 -0
- package/dist/project/topology-plan.js +154 -0
- package/dist/project/topology-plan.js.map +1 -0
- package/dist/project/topology-services.d.ts +7 -0
- package/dist/project/topology-services.d.ts.map +1 -0
- package/dist/project/topology-services.js +36 -0
- package/dist/project/topology-services.js.map +1 -0
- package/dist/project/topology-state.d.ts +27 -0
- package/dist/project/topology-state.d.ts.map +1 -0
- package/dist/project/topology-state.js +71 -0
- package/dist/project/topology-state.js.map +1 -0
- package/dist/project/workspace-kind.d.ts +3 -0
- package/dist/project/workspace-kind.d.ts.map +1 -0
- package/dist/project/workspace-kind.js +13 -0
- package/dist/project/workspace-kind.js.map +1 -0
- package/dist/readiness.d.ts +4 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +3 -0
- package/dist/readiness.js.map +1 -0
- package/dist/release/application-closure.d.ts +4 -0
- package/dist/release/application-closure.d.ts.map +1 -0
- package/dist/release/application-closure.js +61 -0
- package/dist/release/application-closure.js.map +1 -0
- package/dist/release/archive.d.ts +3 -0
- package/dist/release/archive.d.ts.map +1 -0
- package/dist/release/archive.js +3 -0
- package/dist/release/archive.js.map +1 -0
- package/dist/release/artifact-inspection.d.ts +50 -0
- package/dist/release/artifact-inspection.d.ts.map +1 -0
- package/dist/release/artifact-inspection.js +169 -0
- package/dist/release/artifact-inspection.js.map +1 -0
- package/dist/release/artifact-store.d.ts +1 -1
- package/dist/release/artifact-store.d.ts.map +1 -1
- package/dist/release/artifact-store.js +1 -1
- package/dist/release/artifact-store.js.map +1 -1
- package/dist/release/artifact-volumes.d.ts +10 -0
- package/dist/release/artifact-volumes.d.ts.map +1 -0
- package/dist/release/artifact-volumes.js +112 -0
- package/dist/release/artifact-volumes.js.map +1 -0
- package/dist/release/attest-on-node.d.ts +3 -2
- package/dist/release/attest-on-node.d.ts.map +1 -1
- package/dist/release/attest-on-node.js +6 -6
- package/dist/release/attest-on-node.js.map +1 -1
- package/dist/release/attest.d.ts +3 -3
- package/dist/release/attest.js +1 -1
- package/dist/release/build-run.d.ts +14 -17
- package/dist/release/build-run.d.ts.map +1 -1
- package/dist/release/build-run.js +264 -77
- package/dist/release/build-run.js.map +1 -1
- package/dist/release/builder.d.ts +9 -3
- package/dist/release/builder.d.ts.map +1 -1
- package/dist/release/builder.js +19 -5
- package/dist/release/builder.js.map +1 -1
- package/dist/release/closure-library-path.d.ts +26 -0
- package/dist/release/closure-library-path.d.ts.map +1 -0
- package/dist/release/closure-library-path.js +160 -0
- package/dist/release/closure-library-path.js.map +1 -0
- package/dist/release/definition-inputs.d.ts +7 -4
- package/dist/release/definition-inputs.d.ts.map +1 -1
- package/dist/release/definition-inputs.js +146 -15
- package/dist/release/definition-inputs.js.map +1 -1
- package/dist/release/framework-source.d.ts +22 -0
- package/dist/release/framework-source.d.ts.map +1 -0
- package/dist/release/framework-source.js +54 -0
- package/dist/release/framework-source.js.map +1 -0
- package/dist/release/frozen-installation.d.ts +23 -0
- package/dist/release/frozen-installation.d.ts.map +1 -0
- package/dist/release/frozen-installation.js +159 -0
- package/dist/release/frozen-installation.js.map +1 -0
- package/dist/release/import-artifacts.d.ts +9 -0
- package/dist/release/import-artifacts.d.ts.map +1 -0
- package/dist/release/import-artifacts.js +60 -0
- package/dist/release/import-artifacts.js.map +1 -0
- package/dist/release/infrastructure.d.ts +6 -0
- package/dist/release/infrastructure.d.ts.map +1 -0
- package/dist/release/infrastructure.js +74 -0
- package/dist/release/infrastructure.js.map +1 -0
- package/dist/release/installed-artifacts.d.ts +12 -0
- package/dist/release/installed-artifacts.d.ts.map +1 -0
- package/dist/release/installed-artifacts.js +199 -0
- package/dist/release/installed-artifacts.js.map +1 -0
- package/dist/release/installed-graph.d.ts +28 -0
- package/dist/release/installed-graph.d.ts.map +1 -0
- package/dist/release/installed-graph.js +138 -0
- package/dist/release/installed-graph.js.map +1 -0
- package/dist/release/installed-origin.d.ts +42 -0
- package/dist/release/installed-origin.d.ts.map +1 -0
- package/dist/release/installed-origin.js +130 -0
- package/dist/release/installed-origin.js.map +1 -0
- package/dist/release/installed-release.d.ts +23 -0
- package/dist/release/installed-release.d.ts.map +1 -0
- package/dist/release/installed-release.js +141 -0
- package/dist/release/installed-release.js.map +1 -0
- package/dist/release/layout.d.ts +29 -34
- package/dist/release/layout.d.ts.map +1 -1
- package/dist/release/layout.js +164 -53
- package/dist/release/layout.js.map +1 -1
- package/dist/release/load.d.ts +3 -0
- package/dist/release/load.d.ts.map +1 -1
- package/dist/release/load.js +50 -8
- package/dist/release/load.js.map +1 -1
- package/dist/release/local-installation.d.ts +20 -0
- package/dist/release/local-installation.d.ts.map +1 -0
- package/dist/release/local-installation.js +298 -0
- package/dist/release/local-installation.js.map +1 -0
- package/dist/release/manifest.d.ts +87 -12
- package/dist/release/manifest.d.ts.map +1 -1
- package/dist/release/manifest.js +31 -7
- package/dist/release/manifest.js.map +1 -1
- package/dist/release/migration-programs.d.ts +12 -0
- package/dist/release/migration-programs.d.ts.map +1 -0
- package/dist/release/migration-programs.js +45 -0
- package/dist/release/migration-programs.js.map +1 -0
- package/dist/release/migrations.d.ts +12 -1
- package/dist/release/migrations.d.ts.map +1 -1
- package/dist/release/migrations.js +124 -9
- package/dist/release/migrations.js.map +1 -1
- package/dist/release/origin.d.ts +33 -0
- package/dist/release/origin.d.ts.map +1 -0
- package/dist/release/origin.js +253 -0
- package/dist/release/origin.js.map +1 -0
- package/dist/release/owner-admission.d.ts +15 -0
- package/dist/release/owner-admission.d.ts.map +1 -0
- package/dist/release/owner-admission.js +37 -0
- package/dist/release/owner-admission.js.map +1 -0
- package/dist/release/owner-executable.d.ts +3 -0
- package/dist/release/owner-executable.d.ts.map +1 -0
- package/dist/release/owner-executable.js +40 -0
- package/dist/release/owner-executable.js.map +1 -0
- package/dist/release/package-gates.d.ts +9 -0
- package/dist/release/package-gates.d.ts.map +1 -0
- package/dist/release/package-gates.js +42 -0
- package/dist/release/package-gates.js.map +1 -0
- package/dist/release/package-resolution.d.ts +4 -0
- package/dist/release/package-resolution.d.ts.map +1 -0
- package/dist/release/package-resolution.js +28 -0
- package/dist/release/package-resolution.js.map +1 -0
- package/dist/release/portable-archive.d.ts +12 -0
- package/dist/release/portable-archive.d.ts.map +1 -0
- package/dist/release/portable-archive.js +96 -0
- package/dist/release/portable-archive.js.map +1 -0
- package/dist/release/publish.d.ts +1 -1
- package/dist/release/publish.js +1 -1
- package/dist/release/registry-download.d.ts +2 -0
- package/dist/release/registry-download.d.ts.map +1 -0
- package/dist/release/registry-download.js +157 -0
- package/dist/release/registry-download.js.map +1 -0
- package/dist/release/registry-framework.d.ts +39 -0
- package/dist/release/registry-framework.d.ts.map +1 -0
- package/dist/release/registry-framework.js +279 -0
- package/dist/release/registry-framework.js.map +1 -0
- package/dist/release/registry-transport.d.ts +27 -0
- package/dist/release/registry-transport.d.ts.map +1 -0
- package/dist/release/registry-transport.js +96 -0
- package/dist/release/registry-transport.js.map +1 -0
- package/dist/release/repository-identity.d.ts +12 -0
- package/dist/release/repository-identity.d.ts.map +1 -0
- package/dist/release/repository-identity.js +62 -0
- package/dist/release/repository-identity.js.map +1 -0
- package/dist/release/runtime-extractor.d.ts +4 -0
- package/dist/release/runtime-extractor.d.ts.map +1 -0
- package/dist/release/runtime-extractor.js +15 -0
- package/dist/release/runtime-extractor.js.map +1 -0
- package/dist/release/runtime-provenance.d.ts +40 -0
- package/dist/release/runtime-provenance.d.ts.map +1 -0
- package/dist/release/runtime-provenance.js +229 -0
- package/dist/release/runtime-provenance.js.map +1 -0
- package/dist/release/source.d.ts +12 -0
- package/dist/release/source.d.ts.map +1 -0
- package/dist/release/source.js +168 -0
- package/dist/release/source.js.map +1 -0
- package/dist/release/store.d.ts +14 -5
- package/dist/release/store.d.ts.map +1 -1
- package/dist/release/store.js +73 -17
- package/dist/release/store.js.map +1 -1
- package/dist/release/target-runtime.d.ts +19 -0
- package/dist/release/target-runtime.d.ts.map +1 -0
- package/dist/release/target-runtime.js +41 -0
- package/dist/release/target-runtime.js.map +1 -0
- package/dist/release/vendored-framework.d.ts +28 -0
- package/dist/release/vendored-framework.d.ts.map +1 -0
- package/dist/release/vendored-framework.js +98 -0
- package/dist/release/vendored-framework.js.map +1 -0
- package/dist/release/workspace-source.d.ts +3 -0
- package/dist/release/workspace-source.d.ts.map +1 -0
- package/dist/release/workspace-source.js +33 -0
- package/dist/release/workspace-source.js.map +1 -0
- package/dist/runtime/context.d.ts +29 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +111 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/identity.d.ts +15 -0
- package/dist/runtime/identity.d.ts.map +1 -0
- package/dist/runtime/identity.js +33 -0
- package/dist/runtime/identity.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/services/access.rpc-service.d.ts +41 -0
- package/dist/services/access.rpc-service.d.ts.map +1 -0
- package/dist/services/access.rpc-service.js +248 -0
- package/dist/services/access.rpc-service.js.map +1 -0
- package/dist/services/alert.service.d.ts +3 -3
- package/dist/services/alert.service.d.ts.map +1 -1
- package/dist/services/alert.service.js +3 -3
- package/dist/services/anonymous-input.d.ts +0 -21
- package/dist/services/anonymous-input.d.ts.map +1 -1
- package/dist/services/anonymous-input.js +1 -27
- package/dist/services/anonymous-input.js.map +1 -1
- package/dist/services/audit.service.d.ts +10 -30
- package/dist/services/audit.service.d.ts.map +1 -1
- package/dist/services/audit.service.js +72 -18
- package/dist/services/audit.service.js.map +1 -1
- package/dist/services/auth-context.d.ts +2 -0
- package/dist/services/auth-context.d.ts.map +1 -1
- package/dist/services/auth-context.js +4 -1
- package/dist/services/auth-context.js.map +1 -1
- package/dist/services/auth.rpc-service.d.ts +3 -1
- package/dist/services/auth.rpc-service.d.ts.map +1 -1
- package/dist/services/auth.rpc-service.js +13 -3
- package/dist/services/auth.rpc-service.js.map +1 -1
- package/dist/services/auth.service.d.ts +11 -21
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +125 -139
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/backup-pipeline.d.ts +1 -1
- package/dist/services/backup-pipeline.js +1 -1
- package/dist/services/backup-storage.d.ts +14 -0
- package/dist/services/backup-storage.d.ts.map +1 -0
- package/dist/services/backup-storage.js +195 -0
- package/dist/services/backup-storage.js.map +1 -0
- package/dist/services/backup.rpc-service.d.ts +2 -0
- package/dist/services/backup.rpc-service.d.ts.map +1 -1
- package/dist/services/backup.rpc-service.js +9 -0
- package/dist/services/backup.rpc-service.js.map +1 -1
- package/dist/services/backup.service.d.ts +36 -129
- package/dist/services/backup.service.d.ts.map +1 -1
- package/dist/services/backup.service.js +177 -156
- package/dist/services/backup.service.js.map +1 -1
- package/dist/services/bundle-builder.d.ts +27 -12
- package/dist/services/bundle-builder.d.ts.map +1 -1
- package/dist/services/bundle-builder.js +192 -101
- package/dist/services/bundle-builder.js.map +1 -1
- package/dist/services/bundle-overrides.d.ts +25 -0
- package/dist/services/bundle-overrides.d.ts.map +1 -0
- package/dist/services/bundle-overrides.js +96 -0
- package/dist/services/bundle-overrides.js.map +1 -0
- package/dist/services/daemon-authority.d.ts +16 -0
- package/dist/services/daemon-authority.d.ts.map +1 -0
- package/dist/services/daemon-authority.js +54 -0
- package/dist/services/daemon-authority.js.map +1 -0
- package/dist/services/daemon-principal.d.ts +31 -0
- package/dist/services/daemon-principal.d.ts.map +1 -0
- package/dist/services/daemon-principal.js +37 -0
- package/dist/services/daemon-principal.js.map +1 -0
- package/dist/services/deploy.service.d.ts +13 -57
- package/dist/services/deploy.service.d.ts.map +1 -1
- package/dist/services/deploy.service.js +15 -263
- package/dist/services/deploy.service.js.map +1 -1
- package/dist/services/discovery.service.d.ts +1 -1
- package/dist/services/discovery.service.js +1 -1
- package/dist/services/event-broadcaster.rpc-service.d.ts +7 -1
- package/dist/services/event-broadcaster.rpc-service.d.ts.map +1 -1
- package/dist/services/event-broadcaster.rpc-service.js +89 -11
- package/dist/services/event-broadcaster.rpc-service.js.map +1 -1
- package/dist/services/fleet.rpc-service.d.ts +0 -6
- package/dist/services/fleet.rpc-service.d.ts.map +1 -1
- package/dist/services/fleet.rpc-service.js +0 -13
- package/dist/services/fleet.rpc-service.js.map +1 -1
- package/dist/services/health-check.service.d.ts +1 -1
- package/dist/services/health-check.service.d.ts.map +1 -1
- package/dist/services/health-check.service.js +30 -5
- package/dist/services/health-check.service.js.map +1 -1
- package/dist/services/host-operation-barrier.d.ts +8 -0
- package/dist/services/host-operation-barrier.d.ts.map +1 -0
- package/dist/services/host-operation-barrier.js +44 -0
- package/dist/services/host-operation-barrier.js.map +1 -0
- package/dist/services/host-upgrade.d.ts +181 -0
- package/dist/services/host-upgrade.d.ts.map +1 -0
- package/dist/services/host-upgrade.js +503 -0
- package/dist/services/host-upgrade.js.map +1 -0
- package/dist/services/infrastructure.rpc-service.d.ts +24 -2
- package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
- package/dist/services/infrastructure.rpc-service.js +178 -36
- package/dist/services/infrastructure.rpc-service.js.map +1 -1
- package/dist/services/kernel-catalog.d.ts +14 -0
- package/dist/services/kernel-catalog.d.ts.map +1 -0
- package/dist/services/kernel-catalog.js +172 -0
- package/dist/services/kernel-catalog.js.map +1 -0
- package/dist/services/kernel-distribution.d.ts +5 -0
- package/dist/services/kernel-distribution.d.ts.map +1 -0
- package/dist/services/kernel-distribution.js +49 -0
- package/dist/services/kernel-distribution.js.map +1 -0
- package/dist/services/kernel-rollout.service.d.ts +55 -0
- package/dist/services/kernel-rollout.service.d.ts.map +1 -0
- package/dist/services/kernel-rollout.service.js +394 -0
- package/dist/services/kernel-rollout.service.js.map +1 -0
- package/dist/services/local-bundle.d.ts +9 -0
- package/dist/services/local-bundle.d.ts.map +1 -1
- package/dist/services/local-bundle.js +14 -3
- package/dist/services/local-bundle.js.map +1 -1
- package/dist/services/log-collector.rpc-service.d.ts +5 -11
- package/dist/services/log-collector.rpc-service.d.ts.map +1 -1
- package/dist/services/log-collector.rpc-service.js +6 -6
- package/dist/services/log-collector.rpc-service.js.map +1 -1
- package/dist/services/log-collector.service.d.ts +39 -109
- package/dist/services/log-collector.service.d.ts.map +1 -1
- package/dist/services/log-collector.service.js +286 -261
- package/dist/services/log-collector.service.js.map +1 -1
- package/dist/services/log-query.d.ts +6 -0
- package/dist/services/log-query.d.ts.map +1 -0
- package/dist/services/log-query.js +46 -0
- package/dist/services/log-query.js.map +1 -0
- package/dist/services/maintenance-lock.d.ts +2 -2
- package/dist/services/maintenance-lock.d.ts.map +1 -1
- package/dist/services/maintenance-lock.js +1 -1
- package/dist/services/managed-rollout.d.ts +54 -0
- package/dist/services/managed-rollout.d.ts.map +1 -0
- package/dist/services/managed-rollout.js +124 -0
- package/dist/services/managed-rollout.js.map +1 -0
- package/dist/services/metrics.rpc-service.d.ts +23 -0
- package/dist/services/metrics.rpc-service.d.ts.map +1 -0
- package/dist/services/metrics.rpc-service.js +73 -0
- package/dist/services/metrics.rpc-service.js.map +1 -0
- package/dist/services/migration-output.d.ts +2 -2
- package/dist/services/migration-output.js +2 -2
- package/dist/services/migration-process.d.ts +32 -0
- package/dist/services/migration-process.d.ts.map +1 -0
- package/dist/services/migration-process.js +71 -0
- package/dist/services/migration-process.js.map +1 -0
- package/dist/services/migration-target.d.ts +57 -0
- package/dist/services/migration-target.d.ts.map +1 -0
- package/dist/services/migration-target.js +221 -0
- package/dist/services/migration-target.js.map +1 -0
- package/dist/services/node-deploy-lease.d.ts +19 -2
- package/dist/services/node-deploy-lease.d.ts.map +1 -1
- package/dist/services/node-deploy-lease.js +84 -10
- package/dist/services/node-deploy-lease.js.map +1 -1
- package/dist/services/node-health.repository.d.ts +2 -59
- package/dist/services/node-health.repository.d.ts.map +1 -1
- package/dist/services/node-health.repository.js.map +1 -1
- package/dist/services/node-host-key.d.ts +9 -0
- package/dist/services/node-host-key.d.ts.map +1 -0
- package/dist/services/node-host-key.js +17 -0
- package/dist/services/node-host-key.js.map +1 -0
- package/dist/services/node-manager.rpc-service.d.ts +50 -38
- package/dist/services/node-manager.rpc-service.d.ts.map +1 -1
- package/dist/services/node-manager.rpc-service.js +233 -38
- package/dist/services/node-manager.rpc-service.js.map +1 -1
- package/dist/services/node-manager.service.d.ts +4 -1
- package/dist/services/node-manager.service.d.ts.map +1 -1
- package/dist/services/node-manager.service.js +79 -28
- package/dist/services/node-manager.service.js.map +1 -1
- package/dist/services/node-registration.d.ts +17 -0
- package/dist/services/node-registration.d.ts.map +1 -0
- package/dist/services/node-registration.js +73 -0
- package/dist/services/node-registration.js.map +1 -0
- package/dist/services/node-upgrade.d.ts +1 -1
- package/dist/services/node-upgrade.service.d.ts +2 -28
- package/dist/services/node-upgrade.service.d.ts.map +1 -1
- package/dist/services/node-upgrade.service.js +2 -3
- package/dist/services/node-upgrade.service.js.map +1 -1
- package/dist/services/pipeline-definition.d.ts +14 -0
- package/dist/services/pipeline-definition.d.ts.map +1 -0
- package/dist/services/pipeline-definition.js +77 -0
- package/dist/services/pipeline-definition.js.map +1 -0
- package/dist/services/pipeline.rpc-service.d.ts +14 -3
- package/dist/services/pipeline.rpc-service.d.ts.map +1 -1
- package/dist/services/pipeline.rpc-service.js +45 -12
- package/dist/services/pipeline.rpc-service.js.map +1 -1
- package/dist/services/pipeline.service.d.ts +30 -44
- package/dist/services/pipeline.service.d.ts.map +1 -1
- package/dist/services/pipeline.service.js +402 -333
- package/dist/services/pipeline.service.js.map +1 -1
- package/dist/services/project.rpc-service.d.ts +219 -14
- package/dist/services/project.rpc-service.d.ts.map +1 -1
- package/dist/services/project.rpc-service.js +314 -31
- package/dist/services/project.rpc-service.js.map +1 -1
- package/dist/services/project.service.d.ts +195 -67
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +2029 -705
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/redeploy-decision.d.ts +2 -2
- package/dist/services/redeploy-decision.js +1 -1
- package/dist/services/release.rpc-service.d.ts +6 -19
- package/dist/services/release.rpc-service.d.ts.map +1 -1
- package/dist/services/release.rpc-service.js +35 -12
- package/dist/services/release.rpc-service.js.map +1 -1
- package/dist/services/release.service.d.ts +2 -47
- package/dist/services/release.service.d.ts.map +1 -1
- package/dist/services/release.service.js +26 -2
- package/dist/services/release.service.js.map +1 -1
- package/dist/services/remote-deployer.service.d.ts +65 -126
- package/dist/services/remote-deployer.service.d.ts.map +1 -1
- package/dist/services/remote-deployer.service.js +671 -466
- package/dist/services/remote-deployer.service.js.map +1 -1
- package/dist/services/remote-provisioner.d.ts +3 -1
- package/dist/services/remote-provisioner.d.ts.map +1 -1
- package/dist/services/remote-provisioner.js +2 -2
- package/dist/services/remote-provisioner.js.map +1 -1
- package/dist/services/replication-evidence.d.ts +24 -0
- package/dist/services/replication-evidence.d.ts.map +1 -0
- package/dist/services/replication-evidence.js +61 -0
- package/dist/services/replication-evidence.js.map +1 -0
- package/dist/services/replication-history.d.ts +80 -0
- package/dist/services/replication-history.d.ts.map +1 -0
- package/dist/services/replication-history.js +279 -0
- package/dist/services/replication-history.js.map +1 -0
- package/dist/services/replication-reconciliation.d.ts +74 -0
- package/dist/services/replication-reconciliation.d.ts.map +1 -0
- package/dist/services/replication-reconciliation.js +351 -0
- package/dist/services/replication-reconciliation.js.map +1 -0
- package/dist/services/slave-storage.service.d.ts +2 -19
- package/dist/services/slave-storage.service.d.ts.map +1 -1
- package/dist/services/slave-storage.service.js +19 -6
- package/dist/services/slave-storage.service.js.map +1 -1
- package/dist/services/static-carry-forward.d.ts +28 -3
- package/dist/services/static-carry-forward.d.ts.map +1 -1
- package/dist/services/static-carry-forward.js +285 -4
- package/dist/services/static-carry-forward.js.map +1 -1
- package/dist/services/sync-summary.d.ts.map +1 -1
- package/dist/services/sync-summary.js +1 -18
- package/dist/services/sync-summary.js.map +1 -1
- package/dist/services/sync.rpc-service.d.ts +17 -20
- package/dist/services/sync.rpc-service.d.ts.map +1 -1
- package/dist/services/sync.rpc-service.js +23 -37
- package/dist/services/sync.rpc-service.js.map +1 -1
- package/dist/services/sync.service.d.ts +53 -157
- package/dist/services/sync.service.d.ts.map +1 -1
- package/dist/services/sync.service.js +239 -337
- package/dist/services/sync.service.js.map +1 -1
- package/dist/services/system-info.rpc-service.d.ts +3 -1
- package/dist/services/system-info.rpc-service.d.ts.map +1 -1
- package/dist/services/system-info.rpc-service.js +5 -5
- package/dist/services/system-info.rpc-service.js.map +1 -1
- package/dist/services/system-info.service.d.ts +3 -1
- package/dist/services/system-info.service.d.ts.map +1 -1
- package/dist/services/system-info.service.js +17 -9
- package/dist/services/system-info.service.js.map +1 -1
- package/dist/services/telemetry.rpc-service.d.ts +4 -23
- package/dist/services/telemetry.rpc-service.d.ts.map +1 -1
- package/dist/services/telemetry.rpc-service.js +4 -32
- package/dist/services/telemetry.rpc-service.js.map +1 -1
- package/dist/services/telemetry.service.d.ts +2 -30
- package/dist/services/telemetry.service.d.ts.map +1 -1
- package/dist/services/telemetry.service.js +25 -131
- package/dist/services/telemetry.service.js.map +1 -1
- package/dist/services/terminal-ssh.d.ts +16 -0
- package/dist/services/terminal-ssh.d.ts.map +1 -0
- package/dist/services/terminal-ssh.js +101 -0
- package/dist/services/terminal-ssh.js.map +1 -0
- package/dist/services/terminal.rpc-service.d.ts +84 -0
- package/dist/services/terminal.rpc-service.d.ts.map +1 -0
- package/dist/services/terminal.rpc-service.js +508 -0
- package/dist/services/terminal.rpc-service.js.map +1 -0
- package/dist/services/trace-collector.rpc-service.js +3 -3
- package/dist/services/trace-collector.rpc-service.js.map +1 -1
- package/dist/services/wait-for-postgres.d.ts +1 -1
- package/dist/services/wait-for-postgres.d.ts.map +1 -1
- package/dist/services/wait-for-postgres.js +13 -28
- package/dist/services/wait-for-postgres.js.map +1 -1
- package/dist/shared/build-stamp.d.ts +4 -1
- package/dist/shared/build-stamp.d.ts.map +1 -1
- package/dist/shared/build-stamp.js +14 -6
- package/dist/shared/build-stamp.js.map +1 -1
- package/dist/shared/describe-error.d.ts +1 -21
- package/dist/shared/describe-error.d.ts.map +1 -1
- package/dist/shared/describe-error.js +1 -38
- package/dist/shared/describe-error.js.map +1 -1
- package/dist/shared/dto/access.d.ts +55 -0
- package/dist/shared/dto/access.d.ts.map +1 -0
- package/dist/shared/dto/access.js +2 -0
- package/dist/shared/dto/access.js.map +1 -0
- package/dist/shared/dto/audit.d.ts +32 -0
- package/dist/shared/dto/audit.d.ts.map +1 -0
- package/dist/shared/dto/audit.js +3 -0
- package/dist/shared/dto/audit.js.map +1 -0
- package/dist/shared/dto/auth.d.ts +2 -0
- package/dist/shared/dto/auth.d.ts.map +1 -1
- package/dist/shared/dto/auth.js +5 -1
- package/dist/shared/dto/auth.js.map +1 -1
- package/dist/shared/dto/backend.d.ts +50 -0
- package/dist/shared/dto/backend.d.ts.map +1 -0
- package/dist/shared/dto/backend.js +3 -0
- package/dist/shared/dto/backend.js.map +1 -0
- package/dist/shared/dto/backups.d.ts +99 -0
- package/dist/shared/dto/backups.d.ts.map +1 -1
- package/dist/shared/dto/cluster.d.ts +44 -0
- package/dist/shared/dto/cluster.d.ts.map +1 -0
- package/dist/shared/dto/cluster.js +3 -0
- package/dist/shared/dto/cluster.js.map +1 -0
- package/dist/shared/dto/files.d.ts +328 -0
- package/dist/shared/dto/files.d.ts.map +1 -0
- package/dist/shared/dto/files.js +2 -0
- package/dist/shared/dto/files.js.map +1 -0
- package/dist/shared/dto/health.d.ts +59 -0
- package/dist/shared/dto/health.d.ts.map +1 -1
- package/dist/shared/dto/installed-release.d.ts +40 -0
- package/dist/shared/dto/installed-release.d.ts.map +1 -0
- package/dist/shared/dto/installed-release.js +2 -0
- package/dist/shared/dto/installed-release.js.map +1 -0
- package/dist/shared/dto/kernel-catalog.d.ts +11 -0
- package/dist/shared/dto/kernel-catalog.d.ts.map +1 -0
- package/dist/shared/dto/kernel-catalog.js +2 -0
- package/dist/shared/dto/kernel-catalog.js.map +1 -0
- package/dist/shared/dto/logs.d.ts +21 -0
- package/dist/shared/dto/logs.d.ts.map +1 -1
- package/dist/shared/dto/metrics.d.ts +4 -64
- package/dist/shared/dto/metrics.d.ts.map +1 -1
- package/dist/shared/dto/metrics.js +0 -15
- package/dist/shared/dto/metrics.js.map +1 -1
- package/dist/shared/dto/nodes.d.ts +98 -24
- package/dist/shared/dto/nodes.d.ts.map +1 -1
- package/dist/shared/dto/pipelines.d.ts +22 -1
- package/dist/shared/dto/pipelines.d.ts.map +1 -1
- package/dist/shared/dto/project.d.ts +289 -30
- package/dist/shared/dto/project.d.ts.map +1 -1
- package/dist/shared/dto/releases.d.ts +59 -0
- package/dist/shared/dto/releases.d.ts.map +1 -0
- package/dist/shared/dto/releases.js +2 -0
- package/dist/shared/dto/releases.js.map +1 -0
- package/dist/shared/dto/services.d.ts +201 -37
- package/dist/shared/dto/services.d.ts.map +1 -1
- package/dist/shared/dto/services.js +15 -6
- package/dist/shared/dto/services.js.map +1 -1
- package/dist/shared/dto/stack-diagnosis.d.ts +20 -0
- package/dist/shared/dto/stack-diagnosis.d.ts.map +1 -0
- package/dist/shared/dto/stack-diagnosis.js +8 -0
- package/dist/shared/dto/stack-diagnosis.js.map +1 -0
- package/dist/shared/dto/stack-execution.d.ts +65 -0
- package/dist/shared/dto/stack-execution.d.ts.map +1 -0
- package/dist/shared/dto/stack-execution.js +33 -0
- package/dist/shared/dto/stack-execution.js.map +1 -0
- package/dist/shared/dto/system-info.d.ts +11 -0
- package/dist/shared/dto/system-info.d.ts.map +1 -1
- package/dist/shared/dto/system-info.js +7 -1
- package/dist/shared/dto/system-info.js.map +1 -1
- package/dist/shared/dto/terminal.d.ts +40 -0
- package/dist/shared/dto/terminal.d.ts.map +1 -0
- package/dist/shared/dto/terminal.js +2 -0
- package/dist/shared/dto/terminal.js.map +1 -0
- package/dist/shared/dto/topology.d.ts +96 -0
- package/dist/shared/dto/topology.d.ts.map +1 -0
- package/dist/shared/dto/topology.js +2 -0
- package/dist/shared/dto/topology.js.map +1 -0
- package/dist/shared/emergency-output.d.ts +3 -0
- package/dist/shared/emergency-output.d.ts.map +1 -0
- package/dist/shared/emergency-output.js +22 -0
- package/dist/shared/emergency-output.js.map +1 -0
- package/dist/shared/env-config.d.ts +2 -0
- package/dist/shared/env-config.d.ts.map +1 -1
- package/dist/shared/env-config.js +1 -0
- package/dist/shared/env-config.js.map +1 -1
- package/dist/shared/events.d.ts +2 -0
- package/dist/shared/events.d.ts.map +1 -1
- package/dist/shared/events.js.map +1 -1
- package/dist/shared/execution-identity.d.ts +39 -0
- package/dist/shared/execution-identity.d.ts.map +1 -0
- package/dist/shared/execution-identity.js +160 -0
- package/dist/shared/execution-identity.js.map +1 -0
- package/dist/shared/file-policy.d.ts +5 -0
- package/dist/shared/file-policy.d.ts.map +1 -0
- package/dist/shared/file-policy.js +18 -0
- package/dist/shared/file-policy.js.map +1 -0
- package/dist/shared/framework-source.d.ts +11 -0
- package/dist/shared/framework-source.d.ts.map +1 -0
- package/dist/shared/framework-source.js +31 -0
- package/dist/shared/framework-source.js.map +1 -0
- package/dist/shared/fresh-module-reader.d.ts +40 -0
- package/dist/shared/fresh-module-reader.d.ts.map +1 -0
- package/dist/shared/fresh-module-reader.js +257 -0
- package/dist/shared/fresh-module-reader.js.map +1 -0
- package/dist/shared/host-boot-identity.d.ts +12 -0
- package/dist/shared/host-boot-identity.d.ts.map +1 -0
- package/dist/shared/host-boot-identity.js +89 -0
- package/dist/shared/host-boot-identity.js.map +1 -0
- package/dist/shared/kernel-file-lock.d.ts +3 -0
- package/dist/shared/kernel-file-lock.d.ts.map +1 -0
- package/dist/shared/kernel-file-lock.js +53 -0
- package/dist/shared/kernel-file-lock.js.map +1 -0
- package/dist/shared/linux-file-lock.d.ts +4 -0
- package/dist/shared/linux-file-lock.d.ts.map +1 -0
- package/dist/shared/linux-file-lock.js +36 -0
- package/dist/shared/linux-file-lock.js.map +1 -0
- package/dist/shared/local-infra-host.d.ts +2 -2
- package/dist/shared/local-infra-host.js +2 -2
- package/dist/shared/log-ingestion-reading.d.ts +9 -0
- package/dist/shared/log-ingestion-reading.d.ts.map +1 -0
- package/dist/shared/log-ingestion-reading.js +32 -0
- package/dist/shared/log-ingestion-reading.js.map +1 -0
- package/dist/shared/log-levels.d.ts +19 -0
- package/dist/shared/log-levels.d.ts.map +1 -0
- package/dist/shared/log-levels.js +23 -0
- package/dist/shared/log-levels.js.map +1 -0
- package/dist/shared/metrics-storage-reading.d.ts +8 -0
- package/dist/shared/metrics-storage-reading.d.ts.map +1 -0
- package/dist/shared/metrics-storage-reading.js +27 -0
- package/dist/shared/metrics-storage-reading.js.map +1 -0
- package/dist/shared/pnpm.d.ts.map +1 -1
- package/dist/shared/pnpm.js +7 -0
- package/dist/shared/pnpm.js.map +1 -1
- package/dist/shared/pool-scale.d.ts +10 -0
- package/dist/shared/pool-scale.d.ts.map +1 -0
- package/dist/shared/pool-scale.js +25 -0
- package/dist/shared/pool-scale.js.map +1 -0
- package/dist/shared/private-files.d.ts +10 -22
- package/dist/shared/private-files.d.ts.map +1 -1
- package/dist/shared/private-files.js +95 -82
- package/dist/shared/private-files.js.map +1 -1
- package/dist/shared/relay-reading.d.ts +19 -0
- package/dist/shared/relay-reading.d.ts.map +1 -0
- package/dist/shared/relay-reading.js +22 -0
- package/dist/shared/relay-reading.js.map +1 -0
- package/dist/shared/release-reading.d.ts +124 -32
- package/dist/shared/release-reading.d.ts.map +1 -1
- package/dist/shared/release-reading.js +208 -28
- package/dist/shared/release-reading.js.map +1 -1
- package/dist/shared/roles.d.ts +19 -18
- package/dist/shared/roles.d.ts.map +1 -1
- package/dist/shared/roles.js +21 -26
- package/dist/shared/roles.js.map +1 -1
- package/dist/shared/runtime-artifact.d.ts +20 -0
- package/dist/shared/runtime-artifact.d.ts.map +1 -0
- package/dist/shared/runtime-artifact.js +83 -0
- package/dist/shared/runtime-artifact.js.map +1 -0
- package/dist/shared/stack-reading.d.ts +18 -0
- package/dist/shared/stack-reading.d.ts.map +1 -0
- package/dist/shared/stack-reading.js +15 -0
- package/dist/shared/stack-reading.js.map +1 -0
- package/dist/shared/sync-reading.d.ts +30 -38
- package/dist/shared/sync-reading.d.ts.map +1 -1
- package/dist/shared/sync-reading.js +90 -29
- package/dist/shared/sync-reading.js.map +1 -1
- package/dist/shared/tokens.d.ts +3 -1
- package/dist/shared/tokens.d.ts.map +1 -1
- package/dist/shared/tokens.js +1 -0
- package/dist/shared/tokens.js.map +1 -1
- package/dist/supervisor/restart-policy.d.ts.map +1 -1
- package/dist/supervisor/restart-policy.js +7 -5
- package/dist/supervisor/restart-policy.js.map +1 -1
- package/dist/webapp/console-container.d.ts +7 -0
- package/dist/webapp/console-container.d.ts.map +1 -0
- package/dist/webapp/console-container.js +23 -0
- package/dist/webapp/console-container.js.map +1 -0
- package/dist/webapp/webapp.service.d.ts.map +1 -1
- package/dist/webapp/webapp.service.js +71 -24
- package/dist/webapp/webapp.service.js.map +1 -1
- package/dist/workers/health-monitor.service.d.ts.map +1 -1
- package/dist/workers/health-monitor.service.js +8 -1
- package/dist/workers/health-monitor.service.js.map +1 -1
- package/dist/workers/types.d.ts +2 -0
- package/dist/workers/types.d.ts.map +1 -1
- package/package.json +109 -43
- package/webapp/dist/BUILD.json +4 -3
- package/webapp/dist/assets/access-48hkC2-P.js +1 -0
- package/webapp/dist/assets/access-jWkHxGc7.js +1 -0
- package/webapp/dist/assets/admin-data-table-BKSDI8sU.js +1 -0
- package/webapp/dist/assets/alert-expression-CSblScoa.js +1 -0
- package/webapp/dist/assets/alert-notify-settings-C_CHZrFm.js +1 -0
- package/webapp/dist/assets/alert-uBG6r9NW.js +1 -0
- package/webapp/dist/assets/alerts-CuPQ9YQJ.js +1 -0
- package/webapp/dist/assets/app-address-BCXfN59F.js +1 -0
- package/webapp/dist/assets/apps-Drio028B.js +1 -0
- package/webapp/dist/assets/audit-npSFJECe.js +1 -0
- package/webapp/dist/assets/auth-CcgjP7hR.js +1 -0
- package/webapp/dist/assets/card-DkH32gMY.js +1 -0
- package/webapp/dist/assets/chart-BpGXlCJc.js +2 -0
- package/webapp/dist/assets/charts-BY7qI6s2.js +41 -0
- package/webapp/dist/assets/charts-CpBdHU_A.js +56 -0
- package/webapp/dist/assets/client-DFnuv2z3.js +3 -0
- package/webapp/dist/assets/code-editor-DK6Tmmxv.js +4 -0
- package/webapp/dist/assets/comparison-bars-BtC0dHjF.js +1 -0
- package/webapp/dist/assets/confirm-dialog-BbGsAz7_.js +1 -0
- package/webapp/dist/assets/console-keyboard-D3YCA0yU.js +1 -0
- package/webapp/dist/assets/constants-CyhngVXh.js +1 -0
- package/webapp/dist/assets/containers-Bl8gAyZX.js +3 -0
- package/webapp/dist/assets/context-FIuwpd9_.js +1 -0
- package/webapp/dist/assets/controls-C5W8gHU4.js +1 -0
- package/webapp/dist/assets/daemon-memory-BiEtr9jr.js +1 -0
- package/webapp/dist/assets/dashboard-UHlet3H9.js +1 -0
- package/webapp/dist/assets/dashboard-builder-67PZFqhE.js +1 -0
- package/webapp/dist/assets/dayjs.min-DHwAvH5f.js +1 -0
- package/webapp/dist/assets/deploy-release-dialog-BOXcD5Wo.js +1 -0
- package/webapp/dist/assets/deployments-qV20Ak0P.js +1 -0
- package/webapp/dist/assets/detail-B4TiBfxU.js +8 -0
- package/webapp/dist/assets/detail-B85-Wees.js +2 -0
- package/webapp/dist/assets/detail-CYS81K6Y.js +1 -0
- package/webapp/dist/assets/dist-BUDn8hLh.js +11 -0
- package/webapp/dist/assets/dist-Ci5pYRbx.js +1 -0
- package/webapp/dist/assets/dist-DQmjHv4-.js +1 -0
- package/webapp/dist/assets/dist-De9Fty7a.js +1 -0
- package/webapp/dist/assets/dist-DsGmKwmk.js +23 -0
- package/webapp/dist/assets/empty-content-7jOFhG4C.js +1 -0
- package/webapp/dist/assets/error-boundary-CLLI8PWw.js +3 -0
- package/webapp/dist/assets/event-fields-CiyqrRO8.js +1 -0
- package/webapp/dist/assets/files-DigwXndm.js +2 -0
- package/webapp/dist/assets/formatters-RnRy4bXm.js +1 -0
- package/webapp/dist/assets/icons-OboZgWSt.js +1 -0
- package/webapp/dist/assets/index-uq8Dkk4c.js +1 -0
- package/webapp/dist/assets/label-B1SF0RTL.js +1 -0
- package/webapp/dist/assets/log-level-filter-Bcn31D1F.js +1 -0
- package/webapp/dist/assets/log-location-DuBjh6HQ.js +1 -0
- package/webapp/dist/assets/logs-BqHOufk8.js +4 -0
- package/webapp/dist/assets/metric-chart-M-kI-XOt.js +1 -0
- package/webapp/dist/assets/metric-series-WxePniyw.js +1 -0
- package/webapp/dist/assets/metrics-QIQaF7iS.js +1 -0
- package/webapp/dist/assets/model-ZEXEHm0l.js +2 -0
- package/webapp/dist/assets/mui-6ssfcZcz.js +1 -0
- package/webapp/dist/assets/mui-AnEgAVjz.js +52 -0
- package/webapp/dist/assets/mui-B1jrje6Z.js +1 -0
- package/webapp/dist/assets/mui-BIz4LSVs.js +1 -0
- package/webapp/dist/assets/mui-BTN91yBK.js +28 -0
- package/webapp/dist/assets/mui-BXSXx9c9.js +124 -0
- package/webapp/dist/assets/mui-BwsIrivo.js +37 -0
- package/webapp/dist/assets/mui-C_hromOc.js +1 -0
- package/webapp/dist/assets/mui-Cd711nOk.js +4 -0
- package/webapp/dist/assets/mui-CdB_p2lt.js +10 -0
- package/webapp/dist/assets/mui-DyT2n1lx.js +1 -0
- package/webapp/dist/assets/mui-agG5dued.js +1 -0
- package/webapp/dist/assets/mui-y44PQOfG.js +1 -0
- package/webapp/dist/assets/nodes-BJPk02vv.js +7 -0
- package/webapp/dist/assets/omnitron-logo-koMCbsCv.js +1 -0
- package/webapp/dist/assets/operation-status-Bm1x1wTz.js +1 -0
- package/webapp/dist/assets/page-breadcrumbs-fR7Jmpp4.js +1 -0
- package/webapp/dist/assets/pipelines-T6Ch9LTw.js +3 -0
- package/webapp/dist/assets/popper-m_9b0JRf.js +1 -0
- package/webapp/dist/assets/preload-helper-DzCYNvzR.js +1 -0
- package/webapp/dist/assets/project.store--G5pIF-J.js +1 -0
- package/webapp/dist/assets/projects-YoBzcQ24.js +1 -0
- package/webapp/dist/assets/react-BN8qLIAi.js +9 -0
- package/webapp/dist/assets/react-CEuifcaQ.js +1 -0
- package/webapp/dist/assets/react-apexcharts.esm-DoOt-pu4.js +1 -0
- package/webapp/dist/assets/realtime.store-CBDO4Abj.js +1 -0
- package/webapp/dist/assets/release-bits-B2a_wbft.js +1 -0
- package/webapp/dist/assets/release-build-panel-BQmIdKW9.js +1 -0
- package/webapp/dist/assets/release-deployment-state-DRrEI2pP.js +1 -0
- package/webapp/dist/assets/release-wire-f2VuBoBF.js +1 -0
- package/webapp/dist/assets/releases-Bp3CDgmo.js +2 -0
- package/webapp/dist/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
- package/webapp/dist/assets/search-input-C0MHI8vO.js +1 -0
- package/webapp/dist/assets/services-BddvcFi8.js +1 -0
- package/webapp/dist/assets/settings-B3egRvbI.js +1 -0
- package/webapp/dist/assets/settled-pair-B-wgSZl6.js +1 -0
- package/webapp/dist/assets/shared-styles-C0vN3I7_.js +1 -0
- package/webapp/dist/assets/shell-command-C3pXf1vi.js +1 -0
- package/webapp/dist/assets/sign-in-9q-Nfy83.js +1 -0
- package/webapp/dist/assets/skeleton-BmWDJWF9.js +1 -0
- package/webapp/dist/assets/snackbar-DSPmjMyi.js +1 -0
- package/webapp/dist/assets/stack-health-status-BIv2C10x.js +1 -0
- package/webapp/dist/assets/stack-start-notice-BVnMOwRQ.js +1 -0
- package/webapp/dist/assets/stacks-IZ1mfizi.js +1 -0
- package/webapp/dist/assets/storage-CKXnhqkt.js +1 -0
- package/webapp/dist/assets/store-bqUBn2A4.js +1 -0
- package/webapp/dist/assets/sync-reading-CN0jPKAg.js +1 -0
- package/webapp/dist/assets/system-info-BFKNM-hf.js +1 -0
- package/webapp/dist/assets/table-BOWT6Y9d.js +1 -0
- package/webapp/dist/assets/table-viewport-Bp4pq-c_.js +1 -0
- package/webapp/dist/assets/tabs-k5NPGLnJ.js +1 -0
- package/webapp/dist/assets/terminal-BrP-ENHg.css +1 -0
- package/webapp/dist/assets/terminal-QWBau-Us.js +62 -0
- package/webapp/dist/assets/terminal-input-b7f2VoE_.js +2 -0
- package/webapp/dist/assets/topology-CPey5dP6.js +8 -0
- package/webapp/dist/assets/topology-metadata-dialog-BivuU2y6.js +2 -0
- package/webapp/dist/assets/topology-routes.worker-BoYrIzAH.js +1 -0
- package/webapp/dist/assets/traces-By0rdfDC.js +1 -0
- package/webapp/dist/assets/use-copy-to-clipboard-BWYpToS3.js +1 -0
- package/webapp/dist/assets/use-polled-resource-B22hhr5L.js +1 -0
- package/webapp/dist/assets/use-stack-context-UkBsF-l7.js +1 -0
- package/webapp/dist/assets/webapp-BnxOVfrc.css +1 -0
- package/webapp/dist/assets/webapp-D-UhjFhS.js +35 -0
- package/webapp/dist/console-version.json +1 -0
- package/webapp/dist/index.html +54 -7
- package/dist/release/daemon-omni.d.ts +0 -41
- package/dist/release/daemon-omni.d.ts.map +0 -1
- package/dist/release/daemon-omni.js +0 -59
- package/dist/release/daemon-omni.js.map +0 -1
- package/dist/services/netron-telemetry-transport.d.ts +0 -42
- package/dist/services/netron-telemetry-transport.d.ts.map +0 -1
- package/dist/services/netron-telemetry-transport.js +0 -93
- package/dist/services/netron-telemetry-transport.js.map +0 -1
- package/webapp/dist/assets/FormControlLabel-BrH2diyl.js +0 -1
- package/webapp/dist/assets/Grid-BZ7xhoHQ.js +0 -1
- package/webapp/dist/assets/Switch-DGSAxQPM.js +0 -1
- package/webapp/dist/assets/alerts-sXqHJbsa.js +0 -1
- package/webapp/dist/assets/apps-B_Ah8Flh.js +0 -1
- package/webapp/dist/assets/audit-YXihwyui.js +0 -1
- package/webapp/dist/assets/client-CGbBvy9k.js +0 -1
- package/webapp/dist/assets/constants-veKGFkC6.js +0 -1
- package/webapp/dist/assets/containers-Di4NOTLf.js +0 -2
- package/webapp/dist/assets/daemon-memory-CCPZ4cV_.js +0 -1
- package/webapp/dist/assets/dashboard-Cfuf95N3.js +0 -1
- package/webapp/dist/assets/dashboard-builder-CowaYb-M.js +0 -1
- package/webapp/dist/assets/deploy-release-dialog-D6C-XcKY.js +0 -1
- package/webapp/dist/assets/deployments-ChTq5kCw.js +0 -1
- package/webapp/dist/assets/detail-BsGWv7Dj.js +0 -1
- package/webapp/dist/assets/detail-CyVf_cw5.js +0 -2
- package/webapp/dist/assets/detail-D027j6aO.js +0 -8
- package/webapp/dist/assets/formatters-BqjL73N3.js +0 -1
- package/webapp/dist/assets/fs-Ccl3sAcp.js +0 -1
- package/webapp/dist/assets/index-B8AHIPc-.css +0 -1
- package/webapp/dist/assets/index-DiZbrwxT.js +0 -414
- package/webapp/dist/assets/logs-L2b8Oqmo.js +0 -12
- package/webapp/dist/assets/maplibre-gl-MlmG2kbn.js +0 -801
- package/webapp/dist/assets/metrics-CQgjz2Lz.js +0 -2
- package/webapp/dist/assets/nodes-Nwia2Ty6.js +0 -7
- package/webapp/dist/assets/pipelines-DCu_rXOr.js +0 -4
- package/webapp/dist/assets/projects-Bb4aV76h.js +0 -1
- package/webapp/dist/assets/react-CwJFpaho.js +0 -1
- package/webapp/dist/assets/react-apexcharts.esm-Dy0Fez42.js +0 -1956
- package/webapp/dist/assets/release-bits-Dj4EOhhb.js +0 -1
- package/webapp/dist/assets/release-build-panel-D6OCVlhT.js +0 -1
- package/webapp/dist/assets/releases-CnAt3yO6.js +0 -2
- package/webapp/dist/assets/rolldown-runtime-hePW80VL.js +0 -1
- package/webapp/dist/assets/settings-DU-mjiwb.js +0 -1
- package/webapp/dist/assets/settled-pair-C7SjZuns.js +0 -1
- package/webapp/dist/assets/sign-in-Bcvjrr4C.js +0 -1
- package/webapp/dist/assets/stacks-B-e8p50I.js +0 -1
- package/webapp/dist/assets/sync-reading-C3PnJ7AX.js +0 -1
- package/webapp/dist/assets/system-info-BtE3dgfw.js +0 -1
- package/webapp/dist/assets/topology-Cwch-uF1.js +0 -12
- package/webapp/dist/assets/traces-BbShR-4P.js +0 -1
- package/webapp/dist/assets/use-stack-context-CZUgPq88.js +0 -1
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { observeStackHealth } from '../project/stack-health.js';
|
|
11
|
+
import { LocalReleaseSelection } from '../project/local-release-selection.js';
|
|
12
|
+
import { resolveAppPath } from '../config/app-path.js';
|
|
13
|
+
import { infrastructureOwnership, ownsStackInfrastructure } from '../project/infrastructure-ownership.js';
|
|
14
|
+
import { InstalledStackResume, resumeDestinations } from '../project/installed-stack-resume.js';
|
|
15
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
16
|
+
import { diagnoseContainers } from '../project/stack-diagnosis.js';
|
|
17
|
+
import { diagnosisStatus } from '../shared/dto/stack-diagnosis.js';
|
|
18
|
+
import { buildInfraFromRequirements, requirementsFromDefinitions } from '../project/requirements-scanner.js';
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
20
|
+
import { NodeStackStop, stackDefinitionFingerprint } from '../project/node-stack-stop.js';
|
|
21
|
+
import { stackStopReview } from '../project/stack-stop-review.js';
|
|
22
|
+
import { inspectStackContainers, removeContainer } from '../infrastructure/container-runtime.js';
|
|
23
|
+
import { stackExecutionKey } from '../shared/dto/stack-execution.js';
|
|
24
|
+
import { publicStack } from '../project/public-stack.js';
|
|
25
|
+
import { TopologyStateStore } from '../project/topology-state.js';
|
|
26
|
+
import { TopologyControl } from '../project/topology-control.js';
|
|
27
|
+
import { artifactVolumeBindings, artifactVolumeEntries, bindArtifactVolumes } from '../release/artifact-volumes.js';
|
|
28
|
+
import { mergeInfrastructure, mergeInferredInfrastructure } from '../infrastructure/merge-config.js';
|
|
29
|
+
export { mergeInfrastructure } from '../infrastructure/merge-config.js';
|
|
30
|
+
import { currentActor, describeFailure } from './audit.service.js';
|
|
31
|
+
import { publicLaunchFailure } from '../project/launch-failure.js';
|
|
32
|
+
import { nodeApplicationNames, resolveNodePlacements, stackNodeKey } from '../project/stack-placement.js';
|
|
33
|
+
import { StackTargets } from '../project/stack-targets.js';
|
|
34
|
+
import { configurationInfo, publicStackName, validateConfigurationNames } from '../project/configuration-catalog.js';
|
|
35
|
+
import { nodePlacement, observeStack } from '../project/stack-observation.js';
|
|
36
|
+
import { getCachedBootstrapDefinition } from '../orchestrator/bootstrap-loader.js';
|
|
37
|
+
import { BootstrapMetadataStore, readBootstrapMetadata, metadataSourceChanged, TOPOLOGY_REFRESH_TIMEOUT_MS } from '../project/bootstrap-metadata.js';
|
|
38
|
+
import { appDependencies } from '../project/dependency-topology.js';
|
|
39
|
+
import { isControlPlaneWorkspace } from '../project/workspace-kind.js';
|
|
40
|
+
import { RecoveryMutation, RecoveryBusyError } from '../daemon/recovery-state.js';
|
|
1
41
|
/**
|
|
2
42
|
* ProjectService — Manages projects and their stacks
|
|
3
43
|
*
|
|
@@ -24,6 +64,7 @@ import { reportPhases } from '../project/deploy-phases.js';
|
|
|
24
64
|
import { effectiveAppName } from '../orchestrator/orchestrator.service.js';
|
|
25
65
|
import { summariseSync } from './sync-summary.js';
|
|
26
66
|
import { StackInfrastructureManager } from '../infrastructure/stack-infra-manager.js';
|
|
67
|
+
import { describeProvisioning, summariseProvisioning, stackServiceObservation } from '../infrastructure/provisioning-outcome.js';
|
|
27
68
|
import { waitForPostgres } from './wait-for-postgres.js';
|
|
28
69
|
import { resolveStack, resolvedConfigToEnv } from '../project/config-resolver.js';
|
|
29
70
|
import { resolveStartupOrder } from '../orchestrator/dependency-resolver.js';
|
|
@@ -33,8 +74,9 @@ import { NODE_STACK } from '../project/node-app-config.js';
|
|
|
33
74
|
import { overlayCredentials } from '../infrastructure/node-credentials.js';
|
|
34
75
|
import { readDeclaredConfig, withDeclaredConfig } from '../project/declared-config.js';
|
|
35
76
|
import { bindService } from '../infrastructure/service-binding.js';
|
|
36
|
-
import { RemoteDeployer, stackNodeToDeployTarget, withNodeCredentials, } from './remote-deployer.service.js';
|
|
77
|
+
import { RemoteDeployer, stackNodeToDeployTarget, withNodeCredentials, withDeploymentProgress, } from './remote-deployer.service.js';
|
|
37
78
|
import { withNodeLeases } from './node-deploy-lease.js';
|
|
79
|
+
import { readDeploymentProgress } from '../shared/release-reading.js';
|
|
38
80
|
import { DEFAULT_MAINTENANCE_TIMING, MaintenanceLock } from './maintenance-lock.js';
|
|
39
81
|
import { gatewayRedisDb } from '../infrastructure/gateway-redis-db.js';
|
|
40
82
|
/**
|
|
@@ -44,7 +86,21 @@ import { gatewayRedisDb } from '../infrastructure/gateway-redis-db.js';
|
|
|
44
86
|
*/
|
|
45
87
|
const MAINTENANCE_PROBE_PATHS = ['/', '/api/main/health'];
|
|
46
88
|
import { migrationOutput } from './migration-output.js';
|
|
89
|
+
import { redactDiagnosticText } from '@omnitron-dev/titan/diagnostics';
|
|
90
|
+
import { COMPILED_MIGRATION_ENTRY, localMigrationTargets, requestedMigrations, migrationDatabaseEnvironment, validateMigrationDestinations, validateMigrationOwners, orderMigrationPlacements } from './migration-target.js';
|
|
91
|
+
import { executeLocalMigration } from './migration-process.js';
|
|
92
|
+
import { expandServiceDeclarations, normalizeInfraConfig } from '../infrastructure/config-normalizer.js';
|
|
93
|
+
import { createDefaultRegistry } from '../infrastructure/presets/index.js';
|
|
94
|
+
import { readArchiveInfrastructure } from '../release/infrastructure.js';
|
|
95
|
+
import { releaseArtifactOrigins } from '../release/origin.js';
|
|
47
96
|
import { ExecutionService } from '../execution/execution.service.js';
|
|
97
|
+
class LocalReleaseStopRequired extends Error {
|
|
98
|
+
}
|
|
99
|
+
function sameLaunchCode(actual, expected) {
|
|
100
|
+
if (!expected.releaseRoot)
|
|
101
|
+
return !actual.releaseRoot;
|
|
102
|
+
return actual.bootstrap === expected.bootstrap && actual.script === expected.script && actual.releaseRoot === expected.releaseRoot;
|
|
103
|
+
}
|
|
48
104
|
/** Whether two paths name one directory — symlinks and `..` resolved. */
|
|
49
105
|
function samePath(a, b) {
|
|
50
106
|
const real = (p) => {
|
|
@@ -57,25 +113,6 @@ function samePath(a, b) {
|
|
|
57
113
|
};
|
|
58
114
|
return real(a) === real(b);
|
|
59
115
|
}
|
|
60
|
-
/**
|
|
61
|
-
* The infrastructure a stack actually gets: the ecosystem's, plus its own.
|
|
62
|
-
*
|
|
63
|
-
* Shallow for the top-level keys — a stack that declares `postgres` replaces
|
|
64
|
-
* the ecosystem's — and merged for `services`, because those are named
|
|
65
|
-
* things and a stack adding one must not remove the rest.
|
|
66
|
-
*/
|
|
67
|
-
export function mergeInfrastructure(ecosystemConfig, stackConfig) {
|
|
68
|
-
if (!stackConfig.infrastructure && !ecosystemConfig.infrastructure)
|
|
69
|
-
return undefined;
|
|
70
|
-
return {
|
|
71
|
-
...ecosystemConfig.infrastructure,
|
|
72
|
-
...stackConfig.infrastructure,
|
|
73
|
-
services: {
|
|
74
|
-
...ecosystemConfig.infrastructure?.services,
|
|
75
|
-
...stackConfig.infrastructure?.services,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
116
|
// =============================================================================
|
|
80
117
|
// ProjectService
|
|
81
118
|
// =============================================================================
|
|
@@ -99,22 +136,99 @@ const CREDENTIALED_SERVICES = ['postgres', 'redis', 'minio'];
|
|
|
99
136
|
export class ProjectService extends EventEmitter {
|
|
100
137
|
logger;
|
|
101
138
|
orchestrator;
|
|
139
|
+
daemonStateStore;
|
|
140
|
+
recoveryRegistryDigest() { return this.registry.recoveryDigest(); }
|
|
141
|
+
/** Read-only bookkeeping; no timers are stopped and no Docker call runs. */
|
|
142
|
+
captureRecoveryState() {
|
|
143
|
+
if (this.rereading?.size || this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.size || this.stopsInFlight.size || this.nodeStackStop.busy || this.reconcilingStacks ||
|
|
144
|
+
[...this.stackStates.values()].some(state => state.status === 'starting' || state.status === 'stopping')) {
|
|
145
|
+
throw new RecoveryBusyError('Project ownership is changing; a recovery snapshot is not ready');
|
|
146
|
+
}
|
|
147
|
+
return this.projectSnapshot(this.infraManager.captureRecoveryState(), this.resumeTimer !== null);
|
|
148
|
+
}
|
|
149
|
+
projectSnapshot(infrastructure, reconciling) {
|
|
150
|
+
return structuredClone({
|
|
151
|
+
projects: [...this.configRegistry], infrastructure,
|
|
152
|
+
bootstrapTopology: [...this.bootstrapTopology],
|
|
153
|
+
resumeBackoff: [...this.resumeBackoff], reconciling,
|
|
154
|
+
stacks: [...this.stackStates].map(([key, { infraService: _infra, ...state }]) => [key, state]),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** Capture controllers' bookkeeping, retaining native application ownership in the execution host. */
|
|
158
|
+
async prepareHandoff() {
|
|
159
|
+
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')) {
|
|
160
|
+
throw new Error('A stack lifecycle operation is active; finish it before transferring the backend');
|
|
161
|
+
}
|
|
162
|
+
this.handoffReconciling = this.resumeTimer !== null;
|
|
163
|
+
this.ownershipFrozen = true;
|
|
164
|
+
if (this.resumeTimer)
|
|
165
|
+
clearInterval(this.resumeTimer);
|
|
166
|
+
this.resumeTimer = null;
|
|
167
|
+
try {
|
|
168
|
+
const infrastructure = await this.infraManager.prepareHandoff();
|
|
169
|
+
return this.projectSnapshot(infrastructure, this.handoffReconciling);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.resumeAfterHandoff();
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
resumeAfterHandoff() {
|
|
177
|
+
this.ownershipFrozen = false;
|
|
178
|
+
this.infraManager.resumeAfterHandoff();
|
|
179
|
+
if (this.handoffReconciling)
|
|
180
|
+
this.startEnabledStacksReconciler();
|
|
181
|
+
this.handoffReconciling = false;
|
|
182
|
+
}
|
|
183
|
+
async adoptHandoff(snapshot) {
|
|
184
|
+
if (this.stackStates.size)
|
|
185
|
+
throw new Error('Project runtime is not empty before adoption');
|
|
186
|
+
for (const [name, project] of snapshot.projects) {
|
|
187
|
+
const registered = this.registry.get(name);
|
|
188
|
+
if (!registered)
|
|
189
|
+
throw new Error(`Project disappeared during backend transfer: ${name}`);
|
|
190
|
+
this.configRegistry.set(name, project);
|
|
191
|
+
}
|
|
192
|
+
for (const [key, definition] of snapshot.bootstrapTopology ?? [])
|
|
193
|
+
this.bootstrapTopology.set(key, definition);
|
|
194
|
+
await this.infraManager.adoptHandoff(snapshot.infrastructure);
|
|
195
|
+
for (const [key, value] of snapshot.resumeBackoff)
|
|
196
|
+
this.resumeBackoff.set(key, value);
|
|
197
|
+
this.handoffReconciling = snapshot.reconciling;
|
|
198
|
+
this.ownershipFrozen = true;
|
|
199
|
+
for (const [key, state] of snapshot.stacks) {
|
|
200
|
+
this.stackStates.set(key, { ...state, infraService: this.infraManager.getInstance(state.project, state.stack) });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
102
203
|
registry;
|
|
204
|
+
topologyControl;
|
|
205
|
+
bootstrapTopology = new Map();
|
|
206
|
+
bootstrapMetadata;
|
|
103
207
|
configRegistry = new Map();
|
|
104
208
|
stackStates = new Map();
|
|
105
209
|
infraManager;
|
|
210
|
+
nodeStackStop;
|
|
211
|
+
clusterStop;
|
|
212
|
+
installedResume;
|
|
213
|
+
coordinatingResume = false;
|
|
214
|
+
get resumingInstalled() { return this.coordinatingResume || this.installedResume?.busy === true; }
|
|
215
|
+
hostedInfrastructure = () => undefined;
|
|
216
|
+
drainHostedProvisioning = async () => { };
|
|
217
|
+
stopsInFlight = new Map();
|
|
106
218
|
slaveConnector = null;
|
|
107
219
|
deployer;
|
|
108
220
|
// Enabled-stacks reconciler state (see startEnabledStacksReconciler)
|
|
109
221
|
resumeTimer = null;
|
|
110
222
|
resumeBackoff = new Map();
|
|
111
|
-
|
|
223
|
+
reconcilingStacks = false;
|
|
224
|
+
handoffReconciling = false;
|
|
225
|
+
ownershipFrozen = false;
|
|
112
226
|
syncService;
|
|
113
227
|
secrets;
|
|
114
228
|
audit;
|
|
115
229
|
constructor(logger, orchestrator, daemonStateStore,
|
|
116
230
|
/**
|
|
117
|
-
*
|
|
231
|
+
* Optional local collaborators — by NAME. Both daemon roles have a vault. These were four more
|
|
118
232
|
* positional parameters, and the daemon's factory passed them through an
|
|
119
233
|
* `any`: `new ProjectService(logger, orchestrator, dStore, fleet,
|
|
120
234
|
* undefined, secrets, audit)`. A slot removed or reordered there moves
|
|
@@ -126,6 +240,7 @@ export class ProjectService extends EventEmitter {
|
|
|
126
240
|
super();
|
|
127
241
|
this.logger = logger;
|
|
128
242
|
this.orchestrator = orchestrator;
|
|
243
|
+
this.daemonStateStore = daemonStateStore;
|
|
129
244
|
this.syncService = options.syncService;
|
|
130
245
|
this.secrets = options.secrets;
|
|
131
246
|
this.audit = options.audit;
|
|
@@ -134,11 +249,237 @@ export class ProjectService extends EventEmitter {
|
|
|
134
249
|
// pid-lock, etc.). The registry itself is unchanged in API;
|
|
135
250
|
// only its backing storage moved.
|
|
136
251
|
this.registry = new ProjectRegistry(daemonStateStore);
|
|
252
|
+
this.bootstrapMetadata = new BootstrapMetadataStore(daemonStateStore);
|
|
253
|
+
this.topologyControl = new TopologyControl(new TopologyStateStore(daemonStateStore), {
|
|
254
|
+
configuration: async (scope) => {
|
|
255
|
+
const config = await this.loadProjectConfig(scope.project);
|
|
256
|
+
const stack = this.resolveStacks(config, scope.project)[scope.stack];
|
|
257
|
+
if (!stack)
|
|
258
|
+
throw new Error(`Stack '${scope.stack}' not found`);
|
|
259
|
+
return { config, stack, apps: this.resolveStackApps(stack, config), userStacks: this.loadUserStacks(scope.project) };
|
|
260
|
+
},
|
|
261
|
+
observation: scope => this.withRemoteAppStatuses(scope.project, this.getStack(scope.project, scope.stack)),
|
|
262
|
+
dependencies: scope => this.getTopology(scope),
|
|
263
|
+
nodes: () => daemonStateStore.selectNodesSync().map(node => ({
|
|
264
|
+
id: node.id, name: node.name, host: node.host, port: node.port,
|
|
265
|
+
})),
|
|
266
|
+
});
|
|
137
267
|
this.infraManager = new StackInfrastructureManager(logger);
|
|
268
|
+
this.nodeStackStop = new NodeStackStop(daemonStateStore, {
|
|
269
|
+
execution: orchestrator,
|
|
270
|
+
definition: (scope, cached) => this.stopDefinition(scope, cached),
|
|
271
|
+
containers: scope => inspectStackContainers(scope),
|
|
272
|
+
disableResume: scope => this.registry.setStackEnabled(scope.project, scope.stack, false),
|
|
273
|
+
retireInfrastructure: async (scope) => {
|
|
274
|
+
await this.drainHostedProvisioning(scope);
|
|
275
|
+
await this.hostedInfrastructure()?.retireStack(scope);
|
|
276
|
+
await this.infraManager.getInstance(scope.project, scope.stack)?.retireStack(scope);
|
|
277
|
+
},
|
|
278
|
+
removeContainer: id => removeContainer(id, { graceful: true }),
|
|
279
|
+
});
|
|
138
280
|
// The deployer reaches nodes through the same SSH implementation as the
|
|
139
281
|
// health checks, which is the only one that can present a password or a
|
|
140
282
|
// key passphrase — see the note at the top of `remote-deployer.service.ts`.
|
|
141
283
|
this.deployer = new RemoteDeployer(logger, new ExecutionService(logger));
|
|
284
|
+
this.deployer.onProgress(event => {
|
|
285
|
+
if (event.project && event.stack)
|
|
286
|
+
this.emit('stack:deploy_progress', event.project, event.stack, event);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
setHostedInfrastructure(get, drain) {
|
|
290
|
+
this.hostedInfrastructure = get;
|
|
291
|
+
this.drainHostedProvisioning = drain;
|
|
292
|
+
}
|
|
293
|
+
/** Authority needed by an explicit start, including infrastructure provisioning. */
|
|
294
|
+
assertStartAuthority(scope, stack) {
|
|
295
|
+
if (this.clusterStop && (stack.type !== 'local' || stack.managedOrigin)) {
|
|
296
|
+
throw new Error('Deploying this allocation requires a recipient-fenced deployment adapter. To recover retained applications after a confirmed stop, use stack resume.');
|
|
297
|
+
}
|
|
298
|
+
this.assertStackMutable(stack.managedOrigin ?? scope);
|
|
299
|
+
}
|
|
300
|
+
assertStackMutable(scope) {
|
|
301
|
+
this.assertInfrastructureMutable(scope);
|
|
302
|
+
}
|
|
303
|
+
assertInfrastructureMutable(scope) {
|
|
304
|
+
if (this.clusterStop)
|
|
305
|
+
throw new Error('Direct cluster lifecycle changes require recipient-fenced authority. Use a reviewed stack stop or resume the installed applications.');
|
|
306
|
+
this.nodeStackStop.assertMutable(scope);
|
|
307
|
+
}
|
|
308
|
+
setClusterStop(recipient, election) {
|
|
309
|
+
this.nodeStackStop.setRecipient(recipient);
|
|
310
|
+
this.clusterStop = { recipient, election };
|
|
311
|
+
this.installedResume = new InstalledStackResume(recipient, this.nodeStackStop, this.daemonStateStore, this.orchestrator, async (scope) => {
|
|
312
|
+
const { runtime, fingerprint } = await this.stopDefinition({ ...scope, applications: true }, true);
|
|
313
|
+
if (!runtime)
|
|
314
|
+
throw new Error('No installed application allocation is bound to this stack');
|
|
315
|
+
const config = this.getLoadedConfig(runtime.project);
|
|
316
|
+
const stack = this.resolveStacks(config, runtime.project)[runtime.stack];
|
|
317
|
+
if (ownsStackInfrastructure(stack))
|
|
318
|
+
throw new Error('Installed recovery requires application-only infrastructure ownership');
|
|
319
|
+
const order = resolveStartupOrder(this.resolveStackApps(stack, config)).flat().map(app => app.name);
|
|
320
|
+
return { runtime, fingerprint, order };
|
|
321
|
+
}, (runtime, receipt) => {
|
|
322
|
+
const state = this.stackStates.get(stackExecutionKey(runtime));
|
|
323
|
+
if (state) {
|
|
324
|
+
state.status = receipt.outcome === 'running' ? 'running' : 'error';
|
|
325
|
+
state.startedAt = receipt.outcome === 'running' ? Date.parse(receipt.observedAt) : null;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
resumeRecipient() {
|
|
330
|
+
if (!this.installedResume)
|
|
331
|
+
throw new Error('This node is not enrolled for signed application recovery');
|
|
332
|
+
if (this.ownershipFrozen || this.reconcilingStacks || this.startsInFlight.size || this.stopsInFlight.size || this.nodeStackStop.busy) {
|
|
333
|
+
throw new Error('A stack operation or backend transfer is in progress');
|
|
334
|
+
}
|
|
335
|
+
return this.installedResume;
|
|
336
|
+
}
|
|
337
|
+
prepareNodeInstalledResume(request) { return this.resumeRecipient().prepare(request); }
|
|
338
|
+
executeNodeInstalledResume(request) { return this.resumeRecipient().execute(request); }
|
|
339
|
+
getNodeInstalledResumeReceipt(request) {
|
|
340
|
+
if (!this.installedResume)
|
|
341
|
+
throw new Error('This node is not enrolled for signed application recovery');
|
|
342
|
+
return this.installedResume.receipt(request);
|
|
343
|
+
}
|
|
344
|
+
async resumeInstalledStack(project, stack) {
|
|
345
|
+
const scope = { project, stack }, key = stackExecutionKey(scope);
|
|
346
|
+
const cluster = this.clusterStop;
|
|
347
|
+
if (!cluster?.election?.isLeader)
|
|
348
|
+
throw new Error('Installed stack recovery requires the elected controller');
|
|
349
|
+
const election = cluster.election;
|
|
350
|
+
this.resumeRecipient();
|
|
351
|
+
if (this.resumingInstalled)
|
|
352
|
+
throw new Error('Another installed allocation is resuming');
|
|
353
|
+
this.coordinatingResume = true;
|
|
354
|
+
try {
|
|
355
|
+
const stop = this.daemonStateStore.kvGetSync(`stack:${key}:last-stop`, { strict: true });
|
|
356
|
+
if (stop?.phase !== 'stopped' || stop.supersededAt || !stop.intentCommitted || !stop.nodes.length) {
|
|
357
|
+
throw new Error('Review and complete a signed stop on every recipient before installed recovery');
|
|
358
|
+
}
|
|
359
|
+
const config = await this.loadProjectConfig(project);
|
|
360
|
+
const declaration = this.resolveStacks(config, project)[stack];
|
|
361
|
+
if (!declaration)
|
|
362
|
+
throw new Error('Registered stack configuration is unavailable');
|
|
363
|
+
const apps = this.resolveStackApps(declaration, config);
|
|
364
|
+
const source = declaration.managedOrigin ?? scope;
|
|
365
|
+
const operationId = randomUUID();
|
|
366
|
+
const placements = declaration.type === 'local'
|
|
367
|
+
? [{ node: undefined, apps }]
|
|
368
|
+
: resolveNodePlacements(declaration, apps);
|
|
369
|
+
if (declaration.type === 'local' && !declaration.managedOrigin)
|
|
370
|
+
throw new Error('Local recovery requires an explicitly bound allocation');
|
|
371
|
+
const targets = placements.map(placement => {
|
|
372
|
+
const { node } = placement;
|
|
373
|
+
const nodeId = node ? node.nodeId : cluster.recipient.audience.nodeId;
|
|
374
|
+
const label = node ? stackNodeKey(node.host, node.port) : 'local';
|
|
375
|
+
const stopped = stop.nodes.find(item => item.authority?.nodeId === nodeId &&
|
|
376
|
+
// Older stop journals used unbracketed IPv6 endpoint strings.
|
|
377
|
+
(item.node === label || (node && item.node === `${node.host}:${node.port ?? 9700}`)));
|
|
378
|
+
if (!nodeId || !placement.apps.length || !stopped?.observedAt || !stopped.operationId || stopped.outcome !== 'stopped' ||
|
|
379
|
+
stopped.authority?.clusterId !== cluster.recipient.audience.clusterId) {
|
|
380
|
+
throw new Error(`Target '${label}' lacks a matching signed stop for the application placement`);
|
|
381
|
+
}
|
|
382
|
+
const request = { ...source, audience: { clusterId: stopped.authority.clusterId, nodeId },
|
|
383
|
+
operationId, stopOperationId: stopped.operationId, stopObservedAt: stopped.observedAt, apps: placement.apps.map(app => app.name) };
|
|
384
|
+
if (!node)
|
|
385
|
+
return { node: label, request, prepare: () => this.prepareNodeInstalledResume(request),
|
|
386
|
+
execute: (plan, permit) => this.executeNodeInstalledResume({ ...request, plan, permit }),
|
|
387
|
+
receipt: async () => this.getNodeInstalledResumeReceipt(request) };
|
|
388
|
+
const connector = this.slaveConnector;
|
|
389
|
+
if (!connector)
|
|
390
|
+
throw new Error('Cluster node RPC is unavailable');
|
|
391
|
+
const rpc = (method, input, read = false) => connector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronProject', method, [input], { retryOnDisconnect: read });
|
|
392
|
+
return { node: label, request,
|
|
393
|
+
prepare: () => rpc('prepareNodeInstalledResume', request, true),
|
|
394
|
+
execute: (plan, permit) => rpc('executeNodeInstalledResume', { ...request, plan, permit }),
|
|
395
|
+
receipt: () => rpc('getNodeInstalledResumeReceipt', request, true) };
|
|
396
|
+
});
|
|
397
|
+
if (targets.length !== stop.nodes.length || new Set(targets.map(t => t.request.audience.nodeId)).size !== targets.length) {
|
|
398
|
+
throw new Error('Placement differs from the last confirmed stop');
|
|
399
|
+
}
|
|
400
|
+
const report = { ...scope, operationId, stopOperationId: stop.id, outcome: 'unconfirmed', nodes: [] };
|
|
401
|
+
const journal = `stack:${key}:last-resume`;
|
|
402
|
+
const previous = this.daemonStateStore.kvGetSync(journal, { strict: true });
|
|
403
|
+
if (previous?.stopOperationId === stop.id)
|
|
404
|
+
throw new Error('Recovery was already attempted. Inspect its receipts and obtain a fresh signed stop before retrying.');
|
|
405
|
+
let targetsRemembered = false;
|
|
406
|
+
const result = await resumeDestinations(report, targets, plan => election.authorizeEffect(plan), progress => {
|
|
407
|
+
if (!targetsRemembered && declaration.type !== 'local') {
|
|
408
|
+
this.rememberStackTargets(project, stack, declaration, config);
|
|
409
|
+
targetsRemembered = true;
|
|
410
|
+
}
|
|
411
|
+
this.daemonStateStore.kvUpdateSync(journal, current => {
|
|
412
|
+
if (current?.stopOperationId === stop.id && current.operationId !== operationId)
|
|
413
|
+
throw new Error('A different controller already attempted this stop');
|
|
414
|
+
return progress;
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
this.stackStates.set(key, { project, stack, config: declaration, status: result.outcome === 'running' ? 'running' : 'error',
|
|
418
|
+
startedAt: result.outcome === 'running' ? Date.now() : null, infraService: null });
|
|
419
|
+
if (result.outcome === 'running')
|
|
420
|
+
this.markPriorStopSuperseded(project, stack, stop.id);
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
finally {
|
|
424
|
+
this.coordinatingResume = false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async stopDefinition(scope, cached = false) {
|
|
428
|
+
let allocation;
|
|
429
|
+
let unbound = false;
|
|
430
|
+
// Only registrations owned by this project can claim one of its allocations.
|
|
431
|
+
const registrations = this.registry.get(scope.project) ? this.registry.registrations(scope.project) : [];
|
|
432
|
+
for (const registration of registrations) {
|
|
433
|
+
const config = cached ? this.getLoadedConfig(registration.name) : await this.loadProjectConfig(registration.name);
|
|
434
|
+
if (!config)
|
|
435
|
+
throw new Error(`Configuration '${registration.name}' is unavailable for inspection; refresh its declarations`);
|
|
436
|
+
for (const [name, stack] of Object.entries(this.resolveStacks(config, registration.name))) {
|
|
437
|
+
if (stack.type !== 'local')
|
|
438
|
+
continue;
|
|
439
|
+
if (!stack.managedOrigin) {
|
|
440
|
+
unbound = true;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (stack.managedOrigin.project !== scope.project || stack.managedOrigin.stack !== scope.stack)
|
|
444
|
+
continue;
|
|
445
|
+
if (allocation)
|
|
446
|
+
throw new Error('Ambiguous node allocation ownership');
|
|
447
|
+
if (!scope.applications)
|
|
448
|
+
throw new Error('Node owns an application allocation outside the requested placement; reconcile the topology');
|
|
449
|
+
const runtime = { project: registration.name, stack: name };
|
|
450
|
+
allocation = { runtime, fingerprint: stackDefinitionFingerprint([runtime.project, name, stack, config.apps]) };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!allocation && (scope.applications || unbound))
|
|
454
|
+
throw new Error(`Node allocation '${scope.project}/${scope.stack}' has no verified source-stack binding; bind its managedOrigin before remote stop`);
|
|
455
|
+
return allocation ?? { runtime: null, fingerprint: stackDefinitionFingerprint([scope.project, scope.stack, false]) };
|
|
456
|
+
}
|
|
457
|
+
inspectNodeStackStop(scope) {
|
|
458
|
+
return this.nodeStackStop.inspect(scope);
|
|
459
|
+
}
|
|
460
|
+
async prepareNodeStackStop(scope) {
|
|
461
|
+
if (this.resumingInstalled)
|
|
462
|
+
throw new Error('An installed allocation is resuming');
|
|
463
|
+
if (this.ownershipFrozen)
|
|
464
|
+
throw new Error('Project ownership is frozen for backend transfer');
|
|
465
|
+
if (this.startsInFlight.size || this.stopsInFlight.size)
|
|
466
|
+
throw new Error('A stack lifecycle operation is active; retry when it finishes');
|
|
467
|
+
return this.nodeStackStop.prepare(scope);
|
|
468
|
+
}
|
|
469
|
+
async executeNodeStackStop(scope) {
|
|
470
|
+
if (this.resumingInstalled)
|
|
471
|
+
throw new Error('An installed allocation is resuming');
|
|
472
|
+
if (this.ownershipFrozen)
|
|
473
|
+
throw new Error('Project ownership is frozen for backend transfer');
|
|
474
|
+
const receipt = await this.nodeStackStop.execute(scope);
|
|
475
|
+
if (receipt.applications) {
|
|
476
|
+
const state = this.stackStates.get(stackExecutionKey(receipt.applications));
|
|
477
|
+
if (state) {
|
|
478
|
+
state.status = 'stopped';
|
|
479
|
+
state.startedAt = null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return receipt;
|
|
142
483
|
}
|
|
143
484
|
/**
|
|
144
485
|
* Adopt the daemon's mesh connector.
|
|
@@ -174,7 +515,7 @@ export class ProjectService extends EventEmitter {
|
|
|
174
515
|
const declared = stackNodeToDeployTarget(node);
|
|
175
516
|
if (!this.resolveNodeCredentials)
|
|
176
517
|
return declared;
|
|
177
|
-
const registered = await this.resolveNodeCredentials(node.host
|
|
518
|
+
const registered = await this.resolveNodeCredentials(node.host, node.port ?? 9700, node.nodeId);
|
|
178
519
|
return withNodeCredentials(declared, registered);
|
|
179
520
|
}
|
|
180
521
|
/**
|
|
@@ -212,28 +553,110 @@ export class ProjectService extends EventEmitter {
|
|
|
212
553
|
* `deploying` from the first second to the last, and every event that said
|
|
213
554
|
* WHERE it was reached a handler that re-emitted it to nobody.
|
|
214
555
|
*/
|
|
215
|
-
getDeployProgress() {
|
|
216
|
-
|
|
556
|
+
getDeployProgress(scope) {
|
|
557
|
+
const records = this.deployer.getProgress();
|
|
558
|
+
if (!scope)
|
|
559
|
+
return records;
|
|
560
|
+
const configurations = this.registry.registrations(scope.project).map(configurationInfo);
|
|
561
|
+
return readDeploymentProgress(records, { name: scope.project, configurations }, scope.stack).transfers;
|
|
217
562
|
}
|
|
218
563
|
listProjects() {
|
|
219
|
-
|
|
564
|
+
const revision = this.registry.catalogRevision();
|
|
565
|
+
return this.registry.list().filter(project => !isControlPlaneWorkspace(project.path)).map(project => {
|
|
566
|
+
const records = this.registry.registrations(project.name);
|
|
567
|
+
const infos = records.map(record => this.toProjectInfo(record));
|
|
568
|
+
return {
|
|
569
|
+
...this.toProjectInfo(project),
|
|
570
|
+
enabledStacks: records.flatMap(record => (record.enabledStacks ?? []).map(stack => publicStackName(record, stack))),
|
|
571
|
+
runningStacks: infos.reduce((count, info) => count + info.runningStacks, 0),
|
|
572
|
+
totalStacks: infos.reduce((count, info) => count + info.totalStacks, 0),
|
|
573
|
+
configurations: records.map(configurationInfo),
|
|
574
|
+
catalogRevision: revision,
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/** Source inventory for execution, backup and recovery; not the public project catalogue. */
|
|
579
|
+
listProjectRegistrations() {
|
|
580
|
+
return this.registry.listRegistrations().filter(project => !isControlPlaneWorkspace(project.path)).map(project => this.toProjectInfo(project));
|
|
581
|
+
}
|
|
582
|
+
projectConfigurations(project) {
|
|
583
|
+
return this.registry.registrations(project);
|
|
584
|
+
}
|
|
585
|
+
async resolveProjectStack(scope, options = {}) {
|
|
586
|
+
const resolved = this.registry.resolveStack(scope);
|
|
587
|
+
if (resolved.project !== scope.project) {
|
|
588
|
+
const config = options.readOnly ? this.getLoadedConfig(scope.project) : await this.ensureConfig(scope.project);
|
|
589
|
+
if (!config)
|
|
590
|
+
throw new Error('Project configuration is not loaded; reload it before resolving imported stack diagnostics');
|
|
591
|
+
if (Object.hasOwn(this.resolveStacks(config, scope.project), scope.stack))
|
|
592
|
+
throw new Error(`Ambiguous stack '${scope.stack}': both default and imported configurations declare it`);
|
|
593
|
+
}
|
|
594
|
+
return { ...scope, ...resolved };
|
|
595
|
+
}
|
|
596
|
+
publicStackInfo(project, source, info) {
|
|
597
|
+
return {
|
|
598
|
+
...publicStack(info),
|
|
599
|
+
name: project === source.name ? info.name : publicStackName(source, info.name),
|
|
600
|
+
runtimeScope: { project: source.name, stack: info.name },
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async importProjectConfiguration(input) {
|
|
604
|
+
validateConfigurationNames(input?.name, input?.stacks);
|
|
605
|
+
if (typeof input.project !== 'string' || typeof input.path !== 'string' || typeof input.expectedRevision !== 'string')
|
|
606
|
+
throw new Error('Project, configuration path and catalogue revision are required');
|
|
607
|
+
const candidate = this.registry.prepareConfiguration(input);
|
|
608
|
+
const { loadEcosystemConfig } = await import('../config/loader.js');
|
|
609
|
+
const source = await loadEcosystemConfig(candidate.path);
|
|
610
|
+
const sourceStacks = this.resolveStacks(source, candidate);
|
|
611
|
+
const declared = Object.keys(sourceStacks);
|
|
612
|
+
if (declared.length !== Object.keys(input.stacks).length || declared.some(name => !Object.hasOwn(input.stacks, name)))
|
|
613
|
+
throw new Error('Map every configured source stack explicitly; none may disappear during import');
|
|
614
|
+
const owner = await this.ensureConfig(input.project);
|
|
615
|
+
const occupied = new Set(Object.keys(this.resolveStacks(owner, input.project)));
|
|
616
|
+
for (const name of Object.values(input.stacks))
|
|
617
|
+
if (occupied.has(name))
|
|
618
|
+
throw new Error(`Stack '${name}' already exists in the project default configuration`);
|
|
619
|
+
const existing = this.registry.get(candidate.name);
|
|
620
|
+
if (existing) {
|
|
621
|
+
for (const name of existing.enabledStacks ?? [])
|
|
622
|
+
if (!input.stacks[name])
|
|
623
|
+
throw new Error(`Auto-start source stack '${name}' is missing from the mapping`);
|
|
624
|
+
for (const name of this.getRunningStacks(existing.name, true))
|
|
625
|
+
if (!input.stacks[name])
|
|
626
|
+
throw new Error(`Running source stack '${name}' is missing from the mapping`);
|
|
627
|
+
for (const name of Object.keys(this.loadUserStacks(existing.name)))
|
|
628
|
+
if (!input.stacks[name])
|
|
629
|
+
throw new Error(`User-defined source stack '${name}' is missing from the mapping`);
|
|
630
|
+
}
|
|
631
|
+
const record = this.registry.importConfiguration(input);
|
|
632
|
+
this.configRegistry.set(record.name, { config: source, loadedAt: Date.now() });
|
|
633
|
+
await this.audit?.record({ action: 'project.configuration.import', resourceType: 'project', resourceId: input.project,
|
|
634
|
+
details: { configuration: input.name, runtimeProject: record.name, stacks: input.stacks } });
|
|
635
|
+
this.emit('project:updated', input.project, {});
|
|
636
|
+
return this.getProject(input.project);
|
|
637
|
+
}
|
|
638
|
+
async detachProjectConfiguration(data) {
|
|
639
|
+
this.registry.detachConfiguration(data.project, data.name, data.expectedRevision);
|
|
640
|
+
await this.audit?.record({ action: 'project.configuration.detach', resourceType: 'project', resourceId: data.project,
|
|
641
|
+
details: { configuration: data.name } });
|
|
642
|
+
this.emit('project:updated', data.project, {});
|
|
220
643
|
}
|
|
221
644
|
getProject(name) {
|
|
222
645
|
const project = this.registry.get(name);
|
|
223
646
|
if (!project)
|
|
224
647
|
throw new Error(`Project '${name}' not found`);
|
|
225
|
-
return this.
|
|
648
|
+
return this.listProjects().find(record => record.name === (project.configuration?.project ?? name));
|
|
226
649
|
}
|
|
227
650
|
async addProject(name, projectPath) {
|
|
228
651
|
// Already registered AT THIS PATH: read it again rather than refuse.
|
|
229
652
|
//
|
|
230
653
|
// A deployment registers a node's project after writing its config, on
|
|
231
|
-
// every deployment. The node answered «Project '
|
|
654
|
+
// every deployment. The node answered «Project 'sample' already registered»
|
|
232
655
|
// to every one after the first (four in two days, measured), the
|
|
233
656
|
// deployer's SSH step failed on the exit code, and the node never read
|
|
234
657
|
// the config it had just been given — a redeployment that changed an
|
|
235
|
-
// app's environment restarted the app on the old one (
|
|
236
|
-
//
|
|
658
|
+
// app's environment restarted the app on the old one (payments on
|
|
659
|
+
// sample/test, pointed at the chain's previous address, 2026-09-23). A
|
|
237
660
|
// different path under the same name is still refused: that is another
|
|
238
661
|
// project, not the same one again.
|
|
239
662
|
const existing = this.registry.get(name);
|
|
@@ -256,24 +679,37 @@ export class ProjectService extends EventEmitter {
|
|
|
256
679
|
* running apps were redefined.
|
|
257
680
|
*/
|
|
258
681
|
async rereadProject(name) {
|
|
682
|
+
if (this.ownershipFrozen || this.rereading?.has(name) || [...this.startsInFlight.keys()].some(key => key.startsWith(name + '/')))
|
|
683
|
+
throw new Error('Project launch definitions are changing; retry after the current operation');
|
|
684
|
+
(this.rereading ??= new Set()).add(name);
|
|
685
|
+
try {
|
|
686
|
+
return await this.rereadProjectOnce(name);
|
|
687
|
+
}
|
|
688
|
+
finally {
|
|
689
|
+
this.rereading.delete(name);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
async rereadProjectOnce(name) {
|
|
259
693
|
this.configRegistry.delete(name);
|
|
260
694
|
const config = await this.loadProjectConfig(name);
|
|
261
695
|
const stacks = this.resolveStacks(config, name);
|
|
262
696
|
let redefined = 0;
|
|
263
697
|
for (const [key, state] of this.stackStates) {
|
|
264
|
-
if (!key.startsWith(`${name}/`) || state.status === 'stopped' || state.status === 'error')
|
|
698
|
+
if (!key.startsWith(`${name}/`) || state.status === 'stopped' || (state.status === 'error' && state.startedAt === null))
|
|
265
699
|
continue;
|
|
266
700
|
const stackConfig = stacks[state.stack];
|
|
267
701
|
// A remote stack's apps run on its nodes; they are redefined there.
|
|
268
702
|
if (!stackConfig || stackConfig.type !== 'local')
|
|
269
703
|
continue;
|
|
270
|
-
const
|
|
271
|
-
|
|
704
|
+
const entries = await this.launchEntries(name, state.stack, stackConfig, config);
|
|
705
|
+
if (entries.some(entry => entry.releaseRoot) && !stackConfig.managedOrigin)
|
|
706
|
+
await this.checkLocalOwner(name, state.stack, entries, false);
|
|
707
|
+
const definitions = await this.loadAppDefinitions(name, stackConfig, config, entries);
|
|
272
708
|
const stackEnv = await this.stackEnvFor(name, state.stack, stackConfig, entries.map((e) => e.name));
|
|
273
709
|
const build = this.stackEntryBuilder(name, state.stack, stackConfig, config, definitions, stackEnv);
|
|
274
710
|
for (const entry of entries) {
|
|
275
711
|
const built = build(entry);
|
|
276
|
-
if (this.orchestrator.redefineApp(built.name, built))
|
|
712
|
+
if (await this.orchestrator.redefineApp(built.name, built))
|
|
277
713
|
redefined += 1;
|
|
278
714
|
}
|
|
279
715
|
}
|
|
@@ -281,31 +717,68 @@ export class ProjectService extends EventEmitter {
|
|
|
281
717
|
this.emit('project:updated', name, {});
|
|
282
718
|
return redefined;
|
|
283
719
|
}
|
|
720
|
+
/** Refresh only local container monitoring from the current validated project config. */
|
|
721
|
+
async refreshInfrastructureOwnership(projectName, stackName) {
|
|
722
|
+
if (this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.size || this.stopsInFlight.size || this.reconcilingStacks)
|
|
723
|
+
throw new Error('Project lifecycle ownership is changing; retry the infrastructure refresh after it settles');
|
|
724
|
+
this.ownershipFrozen = true;
|
|
725
|
+
try {
|
|
726
|
+
return await this.refreshInfrastructureOwnershipOnce(projectName, stackName);
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
this.ownershipFrozen = false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
async refreshInfrastructureOwnershipOnce(projectName, stackName) {
|
|
733
|
+
const project = this.registry.get(projectName);
|
|
734
|
+
if (!project)
|
|
735
|
+
throw new Error(`Project '${projectName}' not found`);
|
|
736
|
+
this.configRegistry.delete(projectName);
|
|
737
|
+
const config = await this.loadProjectConfig(projectName);
|
|
738
|
+
const stack = this.resolveStacks(config, projectName)[stackName];
|
|
739
|
+
if (!stack || stack.type !== 'local')
|
|
740
|
+
throw new Error('Ownership refresh requires a declared local stack');
|
|
741
|
+
if (!ownsStackInfrastructure(stack)) {
|
|
742
|
+
const released = await this.infraManager.refreshOwnership(projectName, stackName, stack, {}, undefined, project.path);
|
|
743
|
+
const state = this.stackStates.get(`${projectName}/${stackName}`);
|
|
744
|
+
if (state) {
|
|
745
|
+
state.config = stack;
|
|
746
|
+
state.infraService = null;
|
|
747
|
+
}
|
|
748
|
+
return released;
|
|
749
|
+
}
|
|
750
|
+
const entries = await this.launchEntries(projectName, stackName, stack, config);
|
|
751
|
+
const definitions = await this.loadAppDefinitions(projectName, stack, config, entries);
|
|
752
|
+
// The same requirements merge used at stack start. A failed fresh scan must
|
|
753
|
+
// refuse a handoff rather than silently releasing auto-detected services.
|
|
754
|
+
const auto = buildInfraFromRequirements(requirementsFromDefinitions(definitions));
|
|
755
|
+
const effective = mergeInferredInfrastructure(auto, mergeInfrastructure(config, stack));
|
|
756
|
+
return this.infraManager.refreshOwnership(projectName, stackName, stack, effective, definitions, project.path);
|
|
757
|
+
}
|
|
284
758
|
updateProject(name, data) {
|
|
285
759
|
const project = this.registry.get(name);
|
|
286
760
|
if (!project)
|
|
287
761
|
throw new Error(`Project '${name}' not found`);
|
|
288
762
|
if (data.path) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
this.logger.info({ oldName: name, newName: updated.name, path: data.path }, 'Project renamed and path updated');
|
|
296
|
-
this.emit('project:renamed', name, updated.name);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
this.logger.info({ project: name, path: data.path }, 'Project path updated');
|
|
763
|
+
const nextPath = path.resolve(data.path);
|
|
764
|
+
if (nextPath !== path.resolve(project.path)) {
|
|
765
|
+
const running = this.getRunningStacks(name, true);
|
|
766
|
+
if (running.length > 0) {
|
|
767
|
+
throw new Error(`Project '${name}' has running stacks: ${running.join(', ')}. Stop them before changing its path.`);
|
|
768
|
+
}
|
|
300
769
|
}
|
|
301
|
-
|
|
770
|
+
// Validate and persist first. A rejected path must not invalidate a live config.
|
|
771
|
+
const updated = this.registry.updatePath(name, nextPath);
|
|
772
|
+
this.configRegistry.delete(name);
|
|
773
|
+
this.logger.info({ project: name, path: nextPath }, 'Project path updated');
|
|
774
|
+
this.emit('project:updated', name, data);
|
|
302
775
|
return this.toProjectInfo(updated);
|
|
303
776
|
}
|
|
304
777
|
return this.toProjectInfo(project);
|
|
305
778
|
}
|
|
306
779
|
removeProject(name) {
|
|
307
780
|
// Stop all running stacks for this project first
|
|
308
|
-
const runningStacks = this.getRunningStacks(name);
|
|
781
|
+
const runningStacks = this.getRunningStacks(name, true);
|
|
309
782
|
if (runningStacks.length > 0) {
|
|
310
783
|
throw new Error(`Project '${name}' has running stacks: ${runningStacks.join(', ')}. Stop them first.`);
|
|
311
784
|
}
|
|
@@ -367,7 +840,7 @@ export class ProjectService extends EventEmitter {
|
|
|
367
840
|
// Check for duplicates in both config stacks and user stacks
|
|
368
841
|
const config = await this.loadProjectConfig(project);
|
|
369
842
|
const allStacks = this.resolveStacks(config, project);
|
|
370
|
-
if (allStacks[input.name]) {
|
|
843
|
+
if (allStacks[input.name] || this.registry.registrations(project).some(source => Object.values(source.configuration?.stacks ?? {}).includes(input.name))) {
|
|
371
844
|
throw new Error(`Stack '${input.name}' already exists`);
|
|
372
845
|
}
|
|
373
846
|
// Build stack config
|
|
@@ -375,7 +848,12 @@ export class ProjectService extends EventEmitter {
|
|
|
375
848
|
type: input.type,
|
|
376
849
|
apps: input.apps,
|
|
377
850
|
...(input.nodeIds && input.type !== 'local' && {
|
|
378
|
-
nodes: input.nodeIds.map((id) =>
|
|
851
|
+
nodes: input.nodeIds.map((id) => {
|
|
852
|
+
const node = this.daemonStateStore.selectNodesSync().find(candidate => candidate.id === id);
|
|
853
|
+
if (!node)
|
|
854
|
+
throw new Error(`Fleet node '${id}' not found; register it before creating a stack`);
|
|
855
|
+
return { nodeId: node.id, host: node.host, port: node.port, label: node.name, role: 'app' };
|
|
856
|
+
}),
|
|
379
857
|
}),
|
|
380
858
|
};
|
|
381
859
|
// For local stacks, auto-generate optimal infrastructure from app requirements
|
|
@@ -418,7 +896,6 @@ export class ProjectService extends EventEmitter {
|
|
|
418
896
|
// `pull access denied` for, while the preset named the image the
|
|
419
897
|
// resolver actually uses. A prefetch that pulls something else is
|
|
420
898
|
// worse than none — it reports success and warms nothing.
|
|
421
|
-
const { createDefaultRegistry } = await import('../infrastructure/presets/index.js');
|
|
422
899
|
const presets = createDefaultRegistry();
|
|
423
900
|
const images = new Set();
|
|
424
901
|
for (const name of ['postgres', 'redis', 'minio']) {
|
|
@@ -464,6 +941,11 @@ export class ProjectService extends EventEmitter {
|
|
|
464
941
|
if (state && state.status !== 'stopped') {
|
|
465
942
|
throw new Error(`Stack '${stackName}' is currently ${state.status}. Stop it first.`);
|
|
466
943
|
}
|
|
944
|
+
if (userStacks[stackName].type === 'local') {
|
|
945
|
+
const selection = this.localSelection(project, stackName).read();
|
|
946
|
+
if (selection.release)
|
|
947
|
+
throw new Error('Clear the selected release before deleting this stack');
|
|
948
|
+
}
|
|
467
949
|
this.deleteUserStack(project, stackName);
|
|
468
950
|
this.stackStates.delete(stateKey);
|
|
469
951
|
this.logger.info({ project, stack: stackName }, 'Stack deleted');
|
|
@@ -475,14 +957,18 @@ export class ProjectService extends EventEmitter {
|
|
|
475
957
|
throw new Error(`Project '${project}' not found`);
|
|
476
958
|
return path.join(proj.path, 'omnitron.stacks.json');
|
|
477
959
|
}
|
|
478
|
-
loadUserStacks(project) {
|
|
479
|
-
const filePath = this.getUserStacksPath(project);
|
|
960
|
+
loadUserStacks(project, filePath = this.getUserStacksPath(project)) {
|
|
480
961
|
try {
|
|
481
962
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
482
|
-
|
|
963
|
+
const stacks = JSON.parse(raw);
|
|
964
|
+
if (!stacks || typeof stacks !== 'object' || Array.isArray(stacks))
|
|
965
|
+
throw new Error('Expected an object of stacks');
|
|
966
|
+
return stacks;
|
|
483
967
|
}
|
|
484
|
-
catch {
|
|
485
|
-
|
|
968
|
+
catch (error) {
|
|
969
|
+
if (error.code === 'ENOENT')
|
|
970
|
+
return {};
|
|
971
|
+
throw new Error(`Cannot read stack overrides for '${project}'; file left intact`, { cause: error });
|
|
486
972
|
}
|
|
487
973
|
}
|
|
488
974
|
saveUserStack(project, name, config) {
|
|
@@ -500,6 +986,368 @@ export class ProjectService extends EventEmitter {
|
|
|
500
986
|
// ===========================================================================
|
|
501
987
|
// Stacks
|
|
502
988
|
// ===========================================================================
|
|
989
|
+
/** Remote deployments across projects; local process/container inventory has its own authoritative source. */
|
|
990
|
+
async getTopologyInventory() {
|
|
991
|
+
const result = {
|
|
992
|
+
generatedAt: new Date().toISOString(), deployments: [], errors: [],
|
|
993
|
+
};
|
|
994
|
+
for (const project of this.listProjectRegistrations()) {
|
|
995
|
+
const source = this.registry.get(project.name);
|
|
996
|
+
const owner = source.configuration?.project ?? project.name;
|
|
997
|
+
try {
|
|
998
|
+
await this.ensureConfig(project.name);
|
|
999
|
+
const stacks = this.listStacks(project.name).filter(stack => stack.type !== 'local');
|
|
1000
|
+
for (let offset = 0; offset < stacks.length; offset += 4) {
|
|
1001
|
+
const observations = await Promise.allSettled(stacks.slice(offset, offset + 4).map(stack => this.withRemoteAppStatuses(project.name, stack)));
|
|
1002
|
+
observations.forEach((observation, index) => {
|
|
1003
|
+
if (observation.status === 'fulfilled') {
|
|
1004
|
+
result.deployments.push({ project: owner, stack: this.publicStackInfo(owner, source, observation.value) });
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
result.errors.push({
|
|
1008
|
+
project: owner, stack: publicStackName(source, stacks[offset + index].name), reason: 'Node inventory could not be read',
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
catch {
|
|
1015
|
+
result.errors.push({ project: owner, reason: `Configuration '${source.configuration?.name ?? 'default'}' could not be read` });
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
async getTopologyWorkspace(scope) {
|
|
1021
|
+
return this.topologyControl.workspace(scope);
|
|
1022
|
+
}
|
|
1023
|
+
async saveTopologyDraft(data) {
|
|
1024
|
+
return this.topologyControl.saveDraft(data);
|
|
1025
|
+
}
|
|
1026
|
+
async planTopology(data) {
|
|
1027
|
+
return this.topologyControl.plan(data);
|
|
1028
|
+
}
|
|
1029
|
+
async applyTopologyPlan(data) {
|
|
1030
|
+
const result = await this.topologyControl.apply(data, currentActor());
|
|
1031
|
+
if (result.changed) {
|
|
1032
|
+
// The local FULL commit already contains intent and its receipt; this is a searchable audit copy.
|
|
1033
|
+
await this.audit?.record({
|
|
1034
|
+
action: 'topology.apply', resourceType: 'stack', resourceId: `${data.project}/${data.stack}`,
|
|
1035
|
+
details: {
|
|
1036
|
+
plan: result.plan.id, revision: result.plan.baseRevision,
|
|
1037
|
+
effect: result.plan.effect, changes: result.plan.changes,
|
|
1038
|
+
},
|
|
1039
|
+
});
|
|
1040
|
+
this.emit('project:updated', data.project, {});
|
|
1041
|
+
}
|
|
1042
|
+
return result.plan;
|
|
1043
|
+
}
|
|
1044
|
+
/** Reads persisted launch evidence and static JSON only; never imports application code. */
|
|
1045
|
+
readAppDeclaration(project, stack, app, bootstrap) {
|
|
1046
|
+
const state = this.bootstrapMetadata?.readState(bootstrap);
|
|
1047
|
+
const saved = state?.metadata;
|
|
1048
|
+
const launched = this.orchestrator?.getHandle(ProjectService.handleKey(project, stack, app))?.bootstrapMetadata;
|
|
1049
|
+
const observed = launched?.bootstrap === bootstrap ? launched : null;
|
|
1050
|
+
const record = observed && (!saved || observed.capturedAt > saved.capturedAt) ? observed : saved;
|
|
1051
|
+
const metadata = record?.definition ?? getCachedBootstrapDefinition(bootstrap) ?? this.bootstrapTopology.get(bootstrap);
|
|
1052
|
+
const declared = readDeclaredConfig(bootstrap);
|
|
1053
|
+
return { state, record, metadata, declared, observed };
|
|
1054
|
+
}
|
|
1055
|
+
/** Inspect prerequisites without provisioning, migration, application start or bootstrap imports. */
|
|
1056
|
+
diagnosesInFlight = new Map();
|
|
1057
|
+
async diagnoseStack(projectName, stackName) {
|
|
1058
|
+
const key = JSON.stringify([projectName, stackName]);
|
|
1059
|
+
const pending = this.diagnosesInFlight.get(key);
|
|
1060
|
+
if (pending)
|
|
1061
|
+
return pending;
|
|
1062
|
+
const request = this.inspectStackReadiness(projectName, stackName);
|
|
1063
|
+
this.diagnosesInFlight.set(key, request);
|
|
1064
|
+
try {
|
|
1065
|
+
return await request;
|
|
1066
|
+
}
|
|
1067
|
+
finally {
|
|
1068
|
+
this.diagnosesInFlight.delete(key);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
async inspectStackReadiness(projectName, stackName) {
|
|
1072
|
+
const started = Date.now();
|
|
1073
|
+
const checks = [];
|
|
1074
|
+
const deferred = ['Application startup and application health', 'Database authentication, schema and migrations', 'Registry downloads and available storage capacity', 'Configuration changes made after this observation'];
|
|
1075
|
+
const done = () => ({ project: projectName, stack: stackName, checkedAt: new Date().toISOString(), durationMs: Date.now() - started, status: diagnosisStatus(checks), checks, deferred });
|
|
1076
|
+
const add = (code, resource, state, message, action) => checks.push({ code, resource, state, message, ...(action && { action }) });
|
|
1077
|
+
const project = this.registry.get(projectName);
|
|
1078
|
+
if (!project)
|
|
1079
|
+
throw new Error('Project is not registered');
|
|
1080
|
+
const loaded = this.configRegistry.get(projectName);
|
|
1081
|
+
if (!loaded) {
|
|
1082
|
+
add('configuration.unloaded', 'Project configuration', 'unknown', 'Project configuration has not been loaded.', 'Reload the trusted project configuration, then retry.');
|
|
1083
|
+
return done();
|
|
1084
|
+
}
|
|
1085
|
+
const config = loaded.config;
|
|
1086
|
+
const stack = this.resolveStacks(config, projectName)[stackName];
|
|
1087
|
+
if (!stack)
|
|
1088
|
+
throw new Error('Stack is not declared');
|
|
1089
|
+
try {
|
|
1090
|
+
this.assertStartAuthority({ project: projectName, stack: stackName }, stack);
|
|
1091
|
+
}
|
|
1092
|
+
catch (err) {
|
|
1093
|
+
add('start.authority', 'Start authority', 'blocked', err.message, 'Inspect the allocation owner and its stop operation before planning a start.');
|
|
1094
|
+
}
|
|
1095
|
+
add('configuration.loaded', 'Project configuration', 'pass', `Using daemon configuration loaded at ${new Date(loaded.loadedAt).toISOString()}.`, 'Reload project configuration after editing it.');
|
|
1096
|
+
// This is observed history, not a fresh execution of user code. Reuse the
|
|
1097
|
+
// status reader so a recovered app or an intentionally stopped stack does
|
|
1098
|
+
// not keep an obsolete launch failure in its readiness report.
|
|
1099
|
+
if (stack.type === 'local' && this.stackStates.get(`${projectName}/${stackName}`)?.appStartErrors) {
|
|
1100
|
+
const previous = this.toStackInfo(projectName, stackName, stack);
|
|
1101
|
+
for (const issue of previous.notUp ?? [])
|
|
1102
|
+
add('application.previous-start', 'Previous start', 'unknown', `${previous.startedAt ? previous.startedAt + ' · ' : ''}${issue}`, 'This records the previous start attempt; current startup remains unverified. Correct the cause and retry the stack start.');
|
|
1103
|
+
}
|
|
1104
|
+
let entries;
|
|
1105
|
+
let infrastructure = mergeInfrastructure(config, stack);
|
|
1106
|
+
try {
|
|
1107
|
+
const selected = stack.type === 'local' && !stack.managedOrigin ? this.localSelection(projectName, stackName).read().release : null;
|
|
1108
|
+
if (selected && artifactVolumeBindings(infrastructure).length) {
|
|
1109
|
+
const { loadRelease } = await import('../release/load.js');
|
|
1110
|
+
const release = await loadRelease(selected.id, await this.releaseStore());
|
|
1111
|
+
if (release.manifestSha256 !== selected.manifestSha256)
|
|
1112
|
+
throw Error('Selected release manifest changed on disk');
|
|
1113
|
+
const installed = await this.installedRelease(projectName, stackName, stack, config, release);
|
|
1114
|
+
if (!installed.infrastructure)
|
|
1115
|
+
throw Error('Selected artifact infrastructure is unavailable');
|
|
1116
|
+
entries = installed.entries;
|
|
1117
|
+
infrastructure = installed.infrastructure;
|
|
1118
|
+
}
|
|
1119
|
+
else
|
|
1120
|
+
entries = await this.launchEntries(projectName, stackName, stack, config);
|
|
1121
|
+
if (selected)
|
|
1122
|
+
add('release.selected', 'Release', 'pass', 'Next start uses verified release ' + selected.id);
|
|
1123
|
+
else if (stack.release?.mode === 'required')
|
|
1124
|
+
add('release.required', 'Release', 'unknown', 'This stack requires an immutable release; no release was selected for this check.', 'Use Deploy release and review its admission checks.');
|
|
1125
|
+
}
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
add('release.unavailable', 'Selected release', 'blocked', describeFailure(error), 'Restore the pinned installation or select another verified release.');
|
|
1128
|
+
return done();
|
|
1129
|
+
}
|
|
1130
|
+
if (Array.isArray(stack.apps)) {
|
|
1131
|
+
const available = new Set(entries.map(entry => entry.name));
|
|
1132
|
+
for (const name of stack.apps)
|
|
1133
|
+
if (!available.has(name))
|
|
1134
|
+
add('application.undeclared', name, 'blocked', 'The selected application is absent or disabled in project configuration.');
|
|
1135
|
+
}
|
|
1136
|
+
if (!entries.length)
|
|
1137
|
+
add('applications.empty', 'Applications', 'blocked', 'No enabled applications are selected.');
|
|
1138
|
+
try {
|
|
1139
|
+
resolveStartupOrder(entries);
|
|
1140
|
+
add('dependencies.valid', 'Application dependencies', 'pass', 'Startup dependency order resolved.');
|
|
1141
|
+
}
|
|
1142
|
+
catch {
|
|
1143
|
+
add('dependencies.invalid', 'Application dependencies', 'blocked', 'Application dependencies cannot be ordered.', 'Check missing dependencies and dependency cycles in the project configuration.');
|
|
1144
|
+
}
|
|
1145
|
+
let migrationEnvironment = null;
|
|
1146
|
+
try {
|
|
1147
|
+
migrationEnvironment = await this.stackEnvFor(projectName, stackName, stack, entries.map(entry => entry.name));
|
|
1148
|
+
add('environment.valid', 'Environment', 'pass', 'Environment declarations and vault references resolved; values are withheld.');
|
|
1149
|
+
}
|
|
1150
|
+
catch {
|
|
1151
|
+
add('environment.invalid', 'Environment', 'blocked', 'Environment declarations or vault references could not be resolved.', 'Check stack environment variable names and required vault entries.');
|
|
1152
|
+
}
|
|
1153
|
+
const definitions = new Map();
|
|
1154
|
+
for (const entry of entries) {
|
|
1155
|
+
if (entry.releaseFrom) {
|
|
1156
|
+
add('release.import-unselected', entry.name, 'blocked', 'This application comes from a pinned producer release; no verified installation is selected.', 'Prepare and select the consumer release before checking its application prerequisites.');
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (!entry.bootstrap) {
|
|
1160
|
+
add('application.classic', entry.name, 'unknown', 'Executable prerequisites for this command-based application have not been probed.', 'Check the configured command and working directory before starting.');
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
const bootstrap = resolveAppPath(project.path, entry, entry.bootstrap);
|
|
1164
|
+
if (!fs.existsSync(bootstrap))
|
|
1165
|
+
add('bootstrap.missing', entry.name, 'blocked', 'The configured bootstrap file is missing.', 'Restore the application sources or correct its bootstrap path.');
|
|
1166
|
+
const { metadata, declared, state, record } = this.readAppDeclaration(projectName, stackName, entry.name, bootstrap);
|
|
1167
|
+
if (declared.malformed)
|
|
1168
|
+
add('declaration.invalid', entry.name, 'blocked', 'Application configuration is invalid or unreadable.', 'Check config/default.json syntax and permissions.');
|
|
1169
|
+
if (!metadata || !record || metadataSourceChanged(record) || state?.failure)
|
|
1170
|
+
add('declaration.unknown', entry.name, 'unknown', state?.failure ? `Last declaration read at ${state.failure.at}: ${state.failure.reason}` : 'Current bootstrap declarations have not been verified.', 'Refresh declarations as an administrator, then run diagnosis again.');
|
|
1171
|
+
else
|
|
1172
|
+
add('declaration.current', entry.name, 'pass', `Bootstrap declaration captured at ${record.capturedAt}; source file is unchanged.`);
|
|
1173
|
+
if (metadata || declared.omnitron)
|
|
1174
|
+
definitions.set(entry.name, withDeclaredConfig({ name: entry.name, version: '', processes: [], ...metadata }, declared));
|
|
1175
|
+
}
|
|
1176
|
+
try {
|
|
1177
|
+
if (!ownsStackInfrastructure(stack)) {
|
|
1178
|
+
const mode = infrastructureOwnership(stack);
|
|
1179
|
+
const owner = mode === 'controller' ? `${stack.managedOrigin.project}/${stack.managedOrigin.stack}` : 'an external operator';
|
|
1180
|
+
add('infrastructure.delegated', 'Infrastructure ownership', 'pass', `Infrastructure provisioning and migrations belong to ${owner}; this stack starts applications only.`);
|
|
1181
|
+
add('infrastructure.external-readiness', 'Application connections', 'unknown', 'Explicit application connection settings are retained. Endpoint reachability, credentials and schema have not been verified.', 'Inspect the owning infrastructure and application health; this node will not create substitute services.');
|
|
1182
|
+
return done();
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
catch (error) {
|
|
1186
|
+
add('infrastructure.ownership-invalid', 'Infrastructure ownership', 'blocked', error.message, 'Correct the explicit ownership binding; diagnosis changes no infrastructure.');
|
|
1187
|
+
return done();
|
|
1188
|
+
}
|
|
1189
|
+
if (stack.type !== 'local') {
|
|
1190
|
+
try {
|
|
1191
|
+
resolveNodePlacements(stack, entries);
|
|
1192
|
+
add('placement.valid', 'Placement', 'pass', 'Configured node placement resolved.');
|
|
1193
|
+
}
|
|
1194
|
+
catch {
|
|
1195
|
+
add('placement.invalid', 'Placement', 'blocked', 'Node placement is incomplete or inconsistent.', 'Check selected applications, node identities and placement assignments.');
|
|
1196
|
+
}
|
|
1197
|
+
add('remote.prerequisites', 'Remote infrastructure', 'unknown', 'Host prerequisites must be inspected on each target node; local Docker is not evidence for a remote stack.', `Use omnitron infra inspect ${projectName}/${stackName} for host-service evidence and Nodes for RPC diagnostics.`);
|
|
1198
|
+
return done();
|
|
1199
|
+
}
|
|
1200
|
+
try {
|
|
1201
|
+
const effective = mergeInferredInfrastructure(buildInfraFromRequirements(requirementsFromDefinitions(definitions)), infrastructure);
|
|
1202
|
+
const migrations = requestedMigrations(expandServiceDeclarations(effective, createDefaultRegistry()));
|
|
1203
|
+
if (migrations.length) {
|
|
1204
|
+
try {
|
|
1205
|
+
const targets = await localMigrationTargets(migrations, entries, project.path, (names, files) => this.launchEntries(projectName, stackName, stack, config, names, files));
|
|
1206
|
+
if (migrationEnvironment) {
|
|
1207
|
+
const resolveMigration = this.stackEntryBuilder(projectName, stackName, stack, config, definitions, migrationEnvironment);
|
|
1208
|
+
validateMigrationDestinations(targets.map(target => {
|
|
1209
|
+
const entry = entries.find(app => app.name === target.app);
|
|
1210
|
+
return { app: target.app, database: migrationDatabaseEnvironment(target.environmentApp, resolveMigration(entry).env?.['DATABASE_URL'] ?? '', target.database, target.mode) };
|
|
1211
|
+
}));
|
|
1212
|
+
}
|
|
1213
|
+
add('migrations.entries', 'Database migration programs', 'pass', `Migration owners: ${targets.map(target => `${target.service}/${target.database} → ${target.app} (${target.mode === 'config' ? target.release : 'legacy script'})`).join('; ')}. Programs are available; database access, schema and execution remain unverified.`);
|
|
1214
|
+
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
add('migrations.unavailable', 'Database migration programs', 'blocked', describeFailure(error), 'Restore the requested migration program or prepare a complete verified release. No database or infrastructure was changed.');
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
const declaredServices = { ...effective.services };
|
|
1220
|
+
for (const definition of definitions.values())
|
|
1221
|
+
Object.assign(declaredServices, definition.omnitronConfig?.infrastructure);
|
|
1222
|
+
for (const [service, requirement] of Object.entries(declaredServices)) {
|
|
1223
|
+
// Presets acquire their provisioning definition in the shared resolver below.
|
|
1224
|
+
if ('preset' in requirement)
|
|
1225
|
+
continue;
|
|
1226
|
+
const binding = bindService(requirement, stack.serviceOverrides?.[service]);
|
|
1227
|
+
if (binding.provisioning === 'none')
|
|
1228
|
+
add('service.unconfigured', service, 'blocked', 'This service has no provisioning definition or external endpoint.');
|
|
1229
|
+
if (binding.provisioning === 'bareMetal' || binding.provisioning === 'external')
|
|
1230
|
+
add('service.host', service, 'unknown', 'This service uses a host or external endpoint; Docker readiness does not verify it.', 'Check the service endpoint, authentication and host configuration separately.');
|
|
1231
|
+
}
|
|
1232
|
+
const mounts = artifactVolumeBindings(effective);
|
|
1233
|
+
if (mounts.length) {
|
|
1234
|
+
add('release.mounts-unselected', 'Artifact volumes', 'blocked', 'These mounts require an installed release: ' + mounts.map(([, mount]) => mount.app + ' → ' + mount.service + ':' + mount.target).join(', '), 'Prepare and select the consumer release, then run diagnosis again.');
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
const containers = await this.infraManager.planStack(projectName, stackName, stack, effective, definitions, project.path);
|
|
1238
|
+
checks.push(...await diagnoseContainers(containers));
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
catch {
|
|
1242
|
+
add('infrastructure.invalid', 'Infrastructure plan', 'blocked', 'The infrastructure declaration could not be resolved.', 'Review service presets, port mappings and service overrides. No infrastructure was changed.');
|
|
1243
|
+
}
|
|
1244
|
+
return done();
|
|
1245
|
+
}
|
|
1246
|
+
/** Configuration evidence from the launch builder; it never starts, probes or modifies applications. */
|
|
1247
|
+
async getTopology(scope) {
|
|
1248
|
+
const result = { version: 1, generatedAt: new Date().toISOString(), dependencies: [], warnings: [], coverage: [] };
|
|
1249
|
+
const projects = scope.project ? [scope.project] : this.listProjectRegistrations().map(project => project.name);
|
|
1250
|
+
for (const project of projects) {
|
|
1251
|
+
let config;
|
|
1252
|
+
try {
|
|
1253
|
+
config = await this.ensureConfig(project);
|
|
1254
|
+
}
|
|
1255
|
+
catch {
|
|
1256
|
+
result.warnings.push({ project, reason: 'Project configuration could not be read' });
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
for (const [stack, stackConfig] of Object.entries(this.resolveStacks(config, project))) {
|
|
1260
|
+
if (scope.stack && stack !== scope.stack)
|
|
1261
|
+
continue;
|
|
1262
|
+
try {
|
|
1263
|
+
const entries = this.resolveStackApps(stackConfig, config);
|
|
1264
|
+
const pinned = stackConfig.type === 'local' && !stackConfig.managedOrigin && this.localSelection(project, stack).read().release;
|
|
1265
|
+
const definitions = new Map();
|
|
1266
|
+
const directory = this.registry.get(project)?.path;
|
|
1267
|
+
for (const entry of entries) {
|
|
1268
|
+
const handle = pinned ? this.orchestrator.getHandle(ProjectService.handleKey(project, stack, entry.name)) : undefined;
|
|
1269
|
+
if (!entry.bootstrap && (!entry.releaseFrom || (handle?.entry?.script && !handle.entry.bootstrap))) {
|
|
1270
|
+
result.coverage.push({ project, stack, app: entry.name, source: 'entry' });
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (!directory)
|
|
1274
|
+
continue;
|
|
1275
|
+
const bootstrap = pinned ? handle?.bootstrapMetadata?.bootstrap
|
|
1276
|
+
: entry.bootstrap ? resolveAppPath(directory, entry, entry.bootstrap) : undefined;
|
|
1277
|
+
if (!bootstrap) {
|
|
1278
|
+
result.coverage.push({ project, stack, app: entry.name, source: 'missing' });
|
|
1279
|
+
result.warnings.push({ project, stack, app: entry.name, reason: pinned ? 'Owner has not reported a launch declaration for the selected release' : 'Imported application has no selected release; prepare and select it before reading topology declarations' });
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
const { state, record, metadata, declared, observed } = this.readAppDeclaration(project, stack, entry.name, bootstrap);
|
|
1283
|
+
result.coverage.push({ project, stack, app: entry.name,
|
|
1284
|
+
source: record ? record === observed ? 'launch' : 'snapshot' : metadata ? 'memory' : 'missing',
|
|
1285
|
+
...(record && { capturedAt: record.capturedAt }) });
|
|
1286
|
+
if (!metadata)
|
|
1287
|
+
result.warnings.push({ project, stack, app: entry.name, reason: 'Bootstrap metadata not loaded; refresh declarations before relying on complete dependency coverage' });
|
|
1288
|
+
if (state?.failure)
|
|
1289
|
+
result.warnings.push({ project, stack, app: entry.name, reason: state.failure.reason });
|
|
1290
|
+
if (record && metadataSourceChanged(record))
|
|
1291
|
+
result.warnings.push({ project, stack, app: entry.name, reason: 'Bootstrap file changed after declaration capture; refresh metadata to inspect the current configuration' });
|
|
1292
|
+
if (declared.malformed)
|
|
1293
|
+
result.warnings.push({ project, stack, app: entry.name, reason: 'Application configuration is malformed; file declarations are unavailable' });
|
|
1294
|
+
if (metadata || declared.omnitron)
|
|
1295
|
+
definitions.set(entry.name, withDeclaredConfig({ name: entry.name, version: '', processes: [], ...metadata }, declared));
|
|
1296
|
+
}
|
|
1297
|
+
const env = await this.stackEnvFor(project, stack, stackConfig, entries.map(entry => entry.name));
|
|
1298
|
+
const build = this.stackEntryBuilder(project, stack, stackConfig, config, definitions, env);
|
|
1299
|
+
for (const entry of entries) {
|
|
1300
|
+
if (entry.enabled === false)
|
|
1301
|
+
continue;
|
|
1302
|
+
const definition = definitions.get(entry.name);
|
|
1303
|
+
result.dependencies.push(...appDependencies(project, stack, build(entry), definition));
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
catch {
|
|
1307
|
+
result.warnings.push({ project, stack, reason: 'Launch configuration could not be resolved; dependencies are incomplete' });
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return result;
|
|
1312
|
+
}
|
|
1313
|
+
/** Explicit administrative discovery. Imports trusted declarations in bounded child processes, never starts a stack. */
|
|
1314
|
+
async refreshTopologyMetadata(scope, deadline = Date.now() + TOPOLOGY_REFRESH_TIMEOUT_MS) {
|
|
1315
|
+
const config = await this.ensureConfig(scope.project);
|
|
1316
|
+
const directory = this.registry.get(scope.project)?.path;
|
|
1317
|
+
if (!directory)
|
|
1318
|
+
throw new Error('Project is not registered');
|
|
1319
|
+
const bootstraps = new Map();
|
|
1320
|
+
const stacks = this.resolveStacks(config, scope.project);
|
|
1321
|
+
if (scope.stack && !stacks[scope.stack])
|
|
1322
|
+
throw new Error('Stack is not configured');
|
|
1323
|
+
for (const [name, stack] of Object.entries(stacks)) {
|
|
1324
|
+
if (scope.stack && scope.stack !== name)
|
|
1325
|
+
continue;
|
|
1326
|
+
for (const app of await this.launchEntries(scope.project, name, stack, config))
|
|
1327
|
+
if (app.bootstrap)
|
|
1328
|
+
bootstraps.set(resolveAppPath(directory, app, app.bootstrap), app.name);
|
|
1329
|
+
}
|
|
1330
|
+
const result = { refreshed: 0, failures: [] };
|
|
1331
|
+
for (const [bootstrap, app] of bootstraps) {
|
|
1332
|
+
try {
|
|
1333
|
+
const remaining = Math.min(30_000, deadline - Date.now());
|
|
1334
|
+
if (remaining <= 0)
|
|
1335
|
+
throw new Error('Declaration refresh deadline exceeded; this application was not read');
|
|
1336
|
+
const { metadata: record } = await readBootstrapMetadata(bootstrap, false, remaining);
|
|
1337
|
+
this.bootstrapMetadata.save(record);
|
|
1338
|
+
this.bootstrapTopology.set(bootstrap, record.definition);
|
|
1339
|
+
result.refreshed += 1;
|
|
1340
|
+
}
|
|
1341
|
+
catch (error) {
|
|
1342
|
+
this.bootstrapMetadata.fail(bootstrap, error);
|
|
1343
|
+
this.logger.error({ project: scope.project, app, error }, 'Bootstrap metadata refresh failed; previous declaration retained');
|
|
1344
|
+
result.failures.push({ project: scope.project, app, reason: `Declaration read failed: ${publicLaunchFailure(error)}. Previous metadata was retained.` });
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
await this.audit?.record({ action: 'topology.metadata.refresh', resourceType: 'project', resourceId: scope.project,
|
|
1348
|
+
details: { stack: scope.stack, refreshed: result.refreshed, failures: result.failures.length } });
|
|
1349
|
+
return result;
|
|
1350
|
+
}
|
|
503
1351
|
listStacks(projectName) {
|
|
504
1352
|
const config = this.getLoadedConfig(projectName);
|
|
505
1353
|
if (!config)
|
|
@@ -524,12 +1372,12 @@ export class ProjectService extends EventEmitter {
|
|
|
524
1372
|
* loaded and the short-circuits above have run — so two callers that arrive
|
|
525
1373
|
* together both see no state and both proceed. Measured on the test stack:
|
|
526
1374
|
* the daemon's own `startProjectStacks` on boot and an operator's
|
|
527
|
-
* `omnitron stack start
|
|
1375
|
+
* `omnitron stack start sample test` landed within twenty seconds of each
|
|
528
1376
|
* other, and ran the whole remote deployment TWICE, concurrently.
|
|
529
1377
|
*
|
|
530
1378
|
* That is not merely wasteful. The artifact build rebuilds shared packages
|
|
531
1379
|
* whose `build` script is `rm -rf dist && tsc`, so one pass emptied
|
|
532
|
-
* `@omnitron-dev/titan-database/dist` while the other compiled `@
|
|
1380
|
+
* `@omnitron-dev/titan-database/dist` while the other compiled `@sample/main`
|
|
533
1381
|
* against it:
|
|
534
1382
|
*
|
|
535
1383
|
* src/modules/rbac/rls-schema.ts(53,41): error TS2307: Cannot find
|
|
@@ -543,18 +1391,32 @@ export class ProjectService extends EventEmitter {
|
|
|
543
1391
|
* asking for a stack that is already being started should end when it has
|
|
544
1392
|
* been started, which is what the caller meant.
|
|
545
1393
|
*/
|
|
1394
|
+
rereading;
|
|
546
1395
|
startsInFlight = new Map();
|
|
547
1396
|
async startStack(projectName, stackName, opts) {
|
|
1397
|
+
if (this.resumingInstalled)
|
|
1398
|
+
throw new Error('An installed allocation is resuming');
|
|
1399
|
+
if (this.ownershipFrozen)
|
|
1400
|
+
throw new Error('Project ownership is frozen for backend transfer');
|
|
548
1401
|
const inFlightKey = `${projectName}/${stackName}`;
|
|
1402
|
+
if (this.stopsInFlight.has(inFlightKey))
|
|
1403
|
+
throw new Error('This stack is stopping; wait for its acknowledgement');
|
|
1404
|
+
if (this.rereading?.has(projectName))
|
|
1405
|
+
throw new Error('Project definitions are being updated; retry the start');
|
|
1406
|
+
if (opts?.workingTree && opts.release)
|
|
1407
|
+
throw new Error('Select either a release or the working tree');
|
|
1408
|
+
const selection = JSON.stringify([opts?.release, opts?.workingTree === true, opts?.reinstall === true]);
|
|
549
1409
|
const running = this.startsInFlight.get(inFlightKey);
|
|
550
1410
|
if (running) {
|
|
551
1411
|
this.logger.info({ project: projectName, stack: stackName }, 'This stack is already being started — joining the run in progress');
|
|
552
|
-
|
|
1412
|
+
if (running.selection !== selection)
|
|
1413
|
+
throw new Error('Another stack start selected different launch code; retry after it completes');
|
|
1414
|
+
return running.promise;
|
|
553
1415
|
}
|
|
554
|
-
const started = this.startStackOnce(projectName, stackName, opts?.source ?? 'unknown', opts?.allowDirty === true, opts?.release, opts?.reinstall === true).finally(() => {
|
|
1416
|
+
const started = this.startStackOnce(projectName, stackName, opts?.source ?? 'unknown', opts?.allowDirty === true, opts?.release, opts?.reinstall === true, opts?.workingTree === true).finally(() => {
|
|
555
1417
|
this.startsInFlight.delete(inFlightKey);
|
|
556
1418
|
});
|
|
557
|
-
this.startsInFlight.set(inFlightKey, started);
|
|
1419
|
+
this.startsInFlight.set(inFlightKey, { selection, promise: started });
|
|
558
1420
|
return started;
|
|
559
1421
|
}
|
|
560
1422
|
/**
|
|
@@ -566,7 +1428,7 @@ export class ProjectService extends EventEmitter {
|
|
|
566
1428
|
* the trail began (2026-09-20 15:37Z), not one of them a failure, against
|
|
567
1429
|
* the log's eight operator starts that threw — five dirty-tree refusals
|
|
568
1430
|
* (09-22 04:37:24 … 08:27:49), a release refused for a failed gate
|
|
569
|
-
* (15:12:41), «
|
|
1431
|
+
* (15:12:41), «sample/test takes releases only» (15:32:43), and the
|
|
570
1432
|
* deployment of 21:19:02 that failed on 6 of 6 apps, followed at 21:20:37
|
|
571
1433
|
* by the redeploy the trail DID record, alone, as if it had been the only
|
|
572
1434
|
* attempt.
|
|
@@ -582,10 +1444,10 @@ export class ProjectService extends EventEmitter {
|
|
|
582
1444
|
* deploys nothing, and the reconciler re-asks a node that does not answer
|
|
583
1445
|
* every few minutes — rows about looking, not about changing anything.
|
|
584
1446
|
*/
|
|
585
|
-
async startStackOnce(projectName, stackName, source, allowDirty, releaseId, reinstall = false) {
|
|
1447
|
+
async startStackOnce(projectName, stackName, source, allowDirty, releaseId, reinstall = false, workingTree = false) {
|
|
586
1448
|
const known = { attach: false };
|
|
587
1449
|
try {
|
|
588
|
-
return await this.runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall);
|
|
1450
|
+
return await this.runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall, workingTree);
|
|
589
1451
|
}
|
|
590
1452
|
catch (err) {
|
|
591
1453
|
if (!known.attach) {
|
|
@@ -598,7 +1460,7 @@ export class ProjectService extends EventEmitter {
|
|
|
598
1460
|
details: {
|
|
599
1461
|
source,
|
|
600
1462
|
...(known.type ? { type: known.type } : {}),
|
|
601
|
-
...(releaseId ? { release: releaseId } : {}),
|
|
1463
|
+
...((releaseId ?? known.release) ? { release: releaseId ?? known.release } : {}),
|
|
602
1464
|
...(known.commit !== undefined ? { commit: known.commit } : {}),
|
|
603
1465
|
},
|
|
604
1466
|
});
|
|
@@ -606,23 +1468,34 @@ export class ProjectService extends EventEmitter {
|
|
|
606
1468
|
throw err;
|
|
607
1469
|
}
|
|
608
1470
|
}
|
|
609
|
-
async runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall = false) {
|
|
1471
|
+
async runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall = false, workingTree = false) {
|
|
610
1472
|
const config = await this.loadProjectConfig(projectName);
|
|
611
1473
|
const stacks = this.resolveStacks(config, projectName);
|
|
612
1474
|
const stackConfig = stacks[stackName];
|
|
613
1475
|
if (!stackConfig)
|
|
614
1476
|
throw new Error(`Stack '${stackName}' not found in project '${projectName}'`);
|
|
615
1477
|
known.type = stackConfig.type;
|
|
1478
|
+
ownsStackInfrastructure(stackConfig); // Reject ambiguous ownership before attachment, build, migration or provisioning.
|
|
616
1479
|
// A restart of this daemon re-attaches a remote stack; it never deploys
|
|
617
1480
|
// one. See `attachRemoteStack`.
|
|
618
1481
|
if (stackConfig.type !== 'local' && (source === 'boot' || source === 'auto-resume')) {
|
|
619
1482
|
known.attach = true;
|
|
620
1483
|
return this.attachRemoteStack(projectName, stackName, stackConfig, source);
|
|
621
1484
|
}
|
|
1485
|
+
this.assertStartAuthority({ project: projectName, stack: stackName }, stackConfig);
|
|
622
1486
|
// A release, when one is named — and a stack that takes nothing else.
|
|
1487
|
+
const local = stackConfig.type === 'local' && !stackConfig.managedOrigin;
|
|
1488
|
+
if ((workingTree || (releaseId && stackConfig.type === 'local')) && !local)
|
|
1489
|
+
throw new Error('Only an independently managed local stack can change its local release selection');
|
|
1490
|
+
const selector = local ? this.localSelection(projectName, stackName) : null;
|
|
1491
|
+
const prior = selector?.read();
|
|
1492
|
+
const requested = releaseId ?? (workingTree ? undefined : prior?.release?.id);
|
|
623
1493
|
let release = null;
|
|
624
|
-
if (
|
|
625
|
-
release =
|
|
1494
|
+
if (requested) {
|
|
1495
|
+
known.release = requested;
|
|
1496
|
+
release = await this.admitRelease(projectName, stackName, stackConfig, config, requested);
|
|
1497
|
+
if (prior?.release?.id === requested && prior.release.manifestSha256 !== release.manifestSha256)
|
|
1498
|
+
throw new Error('The selected release manifest changed on disk; restore its original bytes');
|
|
626
1499
|
}
|
|
627
1500
|
else if (stackConfig.release?.mode === 'required') {
|
|
628
1501
|
throw new Error(`${projectName}/${stackName} takes releases only: \`omnitron release build ${projectName} --for ${stackName}\`, ` +
|
|
@@ -677,7 +1550,7 @@ export class ProjectService extends EventEmitter {
|
|
|
677
1550
|
// With a release the disk has already been held to the release's commit
|
|
678
1551
|
// (`admitRelease`) — stricter than HEAD, and the artifacts are not built
|
|
679
1552
|
// from it at all.
|
|
680
|
-
if (stackConfig.type
|
|
1553
|
+
if (stackConfig.type !== 'local' && !release) {
|
|
681
1554
|
if (!allowDirty) {
|
|
682
1555
|
for (const { root, tree: t } of trees) {
|
|
683
1556
|
const refusal = refusalForDirtyTree(t, `${projectName}/${stackName}`);
|
|
@@ -695,10 +1568,32 @@ export class ProjectService extends EventEmitter {
|
|
|
695
1568
|
this.logger.warn({ project: projectName, stack: stackName, why: tree.why }, 'Could not tell whether the working tree matches its commit — shipping what is on disk');
|
|
696
1569
|
}
|
|
697
1570
|
}
|
|
1571
|
+
const selected = release ? { id: release.id, manifestSha256: release.manifestSha256 } : null;
|
|
1572
|
+
const changing = !!selector && !isDeepStrictEqual(prior?.release, selected);
|
|
1573
|
+
const mounted = artifactVolumeBindings(mergeInfrastructure(config, stackConfig));
|
|
1574
|
+
if (mounted.length && (!local || !release || !ownsStackInfrastructure(stackConfig)))
|
|
1575
|
+
throw Error('Artifact volumes require a selected release on an independently owned local stack');
|
|
1576
|
+
const installation = mounted.length && release
|
|
1577
|
+
? await this.installedRelease(projectName, stackName, stackConfig, config, release, true) : undefined;
|
|
1578
|
+
const entries = installation?.entries ?? (local && release ? await this.installedEntries(projectName, stackName, stackConfig, config, release, true) : undefined);
|
|
1579
|
+
let localMembershipChanged = false;
|
|
1580
|
+
if (local) {
|
|
1581
|
+
const target = entries ?? this.resolveStackApps(stackConfig, config);
|
|
1582
|
+
const plan = await this.checkLocalOwner(projectName, stackName, target, changing, source === 'boot' || source === 'auto-resume');
|
|
1583
|
+
// A changed immutable mount may not reach Docker before the ordinary owner stop fence.
|
|
1584
|
+
if (mounted.length && changing && plan.phase !== 'stopped')
|
|
1585
|
+
throw new LocalReleaseStopRequired('Stop this local stack before changing its immutable artifact volumes');
|
|
1586
|
+
const names = new Set(target.map(entry => ProjectService.handleKey(projectName, stackName, entry.name)));
|
|
1587
|
+
localMembershipChanged = plan.apps.some(app => !names.has(app.name));
|
|
1588
|
+
if ((source === 'boot' || source === 'auto-resume') && plan.phase !== 'running')
|
|
1589
|
+
throw new Error('The selected local stack is fenced; an explicit start must recover or roll back the selection');
|
|
1590
|
+
}
|
|
698
1591
|
const stateKey = `${projectName}/${stackName}`;
|
|
699
1592
|
const existing = this.stackStates.get(stateKey);
|
|
700
1593
|
// Not for a release: putting a release onto a running stack is the point.
|
|
701
|
-
|
|
1594
|
+
const completedLocalStart = stackConfig.type === 'local' && existing?.startedAt != null &&
|
|
1595
|
+
(existing.status === 'degraded' || existing.status === 'error');
|
|
1596
|
+
if ((existing?.status === 'running' || completedLocalStart) && !changing && !localMembershipChanged && (!release || local)) {
|
|
702
1597
|
// `running` is a claim about the past, and nothing revises it when the
|
|
703
1598
|
// apps underneath fall over — so this short-circuit used to hand the
|
|
704
1599
|
// operator a stack it had not touched. `toStackInfo` reads live
|
|
@@ -713,7 +1608,7 @@ export class ProjectService extends EventEmitter {
|
|
|
713
1608
|
// applications because they run on the node — so `down` was all six,
|
|
714
1609
|
// every time, and this short-circuit could never fire for a remote
|
|
715
1610
|
// stack. Measured 2026-09-22: six occurrences, every one of them
|
|
716
|
-
// `down: [main, storage,
|
|
1611
|
+
// `down: [main, storage, quotes, payments, messaging, geo]` against
|
|
717
1612
|
// a node where all six were online with pids and uptimes to show for
|
|
718
1613
|
// it. Each fell through to a full deployment of a stack that needed
|
|
719
1614
|
// nothing — which is most of the «four master restarts, four full
|
|
@@ -724,7 +1619,9 @@ export class ProjectService extends EventEmitter {
|
|
|
724
1619
|
// wrapper returns the info untouched.
|
|
725
1620
|
const current = await this.withRemoteAppStatuses(projectName, this.toStackInfo(projectName, stackName, stackConfig));
|
|
726
1621
|
const down = current.apps.filter((a) => a.status !== 'online');
|
|
727
|
-
if (down.length === 0) {
|
|
1622
|
+
if (down.length === 0 && !current.notUp?.length) {
|
|
1623
|
+
if (existing)
|
|
1624
|
+
existing.status = 'running';
|
|
728
1625
|
this.logger.warn({ project: projectName, stack: stackName }, 'Stack already running');
|
|
729
1626
|
return current;
|
|
730
1627
|
}
|
|
@@ -751,33 +1648,63 @@ export class ProjectService extends EventEmitter {
|
|
|
751
1648
|
this.emit('stack:starting', projectName, stackName, stackConfig.type);
|
|
752
1649
|
let reach = null;
|
|
753
1650
|
try {
|
|
1651
|
+
this.assertStackMutable(stackConfig.managedOrigin ?? { project: projectName, stack: stackName });
|
|
754
1652
|
if (stackConfig.type === 'local') {
|
|
755
|
-
|
|
1653
|
+
const scope = { project: projectName, stack: stackName };
|
|
1654
|
+
const enable = () => this.orchestrator.enableStackExecution(scope, { automatic: source === 'boot' || source === 'auto-resume' });
|
|
1655
|
+
if (selector && prior) {
|
|
1656
|
+
const target = entries ?? this.resolveStackApps(stackConfig, config);
|
|
1657
|
+
await this.startLocalStack(projectName, stackName, stackConfig, config, target, async (build) => {
|
|
1658
|
+
await this.prepareLocalOwner(projectName, stackName, target, changing, build, () => {
|
|
1659
|
+
if (changing)
|
|
1660
|
+
selector.select(prior.revision, selected);
|
|
1661
|
+
else if (selector.read().revision !== prior.revision)
|
|
1662
|
+
throw new Error('Local release selection changed during admission');
|
|
1663
|
+
});
|
|
1664
|
+
await enable();
|
|
1665
|
+
}, release ? (names, files) => this.installedEntries(projectName, stackName, stackConfig, config, release, false, names, files) : undefined, installation ? async () => {
|
|
1666
|
+
const plan = await this.checkLocalOwner(projectName, stackName, target, changing);
|
|
1667
|
+
if (changing && plan.phase !== 'stopped')
|
|
1668
|
+
throw new LocalReleaseStopRequired('Artifact volume selection requires the owner stop fence');
|
|
1669
|
+
return (await this.installedRelease(projectName, stackName, stackConfig, config, release, false)).infrastructure;
|
|
1670
|
+
} : undefined);
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
await this.startLocalStack(projectName, stackName, stackConfig, config, undefined, async () => { await enable(); });
|
|
1674
|
+
}
|
|
756
1675
|
}
|
|
757
|
-
else
|
|
1676
|
+
else {
|
|
1677
|
+
this.rememberStackTargets(projectName, stackName, stackConfig, config);
|
|
758
1678
|
reach = await this.startRemoteStack(projectName, stackName, stackConfig, config, release, reinstall);
|
|
759
1679
|
}
|
|
760
|
-
else if (stackConfig.type === 'cluster') {
|
|
761
|
-
await this.startClusterStack(projectName, stackName, stackConfig, config);
|
|
762
|
-
}
|
|
763
1680
|
state.status = 'running';
|
|
764
1681
|
state.startedAt = Date.now();
|
|
765
1682
|
// Update registry with enabled stack
|
|
766
1683
|
this.updateEnabledStacks(projectName, stackName, true);
|
|
767
|
-
this.
|
|
768
|
-
this.logger.info({ project: projectName, stack: stackName, source }, 'Stack started');
|
|
1684
|
+
this.markPriorStopSuperseded(projectName, stackName);
|
|
769
1685
|
const info = this.toStackInfo(projectName, stackName, stackConfig);
|
|
770
1686
|
// What did not come up, said in the row. A boot at 12:14 UTC on
|
|
771
|
-
// 2026-09-23 started
|
|
1687
|
+
// 2026-09-23 started sample/dev with payments and messaging errored and
|
|
772
1688
|
// recorded `outcome: ok, apps: 6`: the stack «started», two of its six
|
|
773
1689
|
// apps never did. For a local stack the apps are this daemon's; for a
|
|
774
1690
|
// remote one, what did not happen is a node that was skipped.
|
|
775
1691
|
const notUp = stackConfig.type === 'local'
|
|
776
|
-
? info.apps.filter((a) => a.status !== 'online').map((a) => `${a.name} (${a.status})`)
|
|
1692
|
+
? info.notUp ?? info.apps.filter((a) => a.status !== 'online').map((a) => `${a.name} (${a.status})`)
|
|
777
1693
|
: [
|
|
778
1694
|
...(reach?.skipped ?? []).map((n) => `node ${typeof n === 'string' ? n : JSON.stringify(n)}`),
|
|
779
1695
|
...(reach?.notReady ?? []).map((n) => `infrastructure on ${n}`),
|
|
780
1696
|
];
|
|
1697
|
+
if (stackConfig.type === 'local')
|
|
1698
|
+
state.status = info.status;
|
|
1699
|
+
const outcome = notUp.length === 0 ? 'ok' : state.status === 'error' ? 'failed' : 'partial';
|
|
1700
|
+
if (outcome === 'ok') {
|
|
1701
|
+
this.emit('stack:started', projectName, stackName, stackConfig.type, info.apps.length, info.nodes?.length ?? 0);
|
|
1702
|
+
this.logger.info({ project: projectName, stack: stackName, source }, 'Stack started');
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
this.emit('stack:error', projectName, stackName, notUp.join(', '));
|
|
1706
|
+
this.logger.warn({ project: projectName, stack: stackName, source, outcome, notUp }, 'Stack start incomplete');
|
|
1707
|
+
}
|
|
781
1708
|
// Recorded for every caller, and saying WHICH one. `source: 'unknown'`
|
|
782
1709
|
// is deliberate rather than a default of `'operator'`: a caller that
|
|
783
1710
|
// has not been taught to identify itself should be visible in the
|
|
@@ -791,7 +1718,7 @@ export class ProjectService extends EventEmitter {
|
|
|
791
1718
|
resourceType: 'stack',
|
|
792
1719
|
resourceId: `${projectName}/${stackName}`,
|
|
793
1720
|
// Said, now that the other ending is recorded too (`startStackOnce`).
|
|
794
|
-
outcome
|
|
1721
|
+
outcome,
|
|
795
1722
|
// The commit, so the trail answers what went out and not only when.
|
|
796
1723
|
// A node's artifacts carry no revision (every app is version `0.0.1`
|
|
797
1724
|
// forever, and `BUNDLE.json` is excluded from the checksum and read
|
|
@@ -822,6 +1749,7 @@ export class ProjectService extends EventEmitter {
|
|
|
822
1749
|
...(release
|
|
823
1750
|
? {
|
|
824
1751
|
release: release.id,
|
|
1752
|
+
releaseManifestSha256: release.manifestSha256,
|
|
825
1753
|
releaseProjectCommit: release.manifest.project.commit.slice(0, 8),
|
|
826
1754
|
releaseOmniCommit: release.manifest.omni.commit.slice(0, 8),
|
|
827
1755
|
}
|
|
@@ -842,58 +1770,299 @@ export class ProjectService extends EventEmitter {
|
|
|
842
1770
|
throw err;
|
|
843
1771
|
}
|
|
844
1772
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1773
|
+
declaredStopTargets(stack, config) {
|
|
1774
|
+
if (stack.type === 'local')
|
|
1775
|
+
return [];
|
|
1776
|
+
const apps = this.resolveStackApps(stack, config).map(app => app.name);
|
|
1777
|
+
return (stack.nodes ?? []).map(node => ({ host: node.host, port: node.port ?? 9700,
|
|
1778
|
+
applications: nodeApplicationNames(stack, node, apps).length > 0, ...(node.nodeId && { nodeId: node.nodeId }) }));
|
|
1779
|
+
}
|
|
1780
|
+
stopRequest(scope, node, keepInfrastructure = false) {
|
|
1781
|
+
if (this.clusterStop && !node.nodeId)
|
|
1782
|
+
throw new Error('A pinned recipient nodeId is required for clustered stack stop');
|
|
1783
|
+
return { ...scope, applications: node.applications, ...(keepInfrastructure && { keepInfrastructure: true }),
|
|
1784
|
+
...(this.clusterStop && { audience: { clusterId: this.clusterStop.recipient.audience.clusterId, nodeId: node.nodeId } }) };
|
|
1785
|
+
}
|
|
1786
|
+
validateStopInventory(request, result) {
|
|
1787
|
+
if (result?.protocol !== 1 || result.project !== request.project || result.stack !== request.stack ||
|
|
1788
|
+
result.applications !== request.applications || !result.definition || !Array.isArray(result.containers) ||
|
|
1789
|
+
(request.applications ? !result.execution || !Array.isArray(result.execution.apps) : result.execution !== null)) {
|
|
1790
|
+
throw new Error('Invalid node stop inventory; update the recipient before stopping');
|
|
1791
|
+
}
|
|
1792
|
+
if (result.audience?.clusterId !== request.audience?.clusterId || result.audience?.nodeId !== request.audience?.nodeId) {
|
|
1793
|
+
throw new Error('Recipient did not confirm the required cluster and node identity');
|
|
855
1794
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1795
|
+
if ((result.keepInfrastructure === true) !== (request.keepInfrastructure === true) ||
|
|
1796
|
+
(result.keepInfrastructure !== undefined && typeof result.keepInfrastructure !== 'boolean')) {
|
|
1797
|
+
throw new Error('Recipient did not confirm the requested infrastructure retention mode; update it before stopping');
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
async localStopInventory(scope, stack, execution, keepInfrastructure = false) {
|
|
1801
|
+
const containers = await this.infraManager.inspectStopContainers(scope.project, scope.stack);
|
|
1802
|
+
return { ...scope, protocol: 1, applications: true, execution, definition: stackDefinitionFingerprint(stack),
|
|
1803
|
+
containers, ...(keepInfrastructure && { keepInfrastructure: true }) };
|
|
1804
|
+
}
|
|
1805
|
+
async reviewStackStop(project, stack, keepInfrastructure = false) {
|
|
1806
|
+
if (typeof keepInfrastructure !== 'boolean')
|
|
1807
|
+
throw new Error('Invalid infrastructure retention mode');
|
|
1808
|
+
const scope = { project, stack };
|
|
1809
|
+
const key = stackExecutionKey(scope);
|
|
1810
|
+
const targets = [];
|
|
859
1811
|
try {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1812
|
+
if (this.ownershipFrozen || this.resumingInstalled || this.startsInFlight.has(key) || this.stopsInFlight.has(key))
|
|
1813
|
+
throw new Error('A lifecycle operation is active; wait and check again');
|
|
1814
|
+
if (this.clusterStop) {
|
|
1815
|
+
if (!this.clusterStop.election)
|
|
1816
|
+
throw new Error('Review this stop on the elected controller');
|
|
1817
|
+
await this.clusterStop.election.confirmStopAuthority();
|
|
1818
|
+
}
|
|
1819
|
+
const config = this.getLoadedConfig(project);
|
|
1820
|
+
if (!config)
|
|
1821
|
+
throw new Error('Project declarations are unavailable; refresh them before reviewing this stop');
|
|
1822
|
+
const declaration = this.stackStates.get(key)?.config ?? this.resolveStacks(config, project)[stack];
|
|
1823
|
+
if (!declaration)
|
|
1824
|
+
throw new Error(`Stack '${key}' not found`);
|
|
1825
|
+
const journal = new StackTargets(this.daemonStateStore);
|
|
1826
|
+
const { nodes } = journal.inspect(scope, this.declaredStopTargets(declaration, config));
|
|
1827
|
+
if (declaration.type === 'local') {
|
|
864
1828
|
try {
|
|
865
|
-
|
|
1829
|
+
if (this.clusterStop && !declaration.managedOrigin)
|
|
1830
|
+
throw new Error('Cluster stack stop requires an explicitly bound allocation');
|
|
1831
|
+
const inventory = declaration.managedOrigin
|
|
1832
|
+
? await this.nodeStackStop.inspect({ ...declaration.managedOrigin, applications: true,
|
|
1833
|
+
...(keepInfrastructure && { keepInfrastructure: true }),
|
|
1834
|
+
...(this.clusterStop && { audience: this.clusterStop.recipient.audience }) })
|
|
1835
|
+
: await this.localStopInventory(scope, declaration, await this.orchestrator.inspectStackExecution(scope), keepInfrastructure);
|
|
1836
|
+
targets.push({ node: 'local', inventory });
|
|
866
1837
|
}
|
|
867
|
-
catch (
|
|
868
|
-
|
|
1838
|
+
catch (error) {
|
|
1839
|
+
targets.push({ node: 'local', reason: describeFailure(error) });
|
|
869
1840
|
}
|
|
870
1841
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1842
|
+
else if (!nodes.length)
|
|
1843
|
+
throw new Error('No remote allocation inventory is available');
|
|
1844
|
+
for (const node of nodes) {
|
|
1845
|
+
const endpoint = stackNodeKey(node.host, node.port);
|
|
1846
|
+
try {
|
|
1847
|
+
if (!this.slaveConnector)
|
|
1848
|
+
throw new Error('Node RPC is unavailable');
|
|
1849
|
+
const request = this.stopRequest(scope, node, keepInfrastructure);
|
|
1850
|
+
const inventory = await this.slaveConnector.invokeOnSlave(node.host, node.port, 'OmnitronProject', 'inspectNodeStackStop', [request]);
|
|
1851
|
+
this.validateStopInventory(request, inventory);
|
|
1852
|
+
targets.push({ node: endpoint, inventory });
|
|
1853
|
+
}
|
|
1854
|
+
catch (error) {
|
|
1855
|
+
targets.push({ node: endpoint, reason: describeFailure(error) });
|
|
875
1856
|
}
|
|
876
1857
|
}
|
|
877
|
-
|
|
878
|
-
|
|
1858
|
+
}
|
|
1859
|
+
catch (error) {
|
|
1860
|
+
targets.push({ node: 'controller', reason: describeFailure(error) });
|
|
1861
|
+
}
|
|
1862
|
+
return stackStopReview(scope, targets, keepInfrastructure);
|
|
1863
|
+
}
|
|
1864
|
+
async stopStack(projectName, stackName, review, keepInfrastructure = false) {
|
|
1865
|
+
if (typeof keepInfrastructure !== 'boolean')
|
|
1866
|
+
throw new Error('Invalid infrastructure retention mode');
|
|
1867
|
+
const key = stackExecutionKey({ project: projectName, stack: stackName });
|
|
1868
|
+
if (this.resumingInstalled)
|
|
1869
|
+
throw new Error('An installed allocation is resuming');
|
|
1870
|
+
if (this.ownershipFrozen)
|
|
1871
|
+
throw new Error('Project ownership is frozen for backend transfer');
|
|
1872
|
+
if (this.startsInFlight.has(key))
|
|
1873
|
+
throw new Error('This stack is starting; finish or reconcile the start before stopping');
|
|
1874
|
+
const pending = this.stopsInFlight.get(key);
|
|
1875
|
+
if (pending && (review !== undefined || pending.keepInfrastructure !== keepInfrastructure))
|
|
1876
|
+
throw new Error('A stop is already running; inspect its progress');
|
|
1877
|
+
if (pending)
|
|
1878
|
+
return pending.promise;
|
|
1879
|
+
if (review !== undefined && (typeof review !== 'string' || !/^[a-f0-9]{64}$/.test(review)))
|
|
1880
|
+
throw new Error('Invalid stack stop review');
|
|
1881
|
+
const operation = this.stopStackOnce(projectName, stackName, review, keepInfrastructure).finally(() => this.stopsInFlight.delete(key));
|
|
1882
|
+
this.stopsInFlight.set(key, { keepInfrastructure, promise: operation });
|
|
1883
|
+
return operation;
|
|
1884
|
+
}
|
|
1885
|
+
async stopStackOnce(projectName, stackName, review, keepInfrastructure) {
|
|
1886
|
+
const key = `${projectName}/${stackName}`;
|
|
1887
|
+
const config = await this.loadProjectConfig(projectName);
|
|
1888
|
+
const stackConfig = this.stackStates.get(key)?.config ?? this.resolveStacks(config, projectName)[stackName];
|
|
1889
|
+
if (!stackConfig)
|
|
1890
|
+
throw new Error(`Stack '${stackName}' not found`);
|
|
1891
|
+
const state = this.stackStates.get(key) ?? {
|
|
1892
|
+
project: projectName, stack: stackName, status: 'stopped',
|
|
1893
|
+
config: stackConfig, startedAt: null, infraService: null,
|
|
1894
|
+
};
|
|
1895
|
+
this.stackStates.set(key, state);
|
|
1896
|
+
const progress = {
|
|
1897
|
+
id: randomUUID(), phase: 'preparing', startedAt: new Date().toISOString(), nodes: [],
|
|
1898
|
+
...(keepInfrastructure && { keepInfrastructure: true }),
|
|
1899
|
+
};
|
|
1900
|
+
const persist = () => this.daemonStateStore.kvUpdateSync(`stack:${key}:last-stop`, () => progress);
|
|
1901
|
+
persist();
|
|
1902
|
+
try {
|
|
1903
|
+
// Complete every read-only admission check before sending any stop.
|
|
1904
|
+
const prepared = [];
|
|
1905
|
+
const cluster = this.clusterStop;
|
|
1906
|
+
if (cluster && !cluster.election?.isLeader)
|
|
1907
|
+
throw new Error('Stack stop requires the elected controller of this cluster');
|
|
1908
|
+
let managedPlan = null;
|
|
1909
|
+
let managedPermit;
|
|
1910
|
+
let targetsRevision;
|
|
1911
|
+
let localPlan = null;
|
|
1912
|
+
if (stackConfig.type === 'local' && stackConfig.managedOrigin) {
|
|
1913
|
+
managedPlan = await this.nodeStackStop.prepare({ ...stackConfig.managedOrigin, applications: true,
|
|
1914
|
+
...(keepInfrastructure && { keepInfrastructure: true }),
|
|
1915
|
+
...(cluster && { audience: cluster.recipient.audience }) });
|
|
1916
|
+
managedPermit = cluster?.election?.authorizeStop(managedPlan);
|
|
1917
|
+
}
|
|
1918
|
+
else if (stackConfig.type === 'local') {
|
|
1919
|
+
if (cluster)
|
|
1920
|
+
throw new Error('Cluster stack stop requires an explicitly bound allocation');
|
|
1921
|
+
localPlan = await this.orchestrator.inspectStackExecution({ project: projectName, stack: stackName });
|
|
1922
|
+
}
|
|
1923
|
+
const scope = { project: projectName, stack: stackName };
|
|
1924
|
+
const localInventory = localPlan ? await this.localStopInventory(scope, stackConfig, localPlan, keepInfrastructure) : null;
|
|
1925
|
+
const declared = this.declaredStopTargets(stackConfig, config);
|
|
1926
|
+
const targetJournal = new StackTargets(this.daemonStateStore);
|
|
1927
|
+
if (declared.length)
|
|
1928
|
+
targetJournal.remember({ project: projectName, stack: stackName }, declared);
|
|
1929
|
+
const targets = targetJournal.inspect({ project: projectName, stack: stackName });
|
|
1930
|
+
if (stackConfig.type !== 'local' && targets.nodes.length === 0)
|
|
1931
|
+
throw new Error('No remote allocation inventory is available; stop cannot be confirmed');
|
|
1932
|
+
if (targets.nodes.length) {
|
|
1933
|
+
if (!this.slaveConnector)
|
|
1934
|
+
throw new Error('Node RPC is unavailable; no remote stop has been sent');
|
|
1935
|
+
targetsRevision = targets.revision;
|
|
1936
|
+
progress.nodes = targets.nodes.map(node => ({ node: `${node.host}:${node.port}`, outcome: 'checking' }));
|
|
1937
|
+
persist();
|
|
1938
|
+
for (const [index, node] of targets.nodes.entries()) {
|
|
1939
|
+
let plan;
|
|
1940
|
+
try {
|
|
1941
|
+
const request = this.stopRequest(scope, node, keepInfrastructure);
|
|
1942
|
+
plan = await this.slaveConnector.invokeOnSlave(node.host, node.port, 'OmnitronProject', 'prepareNodeStackStop', [request]);
|
|
1943
|
+
this.validateStopInventory(request, plan);
|
|
1944
|
+
if (!plan.operationId)
|
|
1945
|
+
throw new Error('Invalid node stop plan');
|
|
1946
|
+
const permit = cluster?.election?.authorizeStop(plan);
|
|
1947
|
+
prepared.push({ host: node.host, port: node.port, plan, permit });
|
|
1948
|
+
if (permit && request.audience)
|
|
1949
|
+
progress.nodes[index].authority = { ...request.audience, epoch: permit.epoch };
|
|
1950
|
+
}
|
|
1951
|
+
catch (error) {
|
|
1952
|
+
progress.nodes[index].outcome = 'unknown';
|
|
1953
|
+
progress.nodes[index].reason = describeFailure(error);
|
|
1954
|
+
throw new Error(`Cannot prepare ${node.host}:${node.port}: ${describeFailure(error)}`, { cause: error });
|
|
1955
|
+
}
|
|
1956
|
+
progress.nodes[index].outcome = 'prepared';
|
|
1957
|
+
persist();
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
if (review !== undefined) {
|
|
1961
|
+
const actual = [];
|
|
1962
|
+
if (managedPlan)
|
|
1963
|
+
actual.push({ node: 'local', inventory: managedPlan });
|
|
1964
|
+
if (localInventory)
|
|
1965
|
+
actual.push({ node: 'local', inventory: localInventory });
|
|
1966
|
+
for (const target of prepared)
|
|
1967
|
+
actual.push({ node: stackNodeKey(target.host, target.port), inventory: target.plan });
|
|
1968
|
+
if (stackStopReview(scope, actual, keepInfrastructure).digest !== review)
|
|
1969
|
+
throw new Error('Stack changed since review; inspect the current footprint before stopping');
|
|
1970
|
+
}
|
|
1971
|
+
// Losing this durable write refuses the operation before any effect.
|
|
1972
|
+
if (cluster)
|
|
1973
|
+
await cluster.election.confirmStopAuthority();
|
|
1974
|
+
this.updateEnabledStacks(projectName, stackName, false);
|
|
1975
|
+
progress.phase = 'stopping';
|
|
1976
|
+
progress.intentCommitted = true;
|
|
1977
|
+
persist();
|
|
1978
|
+
state.status = 'stopping';
|
|
1979
|
+
this.emit('stack:stopping', projectName, stackName);
|
|
1980
|
+
if (managedPlan) {
|
|
1981
|
+
const receipt = await this.nodeStackStop.execute({ ...stackConfig.managedOrigin, operationId: managedPlan.operationId,
|
|
1982
|
+
...(keepInfrastructure && { keepInfrastructure: true }), ...(managedPermit && { permit: managedPermit }) });
|
|
1983
|
+
if (receipt.authority) {
|
|
1984
|
+
progress.nodes.push({ node: 'local', outcome: 'stopped', operationId: receipt.operationId, observedAt: receipt.observedAt, authority: receipt.authority });
|
|
1985
|
+
persist();
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
else if (localPlan && localInventory) {
|
|
1989
|
+
await this.orchestrator.stopStackExecution({ operationId: progress.id, plan: localPlan });
|
|
1990
|
+
if (keepInfrastructure) {
|
|
1991
|
+
const retained = await this.infraManager.inspectStopContainers(projectName, stackName);
|
|
1992
|
+
if (!isDeepStrictEqual([...retained].sort((a, b) => a.id.localeCompare(b.id)), [...localInventory.containers].sort((a, b) => a.id.localeCompare(b.id)))) {
|
|
1993
|
+
throw new Error('Retained stack infrastructure changed; inspect before confirming this application stop');
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
else
|
|
1997
|
+
await this.infraManager.teardownStack(projectName, stackName, localInventory.containers);
|
|
1998
|
+
}
|
|
1999
|
+
if (prepared.length) {
|
|
2000
|
+
const failures = [];
|
|
2001
|
+
for (const [index, target] of prepared.entries()) {
|
|
2002
|
+
const item = progress.nodes[index];
|
|
2003
|
+
item.outcome = 'stopping';
|
|
2004
|
+
persist();
|
|
2005
|
+
try {
|
|
2006
|
+
const receipt = await this.slaveConnector.invokeOnSlave(target.host, target.port, 'OmnitronProject', 'executeNodeStackStop', [{ project: projectName, stack: stackName, operationId: target.plan.operationId,
|
|
2007
|
+
...(keepInfrastructure && { keepInfrastructure: true }),
|
|
2008
|
+
...(target.permit && { permit: target.permit }) }], { retryOnDisconnect: true });
|
|
2009
|
+
if (receipt?.protocol !== 1 || receipt.operationId !== target.plan.operationId || receipt.project !== projectName ||
|
|
2010
|
+
receipt.stack !== stackName || receipt.outcome !== 'stopped' ||
|
|
2011
|
+
(receipt.keepInfrastructure === true) !== keepInfrastructure ||
|
|
2012
|
+
(keepInfrastructure && (!Array.isArray(receipt.keptContainers) || receipt.removedContainers?.length !== 0 ||
|
|
2013
|
+
!isDeepStrictEqual([...receipt.keptContainers].sort(), target.plan.containers.map(c => c.id).sort()))) || (target.permit &&
|
|
2014
|
+
(receipt.authority?.epoch !== target.permit.epoch || receipt.authority.nodeId !== target.plan.audience.nodeId ||
|
|
2015
|
+
receipt.authority.clusterId !== target.plan.audience.clusterId)))
|
|
2016
|
+
throw new Error('Invalid node stop acknowledgement');
|
|
2017
|
+
item.outcome = 'stopped';
|
|
2018
|
+
item.operationId = receipt.operationId;
|
|
2019
|
+
item.observedAt = receipt.observedAt;
|
|
2020
|
+
}
|
|
2021
|
+
catch (error) {
|
|
2022
|
+
item.outcome = 'unknown';
|
|
2023
|
+
item.reason = describeFailure(error);
|
|
2024
|
+
failures.push(`${item.node}: ${item.reason}`);
|
|
2025
|
+
}
|
|
2026
|
+
persist();
|
|
2027
|
+
}
|
|
2028
|
+
if (failures.length)
|
|
2029
|
+
throw new Error(`Stack stop is unconfirmed on ${failures.join('; ')}`);
|
|
2030
|
+
}
|
|
2031
|
+
// Retained infrastructure is still owned here, including former placements.
|
|
2032
|
+
// A later full stop must retain the obligation to contact those nodes.
|
|
2033
|
+
if (!keepInfrastructure && targetsRevision !== undefined)
|
|
2034
|
+
new StackTargets(this.daemonStateStore)
|
|
2035
|
+
.confirmStopped({ project: projectName, stack: stackName }, targetsRevision);
|
|
2036
|
+
progress.phase = 'stopped';
|
|
2037
|
+
progress.completedAt = new Date().toISOString();
|
|
2038
|
+
persist();
|
|
879
2039
|
state.status = 'stopped';
|
|
880
2040
|
state.startedAt = null;
|
|
881
|
-
this.updateEnabledStacks(projectName, stackName, false);
|
|
882
2041
|
this.emit('stack:stopped', projectName, stackName, 'manual');
|
|
883
|
-
this.
|
|
884
|
-
return this.toStackInfo(projectName, stackName, state.config);
|
|
2042
|
+
return this.toStackInfo(projectName, stackName, stackConfig);
|
|
885
2043
|
}
|
|
886
|
-
catch (
|
|
887
|
-
|
|
888
|
-
|
|
2044
|
+
catch (error) {
|
|
2045
|
+
progress.phase = 'failed';
|
|
2046
|
+
progress.error = describeFailure(error);
|
|
2047
|
+
persist();
|
|
2048
|
+
if (progress.intentCommitted)
|
|
2049
|
+
state.status = 'error';
|
|
2050
|
+
this.emit('stack:error', projectName, stackName, String(error));
|
|
2051
|
+
throw error;
|
|
889
2052
|
}
|
|
890
2053
|
}
|
|
2054
|
+
rememberStackTargets(project, stack, definition, config) {
|
|
2055
|
+
const targets = resolveNodePlacements(definition, this.resolveStackApps(definition, config))
|
|
2056
|
+
.map(({ node, apps }) => ({ host: node.host, port: node.port ?? 9700, applications: apps.length > 0,
|
|
2057
|
+
...(node.nodeId && { nodeId: node.nodeId }) }));
|
|
2058
|
+
new StackTargets(this.daemonStateStore).remember({ project, stack }, targets);
|
|
2059
|
+
}
|
|
891
2060
|
/**
|
|
892
2061
|
* What is running in this stack, asked of whoever is running it.
|
|
893
2062
|
*
|
|
894
2063
|
* For a remote stack that is the node. This counted `this.orchestrator
|
|
895
2064
|
* .list()` — the MASTER's process table — which lists none of a remote
|
|
896
|
-
* stack's applications, so `stack runtime
|
|
2065
|
+
* stack's applications, so `stack runtime sample test` answered
|
|
897
2066
|
* `totalApps: 0, onlineApps: 0, connectedNodes: 0` while `stack status`
|
|
898
2067
|
* listed six apps online from the same node, three times in three seconds
|
|
899
2068
|
* (measured 2026-09-22). The JSON surface is the one a script and a
|
|
@@ -913,60 +2082,23 @@ export class ProjectService extends EventEmitter {
|
|
|
913
2082
|
const stackConfig = stacks[stackName] ?? state?.config;
|
|
914
2083
|
if (!stackConfig)
|
|
915
2084
|
throw new Error(`Stack '${stackName}' not found`);
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const configForStatus = this.getLoadedConfig(projectName);
|
|
922
|
-
const configuredNames = configForStatus
|
|
923
|
-
? new Set(this.resolveStackApps(stackConfig, configForStatus).map((a) => a.name))
|
|
924
|
-
: new Set();
|
|
925
|
-
const apps = this.orchestrator.list().filter((a) => {
|
|
926
|
-
if (a.name.startsWith(prefix))
|
|
927
|
-
return true;
|
|
928
|
-
if (a.name.includes('/'))
|
|
929
|
-
return false;
|
|
930
|
-
return configuredNames.has(effectiveAppName(a.name));
|
|
931
|
-
});
|
|
932
|
-
const onlineApps = apps.filter((a) => a.status === 'online');
|
|
933
|
-
// Derive status from app health if running
|
|
934
|
-
let status = state?.status ?? 'stopped';
|
|
935
|
-
if (status === 'running' && apps.length > 0 && onlineApps.length < apps.length) {
|
|
936
|
-
status = 'degraded';
|
|
937
|
-
}
|
|
938
|
-
const base = {
|
|
2085
|
+
const observed = stackConfig.type === 'local'
|
|
2086
|
+
? { info: await this.withRemoteAppStatuses(projectName, this.toStackInfo(projectName, stackName, stackConfig)), answered: 1 }
|
|
2087
|
+
: await this.askNodes(projectName, this.getStack(projectName, stackName));
|
|
2088
|
+
const { info, answered } = observed;
|
|
2089
|
+
return {
|
|
939
2090
|
name: stackName,
|
|
940
2091
|
type: stackConfig.type,
|
|
941
|
-
status,
|
|
942
|
-
totalApps: apps.length,
|
|
943
|
-
onlineApps: onlineApps.length,
|
|
944
|
-
totalNodes: stackConfig.nodes?.length ?? 1,
|
|
945
|
-
connectedNodes: stackConfig.type === 'local' ? 1 : 0,
|
|
946
|
-
totalCpu: apps.reduce((sum, a) => sum + a.cpu, 0),
|
|
947
|
-
totalMemory: apps.reduce((sum, a) => sum + a.memory, 0),
|
|
948
|
-
syncSummary: (stackConfig.type === 'remote' || stackConfig.type === 'cluster')
|
|
949
|
-
? summariseSync(this.nodesWithSync(stackConfig))
|
|
950
|
-
: null,
|
|
951
|
-
};
|
|
952
|
-
if (stackConfig.type === 'local')
|
|
953
|
-
return base;
|
|
954
|
-
// The node is asked, and what it answers replaces every count that was
|
|
955
|
-
// taken from this machine. `connectedNodes` is the number of nodes that
|
|
956
|
-
// ANSWERED rather than the number of entries in the connector's
|
|
957
|
-
// registry: that registry reported 0 connections for a node that was
|
|
958
|
-
// answering RPCs in the same second.
|
|
959
|
-
const { info, answered } = await this.askNodes(projectName, this.getStack(projectName, stackName));
|
|
960
|
-
const remote = info.apps;
|
|
961
|
-
const up = remote.filter((a) => a.status === 'online');
|
|
962
|
-
return {
|
|
963
|
-
...base,
|
|
964
2092
|
status: info.status,
|
|
965
|
-
|
|
966
|
-
|
|
2093
|
+
...(info.health && { health: info.health }),
|
|
2094
|
+
...(info.notUp?.length && { notUp: info.notUp }),
|
|
2095
|
+
totalApps: info.apps.length,
|
|
2096
|
+
onlineApps: info.apps.filter(app => app.status === 'online').length,
|
|
2097
|
+
totalNodes: stackConfig.type === 'local' ? 1 : stackConfig.nodes?.length ?? 0,
|
|
967
2098
|
connectedNodes: answered,
|
|
968
|
-
totalCpu:
|
|
969
|
-
totalMemory:
|
|
2099
|
+
totalCpu: info.apps.reduce((sum, app) => sum + (app.cpu ?? 0), 0),
|
|
2100
|
+
totalMemory: info.apps.reduce((sum, app) => sum + (app.memory ?? 0), 0),
|
|
2101
|
+
syncSummary: stackConfig.type === 'local' ? null : summariseSync(this.nodesWithSync(stackConfig)),
|
|
970
2102
|
};
|
|
971
2103
|
}
|
|
972
2104
|
/**
|
|
@@ -990,6 +2122,66 @@ export class ProjectService extends EventEmitter {
|
|
|
990
2122
|
const { releasesRoot } = await import('../release/load.js');
|
|
991
2123
|
return releasesRoot();
|
|
992
2124
|
}
|
|
2125
|
+
/** Explicit disk inspection: never performed by polling and never alters launch evidence. */
|
|
2126
|
+
async inspectInstalledArtifacts(project, stack, catalogProject) {
|
|
2127
|
+
const { inspectInstalledArtifacts } = await import('../release/installed-artifacts.js');
|
|
2128
|
+
const { installedReleaseCatalog, installedReleaseReport, boundedInstalledNodeRead, installedNodeFailure, installedReadDeadline } = await import('../release/installed-release.js');
|
|
2129
|
+
await this.ensureConfig(project);
|
|
2130
|
+
const configured = this.getStack(project, stack);
|
|
2131
|
+
const catalog = await installedReleaseCatalog(await this.releaseStore(), catalogProject);
|
|
2132
|
+
let info = configured;
|
|
2133
|
+
if (configured.type !== 'local') {
|
|
2134
|
+
this.getSlaveConnector();
|
|
2135
|
+
try {
|
|
2136
|
+
info = await installedReadDeadline(() => this.withRemoteAppStatuses(project, configured), 10_000);
|
|
2137
|
+
}
|
|
2138
|
+
catch {
|
|
2139
|
+
return installedReleaseReport(catalogProject, stack, catalog, [{ node: stack, error: 'Stack node inventory is unavailable or timed out; installed files were not inspected' }]);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
const nodes = [];
|
|
2143
|
+
if (info.type === 'local') {
|
|
2144
|
+
const names = info.apps.map(app => app.handleKey);
|
|
2145
|
+
try {
|
|
2146
|
+
nodes.push({ node: 'local', observations: names.length ? await inspectInstalledArtifacts(this.orchestrator, names) : [] });
|
|
2147
|
+
}
|
|
2148
|
+
catch {
|
|
2149
|
+
nodes.push({ node: 'local', error: 'Installed-file inspection failed or the process owner is unavailable' });
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
else {
|
|
2153
|
+
const placements = info.placements ?? [];
|
|
2154
|
+
if (placements.length > 16)
|
|
2155
|
+
throw new Error('Installed-file inspection supports up to 16 nodes per stack');
|
|
2156
|
+
const deadline = Date.now() + 60_000;
|
|
2157
|
+
for (let offset = 0; offset < placements.length; offset += 4) {
|
|
2158
|
+
await Promise.all(placements.slice(offset, offset + 4).map(async (placement) => {
|
|
2159
|
+
const node = placement.label ?? placement.nodeKey;
|
|
2160
|
+
if (deadline - Date.now() < 55_000) {
|
|
2161
|
+
nodes.push({ node, error: 'Stack inspection time budget exhausted before this node; inspect again' });
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const names = placement.apps.flatMap(app => app.process ? [app.process.name] : []);
|
|
2165
|
+
if (!placement.appsObserved) {
|
|
2166
|
+
nodes.push({ node, error: 'Node inventory is unavailable' });
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
if (!names.length) {
|
|
2170
|
+
nodes.push({ node, error: 'No installed application handles were reported by this node' });
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
try {
|
|
2174
|
+
const observations = await boundedInstalledNodeRead(() => this.getSlaveConnector().invokeOnSlave(placement.host, placement.port, 'OmnitronDaemon', 'inspectInstalledArtifacts', [{ names }]));
|
|
2175
|
+
nodes.push({ node, observations });
|
|
2176
|
+
}
|
|
2177
|
+
catch (error) {
|
|
2178
|
+
nodes.push({ node, error: installedNodeFailure(error) });
|
|
2179
|
+
}
|
|
2180
|
+
}));
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return installedReleaseReport(catalogProject, stack, catalog, nodes);
|
|
2184
|
+
}
|
|
993
2185
|
/**
|
|
994
2186
|
* Take a release for a stack, or say why not.
|
|
995
2187
|
*
|
|
@@ -1014,25 +2206,51 @@ export class ProjectService extends EventEmitter {
|
|
|
1014
2206
|
* thing worse than a refusal at deploy time is a console that promised the
|
|
1015
2207
|
* opposite.
|
|
1016
2208
|
*/
|
|
1017
|
-
async checkRelease(projectName, stackName, releaseId) {
|
|
2209
|
+
async checkRelease(projectName, stackName, releaseId, prepare = false) {
|
|
2210
|
+
let preparedArtifactVolumes;
|
|
1018
2211
|
try {
|
|
1019
2212
|
const config = await this.loadProjectConfig(projectName);
|
|
1020
2213
|
const stacks = this.resolveStacks(config, projectName);
|
|
1021
2214
|
const stackConfig = stacks[stackName];
|
|
1022
2215
|
if (!stackConfig)
|
|
1023
2216
|
throw new Error(`Stack '${stackName}' not found in project '${projectName}'`);
|
|
2217
|
+
this.assertStartAuthority({ project: projectName, stack: stackName }, stackConfig);
|
|
2218
|
+
if (prepare && stackConfig.type !== 'local')
|
|
2219
|
+
throw new Error('Preparing a local release requires a local stack');
|
|
1024
2220
|
const release = await this.admitRelease(projectName, stackName, stackConfig, config, releaseId, 'check');
|
|
2221
|
+
let retiring = [];
|
|
2222
|
+
if (stackConfig.type === 'local') {
|
|
2223
|
+
const inspected = artifactVolumeBindings(mergeInfrastructure(config, stackConfig)).length
|
|
2224
|
+
? await this.installedRelease(projectName, stackName, stackConfig, config, release, prepare) : undefined;
|
|
2225
|
+
const entries = inspected?.entries ?? await this.installedEntries(projectName, stackName, stackConfig, config, release, prepare);
|
|
2226
|
+
if (prepare && inspected)
|
|
2227
|
+
preparedArtifactVolumes = inspected.preparedArtifactVolumes;
|
|
2228
|
+
const prior = this.localSelection(projectName, stackName).read().release;
|
|
2229
|
+
if (prior?.id === release.id && prior.manifestSha256 !== release.manifestSha256)
|
|
2230
|
+
throw new Error('Selected manifest changed on disk');
|
|
2231
|
+
const plan = await this.checkLocalOwner(projectName, stackName, entries, prior?.id !== release.id);
|
|
2232
|
+
if (artifactVolumeBindings(mergeInfrastructure(config, stackConfig)).length && prior?.id !== release.id && plan.phase !== 'stopped')
|
|
2233
|
+
throw new LocalReleaseStopRequired('Stop this local stack before changing its immutable artifact volumes');
|
|
2234
|
+
const target = new Set(entries.map(entry => ProjectService.handleKey(projectName, stackName, entry.name)));
|
|
2235
|
+
retiring = plan.apps.filter(app => !target.has(app.name)).map(app => app.name);
|
|
2236
|
+
}
|
|
1025
2237
|
const gates = release.manifest.gates;
|
|
1026
2238
|
const passed = gates.filter((g) => g.status === 'passed').length;
|
|
1027
2239
|
return {
|
|
1028
2240
|
ok: true,
|
|
2241
|
+
...(preparedArtifactVolumes?.length && { preparedArtifactVolumes }),
|
|
1029
2242
|
because: `${projectName}/${stackName} would take ${release.id}: ${passed} of ${gates.length} gates passed, ` +
|
|
1030
2243
|
`${release.files.length} artifact(s) match the manifest` +
|
|
1031
|
-
`${release.staticsDir ? `, static bundle built for ${release.manifest.statics?.stack}` : ''}
|
|
2244
|
+
`${release.staticsDir ? `, static bundle built for ${release.manifest.statics?.stack}` : ''}.` +
|
|
2245
|
+
(retiring.length ? ` After a confirmed whole-stack stop, reconciliation will retire these application registrations: ${retiring.join(', ')}. Logs, artifacts and infrastructure are preserved.` : ''),
|
|
1032
2246
|
};
|
|
1033
2247
|
}
|
|
1034
2248
|
catch (err) {
|
|
1035
|
-
|
|
2249
|
+
const { LocalReleaseNotPreparedError } = await import('../release/local-installation.js');
|
|
2250
|
+
return { ok: false, because: err.message,
|
|
2251
|
+
...(err instanceof LocalReleaseNotPreparedError && { state: 'unprepared' }),
|
|
2252
|
+
...(err instanceof LocalReleaseStopRequired && { state: 'requires-stop',
|
|
2253
|
+
...(preparedArtifactVolumes?.length && { preparedArtifactVolumes }) }) };
|
|
1036
2254
|
}
|
|
1037
2255
|
}
|
|
1038
2256
|
/**
|
|
@@ -1061,7 +2279,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1061
2279
|
throw new Error(`${projectName}/${stackName} is local: its probes run on this machine with \`node scripts/attest.mjs --stack=${stackName}\`, and need no transport`);
|
|
1062
2280
|
}
|
|
1063
2281
|
// The probes write to the system they measure — accounts, organisations,
|
|
1064
|
-
//
|
|
2282
|
+
// payments accounts with their deposit addresses (on sample/test, 2026-09-24:
|
|
1065
2283
|
// 91 probe organisations in the public catalogue, 10 mainnet addresses on
|
|
1066
2284
|
// accounts nobody owns). So they run only where a stack said they may,
|
|
1067
2285
|
// and never on one that takes other stacks' attestations: a stack that
|
|
@@ -1122,7 +2340,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1122
2340
|
// Through the data channel: the producer's line is evidence to
|
|
1123
2341
|
// store, and a probe's output that said `password=…` was cut by
|
|
1124
2342
|
// the transport's masker through its closing quote.
|
|
1125
|
-
const run = await this.deployer.readFromNode(target, attestationCommand({ remoteDir, stack: stackName, releaseId, containerPrefix, provision: accounts === 'provisioned' }));
|
|
2343
|
+
const run = await this.deployer.readFromNode(target, attestationCommand({ projectName, remoteDir, stack: stackName, releaseId, containerPrefix, provision: accounts === 'provisioned' }));
|
|
1126
2344
|
return { ...run, node: machine, scriptsFrom: staged.scriptsFrom, sourceFiles: staged.sourceFiles, accounts };
|
|
1127
2345
|
}
|
|
1128
2346
|
finally {
|
|
@@ -1201,7 +2419,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1201
2419
|
const key = await tool.sealingKey();
|
|
1202
2420
|
return this.withOperatorTool(projectName, stackName, `operator account ${username}`, async (on) => {
|
|
1203
2421
|
const facts = { project: projectName, stack: stackName, node: on.machine, username };
|
|
1204
|
-
const run = await on.run((remoteDir, containerPrefix) => tool.operatorAccountCommand({
|
|
2422
|
+
const run = await on.run((remoteDir, containerPrefix) => tool.operatorAccountCommand({ projectName,
|
|
1205
2423
|
remoteDir,
|
|
1206
2424
|
containerPrefix,
|
|
1207
2425
|
username,
|
|
@@ -1216,7 +2434,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1216
2434
|
// what is there and how to take it away.
|
|
1217
2435
|
let holds = '';
|
|
1218
2436
|
if (outcome.uncertain) {
|
|
1219
|
-
const shown = tool.readShowRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountShowCommand({ remoteDir, containerPrefix, username }), 60_000));
|
|
2437
|
+
const shown = tool.readShowRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountShowCommand({ projectName, remoteDir, containerPrefix, username }), 60_000));
|
|
1220
2438
|
holds = !shown.ok
|
|
1221
2439
|
? ` What the stand holds could not be read either: ${shown.because}`
|
|
1222
2440
|
: shown.account
|
|
@@ -1233,7 +2451,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1233
2451
|
}
|
|
1234
2452
|
catch (err) {
|
|
1235
2453
|
const why = err instanceof Error ? err.message : String(err);
|
|
1236
|
-
const undo = tool.readRemoveRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountRemoveCommand({ remoteDir, containerPrefix, username, id: account.id }), 120_000), username, account.id);
|
|
2454
|
+
const undo = tool.readRemoveRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountRemoveCommand({ projectName, remoteDir, containerPrefix, username, id: account.id }), 120_000), username, account.id);
|
|
1237
2455
|
this.logger.error({ ...facts, id: account.id, vaultKey, undone: undo.removed, error: why }, 'The vault refused an operator account\'s password');
|
|
1238
2456
|
throw new Error(undo.removed
|
|
1239
2457
|
? `${on.where}: the vault refused the password of ${username} (${why}) — the account was taken away again; nothing is left on the stand`
|
|
@@ -1307,7 +2525,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1307
2525
|
? 'rehearsal of removing what the probes left'
|
|
1308
2526
|
: 'removal of what the probes left';
|
|
1309
2527
|
return this.withOperatorTool(projectName, stackName, what, async (on) => {
|
|
1310
|
-
const read = tool.readLeftoversRun(await on.run((remoteDir, containerPrefix) => tool.probeLeftoversCommand({ remoteDir, containerPrefix, mode, also: request.also }),
|
|
2528
|
+
const read = tool.readLeftoversRun(await on.run((remoteDir, containerPrefix) => tool.probeLeftoversCommand({ projectName, remoteDir, containerPrefix, mode, also: request.also }),
|
|
1311
2529
|
// A rehearsal or a removal runs every database's removal and its
|
|
1312
2530
|
// invariants — longer than counting.
|
|
1313
2531
|
mode === 'census' ? 300_000 : 900_000), mode);
|
|
@@ -1329,7 +2547,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1329
2547
|
// Three minutes, not one: the census checks privileged accounts against
|
|
1330
2548
|
// every password the repository publishes with bcrypt at the stand's
|
|
1331
2549
|
// cost — 18 s for 17 accounts on the dev stand.
|
|
1332
|
-
const read = tool.readCensusRun(await on.run((remoteDir, containerPrefix) => tool.operatorCensusCommand({ remoteDir, containerPrefix }), 180_000));
|
|
2550
|
+
const read = tool.readCensusRun(await on.run((remoteDir, containerPrefix) => tool.operatorCensusCommand({ projectName, remoteDir, containerPrefix }), 180_000));
|
|
1333
2551
|
if (!read.ok)
|
|
1334
2552
|
throw new Error(`${on.where}: could not take the census: ${read.because}`);
|
|
1335
2553
|
return { node: on.machine, commit: on.commit, census: read.census };
|
|
@@ -1342,7 +2560,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1342
2560
|
throw new Error('Which account? (--show <name>)');
|
|
1343
2561
|
const tool = await import('../project/operator-account.js');
|
|
1344
2562
|
return this.withOperatorTool(projectName, stackName, `reading operator account ${name}`, async (on) => {
|
|
1345
|
-
const shown = tool.readShowRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountShowCommand({ remoteDir, containerPrefix, username: name }), 60_000));
|
|
2563
|
+
const shown = tool.readShowRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountShowCommand({ projectName, remoteDir, containerPrefix, username: name }), 60_000));
|
|
1346
2564
|
if (!shown.ok)
|
|
1347
2565
|
throw new Error(`${on.where}: could not read ${name}: ${shown.because}`);
|
|
1348
2566
|
return { node: on.machine, commit: on.commit, account: shown.account };
|
|
@@ -1362,7 +2580,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1362
2580
|
const tool = await import('../project/operator-account.js');
|
|
1363
2581
|
const vaultKey = input.vaultKey?.trim() || tool.accountVaultKey(projectName, stackName, username);
|
|
1364
2582
|
const removed = await this.withOperatorTool(projectName, stackName, `removing operator account ${username}`, async (on) => {
|
|
1365
|
-
const outcome = tool.readRemoveRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountRemoveCommand({ remoteDir, containerPrefix, username, id }), 120_000), username, id);
|
|
2583
|
+
const outcome = tool.readRemoveRun(await on.run((remoteDir, containerPrefix) => tool.operatorAccountRemoveCommand({ projectName, remoteDir, containerPrefix, username, id }), 120_000), username, id);
|
|
1366
2584
|
if (!outcome.removed)
|
|
1367
2585
|
throw new Error(`${on.where}: ${username} (id ${id}) was not removed: ${outcome.because}`);
|
|
1368
2586
|
return { node: on.machine, commit: on.commit };
|
|
@@ -1474,20 +2692,25 @@ export class ProjectService extends EventEmitter {
|
|
|
1474
2692
|
* with nothing, so an uncommitted edit to the gateway's template reached the
|
|
1475
2693
|
* test node past every gate a release has.
|
|
1476
2694
|
*/
|
|
1477
|
-
async deploymentReads(projectName, stackConfig, config, root) {
|
|
2695
|
+
async deploymentReads(projectName, stackConfig, config, root, definitions) {
|
|
1478
2696
|
const { definitionInputs } = await import('../release/definition-inputs.js');
|
|
1479
2697
|
const { shippedDirsOf } = await import('../infrastructure/shipped-config.js');
|
|
1480
|
-
const shipped = shippedDirsOf(mergeInfrastructure(config, stackConfig), await this.collectDeclaredServices(projectName, stackConfig, config));
|
|
1481
|
-
return definitionInputs(root, this.resolveStackApps(stackConfig, config).flatMap((a) => (a.bootstrap ? [a.bootstrap] : [])), shipped.map(([, dir]) => dir));
|
|
2698
|
+
const shipped = shippedDirsOf(mergeInfrastructure(config, stackConfig), await this.collectDeclaredServices(projectName, stackConfig, config, { definitions }));
|
|
2699
|
+
return definitionInputs(root, this.resolveStackApps(stackConfig, config).flatMap((a) => (a.bootstrap ? [resolveAppPath(root, a, a.bootstrap)] : [])), shipped.map(([, dir]) => dir));
|
|
1482
2700
|
}
|
|
1483
2701
|
async admitRelease(projectName, stackName, stackConfig, config, releaseId, purpose = 'deploy') {
|
|
1484
|
-
if (stackConfig.type === 'local')
|
|
1485
|
-
throw new Error(
|
|
1486
|
-
}
|
|
2702
|
+
if (stackConfig.type === 'local' && stackConfig.managedOrigin)
|
|
2703
|
+
throw new Error('A node allocation takes its release from its owning controller');
|
|
1487
2704
|
const { loadRelease, treeEqualsCommit } = await import('../release/load.js');
|
|
1488
2705
|
const { decideStackRelease } = await import('../release/manifest.js');
|
|
1489
2706
|
const release = await loadRelease(releaseId, await this.releaseStore());
|
|
1490
|
-
const
|
|
2707
|
+
const configuredApps = this.resolveStackApps(stackConfig, config);
|
|
2708
|
+
if (stackConfig.type !== 'local' && configuredApps.some(entry => entry.releaseFrom || release.manifest.artifacts.some(artifact => artifact.app === entry.name && artifact.origin)))
|
|
2709
|
+
throw Error('Imported applications require verified local release installation. Remote admission needs producer-captured deployment declarations and a compatible execution owner.');
|
|
2710
|
+
const mounted = artifactVolumeEntries(mergeInfrastructure(config, stackConfig), config.apps);
|
|
2711
|
+
if (mounted.length && (stackConfig.type !== 'local' || stackConfig.managedOrigin || !ownsStackInfrastructure(stackConfig)))
|
|
2712
|
+
throw Error('Artifact volumes require an independently owned local stack');
|
|
2713
|
+
const apps = [...new Set([...configuredApps, ...mounted].map(a => a.name))];
|
|
1491
2714
|
// The fourth argument, which was `[]` for as long as nothing wrote one:
|
|
1492
2715
|
// a stack declaring `verifiedOn` refused every release forever, with a
|
|
1493
2716
|
// refusal that read «nothing has been verified on 'test'» while there
|
|
@@ -1507,13 +2730,23 @@ export class ProjectService extends EventEmitter {
|
|
|
1507
2730
|
this.logger.info({ project: projectName, stack: stackName }, 'No release is recorded as running on this stack — applied migrations are not compared');
|
|
1508
2731
|
}
|
|
1509
2732
|
else if (running !== release.id) {
|
|
1510
|
-
let
|
|
2733
|
+
let previous;
|
|
1511
2734
|
try {
|
|
1512
|
-
|
|
2735
|
+
previous = (await loadRelease(running, await this.releaseStore())).manifest;
|
|
1513
2736
|
}
|
|
1514
2737
|
catch (err) {
|
|
1515
2738
|
this.logger.warn({ project: projectName, stack: stackName, running, error: err.message }, 'The release running on this stack could not be read — its migrations are not compared');
|
|
1516
2739
|
}
|
|
2740
|
+
if (previous) {
|
|
2741
|
+
const { changedImportedMigrations } = await import('../release/migrations.js');
|
|
2742
|
+
const imported = changedImportedMigrations(previous, release.manifest, apps);
|
|
2743
|
+
if (imported.length)
|
|
2744
|
+
throw Error('Refusing release ' + release.id + ': imported migration history changed (' + imported.slice(0, 5).join(', ') + '). Preserve applied migrations in the producer package.');
|
|
2745
|
+
}
|
|
2746
|
+
else if (release.manifest.artifacts.some(artifact => apps.includes(artifact.app) && artifact.origin)) {
|
|
2747
|
+
throw Error('Cannot verify imported migration history because the previously running release is unavailable: ' + running);
|
|
2748
|
+
}
|
|
2749
|
+
const ran = previous?.migrations;
|
|
1517
2750
|
if (ran && release.manifest.migrations) {
|
|
1518
2751
|
const { changedReleasedMigrations } = await import('../release/migrations.js');
|
|
1519
2752
|
const changed = changedReleasedMigrations(ran, release.manifest.migrations);
|
|
@@ -1531,17 +2764,48 @@ export class ProjectService extends EventEmitter {
|
|
|
1531
2764
|
if (!project)
|
|
1532
2765
|
throw new Error(`Project '${projectName}' is not in the registry`);
|
|
1533
2766
|
const commit = release.manifest.project.commit;
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
2767
|
+
if (stackConfig.type === 'local') {
|
|
2768
|
+
if (!/^[a-f0-9]{40}$/.test(commit))
|
|
2769
|
+
throw new Error('A local release requires a full source commit');
|
|
2770
|
+
const { promisify } = await import('node:util');
|
|
2771
|
+
const { execFile } = await import('node:child_process');
|
|
2772
|
+
const { stdout } = await promisify(execFile)('git', ['rev-parse', '--show-prefix'], { cwd: project.path });
|
|
2773
|
+
const directory = stdout.trimEnd().replace(/\/$/, '');
|
|
2774
|
+
if (directory !== (release.manifest.project.directory ?? ''))
|
|
2775
|
+
throw new Error('This local release belongs to another project directory in the repository');
|
|
2776
|
+
}
|
|
2777
|
+
// Only what the definition reads has to be the release's. A failed input
|
|
2778
|
+
// inventory cannot be replaced by a narrower or unverified comparison.
|
|
1538
2779
|
const root = fs.realpathSync(project.path);
|
|
2780
|
+
// Local admission materializes the verified package itself. On remote deployments
|
|
2781
|
+
// the old single-project bootstrap still supplies its code declarations; its data
|
|
2782
|
+
// file must come from the admitted archive, including when that file is absent.
|
|
2783
|
+
let remoteDefinitions;
|
|
2784
|
+
if (stackConfig.type !== 'local') {
|
|
2785
|
+
const entries = this.resolveStackApps(stackConfig, config);
|
|
2786
|
+
const archived = new Map();
|
|
2787
|
+
for (const entry of entries) {
|
|
2788
|
+
if (entry.bootstrap)
|
|
2789
|
+
archived.set(entry.name, await readArchiveInfrastructure(release, entry.name));
|
|
2790
|
+
}
|
|
2791
|
+
remoteDefinitions = await this.loadAppDefinitions(projectName, stackConfig, config, entries, archived);
|
|
2792
|
+
}
|
|
1539
2793
|
let inputs;
|
|
1540
2794
|
try {
|
|
1541
|
-
|
|
2795
|
+
// Local launches read executable app declarations from the verified installation. Only
|
|
2796
|
+
// project-level configuration and explicitly mounted infrastructure still come from Git.
|
|
2797
|
+
if (stackConfig.type === 'local') {
|
|
2798
|
+
const { definitionInputs } = await import('../release/definition-inputs.js');
|
|
2799
|
+
const { shippedDirsOf } = await import('../infrastructure/shipped-config.js');
|
|
2800
|
+
inputs = await definitionInputs(root, [], shippedDirsOf(mergeInfrastructure(config, stackConfig), {}).map(([, dir]) => dir));
|
|
2801
|
+
if (!inputs.length)
|
|
2802
|
+
throw new Error('No registered configuration inputs are available');
|
|
2803
|
+
}
|
|
2804
|
+
else
|
|
2805
|
+
inputs = await this.deploymentReads(projectName, stackConfig, config, root, remoteDefinitions);
|
|
1542
2806
|
}
|
|
1543
2807
|
catch (err) {
|
|
1544
|
-
|
|
2808
|
+
throw new Error(`Cannot verify deployment inputs for ${projectName}/${stackName}: ${err.message}`, { cause: err });
|
|
1545
2809
|
}
|
|
1546
2810
|
const tree = await treeEqualsCommit(root, commit, inputs);
|
|
1547
2811
|
if (!tree.equal) {
|
|
@@ -1552,15 +2816,17 @@ export class ProjectService extends EventEmitter {
|
|
|
1552
2816
|
`The definition is read from ${project.path}, so those have to be the release's: commit or set them aside, ` +
|
|
1553
2817
|
`check out ${commit.slice(0, 8)}, or build a release from what is here.`);
|
|
1554
2818
|
}
|
|
1555
|
-
//
|
|
1556
|
-
//
|
|
1557
|
-
const {
|
|
1558
|
-
const
|
|
1559
|
-
if (
|
|
1560
|
-
throw new Error(`Refusing release ${release.id} for ${projectName}/${stackName}: ${
|
|
2819
|
+
// Admission and installation use the same native resident/runtime proof.
|
|
2820
|
+
// A frozen application supplies its own SDK; a controller build is not its runtime.
|
|
2821
|
+
const { refuseReleaseOwners } = await import('../release/owner-admission.js');
|
|
2822
|
+
const incompatible = stackConfig.type === 'local' ? null : refuseReleaseOwners(release.manifest, stackConfig, configuredApps, await this.askReleaseOwners(stackConfig, configuredApps));
|
|
2823
|
+
if (incompatible) {
|
|
2824
|
+
throw new Error(`Refusing release ${release.id} for ${projectName}/${stackName}: ${incompatible}.`);
|
|
1561
2825
|
}
|
|
1562
2826
|
const infra = mergeInfrastructure(config, stackConfig);
|
|
1563
2827
|
const servesStatics = Boolean(infra?.services?.['gateway']?.config?.staticDir ?? infra?.gateway?.staticDir);
|
|
2828
|
+
if (servesStatics && stackConfig.type === 'local')
|
|
2829
|
+
throw new Error('This local gateway serves a working-tree static directory; an immutable local gateway binding is required');
|
|
1564
2830
|
if (servesStatics && (!release.staticsDir || release.manifest.statics?.stack !== stackName)) {
|
|
1565
2831
|
throw new Error(`Refusing release ${release.id} for ${projectName}/${stackName}: the stack's gateway serves a static bundle, and the ` +
|
|
1566
2832
|
`release carries ${release.manifest.statics ? `one built for '${release.manifest.statics.stack}'` : 'none'} — ` +
|
|
@@ -1574,18 +2840,19 @@ export class ProjectService extends EventEmitter {
|
|
|
1574
2840
|
projectCommit: commit.slice(0, 8),
|
|
1575
2841
|
omniCommit: release.manifest.omni.commit.slice(0, 8),
|
|
1576
2842
|
}, purpose === 'check' ? 'Release would be admitted — asked, not deployed' : 'Release admitted');
|
|
1577
|
-
return release;
|
|
2843
|
+
return remoteDefinitions ? { ...release, remoteDefinitions } : release;
|
|
1578
2844
|
}
|
|
1579
|
-
/**
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
return Promise.all((stackConfig.
|
|
2845
|
+
/** Inspect exactly the nodes that will execute applications. Delivery repeats
|
|
2846
|
+
* this OS/private-Netron proof; version suffixes never substitute for it. */
|
|
2847
|
+
async askReleaseOwners(stackConfig, apps) {
|
|
2848
|
+
return Promise.all(resolveNodePlacements(stackConfig, apps).filter(placement => placement.apps.length).map(async ({ node }) => {
|
|
1583
2849
|
try {
|
|
1584
|
-
const
|
|
1585
|
-
return { host: node.host,
|
|
2850
|
+
const bundle = await this.deployer.inspectInstalledBundle(await this.targetForStackNode(node));
|
|
2851
|
+
return { host: node.host, port: node.port ?? 9700,
|
|
2852
|
+
...(bundle.applicationRuntime && { runtime: bundle.applicationRuntime }) };
|
|
1586
2853
|
}
|
|
1587
2854
|
catch (err) {
|
|
1588
|
-
return { host: node.host,
|
|
2855
|
+
return { host: node.host, port: node.port ?? 9700, error: describeFailure(err) };
|
|
1589
2856
|
}
|
|
1590
2857
|
}));
|
|
1591
2858
|
}
|
|
@@ -1646,6 +2913,38 @@ export class ProjectService extends EventEmitter {
|
|
|
1646
2913
|
getInfraManager() {
|
|
1647
2914
|
return this.infraManager;
|
|
1648
2915
|
}
|
|
2916
|
+
/** Cached declarations plus persisted intent; this query never starts infrastructure. */
|
|
2917
|
+
getInfrastructureHealthContext() {
|
|
2918
|
+
const context = { stacks: [], requiredContainers: [], unresolvedStacks: [],
|
|
2919
|
+
observedServices: this.infraManager.listInstances().flatMap(({ infra }) => Object.values(infra.getState().services)) };
|
|
2920
|
+
const apps = this.orchestrator.list();
|
|
2921
|
+
for (const project of this.registry.listRegistrations()) {
|
|
2922
|
+
const config = this.getLoadedConfig(project.name);
|
|
2923
|
+
if (!config)
|
|
2924
|
+
continue; // Unknown ownership must remain visible to the indicator.
|
|
2925
|
+
const running = this.getRunningStacks(project.name);
|
|
2926
|
+
for (const [stack, declaration] of Object.entries(this.resolveStacks(config, project.name))) {
|
|
2927
|
+
if (declaration.type !== 'local')
|
|
2928
|
+
continue;
|
|
2929
|
+
const prefix = `${project.name}/${stack}/`;
|
|
2930
|
+
const active = project.enabledStacks?.includes(stack) || running.includes(stack) ||
|
|
2931
|
+
apps.some(app => app.name.startsWith(prefix) && ['online', 'starting', 'restarting'].includes(app.status));
|
|
2932
|
+
const intent = active ? 'running' : project.enabledStacks ? 'inactive' : 'unknown';
|
|
2933
|
+
const scope = { project: project.name, stack, intent };
|
|
2934
|
+
context.stacks.push(scope);
|
|
2935
|
+
if (intent !== 'running')
|
|
2936
|
+
continue;
|
|
2937
|
+
const infra = this.infraManager.getInstance(project.name, stack);
|
|
2938
|
+
if (infra) {
|
|
2939
|
+
scope.containers = infra.getDesiredServices().map(service => service.name);
|
|
2940
|
+
context.requiredContainers.push(...scope.containers);
|
|
2941
|
+
}
|
|
2942
|
+
else if (mergeInfrastructure(config, declaration))
|
|
2943
|
+
context.unresolvedStacks.push(`${project.name}/${stack}`);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return context;
|
|
2947
|
+
}
|
|
1649
2948
|
// ===========================================================================
|
|
1650
2949
|
// Enabled-stacks reconciler — desired-state convergence
|
|
1651
2950
|
// ===========================================================================
|
|
@@ -1667,9 +2966,12 @@ export class ProjectService extends EventEmitter {
|
|
|
1667
2966
|
if (this.resumeTimer)
|
|
1668
2967
|
return;
|
|
1669
2968
|
this.resumeTimer = setInterval(() => {
|
|
2969
|
+
if (this.ownershipFrozen || this.resumingInstalled || this.reconcilingStacks)
|
|
2970
|
+
return;
|
|
2971
|
+
this.reconcilingStacks = true;
|
|
1670
2972
|
void this.reconcileEnabledStacks().catch((err) => {
|
|
1671
2973
|
this.logger.warn({ error: err.message }, 'Enabled-stacks reconciler tick failed');
|
|
1672
|
-
});
|
|
2974
|
+
}).finally(() => { this.reconcilingStacks = false; });
|
|
1673
2975
|
}, intervalMs);
|
|
1674
2976
|
this.resumeTimer.unref();
|
|
1675
2977
|
this.logger.info({ intervalMs }, 'Enabled-stacks reconciler started');
|
|
@@ -1677,10 +2979,13 @@ export class ProjectService extends EventEmitter {
|
|
|
1677
2979
|
async reconcileEnabledStacks() {
|
|
1678
2980
|
// Collect the divergence set first — cheap, no I/O.
|
|
1679
2981
|
const pending = [];
|
|
1680
|
-
for (const project of this.registry.
|
|
2982
|
+
for (const project of this.registry.listRegistrations()) {
|
|
1681
2983
|
for (const stackName of project.enabledStacks ?? []) {
|
|
1682
2984
|
const state = this.stackStates.get(`${project.name}/${stackName}`);
|
|
1683
|
-
|
|
2985
|
+
// Only retry a failed lifecycle attempt. App supervision owns later
|
|
2986
|
+
// changes to a running stack, including individual operator stops.
|
|
2987
|
+
const status = state?.status === 'error' && state.config.type === 'local' && state.startedAt != null
|
|
2988
|
+
? this.toStackInfo(project.name, stackName, state.config).status : state?.status;
|
|
1684
2989
|
// Converge only from fully-down states (absent/stopped/error).
|
|
1685
2990
|
// 'degraded' means mostly-up — restarting the whole stack over it
|
|
1686
2991
|
// would churn healthy apps; per-app recovery owns that case.
|
|
@@ -1692,17 +2997,9 @@ export class ProjectService extends EventEmitter {
|
|
|
1692
2997
|
}
|
|
1693
2998
|
if (pending.length === 0)
|
|
1694
2999
|
return;
|
|
1695
|
-
//
|
|
1696
|
-
//
|
|
1697
|
-
|
|
1698
|
-
if (!(await isDockerAvailable())) {
|
|
1699
|
-
if (this.dockerWasAvailable !== false) {
|
|
1700
|
-
this.logger.warn({ pending: pending.map((p) => `${p.project}/${p.stack}`) }, 'Reconciler: Docker unavailable — deferring enabled-stack resume');
|
|
1701
|
-
}
|
|
1702
|
-
this.dockerWasAvailable = false;
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
this.dockerWasAvailable = true;
|
|
3000
|
+
// Infrastructure admission belongs to startStack, including ownership and
|
|
3001
|
+
// inferred requirements. A global Docker probe would strand remote and
|
|
3002
|
+
// externally provisioned stacks behind an unrelated local dependency.
|
|
1706
3003
|
const now = Date.now();
|
|
1707
3004
|
for (const { project, stack } of pending) {
|
|
1708
3005
|
const key = `${project}/${stack}`;
|
|
@@ -1716,7 +3013,14 @@ export class ProjectService extends EventEmitter {
|
|
|
1716
3013
|
if (!this.getLoadedConfig(project)) {
|
|
1717
3014
|
await this.loadProjectConfig(project);
|
|
1718
3015
|
}
|
|
1719
|
-
await this.startStack(project, stack, { source: 'auto-resume' });
|
|
3016
|
+
const result = await this.startStack(project, stack, { source: 'auto-resume' });
|
|
3017
|
+
if (result.type === 'local' && result.status !== 'running') {
|
|
3018
|
+
if (result.status === 'degraded') {
|
|
3019
|
+
this.logger.warn({ project, stack, notUp: result.notUp }, 'Reconciler: partial start under application supervision');
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3022
|
+
throw new Error(result.notUp?.join(', ') || `Stack state: ${result.status}`);
|
|
3023
|
+
}
|
|
1720
3024
|
this.resumeBackoff.delete(key);
|
|
1721
3025
|
this.logger.info({ project, stack }, 'Reconciler: stack resumed');
|
|
1722
3026
|
}
|
|
@@ -1738,7 +3042,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1738
3042
|
this.resumeTimer = null;
|
|
1739
3043
|
}
|
|
1740
3044
|
this.slaveConnector?.dispose();
|
|
1741
|
-
|
|
3045
|
+
this.infraManager.dispose();
|
|
1742
3046
|
}
|
|
1743
3047
|
// ===========================================================================
|
|
1744
3048
|
// Config Management
|
|
@@ -1762,7 +3066,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1762
3066
|
*
|
|
1763
3067
|
* A reader that arrived in the first seconds after a daemon start found no
|
|
1764
3068
|
* config and was answered with an error: measured on the master restarted
|
|
1765
|
-
* at 12:07:48 UTC, 20 ERROR lines «Project '
|
|
3069
|
+
* at 12:07:48 UTC, 20 ERROR lines «Project 'sample' config not loaded» from
|
|
1766
3070
|
* the console's `getStack` polls, logged as server failures and counted by
|
|
1767
3071
|
* `doctor` — for a project that loads a moment later, which is what the
|
|
1768
3072
|
* reconciler already does for itself (`loadProjectConfig` when absent).
|
|
@@ -1784,7 +3088,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1784
3088
|
this.emit('project:config_reloaded', projectName);
|
|
1785
3089
|
}
|
|
1786
3090
|
else {
|
|
1787
|
-
for (const project of this.registry.
|
|
3091
|
+
for (const project of this.registry.listRegistrations()) {
|
|
1788
3092
|
await this.loadProjectConfig(project.name);
|
|
1789
3093
|
this.emit('project:config_reloaded', project.name);
|
|
1790
3094
|
}
|
|
@@ -1793,12 +3097,19 @@ export class ProjectService extends EventEmitter {
|
|
|
1793
3097
|
// ===========================================================================
|
|
1794
3098
|
// Helpers
|
|
1795
3099
|
// ===========================================================================
|
|
1796
|
-
getRunningStacks(projectName) {
|
|
3100
|
+
getRunningStacks(projectName, includeIncomplete = false) {
|
|
1797
3101
|
const result = [];
|
|
1798
3102
|
for (const [, state] of this.stackStates) {
|
|
1799
|
-
if (state.project
|
|
3103
|
+
if (state.project !== projectName)
|
|
3104
|
+
continue;
|
|
3105
|
+
if (includeIncomplete && state.status !== 'stopped') {
|
|
1800
3106
|
result.push(state.stack);
|
|
3107
|
+
continue;
|
|
1801
3108
|
}
|
|
3109
|
+
const status = state.config.type === 'local' && state.startedAt != null
|
|
3110
|
+
? this.toStackInfo(projectName, state.stack, state.config).status : state.status;
|
|
3111
|
+
if (status === 'running' || status === 'starting')
|
|
3112
|
+
result.push(state.stack);
|
|
1802
3113
|
}
|
|
1803
3114
|
return result;
|
|
1804
3115
|
}
|
|
@@ -1858,7 +3169,7 @@ export class ProjectService extends EventEmitter {
|
|
|
1858
3169
|
if (config.database) {
|
|
1859
3170
|
const dbConfig = typeof config.database === 'object' ? config.database : {};
|
|
1860
3171
|
databases[appName] = {
|
|
1861
|
-
|
|
3172
|
+
// A discovered dependency is not authority to execute a migration.
|
|
1862
3173
|
...(dbConfig.extensions?.length ? { extensions: dbConfig.extensions } : {}),
|
|
1863
3174
|
};
|
|
1864
3175
|
}
|
|
@@ -1954,110 +3265,51 @@ export class ProjectService extends EventEmitter {
|
|
|
1954
3265
|
return readings;
|
|
1955
3266
|
}
|
|
1956
3267
|
async validateStackInfrastructure(projectName, stackName) {
|
|
1957
|
-
const
|
|
1958
|
-
const
|
|
1959
|
-
const stackConfig = stacks[stackName];
|
|
1960
|
-
if (!stackConfig) {
|
|
1961
|
-
return { valid: false, issues: [`Stack '${stackName}' not found`] };
|
|
1962
|
-
}
|
|
1963
|
-
const project = this.registry.get(projectName);
|
|
1964
|
-
if (!project) {
|
|
1965
|
-
return { valid: false, issues: [`Project '${projectName}' not found`] };
|
|
1966
|
-
}
|
|
1967
|
-
// Determine effective infrastructure: stack-level overrides ecosystem-level
|
|
1968
|
-
const effectiveInfra = stackConfig.infrastructure ?? config.infrastructure;
|
|
1969
|
-
const issues = [];
|
|
1970
|
-
// Scan app requirements
|
|
1971
|
-
const { scanRequirements } = await import('../project/requirements-scanner.js');
|
|
1972
|
-
const appEntries = this.resolveStackApps(stackConfig, config);
|
|
1973
|
-
const reqs = await scanRequirements(appEntries, project.path);
|
|
1974
|
-
// Check PostgreSQL
|
|
1975
|
-
if (reqs.databases.length > 0 && !effectiveInfra?.postgres) {
|
|
1976
|
-
issues.push(`PostgreSQL required by ${reqs.databases.map((d) => d.app).join(', ')} but not configured in infrastructure`);
|
|
1977
|
-
}
|
|
1978
|
-
else if (reqs.databases.length > 0 && effectiveInfra?.postgres) {
|
|
1979
|
-
// Check individual databases exist in config
|
|
1980
|
-
const configuredDbs = new Set(Object.keys(effectiveInfra.postgres.databases ?? {}));
|
|
1981
|
-
for (const db of reqs.databases) {
|
|
1982
|
-
if (!configuredDbs.has(db.database)) {
|
|
1983
|
-
issues.push(`Database '${db.database}' needed by '${db.app}' not found in postgres.databases`);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
// Check Redis
|
|
1988
|
-
if (reqs.redis.appCount > 0 && !effectiveInfra?.redis) {
|
|
1989
|
-
issues.push(`Redis required by ${reqs.redis.allocations.map((a) => a.app).join(', ')} but not configured in infrastructure`);
|
|
1990
|
-
}
|
|
1991
|
-
// Check S3/MinIO
|
|
1992
|
-
if (reqs.buckets.length > 0 && !effectiveInfra?.minio) {
|
|
1993
|
-
issues.push(`S3/MinIO required by ${reqs.buckets.map((b) => b.app).join(', ')} but not configured in infrastructure`);
|
|
1994
|
-
}
|
|
1995
|
-
else if (reqs.buckets.length > 0 && effectiveInfra?.minio) {
|
|
1996
|
-
const configuredBuckets = new Set(effectiveInfra.minio.buckets ?? []);
|
|
1997
|
-
for (const b of reqs.buckets) {
|
|
1998
|
-
if (configuredBuckets.size > 0 && !configuredBuckets.has(b.bucket)) {
|
|
1999
|
-
issues.push(`Bucket '${b.bucket}' needed by '${b.app}' not found in minio.buckets`);
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
3268
|
+
const report = await this.diagnoseStack(projectName, stackName);
|
|
3269
|
+
const issues = report.checks.filter(check => check.state === 'blocked' || check.state === 'unknown').map(check => `${check.resource}: ${check.message}`);
|
|
2003
3270
|
return { valid: issues.length === 0, issues };
|
|
2004
3271
|
}
|
|
2005
3272
|
// ===========================================================================
|
|
2006
3273
|
// Private — Stack Launchers
|
|
2007
3274
|
// ===========================================================================
|
|
2008
|
-
async startLocalStack(projectName, stackName, stackConfig, ecosystemConfig) {
|
|
3275
|
+
async startLocalStack(projectName, stackName, stackConfig, ecosystemConfig, selectedEntries, enable, verifyMigrations, verifyArtifactInfrastructure) {
|
|
3276
|
+
const ownsInfrastructure = ownsStackInfrastructure(stackConfig);
|
|
2009
3277
|
const stateKey = `${projectName}/${stackName}`;
|
|
2010
3278
|
const state = this.stackStates.get(stateKey);
|
|
2011
|
-
// 0. Validate infrastructure covers app requirements (warn only, don't block)
|
|
2012
|
-
try {
|
|
2013
|
-
const validation = await this.validateStackInfrastructure(projectName, stackName);
|
|
2014
|
-
if (!validation.valid) {
|
|
2015
|
-
for (const issue of validation.issues) {
|
|
2016
|
-
this.logger.warn({ project: projectName, stack: stackName, issue }, 'Infrastructure validation issue');
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
catch {
|
|
2021
|
-
// Validation is best-effort — don't block stack start
|
|
2022
|
-
}
|
|
2023
3279
|
// 1. Load bootstrap definitions (needed for both infra provisioning and config resolution)
|
|
2024
|
-
const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
|
|
3280
|
+
const appEntries = selectedEntries ?? this.resolveStackApps(stackConfig, ecosystemConfig);
|
|
2025
3281
|
// What the stack gives its apps, vault references resolved — now, before
|
|
2026
3282
|
// anything is provisioned: a key the vault does not hold refuses the
|
|
2027
3283
|
// start here (project/stack-env.ts).
|
|
2028
3284
|
const stackEnv = await this.stackEnvFor(projectName, stackName, stackConfig, appEntries.map((e) => e.name));
|
|
2029
3285
|
const project = this.registry.get(projectName);
|
|
2030
|
-
const appDefinitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
|
|
3286
|
+
const appDefinitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig, appEntries);
|
|
2031
3287
|
// 2. Provision per-stack infrastructure (built-in + app-declared services)
|
|
2032
3288
|
// Auto-detect core services (postgres, redis, minio) from app requirements,
|
|
2033
3289
|
// then merge with explicit infrastructure config (gateway, custom services).
|
|
2034
3290
|
// Merge stack-level + ecosystem-level infrastructure (stack overrides ecosystem, but both contribute)
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
services: { ...explicitServices },
|
|
2049
|
-
};
|
|
2050
|
-
this.logger.info({ project: projectName, stack: stackName, auto: Object.keys(autoInfra), explicit: Object.keys(effectiveInfra.services ?? {}) }, 'Merged auto-detected + explicit infrastructure');
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
catch (err) {
|
|
2054
|
-
this.logger.warn({ error: err.message }, 'Failed to auto-detect infrastructure requirements');
|
|
2055
|
-
}
|
|
3291
|
+
for (const entry of appEntries) {
|
|
3292
|
+
if (entry.bootstrap && !appDefinitions.has(entry.name))
|
|
3293
|
+
throw new Error(`Bootstrap declarations unavailable for ${entry.name}`);
|
|
3294
|
+
}
|
|
3295
|
+
const effectiveInfra = ownsInfrastructure ? mergeInferredInfrastructure(buildInfraFromRequirements(requirementsFromDefinitions(appDefinitions)), verifyArtifactInfrastructure ? await verifyArtifactInfrastructure() : mergeInfrastructure(ecosystemConfig, stackConfig)) : undefined;
|
|
3296
|
+
// Refuse missing/tampered migrators before creating or changing infrastructure.
|
|
3297
|
+
if (effectiveInfra && project) {
|
|
3298
|
+
const migrations = await localMigrationTargets(requestedMigrations(normalizeInfraConfig(effectiveInfra, createDefaultRegistry())), appEntries, project.path, verifyMigrations);
|
|
3299
|
+
const resolveMigration = this.stackEntryBuilder(projectName, stackName, stackConfig, ecosystemConfig, appDefinitions, stackEnv);
|
|
3300
|
+
validateMigrationDestinations(migrations.map(target => {
|
|
3301
|
+
const entry = appEntries.find(app => app.name === target.app);
|
|
3302
|
+
return { app: target.app, database: migrationDatabaseEnvironment(target.environmentApp, resolveMigration(entry).env?.['DATABASE_URL'] ?? '', target.database, target.mode) };
|
|
3303
|
+
}));
|
|
2056
3304
|
}
|
|
2057
3305
|
if (effectiveInfra) {
|
|
2058
3306
|
try {
|
|
2059
3307
|
const infraService = await this.infraManager.provisionStack(projectName, stackName, stackConfig, effectiveInfra, appDefinitions, project?.path);
|
|
2060
3308
|
state.infraService = infraService;
|
|
3309
|
+
const outcome = summariseProvisioning(infraService.getDesiredServices(), infraService.getState().services);
|
|
3310
|
+
if (!outcome.ready && !outcome.empty) {
|
|
3311
|
+
throw new Error(describeProvisioning(outcome));
|
|
3312
|
+
}
|
|
2061
3313
|
}
|
|
2062
3314
|
catch (err) {
|
|
2063
3315
|
const reason = err.message;
|
|
@@ -2069,10 +3321,10 @@ export class ProjectService extends EventEmitter {
|
|
|
2069
3321
|
// Bail out with a clear, structured error so the operator sees the
|
|
2070
3322
|
// real cause first.
|
|
2071
3323
|
//
|
|
2072
|
-
//
|
|
2073
|
-
//
|
|
2074
|
-
//
|
|
2075
|
-
// operation
|
|
3324
|
+
// Explicit controller/external ownership never enters this provisioning
|
|
3325
|
+
// branch. Declarations alone do not imply permission to manage an
|
|
3326
|
+
// application's existing services. For deliberate degraded-mode
|
|
3327
|
+
// operation of stack-owned infrastructure, settings can opt in via
|
|
2076
3328
|
// `settings.allowDegradedInfra: true` (the field is namespaced under
|
|
2077
3329
|
// settings to remain forward-compatible with stack schema).
|
|
2078
3330
|
const allowDegraded = stackConfig.settings?.['allowDegradedInfra'] === true;
|
|
@@ -2088,17 +3340,17 @@ export class ProjectService extends EventEmitter {
|
|
|
2088
3340
|
throwable.cause = err;
|
|
2089
3341
|
throw throwable;
|
|
2090
3342
|
}
|
|
3343
|
+
state.infrastructureStartFailed = true;
|
|
2091
3344
|
this.logger.warn({ project: projectName, stack: stackName }, 'allowDegradedInfra=true — proceeding with app start despite infra failure');
|
|
2092
3345
|
}
|
|
2093
3346
|
}
|
|
2094
|
-
//
|
|
3347
|
+
// Resolve once for both migrations and application startup. Inferred service
|
|
3348
|
+
// requirements are not permission to connect to a database or run migrations.
|
|
3349
|
+
const appConfigBuilder = this.stackEntryBuilder(projectName, stackName, stackConfig, ecosystemConfig, appDefinitions, stackEnv);
|
|
2095
3350
|
if (effectiveInfra && project) {
|
|
2096
|
-
await this.runStackMigrations(projectName, stackName,
|
|
3351
|
+
await this.runStackMigrations(projectName, stackName, this.infraManager.getNormalizedServices(projectName, stackName) ?? {}, appEntries, project.path, appConfigBuilder, verifyMigrations);
|
|
2097
3352
|
}
|
|
2098
|
-
|
|
2099
|
-
// configuration (`stackEntryBuilder` — the same builder a re-read of
|
|
2100
|
-
// the project uses to redefine running apps).
|
|
2101
|
-
const appConfigBuilder = this.stackEntryBuilder(projectName, stackName, stackConfig, ecosystemConfig, appDefinitions, stackEnv);
|
|
3353
|
+
await enable?.(appConfigBuilder);
|
|
2102
3354
|
// 4. Start apps in dependency-aware parallel batches.
|
|
2103
3355
|
//
|
|
2104
3356
|
// Pre-fix this was a serial `for ... await` loop in declaration
|
|
@@ -2116,13 +3368,12 @@ export class ProjectService extends EventEmitter {
|
|
|
2116
3368
|
// parallel, and a wave can't start until every member of the
|
|
2117
3369
|
// previous wave is online. Same skip-on-failed-dep semantics
|
|
2118
3370
|
// as startAll.
|
|
2119
|
-
const
|
|
2120
|
-
const blocked = new Set();
|
|
3371
|
+
const problems = new Map();
|
|
2121
3372
|
for (const wave of resolveStartupOrder(appEntries)) {
|
|
2122
3373
|
await Promise.all(wave.map(async (entry) => {
|
|
2123
|
-
const blockingDep = (entry.dependsOn ?? []).find((d) =>
|
|
3374
|
+
const blockingDep = (entry.dependsOn ?? []).find((d) => problems.has(d));
|
|
2124
3375
|
if (blockingDep) {
|
|
2125
|
-
|
|
3376
|
+
problems.set(entry.name, `dependency ${blockingDep} did not start`);
|
|
2126
3377
|
this.logger.warn({ app: entry.name, project: projectName, stack: stackName, blockedBy: blockingDep }, 'Skipping app — dependency failed or was blocked');
|
|
2127
3378
|
return;
|
|
2128
3379
|
}
|
|
@@ -2139,14 +3390,15 @@ export class ProjectService extends EventEmitter {
|
|
|
2139
3390
|
await this.orchestrator.startApp(namespacedEntry, ecosystemConfig);
|
|
2140
3391
|
}
|
|
2141
3392
|
catch (err) {
|
|
2142
|
-
|
|
3393
|
+
// The raw exception can contain credentials, app output or host paths.
|
|
3394
|
+
// Keep bounded process evidence in stack views available to readers.
|
|
3395
|
+
problems.set(entry.name, publicLaunchFailure(err));
|
|
2143
3396
|
this.logger.error({ app: entry.name, project: projectName, stack: stackName, error: err.message }, 'Failed to start app in stack');
|
|
2144
3397
|
}
|
|
2145
3398
|
}));
|
|
2146
3399
|
}
|
|
2147
|
-
if (
|
|
2148
|
-
|
|
2149
|
-
}
|
|
3400
|
+
if (problems.size)
|
|
3401
|
+
state.appStartErrors = Object.fromEntries(problems);
|
|
2150
3402
|
}
|
|
2151
3403
|
/**
|
|
2152
3404
|
* Deploy a stack to its nodes, and say what it is doing while it does it.
|
|
@@ -2160,7 +3412,7 @@ export class ProjectService extends EventEmitter {
|
|
|
2160
3412
|
async startRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, release = null, reinstall = false) {
|
|
2161
3413
|
const phases = reportPhases(this.logger, { project: projectName, stack: stackName });
|
|
2162
3414
|
try {
|
|
2163
|
-
return await this.deployRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, phases, release, reinstall);
|
|
3415
|
+
return await withDeploymentProgress({ project: projectName, stack: stackName, operationId: randomUUID(), ...(release && { releaseId: release.id }) }, () => this.deployRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, phases, release, reinstall));
|
|
2164
3416
|
}
|
|
2165
3417
|
finally {
|
|
2166
3418
|
// Every exit, including the refusals: a reporter that outlives its
|
|
@@ -2197,9 +3449,15 @@ export class ProjectService extends EventEmitter {
|
|
|
2197
3449
|
// variant selection and override merging, on the side with less
|
|
2198
3450
|
// information.
|
|
2199
3451
|
phases.enter('reading what the applications declare');
|
|
2200
|
-
|
|
3452
|
+
if (release && !release.remoteDefinitions)
|
|
3453
|
+
throw Error('Checked remote release has no admitted infrastructure definitions');
|
|
3454
|
+
const declaredServices = await this.collectDeclaredServices(projectName, stackConfig, ecosystemConfig, { definitions: release?.remoteDefinitions });
|
|
2201
3455
|
// Build artifacts for apps in this stack
|
|
2202
3456
|
const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
|
|
3457
|
+
const migrationRequests = requestedMigrations({ ...declaredServices,
|
|
3458
|
+
...normalizeInfraConfig(mergeInfrastructure(ecosystemConfig, stackConfig) ?? {}, createDefaultRegistry()) });
|
|
3459
|
+
validateMigrationOwners(migrationRequests, appEntries);
|
|
3460
|
+
const placements = orderMigrationPlacements(migrationRequests, resolveNodePlacements(stackConfig, appEntries));
|
|
2203
3461
|
// What the stack gives its apps, vault references resolved — before any
|
|
2204
3462
|
// node is touched: a key the vault does not hold refuses the deployment
|
|
2205
3463
|
// here, not after a node was provisioned (project/stack-env.ts). A remote
|
|
@@ -2217,6 +3475,7 @@ export class ProjectService extends EventEmitter {
|
|
|
2217
3475
|
// Built in clean clones, gated, and checked against the manifest by
|
|
2218
3476
|
// `loadRelease` — nothing is compiled here, from this machine's disk.
|
|
2219
3477
|
const files = new Map(release.files.map((f) => [f.app, f]));
|
|
3478
|
+
const sources = new Map(releaseArtifactOrigins(release.manifest, release.manifestSha256).map(source => [source.app, source]));
|
|
2220
3479
|
artifacts = appEntries.flatMap((entry) => {
|
|
2221
3480
|
const file = files.get(entry.name);
|
|
2222
3481
|
return file
|
|
@@ -2228,6 +3487,8 @@ export class ProjectService extends EventEmitter {
|
|
|
2228
3487
|
builtAt: release.manifest.builtAt,
|
|
2229
3488
|
checksum: file.inputs,
|
|
2230
3489
|
tarballSha256: file.sha256,
|
|
3490
|
+
release: sources.get(file.app).origin,
|
|
3491
|
+
...(sources.get(file.app).artifact.launch && { launch: sources.get(file.app).artifact.launch }),
|
|
2231
3492
|
}]
|
|
2232
3493
|
: [];
|
|
2233
3494
|
});
|
|
@@ -2330,7 +3591,9 @@ export class ProjectService extends EventEmitter {
|
|
|
2330
3591
|
const locks = new Map();
|
|
2331
3592
|
try {
|
|
2332
3593
|
// Deploy to each node: provision slave → deploy artifacts → connect
|
|
2333
|
-
for (const node of
|
|
3594
|
+
for (const { node, apps: nodeApps } of placements) {
|
|
3595
|
+
const selectedNames = new Set(nodeApps.map(app => app.name));
|
|
3596
|
+
const nodeArtifacts = artifacts.filter(artifact => selectedNames.has(artifact.app));
|
|
2334
3597
|
const nodeKey = `${node.host}:${node.port ?? 9700}`;
|
|
2335
3598
|
if (!leases.has(nodeKey)) {
|
|
2336
3599
|
skipped.push(nodeKey);
|
|
@@ -2338,9 +3601,6 @@ export class ProjectService extends EventEmitter {
|
|
|
2338
3601
|
continue;
|
|
2339
3602
|
}
|
|
2340
3603
|
// 1. Provision slave: install runtime + omnitron + generate slave config + start daemon
|
|
2341
|
-
const unsubProvision = this.deployer.onProgress((progress) => {
|
|
2342
|
-
this.emit('stack:deploy_progress', projectName, stackName, progress);
|
|
2343
|
-
});
|
|
2344
3604
|
const target = targets.get(nodeKey);
|
|
2345
3605
|
const master = await resolveMasterHost({ advertiseHost: _dc.advertiseHost, bindHost: _dc.host }, target);
|
|
2346
3606
|
if (master.host) {
|
|
@@ -2358,7 +3618,6 @@ export class ProjectService extends EventEmitter {
|
|
|
2358
3618
|
locks.set(nodeKey, lock);
|
|
2359
3619
|
phases.enter(`provisioning ${node.host}`);
|
|
2360
3620
|
const provisioned = await this.deployer.provisionSlaveNode(target, master.host, masterPort, projectName);
|
|
2361
|
-
unsubProvision();
|
|
2362
3621
|
if (!provisioned) {
|
|
2363
3622
|
skipped.push(nodeKey);
|
|
2364
3623
|
this.logger.error({ node: nodeKey }, 'Failed to provision slave — skipping');
|
|
@@ -2443,29 +3702,27 @@ export class ProjectService extends EventEmitter {
|
|
|
2443
3702
|
// Writing one was the first attempt and it was worse than the problem:
|
|
2444
3703
|
// a stack with an infrastructure block is a stack the node PROVISIONS,
|
|
2445
3704
|
// so the node autostarted its own `deployed` stack, built a second
|
|
2446
|
-
// complete set of containers under `
|
|
2447
|
-
// and swept the master's `
|
|
3705
|
+
// complete set of containers under `sample-deployed-*` on empty volumes,
|
|
3706
|
+
// and swept the master's `sample-test-*` as orphans. An address is not an
|
|
2448
3707
|
// instruction to build what it points at.
|
|
2449
3708
|
phases.enter(`resolving the environment for ${node.host}`);
|
|
2450
|
-
const appEnv = await this.resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig,
|
|
3709
|
+
const appEnv = await this.resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, nodeApps, deployedInfra, release?.remoteDefinitions);
|
|
2451
3710
|
// The stack's own, over the computed — as on a local stack, where
|
|
2452
3711
|
// `settings.env` wins over both.
|
|
2453
|
-
for (const [app, env] of Object.entries(stackEnv))
|
|
3712
|
+
for (const [app, env] of Object.entries(stackEnv).filter(([name]) => selectedNames.has(name)))
|
|
2454
3713
|
appEnv[app] = { ...(appEnv[app] ?? {}), ...env };
|
|
2455
3714
|
// 3. Deploy app artifacts via SSH
|
|
2456
|
-
if (
|
|
2457
|
-
const unsubDeploy = this.deployer.onProgress((progress) => {
|
|
2458
|
-
this.emit('stack:deploy_progress', projectName, stackName, progress);
|
|
2459
|
-
});
|
|
3715
|
+
if (nodeArtifacts.length > 0) {
|
|
2460
3716
|
// The definitions travel with the artifacts. A node that receives
|
|
2461
3717
|
// one without the other has files it cannot run, and says so only if
|
|
2462
3718
|
// someone asks it directly.
|
|
2463
|
-
await leases.confirm(nodeKey, `delivering ${
|
|
2464
|
-
phases.enter(`delivering ${
|
|
3719
|
+
await leases.confirm(nodeKey, `delivering ${nodeArtifacts.length} artifact(s) to ${node.host}`);
|
|
3720
|
+
phases.enter(`delivering ${nodeArtifacts.length} artifact(s) to ${node.host}`);
|
|
2465
3721
|
const sink = ecosystemConfig.monitoring?.alertSink;
|
|
2466
|
-
const results = await this.deployer.deployToStack([target],
|
|
2467
|
-
apps:
|
|
3722
|
+
const results = await this.deployer.deployToStack([target], nodeArtifacts, projectName, {
|
|
3723
|
+
apps: nodeApps,
|
|
2468
3724
|
appEnv,
|
|
3725
|
+
migrations: migrationRequests.filter(request => selectedNames.has(request.app)),
|
|
2469
3726
|
stack: stackName,
|
|
2470
3727
|
// Past the node's record of what each app runs: ship and restart all.
|
|
2471
3728
|
...(reinstall ? { force: true } : {}),
|
|
@@ -2473,7 +3730,6 @@ export class ProjectService extends EventEmitter {
|
|
|
2473
3730
|
// sink on the stack it runs; named as the master names the stack.
|
|
2474
3731
|
...(sink && { alertSink: { ...sink, host: `${projectName}/${stackName}` } }),
|
|
2475
3732
|
});
|
|
2476
|
-
unsubDeploy();
|
|
2477
3733
|
const failed = results.filter((r) => r.status === 'failed');
|
|
2478
3734
|
if (failed.length > 0) {
|
|
2479
3735
|
// The other half of the same invariant. Building every artifact
|
|
@@ -2597,7 +3853,7 @@ export class ProjectService extends EventEmitter {
|
|
|
2597
3853
|
* node, beside containers whose ports are published on that node's
|
|
2598
3854
|
* loopback. Local is what they are from where they stand.
|
|
2599
3855
|
*/
|
|
2600
|
-
async resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, appEntries, infrastructure) {
|
|
3856
|
+
async resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, appEntries, infrastructure, admitted) {
|
|
2601
3857
|
if (!infrastructure)
|
|
2602
3858
|
return {};
|
|
2603
3859
|
try {
|
|
@@ -2610,7 +3866,7 @@ export class ProjectService extends EventEmitter {
|
|
|
2610
3866
|
// Measured: the node's config carried five environment variables per
|
|
2611
3867
|
// app and not the one the apps were failing for. An empty map is not a
|
|
2612
3868
|
// map of nothing declared; it is a map nobody filled.
|
|
2613
|
-
const definitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
|
|
3869
|
+
const definitions = admitted ?? await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
|
|
2614
3870
|
// The stack's answers about services it does not provision.
|
|
2615
3871
|
//
|
|
2616
3872
|
// `serviceOverrides` is where a stack says that bitcoin and monero are
|
|
@@ -2645,8 +3901,8 @@ export class ProjectService extends EventEmitter {
|
|
|
2645
3901
|
// Not fatal, and loud: the apps would start with whatever their own
|
|
2646
3902
|
// definitions carry, which is how they came to be started with
|
|
2647
3903
|
// `postgres:postgres` in the first place.
|
|
2648
|
-
this.logger.error({ project: projectName, error: err.message }, 'Could not resolve
|
|
2649
|
-
|
|
3904
|
+
this.logger.error({ project: projectName, error: err.message }, 'Could not resolve application service bindings; deployment refused before application delivery');
|
|
3905
|
+
throw new Error('Could not resolve application service bindings', { cause: err });
|
|
2650
3906
|
}
|
|
2651
3907
|
}
|
|
2652
3908
|
/**
|
|
@@ -2726,7 +3982,7 @@ export class ProjectService extends EventEmitter {
|
|
|
2726
3982
|
// it does, it means the node's daemon holds no infrastructure for it — it
|
|
2727
3983
|
// restarted since it last provisioned, and the step that would have
|
|
2728
3984
|
// provisioned it did not reach it. The declared password in its place is
|
|
2729
|
-
// wrong by construction: on
|
|
3985
|
+
// wrong by construction: on sample/test (2026-09-25) a deployment right
|
|
2730
3986
|
// after a `fleet upgrade` found the node 66 s into a 60 s mesh wait,
|
|
2731
3987
|
// «deployed anyway», configured six apps with `postgres`, and was stopped
|
|
2732
3988
|
// only because each app's migrations had to log in first.
|
|
@@ -2984,206 +4240,59 @@ export class ProjectService extends EventEmitter {
|
|
|
2984
4240
|
return { ready: false, detail: `could not ask the node: ${err.message}` };
|
|
2985
4241
|
}
|
|
2986
4242
|
}
|
|
2987
|
-
async startClusterStack(projectName, stackName, stackConfig, ecosystemConfig) {
|
|
2988
|
-
const nodes = stackConfig.nodes ?? [];
|
|
2989
|
-
if (nodes.length === 0) {
|
|
2990
|
-
throw new Error(`Cluster stack '${stackName}' has no nodes configured`);
|
|
2991
|
-
}
|
|
2992
|
-
this.logger.info({ project: projectName, stack: stackName, nodes: nodes.length }, 'Starting cluster stack — coordinating slave daemons');
|
|
2993
|
-
const connector = this.getSlaveConnector();
|
|
2994
|
-
// Build artifacts
|
|
2995
|
-
const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
|
|
2996
|
-
const project = this.registry.get(projectName);
|
|
2997
|
-
let artifacts = [];
|
|
2998
|
-
if (project) {
|
|
2999
|
-
try {
|
|
3000
|
-
const { ArtifactBuilder } = await import('../project/artifact-builder.js');
|
|
3001
|
-
const builder = new ArtifactBuilder(project.path, undefined, {
|
|
3002
|
-
info: (msg) => this.logger.info({ project: project.name }, msg),
|
|
3003
|
-
});
|
|
3004
|
-
const outcome = await builder.buildAll(appEntries);
|
|
3005
|
-
artifacts = outcome.built;
|
|
3006
|
-
for (const f of outcome.failed) {
|
|
3007
|
-
this.logger.error({ app: f.app, error: f.error }, 'Artifact build failed');
|
|
3008
|
-
}
|
|
3009
|
-
}
|
|
3010
|
-
catch (err) {
|
|
3011
|
-
this.logger.error({ error: err.message }, 'Artifact build failed');
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
// Resolve master address (from the SLAVE's perspective — what it dials)
|
|
3015
|
-
const { DEFAULT_DAEMON_CONFIG: _dc } = await import('../config/defaults.js');
|
|
3016
|
-
const { resolveMasterHost } = await import('./master-address.js');
|
|
3017
|
-
const masterPort = _dc.port;
|
|
3018
|
-
// Provision all slave nodes in parallel (install runtime + omnitron + config)
|
|
3019
|
-
const appNodes = nodes.filter((n) => n.role !== 'database' && n.role !== 'cache');
|
|
3020
|
-
// One writer per machine, as for a remote stack — see `deployRemoteStack`
|
|
3021
|
-
// and `node-deploy-lease.ts`.
|
|
3022
|
-
const targets = new Map();
|
|
3023
|
-
for (const node of appNodes) {
|
|
3024
|
-
targets.set(`${node.host}:${node.port ?? 9700}`, await this.targetForStackNode(node));
|
|
3025
|
-
}
|
|
3026
|
-
await withNodeLeases([...targets].map(([node, target]) => ({
|
|
3027
|
-
node,
|
|
3028
|
-
machine: `${target.host}:${target.sshPort ?? 22}`,
|
|
3029
|
-
run: this.deployer.leaseRunner(target),
|
|
3030
|
-
})), `${projectName}/${stackName}`, this.logger, async (leases) => {
|
|
3031
|
-
const leased = appNodes.filter((n) => leases.has(`${n.host}:${n.port ?? 9700}`));
|
|
3032
|
-
for (const [node, because] of leases.unreachable) {
|
|
3033
|
-
this.logger.error({ node, because }, 'Could not reach this node to lease it — skipping');
|
|
3034
|
-
}
|
|
3035
|
-
for (const node of leased) {
|
|
3036
|
-
const nodeKey = `${node.host}:${node.port ?? 9700}`;
|
|
3037
|
-
const unsubProvision = this.deployer.onProgress((progress) => {
|
|
3038
|
-
this.emit('stack:deploy_progress', projectName, stackName, progress);
|
|
3039
|
-
});
|
|
3040
|
-
const target = targets.get(nodeKey);
|
|
3041
|
-
const master = await resolveMasterHost({ advertiseHost: _dc.advertiseHost, bindHost: _dc.host }, target);
|
|
3042
|
-
this.logger.info({ node: node.host, masterHost: master.host ?? '(none — this node is pulled from)', from: master.source }, 'Resolved the master address this slave will dial');
|
|
3043
|
-
await leases.confirm(nodeKey, `provisioning ${node.host}`);
|
|
3044
|
-
await this.deployer.provisionSlaveNode(target, master.host, masterPort, projectName);
|
|
3045
|
-
unsubProvision();
|
|
3046
|
-
}
|
|
3047
|
-
// Deploy app artifacts to all provisioned nodes
|
|
3048
|
-
if (artifacts.length > 0 && leased.length > 0) {
|
|
3049
|
-
for (const node of leased) {
|
|
3050
|
-
await leases.confirm(`${node.host}:${node.port ?? 9700}`, `delivering to ${node.host}`);
|
|
3051
|
-
}
|
|
3052
|
-
const unsubDeploy = this.deployer.onProgress((progress) => {
|
|
3053
|
-
this.emit('stack:deploy_progress', projectName, stackName, progress);
|
|
3054
|
-
});
|
|
3055
|
-
const results = await this.deployer.deployToStack(leased.map((n) => targets.get(`${n.host}:${n.port ?? 9700}`)), artifacts, projectName,
|
|
3056
|
-
// Same on the cluster path: artifacts without definitions are files a
|
|
3057
|
-
// node cannot run.
|
|
3058
|
-
{ concurrency: 5, apps: appEntries });
|
|
3059
|
-
unsubDeploy();
|
|
3060
|
-
const successful = results.filter((r) => r.status === 'success').length;
|
|
3061
|
-
const failed = results.filter((r) => r.status === 'failed').length;
|
|
3062
|
-
this.logger.info({ successful, failed, total: results.length }, 'Cluster deployment complete');
|
|
3063
|
-
}
|
|
3064
|
-
});
|
|
3065
|
-
// Connect to all slave daemons
|
|
3066
|
-
for (const node of nodes) {
|
|
3067
|
-
await connector.addSlave({
|
|
3068
|
-
host: node.host,
|
|
3069
|
-
port: node.port ?? 9700,
|
|
3070
|
-
label: node.label,
|
|
3071
|
-
stack: stackName,
|
|
3072
|
-
project: projectName,
|
|
3073
|
-
});
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
4243
|
// ===========================================================================
|
|
3077
4244
|
// Private — Utilities
|
|
3078
4245
|
// ===========================================================================
|
|
3079
4246
|
/**
|
|
3080
|
-
*
|
|
3081
|
-
* stack infrastructure config. Migrations are executed after databases are
|
|
4247
|
+
* Execute the explicit normalized migration owners in their declared order. Migrations are executed after databases are
|
|
3082
4248
|
* created but before apps are started, so apps always see a current schema.
|
|
3083
4249
|
*
|
|
3084
|
-
*
|
|
3085
|
-
*
|
|
3086
|
-
*
|
|
4250
|
+
* Config bindings use declared cwd and verified release members. Legacy true
|
|
4251
|
+
* retains the conventional script. Both receive the owner's resolved environment.
|
|
4252
|
+
* Normalized presets are the authority for opt-in, including legacy sugar.
|
|
3087
4253
|
*/
|
|
3088
|
-
async runStackMigrations(projectName, stackName,
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
const
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
//
|
|
3099
|
-
//
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
// Three minutes, not one. A minute fits a container that already has its
|
|
3103
|
-
// data directory; it does not fit a cold one, which has to initdb and
|
|
3104
|
-
// install extensions before it listens — and on a loaded machine that is
|
|
3105
|
-
// several minutes. When the wait gives up the whole `stack start` aborts
|
|
3106
|
-
// and no app is started at all, so a deadline set for the warm case turns
|
|
3107
|
-
// a slow first boot into a stack that will not come up.
|
|
3108
|
-
await waitForPostgres('localhost', port, user, password, 180_000);
|
|
3109
|
-
for (const [dbName, dbConfig] of Object.entries(pgConfig.databases)) {
|
|
3110
|
-
// Only run if migrate flag is set AND a matching app exists
|
|
3111
|
-
if (!dbConfig || typeof dbConfig !== 'object' || !dbConfig.migrate)
|
|
3112
|
-
continue;
|
|
3113
|
-
if (!appNames.has(dbName))
|
|
3114
|
-
continue;
|
|
3115
|
-
const migrateScript = path.join(projectPath, 'apps', dbName, 'src', 'database', 'migrate.ts');
|
|
3116
|
-
try {
|
|
3117
|
-
await fs.promises.access(migrateScript);
|
|
3118
|
-
}
|
|
3119
|
-
catch {
|
|
3120
|
-
this.logger.debug({ app: dbName, script: migrateScript }, 'Migration script not found — skipping');
|
|
3121
|
-
continue;
|
|
3122
|
-
}
|
|
3123
|
-
this.logger.info({ project: projectName, stack: stackName, database: dbName }, 'Running database migrations');
|
|
3124
|
-
// Retry policy: up to 5 attempts with exponential backoff (1s, 2s, 4s, 8s, 16s).
|
|
3125
|
-
// Migration tools are typically idempotent, so retrying after a transient
|
|
3126
|
-
// ECONNREFUSED / role-creation race is safe.
|
|
3127
|
-
const maxAttempts = 5;
|
|
3128
|
-
let attempt = 0;
|
|
3129
|
-
let lastErr = null;
|
|
3130
|
-
while (attempt < maxAttempts) {
|
|
3131
|
-
attempt++;
|
|
4254
|
+
async runStackMigrations(projectName, stackName, services, appEntries, projectPath, resolveEntry, verifyMigrations) {
|
|
4255
|
+
const names = requestedMigrations(services);
|
|
4256
|
+
const planned = await localMigrationTargets(names, appEntries, projectPath, verifyMigrations);
|
|
4257
|
+
// Validate every resolved destination before the first DB probe. No missing source fallback.
|
|
4258
|
+
const targets = planned.map(target => {
|
|
4259
|
+
const env = resolveEntry(appEntries.find(entry => entry.name === target.app)).env;
|
|
4260
|
+
const database = migrationDatabaseEnvironment(target.environmentApp, env?.['DATABASE_URL'] ?? '', target.database, target.mode);
|
|
4261
|
+
return { target, env, database };
|
|
4262
|
+
});
|
|
4263
|
+
validateMigrationDestinations(targets.map(({ target, database }) => ({ app: target.app, database })));
|
|
4264
|
+
// Import every config and resolve its definitions before the first connection.
|
|
4265
|
+
// Runtime connect()/SQL are intentionally not executed by this preflight.
|
|
4266
|
+
for (const { target, env, database } of targets)
|
|
4267
|
+
if (target.mode === 'config') {
|
|
3132
4268
|
try {
|
|
3133
|
-
|
|
3134
|
-
// holds the daemon's whole event loop for as long as the child runs
|
|
3135
|
-
// — no RPC answered, no heartbeat, no mesh — which the event-loop
|
|
3136
|
-
// watch caught at a master boot on 2026-09-23: six migrations back
|
|
3137
|
-
// to back, 1 539 / 871 / 727 / 941 / 740 / 782 ms, 5.6 s of a daemon
|
|
3138
|
-
// that answered nothing, on every restart. And with the retries
|
|
3139
|
-
// below, a migration that hangs held it up to 60 s per attempt.
|
|
3140
|
-
const { execFile } = await import('node:child_process');
|
|
3141
|
-
const { promisify } = await import('node:util');
|
|
3142
|
-
const running = promisify(execFile)(process.execPath, ['--import', 'tsx/esm', migrateScript], {
|
|
3143
|
-
cwd: path.join(projectPath, 'apps', dbName),
|
|
3144
|
-
env: {
|
|
3145
|
-
...process.env,
|
|
3146
|
-
DATABASE_URL: `postgresql://${user}:${password}@localhost:${port}/${dbName}`,
|
|
3147
|
-
// App-specific env vars (various naming conventions)
|
|
3148
|
-
[`${dbName.toUpperCase()}__DATABASE__HOST`]: 'localhost',
|
|
3149
|
-
[`${dbName.toUpperCase()}__DATABASE__PORT`]: String(port),
|
|
3150
|
-
[`${dbName.toUpperCase()}__DATABASE__DATABASE`]: dbName,
|
|
3151
|
-
[`${dbName.toUpperCase()}__DATABASE__USER`]: user,
|
|
3152
|
-
[`${dbName.toUpperCase()}__DATABASE__PASSWORD`]: password,
|
|
3153
|
-
},
|
|
3154
|
-
timeout: 60_000,
|
|
3155
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
3156
|
-
});
|
|
3157
|
-
// Nothing to read from us, as `stdio: 'ignore'` said before.
|
|
3158
|
-
running.child.stdin?.end();
|
|
3159
|
-
const { stdout } = await running;
|
|
3160
|
-
// What the migrations said, in order — see `migrationOutput`.
|
|
3161
|
-
for (const line of migrationOutput(String(stdout))) {
|
|
3162
|
-
this.logger.info({ database: dbName, line }, 'Migration said');
|
|
3163
|
-
}
|
|
3164
|
-
this.logger.info({ database: dbName, attempts: attempt }, 'Migrations applied');
|
|
3165
|
-
lastErr = null;
|
|
3166
|
-
break;
|
|
4269
|
+
await executeLocalMigration(target, { ...env, ...database.env }, { validate: true });
|
|
3167
4270
|
}
|
|
3168
|
-
catch (
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
// Only retry on transient connection errors. Schema / SQL errors
|
|
3172
|
-
// are deterministic and won't go away by trying again.
|
|
3173
|
-
const transient = /ECONNREFUSED|ECONNRESET|ENOTCONN|terminat(ing|ed)|server closing|kysely.*Connection/i.test(stderr) ||
|
|
3174
|
-
/ECONNREFUSED|ECONNRESET/i.test(err.message ?? '');
|
|
3175
|
-
if (!transient || attempt >= maxAttempts) {
|
|
3176
|
-
this.logger.error({ database: dbName, attempt, error: err.message, stderr }, 'Migration failed — app may fail to start');
|
|
3177
|
-
break;
|
|
3178
|
-
}
|
|
3179
|
-
const delayMs = Math.min(1000 * 2 ** (attempt - 1), 16_000);
|
|
3180
|
-
this.logger.warn({ database: dbName, attempt, nextRetryInMs: delayMs, error: err.message }, 'Migration transient failure — retrying');
|
|
3181
|
-
await new Promise((r) => setTimeout(r, delayMs));
|
|
4271
|
+
catch (error) {
|
|
4272
|
+
const failed = error;
|
|
4273
|
+
throw new Error(`Migration config refused for ${target.service}/${target.database} (${target.app}); no migrations executed: ${describeFailure(failed.stderr?.trim() || failed)}`, { cause: error });
|
|
3182
4274
|
}
|
|
3183
4275
|
}
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
4276
|
+
for (const { target, env, database } of targets) {
|
|
4277
|
+
const { host, port, user, password, connectionString } = database;
|
|
4278
|
+
this.logger.info({ project: projectName, stack: stackName, database: target.database, host, port }, 'Waiting for migration database');
|
|
4279
|
+
await waitForPostgres(host, port, user, password, 180_000, connectionString);
|
|
4280
|
+
// Database readiness may take minutes. Recheck the canonical payload and owner immediately before execution.
|
|
4281
|
+
const [current] = await localMigrationTargets([target], appEntries, projectPath, verifyMigrations);
|
|
4282
|
+
if (!current || !isDeepStrictEqual(current, target))
|
|
4283
|
+
throw Error('Migration target changed during database readiness');
|
|
4284
|
+
this.logger.info({ project: projectName, stack: stackName, database: target.database }, 'Running database migrations');
|
|
4285
|
+
try {
|
|
4286
|
+
const { stdout } = await executeLocalMigration(target, { ...env, ...database.env });
|
|
4287
|
+
for (const line of migrationOutput(stdout))
|
|
4288
|
+
this.logger.info({ database: target.database, line: redactDiagnosticText(line) }, 'Migration said');
|
|
4289
|
+
this.logger.info({ database: target.database, attempts: 1 }, 'Migrations applied');
|
|
4290
|
+
}
|
|
4291
|
+
catch (error) {
|
|
4292
|
+
const failed = error;
|
|
4293
|
+
this.logger.error({ database: target.database, error: redactDiagnosticText(failed.message), stderr: redactDiagnosticText(failed.stderr?.trim() ?? '') }, 'Migration failed — stack startup refused');
|
|
4294
|
+
const reason = describeFailure(failed.stderr?.trim() || failed);
|
|
4295
|
+
throw new Error(`Migration failed for ${projectName}/${stackName}/${target.service}/${target.database} (${target.app}); stack startup refused: ${reason}. Inspect the migration ledger before retrying.`, { cause: error });
|
|
3187
4296
|
}
|
|
3188
4297
|
}
|
|
3189
4298
|
}
|
|
@@ -3193,10 +4302,13 @@ export class ProjectService extends EventEmitter {
|
|
|
3193
4302
|
* Config stacks take precedence over user stacks with the same name.
|
|
3194
4303
|
* No implicit fallbacks — stacks must be created explicitly.
|
|
3195
4304
|
*/
|
|
3196
|
-
resolveStacks(config,
|
|
4305
|
+
resolveStacks(config, project) {
|
|
3197
4306
|
const configStacks = config.stacks ?? {};
|
|
3198
|
-
if (
|
|
3199
|
-
const userStacks = this.loadUserStacks(
|
|
4307
|
+
if (project) {
|
|
4308
|
+
const userStacks = typeof project === 'string' ? this.loadUserStacks(project)
|
|
4309
|
+
: this.loadUserStacks(project.name, path.join(project.path, 'omnitron.stacks.json'));
|
|
4310
|
+
// Saved topology intent is deliberately separate from executable configuration.
|
|
4311
|
+
// A daemon restart, auto-resume or ordinary stack start must not admit a migration.
|
|
3200
4312
|
return { ...userStacks, ...configStacks };
|
|
3201
4313
|
}
|
|
3202
4314
|
return configStacks;
|
|
@@ -3230,17 +4342,17 @@ export class ProjectService extends EventEmitter {
|
|
|
3230
4342
|
const project = this.registry.get(projectName);
|
|
3231
4343
|
const portAllocation = this.infraManager.getPortAllocation(projectName, stackName);
|
|
3232
4344
|
const normalizedSvcs = this.infraManager.getNormalizedServices(projectName, stackName);
|
|
3233
|
-
const resolvedStack = resolveStack(ecosystemConfig, projectName, stackName, stackConfig, appDefinitions, portAllocation ?? undefined, normalizedSvcs ?? undefined);
|
|
4345
|
+
const resolvedStack = ownsStackInfrastructure(stackConfig) ? resolveStack(ecosystemConfig, projectName, stackName, stackConfig, appDefinitions, portAllocation ?? undefined, normalizedSvcs ?? undefined) : undefined;
|
|
3234
4346
|
return (entry) => {
|
|
3235
|
-
const appConfig = resolvedStack
|
|
4347
|
+
const appConfig = resolvedStack?.appConfigs.get(entry.name);
|
|
3236
4348
|
const infraEnv = appConfig ? resolvedConfigToEnv(appConfig, entry.name, stackName) : {};
|
|
3237
4349
|
return {
|
|
3238
4350
|
...entry,
|
|
3239
4351
|
name: ProjectService.handleKey(projectName, stackName, entry.name),
|
|
3240
4352
|
...(entry.bootstrap && project
|
|
3241
|
-
? { bootstrap:
|
|
4353
|
+
? { bootstrap: resolveAppPath(project.path, entry, entry.bootstrap) }
|
|
3242
4354
|
: {}),
|
|
3243
|
-
...(project ? { cwd: project.path } : {}),
|
|
4355
|
+
...(project ? { cwd: resolveAppPath(project.path, entry) } : {}),
|
|
3244
4356
|
env: {
|
|
3245
4357
|
// Derived first, stated second. `infraEnv` is COMPUTED from the
|
|
3246
4358
|
// stack's configuration; `entry.env` is WRITTEN — by a developer
|
|
@@ -3281,39 +4393,35 @@ export class ProjectService extends EventEmitter {
|
|
|
3281
4393
|
* its chain daemon, its cache — was invisible on that path. The local
|
|
3282
4394
|
* path had it all along, twenty lines up from where it was needed.
|
|
3283
4395
|
*/
|
|
3284
|
-
async loadAppDefinitions(projectName, stackConfig, ecosystemConfig) {
|
|
4396
|
+
async loadAppDefinitions(projectName, stackConfig, ecosystemConfig, appEntries = this.resolveStackApps(stackConfig, ecosystemConfig), archived) {
|
|
3285
4397
|
const appDefinitions = new Map();
|
|
3286
|
-
const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
|
|
3287
4398
|
const project = this.registry.get(projectName);
|
|
3288
4399
|
for (const entry of appEntries) {
|
|
4400
|
+
if (entry.releaseFrom)
|
|
4401
|
+
throw Error('Application ' + entry.name + ' requires a prepared and selected release before its prerequisites can be read');
|
|
3289
4402
|
if (entry.bootstrap && project) {
|
|
3290
4403
|
try {
|
|
3291
|
-
const bootstrapAbsPath =
|
|
3292
|
-
const {
|
|
3293
|
-
|
|
4404
|
+
const bootstrapAbsPath = resolveAppPath(project.path, entry, entry.bootstrap);
|
|
4405
|
+
const { definition: loaded, metadata } = await readBootstrapMetadata(bootstrapAbsPath);
|
|
4406
|
+
this.bootstrapMetadata.save(metadata);
|
|
4407
|
+
// Preserve only declarative data across backend handoffs, never executable hooks.
|
|
4408
|
+
this.bootstrapTopology.set(bootstrapAbsPath, structuredClone({ ...(loaded.requires ? { requires: loaded.requires } : {}), ...(loaded.omnitronConfig ? { omnitronConfig: loaded.omnitronConfig } : {}) }));
|
|
3294
4409
|
// The app's `config/default.json`, read now and put on a COPY of
|
|
3295
4410
|
// the definition: the loaded one is the loader's cache entry, and
|
|
3296
4411
|
// writing to it made the first read the only one until the daemon
|
|
3297
4412
|
// restarted (`project/declared-config.ts`).
|
|
3298
|
-
const declared = readDeclaredConfig(bootstrapAbsPath);
|
|
3299
|
-
if (declared
|
|
3300
|
-
|
|
3301
|
-
|
|
4413
|
+
const declared = archived ? archived.get(entry.name) : readDeclaredConfig(bootstrapAbsPath);
|
|
4414
|
+
if (!declared)
|
|
4415
|
+
throw Error('Missing admitted app declaration');
|
|
4416
|
+
if (declared.malformed)
|
|
4417
|
+
throw new Error(`Application configuration is invalid or unreadable: ${entry.name}`);
|
|
3302
4418
|
const definition = withDeclaredConfig(loaded, declared);
|
|
3303
4419
|
appDefinitions.set(entry.name, definition);
|
|
3304
4420
|
}
|
|
3305
4421
|
catch (err) {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
// daemon — so a swallowed failure here does not degrade the
|
|
3310
|
-
// deployment, it silently removes a whole category from it.
|
|
3311
|
-
//
|
|
3312
|
-
// Measured: a remote stack reported `declared by apps: (none)` and
|
|
3313
|
-
// provisioned no infrastructure, because every bootstrap import
|
|
3314
|
-
// had failed in a process that could not load TypeScript. Nothing
|
|
3315
|
-
// said so at any level.
|
|
3316
|
-
this.logger.warn({ app: entry.name, bootstrap: entry.bootstrap, error: err.message }, 'Could not load this app\'s definition — anything it declares it needs will not be provisioned');
|
|
4422
|
+
this.bootstrapMetadata.fail(resolveAppPath(project.path, entry, entry.bootstrap), err);
|
|
4423
|
+
this.logger.error({ app: entry.name, bootstrap: entry.bootstrap }, 'Stack prerequisites unavailable; refusing incomplete provisioning');
|
|
4424
|
+
throw new Error(`Could not read declarations for ${entry.name}: ${publicLaunchFailure(err)}. Refresh declarations before starting.`, { cause: err });
|
|
3317
4425
|
}
|
|
3318
4426
|
}
|
|
3319
4427
|
}
|
|
@@ -3325,16 +4433,14 @@ export class ProjectService extends EventEmitter {
|
|
|
3325
4433
|
* One entry per service name: two applications that both declare
|
|
3326
4434
|
* `postgres` mean one Postgres, which is what `serviceOverrides` keys on
|
|
3327
4435
|
* and what the container resolver has always assumed. A later declaration
|
|
3328
|
-
* that disagrees with an earlier one
|
|
3329
|
-
*
|
|
3330
|
-
* different versions of one service is a fact about the stack, not a
|
|
3331
|
-
* detail to resolve by ordering.
|
|
4436
|
+
* that disagrees with an earlier one refuses the plan; application ordering
|
|
4437
|
+
* must not silently choose the configuration of a shared service.
|
|
3332
4438
|
*/
|
|
3333
4439
|
async collectDeclaredServices(projectName, stackConfig, ecosystemConfig,
|
|
3334
4440
|
/** Keep the ones reached elsewhere too — to ask whether they answer, not to provision them. */
|
|
3335
4441
|
options = {}) {
|
|
3336
4442
|
const merged = {};
|
|
3337
|
-
const definitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
|
|
4443
|
+
const definitions = options.definitions ?? await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
|
|
3338
4444
|
for (const [appName, definition] of definitions) {
|
|
3339
4445
|
const declared = definition.omnitronConfig?.infrastructure;
|
|
3340
4446
|
if (!declared)
|
|
@@ -3348,7 +4454,8 @@ export class ProjectService extends EventEmitter {
|
|
|
3348
4454
|
if (provisioning === 'disabled' || (provisioning === 'external' && !options.external))
|
|
3349
4455
|
continue;
|
|
3350
4456
|
if (merged[name]) {
|
|
3351
|
-
|
|
4457
|
+
if (!isDeepStrictEqual(merged[name], requirement))
|
|
4458
|
+
throw new Error(`Conflicting infrastructure declarations for ${name}; use one shared specification or distinct service names`);
|
|
3352
4459
|
continue;
|
|
3353
4460
|
}
|
|
3354
4461
|
merged[name] = requirement;
|
|
@@ -3356,6 +4463,114 @@ export class ProjectService extends EventEmitter {
|
|
|
3356
4463
|
}
|
|
3357
4464
|
return merged;
|
|
3358
4465
|
}
|
|
4466
|
+
localSelection(project, stack) {
|
|
4467
|
+
const registration = this.registry.get(project);
|
|
4468
|
+
if (!registration)
|
|
4469
|
+
throw new Error('Project is not registered');
|
|
4470
|
+
return new LocalReleaseSelection(this.daemonStateStore, { project, stack }, registration.registeredAt);
|
|
4471
|
+
}
|
|
4472
|
+
async installedEntries(project, stack, definition, config, release, prepare = false, requiredMigrations, migrationFiles) {
|
|
4473
|
+
return (await this.installedRelease(project, stack, definition, config, release, prepare, requiredMigrations, migrationFiles)).entries;
|
|
4474
|
+
}
|
|
4475
|
+
async installedRelease(project, stack, definition, config, release, prepare = false, requiredMigrations, migrationFiles) {
|
|
4476
|
+
const root = this.registry.get(project)?.path;
|
|
4477
|
+
if (!root)
|
|
4478
|
+
throw new Error('Project is not registered');
|
|
4479
|
+
const { inspectLocalReleaseInstallation, inspectLocalReleasePayloads } = await import('../release/local-installation.js');
|
|
4480
|
+
const stackEntries = this.resolveStackApps(definition, config);
|
|
4481
|
+
// Validate the complete stack before narrowing executable verification to
|
|
4482
|
+
// migration owners. Other applications still own their declared environment.
|
|
4483
|
+
const env = await this.stackEnvFor(project, stack, definition, stackEntries.map(entry => entry.name));
|
|
4484
|
+
const configured = stackEntries.filter(entry => !requiredMigrations || requiredMigrations.includes(entry.name));
|
|
4485
|
+
const entries = configured.map(entry => ({
|
|
4486
|
+
...entry, env: { ...entry.env, ...env[entry.name] },
|
|
4487
|
+
...(!entry.releaseFrom || entry.cwd !== undefined ? { cwd: resolveAppPath(root, entry) } : {}),
|
|
4488
|
+
...(entry.bootstrap && { bootstrap: resolveAppPath(root, entry, entry.bootstrap) }),
|
|
4489
|
+
}));
|
|
4490
|
+
const ownerPid = this.orchestrator.getExecutionOwnerPid?.();
|
|
4491
|
+
if (!ownerPid)
|
|
4492
|
+
throw new Error('A verified local release requires a connected independent execution owner');
|
|
4493
|
+
const ownerPackage = await this.orchestrator.getExecutionOwnerPackage?.();
|
|
4494
|
+
if (!ownerPackage)
|
|
4495
|
+
throw new Error('Verified runtime origin requires an independently verified execution-owner package');
|
|
4496
|
+
const infrastructure = mergeInfrastructure(config, definition);
|
|
4497
|
+
const payloads = artifactVolumeEntries(infrastructure, config.apps);
|
|
4498
|
+
const roots = await inspectLocalReleasePayloads(release, payloads, prepare);
|
|
4499
|
+
const resolved = await bindArtifactVolumes(infrastructure, roots);
|
|
4500
|
+
const installed = await inspectLocalReleaseInstallation(release, stack, entries, { prepare, ownerPid, ownerPackage,
|
|
4501
|
+
...(requiredMigrations && { requiredFiles: migrationFiles ?? Object.fromEntries(requiredMigrations.map(app => [app, [COMPILED_MIGRATION_ENTRY]])) }),
|
|
4502
|
+
});
|
|
4503
|
+
const preparedArtifactVolumes = artifactVolumeBindings(infrastructure).map(([binding, value]) => {
|
|
4504
|
+
const service = resolved?.services?.[value.service];
|
|
4505
|
+
const mount = service && !('preset' in service) ? service.docker?.volumes?.[binding] : undefined;
|
|
4506
|
+
const artifact = release.manifest.artifacts.find(item => item.app === value.app);
|
|
4507
|
+
if (!mount || typeof mount === 'string' || mount.readonly !== true || !artifact)
|
|
4508
|
+
throw Error('Verified artifact volume result is unavailable');
|
|
4509
|
+
return { binding, ...value, source: mount.source, target: mount.target, readonly: true,
|
|
4510
|
+
releaseId: release.id, manifestSha256: release.manifestSha256, artifactSha256: artifact.sha256 };
|
|
4511
|
+
});
|
|
4512
|
+
return { entries: installed, infrastructure: resolved, preparedArtifactVolumes };
|
|
4513
|
+
}
|
|
4514
|
+
/** All consumers of executable declarations must see the same physical entry as the owner. */
|
|
4515
|
+
async launchEntries(project, stack, definition, config, requiredMigrations, migrationFiles) {
|
|
4516
|
+
const selected = definition.type === 'local' && !definition.managedOrigin ? this.localSelection(project, stack).read().release : null;
|
|
4517
|
+
if (!selected)
|
|
4518
|
+
return this.resolveStackApps(definition, config);
|
|
4519
|
+
const { loadRelease } = await import('../release/load.js');
|
|
4520
|
+
const release = await loadRelease(selected.id, await this.releaseStore());
|
|
4521
|
+
if (release.manifestSha256 !== selected.manifestSha256)
|
|
4522
|
+
throw new Error('Selected release manifest changed on disk');
|
|
4523
|
+
return this.installedEntries(project, stack, definition, config, release, false, requiredMigrations, migrationFiles);
|
|
4524
|
+
}
|
|
4525
|
+
async checkLocalOwner(project, stack, entries, changing, automatic = false) {
|
|
4526
|
+
const plan = await this.orchestrator.inspectStackExecution({ project, stack });
|
|
4527
|
+
await this.orchestrator.refreshObservation?.();
|
|
4528
|
+
if (plan.phase === 'stopping' || plan.phase === 'starting')
|
|
4529
|
+
throw new Error('Stack has an unfinished owner operation; inspect it before selecting a release');
|
|
4530
|
+
const expected = new Map(entries.map(entry => [ProjectService.handleKey(project, stack, entry.name), entry]));
|
|
4531
|
+
const retiring = plan.apps.filter(app => !expected.has(app.name));
|
|
4532
|
+
if (retiring.length) {
|
|
4533
|
+
if (this.orchestrator.getExecutionCapabilities?.()['stoppedStackReconciliation'] !== 1)
|
|
4534
|
+
throw new Error('Execution owner does not support stopped stack reconciliation; prepare an owner upgrade with an application restart plan before changing stack membership.');
|
|
4535
|
+
if (automatic)
|
|
4536
|
+
throw new Error('Automatic resume cannot remove retained applications; explicitly stop and start the selected stack to reconcile its membership');
|
|
4537
|
+
}
|
|
4538
|
+
for (const app of plan.apps) {
|
|
4539
|
+
const handle = this.orchestrator.getHandle(app.name);
|
|
4540
|
+
const entry = expected.get(app.name);
|
|
4541
|
+
if (!handle)
|
|
4542
|
+
throw new Error('Owner observation changed; inspect the local stack before selecting a release: ' + app.name);
|
|
4543
|
+
if (handle.status === 'stopped' && handle.pid === null)
|
|
4544
|
+
continue;
|
|
4545
|
+
if (retiring.length || changing || plan.phase === 'stopped' || !entry || !sameLaunchCode(handle.entry, entry))
|
|
4546
|
+
throw new LocalReleaseStopRequired('Stop the applications of this local stack before changing launch code or membership: ' + app.name);
|
|
4547
|
+
}
|
|
4548
|
+
return plan;
|
|
4549
|
+
}
|
|
4550
|
+
/** The owner's durable stop fence covers the gap between the SQLite commit and the complete graph reconciliation. */
|
|
4551
|
+
async prepareLocalOwner(project, stack, entries, changing, build, commit) {
|
|
4552
|
+
let plan = await this.checkLocalOwner(project, stack, entries, changing);
|
|
4553
|
+
const next = new Map(entries.map(entry => { const built = build(entry); return [built.name, built]; }));
|
|
4554
|
+
const retiring = plan.apps.some(app => !next.has(app.name));
|
|
4555
|
+
if ((changing || retiring) && plan.phase !== 'stopped') {
|
|
4556
|
+
await this.orchestrator.stopStackExecution({ operationId: randomUUID(), plan });
|
|
4557
|
+
plan = await this.orchestrator.inspectStackExecution({ project, stack });
|
|
4558
|
+
}
|
|
4559
|
+
commit();
|
|
4560
|
+
if (changing || plan.phase === 'stopped') {
|
|
4561
|
+
if (this.orchestrator.getExecutionCapabilities?.()['stoppedStackReconciliation'] === 1) {
|
|
4562
|
+
await this.orchestrator.reconcileStoppedStack({ operationId: randomUUID(), plan, entries: [...next.values()] });
|
|
4563
|
+
}
|
|
4564
|
+
else {
|
|
4565
|
+
// Existing owners can replace entries of an unchanged inventory only.
|
|
4566
|
+
// checkLocalOwner refuses membership changes before committing a selection.
|
|
4567
|
+
for (const { name } of plan.apps) {
|
|
4568
|
+
if (!await this.orchestrator.redefineApp(name, next.get(name)))
|
|
4569
|
+
throw new Error('Owner could not confirm the selected entry for ' + name + '; the stack remains fenced');
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
3359
4574
|
resolveStackApps(stackConfig, ecosystemConfig) {
|
|
3360
4575
|
const allApps = ecosystemConfig.apps.filter((a) => a.enabled !== false);
|
|
3361
4576
|
if (!stackConfig.apps || stackConfig.apps === 'all') {
|
|
@@ -3363,25 +4578,15 @@ export class ProjectService extends EventEmitter {
|
|
|
3363
4578
|
}
|
|
3364
4579
|
return allApps.filter((a) => Array.isArray(stackConfig.apps) ? stackConfig.apps.includes(a.name) : true);
|
|
3365
4580
|
}
|
|
4581
|
+
markPriorStopSuperseded(project, stack, expectedStopId) {
|
|
4582
|
+
this.daemonStateStore.kvUpdateSync(`stack:${project}/${stack}:last-stop`, prior => {
|
|
4583
|
+
if (!prior || (expectedStopId && prior.id !== expectedStopId))
|
|
4584
|
+
return prior;
|
|
4585
|
+
return { ...prior, supersededAt: new Date().toISOString() };
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
3366
4588
|
updateEnabledStacks(projectName, stackName, enabled) {
|
|
3367
|
-
|
|
3368
|
-
if (!project)
|
|
3369
|
-
return;
|
|
3370
|
-
const enabledStacks = new Set(project.enabledStacks ?? []);
|
|
3371
|
-
if (enabled) {
|
|
3372
|
-
enabledStacks.add(stackName);
|
|
3373
|
-
}
|
|
3374
|
-
else {
|
|
3375
|
-
enabledStacks.delete(stackName);
|
|
3376
|
-
}
|
|
3377
|
-
project.enabledStacks = [...enabledStacks];
|
|
3378
|
-
// Persist to disk via registry
|
|
3379
|
-
try {
|
|
3380
|
-
this.registry.persist();
|
|
3381
|
-
}
|
|
3382
|
-
catch {
|
|
3383
|
-
// Non-critical — runtime state is authoritative
|
|
3384
|
-
}
|
|
4589
|
+
this.registry.setStackEnabled(projectName, stackName, enabled);
|
|
3385
4590
|
}
|
|
3386
4591
|
toProjectInfo(project) {
|
|
3387
4592
|
const config = this.configRegistry.get(project.name)?.config;
|
|
@@ -3405,9 +4610,9 @@ export class ProjectService extends EventEmitter {
|
|
|
3405
4610
|
* node's own naming (`<project>/deployed/<app>`) — so every app came back
|
|
3406
4611
|
* `stopped` and the report was:
|
|
3407
4612
|
*
|
|
3408
|
-
* Stack
|
|
3409
|
-
* Not online: main (stopped), storage (stopped),
|
|
3410
|
-
*
|
|
4613
|
+
* Stack sample/test: only 0/6 apps came online.
|
|
4614
|
+
* Not online: main (stopped), storage (stopped), quotes (stopped),
|
|
4615
|
+
* payments (stopped), messaging (stopped), geo (stopped)
|
|
3411
4616
|
*
|
|
3412
4617
|
* measured against a node answering `appsTotal: 6, appsOnline: 6` with
|
|
3413
4618
|
* every port listening, thirty seconds after this same master installed,
|
|
@@ -3415,12 +4620,25 @@ export class ProjectService extends EventEmitter {
|
|
|
3415
4620
|
* deployment fails a script; and the console's stack page shows the same
|
|
3416
4621
|
* six rows, stopped.
|
|
3417
4622
|
*
|
|
3418
|
-
*
|
|
3419
|
-
*
|
|
3420
|
-
* local rows are at least honest about being local.
|
|
4623
|
+
* Each node retains its own observation. An unavailable source is unknown;
|
|
4624
|
+
* a replica on another node cannot turn that missing observation into online.
|
|
3421
4625
|
*/
|
|
4626
|
+
localHealthRead = null;
|
|
3422
4627
|
async withRemoteAppStatuses(projectName, info) {
|
|
3423
|
-
|
|
4628
|
+
if (info.type !== 'local')
|
|
4629
|
+
return (await this.askNodes(projectName, info)).info;
|
|
4630
|
+
// Concurrent inventory consumers share one owner read; no stale health cache
|
|
4631
|
+
// survives an application restart or silently becomes a green default.
|
|
4632
|
+
const read = this.localHealthRead ??= Promise.resolve().then(() => this.orchestrator.getHealth())
|
|
4633
|
+
.catch(() => null).finally(() => { if (this.localHealthRead === read)
|
|
4634
|
+
this.localHealthRead = null; });
|
|
4635
|
+
const health = await read;
|
|
4636
|
+
// Re-read lifecycle after the asynchronous health observation.
|
|
4637
|
+
const current = this.getStack(projectName, info.name);
|
|
4638
|
+
const unchanged = new Set(info.apps.filter(a => current.apps.some(b => b.handleKey === a.handleKey &&
|
|
4639
|
+
b.pid === a.pid && b.status === a.status && b.restarts === a.restarts)).map(a => a.handleKey));
|
|
4640
|
+
const bound = health && Object.fromEntries(Object.entries(health).filter(([name]) => unchanged.has(name)));
|
|
4641
|
+
return observeStackHealth(current, new Map([['local', bound]]), new Date().toISOString());
|
|
3424
4642
|
}
|
|
3425
4643
|
/**
|
|
3426
4644
|
* The same question, with the count of nodes that answered it.
|
|
@@ -3434,85 +4652,46 @@ export class ProjectService extends EventEmitter {
|
|
|
3434
4652
|
async askNodes(projectName, info) {
|
|
3435
4653
|
if (info.type !== 'remote' && info.type !== 'cluster')
|
|
3436
4654
|
return { info, answered: 0 };
|
|
3437
|
-
const nodes = info.config.nodes ?? [];
|
|
3438
|
-
if
|
|
3439
|
-
|
|
3440
|
-
const
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
answered += 1;
|
|
3447
|
-
// The containers are on the node too — `infraManager.getInstance`
|
|
3448
|
-
// answers about THIS machine, where a remote stack has none, so the
|
|
3449
|
-
// stack read "Infrastructure: not provisioned" beside a node running
|
|
3450
|
-
// postgres, redis, minio, the gateway and tor.
|
|
3451
|
-
remoteInfra ??= await this.remoteInfraStatus(projectName, info.name, node);
|
|
3452
|
-
for (const app of status?.apps ?? []) {
|
|
3453
|
-
// The node names them `<project>/deployed/<app>`; the stack knows
|
|
3454
|
-
// them by the app's own name.
|
|
3455
|
-
const bare = app.name.includes('/') ? app.name.slice(app.name.lastIndexOf('/') + 1) : app.name;
|
|
3456
|
-
reported.set(bare, app);
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
catch (err) {
|
|
3460
|
-
this.logger.warn({ project: projectName, stack: info.name, node: node.host, error: err.message }, 'Could not ask this node what it is running — its apps are reported as this master sees them');
|
|
3461
|
-
}
|
|
3462
|
-
}
|
|
3463
|
-
if (answered === 0)
|
|
3464
|
-
return { info, answered };
|
|
3465
|
-
const apps = info.apps.map((app) => {
|
|
3466
|
-
const running = reported.get(app.name);
|
|
3467
|
-
if (!running)
|
|
3468
|
-
return app;
|
|
4655
|
+
const nodes = [...(info.config.nodes ?? [])];
|
|
4656
|
+
// Keep the last actual runtime footprint visible if its source configuration changes.
|
|
4657
|
+
// Unadmitted target plans are never evidence that a workload ran on a node.
|
|
4658
|
+
for (const old of this.stackStates.get(`${projectName}/${info.name}`)?.config.nodes ?? [])
|
|
4659
|
+
if (!nodes.some(node => node.host === old.host && (node.port ?? 9700) === (old.port ?? 9700)))
|
|
4660
|
+
nodes.push({ ...old, apps: [] });
|
|
4661
|
+
if (nodes.length === 0)
|
|
4662
|
+
return { info: observeStackHealth(info, new Map(), new Date().toISOString()), answered: 0 };
|
|
4663
|
+
if (!this.slaveConnector)
|
|
3469
4664
|
return {
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
status: running.status,
|
|
3473
|
-
pid: running.pid,
|
|
3474
|
-
instances: running.instances,
|
|
3475
|
-
uptime: running.uptime,
|
|
3476
|
-
restarts: running.restarts,
|
|
3477
|
-
cpu: running.cpu,
|
|
3478
|
-
memory: running.memory,
|
|
3479
|
-
port: running.port ?? null,
|
|
4665
|
+
info: observeStackHealth(observeStack(info, nodes.map(node => nodePlacement(projectName, info, node, null, null, new Date().toISOString()))), new Map(), new Date().toISOString()),
|
|
4666
|
+
answered: 0,
|
|
3480
4667
|
};
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
//
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
status,
|
|
3501
|
-
infrastructure: remoteInfra ?? info.infrastructure,
|
|
3502
|
-
apps,
|
|
3503
|
-
startedAt: longest > 0 ? new Date(Date.now() - longest).toISOString() : null,
|
|
3504
|
-
uptime: longest,
|
|
3505
|
-
},
|
|
3506
|
-
answered,
|
|
3507
|
-
};
|
|
4668
|
+
const health = new Map();
|
|
4669
|
+
const placements = [];
|
|
4670
|
+
// Bounded fan-out: one failed peer must not hide the other peers' inventory.
|
|
4671
|
+
for (let offset = 0; offset < nodes.length; offset += 4) {
|
|
4672
|
+
placements.push(...await Promise.all(nodes.slice(offset, offset + 4).map(async (node) => {
|
|
4673
|
+
const [processAnswer, infrastructureAnswer, healthAnswer] = await Promise.allSettled([
|
|
4674
|
+
this.slaveConnector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronDaemon', 'status', []),
|
|
4675
|
+
this.remoteInfraStatus(projectName, info.name, node),
|
|
4676
|
+
this.slaveConnector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronDaemon', 'getHealth', [{}]),
|
|
4677
|
+
]);
|
|
4678
|
+
const status = processAnswer.status === 'fulfilled'
|
|
4679
|
+
? processAnswer.value : null;
|
|
4680
|
+
const processes = status && Array.isArray(status.apps) ? status.apps : null;
|
|
4681
|
+
const reading = healthAnswer.status === 'fulfilled' ? healthAnswer.value : null;
|
|
4682
|
+
health.set(stackNodeKey(node.host, node.port), reading?.apps ?? null);
|
|
4683
|
+
return nodePlacement(projectName, info, node, processes, infrastructureAnswer.status === 'fulfilled' ? infrastructureAnswer.value : null, new Date().toISOString());
|
|
4684
|
+
})));
|
|
4685
|
+
}
|
|
4686
|
+
return { info: observeStackHealth(observeStack(info, placements), health, new Date().toISOString()), answered: placements.filter(node => node.appsObserved).length };
|
|
3508
4687
|
}
|
|
3509
4688
|
/**
|
|
3510
4689
|
* One node's infrastructure, named the way the stack names it.
|
|
3511
4690
|
*
|
|
3512
|
-
* The node's container names carry ITS prefix (`
|
|
4691
|
+
* The node's container names carry ITS prefix (`sample-test-postgres`), and
|
|
3513
4692
|
* a reader wants the service — `postgres` — so the same stripping the
|
|
3514
|
-
* local path does is done here. `null`
|
|
3515
|
-
*
|
|
4693
|
+
* local path does is done here. `null` means the node could not answer;
|
|
4694
|
+
* an empty inventory is an observed absence, never this master's inventory.
|
|
3516
4695
|
*/
|
|
3517
4696
|
async remoteInfraStatus(projectName, stackName, node) {
|
|
3518
4697
|
if (!this.slaveConnector)
|
|
@@ -3529,25 +4708,21 @@ export class ProjectService extends EventEmitter {
|
|
|
3529
4708
|
const entries = state?.services
|
|
3530
4709
|
? Object.entries(state.services)
|
|
3531
4710
|
: ((await this.slaveConnector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronInfra', 'listContainers', [])) ?? [])
|
|
3532
|
-
.filter((c) => c.name.startsWith(prefix))
|
|
3533
4711
|
.map((c) => [c.name, c]);
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
4712
|
+
const owned = entries.filter(([, service]) => service.project || service.stack
|
|
4713
|
+
? service.project === projectName && service.stack === stackName
|
|
4714
|
+
: service.name.startsWith(prefix));
|
|
4715
|
+
for (const [containerName, svc] of owned) {
|
|
3537
4716
|
const name = containerName.startsWith(prefix) ? containerName.slice(prefix.length) : containerName;
|
|
3538
|
-
services[name] = {
|
|
3539
|
-
status: svc.status === 'running' ? 'running' : svc.error ? 'error' : 'stopped',
|
|
3540
|
-
containerName: svc.name,
|
|
3541
|
-
port: svc.ports ? Object.values(svc.ports)[0] ?? null : null,
|
|
3542
|
-
};
|
|
4717
|
+
services[name] = stackServiceObservation({ ...svc, service: svc.service ?? name });
|
|
3543
4718
|
}
|
|
3544
4719
|
// Without the node's own verdict, "ready" is what the containers say:
|
|
3545
4720
|
// every one of them running.
|
|
3546
|
-
const ready =
|
|
4721
|
+
const ready = Object.values(services).every(svc => svc.status === 'running' && svc.health !== 'unhealthy' && svc.health !== 'starting' && !svc.error && (!svc.provisioning || svc.provisioning === 'ready'));
|
|
3547
4722
|
return { ready, services };
|
|
3548
4723
|
}
|
|
3549
4724
|
catch (err) {
|
|
3550
|
-
this.logger.warn({ project: projectName, stack: stackName, node: node.host, error: err.message }, '
|
|
4725
|
+
this.logger.warn({ project: projectName, stack: stackName, node: node.host, error: err.message }, 'Node infrastructure inventory unavailable; placement state is unknown');
|
|
3551
4726
|
return null;
|
|
3552
4727
|
}
|
|
3553
4728
|
}
|
|
@@ -3558,29 +4733,16 @@ export class ProjectService extends EventEmitter {
|
|
|
3558
4733
|
// Build app list from config — include both running and stopped apps.
|
|
3559
4734
|
const ecosystemConfig = this.getLoadedConfig(projectName);
|
|
3560
4735
|
const configuredApps = ecosystemConfig ? this.resolveStackApps(config, ecosystemConfig) : [];
|
|
3561
|
-
|
|
3562
|
-
//
|
|
3563
|
-
//
|
|
3564
|
-
// map: the namespaced stack-mode path (`omni/dev/main`) and the
|
|
3565
|
-
// bare-name daemon-RPC path (`main`). Pre-fix the UI only saw the
|
|
3566
|
-
// first; a CLI `omnitron start main` (or any ecosystem.config entry
|
|
3567
|
-
// started before the stack-mode env vars were known) registered
|
|
3568
|
-
// bare and silently disappeared from the project/stack view, even
|
|
3569
|
-
// though `omnitron list` happily reported it online. By keying off
|
|
3570
|
-
// `effectiveAppName` we accept either form so the UI matches the
|
|
3571
|
-
// CLI's worldview. Cross-stack collisions on the same effective
|
|
3572
|
-
// name still resolve via the namespaced handle's prefix; the bare
|
|
3573
|
-
// fallback only applies when that name appears in this stack's
|
|
3574
|
-
// configured-app set, so an unrelated bare handle in another stack
|
|
3575
|
-
// never bleeds in.
|
|
4736
|
+
// A bare handle carries no project/stack ownership. Assigning it by basename
|
|
4737
|
+
// admitted the same process into every project that configured "api". Only an
|
|
4738
|
+
// exact namespace proves ownership; ad-hoc bare processes remain visible in list.
|
|
3576
4739
|
const runningByName = new Map();
|
|
3577
4740
|
for (const a of this.orchestrator.list()) {
|
|
3578
4741
|
const effective = effectiveAppName(a.name);
|
|
3579
4742
|
const inThisPrefix = a.name.startsWith(prefix);
|
|
3580
|
-
|
|
3581
|
-
if (!inThisPrefix && !inThisStackByName)
|
|
4743
|
+
if (!inThisPrefix)
|
|
3582
4744
|
continue;
|
|
3583
|
-
//
|
|
4745
|
+
// An exact qualified identity belongs to this stack only.
|
|
3584
4746
|
const existing = runningByName.get(effective);
|
|
3585
4747
|
if (!existing || a.name.startsWith(prefix)) {
|
|
3586
4748
|
runningByName.set(effective, a);
|
|
@@ -3601,6 +4763,8 @@ export class ProjectService extends EventEmitter {
|
|
|
3601
4763
|
cpu: running.cpu,
|
|
3602
4764
|
memory: running.memory,
|
|
3603
4765
|
port: running.port ?? null,
|
|
4766
|
+
...(running.generation !== undefined && { generation: running.generation }),
|
|
4767
|
+
...(running.release && { release: running.release }),
|
|
3604
4768
|
};
|
|
3605
4769
|
}
|
|
3606
4770
|
// App is configured but not running
|
|
@@ -3630,6 +4794,8 @@ export class ProjectService extends EventEmitter {
|
|
|
3630
4794
|
cpu: running.cpu,
|
|
3631
4795
|
memory: running.memory,
|
|
3632
4796
|
port: running.port ?? null,
|
|
4797
|
+
...(running.generation !== undefined && { generation: running.generation }),
|
|
4798
|
+
...(running.release && { release: running.release }),
|
|
3633
4799
|
});
|
|
3634
4800
|
}
|
|
3635
4801
|
// Populate real node connectivity from SlaveConnector
|
|
@@ -3674,11 +4840,7 @@ export class ProjectService extends EventEmitter {
|
|
|
3674
4840
|
const serviceName = containerName.startsWith(stackPrefix)
|
|
3675
4841
|
? containerName.slice(stackPrefix.length)
|
|
3676
4842
|
: containerName;
|
|
3677
|
-
infraServices[serviceName] =
|
|
3678
|
-
status: svcState.status === 'running' ? 'running' : svcState.error ? 'error' : 'stopped',
|
|
3679
|
-
containerName: svcState.name,
|
|
3680
|
-
port: svcState.ports ? Object.values(svcState.ports)[0] ?? null : null,
|
|
3681
|
-
};
|
|
4843
|
+
infraServices[serviceName] = stackServiceObservation(svcState);
|
|
3682
4844
|
}
|
|
3683
4845
|
}
|
|
3684
4846
|
const infrastructure = {
|
|
@@ -3694,10 +4856,35 @@ export class ProjectService extends EventEmitter {
|
|
|
3694
4856
|
// restart). It is reported as what it is.
|
|
3695
4857
|
const onNodes = config.type === 'remote' || config.type === 'cluster';
|
|
3696
4858
|
const since = state?.startedAt ?? null;
|
|
4859
|
+
let status = state?.status ?? 'stopped';
|
|
4860
|
+
const notUp = [];
|
|
4861
|
+
if (!onNodes && (status === 'running' || (since !== null && (status === 'degraded' || status === 'error')))) {
|
|
4862
|
+
const down = apps.filter(app => app.status !== 'online');
|
|
4863
|
+
for (const app of down) {
|
|
4864
|
+
const errors = state?.appStartErrors;
|
|
4865
|
+
const reason = errors && Object.hasOwn(errors, app.name) ? errors[app.name] : undefined;
|
|
4866
|
+
notUp.push(`${app.name} (${app.status})${reason ? ': ' + reason : ''}`);
|
|
4867
|
+
}
|
|
4868
|
+
if (!infrastructure.ready && (state?.infrastructureStartFailed || state?.infraService?.getDesiredServices().length)) {
|
|
4869
|
+
notUp.push('infrastructure: required services are not ready (see daemon log)');
|
|
4870
|
+
}
|
|
4871
|
+
if (notUp.length === 0)
|
|
4872
|
+
status = 'running';
|
|
4873
|
+
else if (down.length < apps.length || down.some(app => app.status === 'starting') ||
|
|
4874
|
+
(apps.length === 0 && Object.values(infrastructure.services).some(service => service.status === 'running')))
|
|
4875
|
+
status = 'degraded';
|
|
4876
|
+
else
|
|
4877
|
+
status = 'error';
|
|
4878
|
+
}
|
|
4879
|
+
const selectedRelease = config.type === 'local' && !config.managedOrigin ? this.localSelection(projectName, stackName).read().release : null;
|
|
3697
4880
|
return {
|
|
3698
4881
|
name: stackName,
|
|
3699
4882
|
type: config.type,
|
|
3700
|
-
|
|
4883
|
+
...(selectedRelease && { selectedRelease }),
|
|
4884
|
+
status,
|
|
4885
|
+
...(notUp.length > 0 && { notUp }),
|
|
4886
|
+
resumeOperation: this.daemonStateStore.kvGetSync(`stack:${stateKey}:last-resume`, { strict: true }) ?? undefined,
|
|
4887
|
+
stopOperation: this.daemonStateStore.kvGetSync(`stack:${projectName}/${stackName}:last-stop`, { strict: true }) ?? undefined,
|
|
3701
4888
|
config,
|
|
3702
4889
|
nodes,
|
|
3703
4890
|
apps,
|
|
@@ -3709,6 +4896,138 @@ export class ProjectService extends EventEmitter {
|
|
|
3709
4896
|
};
|
|
3710
4897
|
}
|
|
3711
4898
|
}
|
|
4899
|
+
__decorate([
|
|
4900
|
+
RecoveryMutation(),
|
|
4901
|
+
__metadata("design:type", Function),
|
|
4902
|
+
__metadata("design:paramtypes", [Object]),
|
|
4903
|
+
__metadata("design:returntype", void 0)
|
|
4904
|
+
], ProjectService.prototype, "executeNodeInstalledResume", null);
|
|
4905
|
+
__decorate([
|
|
4906
|
+
RecoveryMutation(),
|
|
4907
|
+
__metadata("design:type", Function),
|
|
4908
|
+
__metadata("design:paramtypes", [String, String]),
|
|
4909
|
+
__metadata("design:returntype", Promise)
|
|
4910
|
+
], ProjectService.prototype, "resumeInstalledStack", null);
|
|
4911
|
+
__decorate([
|
|
4912
|
+
RecoveryMutation(),
|
|
4913
|
+
__metadata("design:type", Function),
|
|
4914
|
+
__metadata("design:paramtypes", [Object]),
|
|
4915
|
+
__metadata("design:returntype", Promise)
|
|
4916
|
+
], ProjectService.prototype, "prepareNodeStackStop", null);
|
|
4917
|
+
__decorate([
|
|
4918
|
+
RecoveryMutation(),
|
|
4919
|
+
__metadata("design:type", Function),
|
|
4920
|
+
__metadata("design:paramtypes", [Object]),
|
|
4921
|
+
__metadata("design:returntype", Promise)
|
|
4922
|
+
], ProjectService.prototype, "executeNodeStackStop", null);
|
|
4923
|
+
__decorate([
|
|
4924
|
+
RecoveryMutation(),
|
|
4925
|
+
__metadata("design:type", Function),
|
|
4926
|
+
__metadata("design:paramtypes", [Object]),
|
|
4927
|
+
__metadata("design:returntype", Promise)
|
|
4928
|
+
], ProjectService.prototype, "importProjectConfiguration", null);
|
|
4929
|
+
__decorate([
|
|
4930
|
+
RecoveryMutation(),
|
|
4931
|
+
__metadata("design:type", Function),
|
|
4932
|
+
__metadata("design:paramtypes", [Object]),
|
|
4933
|
+
__metadata("design:returntype", Promise)
|
|
4934
|
+
], ProjectService.prototype, "detachProjectConfiguration", null);
|
|
4935
|
+
__decorate([
|
|
4936
|
+
RecoveryMutation(),
|
|
4937
|
+
__metadata("design:type", Function),
|
|
4938
|
+
__metadata("design:paramtypes", [String, String]),
|
|
4939
|
+
__metadata("design:returntype", Promise)
|
|
4940
|
+
], ProjectService.prototype, "addProject", null);
|
|
4941
|
+
__decorate([
|
|
4942
|
+
RecoveryMutation(),
|
|
4943
|
+
__metadata("design:type", Function),
|
|
4944
|
+
__metadata("design:paramtypes", [String]),
|
|
4945
|
+
__metadata("design:returntype", Promise)
|
|
4946
|
+
], ProjectService.prototype, "rereadProject", null);
|
|
4947
|
+
__decorate([
|
|
4948
|
+
RecoveryMutation(),
|
|
4949
|
+
__metadata("design:type", Function),
|
|
4950
|
+
__metadata("design:paramtypes", [String, String]),
|
|
4951
|
+
__metadata("design:returntype", Promise)
|
|
4952
|
+
], ProjectService.prototype, "refreshInfrastructureOwnership", null);
|
|
4953
|
+
__decorate([
|
|
4954
|
+
RecoveryMutation(),
|
|
4955
|
+
__metadata("design:type", Function),
|
|
4956
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
4957
|
+
__metadata("design:returntype", Object)
|
|
4958
|
+
], ProjectService.prototype, "updateProject", null);
|
|
4959
|
+
__decorate([
|
|
4960
|
+
RecoveryMutation(),
|
|
4961
|
+
__metadata("design:type", Function),
|
|
4962
|
+
__metadata("design:paramtypes", [String]),
|
|
4963
|
+
__metadata("design:returntype", void 0)
|
|
4964
|
+
], ProjectService.prototype, "removeProject", null);
|
|
4965
|
+
__decorate([
|
|
4966
|
+
RecoveryMutation(),
|
|
4967
|
+
__metadata("design:type", Function),
|
|
4968
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
4969
|
+
__metadata("design:returntype", Promise)
|
|
4970
|
+
], ProjectService.prototype, "createStack", null);
|
|
4971
|
+
__decorate([
|
|
4972
|
+
RecoveryMutation(),
|
|
4973
|
+
__metadata("design:type", Function),
|
|
4974
|
+
__metadata("design:paramtypes", [String, String]),
|
|
4975
|
+
__metadata("design:returntype", Promise)
|
|
4976
|
+
], ProjectService.prototype, "deleteStack", null);
|
|
4977
|
+
__decorate([
|
|
4978
|
+
RecoveryMutation(),
|
|
4979
|
+
__metadata("design:type", Function),
|
|
4980
|
+
__metadata("design:paramtypes", [Object]),
|
|
4981
|
+
__metadata("design:returntype", Promise)
|
|
4982
|
+
], ProjectService.prototype, "saveTopologyDraft", null);
|
|
4983
|
+
__decorate([
|
|
4984
|
+
RecoveryMutation(),
|
|
4985
|
+
__metadata("design:type", Function),
|
|
4986
|
+
__metadata("design:paramtypes", [Object]),
|
|
4987
|
+
__metadata("design:returntype", Promise)
|
|
4988
|
+
], ProjectService.prototype, "planTopology", null);
|
|
4989
|
+
__decorate([
|
|
4990
|
+
RecoveryMutation(),
|
|
4991
|
+
__metadata("design:type", Function),
|
|
4992
|
+
__metadata("design:paramtypes", [Object]),
|
|
4993
|
+
__metadata("design:returntype", Promise)
|
|
4994
|
+
], ProjectService.prototype, "applyTopologyPlan", null);
|
|
4995
|
+
__decorate([
|
|
4996
|
+
RecoveryMutation(),
|
|
4997
|
+
__metadata("design:type", Function),
|
|
4998
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
4999
|
+
__metadata("design:returntype", Promise)
|
|
5000
|
+
], ProjectService.prototype, "refreshTopologyMetadata", null);
|
|
5001
|
+
__decorate([
|
|
5002
|
+
RecoveryMutation(),
|
|
5003
|
+
__metadata("design:type", Function),
|
|
5004
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
5005
|
+
__metadata("design:returntype", Promise)
|
|
5006
|
+
], ProjectService.prototype, "startStack", null);
|
|
5007
|
+
__decorate([
|
|
5008
|
+
RecoveryMutation(),
|
|
5009
|
+
__metadata("design:type", Function),
|
|
5010
|
+
__metadata("design:paramtypes", [String, String, String, Object]),
|
|
5011
|
+
__metadata("design:returntype", Promise)
|
|
5012
|
+
], ProjectService.prototype, "stopStack", null);
|
|
5013
|
+
__decorate([
|
|
5014
|
+
RecoveryMutation(),
|
|
5015
|
+
__metadata("design:type", Function),
|
|
5016
|
+
__metadata("design:paramtypes", []),
|
|
5017
|
+
__metadata("design:returntype", Promise)
|
|
5018
|
+
], ProjectService.prototype, "reconcileEnabledStacks", null);
|
|
5019
|
+
__decorate([
|
|
5020
|
+
RecoveryMutation(),
|
|
5021
|
+
__metadata("design:type", Function),
|
|
5022
|
+
__metadata("design:paramtypes", [String]),
|
|
5023
|
+
__metadata("design:returntype", Promise)
|
|
5024
|
+
], ProjectService.prototype, "loadProjectConfig", null);
|
|
5025
|
+
__decorate([
|
|
5026
|
+
RecoveryMutation(),
|
|
5027
|
+
__metadata("design:type", Function),
|
|
5028
|
+
__metadata("design:paramtypes", [String]),
|
|
5029
|
+
__metadata("design:returntype", Promise)
|
|
5030
|
+
], ProjectService.prototype, "reloadConfig", null);
|
|
3712
5031
|
/**
|
|
3713
5032
|
* How a master's daemon builds this service: the tokens it resolves, in
|
|
3714
5033
|
* order, and the factory that receives them.
|
|
@@ -3730,4 +5049,9 @@ export const MASTER_PROJECT_SERVICE_PROVIDER = {
|
|
|
3730
5049
|
// RPC layer that used to record it.
|
|
3731
5050
|
new ProjectService(loggerModule.logger, orchestrator, daemonStateStore, { secrets, audit }),
|
|
3732
5051
|
};
|
|
5052
|
+
/** A slave has its own encrypted vault even though it has no master database/audit service. */
|
|
5053
|
+
export const SLAVE_PROJECT_SERVICE_PROVIDER = {
|
|
5054
|
+
inject: [LOGGER_SERVICE_TOKEN, ORCHESTRATOR_TOKEN, DAEMON_STATE_STORE_TOKEN, SECRETS_SERVICE_TOKEN],
|
|
5055
|
+
useFactory: (loggerModule, orchestrator, daemonStateStore, secrets) => new ProjectService(loggerModule.logger, orchestrator, daemonStateStore, { secrets }),
|
|
5056
|
+
};
|
|
3733
5057
|
//# sourceMappingURL=project.service.js.map
|