@omnitron-dev/omnitron 0.3.1 → 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.
Files changed (1687) hide show
  1. package/README.md +575 -0
  2. package/data/operations/managed-kernel.mjs +302 -0
  3. package/data/operations/stage-kernel.mjs +125 -0
  4. package/dist/BUILD.json +4 -3
  5. package/dist/cli/exit-after-output.d.ts +3 -0
  6. package/dist/cli/exit-after-output.d.ts.map +1 -0
  7. package/dist/cli/exit-after-output.js +11 -0
  8. package/dist/cli/exit-after-output.js.map +1 -0
  9. package/dist/cli/installed.d.ts +3 -0
  10. package/dist/cli/installed.d.ts.map +1 -0
  11. package/dist/cli/installed.js +17 -0
  12. package/dist/cli/installed.js.map +1 -0
  13. package/dist/cli/omnitron.js +432 -100
  14. package/dist/cli/omnitron.js.map +1 -1
  15. package/dist/cli/output-command.d.ts +13 -0
  16. package/dist/cli/output-command.d.ts.map +1 -0
  17. package/dist/cli/output-command.js +48 -0
  18. package/dist/cli/output-command.js.map +1 -0
  19. package/dist/cluster/cluster.rpc-service.d.ts +9 -77
  20. package/dist/cluster/cluster.rpc-service.d.ts.map +1 -1
  21. package/dist/cluster/cluster.rpc-service.js +20 -107
  22. package/dist/cluster/cluster.rpc-service.js.map +1 -1
  23. package/dist/cluster/enrollment.d.ts +9 -0
  24. package/dist/cluster/enrollment.d.ts.map +1 -0
  25. package/dist/cluster/enrollment.js +49 -0
  26. package/dist/cluster/enrollment.js.map +1 -0
  27. package/dist/cluster/index.d.ts +1 -1
  28. package/dist/cluster/index.d.ts.map +1 -1
  29. package/dist/cluster/leader-election.d.ts +39 -211
  30. package/dist/cluster/leader-election.d.ts.map +1 -1
  31. package/dist/cluster/leader-election.js +235 -512
  32. package/dist/cluster/leader-election.js.map +1 -1
  33. package/dist/cluster/mesh-link.d.ts +18 -51
  34. package/dist/cluster/mesh-link.d.ts.map +1 -1
  35. package/dist/cluster/mesh-link.js +65 -77
  36. package/dist/cluster/mesh-link.js.map +1 -1
  37. package/dist/cluster/mesh.d.ts +3 -1
  38. package/dist/cluster/mesh.d.ts.map +1 -1
  39. package/dist/cluster/mesh.js +76 -60
  40. package/dist/cluster/mesh.js.map +1 -1
  41. package/dist/cluster/slave-connector.d.ts +40 -4
  42. package/dist/cluster/slave-connector.d.ts.map +1 -1
  43. package/dist/cluster/slave-connector.js +348 -195
  44. package/dist/cluster/slave-connector.js.map +1 -1
  45. package/dist/cluster/standalone-infrastructure.d.ts +3 -0
  46. package/dist/cluster/standalone-infrastructure.d.ts.map +1 -0
  47. package/dist/cluster/standalone-infrastructure.js +25 -0
  48. package/dist/cluster/standalone-infrastructure.js.map +1 -0
  49. package/dist/cluster/stop-authority.d.ts +45 -0
  50. package/dist/cluster/stop-authority.d.ts.map +1 -0
  51. package/dist/cluster/stop-authority.js +112 -0
  52. package/dist/cluster/stop-authority.js.map +1 -0
  53. package/dist/commands/access.d.ts +9 -0
  54. package/dist/commands/access.d.ts.map +1 -0
  55. package/dist/commands/access.js +45 -0
  56. package/dist/commands/access.js.map +1 -0
  57. package/dist/commands/audit.d.ts +2 -0
  58. package/dist/commands/audit.d.ts.map +1 -1
  59. package/dist/commands/audit.js +9 -4
  60. package/dist/commands/audit.js.map +1 -1
  61. package/dist/commands/backend.d.ts +17 -0
  62. package/dist/commands/backend.d.ts.map +1 -0
  63. package/dist/commands/backend.js +264 -0
  64. package/dist/commands/backend.js.map +1 -0
  65. package/dist/commands/backup.d.ts +5 -0
  66. package/dist/commands/backup.d.ts.map +1 -1
  67. package/dist/commands/backup.js +17 -2
  68. package/dist/commands/backup.js.map +1 -1
  69. package/dist/commands/cluster.d.ts.map +1 -1
  70. package/dist/commands/cluster.js +33 -42
  71. package/dist/commands/cluster.js.map +1 -1
  72. package/dist/commands/daemon-cmd.d.ts.map +1 -1
  73. package/dist/commands/daemon-cmd.js +14 -3
  74. package/dist/commands/daemon-cmd.js.map +1 -1
  75. package/dist/commands/doctor.d.ts +11 -29
  76. package/dist/commands/doctor.d.ts.map +1 -1
  77. package/dist/commands/doctor.js +47 -45
  78. package/dist/commands/doctor.js.map +1 -1
  79. package/dist/commands/files.d.ts +19 -0
  80. package/dist/commands/files.d.ts.map +1 -0
  81. package/dist/commands/files.js +226 -0
  82. package/dist/commands/files.js.map +1 -0
  83. package/dist/commands/fleet-rollout.d.ts +25 -0
  84. package/dist/commands/fleet-rollout.d.ts.map +1 -0
  85. package/dist/commands/fleet-rollout.js +155 -0
  86. package/dist/commands/fleet-rollout.js.map +1 -0
  87. package/dist/commands/fleet.d.ts.map +1 -1
  88. package/dist/commands/fleet.js +1 -0
  89. package/dist/commands/fleet.js.map +1 -1
  90. package/dist/commands/health-check.d.ts.map +1 -1
  91. package/dist/commands/health-check.js +13 -6
  92. package/dist/commands/health-check.js.map +1 -1
  93. package/dist/commands/infra.d.ts +5 -2
  94. package/dist/commands/infra.d.ts.map +1 -1
  95. package/dist/commands/infra.js +145 -53
  96. package/dist/commands/infra.js.map +1 -1
  97. package/dist/commands/logs.d.ts +6 -23
  98. package/dist/commands/logs.d.ts.map +1 -1
  99. package/dist/commands/logs.js +114 -38
  100. package/dist/commands/logs.js.map +1 -1
  101. package/dist/commands/metrics.d.ts.map +1 -1
  102. package/dist/commands/metrics.js +7 -1
  103. package/dist/commands/metrics.js.map +1 -1
  104. package/dist/commands/migrate.d.ts +8 -0
  105. package/dist/commands/migrate.d.ts.map +1 -0
  106. package/dist/commands/migrate.js +64 -0
  107. package/dist/commands/migrate.js.map +1 -0
  108. package/dist/commands/node-reconcile.d.ts +8 -0
  109. package/dist/commands/node-reconcile.d.ts.map +1 -0
  110. package/dist/commands/node-reconcile.js +49 -0
  111. package/dist/commands/node-reconcile.js.map +1 -0
  112. package/dist/commands/node.d.ts +0 -8
  113. package/dist/commands/node.d.ts.map +1 -1
  114. package/dist/commands/node.js +32 -35
  115. package/dist/commands/node.js.map +1 -1
  116. package/dist/commands/output.d.ts +3 -9
  117. package/dist/commands/output.d.ts.map +1 -1
  118. package/dist/commands/output.js +34 -25
  119. package/dist/commands/output.js.map +1 -1
  120. package/dist/commands/pipeline.d.ts +11 -2
  121. package/dist/commands/pipeline.d.ts.map +1 -1
  122. package/dist/commands/pipeline.js +75 -15
  123. package/dist/commands/pipeline.js.map +1 -1
  124. package/dist/commands/project.d.ts +8 -0
  125. package/dist/commands/project.d.ts.map +1 -1
  126. package/dist/commands/project.js +67 -1
  127. package/dist/commands/project.js.map +1 -1
  128. package/dist/commands/release-store.js +1 -1
  129. package/dist/commands/release-store.js.map +1 -1
  130. package/dist/commands/release.d.ts +5 -12
  131. package/dist/commands/release.d.ts.map +1 -1
  132. package/dist/commands/release.js +141 -33
  133. package/dist/commands/release.js.map +1 -1
  134. package/dist/commands/scale.d.ts +1 -4
  135. package/dist/commands/scale.d.ts.map +1 -1
  136. package/dist/commands/scale.js +39 -19
  137. package/dist/commands/scale.js.map +1 -1
  138. package/dist/commands/secret-input.d.ts +3 -0
  139. package/dist/commands/secret-input.d.ts.map +1 -0
  140. package/dist/commands/secret-input.js +36 -0
  141. package/dist/commands/secret-input.js.map +1 -0
  142. package/dist/commands/secret.d.ts +4 -2
  143. package/dist/commands/secret.d.ts.map +1 -1
  144. package/dist/commands/secret.js +12 -2
  145. package/dist/commands/secret.js.map +1 -1
  146. package/dist/commands/service-resident.d.ts +25 -0
  147. package/dist/commands/service-resident.d.ts.map +1 -0
  148. package/dist/commands/service-resident.js +236 -0
  149. package/dist/commands/service-resident.js.map +1 -0
  150. package/dist/commands/service-units.d.ts +13 -1
  151. package/dist/commands/service-units.d.ts.map +1 -1
  152. package/dist/commands/service-units.js +38 -16
  153. package/dist/commands/service-units.js.map +1 -1
  154. package/dist/commands/service.d.ts +2 -24
  155. package/dist/commands/service.d.ts.map +1 -1
  156. package/dist/commands/service.js +89 -7
  157. package/dist/commands/service.js.map +1 -1
  158. package/dist/commands/stack.d.ts +11 -6
  159. package/dist/commands/stack.d.ts.map +1 -1
  160. package/dist/commands/stack.js +212 -38
  161. package/dist/commands/stack.js.map +1 -1
  162. package/dist/commands/start.d.ts +0 -21
  163. package/dist/commands/start.d.ts.map +1 -1
  164. package/dist/commands/start.js +33 -43
  165. package/dist/commands/start.js.map +1 -1
  166. package/dist/commands/status.d.ts +1 -5
  167. package/dist/commands/status.d.ts.map +1 -1
  168. package/dist/commands/status.js +5 -1
  169. package/dist/commands/status.js.map +1 -1
  170. package/dist/commands/stop.d.ts +0 -22
  171. package/dist/commands/stop.d.ts.map +1 -1
  172. package/dist/commands/stop.js +26 -86
  173. package/dist/commands/stop.js.map +1 -1
  174. package/dist/commands/system.d.ts +4 -0
  175. package/dist/commands/system.d.ts.map +1 -0
  176. package/dist/commands/system.js +41 -0
  177. package/dist/commands/system.js.map +1 -0
  178. package/dist/commands/terminal.d.ts +7 -0
  179. package/dist/commands/terminal.d.ts.map +1 -0
  180. package/dist/commands/terminal.js +184 -0
  181. package/dist/commands/terminal.js.map +1 -0
  182. package/dist/commands/topology.d.ts +3 -0
  183. package/dist/commands/topology.d.ts.map +1 -0
  184. package/dist/commands/topology.js +107 -0
  185. package/dist/commands/topology.js.map +1 -0
  186. package/dist/commands/up.d.ts +4 -54
  187. package/dist/commands/up.d.ts.map +1 -1
  188. package/dist/commands/up.js +41 -98
  189. package/dist/commands/up.js.map +1 -1
  190. package/dist/commands/webapp.d.ts +0 -9
  191. package/dist/commands/webapp.d.ts.map +1 -1
  192. package/dist/commands/webapp.js +116 -69
  193. package/dist/commands/webapp.js.map +1 -1
  194. package/dist/config/app-path.d.ts +4 -0
  195. package/dist/config/app-path.d.ts.map +1 -0
  196. package/dist/config/app-path.js +6 -0
  197. package/dist/config/app-path.js.map +1 -0
  198. package/dist/config/defaults.d.ts.map +1 -1
  199. package/dist/config/defaults.js +9 -3
  200. package/dist/config/defaults.js.map +1 -1
  201. package/dist/config/define-ecosystem.d.ts.map +1 -1
  202. package/dist/config/define-ecosystem.js +12 -0
  203. package/dist/config/define-ecosystem.js.map +1 -1
  204. package/dist/config/index.d.ts +3 -1
  205. package/dist/config/index.d.ts.map +1 -1
  206. package/dist/config/index.js +1 -1
  207. package/dist/config/index.js.map +1 -1
  208. package/dist/config/loader-child.d.ts +2 -0
  209. package/dist/config/loader-child.d.ts.map +1 -0
  210. package/dist/config/loader-child.js +56 -0
  211. package/dist/config/loader-child.js.map +1 -0
  212. package/dist/config/loader.d.ts +7 -2
  213. package/dist/config/loader.d.ts.map +1 -1
  214. package/dist/config/loader.js +14 -27
  215. package/dist/config/loader.js.map +1 -1
  216. package/dist/config/saved.d.ts +67 -0
  217. package/dist/config/saved.d.ts.map +1 -0
  218. package/dist/config/saved.js +81 -0
  219. package/dist/config/saved.js.map +1 -0
  220. package/dist/config/types.d.ts +96 -52
  221. package/dist/config/types.d.ts.map +1 -1
  222. package/dist/daemon/backend-candidate.d.ts +32 -0
  223. package/dist/daemon/backend-candidate.d.ts.map +1 -0
  224. package/dist/daemon/backend-candidate.js +129 -0
  225. package/dist/daemon/backend-candidate.js.map +1 -0
  226. package/dist/daemon/backend-child.d.ts +5 -0
  227. package/dist/daemon/backend-child.d.ts.map +1 -0
  228. package/dist/daemon/backend-child.js +13 -0
  229. package/dist/daemon/backend-child.js.map +1 -0
  230. package/dist/daemon/backend-control.d.ts +43 -0
  231. package/dist/daemon/backend-control.d.ts.map +1 -0
  232. package/dist/daemon/backend-control.js +155 -0
  233. package/dist/daemon/backend-control.js.map +1 -0
  234. package/dist/daemon/backend-crash-recovery.d.ts +4 -0
  235. package/dist/daemon/backend-crash-recovery.d.ts.map +1 -0
  236. package/dist/daemon/backend-crash-recovery.js +136 -0
  237. package/dist/daemon/backend-crash-recovery.js.map +1 -0
  238. package/dist/daemon/backend-handoff-types.d.ts +27 -0
  239. package/dist/daemon/backend-handoff-types.d.ts.map +1 -0
  240. package/dist/daemon/backend-handoff-types.js +33 -0
  241. package/dist/daemon/backend-handoff-types.js.map +1 -0
  242. package/dist/daemon/backend-recovery-journal.d.ts +30 -0
  243. package/dist/daemon/backend-recovery-journal.d.ts.map +1 -0
  244. package/dist/daemon/backend-recovery-journal.js +67 -0
  245. package/dist/daemon/backend-recovery-journal.js.map +1 -0
  246. package/dist/daemon/backend-recovery-monitor.d.ts +35 -0
  247. package/dist/daemon/backend-recovery-monitor.d.ts.map +1 -0
  248. package/dist/daemon/backend-recovery-monitor.js +113 -0
  249. package/dist/daemon/backend-recovery-monitor.js.map +1 -0
  250. package/dist/daemon/backend-supervisor-entry.d.ts +2 -0
  251. package/dist/daemon/backend-supervisor-entry.d.ts.map +1 -0
  252. package/dist/daemon/backend-supervisor-entry.js +3 -0
  253. package/dist/daemon/backend-supervisor-entry.js.map +1 -0
  254. package/dist/daemon/backend-supervisor-main.d.ts +2 -0
  255. package/dist/daemon/backend-supervisor-main.d.ts.map +1 -0
  256. package/dist/daemon/backend-supervisor-main.js +45 -0
  257. package/dist/daemon/backend-supervisor-main.js.map +1 -0
  258. package/dist/daemon/backend-supervisor.d.ts +61 -0
  259. package/dist/daemon/backend-supervisor.d.ts.map +1 -0
  260. package/dist/daemon/backend-supervisor.js +339 -0
  261. package/dist/daemon/backend-supervisor.js.map +1 -0
  262. package/dist/daemon/backend-upgrade.d.ts +41 -0
  263. package/dist/daemon/backend-upgrade.d.ts.map +1 -0
  264. package/dist/daemon/backend-upgrade.js +444 -0
  265. package/dist/daemon/backend-upgrade.js.map +1 -0
  266. package/dist/daemon/backend.rpc-service.d.ts +11 -0
  267. package/dist/daemon/backend.rpc-service.d.ts.map +1 -0
  268. package/dist/daemon/backend.rpc-service.js +44 -0
  269. package/dist/daemon/backend.rpc-service.js.map +1 -0
  270. package/dist/daemon/daemon-client.d.ts +12 -79
  271. package/dist/daemon/daemon-client.d.ts.map +1 -1
  272. package/dist/daemon/daemon-client.js +28 -270
  273. package/dist/daemon/daemon-client.js.map +1 -1
  274. package/dist/daemon/daemon-entry.d.ts +0 -7
  275. package/dist/daemon/daemon-entry.d.ts.map +1 -1
  276. package/dist/daemon/daemon-entry.js +2 -115
  277. package/dist/daemon/daemon-entry.js.map +1 -1
  278. package/dist/daemon/daemon-main.d.ts +9 -0
  279. package/dist/daemon/daemon-main.d.ts.map +1 -0
  280. package/dist/daemon/daemon-main.js +105 -0
  281. package/dist/daemon/daemon-main.js.map +1 -0
  282. package/dist/daemon/daemon-scheduler.d.ts +8 -5
  283. package/dist/daemon/daemon-scheduler.d.ts.map +1 -1
  284. package/dist/daemon/daemon-scheduler.js +45 -73
  285. package/dist/daemon/daemon-scheduler.js.map +1 -1
  286. package/dist/daemon/daemon-state-store.service.d.ts +5 -39
  287. package/dist/daemon/daemon-state-store.service.d.ts.map +1 -1
  288. package/dist/daemon/daemon-state-store.service.js +30 -17
  289. package/dist/daemon/daemon-state-store.service.js.map +1 -1
  290. package/dist/daemon/daemon.d.ts +45 -60
  291. package/dist/daemon/daemon.d.ts.map +1 -1
  292. package/dist/daemon/daemon.js +720 -396
  293. package/dist/daemon/daemon.js.map +1 -1
  294. package/dist/daemon/daemon.module.d.ts +13 -11
  295. package/dist/daemon/daemon.module.d.ts.map +1 -1
  296. package/dist/daemon/daemon.module.js +136 -101
  297. package/dist/daemon/daemon.module.js.map +1 -1
  298. package/dist/daemon/daemon.rpc-service.d.ts +9 -17
  299. package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
  300. package/dist/daemon/daemon.rpc-service.js +83 -22
  301. package/dist/daemon/daemon.rpc-service.js.map +1 -1
  302. package/dist/daemon/darwin-owner-evidence.d.ts +57 -0
  303. package/dist/daemon/darwin-owner-evidence.d.ts.map +1 -0
  304. package/dist/daemon/darwin-owner-evidence.js +131 -0
  305. package/dist/daemon/darwin-owner-evidence.js.map +1 -0
  306. package/dist/daemon/database-startup.d.ts +7 -0
  307. package/dist/daemon/database-startup.d.ts.map +1 -0
  308. package/dist/daemon/database-startup.js +40 -0
  309. package/dist/daemon/database-startup.js.map +1 -0
  310. package/dist/daemon/entry-guard.d.ts +2 -0
  311. package/dist/daemon/entry-guard.d.ts.map +1 -0
  312. package/dist/daemon/entry-guard.js +34 -0
  313. package/dist/daemon/entry-guard.js.map +1 -0
  314. package/dist/daemon/execution-capabilities.d.ts +7 -0
  315. package/dist/daemon/execution-capabilities.d.ts.map +1 -0
  316. package/dist/daemon/execution-capabilities.js +23 -0
  317. package/dist/daemon/execution-capabilities.js.map +1 -0
  318. package/dist/daemon/execution-client.d.ts +88 -0
  319. package/dist/daemon/execution-client.d.ts.map +1 -0
  320. package/dist/daemon/execution-client.js +433 -0
  321. package/dist/daemon/execution-client.js.map +1 -0
  322. package/dist/daemon/execution-config.d.ts +7 -0
  323. package/dist/daemon/execution-config.d.ts.map +1 -0
  324. package/dist/daemon/execution-config.js +61 -0
  325. package/dist/daemon/execution-config.js.map +1 -0
  326. package/dist/daemon/execution-entry.d.ts +2 -0
  327. package/dist/daemon/execution-entry.d.ts.map +1 -0
  328. package/dist/daemon/execution-entry.js +3 -0
  329. package/dist/daemon/execution-entry.js.map +1 -0
  330. package/dist/daemon/execution-host.d.ts +133 -0
  331. package/dist/daemon/execution-host.d.ts.map +1 -0
  332. package/dist/daemon/execution-host.js +308 -0
  333. package/dist/daemon/execution-host.js.map +1 -0
  334. package/dist/daemon/execution-journal.d.ts +17 -0
  335. package/dist/daemon/execution-journal.d.ts.map +1 -0
  336. package/dist/daemon/execution-journal.js +75 -0
  337. package/dist/daemon/execution-journal.js.map +1 -0
  338. package/dist/daemon/execution-launcher.d.ts +12 -0
  339. package/dist/daemon/execution-launcher.d.ts.map +1 -0
  340. package/dist/daemon/execution-launcher.js +110 -0
  341. package/dist/daemon/execution-launcher.js.map +1 -0
  342. package/dist/daemon/execution-main.d.ts +2 -0
  343. package/dist/daemon/execution-main.d.ts.map +1 -0
  344. package/dist/daemon/execution-main.js +48 -0
  345. package/dist/daemon/execution-main.js.map +1 -0
  346. package/dist/daemon/execution-owner.d.ts +16 -0
  347. package/dist/daemon/execution-owner.d.ts.map +1 -0
  348. package/dist/daemon/execution-owner.js +97 -0
  349. package/dist/daemon/execution-owner.js.map +1 -0
  350. package/dist/daemon/execution-platform.d.ts +3 -0
  351. package/dist/daemon/execution-platform.d.ts.map +1 -0
  352. package/dist/daemon/execution-platform.js +23 -0
  353. package/dist/daemon/execution-platform.js.map +1 -0
  354. package/dist/daemon/execution-shutdown.d.ts +13 -0
  355. package/dist/daemon/execution-shutdown.d.ts.map +1 -0
  356. package/dist/daemon/execution-shutdown.js +30 -0
  357. package/dist/daemon/execution-shutdown.js.map +1 -0
  358. package/dist/daemon/managed-entry.d.ts +2 -0
  359. package/dist/daemon/managed-entry.d.ts.map +1 -0
  360. package/dist/daemon/managed-entry.js +3 -0
  361. package/dist/daemon/managed-entry.js.map +1 -0
  362. package/dist/daemon/managed-main.d.ts +2 -0
  363. package/dist/daemon/managed-main.d.ts.map +1 -0
  364. package/dist/daemon/managed-main.js +29 -0
  365. package/dist/daemon/managed-main.js.map +1 -0
  366. package/dist/daemon/managed-owner-build.d.ts +17 -0
  367. package/dist/daemon/managed-owner-build.d.ts.map +1 -0
  368. package/dist/daemon/managed-owner-build.js +108 -0
  369. package/dist/daemon/managed-owner-build.js.map +1 -0
  370. package/dist/daemon/managed-owner-entry.d.ts +2 -0
  371. package/dist/daemon/managed-owner-entry.d.ts.map +1 -0
  372. package/dist/daemon/managed-owner-entry.js +3 -0
  373. package/dist/daemon/managed-owner-entry.js.map +1 -0
  374. package/dist/daemon/managed-owner-main.d.ts +2 -0
  375. package/dist/daemon/managed-owner-main.d.ts.map +1 -0
  376. package/dist/daemon/managed-owner-main.js +53 -0
  377. package/dist/daemon/managed-owner-main.js.map +1 -0
  378. package/dist/daemon/managed-runtime.d.ts +39 -0
  379. package/dist/daemon/managed-runtime.d.ts.map +1 -0
  380. package/dist/daemon/managed-runtime.js +137 -0
  381. package/dist/daemon/managed-runtime.js.map +1 -0
  382. package/dist/daemon/network-publication.d.ts +5 -0
  383. package/dist/daemon/network-publication.d.ts.map +1 -0
  384. package/dist/daemon/network-publication.js +25 -0
  385. package/dist/daemon/network-publication.js.map +1 -0
  386. package/dist/daemon/owner-reconciliation.d.ts +58 -0
  387. package/dist/daemon/owner-reconciliation.d.ts.map +1 -0
  388. package/dist/daemon/owner-reconciliation.js +265 -0
  389. package/dist/daemon/owner-reconciliation.js.map +1 -0
  390. package/dist/daemon/pid-manager.d.ts +2 -0
  391. package/dist/daemon/pid-manager.d.ts.map +1 -1
  392. package/dist/daemon/pid-manager.js +5 -0
  393. package/dist/daemon/pid-manager.js.map +1 -1
  394. package/dist/daemon/private-control-client.d.ts +8 -0
  395. package/dist/daemon/private-control-client.d.ts.map +1 -0
  396. package/dist/daemon/private-control-client.js +41 -0
  397. package/dist/daemon/private-control-client.js.map +1 -0
  398. package/dist/daemon/recovery-state.d.ts +20 -0
  399. package/dist/daemon/recovery-state.d.ts.map +1 -0
  400. package/dist/daemon/recovery-state.js +53 -0
  401. package/dist/daemon/recovery-state.js.map +1 -0
  402. package/dist/daemon/rpc-connection.d.ts +29 -0
  403. package/dist/daemon/rpc-connection.d.ts.map +1 -0
  404. package/dist/daemon/rpc-connection.js +137 -0
  405. package/dist/daemon/rpc-connection.js.map +1 -0
  406. package/dist/daemon/runtime-installation.d.ts +58 -0
  407. package/dist/daemon/runtime-installation.d.ts.map +1 -0
  408. package/dist/daemon/runtime-installation.js +346 -0
  409. package/dist/daemon/runtime-installation.js.map +1 -0
  410. package/dist/daemon/runtime-launcher.d.ts +6 -0
  411. package/dist/daemon/runtime-launcher.d.ts.map +1 -0
  412. package/dist/daemon/runtime-launcher.js +45 -0
  413. package/dist/daemon/runtime-launcher.js.map +1 -0
  414. package/dist/daemon/schema-transition.d.ts +43 -0
  415. package/dist/daemon/schema-transition.d.ts.map +1 -0
  416. package/dist/daemon/schema-transition.js +239 -0
  417. package/dist/daemon/schema-transition.js.map +1 -0
  418. package/dist/daemon/spawn-daemon.d.ts +2 -0
  419. package/dist/daemon/spawn-daemon.d.ts.map +1 -1
  420. package/dist/daemon/spawn-daemon.js +4 -0
  421. package/dist/daemon/spawn-daemon.js.map +1 -1
  422. package/dist/daemon/startup-config.d.ts +77 -0
  423. package/dist/daemon/startup-config.d.ts.map +1 -0
  424. package/dist/daemon/startup-config.js +31 -0
  425. package/dist/daemon/startup-config.js.map +1 -0
  426. package/dist/daemon/startup-failure-journal.d.ts +3 -0
  427. package/dist/daemon/startup-failure-journal.d.ts.map +1 -0
  428. package/dist/daemon/startup-failure-journal.js +29 -0
  429. package/dist/daemon/startup-failure-journal.js.map +1 -0
  430. package/dist/daemon/state-store.d.ts +16 -24
  431. package/dist/daemon/state-store.d.ts.map +1 -1
  432. package/dist/daemon/state-store.js +51 -9
  433. package/dist/daemon/state-store.js.map +1 -1
  434. package/dist/daemon/supervisor-transition.d.ts +101 -0
  435. package/dist/daemon/supervisor-transition.d.ts.map +1 -0
  436. package/dist/daemon/supervisor-transition.js +503 -0
  437. package/dist/daemon/supervisor-transition.js.map +1 -0
  438. package/dist/daemon/system-guardian-cgroup.d.ts +16 -0
  439. package/dist/daemon/system-guardian-cgroup.d.ts.map +1 -0
  440. package/dist/daemon/system-guardian-cgroup.js +202 -0
  441. package/dist/daemon/system-guardian-cgroup.js.map +1 -0
  442. package/dist/daemon/system-guardian-unit.d.ts +9 -0
  443. package/dist/daemon/system-guardian-unit.d.ts.map +1 -0
  444. package/dist/daemon/system-guardian-unit.js +41 -0
  445. package/dist/daemon/system-guardian-unit.js.map +1 -0
  446. package/dist/database/connection.d.ts.map +1 -1
  447. package/dist/database/connection.js +2 -6
  448. package/dist/database/connection.js.map +1 -1
  449. package/dist/database/log-durability.d.ts +5 -0
  450. package/dist/database/log-durability.d.ts.map +1 -0
  451. package/dist/database/log-durability.js +28 -0
  452. package/dist/database/log-durability.js.map +1 -0
  453. package/dist/database/migrate.js +2 -15
  454. package/dist/database/migrate.js.map +1 -1
  455. package/dist/database/migration-registry-compatibility.d.ts +13 -0
  456. package/dist/database/migration-registry-compatibility.d.ts.map +1 -0
  457. package/dist/database/migration-registry-compatibility.js +108 -0
  458. package/dist/database/migration-registry-compatibility.js.map +1 -0
  459. package/dist/database/migration-runner.d.ts +8 -42
  460. package/dist/database/migration-runner.d.ts.map +1 -1
  461. package/dist/database/migration-runner.js +114 -123
  462. package/dist/database/migration-runner.js.map +1 -1
  463. package/dist/database/migrations/014_replication_receipts.d.ts +5 -0
  464. package/dist/database/migrations/014_replication_receipts.d.ts.map +1 -0
  465. package/dist/database/migrations/014_replication_receipts.js +6 -0
  466. package/dist/database/migrations/014_replication_receipts.js.map +1 -0
  467. package/dist/database/migrations/015_persistent_metrics.d.ts +5 -0
  468. package/dist/database/migrations/015_persistent_metrics.d.ts.map +1 -0
  469. package/dist/database/migrations/015_persistent_metrics.js +7 -0
  470. package/dist/database/migrations/015_persistent_metrics.js.map +1 -0
  471. package/dist/database/migrations/016_cluster_lease.d.ts +4 -0
  472. package/dist/database/migrations/016_cluster_lease.d.ts.map +1 -0
  473. package/dist/database/migrations/016_cluster_lease.js +6 -0
  474. package/dist/database/migrations/016_cluster_lease.js.map +1 -0
  475. package/dist/database/migrations/index.d.ts +1 -1
  476. package/dist/database/migrations/index.d.ts.map +1 -1
  477. package/dist/database/migrations/index.js +22 -13
  478. package/dist/database/migrations/index.js.map +1 -1
  479. package/dist/database/schema-compatibility.d.ts +34 -0
  480. package/dist/database/schema-compatibility.d.ts.map +1 -0
  481. package/dist/database/schema-compatibility.js +109 -0
  482. package/dist/database/schema-compatibility.js.map +1 -0
  483. package/dist/database/schema-expansion-policy.d.ts +10 -0
  484. package/dist/database/schema-expansion-policy.d.ts.map +1 -0
  485. package/dist/database/schema-expansion-policy.js +21 -0
  486. package/dist/database/schema-expansion-policy.js.map +1 -0
  487. package/dist/database/schema-transition-plan.d.ts +53 -0
  488. package/dist/database/schema-transition-plan.d.ts.map +1 -0
  489. package/dist/database/schema-transition-plan.js +102 -0
  490. package/dist/database/schema-transition-plan.js.map +1 -0
  491. package/dist/database/schema-worker.d.ts +2 -0
  492. package/dist/database/schema-worker.d.ts.map +1 -0
  493. package/dist/database/schema-worker.js +55 -0
  494. package/dist/database/schema-worker.js.map +1 -0
  495. package/dist/database/schema.d.ts +6 -1
  496. package/dist/database/schema.d.ts.map +1 -1
  497. package/dist/database/slave-migrations/001_replication_evidence.d.ts +4 -0
  498. package/dist/database/slave-migrations/001_replication_evidence.d.ts.map +1 -0
  499. package/dist/database/slave-migrations/001_replication_evidence.js +89 -0
  500. package/dist/database/slave-migrations/001_replication_evidence.js.map +1 -0
  501. package/dist/database/slave-migrations/index.d.ts +6 -0
  502. package/dist/database/slave-migrations/index.d.ts.map +1 -0
  503. package/dist/database/slave-migrations/index.js +10 -0
  504. package/dist/database/slave-migrations/index.js.map +1 -0
  505. package/dist/diagnostics/index.d.ts +16 -0
  506. package/dist/diagnostics/index.d.ts.map +1 -0
  507. package/dist/diagnostics/index.js +22 -0
  508. package/dist/diagnostics/index.js.map +1 -0
  509. package/dist/execution/command-failure.d.ts +7 -0
  510. package/dist/execution/command-failure.d.ts.map +1 -0
  511. package/dist/execution/command-failure.js +29 -0
  512. package/dist/execution/command-failure.js.map +1 -0
  513. package/dist/execution/execution.service.d.ts +6 -1
  514. package/dist/execution/execution.service.d.ts.map +1 -1
  515. package/dist/execution/execution.service.js +32 -30
  516. package/dist/execution/execution.service.js.map +1 -1
  517. package/dist/files/archive-codec.d.ts +57 -0
  518. package/dist/files/archive-codec.d.ts.map +1 -0
  519. package/dist/files/archive-codec.js +454 -0
  520. package/dist/files/archive-codec.js.map +1 -0
  521. package/dist/files/archive-node.d.ts +27 -0
  522. package/dist/files/archive-node.d.ts.map +1 -0
  523. package/dist/files/archive-node.js +379 -0
  524. package/dist/files/archive-node.js.map +1 -0
  525. package/dist/files/archive-publication.d.ts +26 -0
  526. package/dist/files/archive-publication.d.ts.map +1 -0
  527. package/dist/files/archive-publication.js +224 -0
  528. package/dist/files/archive-publication.js.map +1 -0
  529. package/dist/files/file-config.d.ts +7 -0
  530. package/dist/files/file-config.d.ts.map +1 -0
  531. package/dist/files/file-config.js +11 -0
  532. package/dist/files/file-config.js.map +1 -0
  533. package/dist/files/file-node.d.ts +65 -0
  534. package/dist/files/file-node.d.ts.map +1 -0
  535. package/dist/files/file-node.js +863 -0
  536. package/dist/files/file-node.js.map +1 -0
  537. package/dist/files/file-node.rpc-service.d.ts +12 -0
  538. package/dist/files/file-node.rpc-service.d.ts.map +1 -0
  539. package/dist/files/file-node.rpc-service.js +42 -0
  540. package/dist/files/file-node.rpc-service.js.map +1 -0
  541. package/dist/files/files.rpc-service.d.ts +107 -0
  542. package/dist/files/files.rpc-service.d.ts.map +1 -0
  543. package/dist/files/files.rpc-service.js +769 -0
  544. package/dist/files/files.rpc-service.js.map +1 -0
  545. package/dist/files/structural-policy.d.ts +3 -0
  546. package/dist/files/structural-policy.d.ts.map +1 -0
  547. package/dist/files/structural-policy.js +27 -0
  548. package/dist/files/structural-policy.js.map +1 -0
  549. package/dist/health/contracts.d.ts +30 -0
  550. package/dist/health/contracts.d.ts.map +1 -0
  551. package/dist/health/contracts.js +58 -0
  552. package/dist/health/contracts.js.map +1 -0
  553. package/dist/health/heartbeat.d.ts +11 -0
  554. package/dist/health/heartbeat.d.ts.map +1 -0
  555. package/dist/health/heartbeat.js +32 -0
  556. package/dist/health/heartbeat.js.map +1 -0
  557. package/dist/health/index.d.ts +4 -0
  558. package/dist/health/index.d.ts.map +1 -0
  559. package/dist/health/index.js +2 -0
  560. package/dist/health/index.js.map +1 -0
  561. package/dist/health/readiness.d.ts +15 -0
  562. package/dist/health/readiness.d.ts.map +1 -0
  563. package/dist/health/readiness.js +170 -0
  564. package/dist/health/readiness.js.map +1 -0
  565. package/dist/index.d.ts +1 -0
  566. package/dist/index.d.ts.map +1 -1
  567. package/dist/infrastructure/apt-packages.d.ts +34 -0
  568. package/dist/infrastructure/apt-packages.d.ts.map +1 -0
  569. package/dist/infrastructure/apt-packages.js +232 -0
  570. package/dist/infrastructure/apt-packages.js.map +1 -0
  571. package/dist/infrastructure/bare-metal-plan.d.ts +10 -1
  572. package/dist/infrastructure/bare-metal-plan.d.ts.map +1 -1
  573. package/dist/infrastructure/bare-metal-plan.js +21 -0
  574. package/dist/infrastructure/bare-metal-plan.js.map +1 -1
  575. package/dist/infrastructure/bare-metal-runner.d.ts.map +1 -1
  576. package/dist/infrastructure/bare-metal-runner.js +13 -2
  577. package/dist/infrastructure/bare-metal-runner.js.map +1 -1
  578. package/dist/infrastructure/config-normalizer.d.ts +4 -3
  579. package/dist/infrastructure/config-normalizer.d.ts.map +1 -1
  580. package/dist/infrastructure/config-normalizer.js +11 -3
  581. package/dist/infrastructure/config-normalizer.js.map +1 -1
  582. package/dist/infrastructure/container-runtime.d.ts +37 -3
  583. package/dist/infrastructure/container-runtime.d.ts.map +1 -1
  584. package/dist/infrastructure/container-runtime.js +235 -64
  585. package/dist/infrastructure/container-runtime.js.map +1 -1
  586. package/dist/infrastructure/health-recovery.d.ts +19 -0
  587. package/dist/infrastructure/health-recovery.d.ts.map +1 -0
  588. package/dist/infrastructure/health-recovery.js +39 -0
  589. package/dist/infrastructure/health-recovery.js.map +1 -0
  590. package/dist/infrastructure/host-ingress-config.d.ts.map +1 -1
  591. package/dist/infrastructure/host-ingress-config.js +11 -8
  592. package/dist/infrastructure/host-ingress-config.js.map +1 -1
  593. package/dist/infrastructure/host-ingress-lock.d.ts +2 -3
  594. package/dist/infrastructure/host-ingress-lock.d.ts.map +1 -1
  595. package/dist/infrastructure/host-ingress-lock.js +1 -35
  596. package/dist/infrastructure/host-ingress-lock.js.map +1 -1
  597. package/dist/infrastructure/host-ingress-takeover.js +3 -3
  598. package/dist/infrastructure/host-ingress-takeover.js.map +1 -1
  599. package/dist/infrastructure/host-ingress-types.d.ts +4 -2
  600. package/dist/infrastructure/host-ingress-types.d.ts.map +1 -1
  601. package/dist/infrastructure/host-ingress.service.d.ts.map +1 -1
  602. package/dist/infrastructure/host-ingress.service.js +6 -6
  603. package/dist/infrastructure/host-ingress.service.js.map +1 -1
  604. package/dist/infrastructure/host-inspection.d.ts +1 -0
  605. package/dist/infrastructure/host-inspection.d.ts.map +1 -1
  606. package/dist/infrastructure/host-inspection.js +3 -2
  607. package/dist/infrastructure/host-inspection.js.map +1 -1
  608. package/dist/infrastructure/infrastructure.service.d.ts +80 -29
  609. package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
  610. package/dist/infrastructure/infrastructure.service.js +561 -216
  611. package/dist/infrastructure/infrastructure.service.js.map +1 -1
  612. package/dist/infrastructure/merge-config.d.ts +10 -0
  613. package/dist/infrastructure/merge-config.d.ts.map +1 -0
  614. package/dist/infrastructure/merge-config.js +27 -0
  615. package/dist/infrastructure/merge-config.js.map +1 -0
  616. package/dist/infrastructure/orphan-containers.d.ts +1 -0
  617. package/dist/infrastructure/orphan-containers.d.ts.map +1 -1
  618. package/dist/infrastructure/orphan-containers.js +1 -1
  619. package/dist/infrastructure/orphan-containers.js.map +1 -1
  620. package/dist/infrastructure/owned-container.d.ts +4 -0
  621. package/dist/infrastructure/owned-container.d.ts.map +1 -0
  622. package/dist/infrastructure/owned-container.js +13 -0
  623. package/dist/infrastructure/owned-container.js.map +1 -0
  624. package/dist/infrastructure/phantom-endpoint-janitor.d.ts +4 -1
  625. package/dist/infrastructure/phantom-endpoint-janitor.d.ts.map +1 -1
  626. package/dist/infrastructure/phantom-endpoint-janitor.js +11 -2
  627. package/dist/infrastructure/phantom-endpoint-janitor.js.map +1 -1
  628. package/dist/infrastructure/presets/minio.d.ts.map +1 -1
  629. package/dist/infrastructure/presets/minio.js +15 -25
  630. package/dist/infrastructure/presets/minio.js.map +1 -1
  631. package/dist/infrastructure/presets/postgres.d.ts.map +1 -1
  632. package/dist/infrastructure/presets/postgres.js +35 -31
  633. package/dist/infrastructure/presets/postgres.js.map +1 -1
  634. package/dist/infrastructure/presets/tor.d.ts +1 -2
  635. package/dist/infrastructure/presets/tor.d.ts.map +1 -1
  636. package/dist/infrastructure/presets/tor.js +7 -13
  637. package/dist/infrastructure/presets/tor.js.map +1 -1
  638. package/dist/infrastructure/provisioning-outcome.d.ts +7 -0
  639. package/dist/infrastructure/provisioning-outcome.d.ts.map +1 -1
  640. package/dist/infrastructure/provisioning-outcome.js +41 -8
  641. package/dist/infrastructure/provisioning-outcome.js.map +1 -1
  642. package/dist/infrastructure/service-resolver.d.ts +1 -1
  643. package/dist/infrastructure/service-resolver.d.ts.map +1 -1
  644. package/dist/infrastructure/service-resolver.js +22 -10
  645. package/dist/infrastructure/service-resolver.js.map +1 -1
  646. package/dist/infrastructure/stack-infra-manager.d.ts +25 -4
  647. package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
  648. package/dist/infrastructure/stack-infra-manager.js +181 -21
  649. package/dist/infrastructure/stack-infra-manager.js.map +1 -1
  650. package/dist/infrastructure/types.d.ts +60 -3
  651. package/dist/infrastructure/types.d.ts.map +1 -1
  652. package/dist/mcp/mcp-bridge.js +1 -1
  653. package/dist/mcp/mcp-bridge.js.map +1 -1
  654. package/dist/mcp/monitoring-reading.d.ts +18 -0
  655. package/dist/mcp/monitoring-reading.d.ts.map +1 -0
  656. package/dist/mcp/monitoring-reading.js +84 -0
  657. package/dist/mcp/monitoring-reading.js.map +1 -0
  658. package/dist/mcp/tool-groups/apps.tools.d.ts.map +1 -1
  659. package/dist/mcp/tool-groups/apps.tools.js +6 -4
  660. package/dist/mcp/tool-groups/apps.tools.js.map +1 -1
  661. package/dist/mcp/tool-groups/management.tools.d.ts +2 -0
  662. package/dist/mcp/tool-groups/management.tools.d.ts.map +1 -1
  663. package/dist/mcp/tool-groups/management.tools.js +17 -38
  664. package/dist/mcp/tool-groups/management.tools.js.map +1 -1
  665. package/dist/mcp/tool-groups/monitoring.tools.d.ts.map +1 -1
  666. package/dist/mcp/tool-groups/monitoring.tools.js +45 -2
  667. package/dist/mcp/tool-groups/monitoring.tools.js.map +1 -1
  668. package/dist/mcp/unavailable-tools.js +2 -2
  669. package/dist/mcp/unavailable-tools.js.map +1 -1
  670. package/dist/migration/configuration.d.ts +8 -0
  671. package/dist/migration/configuration.d.ts.map +1 -0
  672. package/dist/migration/configuration.js +16 -0
  673. package/dist/migration/configuration.js.map +1 -0
  674. package/dist/migration/index.d.ts +25 -0
  675. package/dist/migration/index.d.ts.map +1 -0
  676. package/dist/migration/index.js +27 -0
  677. package/dist/migration/index.js.map +1 -0
  678. package/dist/migration/operation.d.ts +31 -0
  679. package/dist/migration/operation.d.ts.map +1 -0
  680. package/dist/migration/operation.js +67 -0
  681. package/dist/migration/operation.js.map +1 -0
  682. package/dist/migration/workspace.d.ts +28 -0
  683. package/dist/migration/workspace.d.ts.map +1 -0
  684. package/dist/migration/workspace.js +91 -0
  685. package/dist/migration/workspace.js.map +1 -0
  686. package/dist/monitoring/app-health.indicator.d.ts +1 -1
  687. package/dist/monitoring/app-health.indicator.d.ts.map +1 -1
  688. package/dist/monitoring/docker-health.indicator.d.ts +4 -2
  689. package/dist/monitoring/docker-health.indicator.d.ts.map +1 -1
  690. package/dist/monitoring/docker-health.indicator.js +66 -5
  691. package/dist/monitoring/docker-health.indicator.js.map +1 -1
  692. package/dist/monitoring/event-loop-watch.d.ts.map +1 -1
  693. package/dist/monitoring/event-loop-watch.js.map +1 -1
  694. package/dist/monitoring/infrastructure-health.d.ts +15 -0
  695. package/dist/monitoring/infrastructure-health.d.ts.map +1 -0
  696. package/dist/monitoring/infrastructure-health.js +2 -0
  697. package/dist/monitoring/infrastructure-health.js.map +1 -0
  698. package/dist/monitoring/log-entry.d.ts +7 -0
  699. package/dist/monitoring/log-entry.d.ts.map +1 -0
  700. package/dist/monitoring/log-entry.js +16 -0
  701. package/dist/monitoring/log-entry.js.map +1 -0
  702. package/dist/monitoring/log-journal.d.ts +40 -0
  703. package/dist/monitoring/log-journal.d.ts.map +1 -0
  704. package/dist/monitoring/log-journal.js +158 -0
  705. package/dist/monitoring/log-journal.js.map +1 -0
  706. package/dist/monitoring/log-line.d.ts +19 -14
  707. package/dist/monitoring/log-line.d.ts.map +1 -1
  708. package/dist/monitoring/log-line.js +70 -59
  709. package/dist/monitoring/log-line.js.map +1 -1
  710. package/dist/monitoring/log-manager.d.ts +34 -37
  711. package/dist/monitoring/log-manager.d.ts.map +1 -1
  712. package/dist/monitoring/log-manager.js +164 -63
  713. package/dist/monitoring/log-manager.js.map +1 -1
  714. package/dist/monitoring/log-sink-health.indicator.d.ts +10 -0
  715. package/dist/monitoring/log-sink-health.indicator.d.ts.map +1 -0
  716. package/dist/monitoring/log-sink-health.indicator.js +21 -0
  717. package/dist/monitoring/log-sink-health.indicator.js.map +1 -0
  718. package/dist/monitoring/metrics-storage.indicator.d.ts +9 -0
  719. package/dist/monitoring/metrics-storage.indicator.d.ts.map +1 -0
  720. package/dist/monitoring/metrics-storage.indicator.js +18 -0
  721. package/dist/monitoring/metrics-storage.indicator.js.map +1 -0
  722. package/dist/monitoring/process-lifetime.d.ts +11 -0
  723. package/dist/monitoring/process-lifetime.d.ts.map +1 -0
  724. package/dist/monitoring/process-lifetime.js +45 -0
  725. package/dist/monitoring/process-lifetime.js.map +1 -0
  726. package/dist/monitoring/runtime-diagnostics.d.ts +47 -0
  727. package/dist/monitoring/runtime-diagnostics.d.ts.map +1 -0
  728. package/dist/monitoring/runtime-diagnostics.js +88 -0
  729. package/dist/monitoring/runtime-diagnostics.js.map +1 -0
  730. package/dist/observability/metrics-bridge.d.ts +5 -1
  731. package/dist/observability/metrics-bridge.d.ts.map +1 -1
  732. package/dist/observability/metrics-bridge.js +11 -0
  733. package/dist/observability/metrics-bridge.js.map +1 -1
  734. package/dist/operations.d.ts +1 -0
  735. package/dist/operations.d.ts.map +1 -1
  736. package/dist/operations.js.map +1 -1
  737. package/dist/orchestrator/app-handle.d.ts +24 -6
  738. package/dist/orchestrator/app-handle.d.ts.map +1 -1
  739. package/dist/orchestrator/app-handle.js +39 -8
  740. package/dist/orchestrator/app-handle.js.map +1 -1
  741. package/dist/orchestrator/application-runtime-child.d.ts +2 -0
  742. package/dist/orchestrator/application-runtime-child.d.ts.map +1 -0
  743. package/dist/orchestrator/application-runtime-child.js +15 -0
  744. package/dist/orchestrator/application-runtime-child.js.map +1 -0
  745. package/dist/orchestrator/application-runtime-resolution.d.ts +4 -0
  746. package/dist/orchestrator/application-runtime-resolution.d.ts.map +1 -0
  747. package/dist/orchestrator/application-runtime-resolution.js +62 -0
  748. package/dist/orchestrator/application-runtime-resolution.js.map +1 -0
  749. package/dist/orchestrator/application-runtime.d.ts +3 -0
  750. package/dist/orchestrator/application-runtime.d.ts.map +1 -0
  751. package/dist/orchestrator/application-runtime.js +20 -0
  752. package/dist/orchestrator/application-runtime.js.map +1 -0
  753. package/dist/orchestrator/application-topology.d.ts +20 -0
  754. package/dist/orchestrator/application-topology.d.ts.map +1 -0
  755. package/dist/orchestrator/application-topology.js +90 -0
  756. package/dist/orchestrator/application-topology.js.map +1 -0
  757. package/dist/orchestrator/bootstrap-loader-child.d.ts +1 -1
  758. package/dist/orchestrator/bootstrap-loader-child.js +7 -16
  759. package/dist/orchestrator/bootstrap-loader-child.js.map +1 -1
  760. package/dist/orchestrator/bootstrap-loader.d.ts +8 -18
  761. package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
  762. package/dist/orchestrator/bootstrap-loader.js +21 -204
  763. package/dist/orchestrator/bootstrap-loader.js.map +1 -1
  764. package/dist/orchestrator/bootstrap-process.d.ts +5 -5
  765. package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
  766. package/dist/orchestrator/bootstrap-process.js +37 -38
  767. package/dist/orchestrator/bootstrap-process.js.map +1 -1
  768. package/dist/orchestrator/build-service.d.ts +3 -3
  769. package/dist/orchestrator/build-service.d.ts.map +1 -1
  770. package/dist/orchestrator/build-service.js +29 -80
  771. package/dist/orchestrator/build-service.js.map +1 -1
  772. package/dist/orchestrator/call-exposed-service.d.ts +8 -0
  773. package/dist/orchestrator/call-exposed-service.d.ts.map +1 -1
  774. package/dist/orchestrator/classic-launcher.d.ts +3 -2
  775. package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
  776. package/dist/orchestrator/classic-launcher.js +26 -22
  777. package/dist/orchestrator/classic-launcher.js.map +1 -1
  778. package/dist/orchestrator/dependency-resolver.d.ts.map +1 -1
  779. package/dist/orchestrator/dependency-resolver.js +7 -38
  780. package/dist/orchestrator/dependency-resolver.js.map +1 -1
  781. package/dist/orchestrator/execution-contract.d.ts +67 -0
  782. package/dist/orchestrator/execution-contract.d.ts.map +1 -0
  783. package/dist/orchestrator/execution-contract.js +2 -0
  784. package/dist/orchestrator/execution-contract.js.map +1 -0
  785. package/dist/orchestrator/file-watcher.d.ts +1 -1
  786. package/dist/orchestrator/file-watcher.d.ts.map +1 -1
  787. package/dist/orchestrator/file-watcher.js +10 -3
  788. package/dist/orchestrator/file-watcher.js.map +1 -1
  789. package/dist/orchestrator/installed-execution.d.ts +7 -0
  790. package/dist/orchestrator/installed-execution.d.ts.map +1 -0
  791. package/dist/orchestrator/installed-execution.js +25 -0
  792. package/dist/orchestrator/installed-execution.js.map +1 -0
  793. package/dist/orchestrator/module-worker-process.d.ts.map +1 -1
  794. package/dist/orchestrator/module-worker-process.js +2 -1
  795. package/dist/orchestrator/module-worker-process.js.map +1 -1
  796. package/dist/orchestrator/orchestrator.service.d.ts +87 -76
  797. package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
  798. package/dist/orchestrator/orchestrator.service.js +941 -469
  799. package/dist/orchestrator/orchestrator.service.js.map +1 -1
  800. package/dist/orchestrator/process-janitor.d.ts +3 -0
  801. package/dist/orchestrator/process-janitor.d.ts.map +1 -1
  802. package/dist/orchestrator/process-janitor.js +5 -1
  803. package/dist/orchestrator/process-janitor.js.map +1 -1
  804. package/dist/orchestrator/runtime-package-resolution.d.ts +8 -0
  805. package/dist/orchestrator/runtime-package-resolution.d.ts.map +1 -0
  806. package/dist/orchestrator/runtime-package-resolution.js +56 -0
  807. package/dist/orchestrator/runtime-package-resolution.js.map +1 -0
  808. package/dist/orchestrator/service-router.d.ts +4 -108
  809. package/dist/orchestrator/service-router.d.ts.map +1 -1
  810. package/dist/orchestrator/service-router.js +51 -237
  811. package/dist/orchestrator/service-router.js.map +1 -1
  812. package/dist/orchestrator/ts-compiler.d.ts.map +1 -1
  813. package/dist/orchestrator/ts-compiler.js +9 -17
  814. package/dist/orchestrator/ts-compiler.js.map +1 -1
  815. package/dist/project/artifact-builder.d.ts +17 -7
  816. package/dist/project/artifact-builder.d.ts.map +1 -1
  817. package/dist/project/artifact-builder.js +89 -56
  818. package/dist/project/artifact-builder.js.map +1 -1
  819. package/dist/project/bootstrap-metadata.d.ts +42 -0
  820. package/dist/project/bootstrap-metadata.d.ts.map +1 -0
  821. package/dist/project/bootstrap-metadata.js +76 -0
  822. package/dist/project/bootstrap-metadata.js.map +1 -0
  823. package/dist/project/config-resolver.js +3 -3
  824. package/dist/project/config-resolver.js.map +1 -1
  825. package/dist/project/configuration-catalog.d.ts +8 -0
  826. package/dist/project/configuration-catalog.d.ts.map +1 -0
  827. package/dist/project/configuration-catalog.js +29 -0
  828. package/dist/project/configuration-catalog.js.map +1 -0
  829. package/dist/project/declared-config.d.ts +2 -0
  830. package/dist/project/declared-config.d.ts.map +1 -1
  831. package/dist/project/declared-config.js +8 -5
  832. package/dist/project/declared-config.js.map +1 -1
  833. package/dist/project/dependency-topology.d.ts +10 -0
  834. package/dist/project/dependency-topology.d.ts.map +1 -0
  835. package/dist/project/dependency-topology.js +55 -0
  836. package/dist/project/dependency-topology.js.map +1 -0
  837. package/dist/project/infrastructure-ownership.d.ts +5 -0
  838. package/dist/project/infrastructure-ownership.d.ts.map +1 -0
  839. package/dist/project/infrastructure-ownership.js +26 -0
  840. package/dist/project/infrastructure-ownership.js.map +1 -0
  841. package/dist/project/installed-stack-resume.d.ts +62 -0
  842. package/dist/project/installed-stack-resume.d.ts.map +1 -0
  843. package/dist/project/installed-stack-resume.js +261 -0
  844. package/dist/project/installed-stack-resume.js.map +1 -0
  845. package/dist/project/launch-failure.d.ts +3 -0
  846. package/dist/project/launch-failure.d.ts.map +1 -0
  847. package/dist/project/launch-failure.js +32 -0
  848. package/dist/project/launch-failure.js.map +1 -0
  849. package/dist/project/local-release-selection.d.ts +18 -0
  850. package/dist/project/local-release-selection.d.ts.map +1 -0
  851. package/dist/project/local-release-selection.js +36 -0
  852. package/dist/project/local-release-selection.js.map +1 -0
  853. package/dist/project/node-app-config.d.ts +8 -1
  854. package/dist/project/node-app-config.d.ts.map +1 -1
  855. package/dist/project/node-app-config.js +20 -28
  856. package/dist/project/node-app-config.js.map +1 -1
  857. package/dist/project/node-stack-stop.d.ts +83 -0
  858. package/dist/project/node-stack-stop.d.ts.map +1 -0
  859. package/dist/project/node-stack-stop.js +219 -0
  860. package/dist/project/node-stack-stop.js.map +1 -0
  861. package/dist/project/public-stack.d.ts +4 -0
  862. package/dist/project/public-stack.d.ts.map +1 -0
  863. package/dist/project/public-stack.js +29 -0
  864. package/dist/project/public-stack.js.map +1 -0
  865. package/dist/project/registry.d.ts +18 -27
  866. package/dist/project/registry.d.ts.map +1 -1
  867. package/dist/project/registry.js +244 -55
  868. package/dist/project/registry.js.map +1 -1
  869. package/dist/project/requirements-scanner.d.ts +4 -8
  870. package/dist/project/requirements-scanner.d.ts.map +1 -1
  871. package/dist/project/requirements-scanner.js +39 -146
  872. package/dist/project/requirements-scanner.js.map +1 -1
  873. package/dist/project/stack-diagnosis.d.ts +18 -0
  874. package/dist/project/stack-diagnosis.d.ts.map +1 -0
  875. package/dist/project/stack-diagnosis.js +94 -0
  876. package/dist/project/stack-diagnosis.js.map +1 -0
  877. package/dist/project/stack-health.d.ts +12 -0
  878. package/dist/project/stack-health.d.ts.map +1 -0
  879. package/dist/project/stack-health.js +27 -0
  880. package/dist/project/stack-health.js.map +1 -0
  881. package/dist/project/stack-observation.d.ts +8 -0
  882. package/dist/project/stack-observation.d.ts.map +1 -0
  883. package/dist/project/stack-observation.js +126 -0
  884. package/dist/project/stack-observation.js.map +1 -0
  885. package/dist/project/stack-placement.d.ts +11 -0
  886. package/dist/project/stack-placement.d.ts.map +1 -0
  887. package/dist/project/stack-placement.js +57 -0
  888. package/dist/project/stack-placement.js.map +1 -0
  889. package/dist/project/stack-stop-review.d.ts +14 -0
  890. package/dist/project/stack-stop-review.d.ts.map +1 -0
  891. package/dist/project/stack-stop-review.js +22 -0
  892. package/dist/project/stack-stop-review.js.map +1 -0
  893. package/dist/project/stack-targets.d.ts +24 -0
  894. package/dist/project/stack-targets.d.ts.map +1 -0
  895. package/dist/project/stack-targets.js +67 -0
  896. package/dist/project/stack-targets.js.map +1 -0
  897. package/dist/project/topology-control.d.ts +41 -0
  898. package/dist/project/topology-control.d.ts.map +1 -0
  899. package/dist/project/topology-control.js +133 -0
  900. package/dist/project/topology-control.js.map +1 -0
  901. package/dist/project/topology-plan.d.ts +11 -0
  902. package/dist/project/topology-plan.d.ts.map +1 -0
  903. package/dist/project/topology-plan.js +154 -0
  904. package/dist/project/topology-plan.js.map +1 -0
  905. package/dist/project/topology-services.d.ts +7 -0
  906. package/dist/project/topology-services.d.ts.map +1 -0
  907. package/dist/project/topology-services.js +36 -0
  908. package/dist/project/topology-services.js.map +1 -0
  909. package/dist/project/topology-state.d.ts +27 -0
  910. package/dist/project/topology-state.d.ts.map +1 -0
  911. package/dist/project/topology-state.js +71 -0
  912. package/dist/project/topology-state.js.map +1 -0
  913. package/dist/project/workspace-kind.d.ts +3 -0
  914. package/dist/project/workspace-kind.d.ts.map +1 -0
  915. package/dist/project/workspace-kind.js +13 -0
  916. package/dist/project/workspace-kind.js.map +1 -0
  917. package/dist/readiness.d.ts +4 -0
  918. package/dist/readiness.d.ts.map +1 -0
  919. package/dist/readiness.js +3 -0
  920. package/dist/readiness.js.map +1 -0
  921. package/dist/release/application-closure.d.ts +4 -0
  922. package/dist/release/application-closure.d.ts.map +1 -0
  923. package/dist/release/application-closure.js +61 -0
  924. package/dist/release/application-closure.js.map +1 -0
  925. package/dist/release/archive.d.ts +3 -0
  926. package/dist/release/archive.d.ts.map +1 -0
  927. package/dist/release/archive.js +3 -0
  928. package/dist/release/archive.js.map +1 -0
  929. package/dist/release/artifact-inspection.d.ts +50 -0
  930. package/dist/release/artifact-inspection.d.ts.map +1 -0
  931. package/dist/release/artifact-inspection.js +169 -0
  932. package/dist/release/artifact-inspection.js.map +1 -0
  933. package/dist/release/artifact-volumes.d.ts +10 -0
  934. package/dist/release/artifact-volumes.d.ts.map +1 -0
  935. package/dist/release/artifact-volumes.js +112 -0
  936. package/dist/release/artifact-volumes.js.map +1 -0
  937. package/dist/release/build-run.d.ts +12 -15
  938. package/dist/release/build-run.d.ts.map +1 -1
  939. package/dist/release/build-run.js +260 -73
  940. package/dist/release/build-run.js.map +1 -1
  941. package/dist/release/builder.d.ts +8 -2
  942. package/dist/release/builder.d.ts.map +1 -1
  943. package/dist/release/builder.js +18 -4
  944. package/dist/release/builder.js.map +1 -1
  945. package/dist/release/closure-library-path.d.ts +26 -0
  946. package/dist/release/closure-library-path.d.ts.map +1 -0
  947. package/dist/release/closure-library-path.js +160 -0
  948. package/dist/release/closure-library-path.js.map +1 -0
  949. package/dist/release/definition-inputs.d.ts +6 -3
  950. package/dist/release/definition-inputs.d.ts.map +1 -1
  951. package/dist/release/definition-inputs.js +145 -14
  952. package/dist/release/definition-inputs.js.map +1 -1
  953. package/dist/release/framework-source.d.ts +22 -0
  954. package/dist/release/framework-source.d.ts.map +1 -0
  955. package/dist/release/framework-source.js +54 -0
  956. package/dist/release/framework-source.js.map +1 -0
  957. package/dist/release/frozen-installation.d.ts +23 -0
  958. package/dist/release/frozen-installation.d.ts.map +1 -0
  959. package/dist/release/frozen-installation.js +159 -0
  960. package/dist/release/frozen-installation.js.map +1 -0
  961. package/dist/release/import-artifacts.d.ts +9 -0
  962. package/dist/release/import-artifacts.d.ts.map +1 -0
  963. package/dist/release/import-artifacts.js +60 -0
  964. package/dist/release/import-artifacts.js.map +1 -0
  965. package/dist/release/infrastructure.d.ts +6 -0
  966. package/dist/release/infrastructure.d.ts.map +1 -0
  967. package/dist/release/infrastructure.js +74 -0
  968. package/dist/release/infrastructure.js.map +1 -0
  969. package/dist/release/installed-artifacts.d.ts +12 -0
  970. package/dist/release/installed-artifacts.d.ts.map +1 -0
  971. package/dist/release/installed-artifacts.js +199 -0
  972. package/dist/release/installed-artifacts.js.map +1 -0
  973. package/dist/release/installed-graph.d.ts +28 -0
  974. package/dist/release/installed-graph.d.ts.map +1 -0
  975. package/dist/release/installed-graph.js +138 -0
  976. package/dist/release/installed-graph.js.map +1 -0
  977. package/dist/release/installed-origin.d.ts +42 -0
  978. package/dist/release/installed-origin.d.ts.map +1 -0
  979. package/dist/release/installed-origin.js +130 -0
  980. package/dist/release/installed-origin.js.map +1 -0
  981. package/dist/release/installed-release.d.ts +23 -0
  982. package/dist/release/installed-release.d.ts.map +1 -0
  983. package/dist/release/installed-release.js +141 -0
  984. package/dist/release/installed-release.js.map +1 -0
  985. package/dist/release/layout.d.ts +27 -32
  986. package/dist/release/layout.d.ts.map +1 -1
  987. package/dist/release/layout.js +162 -51
  988. package/dist/release/layout.js.map +1 -1
  989. package/dist/release/load.d.ts +3 -0
  990. package/dist/release/load.d.ts.map +1 -1
  991. package/dist/release/load.js +50 -8
  992. package/dist/release/load.js.map +1 -1
  993. package/dist/release/local-installation.d.ts +20 -0
  994. package/dist/release/local-installation.d.ts.map +1 -0
  995. package/dist/release/local-installation.js +298 -0
  996. package/dist/release/local-installation.js.map +1 -0
  997. package/dist/release/manifest.d.ts +87 -12
  998. package/dist/release/manifest.d.ts.map +1 -1
  999. package/dist/release/manifest.js +31 -7
  1000. package/dist/release/manifest.js.map +1 -1
  1001. package/dist/release/migration-programs.d.ts +12 -0
  1002. package/dist/release/migration-programs.d.ts.map +1 -0
  1003. package/dist/release/migration-programs.js +45 -0
  1004. package/dist/release/migration-programs.js.map +1 -0
  1005. package/dist/release/migrations.d.ts +12 -1
  1006. package/dist/release/migrations.d.ts.map +1 -1
  1007. package/dist/release/migrations.js +124 -9
  1008. package/dist/release/migrations.js.map +1 -1
  1009. package/dist/release/origin.d.ts +33 -0
  1010. package/dist/release/origin.d.ts.map +1 -0
  1011. package/dist/release/origin.js +253 -0
  1012. package/dist/release/origin.js.map +1 -0
  1013. package/dist/release/owner-admission.d.ts +15 -0
  1014. package/dist/release/owner-admission.d.ts.map +1 -0
  1015. package/dist/release/owner-admission.js +37 -0
  1016. package/dist/release/owner-admission.js.map +1 -0
  1017. package/dist/release/owner-executable.d.ts +3 -0
  1018. package/dist/release/owner-executable.d.ts.map +1 -0
  1019. package/dist/release/owner-executable.js +40 -0
  1020. package/dist/release/owner-executable.js.map +1 -0
  1021. package/dist/release/package-gates.d.ts +9 -0
  1022. package/dist/release/package-gates.d.ts.map +1 -0
  1023. package/dist/release/package-gates.js +42 -0
  1024. package/dist/release/package-gates.js.map +1 -0
  1025. package/dist/release/package-resolution.d.ts +4 -0
  1026. package/dist/release/package-resolution.d.ts.map +1 -0
  1027. package/dist/release/package-resolution.js +28 -0
  1028. package/dist/release/package-resolution.js.map +1 -0
  1029. package/dist/release/portable-archive.d.ts +12 -0
  1030. package/dist/release/portable-archive.d.ts.map +1 -0
  1031. package/dist/release/portable-archive.js +96 -0
  1032. package/dist/release/portable-archive.js.map +1 -0
  1033. package/dist/release/registry-download.d.ts +2 -0
  1034. package/dist/release/registry-download.d.ts.map +1 -0
  1035. package/dist/release/registry-download.js +157 -0
  1036. package/dist/release/registry-download.js.map +1 -0
  1037. package/dist/release/registry-framework.d.ts +39 -0
  1038. package/dist/release/registry-framework.d.ts.map +1 -0
  1039. package/dist/release/registry-framework.js +279 -0
  1040. package/dist/release/registry-framework.js.map +1 -0
  1041. package/dist/release/registry-transport.d.ts +27 -0
  1042. package/dist/release/registry-transport.d.ts.map +1 -0
  1043. package/dist/release/registry-transport.js +96 -0
  1044. package/dist/release/registry-transport.js.map +1 -0
  1045. package/dist/release/repository-identity.d.ts +12 -0
  1046. package/dist/release/repository-identity.d.ts.map +1 -0
  1047. package/dist/release/repository-identity.js +62 -0
  1048. package/dist/release/repository-identity.js.map +1 -0
  1049. package/dist/release/runtime-extractor.d.ts +4 -0
  1050. package/dist/release/runtime-extractor.d.ts.map +1 -0
  1051. package/dist/release/runtime-extractor.js +15 -0
  1052. package/dist/release/runtime-extractor.js.map +1 -0
  1053. package/dist/release/runtime-provenance.d.ts +40 -0
  1054. package/dist/release/runtime-provenance.d.ts.map +1 -0
  1055. package/dist/release/runtime-provenance.js +229 -0
  1056. package/dist/release/runtime-provenance.js.map +1 -0
  1057. package/dist/release/source.d.ts +12 -0
  1058. package/dist/release/source.d.ts.map +1 -0
  1059. package/dist/release/source.js +168 -0
  1060. package/dist/release/source.js.map +1 -0
  1061. package/dist/release/store.d.ts +13 -4
  1062. package/dist/release/store.d.ts.map +1 -1
  1063. package/dist/release/store.js +70 -14
  1064. package/dist/release/store.js.map +1 -1
  1065. package/dist/release/target-runtime.d.ts +19 -0
  1066. package/dist/release/target-runtime.d.ts.map +1 -0
  1067. package/dist/release/target-runtime.js +41 -0
  1068. package/dist/release/target-runtime.js.map +1 -0
  1069. package/dist/release/vendored-framework.d.ts +28 -0
  1070. package/dist/release/vendored-framework.d.ts.map +1 -0
  1071. package/dist/release/vendored-framework.js +98 -0
  1072. package/dist/release/vendored-framework.js.map +1 -0
  1073. package/dist/release/workspace-source.d.ts +3 -0
  1074. package/dist/release/workspace-source.d.ts.map +1 -0
  1075. package/dist/release/workspace-source.js +33 -0
  1076. package/dist/release/workspace-source.js.map +1 -0
  1077. package/dist/runtime/context.d.ts +29 -0
  1078. package/dist/runtime/context.d.ts.map +1 -0
  1079. package/dist/runtime/context.js +111 -0
  1080. package/dist/runtime/context.js.map +1 -0
  1081. package/dist/runtime/identity.d.ts +15 -0
  1082. package/dist/runtime/identity.d.ts.map +1 -0
  1083. package/dist/runtime/identity.js +33 -0
  1084. package/dist/runtime/identity.js.map +1 -0
  1085. package/dist/runtime/index.d.ts +3 -0
  1086. package/dist/runtime/index.d.ts.map +1 -0
  1087. package/dist/runtime/index.js +3 -0
  1088. package/dist/runtime/index.js.map +1 -0
  1089. package/dist/services/access.rpc-service.d.ts +41 -0
  1090. package/dist/services/access.rpc-service.d.ts.map +1 -0
  1091. package/dist/services/access.rpc-service.js +248 -0
  1092. package/dist/services/access.rpc-service.js.map +1 -0
  1093. package/dist/services/alert.service.d.ts +1 -1
  1094. package/dist/services/alert.service.d.ts.map +1 -1
  1095. package/dist/services/alert.service.js +1 -1
  1096. package/dist/services/anonymous-input.d.ts +0 -21
  1097. package/dist/services/anonymous-input.d.ts.map +1 -1
  1098. package/dist/services/anonymous-input.js +1 -27
  1099. package/dist/services/anonymous-input.js.map +1 -1
  1100. package/dist/services/audit.service.d.ts +10 -30
  1101. package/dist/services/audit.service.d.ts.map +1 -1
  1102. package/dist/services/audit.service.js +72 -18
  1103. package/dist/services/audit.service.js.map +1 -1
  1104. package/dist/services/auth-context.d.ts +2 -0
  1105. package/dist/services/auth-context.d.ts.map +1 -1
  1106. package/dist/services/auth-context.js +4 -1
  1107. package/dist/services/auth-context.js.map +1 -1
  1108. package/dist/services/auth.rpc-service.d.ts +3 -1
  1109. package/dist/services/auth.rpc-service.d.ts.map +1 -1
  1110. package/dist/services/auth.rpc-service.js +13 -3
  1111. package/dist/services/auth.rpc-service.js.map +1 -1
  1112. package/dist/services/auth.service.d.ts +11 -21
  1113. package/dist/services/auth.service.d.ts.map +1 -1
  1114. package/dist/services/auth.service.js +125 -139
  1115. package/dist/services/auth.service.js.map +1 -1
  1116. package/dist/services/backup-storage.d.ts +14 -0
  1117. package/dist/services/backup-storage.d.ts.map +1 -0
  1118. package/dist/services/backup-storage.js +195 -0
  1119. package/dist/services/backup-storage.js.map +1 -0
  1120. package/dist/services/backup.rpc-service.d.ts +2 -0
  1121. package/dist/services/backup.rpc-service.d.ts.map +1 -1
  1122. package/dist/services/backup.rpc-service.js +9 -0
  1123. package/dist/services/backup.rpc-service.js.map +1 -1
  1124. package/dist/services/backup.service.d.ts +33 -126
  1125. package/dist/services/backup.service.d.ts.map +1 -1
  1126. package/dist/services/backup.service.js +174 -153
  1127. package/dist/services/backup.service.js.map +1 -1
  1128. package/dist/services/bundle-builder.d.ts +12 -0
  1129. package/dist/services/bundle-builder.d.ts.map +1 -1
  1130. package/dist/services/bundle-builder.js +77 -62
  1131. package/dist/services/bundle-builder.js.map +1 -1
  1132. package/dist/services/bundle-overrides.d.ts +25 -0
  1133. package/dist/services/bundle-overrides.d.ts.map +1 -0
  1134. package/dist/services/bundle-overrides.js +96 -0
  1135. package/dist/services/bundle-overrides.js.map +1 -0
  1136. package/dist/services/daemon-authority.d.ts +16 -0
  1137. package/dist/services/daemon-authority.d.ts.map +1 -0
  1138. package/dist/services/daemon-authority.js +54 -0
  1139. package/dist/services/daemon-authority.js.map +1 -0
  1140. package/dist/services/daemon-principal.d.ts +31 -0
  1141. package/dist/services/daemon-principal.d.ts.map +1 -0
  1142. package/dist/services/daemon-principal.js +37 -0
  1143. package/dist/services/daemon-principal.js.map +1 -0
  1144. package/dist/services/deploy.service.d.ts +13 -57
  1145. package/dist/services/deploy.service.d.ts.map +1 -1
  1146. package/dist/services/deploy.service.js +15 -263
  1147. package/dist/services/deploy.service.js.map +1 -1
  1148. package/dist/services/event-broadcaster.rpc-service.d.ts +7 -1
  1149. package/dist/services/event-broadcaster.rpc-service.d.ts.map +1 -1
  1150. package/dist/services/event-broadcaster.rpc-service.js +89 -11
  1151. package/dist/services/event-broadcaster.rpc-service.js.map +1 -1
  1152. package/dist/services/fleet.rpc-service.d.ts +0 -6
  1153. package/dist/services/fleet.rpc-service.d.ts.map +1 -1
  1154. package/dist/services/fleet.rpc-service.js +0 -13
  1155. package/dist/services/fleet.rpc-service.js.map +1 -1
  1156. package/dist/services/health-check.service.d.ts +1 -1
  1157. package/dist/services/health-check.service.d.ts.map +1 -1
  1158. package/dist/services/health-check.service.js +30 -5
  1159. package/dist/services/health-check.service.js.map +1 -1
  1160. package/dist/services/host-upgrade.d.ts +55 -3
  1161. package/dist/services/host-upgrade.d.ts.map +1 -1
  1162. package/dist/services/host-upgrade.js +167 -16
  1163. package/dist/services/host-upgrade.js.map +1 -1
  1164. package/dist/services/infrastructure.rpc-service.d.ts +9 -2
  1165. package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
  1166. package/dist/services/infrastructure.rpc-service.js +117 -36
  1167. package/dist/services/infrastructure.rpc-service.js.map +1 -1
  1168. package/dist/services/kernel-catalog.d.ts +14 -0
  1169. package/dist/services/kernel-catalog.d.ts.map +1 -0
  1170. package/dist/services/kernel-catalog.js +172 -0
  1171. package/dist/services/kernel-catalog.js.map +1 -0
  1172. package/dist/services/kernel-distribution.d.ts +5 -0
  1173. package/dist/services/kernel-distribution.d.ts.map +1 -0
  1174. package/dist/services/kernel-distribution.js +49 -0
  1175. package/dist/services/kernel-distribution.js.map +1 -0
  1176. package/dist/services/kernel-rollout.service.d.ts +55 -0
  1177. package/dist/services/kernel-rollout.service.d.ts.map +1 -0
  1178. package/dist/services/kernel-rollout.service.js +394 -0
  1179. package/dist/services/kernel-rollout.service.js.map +1 -0
  1180. package/dist/services/local-bundle.d.ts.map +1 -1
  1181. package/dist/services/local-bundle.js +3 -1
  1182. package/dist/services/local-bundle.js.map +1 -1
  1183. package/dist/services/log-collector.rpc-service.d.ts +5 -11
  1184. package/dist/services/log-collector.rpc-service.d.ts.map +1 -1
  1185. package/dist/services/log-collector.rpc-service.js +6 -6
  1186. package/dist/services/log-collector.rpc-service.js.map +1 -1
  1187. package/dist/services/log-collector.service.d.ts +39 -109
  1188. package/dist/services/log-collector.service.d.ts.map +1 -1
  1189. package/dist/services/log-collector.service.js +286 -261
  1190. package/dist/services/log-collector.service.js.map +1 -1
  1191. package/dist/services/log-query.d.ts +6 -0
  1192. package/dist/services/log-query.d.ts.map +1 -0
  1193. package/dist/services/log-query.js +46 -0
  1194. package/dist/services/log-query.js.map +1 -0
  1195. package/dist/services/managed-rollout.d.ts +54 -0
  1196. package/dist/services/managed-rollout.d.ts.map +1 -0
  1197. package/dist/services/managed-rollout.js +124 -0
  1198. package/dist/services/managed-rollout.js.map +1 -0
  1199. package/dist/services/metrics.rpc-service.d.ts +23 -0
  1200. package/dist/services/metrics.rpc-service.d.ts.map +1 -0
  1201. package/dist/services/metrics.rpc-service.js +73 -0
  1202. package/dist/services/metrics.rpc-service.js.map +1 -0
  1203. package/dist/services/migration-process.d.ts +32 -0
  1204. package/dist/services/migration-process.d.ts.map +1 -0
  1205. package/dist/services/migration-process.js +71 -0
  1206. package/dist/services/migration-process.js.map +1 -0
  1207. package/dist/services/migration-target.d.ts +57 -0
  1208. package/dist/services/migration-target.d.ts.map +1 -0
  1209. package/dist/services/migration-target.js +221 -0
  1210. package/dist/services/migration-target.js.map +1 -0
  1211. package/dist/services/node-health.repository.d.ts +2 -59
  1212. package/dist/services/node-health.repository.d.ts.map +1 -1
  1213. package/dist/services/node-health.repository.js.map +1 -1
  1214. package/dist/services/node-host-key.d.ts +9 -0
  1215. package/dist/services/node-host-key.d.ts.map +1 -0
  1216. package/dist/services/node-host-key.js +17 -0
  1217. package/dist/services/node-host-key.js.map +1 -0
  1218. package/dist/services/node-manager.rpc-service.d.ts +44 -37
  1219. package/dist/services/node-manager.rpc-service.d.ts.map +1 -1
  1220. package/dist/services/node-manager.rpc-service.js +208 -37
  1221. package/dist/services/node-manager.rpc-service.js.map +1 -1
  1222. package/dist/services/node-manager.service.d.ts +3 -0
  1223. package/dist/services/node-manager.service.d.ts.map +1 -1
  1224. package/dist/services/node-manager.service.js +78 -27
  1225. package/dist/services/node-manager.service.js.map +1 -1
  1226. package/dist/services/node-registration.d.ts +17 -0
  1227. package/dist/services/node-registration.d.ts.map +1 -0
  1228. package/dist/services/node-registration.js +73 -0
  1229. package/dist/services/node-registration.js.map +1 -0
  1230. package/dist/services/node-upgrade.service.d.ts +2 -28
  1231. package/dist/services/node-upgrade.service.d.ts.map +1 -1
  1232. package/dist/services/node-upgrade.service.js +2 -3
  1233. package/dist/services/node-upgrade.service.js.map +1 -1
  1234. package/dist/services/pipeline-definition.d.ts +14 -0
  1235. package/dist/services/pipeline-definition.d.ts.map +1 -0
  1236. package/dist/services/pipeline-definition.js +77 -0
  1237. package/dist/services/pipeline-definition.js.map +1 -0
  1238. package/dist/services/pipeline.rpc-service.d.ts +10 -3
  1239. package/dist/services/pipeline.rpc-service.d.ts.map +1 -1
  1240. package/dist/services/pipeline.rpc-service.js +38 -14
  1241. package/dist/services/pipeline.rpc-service.js.map +1 -1
  1242. package/dist/services/pipeline.service.d.ts +30 -46
  1243. package/dist/services/pipeline.service.d.ts.map +1 -1
  1244. package/dist/services/pipeline.service.js +401 -344
  1245. package/dist/services/pipeline.service.js.map +1 -1
  1246. package/dist/services/project.rpc-service.d.ts +212 -14
  1247. package/dist/services/project.rpc-service.d.ts.map +1 -1
  1248. package/dist/services/project.rpc-service.js +303 -31
  1249. package/dist/services/project.rpc-service.js.map +1 -1
  1250. package/dist/services/project.service.d.ts +174 -56
  1251. package/dist/services/project.service.d.ts.map +1 -1
  1252. package/dist/services/project.service.js +1962 -653
  1253. package/dist/services/project.service.js.map +1 -1
  1254. package/dist/services/release.rpc-service.d.ts +5 -18
  1255. package/dist/services/release.rpc-service.d.ts.map +1 -1
  1256. package/dist/services/release.rpc-service.js +34 -11
  1257. package/dist/services/release.rpc-service.js.map +1 -1
  1258. package/dist/services/release.service.d.ts +2 -47
  1259. package/dist/services/release.service.d.ts.map +1 -1
  1260. package/dist/services/release.service.js +26 -2
  1261. package/dist/services/release.service.js.map +1 -1
  1262. package/dist/services/remote-deployer.service.d.ts +41 -113
  1263. package/dist/services/remote-deployer.service.d.ts.map +1 -1
  1264. package/dist/services/remote-deployer.service.js +415 -378
  1265. package/dist/services/remote-deployer.service.js.map +1 -1
  1266. package/dist/services/remote-provisioner.d.ts +1 -1
  1267. package/dist/services/remote-provisioner.js +1 -1
  1268. package/dist/services/replication-evidence.d.ts +24 -0
  1269. package/dist/services/replication-evidence.d.ts.map +1 -0
  1270. package/dist/services/replication-evidence.js +61 -0
  1271. package/dist/services/replication-evidence.js.map +1 -0
  1272. package/dist/services/replication-history.d.ts +80 -0
  1273. package/dist/services/replication-history.d.ts.map +1 -0
  1274. package/dist/services/replication-history.js +279 -0
  1275. package/dist/services/replication-history.js.map +1 -0
  1276. package/dist/services/replication-reconciliation.d.ts +74 -0
  1277. package/dist/services/replication-reconciliation.d.ts.map +1 -0
  1278. package/dist/services/replication-reconciliation.js +351 -0
  1279. package/dist/services/replication-reconciliation.js.map +1 -0
  1280. package/dist/services/slave-storage.service.d.ts +2 -19
  1281. package/dist/services/slave-storage.service.d.ts.map +1 -1
  1282. package/dist/services/slave-storage.service.js +19 -6
  1283. package/dist/services/slave-storage.service.js.map +1 -1
  1284. package/dist/services/static-carry-forward.d.ts +2 -2
  1285. package/dist/services/static-carry-forward.d.ts.map +1 -1
  1286. package/dist/services/static-carry-forward.js +10 -3
  1287. package/dist/services/static-carry-forward.js.map +1 -1
  1288. package/dist/services/sync-summary.d.ts.map +1 -1
  1289. package/dist/services/sync-summary.js +1 -18
  1290. package/dist/services/sync-summary.js.map +1 -1
  1291. package/dist/services/sync.rpc-service.d.ts +17 -20
  1292. package/dist/services/sync.rpc-service.d.ts.map +1 -1
  1293. package/dist/services/sync.rpc-service.js +23 -37
  1294. package/dist/services/sync.rpc-service.js.map +1 -1
  1295. package/dist/services/sync.service.d.ts +53 -157
  1296. package/dist/services/sync.service.d.ts.map +1 -1
  1297. package/dist/services/sync.service.js +239 -337
  1298. package/dist/services/sync.service.js.map +1 -1
  1299. package/dist/services/system-info.rpc-service.d.ts +3 -1
  1300. package/dist/services/system-info.rpc-service.d.ts.map +1 -1
  1301. package/dist/services/system-info.rpc-service.js +5 -5
  1302. package/dist/services/system-info.rpc-service.js.map +1 -1
  1303. package/dist/services/system-info.service.d.ts +3 -1
  1304. package/dist/services/system-info.service.d.ts.map +1 -1
  1305. package/dist/services/system-info.service.js +17 -9
  1306. package/dist/services/system-info.service.js.map +1 -1
  1307. package/dist/services/telemetry.rpc-service.d.ts +4 -23
  1308. package/dist/services/telemetry.rpc-service.d.ts.map +1 -1
  1309. package/dist/services/telemetry.rpc-service.js +4 -32
  1310. package/dist/services/telemetry.rpc-service.js.map +1 -1
  1311. package/dist/services/telemetry.service.d.ts +2 -30
  1312. package/dist/services/telemetry.service.d.ts.map +1 -1
  1313. package/dist/services/telemetry.service.js +25 -131
  1314. package/dist/services/telemetry.service.js.map +1 -1
  1315. package/dist/services/terminal-ssh.d.ts +16 -0
  1316. package/dist/services/terminal-ssh.d.ts.map +1 -0
  1317. package/dist/services/terminal-ssh.js +101 -0
  1318. package/dist/services/terminal-ssh.js.map +1 -0
  1319. package/dist/services/terminal.rpc-service.d.ts +84 -0
  1320. package/dist/services/terminal.rpc-service.d.ts.map +1 -0
  1321. package/dist/services/terminal.rpc-service.js +508 -0
  1322. package/dist/services/terminal.rpc-service.js.map +1 -0
  1323. package/dist/services/trace-collector.rpc-service.js +3 -3
  1324. package/dist/services/trace-collector.rpc-service.js.map +1 -1
  1325. package/dist/services/wait-for-postgres.d.ts +1 -1
  1326. package/dist/services/wait-for-postgres.d.ts.map +1 -1
  1327. package/dist/services/wait-for-postgres.js +13 -28
  1328. package/dist/services/wait-for-postgres.js.map +1 -1
  1329. package/dist/shared/build-stamp.d.ts +4 -1
  1330. package/dist/shared/build-stamp.d.ts.map +1 -1
  1331. package/dist/shared/build-stamp.js +14 -6
  1332. package/dist/shared/build-stamp.js.map +1 -1
  1333. package/dist/shared/describe-error.d.ts +1 -21
  1334. package/dist/shared/describe-error.d.ts.map +1 -1
  1335. package/dist/shared/describe-error.js +1 -38
  1336. package/dist/shared/describe-error.js.map +1 -1
  1337. package/dist/shared/dto/access.d.ts +55 -0
  1338. package/dist/shared/dto/access.d.ts.map +1 -0
  1339. package/dist/shared/dto/access.js +2 -0
  1340. package/dist/shared/dto/access.js.map +1 -0
  1341. package/dist/shared/dto/audit.d.ts +32 -0
  1342. package/dist/shared/dto/audit.d.ts.map +1 -0
  1343. package/dist/shared/dto/audit.js +3 -0
  1344. package/dist/shared/dto/audit.js.map +1 -0
  1345. package/dist/shared/dto/auth.d.ts +2 -0
  1346. package/dist/shared/dto/auth.d.ts.map +1 -1
  1347. package/dist/shared/dto/auth.js +5 -1
  1348. package/dist/shared/dto/auth.js.map +1 -1
  1349. package/dist/shared/dto/backend.d.ts +50 -0
  1350. package/dist/shared/dto/backend.d.ts.map +1 -0
  1351. package/dist/shared/dto/backend.js +3 -0
  1352. package/dist/shared/dto/backend.js.map +1 -0
  1353. package/dist/shared/dto/backups.d.ts +99 -0
  1354. package/dist/shared/dto/backups.d.ts.map +1 -1
  1355. package/dist/shared/dto/cluster.d.ts +44 -0
  1356. package/dist/shared/dto/cluster.d.ts.map +1 -0
  1357. package/dist/shared/dto/cluster.js +3 -0
  1358. package/dist/shared/dto/cluster.js.map +1 -0
  1359. package/dist/shared/dto/files.d.ts +328 -0
  1360. package/dist/shared/dto/files.d.ts.map +1 -0
  1361. package/dist/shared/dto/files.js +2 -0
  1362. package/dist/shared/dto/files.js.map +1 -0
  1363. package/dist/shared/dto/health.d.ts +59 -0
  1364. package/dist/shared/dto/health.d.ts.map +1 -1
  1365. package/dist/shared/dto/installed-release.d.ts +40 -0
  1366. package/dist/shared/dto/installed-release.d.ts.map +1 -0
  1367. package/dist/shared/dto/installed-release.js +2 -0
  1368. package/dist/shared/dto/installed-release.js.map +1 -0
  1369. package/dist/shared/dto/kernel-catalog.d.ts +11 -0
  1370. package/dist/shared/dto/kernel-catalog.d.ts.map +1 -0
  1371. package/dist/shared/dto/kernel-catalog.js +2 -0
  1372. package/dist/shared/dto/kernel-catalog.js.map +1 -0
  1373. package/dist/shared/dto/logs.d.ts +21 -0
  1374. package/dist/shared/dto/logs.d.ts.map +1 -1
  1375. package/dist/shared/dto/metrics.d.ts +4 -64
  1376. package/dist/shared/dto/metrics.d.ts.map +1 -1
  1377. package/dist/shared/dto/metrics.js +0 -15
  1378. package/dist/shared/dto/metrics.js.map +1 -1
  1379. package/dist/shared/dto/nodes.d.ts +96 -22
  1380. package/dist/shared/dto/nodes.d.ts.map +1 -1
  1381. package/dist/shared/dto/pipelines.d.ts +22 -1
  1382. package/dist/shared/dto/pipelines.d.ts.map +1 -1
  1383. package/dist/shared/dto/project.d.ts +289 -30
  1384. package/dist/shared/dto/project.d.ts.map +1 -1
  1385. package/dist/shared/dto/releases.d.ts +59 -0
  1386. package/dist/shared/dto/releases.d.ts.map +1 -0
  1387. package/dist/shared/dto/releases.js +2 -0
  1388. package/dist/shared/dto/releases.js.map +1 -0
  1389. package/dist/shared/dto/services.d.ts +174 -37
  1390. package/dist/shared/dto/services.d.ts.map +1 -1
  1391. package/dist/shared/dto/services.js +15 -6
  1392. package/dist/shared/dto/services.js.map +1 -1
  1393. package/dist/shared/dto/stack-diagnosis.d.ts +20 -0
  1394. package/dist/shared/dto/stack-diagnosis.d.ts.map +1 -0
  1395. package/dist/shared/dto/stack-diagnosis.js +8 -0
  1396. package/dist/shared/dto/stack-diagnosis.js.map +1 -0
  1397. package/dist/shared/dto/stack-execution.d.ts +65 -0
  1398. package/dist/shared/dto/stack-execution.d.ts.map +1 -0
  1399. package/dist/shared/dto/stack-execution.js +33 -0
  1400. package/dist/shared/dto/stack-execution.js.map +1 -0
  1401. package/dist/shared/dto/system-info.d.ts +11 -0
  1402. package/dist/shared/dto/system-info.d.ts.map +1 -1
  1403. package/dist/shared/dto/system-info.js +7 -1
  1404. package/dist/shared/dto/system-info.js.map +1 -1
  1405. package/dist/shared/dto/terminal.d.ts +40 -0
  1406. package/dist/shared/dto/terminal.d.ts.map +1 -0
  1407. package/dist/shared/dto/terminal.js +2 -0
  1408. package/dist/shared/dto/terminal.js.map +1 -0
  1409. package/dist/shared/dto/topology.d.ts +96 -0
  1410. package/dist/shared/dto/topology.d.ts.map +1 -0
  1411. package/dist/shared/dto/topology.js +2 -0
  1412. package/dist/shared/dto/topology.js.map +1 -0
  1413. package/dist/shared/emergency-output.d.ts +3 -0
  1414. package/dist/shared/emergency-output.d.ts.map +1 -0
  1415. package/dist/shared/emergency-output.js +22 -0
  1416. package/dist/shared/emergency-output.js.map +1 -0
  1417. package/dist/shared/env-config.d.ts +2 -0
  1418. package/dist/shared/env-config.d.ts.map +1 -1
  1419. package/dist/shared/env-config.js +1 -0
  1420. package/dist/shared/env-config.js.map +1 -1
  1421. package/dist/shared/events.d.ts +2 -0
  1422. package/dist/shared/events.d.ts.map +1 -1
  1423. package/dist/shared/events.js.map +1 -1
  1424. package/dist/shared/execution-identity.d.ts +39 -0
  1425. package/dist/shared/execution-identity.d.ts.map +1 -0
  1426. package/dist/shared/execution-identity.js +160 -0
  1427. package/dist/shared/execution-identity.js.map +1 -0
  1428. package/dist/shared/file-policy.d.ts +5 -0
  1429. package/dist/shared/file-policy.d.ts.map +1 -0
  1430. package/dist/shared/file-policy.js +18 -0
  1431. package/dist/shared/file-policy.js.map +1 -0
  1432. package/dist/shared/framework-source.d.ts +11 -0
  1433. package/dist/shared/framework-source.d.ts.map +1 -0
  1434. package/dist/shared/framework-source.js +31 -0
  1435. package/dist/shared/framework-source.js.map +1 -0
  1436. package/dist/shared/fresh-module-reader.d.ts +40 -0
  1437. package/dist/shared/fresh-module-reader.d.ts.map +1 -0
  1438. package/dist/shared/fresh-module-reader.js +257 -0
  1439. package/dist/shared/fresh-module-reader.js.map +1 -0
  1440. package/dist/shared/host-boot-identity.d.ts +12 -0
  1441. package/dist/shared/host-boot-identity.d.ts.map +1 -0
  1442. package/dist/shared/host-boot-identity.js +89 -0
  1443. package/dist/shared/host-boot-identity.js.map +1 -0
  1444. package/dist/shared/kernel-file-lock.d.ts +3 -0
  1445. package/dist/shared/kernel-file-lock.d.ts.map +1 -0
  1446. package/dist/shared/kernel-file-lock.js +53 -0
  1447. package/dist/shared/kernel-file-lock.js.map +1 -0
  1448. package/dist/shared/linux-file-lock.d.ts +4 -0
  1449. package/dist/shared/linux-file-lock.d.ts.map +1 -0
  1450. package/dist/shared/linux-file-lock.js +36 -0
  1451. package/dist/shared/linux-file-lock.js.map +1 -0
  1452. package/dist/shared/log-ingestion-reading.d.ts +9 -0
  1453. package/dist/shared/log-ingestion-reading.d.ts.map +1 -0
  1454. package/dist/shared/log-ingestion-reading.js +32 -0
  1455. package/dist/shared/log-ingestion-reading.js.map +1 -0
  1456. package/dist/shared/log-levels.d.ts +19 -0
  1457. package/dist/shared/log-levels.d.ts.map +1 -0
  1458. package/dist/shared/log-levels.js +23 -0
  1459. package/dist/shared/log-levels.js.map +1 -0
  1460. package/dist/shared/metrics-storage-reading.d.ts +8 -0
  1461. package/dist/shared/metrics-storage-reading.d.ts.map +1 -0
  1462. package/dist/shared/metrics-storage-reading.js +27 -0
  1463. package/dist/shared/metrics-storage-reading.js.map +1 -0
  1464. package/dist/shared/pnpm.d.ts.map +1 -1
  1465. package/dist/shared/pnpm.js +7 -0
  1466. package/dist/shared/pnpm.js.map +1 -1
  1467. package/dist/shared/pool-scale.d.ts +10 -0
  1468. package/dist/shared/pool-scale.d.ts.map +1 -0
  1469. package/dist/shared/pool-scale.js +25 -0
  1470. package/dist/shared/pool-scale.js.map +1 -0
  1471. package/dist/shared/private-files.d.ts +10 -22
  1472. package/dist/shared/private-files.d.ts.map +1 -1
  1473. package/dist/shared/private-files.js +95 -82
  1474. package/dist/shared/private-files.js.map +1 -1
  1475. package/dist/shared/relay-reading.d.ts +19 -0
  1476. package/dist/shared/relay-reading.d.ts.map +1 -0
  1477. package/dist/shared/relay-reading.js +22 -0
  1478. package/dist/shared/relay-reading.js.map +1 -0
  1479. package/dist/shared/release-reading.d.ts +124 -32
  1480. package/dist/shared/release-reading.d.ts.map +1 -1
  1481. package/dist/shared/release-reading.js +208 -28
  1482. package/dist/shared/release-reading.js.map +1 -1
  1483. package/dist/shared/roles.d.ts +19 -18
  1484. package/dist/shared/roles.d.ts.map +1 -1
  1485. package/dist/shared/roles.js +21 -26
  1486. package/dist/shared/roles.js.map +1 -1
  1487. package/dist/shared/runtime-artifact.d.ts +20 -0
  1488. package/dist/shared/runtime-artifact.d.ts.map +1 -0
  1489. package/dist/shared/runtime-artifact.js +83 -0
  1490. package/dist/shared/runtime-artifact.js.map +1 -0
  1491. package/dist/shared/stack-reading.d.ts +18 -0
  1492. package/dist/shared/stack-reading.d.ts.map +1 -0
  1493. package/dist/shared/stack-reading.js +15 -0
  1494. package/dist/shared/stack-reading.js.map +1 -0
  1495. package/dist/shared/sync-reading.d.ts +30 -38
  1496. package/dist/shared/sync-reading.d.ts.map +1 -1
  1497. package/dist/shared/sync-reading.js +90 -29
  1498. package/dist/shared/sync-reading.js.map +1 -1
  1499. package/dist/shared/tokens.d.ts +3 -1
  1500. package/dist/shared/tokens.d.ts.map +1 -1
  1501. package/dist/shared/tokens.js +1 -0
  1502. package/dist/shared/tokens.js.map +1 -1
  1503. package/dist/supervisor/restart-policy.d.ts.map +1 -1
  1504. package/dist/supervisor/restart-policy.js +7 -5
  1505. package/dist/supervisor/restart-policy.js.map +1 -1
  1506. package/dist/webapp/console-container.d.ts +7 -0
  1507. package/dist/webapp/console-container.d.ts.map +1 -0
  1508. package/dist/webapp/console-container.js +23 -0
  1509. package/dist/webapp/console-container.js.map +1 -0
  1510. package/dist/webapp/webapp.service.d.ts.map +1 -1
  1511. package/dist/webapp/webapp.service.js +71 -24
  1512. package/dist/webapp/webapp.service.js.map +1 -1
  1513. package/dist/workers/health-monitor.service.d.ts.map +1 -1
  1514. package/dist/workers/health-monitor.service.js +8 -1
  1515. package/dist/workers/health-monitor.service.js.map +1 -1
  1516. package/dist/workers/types.d.ts +2 -0
  1517. package/dist/workers/types.d.ts.map +1 -1
  1518. package/package.json +106 -44
  1519. package/webapp/dist/BUILD.json +4 -3
  1520. package/webapp/dist/assets/access-48hkC2-P.js +1 -0
  1521. package/webapp/dist/assets/access-jWkHxGc7.js +1 -0
  1522. package/webapp/dist/assets/admin-data-table-BKSDI8sU.js +1 -0
  1523. package/webapp/dist/assets/alert-expression-CSblScoa.js +1 -0
  1524. package/webapp/dist/assets/alert-notify-settings-C_CHZrFm.js +1 -0
  1525. package/webapp/dist/assets/alert-uBG6r9NW.js +1 -0
  1526. package/webapp/dist/assets/alerts-CuPQ9YQJ.js +1 -0
  1527. package/webapp/dist/assets/app-address-BCXfN59F.js +1 -0
  1528. package/webapp/dist/assets/apps-Drio028B.js +1 -0
  1529. package/webapp/dist/assets/audit-npSFJECe.js +1 -0
  1530. package/webapp/dist/assets/auth-CcgjP7hR.js +1 -0
  1531. package/webapp/dist/assets/card-DkH32gMY.js +1 -0
  1532. package/webapp/dist/assets/chart-BpGXlCJc.js +2 -0
  1533. package/webapp/dist/assets/charts-BY7qI6s2.js +41 -0
  1534. package/webapp/dist/assets/charts-CpBdHU_A.js +56 -0
  1535. package/webapp/dist/assets/client-DFnuv2z3.js +3 -0
  1536. package/webapp/dist/assets/code-editor-DK6Tmmxv.js +4 -0
  1537. package/webapp/dist/assets/comparison-bars-BtC0dHjF.js +1 -0
  1538. package/webapp/dist/assets/confirm-dialog-BbGsAz7_.js +1 -0
  1539. package/webapp/dist/assets/console-keyboard-D3YCA0yU.js +1 -0
  1540. package/webapp/dist/assets/constants-CyhngVXh.js +1 -0
  1541. package/webapp/dist/assets/containers-Bl8gAyZX.js +3 -0
  1542. package/webapp/dist/assets/context-FIuwpd9_.js +1 -0
  1543. package/webapp/dist/assets/controls-C5W8gHU4.js +1 -0
  1544. package/webapp/dist/assets/daemon-memory-BiEtr9jr.js +1 -0
  1545. package/webapp/dist/assets/dashboard-UHlet3H9.js +1 -0
  1546. package/webapp/dist/assets/dashboard-builder-67PZFqhE.js +1 -0
  1547. package/webapp/dist/assets/dayjs.min-DHwAvH5f.js +1 -0
  1548. package/webapp/dist/assets/deploy-release-dialog-BOXcD5Wo.js +1 -0
  1549. package/webapp/dist/assets/deployments-qV20Ak0P.js +1 -0
  1550. package/webapp/dist/assets/detail-B4TiBfxU.js +8 -0
  1551. package/webapp/dist/assets/detail-B85-Wees.js +2 -0
  1552. package/webapp/dist/assets/detail-CYS81K6Y.js +1 -0
  1553. package/webapp/dist/assets/dist-BUDn8hLh.js +11 -0
  1554. package/webapp/dist/assets/dist-Ci5pYRbx.js +1 -0
  1555. package/webapp/dist/assets/dist-DQmjHv4-.js +1 -0
  1556. package/webapp/dist/assets/dist-De9Fty7a.js +1 -0
  1557. package/webapp/dist/assets/dist-DsGmKwmk.js +23 -0
  1558. package/webapp/dist/assets/empty-content-7jOFhG4C.js +1 -0
  1559. package/webapp/dist/assets/error-boundary-CLLI8PWw.js +3 -0
  1560. package/webapp/dist/assets/event-fields-CiyqrRO8.js +1 -0
  1561. package/webapp/dist/assets/files-DigwXndm.js +2 -0
  1562. package/webapp/dist/assets/formatters-RnRy4bXm.js +1 -0
  1563. package/webapp/dist/assets/icons-OboZgWSt.js +1 -0
  1564. package/webapp/dist/assets/index-uq8Dkk4c.js +1 -0
  1565. package/webapp/dist/assets/label-B1SF0RTL.js +1 -0
  1566. package/webapp/dist/assets/log-level-filter-Bcn31D1F.js +1 -0
  1567. package/webapp/dist/assets/log-location-DuBjh6HQ.js +1 -0
  1568. package/webapp/dist/assets/logs-BqHOufk8.js +4 -0
  1569. package/webapp/dist/assets/metric-chart-M-kI-XOt.js +1 -0
  1570. package/webapp/dist/assets/metric-series-WxePniyw.js +1 -0
  1571. package/webapp/dist/assets/metrics-QIQaF7iS.js +1 -0
  1572. package/webapp/dist/assets/model-ZEXEHm0l.js +2 -0
  1573. package/webapp/dist/assets/mui-6ssfcZcz.js +1 -0
  1574. package/webapp/dist/assets/mui-AnEgAVjz.js +52 -0
  1575. package/webapp/dist/assets/mui-B1jrje6Z.js +1 -0
  1576. package/webapp/dist/assets/mui-BIz4LSVs.js +1 -0
  1577. package/webapp/dist/assets/mui-BTN91yBK.js +28 -0
  1578. package/webapp/dist/assets/mui-BXSXx9c9.js +124 -0
  1579. package/webapp/dist/assets/mui-BwsIrivo.js +37 -0
  1580. package/webapp/dist/assets/mui-C_hromOc.js +1 -0
  1581. package/webapp/dist/assets/mui-Cd711nOk.js +4 -0
  1582. package/webapp/dist/assets/mui-CdB_p2lt.js +10 -0
  1583. package/webapp/dist/assets/mui-DyT2n1lx.js +1 -0
  1584. package/webapp/dist/assets/mui-agG5dued.js +1 -0
  1585. package/webapp/dist/assets/mui-y44PQOfG.js +1 -0
  1586. package/webapp/dist/assets/nodes-BJPk02vv.js +7 -0
  1587. package/webapp/dist/assets/omnitron-logo-koMCbsCv.js +1 -0
  1588. package/webapp/dist/assets/operation-status-Bm1x1wTz.js +1 -0
  1589. package/webapp/dist/assets/page-breadcrumbs-fR7Jmpp4.js +1 -0
  1590. package/webapp/dist/assets/pipelines-T6Ch9LTw.js +3 -0
  1591. package/webapp/dist/assets/popper-m_9b0JRf.js +1 -0
  1592. package/webapp/dist/assets/preload-helper-DzCYNvzR.js +1 -0
  1593. package/webapp/dist/assets/project.store--G5pIF-J.js +1 -0
  1594. package/webapp/dist/assets/projects-YoBzcQ24.js +1 -0
  1595. package/webapp/dist/assets/react-BN8qLIAi.js +9 -0
  1596. package/webapp/dist/assets/react-CEuifcaQ.js +1 -0
  1597. package/webapp/dist/assets/react-apexcharts.esm-DoOt-pu4.js +1 -0
  1598. package/webapp/dist/assets/realtime.store-CBDO4Abj.js +1 -0
  1599. package/webapp/dist/assets/release-bits-B2a_wbft.js +1 -0
  1600. package/webapp/dist/assets/release-build-panel-BQmIdKW9.js +1 -0
  1601. package/webapp/dist/assets/release-deployment-state-DRrEI2pP.js +1 -0
  1602. package/webapp/dist/assets/release-wire-f2VuBoBF.js +1 -0
  1603. package/webapp/dist/assets/releases-Bp3CDgmo.js +2 -0
  1604. package/webapp/dist/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
  1605. package/webapp/dist/assets/search-input-C0MHI8vO.js +1 -0
  1606. package/webapp/dist/assets/services-BddvcFi8.js +1 -0
  1607. package/webapp/dist/assets/settings-B3egRvbI.js +1 -0
  1608. package/webapp/dist/assets/settled-pair-B-wgSZl6.js +1 -0
  1609. package/webapp/dist/assets/shared-styles-C0vN3I7_.js +1 -0
  1610. package/webapp/dist/assets/shell-command-C3pXf1vi.js +1 -0
  1611. package/webapp/dist/assets/sign-in-9q-Nfy83.js +1 -0
  1612. package/webapp/dist/assets/skeleton-BmWDJWF9.js +1 -0
  1613. package/webapp/dist/assets/snackbar-DSPmjMyi.js +1 -0
  1614. package/webapp/dist/assets/stack-health-status-BIv2C10x.js +1 -0
  1615. package/webapp/dist/assets/stack-start-notice-BVnMOwRQ.js +1 -0
  1616. package/webapp/dist/assets/stacks-IZ1mfizi.js +1 -0
  1617. package/webapp/dist/assets/storage-CKXnhqkt.js +1 -0
  1618. package/webapp/dist/assets/store-bqUBn2A4.js +1 -0
  1619. package/webapp/dist/assets/sync-reading-CN0jPKAg.js +1 -0
  1620. package/webapp/dist/assets/system-info-BFKNM-hf.js +1 -0
  1621. package/webapp/dist/assets/table-BOWT6Y9d.js +1 -0
  1622. package/webapp/dist/assets/table-viewport-Bp4pq-c_.js +1 -0
  1623. package/webapp/dist/assets/tabs-k5NPGLnJ.js +1 -0
  1624. package/webapp/dist/assets/terminal-BrP-ENHg.css +1 -0
  1625. package/webapp/dist/assets/terminal-QWBau-Us.js +62 -0
  1626. package/webapp/dist/assets/terminal-input-b7f2VoE_.js +2 -0
  1627. package/webapp/dist/assets/topology-CPey5dP6.js +8 -0
  1628. package/webapp/dist/assets/topology-metadata-dialog-BivuU2y6.js +2 -0
  1629. package/webapp/dist/assets/topology-routes.worker-BoYrIzAH.js +1 -0
  1630. package/webapp/dist/assets/traces-By0rdfDC.js +1 -0
  1631. package/webapp/dist/assets/use-copy-to-clipboard-BWYpToS3.js +1 -0
  1632. package/webapp/dist/assets/use-polled-resource-B22hhr5L.js +1 -0
  1633. package/webapp/dist/assets/use-stack-context-UkBsF-l7.js +1 -0
  1634. package/webapp/dist/assets/webapp-BnxOVfrc.css +1 -0
  1635. package/webapp/dist/assets/webapp-D-UhjFhS.js +35 -0
  1636. package/webapp/dist/console-version.json +1 -0
  1637. package/webapp/dist/index.html +54 -7
  1638. package/dist/release/daemon-omni.d.ts +0 -41
  1639. package/dist/release/daemon-omni.d.ts.map +0 -1
  1640. package/dist/release/daemon-omni.js +0 -59
  1641. package/dist/release/daemon-omni.js.map +0 -1
  1642. package/dist/services/netron-telemetry-transport.d.ts +0 -42
  1643. package/dist/services/netron-telemetry-transport.d.ts.map +0 -1
  1644. package/dist/services/netron-telemetry-transport.js +0 -93
  1645. package/dist/services/netron-telemetry-transport.js.map +0 -1
  1646. package/webapp/dist/assets/FormControlLabel-BbSxpQwN.js +0 -1
  1647. package/webapp/dist/assets/Grid-DizFZYue.js +0 -1
  1648. package/webapp/dist/assets/Switch-Bfiqzx8l.js +0 -1
  1649. package/webapp/dist/assets/alerts-DH_RVRS3.js +0 -1
  1650. package/webapp/dist/assets/apps-B96355sz.js +0 -1
  1651. package/webapp/dist/assets/audit-CYGtbi4k.js +0 -1
  1652. package/webapp/dist/assets/client-BmqFExWT.js +0 -1
  1653. package/webapp/dist/assets/constants-veKGFkC6.js +0 -1
  1654. package/webapp/dist/assets/containers-B_TpThln.js +0 -2
  1655. package/webapp/dist/assets/daemon-memory-CCPZ4cV_.js +0 -1
  1656. package/webapp/dist/assets/dashboard-DHf2_65N.js +0 -1
  1657. package/webapp/dist/assets/dashboard-builder-CWY2pjeG.js +0 -1
  1658. package/webapp/dist/assets/deploy-release-dialog-Dp-zpSAd.js +0 -1
  1659. package/webapp/dist/assets/deployments-tJI6O3fK.js +0 -1
  1660. package/webapp/dist/assets/detail-B2zP1Hto.js +0 -8
  1661. package/webapp/dist/assets/detail-CoIrwmsy.js +0 -1
  1662. package/webapp/dist/assets/detail-DWaGaOtL.js +0 -2
  1663. package/webapp/dist/assets/formatters-BqjL73N3.js +0 -1
  1664. package/webapp/dist/assets/fs-Ccl3sAcp.js +0 -1
  1665. package/webapp/dist/assets/index-Dvo0iprx.css +0 -1
  1666. package/webapp/dist/assets/index-vMnY_4jR.js +0 -412
  1667. package/webapp/dist/assets/logs-DTRjOfX8.js +0 -12
  1668. package/webapp/dist/assets/maplibre-gl-DowHoZAx.js +0 -814
  1669. package/webapp/dist/assets/metrics-UCajtdox.js +0 -2
  1670. package/webapp/dist/assets/nodes-DuTVU0f6.js +0 -7
  1671. package/webapp/dist/assets/pipelines-C8BeDcAl.js +0 -4
  1672. package/webapp/dist/assets/projects-Dp_zFfLY.js +0 -1
  1673. package/webapp/dist/assets/react-DlmbjZJV.js +0 -1
  1674. package/webapp/dist/assets/react-apexcharts.esm-C8pn45ml.js +0 -97
  1675. package/webapp/dist/assets/release-bits-BzTKz5fM.js +0 -1
  1676. package/webapp/dist/assets/release-build-panel-DcW9YcXQ.js +0 -1
  1677. package/webapp/dist/assets/releases-B4hcr1bE.js +0 -2
  1678. package/webapp/dist/assets/rolldown-runtime-XKyaHtl9.js +0 -1
  1679. package/webapp/dist/assets/settings-DIteZTbe.js +0 -1
  1680. package/webapp/dist/assets/settled-pair-C7SjZuns.js +0 -1
  1681. package/webapp/dist/assets/sign-in-D4kMBOHp.js +0 -1
  1682. package/webapp/dist/assets/stacks-Brnf-EB7.js +0 -1
  1683. package/webapp/dist/assets/sync-reading-C3PnJ7AX.js +0 -1
  1684. package/webapp/dist/assets/system-info-CFDNVKkU.js +0 -1
  1685. package/webapp/dist/assets/topology-7WeTUsDN.js +0 -12
  1686. package/webapp/dist/assets/traces-9c3gwCN-.js +0 -1
  1687. package/webapp/dist/assets/use-stack-context-CnA5FTHu.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,7 +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';
27
- import { describeProvisioning, summariseProvisioning } from '../infrastructure/provisioning-outcome.js';
67
+ import { describeProvisioning, summariseProvisioning, stackServiceObservation } from '../infrastructure/provisioning-outcome.js';
28
68
  import { waitForPostgres } from './wait-for-postgres.js';
29
69
  import { resolveStack, resolvedConfigToEnv } from '../project/config-resolver.js';
30
70
  import { resolveStartupOrder } from '../orchestrator/dependency-resolver.js';
@@ -34,8 +74,9 @@ import { NODE_STACK } from '../project/node-app-config.js';
34
74
  import { overlayCredentials } from '../infrastructure/node-credentials.js';
35
75
  import { readDeclaredConfig, withDeclaredConfig } from '../project/declared-config.js';
36
76
  import { bindService } from '../infrastructure/service-binding.js';
37
- import { RemoteDeployer, stackNodeToDeployTarget, withNodeCredentials, } from './remote-deployer.service.js';
77
+ import { RemoteDeployer, stackNodeToDeployTarget, withNodeCredentials, withDeploymentProgress, } from './remote-deployer.service.js';
38
78
  import { withNodeLeases } from './node-deploy-lease.js';
79
+ import { readDeploymentProgress } from '../shared/release-reading.js';
39
80
  import { DEFAULT_MAINTENANCE_TIMING, MaintenanceLock } from './maintenance-lock.js';
40
81
  import { gatewayRedisDb } from '../infrastructure/gateway-redis-db.js';
41
82
  /**
@@ -45,7 +86,21 @@ import { gatewayRedisDb } from '../infrastructure/gateway-redis-db.js';
45
86
  */
46
87
  const MAINTENANCE_PROBE_PATHS = ['/', '/api/main/health'];
47
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';
48
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
+ }
49
104
  /** Whether two paths name one directory — symlinks and `..` resolved. */
50
105
  function samePath(a, b) {
51
106
  const real = (p) => {
@@ -58,25 +113,6 @@ function samePath(a, b) {
58
113
  };
59
114
  return real(a) === real(b);
60
115
  }
61
- /**
62
- * The infrastructure a stack actually gets: the ecosystem's, plus its own.
63
- *
64
- * Shallow for the top-level keys — a stack that declares `postgres` replaces
65
- * the ecosystem's — and merged for `services`, because those are named
66
- * things and a stack adding one must not remove the rest.
67
- */
68
- export function mergeInfrastructure(ecosystemConfig, stackConfig) {
69
- if (!stackConfig.infrastructure && !ecosystemConfig.infrastructure)
70
- return undefined;
71
- return {
72
- ...ecosystemConfig.infrastructure,
73
- ...stackConfig.infrastructure,
74
- services: {
75
- ...ecosystemConfig.infrastructure?.services,
76
- ...stackConfig.infrastructure?.services,
77
- },
78
- };
79
- }
80
116
  // =============================================================================
81
117
  // ProjectService
82
118
  // =============================================================================
@@ -100,16 +136,93 @@ const CREDENTIALED_SERVICES = ['postgres', 'redis', 'minio'];
100
136
  export class ProjectService extends EventEmitter {
101
137
  logger;
102
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
+ }
103
203
  registry;
204
+ topologyControl;
205
+ bootstrapTopology = new Map();
206
+ bootstrapMetadata;
104
207
  configRegistry = new Map();
105
208
  stackStates = new Map();
106
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();
107
218
  slaveConnector = null;
108
219
  deployer;
109
220
  // Enabled-stacks reconciler state (see startEnabledStacksReconciler)
110
221
  resumeTimer = null;
111
222
  resumeBackoff = new Map();
112
- dockerWasAvailable = null;
223
+ reconcilingStacks = false;
224
+ handoffReconciling = false;
225
+ ownershipFrozen = false;
113
226
  syncService;
114
227
  secrets;
115
228
  audit;
@@ -127,6 +240,7 @@ export class ProjectService extends EventEmitter {
127
240
  super();
128
241
  this.logger = logger;
129
242
  this.orchestrator = orchestrator;
243
+ this.daemonStateStore = daemonStateStore;
130
244
  this.syncService = options.syncService;
131
245
  this.secrets = options.secrets;
132
246
  this.audit = options.audit;
@@ -135,11 +249,237 @@ export class ProjectService extends EventEmitter {
135
249
  // pid-lock, etc.). The registry itself is unchanged in API;
136
250
  // only its backing storage moved.
137
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
+ });
138
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
+ });
139
280
  // The deployer reaches nodes through the same SSH implementation as the
140
281
  // health checks, which is the only one that can present a password or a
141
282
  // key passphrase — see the note at the top of `remote-deployer.service.ts`.
142
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;
143
483
  }
144
484
  /**
145
485
  * Adopt the daemon's mesh connector.
@@ -175,7 +515,7 @@ export class ProjectService extends EventEmitter {
175
515
  const declared = stackNodeToDeployTarget(node);
176
516
  if (!this.resolveNodeCredentials)
177
517
  return declared;
178
- const registered = await this.resolveNodeCredentials(node.host).catch(() => null);
518
+ const registered = await this.resolveNodeCredentials(node.host, node.port ?? 9700, node.nodeId);
179
519
  return withNodeCredentials(declared, registered);
180
520
  }
181
521
  /**
@@ -213,17 +553,99 @@ export class ProjectService extends EventEmitter {
213
553
  * `deploying` from the first second to the last, and every event that said
214
554
  * WHERE it was reached a handler that re-emitted it to nobody.
215
555
  */
216
- getDeployProgress() {
217
- return this.deployer.getProgress();
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;
218
562
  }
219
563
  listProjects() {
220
- return this.registry.list().map((p) => this.toProjectInfo(p));
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, {});
221
643
  }
222
644
  getProject(name) {
223
645
  const project = this.registry.get(name);
224
646
  if (!project)
225
647
  throw new Error(`Project '${name}' not found`);
226
- return this.toProjectInfo(project);
648
+ return this.listProjects().find(record => record.name === (project.configuration?.project ?? name));
227
649
  }
228
650
  async addProject(name, projectPath) {
229
651
  // Already registered AT THIS PATH: read it again rather than refuse.
@@ -257,19 +679,32 @@ export class ProjectService extends EventEmitter {
257
679
  * running apps were redefined.
258
680
  */
259
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) {
260
693
  this.configRegistry.delete(name);
261
694
  const config = await this.loadProjectConfig(name);
262
695
  const stacks = this.resolveStacks(config, name);
263
696
  let redefined = 0;
264
697
  for (const [key, state] of this.stackStates) {
265
- if (!key.startsWith(`${name}/`) || state.status === 'stopped' || state.status === 'error')
698
+ if (!key.startsWith(`${name}/`) || state.status === 'stopped' || (state.status === 'error' && state.startedAt === null))
266
699
  continue;
267
700
  const stackConfig = stacks[state.stack];
268
701
  // A remote stack's apps run on its nodes; they are redefined there.
269
702
  if (!stackConfig || stackConfig.type !== 'local')
270
703
  continue;
271
- const definitions = await this.loadAppDefinitions(name, stackConfig, config);
272
- const entries = this.resolveStackApps(stackConfig, config);
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);
273
708
  const stackEnv = await this.stackEnvFor(name, state.stack, stackConfig, entries.map((e) => e.name));
274
709
  const build = this.stackEntryBuilder(name, state.stack, stackConfig, config, definitions, stackEnv);
275
710
  for (const entry of entries) {
@@ -284,6 +719,17 @@ export class ProjectService extends EventEmitter {
284
719
  }
285
720
  /** Refresh only local container monitoring from the current validated project config. */
286
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) {
287
733
  const project = this.registry.get(projectName);
288
734
  if (!project)
289
735
  throw new Error(`Project '${projectName}' not found`);
@@ -292,13 +738,21 @@ export class ProjectService extends EventEmitter {
292
738
  const stack = this.resolveStacks(config, projectName)[stackName];
293
739
  if (!stack || stack.type !== 'local')
294
740
  throw new Error('Ownership refresh requires a declared local stack');
295
- const definitions = await this.loadAppDefinitions(projectName, stack, config);
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);
296
752
  // The same requirements merge used at stack start. A failed fresh scan must
297
753
  // refuse a handoff rather than silently releasing auto-detected services.
298
- const { scanRequirements, buildInfraFromRequirements } = await import('../project/requirements-scanner.js');
299
- const auto = buildInfraFromRequirements(await scanRequirements(this.resolveStackApps(stack, config), project.path));
300
- const explicit = mergeInfrastructure(config, stack);
301
- const effective = { ...auto, ...explicit, services: { ...explicit?.services } };
754
+ const auto = buildInfraFromRequirements(requirementsFromDefinitions(definitions));
755
+ const effective = mergeInferredInfrastructure(auto, mergeInfrastructure(config, stack));
302
756
  return this.infraManager.refreshOwnership(projectName, stackName, stack, effective, definitions, project.path);
303
757
  }
304
758
  updateProject(name, data) {
@@ -308,7 +762,7 @@ export class ProjectService extends EventEmitter {
308
762
  if (data.path) {
309
763
  const nextPath = path.resolve(data.path);
310
764
  if (nextPath !== path.resolve(project.path)) {
311
- const running = this.getRunningStacks(name);
765
+ const running = this.getRunningStacks(name, true);
312
766
  if (running.length > 0) {
313
767
  throw new Error(`Project '${name}' has running stacks: ${running.join(', ')}. Stop them before changing its path.`);
314
768
  }
@@ -324,7 +778,7 @@ export class ProjectService extends EventEmitter {
324
778
  }
325
779
  removeProject(name) {
326
780
  // Stop all running stacks for this project first
327
- const runningStacks = this.getRunningStacks(name);
781
+ const runningStacks = this.getRunningStacks(name, true);
328
782
  if (runningStacks.length > 0) {
329
783
  throw new Error(`Project '${name}' has running stacks: ${runningStacks.join(', ')}. Stop them first.`);
330
784
  }
@@ -386,7 +840,7 @@ export class ProjectService extends EventEmitter {
386
840
  // Check for duplicates in both config stacks and user stacks
387
841
  const config = await this.loadProjectConfig(project);
388
842
  const allStacks = this.resolveStacks(config, project);
389
- if (allStacks[input.name]) {
843
+ if (allStacks[input.name] || this.registry.registrations(project).some(source => Object.values(source.configuration?.stacks ?? {}).includes(input.name))) {
390
844
  throw new Error(`Stack '${input.name}' already exists`);
391
845
  }
392
846
  // Build stack config
@@ -394,7 +848,12 @@ export class ProjectService extends EventEmitter {
394
848
  type: input.type,
395
849
  apps: input.apps,
396
850
  ...(input.nodeIds && input.type !== 'local' && {
397
- nodes: input.nodeIds.map((id) => ({ host: id, role: 'app' })),
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
+ }),
398
857
  }),
399
858
  };
400
859
  // For local stacks, auto-generate optimal infrastructure from app requirements
@@ -437,7 +896,6 @@ export class ProjectService extends EventEmitter {
437
896
  // `pull access denied` for, while the preset named the image the
438
897
  // resolver actually uses. A prefetch that pulls something else is
439
898
  // worse than none — it reports success and warms nothing.
440
- const { createDefaultRegistry } = await import('../infrastructure/presets/index.js');
441
899
  const presets = createDefaultRegistry();
442
900
  const images = new Set();
443
901
  for (const name of ['postgres', 'redis', 'minio']) {
@@ -483,6 +941,11 @@ export class ProjectService extends EventEmitter {
483
941
  if (state && state.status !== 'stopped') {
484
942
  throw new Error(`Stack '${stackName}' is currently ${state.status}. Stop it first.`);
485
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
+ }
486
949
  this.deleteUserStack(project, stackName);
487
950
  this.stackStates.delete(stateKey);
488
951
  this.logger.info({ project, stack: stackName }, 'Stack deleted');
@@ -494,14 +957,18 @@ export class ProjectService extends EventEmitter {
494
957
  throw new Error(`Project '${project}' not found`);
495
958
  return path.join(proj.path, 'omnitron.stacks.json');
496
959
  }
497
- loadUserStacks(project) {
498
- const filePath = this.getUserStacksPath(project);
960
+ loadUserStacks(project, filePath = this.getUserStacksPath(project)) {
499
961
  try {
500
962
  const raw = fs.readFileSync(filePath, 'utf-8');
501
- return JSON.parse(raw);
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;
502
967
  }
503
- catch {
504
- return {};
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 });
505
972
  }
506
973
  }
507
974
  saveUserStack(project, name, config) {
@@ -519,6 +986,368 @@ export class ProjectService extends EventEmitter {
519
986
  // ===========================================================================
520
987
  // Stacks
521
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
+ }
522
1351
  listStacks(projectName) {
523
1352
  const config = this.getLoadedConfig(projectName);
524
1353
  if (!config)
@@ -562,18 +1391,32 @@ export class ProjectService extends EventEmitter {
562
1391
  * asking for a stack that is already being started should end when it has
563
1392
  * been started, which is what the caller meant.
564
1393
  */
1394
+ rereading;
565
1395
  startsInFlight = new Map();
566
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');
567
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]);
568
1409
  const running = this.startsInFlight.get(inFlightKey);
569
1410
  if (running) {
570
1411
  this.logger.info({ project: projectName, stack: stackName }, 'This stack is already being started — joining the run in progress');
571
- return running;
1412
+ if (running.selection !== selection)
1413
+ throw new Error('Another stack start selected different launch code; retry after it completes');
1414
+ return running.promise;
572
1415
  }
573
- 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(() => {
574
1417
  this.startsInFlight.delete(inFlightKey);
575
1418
  });
576
- this.startsInFlight.set(inFlightKey, started);
1419
+ this.startsInFlight.set(inFlightKey, { selection, promise: started });
577
1420
  return started;
578
1421
  }
579
1422
  /**
@@ -601,10 +1444,10 @@ export class ProjectService extends EventEmitter {
601
1444
  * deploys nothing, and the reconciler re-asks a node that does not answer
602
1445
  * every few minutes — rows about looking, not about changing anything.
603
1446
  */
604
- async startStackOnce(projectName, stackName, source, allowDirty, releaseId, reinstall = false) {
1447
+ async startStackOnce(projectName, stackName, source, allowDirty, releaseId, reinstall = false, workingTree = false) {
605
1448
  const known = { attach: false };
606
1449
  try {
607
- return await this.runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall);
1450
+ return await this.runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall, workingTree);
608
1451
  }
609
1452
  catch (err) {
610
1453
  if (!known.attach) {
@@ -617,7 +1460,7 @@ export class ProjectService extends EventEmitter {
617
1460
  details: {
618
1461
  source,
619
1462
  ...(known.type ? { type: known.type } : {}),
620
- ...(releaseId ? { release: releaseId } : {}),
1463
+ ...((releaseId ?? known.release) ? { release: releaseId ?? known.release } : {}),
621
1464
  ...(known.commit !== undefined ? { commit: known.commit } : {}),
622
1465
  },
623
1466
  });
@@ -625,23 +1468,34 @@ export class ProjectService extends EventEmitter {
625
1468
  throw err;
626
1469
  }
627
1470
  }
628
- async runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall = false) {
1471
+ async runStackStart(projectName, stackName, source, allowDirty, releaseId, known, reinstall = false, workingTree = false) {
629
1472
  const config = await this.loadProjectConfig(projectName);
630
1473
  const stacks = this.resolveStacks(config, projectName);
631
1474
  const stackConfig = stacks[stackName];
632
1475
  if (!stackConfig)
633
1476
  throw new Error(`Stack '${stackName}' not found in project '${projectName}'`);
634
1477
  known.type = stackConfig.type;
1478
+ ownsStackInfrastructure(stackConfig); // Reject ambiguous ownership before attachment, build, migration or provisioning.
635
1479
  // A restart of this daemon re-attaches a remote stack; it never deploys
636
1480
  // one. See `attachRemoteStack`.
637
1481
  if (stackConfig.type !== 'local' && (source === 'boot' || source === 'auto-resume')) {
638
1482
  known.attach = true;
639
1483
  return this.attachRemoteStack(projectName, stackName, stackConfig, source);
640
1484
  }
1485
+ this.assertStartAuthority({ project: projectName, stack: stackName }, stackConfig);
641
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);
642
1493
  let release = null;
643
- if (releaseId) {
644
- release = await this.admitRelease(projectName, stackName, stackConfig, config, releaseId);
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');
645
1499
  }
646
1500
  else if (stackConfig.release?.mode === 'required') {
647
1501
  throw new Error(`${projectName}/${stackName} takes releases only: \`omnitron release build ${projectName} --for ${stackName}\`, ` +
@@ -696,7 +1550,7 @@ export class ProjectService extends EventEmitter {
696
1550
  // With a release the disk has already been held to the release's commit
697
1551
  // (`admitRelease`) — stricter than HEAD, and the artifacts are not built
698
1552
  // from it at all.
699
- if (stackConfig.type === 'remote' && !release) {
1553
+ if (stackConfig.type !== 'local' && !release) {
700
1554
  if (!allowDirty) {
701
1555
  for (const { root, tree: t } of trees) {
702
1556
  const refusal = refusalForDirtyTree(t, `${projectName}/${stackName}`);
@@ -714,10 +1568,32 @@ export class ProjectService extends EventEmitter {
714
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');
715
1569
  }
716
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
+ }
717
1591
  const stateKey = `${projectName}/${stackName}`;
718
1592
  const existing = this.stackStates.get(stateKey);
719
1593
  // Not for a release: putting a release onto a running stack is the point.
720
- if (existing?.status === 'running' && !release) {
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)) {
721
1597
  // `running` is a claim about the past, and nothing revises it when the
722
1598
  // apps underneath fall over — so this short-circuit used to hand the
723
1599
  // operator a stack it had not touched. `toStackInfo` reads live
@@ -743,7 +1619,9 @@ export class ProjectService extends EventEmitter {
743
1619
  // wrapper returns the info untouched.
744
1620
  const current = await this.withRemoteAppStatuses(projectName, this.toStackInfo(projectName, stackName, stackConfig));
745
1621
  const down = current.apps.filter((a) => a.status !== 'online');
746
- if (down.length === 0) {
1622
+ if (down.length === 0 && !current.notUp?.length) {
1623
+ if (existing)
1624
+ existing.status = 'running';
747
1625
  this.logger.warn({ project: projectName, stack: stackName }, 'Stack already running');
748
1626
  return current;
749
1627
  }
@@ -770,21 +1648,40 @@ export class ProjectService extends EventEmitter {
770
1648
  this.emit('stack:starting', projectName, stackName, stackConfig.type);
771
1649
  let reach = null;
772
1650
  try {
1651
+ this.assertStackMutable(stackConfig.managedOrigin ?? { project: projectName, stack: stackName });
773
1652
  if (stackConfig.type === 'local') {
774
- await this.startLocalStack(projectName, stackName, stackConfig, config);
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
+ }
775
1675
  }
776
- else if (stackConfig.type === 'remote') {
1676
+ else {
1677
+ this.rememberStackTargets(projectName, stackName, stackConfig, config);
777
1678
  reach = await this.startRemoteStack(projectName, stackName, stackConfig, config, release, reinstall);
778
1679
  }
779
- else if (stackConfig.type === 'cluster') {
780
- await this.startClusterStack(projectName, stackName, stackConfig, config);
781
- }
782
1680
  state.status = 'running';
783
1681
  state.startedAt = Date.now();
784
1682
  // Update registry with enabled stack
785
1683
  this.updateEnabledStacks(projectName, stackName, true);
786
- this.emit('stack:started', projectName, stackName, stackConfig.type);
787
- this.logger.info({ project: projectName, stack: stackName, source }, 'Stack started');
1684
+ this.markPriorStopSuperseded(projectName, stackName);
788
1685
  const info = this.toStackInfo(projectName, stackName, stackConfig);
789
1686
  // What did not come up, said in the row. A boot at 12:14 UTC on
790
1687
  // 2026-09-23 started sample/dev with payments and messaging errored and
@@ -792,11 +1689,22 @@ export class ProjectService extends EventEmitter {
792
1689
  // apps never did. For a local stack the apps are this daemon's; for a
793
1690
  // remote one, what did not happen is a node that was skipped.
794
1691
  const notUp = stackConfig.type === 'local'
795
- ? 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})`)
796
1693
  : [
797
1694
  ...(reach?.skipped ?? []).map((n) => `node ${typeof n === 'string' ? n : JSON.stringify(n)}`),
798
1695
  ...(reach?.notReady ?? []).map((n) => `infrastructure on ${n}`),
799
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
+ }
800
1708
  // Recorded for every caller, and saying WHICH one. `source: 'unknown'`
801
1709
  // is deliberate rather than a default of `'operator'`: a caller that
802
1710
  // has not been taught to identify itself should be visible in the
@@ -810,7 +1718,7 @@ export class ProjectService extends EventEmitter {
810
1718
  resourceType: 'stack',
811
1719
  resourceId: `${projectName}/${stackName}`,
812
1720
  // Said, now that the other ending is recorded too (`startStackOnce`).
813
- outcome: notUp.length > 0 ? 'partial' : 'ok',
1721
+ outcome,
814
1722
  // The commit, so the trail answers what went out and not only when.
815
1723
  // A node's artifacts carry no revision (every app is version `0.0.1`
816
1724
  // forever, and `BUNDLE.json` is excluded from the checksum and read
@@ -841,6 +1749,7 @@ export class ProjectService extends EventEmitter {
841
1749
  ...(release
842
1750
  ? {
843
1751
  release: release.id,
1752
+ releaseManifestSha256: release.manifestSha256,
844
1753
  releaseProjectCommit: release.manifest.project.commit.slice(0, 8),
845
1754
  releaseOmniCommit: release.manifest.omni.commit.slice(0, 8),
846
1755
  }
@@ -861,52 +1770,293 @@ export class ProjectService extends EventEmitter {
861
1770
  throw err;
862
1771
  }
863
1772
  }
864
- async stopStack(projectName, stackName) {
865
- const stateKey = `${projectName}/${stackName}`;
866
- const state = this.stackStates.get(stateKey);
867
- if (!state || state.status === 'stopped') {
868
- const config = this.getLoadedConfig(projectName);
869
- const stacks = config ? this.resolveStacks(config, projectName) : {};
870
- const stackConfig = stacks[stackName];
871
- if (!stackConfig)
872
- throw new Error(`Stack '${stackName}' not found`);
873
- return this.toStackInfo(projectName, stackName, stackConfig);
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');
1794
+ }
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');
874
1798
  }
875
- this.logger.info({ project: projectName, stack: stackName }, 'Stopping stack');
876
- state.status = 'stopping';
877
- this.emit('stack:stopping', projectName, stackName);
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 = [];
878
1811
  try {
879
- // Stop all apps with this stack's namespace prefix (reverse order for graceful shutdown)
880
- const prefix = `${projectName}/${stackName}/`;
881
- const handleNames = this.orchestrator.listHandleNames(prefix);
882
- for (const name of handleNames.reverse()) {
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') {
883
1828
  try {
884
- await this.orchestrator.stopApp(name);
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 });
885
1837
  }
886
- catch (err) {
887
- this.logger.warn({ app: name, error: err.message }, 'Failed to stop app during stack shutdown');
1838
+ catch (error) {
1839
+ targets.push({ node: 'local', reason: describeFailure(error) });
1840
+ }
1841
+ }
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) });
1856
+ }
1857
+ }
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();
888
1986
  }
889
1987
  }
890
- // Disconnect slave daemons for remote/cluster stacks
891
- if (state.config.type !== 'local' && this.slaveConnector) {
892
- for (const node of state.config.nodes ?? []) {
893
- await this.slaveConnector.removeSlave(node.host, node.port ?? 9700);
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
+ }
894
1995
  }
1996
+ else
1997
+ await this.infraManager.teardownStack(projectName, stackName, localInventory.containers);
895
1998
  }
896
- // Teardown per-stack infrastructure (namespaced containers)
897
- await this.infraManager.teardownStack(projectName, stackName);
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();
898
2039
  state.status = 'stopped';
899
2040
  state.startedAt = null;
900
- this.updateEnabledStacks(projectName, stackName, false);
901
2041
  this.emit('stack:stopped', projectName, stackName, 'manual');
902
- this.logger.info({ project: projectName, stack: stackName }, 'Stack stopped');
903
- return this.toStackInfo(projectName, stackName, state.config);
2042
+ return this.toStackInfo(projectName, stackName, stackConfig);
904
2043
  }
905
- catch (err) {
906
- state.status = 'error';
907
- throw err;
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;
908
2052
  }
909
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
+ }
910
2060
  /**
911
2061
  * What is running in this stack, asked of whoever is running it.
912
2062
  *
@@ -932,60 +2082,23 @@ export class ProjectService extends EventEmitter {
932
2082
  const stackConfig = stacks[stackName] ?? state?.config;
933
2083
  if (!stackConfig)
934
2084
  throw new Error(`Stack '${stackName}' not found`);
935
- const prefix = `${projectName}/${stackName}/`;
936
- // Match by namespaced prefix OR by bare-name when the bare entry's
937
- // effective name is part of this stack's configured app set. See
938
- // toStackInfo for the full rationale; we keep the same rule here so
939
- // CLI `omnitron list` and the webapp's stack status agree.
940
- const configForStatus = this.getLoadedConfig(projectName);
941
- const configuredNames = configForStatus
942
- ? new Set(this.resolveStackApps(stackConfig, configForStatus).map((a) => a.name))
943
- : new Set();
944
- const apps = this.orchestrator.list().filter((a) => {
945
- if (a.name.startsWith(prefix))
946
- return true;
947
- if (a.name.includes('/'))
948
- return false;
949
- return configuredNames.has(effectiveAppName(a.name));
950
- });
951
- const onlineApps = apps.filter((a) => a.status === 'online');
952
- // Derive status from app health if running
953
- let status = state?.status ?? 'stopped';
954
- if (status === 'running' && apps.length > 0 && onlineApps.length < apps.length) {
955
- status = 'degraded';
956
- }
957
- 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 {
958
2090
  name: stackName,
959
2091
  type: stackConfig.type,
960
- status,
961
- totalApps: apps.length,
962
- onlineApps: onlineApps.length,
963
- totalNodes: stackConfig.nodes?.length ?? 1,
964
- connectedNodes: stackConfig.type === 'local' ? 1 : 0,
965
- totalCpu: apps.reduce((sum, a) => sum + a.cpu, 0),
966
- totalMemory: apps.reduce((sum, a) => sum + a.memory, 0),
967
- syncSummary: (stackConfig.type === 'remote' || stackConfig.type === 'cluster')
968
- ? summariseSync(this.nodesWithSync(stackConfig))
969
- : null,
970
- };
971
- if (stackConfig.type === 'local')
972
- return base;
973
- // The node is asked, and what it answers replaces every count that was
974
- // taken from this machine. `connectedNodes` is the number of nodes that
975
- // ANSWERED rather than the number of entries in the connector's
976
- // registry: that registry reported 0 connections for a node that was
977
- // answering RPCs in the same second.
978
- const { info, answered } = await this.askNodes(projectName, this.getStack(projectName, stackName));
979
- const remote = info.apps;
980
- const up = remote.filter((a) => a.status === 'online');
981
- return {
982
- ...base,
983
2092
  status: info.status,
984
- totalApps: remote.length,
985
- onlineApps: up.length,
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,
986
2098
  connectedNodes: answered,
987
- totalCpu: remote.reduce((sum, a) => sum + (a.cpu ?? 0), 0),
988
- totalMemory: remote.reduce((sum, a) => sum + (a.memory ?? 0), 0),
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)),
989
2102
  };
990
2103
  }
991
2104
  /**
@@ -1009,6 +2122,66 @@ export class ProjectService extends EventEmitter {
1009
2122
  const { releasesRoot } = await import('../release/load.js');
1010
2123
  return releasesRoot();
1011
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
+ }
1012
2185
  /**
1013
2186
  * Take a release for a stack, or say why not.
1014
2187
  *
@@ -1033,25 +2206,51 @@ export class ProjectService extends EventEmitter {
1033
2206
  * thing worse than a refusal at deploy time is a console that promised the
1034
2207
  * opposite.
1035
2208
  */
1036
- async checkRelease(projectName, stackName, releaseId) {
2209
+ async checkRelease(projectName, stackName, releaseId, prepare = false) {
2210
+ let preparedArtifactVolumes;
1037
2211
  try {
1038
2212
  const config = await this.loadProjectConfig(projectName);
1039
2213
  const stacks = this.resolveStacks(config, projectName);
1040
2214
  const stackConfig = stacks[stackName];
1041
2215
  if (!stackConfig)
1042
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');
1043
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
+ }
1044
2237
  const gates = release.manifest.gates;
1045
2238
  const passed = gates.filter((g) => g.status === 'passed').length;
1046
2239
  return {
1047
2240
  ok: true,
2241
+ ...(preparedArtifactVolumes?.length && { preparedArtifactVolumes }),
1048
2242
  because: `${projectName}/${stackName} would take ${release.id}: ${passed} of ${gates.length} gates passed, ` +
1049
2243
  `${release.files.length} artifact(s) match the manifest` +
1050
- `${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.` : ''),
1051
2246
  };
1052
2247
  }
1053
2248
  catch (err) {
1054
- return { ok: false, because: err.message };
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 }) }) };
1055
2254
  }
1056
2255
  }
1057
2256
  /**
@@ -1493,20 +2692,25 @@ export class ProjectService extends EventEmitter {
1493
2692
  * with nothing, so an uncommitted edit to the gateway's template reached the
1494
2693
  * test node past every gate a release has.
1495
2694
  */
1496
- async deploymentReads(projectName, stackConfig, config, root) {
2695
+ async deploymentReads(projectName, stackConfig, config, root, definitions) {
1497
2696
  const { definitionInputs } = await import('../release/definition-inputs.js');
1498
2697
  const { shippedDirsOf } = await import('../infrastructure/shipped-config.js');
1499
- const shipped = shippedDirsOf(mergeInfrastructure(config, stackConfig), await this.collectDeclaredServices(projectName, stackConfig, config));
1500
- 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));
1501
2700
  }
1502
2701
  async admitRelease(projectName, stackName, stackConfig, config, releaseId, purpose = 'deploy') {
1503
- if (stackConfig.type === 'local') {
1504
- throw new Error(`A release is for a remote stack — ${projectName}/${stackName} is local and runs its working tree.`);
1505
- }
2702
+ if (stackConfig.type === 'local' && stackConfig.managedOrigin)
2703
+ throw new Error('A node allocation takes its release from its owning controller');
1506
2704
  const { loadRelease, treeEqualsCommit } = await import('../release/load.js');
1507
2705
  const { decideStackRelease } = await import('../release/manifest.js');
1508
2706
  const release = await loadRelease(releaseId, await this.releaseStore());
1509
- const apps = this.resolveStackApps(stackConfig, config).map((a) => a.name);
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))];
1510
2714
  // The fourth argument, which was `[]` for as long as nothing wrote one:
1511
2715
  // a stack declaring `verifiedOn` refused every release forever, with a
1512
2716
  // refusal that read «nothing has been verified on 'test'» while there
@@ -1526,13 +2730,23 @@ export class ProjectService extends EventEmitter {
1526
2730
  this.logger.info({ project: projectName, stack: stackName }, 'No release is recorded as running on this stack — applied migrations are not compared');
1527
2731
  }
1528
2732
  else if (running !== release.id) {
1529
- let ran;
2733
+ let previous;
1530
2734
  try {
1531
- ran = (await loadRelease(running, await this.releaseStore())).manifest.migrations;
2735
+ previous = (await loadRelease(running, await this.releaseStore())).manifest;
1532
2736
  }
1533
2737
  catch (err) {
1534
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');
1535
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;
1536
2750
  if (ran && release.manifest.migrations) {
1537
2751
  const { changedReleasedMigrations } = await import('../release/migrations.js');
1538
2752
  const changed = changedReleasedMigrations(ran, release.manifest.migrations);
@@ -1550,17 +2764,48 @@ export class ProjectService extends EventEmitter {
1550
2764
  if (!project)
1551
2765
  throw new Error(`Project '${projectName}' is not in the registry`);
1552
2766
  const commit = release.manifest.project.commit;
1553
- // Only what the definition reads has to be the release's — see
1554
- // `release/definition-inputs.ts`. The rest of the checkout is other
1555
- // people's work in progress, which no deployment reads. When the files
1556
- // cannot be named, the whole tree is compared, as before.
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.
1557
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
+ }
1558
2793
  let inputs;
1559
2794
  try {
1560
- inputs = await this.deploymentReads(projectName, stackConfig, config, root);
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);
1561
2806
  }
1562
2807
  catch (err) {
1563
- this.logger.warn({ project: projectName, stack: stackName, error: err.message }, 'Could not name the files the definition reads — the whole working tree has to be the release\'s commit');
2808
+ throw new Error(`Cannot verify deployment inputs for ${projectName}/${stackName}: ${err.message}`, { cause: err });
1564
2809
  }
1565
2810
  const tree = await treeEqualsCommit(root, commit, inputs);
1566
2811
  if (!tree.equal) {
@@ -1571,15 +2816,17 @@ export class ProjectService extends EventEmitter {
1571
2816
  `The definition is read from ${project.path}, so those have to be the release's: commit or set them aside, ` +
1572
2817
  `check out ${commit.slice(0, 8)}, or build a release from what is here.`);
1573
2818
  }
1574
- // The omni an application runs on a node is that node daemon's, not the
1575
- // release's — see `release/daemon-omni.ts`.
1576
- const { refuseForeignOmni } = await import('../release/daemon-omni.js');
1577
- const foreign = refuseForeignOmni(release.manifest.omni.commit, await this.askDaemonVersions(stackConfig));
1578
- if (foreign) {
1579
- throw new Error(`Refusing release ${release.id} for ${projectName}/${stackName}: ${foreign}.`);
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}.`);
1580
2825
  }
1581
2826
  const infra = mergeInfrastructure(config, stackConfig);
1582
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');
1583
2830
  if (servesStatics && (!release.staticsDir || release.manifest.statics?.stack !== stackName)) {
1584
2831
  throw new Error(`Refusing release ${release.id} for ${projectName}/${stackName}: the stack's gateway serves a static bundle, and the ` +
1585
2832
  `release carries ${release.manifest.statics ? `one built for '${release.manifest.statics.stack}'` : 'none'} — ` +
@@ -1593,18 +2840,19 @@ export class ProjectService extends EventEmitter {
1593
2840
  projectCommit: commit.slice(0, 8),
1594
2841
  omniCommit: release.manifest.omni.commit.slice(0, 8),
1595
2842
  }, purpose === 'check' ? 'Release would be admitted — asked, not deployed' : 'Release admitted');
1596
- return release;
2843
+ return remoteDefinitions ? { ...release, remoteDefinitions } : release;
1597
2844
  }
1598
- /** What each of a stack's nodes says its daemon is — the omni its applications load. */
1599
- async askDaemonVersions(stackConfig) {
1600
- const connector = this.getSlaveConnector();
1601
- return Promise.all((stackConfig.nodes ?? []).map(async (node) => {
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 }) => {
1602
2849
  try {
1603
- const pong = (await connector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronDaemon', 'ping', []));
1604
- return { host: node.host, version: pong?.version ?? null };
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 }) };
1605
2853
  }
1606
2854
  catch (err) {
1607
- return { host: node.host, version: null, error: err.message };
2855
+ return { host: node.host, port: node.port ?? 9700, error: describeFailure(err) };
1608
2856
  }
1609
2857
  }));
1610
2858
  }
@@ -1665,6 +2913,38 @@ export class ProjectService extends EventEmitter {
1665
2913
  getInfraManager() {
1666
2914
  return this.infraManager;
1667
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
+ }
1668
2948
  // ===========================================================================
1669
2949
  // Enabled-stacks reconciler — desired-state convergence
1670
2950
  // ===========================================================================
@@ -1686,9 +2966,12 @@ export class ProjectService extends EventEmitter {
1686
2966
  if (this.resumeTimer)
1687
2967
  return;
1688
2968
  this.resumeTimer = setInterval(() => {
2969
+ if (this.ownershipFrozen || this.resumingInstalled || this.reconcilingStacks)
2970
+ return;
2971
+ this.reconcilingStacks = true;
1689
2972
  void this.reconcileEnabledStacks().catch((err) => {
1690
2973
  this.logger.warn({ error: err.message }, 'Enabled-stacks reconciler tick failed');
1691
- });
2974
+ }).finally(() => { this.reconcilingStacks = false; });
1692
2975
  }, intervalMs);
1693
2976
  this.resumeTimer.unref();
1694
2977
  this.logger.info({ intervalMs }, 'Enabled-stacks reconciler started');
@@ -1696,10 +2979,13 @@ export class ProjectService extends EventEmitter {
1696
2979
  async reconcileEnabledStacks() {
1697
2980
  // Collect the divergence set first — cheap, no I/O.
1698
2981
  const pending = [];
1699
- for (const project of this.registry.list()) {
2982
+ for (const project of this.registry.listRegistrations()) {
1700
2983
  for (const stackName of project.enabledStacks ?? []) {
1701
2984
  const state = this.stackStates.get(`${project.name}/${stackName}`);
1702
- const status = state?.status;
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;
1703
2989
  // Converge only from fully-down states (absent/stopped/error).
1704
2990
  // 'degraded' means mostly-up — restarting the whole stack over it
1705
2991
  // would churn healthy apps; per-app recovery owns that case.
@@ -1711,17 +2997,9 @@ export class ProjectService extends EventEmitter {
1711
2997
  }
1712
2998
  if (pending.length === 0)
1713
2999
  return;
1714
- // One cheap probe gates the whole round: without Docker every start is
1715
- // guaranteed to fail, so don't burn backoff budget on it.
1716
- const { isDockerAvailable } = await import('../infrastructure/container-runtime.js');
1717
- if (!(await isDockerAvailable())) {
1718
- if (this.dockerWasAvailable !== false) {
1719
- this.logger.warn({ pending: pending.map((p) => `${p.project}/${p.stack}`) }, 'Reconciler: Docker unavailable — deferring enabled-stack resume');
1720
- }
1721
- this.dockerWasAvailable = false;
1722
- return;
1723
- }
1724
- 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.
1725
3003
  const now = Date.now();
1726
3004
  for (const { project, stack } of pending) {
1727
3005
  const key = `${project}/${stack}`;
@@ -1735,7 +3013,14 @@ export class ProjectService extends EventEmitter {
1735
3013
  if (!this.getLoadedConfig(project)) {
1736
3014
  await this.loadProjectConfig(project);
1737
3015
  }
1738
- 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
+ }
1739
3024
  this.resumeBackoff.delete(key);
1740
3025
  this.logger.info({ project, stack }, 'Reconciler: stack resumed');
1741
3026
  }
@@ -1803,7 +3088,7 @@ export class ProjectService extends EventEmitter {
1803
3088
  this.emit('project:config_reloaded', projectName);
1804
3089
  }
1805
3090
  else {
1806
- for (const project of this.registry.list()) {
3091
+ for (const project of this.registry.listRegistrations()) {
1807
3092
  await this.loadProjectConfig(project.name);
1808
3093
  this.emit('project:config_reloaded', project.name);
1809
3094
  }
@@ -1812,12 +3097,19 @@ export class ProjectService extends EventEmitter {
1812
3097
  // ===========================================================================
1813
3098
  // Helpers
1814
3099
  // ===========================================================================
1815
- getRunningStacks(projectName) {
3100
+ getRunningStacks(projectName, includeIncomplete = false) {
1816
3101
  const result = [];
1817
3102
  for (const [, state] of this.stackStates) {
1818
- if (state.project === projectName && (state.status === 'running' || state.status === 'starting')) {
3103
+ if (state.project !== projectName)
3104
+ continue;
3105
+ if (includeIncomplete && state.status !== 'stopped') {
1819
3106
  result.push(state.stack);
3107
+ continue;
1820
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);
1821
3113
  }
1822
3114
  return result;
1823
3115
  }
@@ -1877,7 +3169,7 @@ export class ProjectService extends EventEmitter {
1877
3169
  if (config.database) {
1878
3170
  const dbConfig = typeof config.database === 'object' ? config.database : {};
1879
3171
  databases[appName] = {
1880
- migrate: true,
3172
+ // A discovered dependency is not authority to execute a migration.
1881
3173
  ...(dbConfig.extensions?.length ? { extensions: dbConfig.extensions } : {}),
1882
3174
  };
1883
3175
  }
@@ -1973,105 +3265,42 @@ export class ProjectService extends EventEmitter {
1973
3265
  return readings;
1974
3266
  }
1975
3267
  async validateStackInfrastructure(projectName, stackName) {
1976
- const config = await this.loadProjectConfig(projectName);
1977
- const stacks = this.resolveStacks(config, projectName);
1978
- const stackConfig = stacks[stackName];
1979
- if (!stackConfig) {
1980
- return { valid: false, issues: [`Stack '${stackName}' not found`] };
1981
- }
1982
- const project = this.registry.get(projectName);
1983
- if (!project) {
1984
- return { valid: false, issues: [`Project '${projectName}' not found`] };
1985
- }
1986
- // Determine effective infrastructure: stack-level overrides ecosystem-level
1987
- const effectiveInfra = stackConfig.infrastructure ?? config.infrastructure;
1988
- const issues = [];
1989
- // Scan app requirements
1990
- const { scanRequirements } = await import('../project/requirements-scanner.js');
1991
- const appEntries = this.resolveStackApps(stackConfig, config);
1992
- const reqs = await scanRequirements(appEntries, project.path);
1993
- // Check PostgreSQL
1994
- if (reqs.databases.length > 0 && !effectiveInfra?.postgres) {
1995
- issues.push(`PostgreSQL required by ${reqs.databases.map((d) => d.app).join(', ')} but not configured in infrastructure`);
1996
- }
1997
- else if (reqs.databases.length > 0 && effectiveInfra?.postgres) {
1998
- // Check individual databases exist in config
1999
- const configuredDbs = new Set(Object.keys(effectiveInfra.postgres.databases ?? {}));
2000
- for (const db of reqs.databases) {
2001
- if (!configuredDbs.has(db.database)) {
2002
- issues.push(`Database '${db.database}' needed by '${db.app}' not found in postgres.databases`);
2003
- }
2004
- }
2005
- }
2006
- // Check Redis
2007
- if (reqs.redis.appCount > 0 && !effectiveInfra?.redis) {
2008
- issues.push(`Redis required by ${reqs.redis.allocations.map((a) => a.app).join(', ')} but not configured in infrastructure`);
2009
- }
2010
- // Check S3/MinIO
2011
- if (reqs.buckets.length > 0 && !effectiveInfra?.minio) {
2012
- issues.push(`S3/MinIO required by ${reqs.buckets.map((b) => b.app).join(', ')} but not configured in infrastructure`);
2013
- }
2014
- else if (reqs.buckets.length > 0 && effectiveInfra?.minio) {
2015
- const configuredBuckets = new Set(effectiveInfra.minio.buckets ?? []);
2016
- for (const b of reqs.buckets) {
2017
- if (configuredBuckets.size > 0 && !configuredBuckets.has(b.bucket)) {
2018
- issues.push(`Bucket '${b.bucket}' needed by '${b.app}' not found in minio.buckets`);
2019
- }
2020
- }
2021
- }
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}`);
2022
3270
  return { valid: issues.length === 0, issues };
2023
3271
  }
2024
3272
  // ===========================================================================
2025
3273
  // Private — Stack Launchers
2026
3274
  // ===========================================================================
2027
- async startLocalStack(projectName, stackName, stackConfig, ecosystemConfig) {
3275
+ async startLocalStack(projectName, stackName, stackConfig, ecosystemConfig, selectedEntries, enable, verifyMigrations, verifyArtifactInfrastructure) {
3276
+ const ownsInfrastructure = ownsStackInfrastructure(stackConfig);
2028
3277
  const stateKey = `${projectName}/${stackName}`;
2029
3278
  const state = this.stackStates.get(stateKey);
2030
- // 0. Validate infrastructure covers app requirements (warn only, don't block)
2031
- try {
2032
- const validation = await this.validateStackInfrastructure(projectName, stackName);
2033
- if (!validation.valid) {
2034
- for (const issue of validation.issues) {
2035
- this.logger.warn({ project: projectName, stack: stackName, issue }, 'Infrastructure validation issue');
2036
- }
2037
- }
2038
- }
2039
- catch {
2040
- // Validation is best-effort — don't block stack start
2041
- }
2042
3279
  // 1. Load bootstrap definitions (needed for both infra provisioning and config resolution)
2043
- const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
3280
+ const appEntries = selectedEntries ?? this.resolveStackApps(stackConfig, ecosystemConfig);
2044
3281
  // What the stack gives its apps, vault references resolved — now, before
2045
3282
  // anything is provisioned: a key the vault does not hold refuses the
2046
3283
  // start here (project/stack-env.ts).
2047
3284
  const stackEnv = await this.stackEnvFor(projectName, stackName, stackConfig, appEntries.map((e) => e.name));
2048
3285
  const project = this.registry.get(projectName);
2049
- const appDefinitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
3286
+ const appDefinitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig, appEntries);
2050
3287
  // 2. Provision per-stack infrastructure (built-in + app-declared services)
2051
3288
  // Auto-detect core services (postgres, redis, minio) from app requirements,
2052
3289
  // then merge with explicit infrastructure config (gateway, custom services).
2053
3290
  // Merge stack-level + ecosystem-level infrastructure (stack overrides ecosystem, but both contribute)
2054
- let effectiveInfra = mergeInfrastructure(ecosystemConfig, stackConfig);
2055
- if (project) {
2056
- try {
2057
- const { scanRequirements, buildInfraFromRequirements } = await import('../project/requirements-scanner.js');
2058
- const reqs = await scanRequirements(appEntries, project.path);
2059
- const autoInfra = buildInfraFromRequirements(reqs);
2060
- if (Object.keys(autoInfra).length > 0) {
2061
- const explicitServices = effectiveInfra?.services ?? {};
2062
- this.logger.info({ explicitServiceKeys: Object.keys(explicitServices), hasEffective: !!effectiveInfra }, 'Pre-merge: explicit services from config');
2063
- // Merge: auto-detected core services + explicit config (gateway, custom services)
2064
- effectiveInfra = {
2065
- ...autoInfra,
2066
- ...effectiveInfra,
2067
- services: { ...explicitServices },
2068
- };
2069
- this.logger.info({ project: projectName, stack: stackName, auto: Object.keys(autoInfra), explicit: Object.keys(effectiveInfra.services ?? {}) }, 'Merged auto-detected + explicit infrastructure');
2070
- }
2071
- }
2072
- catch (err) {
2073
- this.logger.warn({ error: err.message }, 'Failed to auto-detect infrastructure requirements');
2074
- }
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
+ }));
2075
3304
  }
2076
3305
  if (effectiveInfra) {
2077
3306
  try {
@@ -2092,10 +3321,10 @@ export class ProjectService extends EventEmitter {
2092
3321
  // Bail out with a clear, structured error so the operator sees the
2093
3322
  // real cause first.
2094
3323
  //
2095
- // Edge case: stacks that intentionally talk to *external* infra and
2096
- // declare nothing in their `infrastructure` section never reach this
2097
- // branch (`effectiveInfra` is falsy). For deliberate degraded-mode
2098
- // operation the stack settings can opt in via
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
2099
3328
  // `settings.allowDegradedInfra: true` (the field is namespaced under
2100
3329
  // settings to remain forward-compatible with stack schema).
2101
3330
  const allowDegraded = stackConfig.settings?.['allowDegradedInfra'] === true;
@@ -2111,17 +3340,17 @@ export class ProjectService extends EventEmitter {
2111
3340
  throwable.cause = err;
2112
3341
  throw throwable;
2113
3342
  }
3343
+ state.infrastructureStartFailed = true;
2114
3344
  this.logger.warn({ project: projectName, stack: stackName }, 'allowDegradedInfra=true — proceeding with app start despite infra failure');
2115
3345
  }
2116
3346
  }
2117
- // 2b. Run database migrations for apps that have migrate: true
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);
2118
3350
  if (effectiveInfra && project) {
2119
- await this.runStackMigrations(projectName, stackName, effectiveInfra, appEntries, project.path);
3351
+ await this.runStackMigrations(projectName, stackName, this.infraManager.getNormalizedServices(projectName, stackName) ?? {}, appEntries, project.path, appConfigBuilder, verifyMigrations);
2120
3352
  }
2121
- // 3. How each app is started: its entry, built from the stack's resolved
2122
- // configuration (`stackEntryBuilder` — the same builder a re-read of
2123
- // the project uses to redefine running apps).
2124
- const appConfigBuilder = this.stackEntryBuilder(projectName, stackName, stackConfig, ecosystemConfig, appDefinitions, stackEnv);
3353
+ await enable?.(appConfigBuilder);
2125
3354
  // 4. Start apps in dependency-aware parallel batches.
2126
3355
  //
2127
3356
  // Pre-fix this was a serial `for ... await` loop in declaration
@@ -2139,13 +3368,12 @@ export class ProjectService extends EventEmitter {
2139
3368
  // parallel, and a wave can't start until every member of the
2140
3369
  // previous wave is online. Same skip-on-failed-dep semantics
2141
3370
  // as startAll.
2142
- const failed = new Set();
2143
- const blocked = new Set();
3371
+ const problems = new Map();
2144
3372
  for (const wave of resolveStartupOrder(appEntries)) {
2145
3373
  await Promise.all(wave.map(async (entry) => {
2146
- const blockingDep = (entry.dependsOn ?? []).find((d) => failed.has(d) || blocked.has(d));
3374
+ const blockingDep = (entry.dependsOn ?? []).find((d) => problems.has(d));
2147
3375
  if (blockingDep) {
2148
- blocked.add(entry.name);
3376
+ problems.set(entry.name, `dependency ${blockingDep} did not start`);
2149
3377
  this.logger.warn({ app: entry.name, project: projectName, stack: stackName, blockedBy: blockingDep }, 'Skipping app — dependency failed or was blocked');
2150
3378
  return;
2151
3379
  }
@@ -2162,14 +3390,15 @@ export class ProjectService extends EventEmitter {
2162
3390
  await this.orchestrator.startApp(namespacedEntry, ecosystemConfig);
2163
3391
  }
2164
3392
  catch (err) {
2165
- failed.add(entry.name);
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));
2166
3396
  this.logger.error({ app: entry.name, project: projectName, stack: stackName, error: err.message }, 'Failed to start app in stack');
2167
3397
  }
2168
3398
  }));
2169
3399
  }
2170
- if (failed.size > 0 || blocked.size > 0) {
2171
- this.logger.warn({ project: projectName, stack: stackName, failed: [...failed], blocked: [...blocked] }, 'Stack startup finished with failures — some apps did not launch');
2172
- }
3400
+ if (problems.size)
3401
+ state.appStartErrors = Object.fromEntries(problems);
2173
3402
  }
2174
3403
  /**
2175
3404
  * Deploy a stack to its nodes, and say what it is doing while it does it.
@@ -2183,7 +3412,7 @@ export class ProjectService extends EventEmitter {
2183
3412
  async startRemoteStack(projectName, stackName, stackConfig, ecosystemConfig, release = null, reinstall = false) {
2184
3413
  const phases = reportPhases(this.logger, { project: projectName, stack: stackName });
2185
3414
  try {
2186
- 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));
2187
3416
  }
2188
3417
  finally {
2189
3418
  // Every exit, including the refusals: a reporter that outlives its
@@ -2220,9 +3449,15 @@ export class ProjectService extends EventEmitter {
2220
3449
  // variant selection and override merging, on the side with less
2221
3450
  // information.
2222
3451
  phases.enter('reading what the applications declare');
2223
- const declaredServices = await this.collectDeclaredServices(projectName, stackConfig, ecosystemConfig);
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 });
2224
3455
  // Build artifacts for apps in this stack
2225
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));
2226
3461
  // What the stack gives its apps, vault references resolved — before any
2227
3462
  // node is touched: a key the vault does not hold refuses the deployment
2228
3463
  // here, not after a node was provisioned (project/stack-env.ts). A remote
@@ -2240,6 +3475,7 @@ export class ProjectService extends EventEmitter {
2240
3475
  // Built in clean clones, gated, and checked against the manifest by
2241
3476
  // `loadRelease` — nothing is compiled here, from this machine's disk.
2242
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]));
2243
3479
  artifacts = appEntries.flatMap((entry) => {
2244
3480
  const file = files.get(entry.name);
2245
3481
  return file
@@ -2251,6 +3487,8 @@ export class ProjectService extends EventEmitter {
2251
3487
  builtAt: release.manifest.builtAt,
2252
3488
  checksum: file.inputs,
2253
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 }),
2254
3492
  }]
2255
3493
  : [];
2256
3494
  });
@@ -2353,7 +3591,9 @@ export class ProjectService extends EventEmitter {
2353
3591
  const locks = new Map();
2354
3592
  try {
2355
3593
  // Deploy to each node: provision slave → deploy artifacts → connect
2356
- for (const node of nodes) {
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));
2357
3597
  const nodeKey = `${node.host}:${node.port ?? 9700}`;
2358
3598
  if (!leases.has(nodeKey)) {
2359
3599
  skipped.push(nodeKey);
@@ -2361,9 +3601,6 @@ export class ProjectService extends EventEmitter {
2361
3601
  continue;
2362
3602
  }
2363
3603
  // 1. Provision slave: install runtime + omnitron + generate slave config + start daemon
2364
- const unsubProvision = this.deployer.onProgress((progress) => {
2365
- this.emit('stack:deploy_progress', projectName, stackName, progress);
2366
- });
2367
3604
  const target = targets.get(nodeKey);
2368
3605
  const master = await resolveMasterHost({ advertiseHost: _dc.advertiseHost, bindHost: _dc.host }, target);
2369
3606
  if (master.host) {
@@ -2381,7 +3618,6 @@ export class ProjectService extends EventEmitter {
2381
3618
  locks.set(nodeKey, lock);
2382
3619
  phases.enter(`provisioning ${node.host}`);
2383
3620
  const provisioned = await this.deployer.provisionSlaveNode(target, master.host, masterPort, projectName);
2384
- unsubProvision();
2385
3621
  if (!provisioned) {
2386
3622
  skipped.push(nodeKey);
2387
3623
  this.logger.error({ node: nodeKey }, 'Failed to provision slave — skipping');
@@ -2470,25 +3706,23 @@ export class ProjectService extends EventEmitter {
2470
3706
  // and swept the master's `sample-test-*` as orphans. An address is not an
2471
3707
  // instruction to build what it points at.
2472
3708
  phases.enter(`resolving the environment for ${node.host}`);
2473
- const appEnv = await this.resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, appEntries, deployedInfra);
3709
+ const appEnv = await this.resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, nodeApps, deployedInfra, release?.remoteDefinitions);
2474
3710
  // The stack's own, over the computed — as on a local stack, where
2475
3711
  // `settings.env` wins over both.
2476
- for (const [app, env] of Object.entries(stackEnv))
3712
+ for (const [app, env] of Object.entries(stackEnv).filter(([name]) => selectedNames.has(name)))
2477
3713
  appEnv[app] = { ...(appEnv[app] ?? {}), ...env };
2478
3714
  // 3. Deploy app artifacts via SSH
2479
- if (artifacts.length > 0) {
2480
- const unsubDeploy = this.deployer.onProgress((progress) => {
2481
- this.emit('stack:deploy_progress', projectName, stackName, progress);
2482
- });
3715
+ if (nodeArtifacts.length > 0) {
2483
3716
  // The definitions travel with the artifacts. A node that receives
2484
3717
  // one without the other has files it cannot run, and says so only if
2485
3718
  // someone asks it directly.
2486
- await leases.confirm(nodeKey, `delivering ${artifacts.length} artifact(s) to ${node.host}`);
2487
- phases.enter(`delivering ${artifacts.length} artifact(s) to ${node.host}`);
3719
+ await leases.confirm(nodeKey, `delivering ${nodeArtifacts.length} artifact(s) to ${node.host}`);
3720
+ phases.enter(`delivering ${nodeArtifacts.length} artifact(s) to ${node.host}`);
2488
3721
  const sink = ecosystemConfig.monitoring?.alertSink;
2489
- const results = await this.deployer.deployToStack([target], artifacts, projectName, {
2490
- apps: appEntries,
3722
+ const results = await this.deployer.deployToStack([target], nodeArtifacts, projectName, {
3723
+ apps: nodeApps,
2491
3724
  appEnv,
3725
+ migrations: migrationRequests.filter(request => selectedNames.has(request.app)),
2492
3726
  stack: stackName,
2493
3727
  // Past the node's record of what each app runs: ship and restart all.
2494
3728
  ...(reinstall ? { force: true } : {}),
@@ -2496,7 +3730,6 @@ export class ProjectService extends EventEmitter {
2496
3730
  // sink on the stack it runs; named as the master names the stack.
2497
3731
  ...(sink && { alertSink: { ...sink, host: `${projectName}/${stackName}` } }),
2498
3732
  });
2499
- unsubDeploy();
2500
3733
  const failed = results.filter((r) => r.status === 'failed');
2501
3734
  if (failed.length > 0) {
2502
3735
  // The other half of the same invariant. Building every artifact
@@ -2620,7 +3853,7 @@ export class ProjectService extends EventEmitter {
2620
3853
  * node, beside containers whose ports are published on that node's
2621
3854
  * loopback. Local is what they are from where they stand.
2622
3855
  */
2623
- async resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, appEntries, infrastructure) {
3856
+ async resolveNodeAppEnv(ecosystemConfig, projectName, stackConfig, appEntries, infrastructure, admitted) {
2624
3857
  if (!infrastructure)
2625
3858
  return {};
2626
3859
  try {
@@ -2633,7 +3866,7 @@ export class ProjectService extends EventEmitter {
2633
3866
  // Measured: the node's config carried five environment variables per
2634
3867
  // app and not the one the apps were failing for. An empty map is not a
2635
3868
  // map of nothing declared; it is a map nobody filled.
2636
- const definitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
3869
+ const definitions = admitted ?? await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
2637
3870
  // The stack's answers about services it does not provision.
2638
3871
  //
2639
3872
  // `serviceOverrides` is where a stack says that bitcoin and monero are
@@ -2668,8 +3901,8 @@ export class ProjectService extends EventEmitter {
2668
3901
  // Not fatal, and loud: the apps would start with whatever their own
2669
3902
  // definitions carry, which is how they came to be started with
2670
3903
  // `postgres:postgres` in the first place.
2671
- this.logger.error({ project: projectName, error: err.message }, 'Could not resolve what the deployed apps connect to — they will start with their declared env only');
2672
- return {};
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 });
2673
3906
  }
2674
3907
  }
2675
3908
  /**
@@ -3007,206 +4240,59 @@ export class ProjectService extends EventEmitter {
3007
4240
  return { ready: false, detail: `could not ask the node: ${err.message}` };
3008
4241
  }
3009
4242
  }
3010
- async startClusterStack(projectName, stackName, stackConfig, ecosystemConfig) {
3011
- const nodes = stackConfig.nodes ?? [];
3012
- if (nodes.length === 0) {
3013
- throw new Error(`Cluster stack '${stackName}' has no nodes configured`);
3014
- }
3015
- this.logger.info({ project: projectName, stack: stackName, nodes: nodes.length }, 'Starting cluster stack — coordinating slave daemons');
3016
- const connector = this.getSlaveConnector();
3017
- // Build artifacts
3018
- const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
3019
- const project = this.registry.get(projectName);
3020
- let artifacts = [];
3021
- if (project) {
3022
- try {
3023
- const { ArtifactBuilder } = await import('../project/artifact-builder.js');
3024
- const builder = new ArtifactBuilder(project.path, undefined, {
3025
- info: (msg) => this.logger.info({ project: project.name }, msg),
3026
- });
3027
- const outcome = await builder.buildAll(appEntries);
3028
- artifacts = outcome.built;
3029
- for (const f of outcome.failed) {
3030
- this.logger.error({ app: f.app, error: f.error }, 'Artifact build failed');
3031
- }
3032
- }
3033
- catch (err) {
3034
- this.logger.error({ error: err.message }, 'Artifact build failed');
3035
- }
3036
- }
3037
- // Resolve master address (from the SLAVE's perspective — what it dials)
3038
- const { DEFAULT_DAEMON_CONFIG: _dc } = await import('../config/defaults.js');
3039
- const { resolveMasterHost } = await import('./master-address.js');
3040
- const masterPort = _dc.port;
3041
- // Provision all slave nodes in parallel (install runtime + omnitron + config)
3042
- const appNodes = nodes.filter((n) => n.role !== 'database' && n.role !== 'cache');
3043
- // One writer per machine, as for a remote stack — see `deployRemoteStack`
3044
- // and `node-deploy-lease.ts`.
3045
- const targets = new Map();
3046
- for (const node of appNodes) {
3047
- targets.set(`${node.host}:${node.port ?? 9700}`, await this.targetForStackNode(node));
3048
- }
3049
- await withNodeLeases([...targets].map(([node, target]) => ({
3050
- node,
3051
- machine: `${target.host}:${target.sshPort ?? 22}`,
3052
- run: this.deployer.leaseRunner(target),
3053
- })), `${projectName}/${stackName}`, this.logger, async (leases) => {
3054
- const leased = appNodes.filter((n) => leases.has(`${n.host}:${n.port ?? 9700}`));
3055
- for (const [node, because] of leases.unreachable) {
3056
- this.logger.error({ node, because }, 'Could not reach this node to lease it — skipping');
3057
- }
3058
- for (const node of leased) {
3059
- const nodeKey = `${node.host}:${node.port ?? 9700}`;
3060
- const unsubProvision = this.deployer.onProgress((progress) => {
3061
- this.emit('stack:deploy_progress', projectName, stackName, progress);
3062
- });
3063
- const target = targets.get(nodeKey);
3064
- const master = await resolveMasterHost({ advertiseHost: _dc.advertiseHost, bindHost: _dc.host }, target);
3065
- 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');
3066
- await leases.confirm(nodeKey, `provisioning ${node.host}`);
3067
- await this.deployer.provisionSlaveNode(target, master.host, masterPort, projectName);
3068
- unsubProvision();
3069
- }
3070
- // Deploy app artifacts to all provisioned nodes
3071
- if (artifacts.length > 0 && leased.length > 0) {
3072
- for (const node of leased) {
3073
- await leases.confirm(`${node.host}:${node.port ?? 9700}`, `delivering to ${node.host}`);
3074
- }
3075
- const unsubDeploy = this.deployer.onProgress((progress) => {
3076
- this.emit('stack:deploy_progress', projectName, stackName, progress);
3077
- });
3078
- const results = await this.deployer.deployToStack(leased.map((n) => targets.get(`${n.host}:${n.port ?? 9700}`)), artifacts, projectName,
3079
- // Same on the cluster path: artifacts without definitions are files a
3080
- // node cannot run.
3081
- { concurrency: 5, apps: appEntries });
3082
- unsubDeploy();
3083
- const successful = results.filter((r) => r.status === 'success').length;
3084
- const failed = results.filter((r) => r.status === 'failed').length;
3085
- this.logger.info({ successful, failed, total: results.length }, 'Cluster deployment complete');
3086
- }
3087
- });
3088
- // Connect to all slave daemons
3089
- for (const node of nodes) {
3090
- await connector.addSlave({
3091
- host: node.host,
3092
- port: node.port ?? 9700,
3093
- label: node.label,
3094
- stack: stackName,
3095
- project: projectName,
3096
- });
3097
- }
3098
- }
3099
4243
  // ===========================================================================
3100
4244
  // Private — Utilities
3101
4245
  // ===========================================================================
3102
4246
  /**
3103
- * Run database migrations for apps that declare migrate: true in their
3104
- * 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
3105
4248
  * created but before apps are started, so apps always see a current schema.
3106
4249
  *
3107
- * Convention: each app's migration script is at `apps/{appName}/src/database/migrate.ts`.
3108
- * The script reads DATABASE_URL or app-specific env vars and defaults to
3109
- * localhost:{port}/{dbName} with postgres:postgres — matching the stack's provisioned postgres.
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.
3110
4253
  */
3111
- async runStackMigrations(projectName, stackName, infraConfig, appEntries, projectPath) {
3112
- const pgConfig = infraConfig.postgres;
3113
- if (!pgConfig?.databases)
3114
- return;
3115
- const appNames = new Set(appEntries.map((a) => a.name));
3116
- const port = pgConfig.port ?? 5432;
3117
- const user = pgConfig.user ?? 'postgres';
3118
- const password = typeof pgConfig.password === 'string' ? pgConfig.password : 'postgres';
3119
- // Wait for postgres to actually accept connections before running ANY
3120
- // migrations. Containers can be reported "running" by Docker before
3121
- // pg_isready would pass, especially right after a fresh provision. Without
3122
- // this we hit ECONNREFUSED on the first migration and fail-fast bubbles
3123
- // into the user's face.
3124
- //
3125
- // Three minutes, not one. A minute fits a container that already has its
3126
- // data directory; it does not fit a cold one, which has to initdb and
3127
- // install extensions before it listens — and on a loaded machine that is
3128
- // several minutes. When the wait gives up the whole `stack start` aborts
3129
- // and no app is started at all, so a deadline set for the warm case turns
3130
- // a slow first boot into a stack that will not come up.
3131
- await waitForPostgres('localhost', port, user, password, 180_000);
3132
- for (const [dbName, dbConfig] of Object.entries(pgConfig.databases)) {
3133
- // Only run if migrate flag is set AND a matching app exists
3134
- if (!dbConfig || typeof dbConfig !== 'object' || !dbConfig.migrate)
3135
- continue;
3136
- if (!appNames.has(dbName))
3137
- continue;
3138
- const migrateScript = path.join(projectPath, 'apps', dbName, 'src', 'database', 'migrate.ts');
3139
- try {
3140
- await fs.promises.access(migrateScript);
3141
- }
3142
- catch {
3143
- this.logger.debug({ app: dbName, script: migrateScript }, 'Migration script not found — skipping');
3144
- continue;
3145
- }
3146
- this.logger.info({ project: projectName, stack: stackName, database: dbName }, 'Running database migrations');
3147
- // Retry policy: up to 5 attempts with exponential backoff (1s, 2s, 4s, 8s, 16s).
3148
- // Migration tools are typically idempotent, so retrying after a transient
3149
- // ECONNREFUSED / role-creation race is safe.
3150
- const maxAttempts = 5;
3151
- let attempt = 0;
3152
- let lastErr = null;
3153
- while (attempt < maxAttempts) {
3154
- 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') {
3155
4268
  try {
3156
- // Asynchronously. This was `execFileSync`, and a synchronous spawn
3157
- // holds the daemon's whole event loop for as long as the child runs
3158
- // — no RPC answered, no heartbeat, no mesh — which the event-loop
3159
- // watch caught at a master boot on 2026-09-23: six migrations back
3160
- // to back, 1 539 / 871 / 727 / 941 / 740 / 782 ms, 5.6 s of a daemon
3161
- // that answered nothing, on every restart. And with the retries
3162
- // below, a migration that hangs held it up to 60 s per attempt.
3163
- const { execFile } = await import('node:child_process');
3164
- const { promisify } = await import('node:util');
3165
- const running = promisify(execFile)(process.execPath, ['--import', 'tsx/esm', migrateScript], {
3166
- cwd: path.join(projectPath, 'apps', dbName),
3167
- env: {
3168
- ...process.env,
3169
- DATABASE_URL: `postgresql://${user}:${password}@localhost:${port}/${dbName}`,
3170
- // App-specific env vars (various naming conventions)
3171
- [`${dbName.toUpperCase()}__DATABASE__HOST`]: 'localhost',
3172
- [`${dbName.toUpperCase()}__DATABASE__PORT`]: String(port),
3173
- [`${dbName.toUpperCase()}__DATABASE__DATABASE`]: dbName,
3174
- [`${dbName.toUpperCase()}__DATABASE__USER`]: user,
3175
- [`${dbName.toUpperCase()}__DATABASE__PASSWORD`]: password,
3176
- },
3177
- timeout: 60_000,
3178
- maxBuffer: 16 * 1024 * 1024,
3179
- });
3180
- // Nothing to read from us, as `stdio: 'ignore'` said before.
3181
- running.child.stdin?.end();
3182
- const { stdout } = await running;
3183
- // What the migrations said, in order — see `migrationOutput`.
3184
- for (const line of migrationOutput(String(stdout))) {
3185
- this.logger.info({ database: dbName, line }, 'Migration said');
3186
- }
3187
- this.logger.info({ database: dbName, attempts: attempt }, 'Migrations applied');
3188
- lastErr = null;
3189
- break;
4269
+ await executeLocalMigration(target, { ...env, ...database.env }, { validate: true });
3190
4270
  }
3191
- catch (err) {
3192
- lastErr = err;
3193
- const stderr = err.stderr?.toString?.()?.trim?.() ?? '';
3194
- // Only retry on transient connection errors. Schema / SQL errors
3195
- // are deterministic and won't go away by trying again.
3196
- const transient = /ECONNREFUSED|ECONNRESET|ENOTCONN|terminat(ing|ed)|server closing|kysely.*Connection/i.test(stderr) ||
3197
- /ECONNREFUSED|ECONNRESET/i.test(err.message ?? '');
3198
- if (!transient || attempt >= maxAttempts) {
3199
- this.logger.error({ database: dbName, attempt, error: err.message, stderr }, 'Migration failed — app may fail to start');
3200
- break;
3201
- }
3202
- const delayMs = Math.min(1000 * 2 ** (attempt - 1), 16_000);
3203
- this.logger.warn({ database: dbName, attempt, nextRetryInMs: delayMs, error: err.message }, 'Migration transient failure — retrying');
3204
- 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 });
3205
4274
  }
3206
4275
  }
3207
- if (lastErr) {
3208
- // Already logged above; intentionally do NOT throw here so other
3209
- // databases can still attempt their migrations.
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 });
3210
4296
  }
3211
4297
  }
3212
4298
  }
@@ -3216,10 +4302,13 @@ export class ProjectService extends EventEmitter {
3216
4302
  * Config stacks take precedence over user stacks with the same name.
3217
4303
  * No implicit fallbacks — stacks must be created explicitly.
3218
4304
  */
3219
- resolveStacks(config, projectName) {
4305
+ resolveStacks(config, project) {
3220
4306
  const configStacks = config.stacks ?? {};
3221
- if (projectName) {
3222
- const userStacks = this.loadUserStacks(projectName);
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.
3223
4312
  return { ...userStacks, ...configStacks };
3224
4313
  }
3225
4314
  return configStacks;
@@ -3253,17 +4342,17 @@ export class ProjectService extends EventEmitter {
3253
4342
  const project = this.registry.get(projectName);
3254
4343
  const portAllocation = this.infraManager.getPortAllocation(projectName, stackName);
3255
4344
  const normalizedSvcs = this.infraManager.getNormalizedServices(projectName, stackName);
3256
- 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;
3257
4346
  return (entry) => {
3258
- const appConfig = resolvedStack.appConfigs.get(entry.name);
4347
+ const appConfig = resolvedStack?.appConfigs.get(entry.name);
3259
4348
  const infraEnv = appConfig ? resolvedConfigToEnv(appConfig, entry.name, stackName) : {};
3260
4349
  return {
3261
4350
  ...entry,
3262
4351
  name: ProjectService.handleKey(projectName, stackName, entry.name),
3263
4352
  ...(entry.bootstrap && project
3264
- ? { bootstrap: path.resolve(project.path, entry.bootstrap) }
4353
+ ? { bootstrap: resolveAppPath(project.path, entry, entry.bootstrap) }
3265
4354
  : {}),
3266
- ...(project ? { cwd: project.path } : {}),
4355
+ ...(project ? { cwd: resolveAppPath(project.path, entry) } : {}),
3267
4356
  env: {
3268
4357
  // Derived first, stated second. `infraEnv` is COMPUTED from the
3269
4358
  // stack's configuration; `entry.env` is WRITTEN — by a developer
@@ -3304,39 +4393,35 @@ export class ProjectService extends EventEmitter {
3304
4393
  * its chain daemon, its cache — was invisible on that path. The local
3305
4394
  * path had it all along, twenty lines up from where it was needed.
3306
4395
  */
3307
- async loadAppDefinitions(projectName, stackConfig, ecosystemConfig) {
4396
+ async loadAppDefinitions(projectName, stackConfig, ecosystemConfig, appEntries = this.resolveStackApps(stackConfig, ecosystemConfig), archived) {
3308
4397
  const appDefinitions = new Map();
3309
- const appEntries = this.resolveStackApps(stackConfig, ecosystemConfig);
3310
4398
  const project = this.registry.get(projectName);
3311
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');
3312
4402
  if (entry.bootstrap && project) {
3313
4403
  try {
3314
- const bootstrapAbsPath = path.resolve(project.path, entry.bootstrap);
3315
- const { loadBootstrapConfig } = await import('../orchestrator/bootstrap-loader.js');
3316
- const loaded = await loadBootstrapConfig(bootstrapAbsPath, { devMode: false });
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 } : {}) }));
3317
4409
  // The app's `config/default.json`, read now and put on a COPY of
3318
4410
  // the definition: the loaded one is the loader's cache entry, and
3319
4411
  // writing to it made the first read the only one until the daemon
3320
4412
  // restarted (`project/declared-config.ts`).
3321
- const declared = readDeclaredConfig(bootstrapAbsPath);
3322
- if (declared.malformed) {
3323
- this.logger.error({ app: entry.name, configPath: declared.configPath, error: declared.malformed }, 'config/default.json does not parse — its `omnitron` section is being ignored');
3324
- }
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}`);
3325
4418
  const definition = withDeclaredConfig(loaded, declared);
3326
4419
  appDefinitions.set(entry.name, definition);
3327
4420
  }
3328
4421
  catch (err) {
3329
- // An app can start without its definition; a STACK cannot be
3330
- // provisioned without it. The definition is where an application
3331
- // declares what it needs — its database, its cache, its chain
3332
- // daemon — so a swallowed failure here does not degrade the
3333
- // deployment, it silently removes a whole category from it.
3334
- //
3335
- // Measured: a remote stack reported `declared by apps: (none)` and
3336
- // provisioned no infrastructure, because every bootstrap import
3337
- // had failed in a process that could not load TypeScript. Nothing
3338
- // said so at any level.
3339
- 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 });
3340
4425
  }
3341
4426
  }
3342
4427
  }
@@ -3348,16 +4433,14 @@ export class ProjectService extends EventEmitter {
3348
4433
  * One entry per service name: two applications that both declare
3349
4434
  * `postgres` mean one Postgres, which is what `serviceOverrides` keys on
3350
4435
  * and what the container resolver has always assumed. A later declaration
3351
- * that disagrees with an earlier one is not merged silently — the first
3352
- * wins and the disagreement is said, because two applications wanting
3353
- * different versions of one service is a fact about the stack, not a
3354
- * 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.
3355
4438
  */
3356
4439
  async collectDeclaredServices(projectName, stackConfig, ecosystemConfig,
3357
4440
  /** Keep the ones reached elsewhere too — to ask whether they answer, not to provision them. */
3358
4441
  options = {}) {
3359
4442
  const merged = {};
3360
- const definitions = await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
4443
+ const definitions = options.definitions ?? await this.loadAppDefinitions(projectName, stackConfig, ecosystemConfig);
3361
4444
  for (const [appName, definition] of definitions) {
3362
4445
  const declared = definition.omnitronConfig?.infrastructure;
3363
4446
  if (!declared)
@@ -3371,7 +4454,8 @@ export class ProjectService extends EventEmitter {
3371
4454
  if (provisioning === 'disabled' || (provisioning === 'external' && !options.external))
3372
4455
  continue;
3373
4456
  if (merged[name]) {
3374
- this.logger.debug({ service: name, app: appName }, 'Service already declared by another app in this stack — the first declaration stands');
4457
+ if (!isDeepStrictEqual(merged[name], requirement))
4458
+ throw new Error(`Conflicting infrastructure declarations for ${name}; use one shared specification or distinct service names`);
3375
4459
  continue;
3376
4460
  }
3377
4461
  merged[name] = requirement;
@@ -3379,6 +4463,114 @@ export class ProjectService extends EventEmitter {
3379
4463
  }
3380
4464
  return merged;
3381
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
+ }
3382
4574
  resolveStackApps(stackConfig, ecosystemConfig) {
3383
4575
  const allApps = ecosystemConfig.apps.filter((a) => a.enabled !== false);
3384
4576
  if (!stackConfig.apps || stackConfig.apps === 'all') {
@@ -3386,25 +4578,15 @@ export class ProjectService extends EventEmitter {
3386
4578
  }
3387
4579
  return allApps.filter((a) => Array.isArray(stackConfig.apps) ? stackConfig.apps.includes(a.name) : true);
3388
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
+ }
3389
4588
  updateEnabledStacks(projectName, stackName, enabled) {
3390
- const project = this.registry.get(projectName);
3391
- if (!project)
3392
- return;
3393
- const enabledStacks = new Set(project.enabledStacks ?? []);
3394
- if (enabled) {
3395
- enabledStacks.add(stackName);
3396
- }
3397
- else {
3398
- enabledStacks.delete(stackName);
3399
- }
3400
- project.enabledStacks = [...enabledStacks];
3401
- // Persist to disk via registry
3402
- try {
3403
- this.registry.persist();
3404
- }
3405
- catch {
3406
- // Non-critical — runtime state is authoritative
3407
- }
4589
+ this.registry.setStackEnabled(projectName, stackName, enabled);
3408
4590
  }
3409
4591
  toProjectInfo(project) {
3410
4592
  const config = this.configRegistry.get(project.name)?.config;
@@ -3438,12 +4620,25 @@ export class ProjectService extends EventEmitter {
3438
4620
  * deployment fails a script; and the console's stack page shows the same
3439
4621
  * six rows, stopped.
3440
4622
  *
3441
- * A node that cannot be asked leaves its apps exactly as they were, with a
3442
- * line saying why. "We could not ask" is not "they are down" — and the
3443
- * 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.
3444
4625
  */
4626
+ localHealthRead = null;
3445
4627
  async withRemoteAppStatuses(projectName, info) {
3446
- return (await this.askNodes(projectName, info)).info;
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());
3447
4642
  }
3448
4643
  /**
3449
4644
  * The same question, with the count of nodes that answered it.
@@ -3457,85 +4652,46 @@ export class ProjectService extends EventEmitter {
3457
4652
  async askNodes(projectName, info) {
3458
4653
  if (info.type !== 'remote' && info.type !== 'cluster')
3459
4654
  return { info, answered: 0 };
3460
- const nodes = info.config.nodes ?? [];
3461
- if (nodes.length === 0 || !this.slaveConnector)
3462
- return { info, answered: 0 };
3463
- const reported = new Map();
3464
- let remoteInfra = null;
3465
- let answered = 0;
3466
- for (const node of nodes) {
3467
- try {
3468
- const status = (await this.slaveConnector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronDaemon', 'status', []));
3469
- answered += 1;
3470
- // The containers are on the node too — `infraManager.getInstance`
3471
- // answers about THIS machine, where a remote stack has none, so the
3472
- // stack read "Infrastructure: not provisioned" beside a node running
3473
- // postgres, redis, minio, the gateway and tor.
3474
- remoteInfra ??= await this.remoteInfraStatus(projectName, info.name, node);
3475
- for (const app of status?.apps ?? []) {
3476
- // The node names them `<project>/deployed/<app>`; the stack knows
3477
- // them by the app's own name.
3478
- const bare = app.name.includes('/') ? app.name.slice(app.name.lastIndexOf('/') + 1) : app.name;
3479
- reported.set(bare, app);
3480
- }
3481
- }
3482
- catch (err) {
3483
- 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');
3484
- }
3485
- }
3486
- if (answered === 0)
3487
- return { info, answered };
3488
- const apps = info.apps.map((app) => {
3489
- const running = reported.get(app.name);
3490
- if (!running)
3491
- 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)
3492
4664
  return {
3493
- ...app,
3494
- handleKey: running.name,
3495
- status: running.status,
3496
- pid: running.pid,
3497
- instances: running.instances,
3498
- uptime: running.uptime,
3499
- restarts: running.restarts,
3500
- cpu: running.cpu,
3501
- memory: running.memory,
3502
- 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,
3503
4667
  };
3504
- });
3505
- // The stack's own status is this master's memory of having started it,
3506
- // and a daemon restart forgets it — so a stack whose six applications
3507
- // are running on a node read `stopped` a minute after an upgrade. The
3508
- // apps are the fact here too.
3509
- const online = apps.filter((a) => a.status === 'online').length;
3510
- const status = online === apps.length && apps.length > 0
3511
- ? 'running'
3512
- : online > 0
3513
- ? 'degraded'
3514
- : info.status;
3515
- // And when the stack started is when its applications did: the
3516
- // longest-running one a node reports online. An uptime is a duration the
3517
- // node measured on its own clock, so taking it from this one needs no
3518
- // agreement between the two clocks.
3519
- const longest = apps.reduce((max, a) => (reported.has(a.name) && a.status === 'online' && a.uptime > max ? a.uptime : max), 0);
3520
- return {
3521
- info: {
3522
- ...info,
3523
- status,
3524
- infrastructure: remoteInfra ?? info.infrastructure,
3525
- apps,
3526
- startedAt: longest > 0 ? new Date(Date.now() - longest).toISOString() : null,
3527
- uptime: longest,
3528
- },
3529
- answered,
3530
- };
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 };
3531
4687
  }
3532
4688
  /**
3533
4689
  * One node's infrastructure, named the way the stack names it.
3534
4690
  *
3535
4691
  * The node's container names carry ITS prefix (`sample-test-postgres`), and
3536
4692
  * a reader wants the service — `postgres` — so the same stripping the
3537
- * local path does is done here. `null` when the node cannot answer or has
3538
- * no infrastructure of its own, which leaves the master's view in place.
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.
3539
4695
  */
3540
4696
  async remoteInfraStatus(projectName, stackName, node) {
3541
4697
  if (!this.slaveConnector)
@@ -3552,25 +4708,21 @@ export class ProjectService extends EventEmitter {
3552
4708
  const entries = state?.services
3553
4709
  ? Object.entries(state.services)
3554
4710
  : ((await this.slaveConnector.invokeOnSlave(node.host, node.port ?? 9700, 'OmnitronInfra', 'listContainers', [])) ?? [])
3555
- .filter((c) => c.name.startsWith(prefix))
3556
4711
  .map((c) => [c.name, c]);
3557
- if (entries.length === 0)
3558
- return null;
3559
- for (const [containerName, svc] of entries) {
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) {
3560
4716
  const name = containerName.startsWith(prefix) ? containerName.slice(prefix.length) : containerName;
3561
- services[name] = {
3562
- status: svc.status === 'running' ? 'running' : svc.error ? 'error' : 'stopped',
3563
- containerName: svc.name,
3564
- port: svc.ports ? Object.values(svc.ports)[0] ?? null : null,
3565
- };
4717
+ services[name] = stackServiceObservation({ ...svc, service: svc.service ?? name });
3566
4718
  }
3567
4719
  // Without the node's own verdict, "ready" is what the containers say:
3568
4720
  // every one of them running.
3569
- const ready = state?.ready ?? Object.values(services).every((svc) => svc.status === 'running');
4721
+ const ready = Object.values(services).every(svc => svc.status === 'running' && svc.health !== 'unhealthy' && svc.health !== 'starting' && !svc.error && (!svc.provisioning || svc.provisioning === 'ready'));
3570
4722
  return { ready, services };
3571
4723
  }
3572
4724
  catch (err) {
3573
- this.logger.warn({ project: projectName, stack: stackName, node: node.host, error: err.message }, 'Could not ask this node about its infrastructure — the stack shows this master\'s view');
4725
+ this.logger.warn({ project: projectName, stack: stackName, node: node.host, error: err.message }, 'Node infrastructure inventory unavailable; placement state is unknown');
3574
4726
  return null;
3575
4727
  }
3576
4728
  }
@@ -3611,6 +4763,8 @@ export class ProjectService extends EventEmitter {
3611
4763
  cpu: running.cpu,
3612
4764
  memory: running.memory,
3613
4765
  port: running.port ?? null,
4766
+ ...(running.generation !== undefined && { generation: running.generation }),
4767
+ ...(running.release && { release: running.release }),
3614
4768
  };
3615
4769
  }
3616
4770
  // App is configured but not running
@@ -3640,6 +4794,8 @@ export class ProjectService extends EventEmitter {
3640
4794
  cpu: running.cpu,
3641
4795
  memory: running.memory,
3642
4796
  port: running.port ?? null,
4797
+ ...(running.generation !== undefined && { generation: running.generation }),
4798
+ ...(running.release && { release: running.release }),
3643
4799
  });
3644
4800
  }
3645
4801
  // Populate real node connectivity from SlaveConnector
@@ -3684,11 +4840,7 @@ export class ProjectService extends EventEmitter {
3684
4840
  const serviceName = containerName.startsWith(stackPrefix)
3685
4841
  ? containerName.slice(stackPrefix.length)
3686
4842
  : containerName;
3687
- infraServices[serviceName] = {
3688
- status: svcState.status === 'running' ? 'running' : svcState.error ? 'error' : 'stopped',
3689
- containerName: svcState.name,
3690
- port: svcState.ports ? Object.values(svcState.ports)[0] ?? null : null,
3691
- };
4843
+ infraServices[serviceName] = stackServiceObservation(svcState);
3692
4844
  }
3693
4845
  }
3694
4846
  const infrastructure = {
@@ -3704,10 +4856,35 @@ export class ProjectService extends EventEmitter {
3704
4856
  // restart). It is reported as what it is.
3705
4857
  const onNodes = config.type === 'remote' || config.type === 'cluster';
3706
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;
3707
4880
  return {
3708
4881
  name: stackName,
3709
4882
  type: config.type,
3710
- status: state?.status ?? 'stopped',
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,
3711
4888
  config,
3712
4889
  nodes,
3713
4890
  apps,
@@ -3719,6 +4896,138 @@ export class ProjectService extends EventEmitter {
3719
4896
  };
3720
4897
  }
3721
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);
3722
5031
  /**
3723
5032
  * How a master's daemon builds this service: the tokens it resolves, in
3724
5033
  * order, and the factory that receives them.