@omnitron-dev/omnitron 0.3.3 → 0.4.6

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