@omnitron-dev/omnitron 0.1.3 → 0.3.0

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 (1253) hide show
  1. package/dist/BUILD.json +5 -0
  2. package/dist/cli/omnitron.js +378 -25
  3. package/dist/cli/omnitron.js.map +1 -1
  4. package/dist/cluster/cluster.rpc-service.d.ts +40 -0
  5. package/dist/cluster/cluster.rpc-service.d.ts.map +1 -1
  6. package/dist/cluster/cluster.rpc-service.js +49 -3
  7. package/dist/cluster/cluster.rpc-service.js.map +1 -1
  8. package/dist/cluster/config-sync.d.ts.map +1 -1
  9. package/dist/cluster/config-sync.js.map +1 -1
  10. package/dist/cluster/leader-election.d.ts +90 -2
  11. package/dist/cluster/leader-election.d.ts.map +1 -1
  12. package/dist/cluster/leader-election.js +176 -24
  13. package/dist/cluster/leader-election.js.map +1 -1
  14. package/dist/cluster/mesh-link.d.ts +136 -0
  15. package/dist/cluster/mesh-link.d.ts.map +1 -0
  16. package/dist/cluster/mesh-link.js +212 -0
  17. package/dist/cluster/mesh-link.js.map +1 -0
  18. package/dist/cluster/mesh.d.ts +59 -0
  19. package/dist/cluster/mesh.d.ts.map +1 -0
  20. package/dist/cluster/mesh.js +117 -0
  21. package/dist/cluster/mesh.js.map +1 -0
  22. package/dist/cluster/slave-connector.d.ts +193 -9
  23. package/dist/cluster/slave-connector.d.ts.map +1 -1
  24. package/dist/cluster/slave-connector.js +409 -37
  25. package/dist/cluster/slave-connector.js.map +1 -1
  26. package/dist/commands/audit.d.ts +65 -0
  27. package/dist/commands/audit.d.ts.map +1 -0
  28. package/dist/commands/audit.js +233 -0
  29. package/dist/commands/audit.js.map +1 -0
  30. package/dist/commands/backup.d.ts +4 -0
  31. package/dist/commands/backup.d.ts.map +1 -1
  32. package/dist/commands/backup.js +255 -33
  33. package/dist/commands/backup.js.map +1 -1
  34. package/dist/commands/cluster.d.ts.map +1 -1
  35. package/dist/commands/cluster.js +78 -47
  36. package/dist/commands/cluster.js.map +1 -1
  37. package/dist/commands/daemon-cmd.d.ts +7 -1
  38. package/dist/commands/daemon-cmd.d.ts.map +1 -1
  39. package/dist/commands/daemon-cmd.js +87 -21
  40. package/dist/commands/daemon-cmd.js.map +1 -1
  41. package/dist/commands/daemon-required.d.ts +34 -0
  42. package/dist/commands/daemon-required.d.ts.map +1 -0
  43. package/dist/commands/daemon-required.js +92 -0
  44. package/dist/commands/daemon-required.js.map +1 -0
  45. package/dist/commands/deploy.d.ts +70 -8
  46. package/dist/commands/deploy.d.ts.map +1 -1
  47. package/dist/commands/deploy.js +99 -64
  48. package/dist/commands/deploy.js.map +1 -1
  49. package/dist/commands/discover.d.ts +25 -4
  50. package/dist/commands/discover.d.ts.map +1 -1
  51. package/dist/commands/discover.js +155 -102
  52. package/dist/commands/discover.js.map +1 -1
  53. package/dist/commands/doctor.d.ts +293 -0
  54. package/dist/commands/doctor.d.ts.map +1 -0
  55. package/dist/commands/doctor.js +1706 -0
  56. package/dist/commands/doctor.js.map +1 -0
  57. package/dist/commands/env.d.ts +8 -1
  58. package/dist/commands/env.d.ts.map +1 -1
  59. package/dist/commands/env.js +18 -5
  60. package/dist/commands/env.js.map +1 -1
  61. package/dist/commands/exec.d.ts.map +1 -1
  62. package/dist/commands/exec.js +3 -2
  63. package/dist/commands/exec.js.map +1 -1
  64. package/dist/commands/fleet-asking.d.ts +87 -0
  65. package/dist/commands/fleet-asking.d.ts.map +1 -0
  66. package/dist/commands/fleet-asking.js +176 -0
  67. package/dist/commands/fleet-asking.js.map +1 -0
  68. package/dist/commands/fleet.d.ts +18 -0
  69. package/dist/commands/fleet.d.ts.map +1 -1
  70. package/dist/commands/fleet.js +314 -74
  71. package/dist/commands/fleet.js.map +1 -1
  72. package/dist/commands/health-check.d.ts +8 -6
  73. package/dist/commands/health-check.d.ts.map +1 -1
  74. package/dist/commands/health-check.js +26 -23
  75. package/dist/commands/health-check.js.map +1 -1
  76. package/dist/commands/health.d.ts.map +1 -1
  77. package/dist/commands/health.js +26 -4
  78. package/dist/commands/health.js.map +1 -1
  79. package/dist/commands/infra.d.ts +42 -1
  80. package/dist/commands/infra.d.ts.map +1 -1
  81. package/dist/commands/infra.js +418 -49
  82. package/dist/commands/infra.js.map +1 -1
  83. package/dist/commands/init.d.ts.map +1 -1
  84. package/dist/commands/init.js +21 -36
  85. package/dist/commands/init.js.map +1 -1
  86. package/dist/commands/inspect-graph.d.ts +20 -0
  87. package/dist/commands/inspect-graph.d.ts.map +1 -0
  88. package/dist/commands/inspect-graph.js +74 -0
  89. package/dist/commands/inspect-graph.js.map +1 -0
  90. package/dist/commands/inspect.d.ts.map +1 -1
  91. package/dist/commands/inspect.js +115 -11
  92. package/dist/commands/inspect.js.map +1 -1
  93. package/dist/commands/k8s.d.ts.map +1 -1
  94. package/dist/commands/k8s.js +23 -18
  95. package/dist/commands/k8s.js.map +1 -1
  96. package/dist/commands/kb.d.ts.map +1 -1
  97. package/dist/commands/kb.js +88 -29
  98. package/dist/commands/kb.js.map +1 -1
  99. package/dist/commands/list.d.ts.map +1 -1
  100. package/dist/commands/list.js +25 -3
  101. package/dist/commands/list.js.map +1 -1
  102. package/dist/commands/log-health.d.ts +120 -0
  103. package/dist/commands/log-health.d.ts.map +1 -0
  104. package/dist/commands/log-health.js +212 -0
  105. package/dist/commands/log-health.js.map +1 -0
  106. package/dist/commands/logs.d.ts +96 -2
  107. package/dist/commands/logs.d.ts.map +1 -1
  108. package/dist/commands/logs.js +532 -192
  109. package/dist/commands/logs.js.map +1 -1
  110. package/dist/commands/metrics.d.ts.map +1 -1
  111. package/dist/commands/metrics.js +40 -14
  112. package/dist/commands/metrics.js.map +1 -1
  113. package/dist/commands/monit.d.ts.map +1 -1
  114. package/dist/commands/monit.js +12 -2
  115. package/dist/commands/monit.js.map +1 -1
  116. package/dist/commands/node.d.ts +68 -0
  117. package/dist/commands/node.d.ts.map +1 -1
  118. package/dist/commands/node.js +285 -68
  119. package/dist/commands/node.js.map +1 -1
  120. package/dist/commands/output.d.ts +45 -0
  121. package/dist/commands/output.d.ts.map +1 -0
  122. package/dist/commands/output.js +103 -0
  123. package/dist/commands/output.js.map +1 -0
  124. package/dist/commands/pipeline.d.ts.map +1 -1
  125. package/dist/commands/pipeline.js +26 -19
  126. package/dist/commands/pipeline.js.map +1 -1
  127. package/dist/commands/project.d.ts +19 -0
  128. package/dist/commands/project.d.ts.map +1 -1
  129. package/dist/commands/project.js +125 -27
  130. package/dist/commands/project.js.map +1 -1
  131. package/dist/commands/release-store.d.ts +28 -0
  132. package/dist/commands/release-store.d.ts.map +1 -0
  133. package/dist/commands/release-store.js +158 -0
  134. package/dist/commands/release-store.js.map +1 -0
  135. package/dist/commands/release.d.ts +61 -0
  136. package/dist/commands/release.d.ts.map +1 -0
  137. package/dist/commands/release.js +555 -0
  138. package/dist/commands/release.js.map +1 -0
  139. package/dist/commands/reload.d.ts +16 -0
  140. package/dist/commands/reload.d.ts.map +1 -1
  141. package/dist/commands/reload.js +21 -5
  142. package/dist/commands/reload.js.map +1 -1
  143. package/dist/commands/remote.d.ts +40 -0
  144. package/dist/commands/remote.d.ts.map +1 -1
  145. package/dist/commands/remote.js +146 -35
  146. package/dist/commands/remote.js.map +1 -1
  147. package/dist/commands/restart.d.ts +16 -0
  148. package/dist/commands/restart.d.ts.map +1 -1
  149. package/dist/commands/restart.js +21 -5
  150. package/dist/commands/restart.js.map +1 -1
  151. package/dist/commands/scale.d.ts.map +1 -1
  152. package/dist/commands/scale.js +4 -3
  153. package/dist/commands/scale.js.map +1 -1
  154. package/dist/commands/secret.d.ts +54 -0
  155. package/dist/commands/secret.d.ts.map +1 -1
  156. package/dist/commands/secret.js +236 -34
  157. package/dist/commands/secret.js.map +1 -1
  158. package/dist/commands/service-units.d.ts +181 -0
  159. package/dist/commands/service-units.d.ts.map +1 -0
  160. package/dist/commands/service-units.js +291 -0
  161. package/dist/commands/service-units.js.map +1 -0
  162. package/dist/commands/service.d.ts +39 -0
  163. package/dist/commands/service.d.ts.map +1 -0
  164. package/dist/commands/service.js +370 -0
  165. package/dist/commands/service.js.map +1 -0
  166. package/dist/commands/spinner.d.ts +29 -0
  167. package/dist/commands/spinner.d.ts.map +1 -0
  168. package/dist/commands/spinner.js +44 -0
  169. package/dist/commands/spinner.js.map +1 -0
  170. package/dist/commands/stack.d.ts +49 -1
  171. package/dist/commands/stack.d.ts.map +1 -1
  172. package/dist/commands/stack.js +341 -13
  173. package/dist/commands/stack.js.map +1 -1
  174. package/dist/commands/start.d.ts +16 -0
  175. package/dist/commands/start.d.ts.map +1 -1
  176. package/dist/commands/start.js +20 -3
  177. package/dist/commands/start.js.map +1 -1
  178. package/dist/commands/status.d.ts +9 -0
  179. package/dist/commands/status.d.ts.map +1 -1
  180. package/dist/commands/status.js +62 -29
  181. package/dist/commands/status.js.map +1 -1
  182. package/dist/commands/stop.d.ts +16 -0
  183. package/dist/commands/stop.d.ts.map +1 -1
  184. package/dist/commands/stop.js +34 -6
  185. package/dist/commands/stop.js.map +1 -1
  186. package/dist/commands/tor.d.ts +20 -0
  187. package/dist/commands/tor.d.ts.map +1 -0
  188. package/dist/commands/tor.js +180 -0
  189. package/dist/commands/tor.js.map +1 -0
  190. package/dist/commands/up.d.ts +85 -1
  191. package/dist/commands/up.d.ts.map +1 -1
  192. package/dist/commands/up.js +181 -33
  193. package/dist/commands/up.js.map +1 -1
  194. package/dist/commands/webapp.d.ts.map +1 -1
  195. package/dist/commands/webapp.js +2 -1
  196. package/dist/commands/webapp.js.map +1 -1
  197. package/dist/config/defaults.d.ts +16 -0
  198. package/dist/config/defaults.d.ts.map +1 -1
  199. package/dist/config/defaults.js +26 -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 +2 -0
  203. package/dist/config/define-ecosystem.js.map +1 -1
  204. package/dist/config/index.d.ts +1 -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.d.ts +45 -1
  209. package/dist/config/loader.d.ts.map +1 -1
  210. package/dist/config/loader.js +93 -11
  211. package/dist/config/loader.js.map +1 -1
  212. package/dist/config/omnitron-config.schema.d.ts.map +1 -1
  213. package/dist/config/omnitron-config.schema.js +16 -0
  214. package/dist/config/omnitron-config.schema.js.map +1 -1
  215. package/dist/config/types.d.ts +425 -42
  216. package/dist/config/types.d.ts.map +1 -1
  217. package/dist/daemon/config-reload.d.ts +27 -0
  218. package/dist/daemon/config-reload.d.ts.map +1 -0
  219. package/dist/daemon/config-reload.js +42 -0
  220. package/dist/daemon/config-reload.js.map +1 -0
  221. package/dist/daemon/daemon-client.d.ts +150 -2
  222. package/dist/daemon/daemon-client.d.ts.map +1 -1
  223. package/dist/daemon/daemon-client.js +311 -21
  224. package/dist/daemon/daemon-client.js.map +1 -1
  225. package/dist/daemon/daemon-entry.js +92 -38
  226. package/dist/daemon/daemon-entry.js.map +1 -1
  227. package/dist/daemon/daemon-scheduler.d.ts +4 -0
  228. package/dist/daemon/daemon-scheduler.d.ts.map +1 -1
  229. package/dist/daemon/daemon-scheduler.js +137 -7
  230. package/dist/daemon/daemon-scheduler.js.map +1 -1
  231. package/dist/daemon/daemon-state-store.service.d.ts +263 -0
  232. package/dist/daemon/daemon-state-store.service.d.ts.map +1 -0
  233. package/dist/daemon/daemon-state-store.service.js +446 -0
  234. package/dist/daemon/daemon-state-store.service.js.map +1 -0
  235. package/dist/daemon/daemon.d.ts +207 -1
  236. package/dist/daemon/daemon.d.ts.map +1 -1
  237. package/dist/daemon/daemon.js +1254 -143
  238. package/dist/daemon/daemon.js.map +1 -1
  239. package/dist/daemon/daemon.module.d.ts.map +1 -1
  240. package/dist/daemon/daemon.module.js +186 -79
  241. package/dist/daemon/daemon.module.js.map +1 -1
  242. package/dist/daemon/daemon.rpc-service.d.ts +109 -1
  243. package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
  244. package/dist/daemon/daemon.rpc-service.js +451 -95
  245. package/dist/daemon/daemon.rpc-service.js.map +1 -1
  246. package/dist/daemon/pid-manager.d.ts +69 -5
  247. package/dist/daemon/pid-manager.d.ts.map +1 -1
  248. package/dist/daemon/pid-manager.js +151 -17
  249. package/dist/daemon/pid-manager.js.map +1 -1
  250. package/dist/daemon/spawn-daemon.d.ts +73 -0
  251. package/dist/daemon/spawn-daemon.d.ts.map +1 -0
  252. package/dist/daemon/spawn-daemon.js +101 -0
  253. package/dist/daemon/spawn-daemon.js.map +1 -0
  254. package/dist/daemon/state-store.d.ts +65 -3
  255. package/dist/daemon/state-store.d.ts.map +1 -1
  256. package/dist/daemon/state-store.js +103 -19
  257. package/dist/daemon/state-store.js.map +1 -1
  258. package/dist/database/connection.d.ts +71 -0
  259. package/dist/database/connection.d.ts.map +1 -0
  260. package/dist/database/connection.js +129 -0
  261. package/dist/database/connection.js.map +1 -0
  262. package/dist/database/dialect.d.ts +60 -0
  263. package/dist/database/dialect.d.ts.map +1 -0
  264. package/dist/database/dialect.js +80 -0
  265. package/dist/database/dialect.js.map +1 -0
  266. package/dist/database/migrate.d.ts +5 -2
  267. package/dist/database/migrate.d.ts.map +1 -1
  268. package/dist/database/migrate.js +23 -33
  269. package/dist/database/migrate.js.map +1 -1
  270. package/dist/database/migration-runner.d.ts +50 -0
  271. package/dist/database/migration-runner.d.ts.map +1 -0
  272. package/dist/database/migration-runner.js +143 -0
  273. package/dist/database/migration-runner.js.map +1 -0
  274. package/dist/database/migrations/006_login_throttle.d.ts +17 -0
  275. package/dist/database/migrations/006_login_throttle.d.ts.map +1 -0
  276. package/dist/database/migrations/006_login_throttle.js +33 -0
  277. package/dist/database/migrations/006_login_throttle.js.map +1 -0
  278. package/dist/database/migrations/007_sync_ingested.d.ts +21 -0
  279. package/dist/database/migrations/007_sync_ingested.d.ts.map +1 -0
  280. package/dist/database/migrations/007_sync_ingested.js +39 -0
  281. package/dist/database/migrations/007_sync_ingested.js.map +1 -0
  282. package/dist/database/migrations/008_node_health_version_text.d.ts +24 -0
  283. package/dist/database/migrations/008_node_health_version_text.d.ts.map +1 -0
  284. package/dist/database/migrations/008_node_health_version_text.js +35 -0
  285. package/dist/database/migrations/008_node_health_version_text.js.map +1 -0
  286. package/dist/database/migrations/009_node_health_unmeasured.d.ts +40 -0
  287. package/dist/database/migrations/009_node_health_unmeasured.d.ts.map +1 -0
  288. package/dist/database/migrations/009_node_health_unmeasured.js +63 -0
  289. package/dist/database/migrations/009_node_health_unmeasured.js.map +1 -0
  290. package/dist/database/migrations/010_metrics_raw_had_no_reader.d.ts +22 -0
  291. package/dist/database/migrations/010_metrics_raw_had_no_reader.d.ts.map +1 -0
  292. package/dist/database/migrations/010_metrics_raw_had_no_reader.js +26 -0
  293. package/dist/database/migrations/010_metrics_raw_had_no_reader.js.map +1 -0
  294. package/dist/database/migrations/011_alerts_nobody_had_to_write.d.ts +56 -0
  295. package/dist/database/migrations/011_alerts_nobody_had_to_write.d.ts.map +1 -0
  296. package/dist/database/migrations/011_alerts_nobody_had_to_write.js +79 -0
  297. package/dist/database/migrations/011_alerts_nobody_had_to_write.js.map +1 -0
  298. package/dist/database/migrations/012_a_disk_nobody_watched.d.ts +26 -0
  299. package/dist/database/migrations/012_a_disk_nobody_watched.d.ts.map +1 -0
  300. package/dist/database/migrations/012_a_disk_nobody_watched.js +40 -0
  301. package/dist/database/migrations/012_a_disk_nobody_watched.js.map +1 -0
  302. package/dist/database/migrations/013_an_alarm_delivered.d.ts +14 -0
  303. package/dist/database/migrations/013_an_alarm_delivered.d.ts.map +1 -0
  304. package/dist/database/migrations/013_an_alarm_delivered.js +28 -0
  305. package/dist/database/migrations/013_an_alarm_delivered.js.map +1 -0
  306. package/dist/database/migrations/index.d.ts +21 -0
  307. package/dist/database/migrations/index.d.ts.map +1 -0
  308. package/dist/database/migrations/index.js +47 -0
  309. package/dist/database/migrations/index.js.map +1 -0
  310. package/dist/database/schema.d.ts +27 -11
  311. package/dist/database/schema.d.ts.map +1 -1
  312. package/dist/database/sqlite-date-binding.d.ts +71 -0
  313. package/dist/database/sqlite-date-binding.d.ts.map +1 -0
  314. package/dist/database/sqlite-date-binding.js +174 -0
  315. package/dist/database/sqlite-date-binding.js.map +1 -0
  316. package/dist/execution/data-channel.d.ts +48 -0
  317. package/dist/execution/data-channel.d.ts.map +1 -0
  318. package/dist/execution/data-channel.js +55 -0
  319. package/dist/execution/data-channel.js.map +1 -0
  320. package/dist/execution/execution.service.d.ts +139 -0
  321. package/dist/execution/execution.service.d.ts.map +1 -1
  322. package/dist/execution/execution.service.js +273 -10
  323. package/dist/execution/execution.service.js.map +1 -1
  324. package/dist/index.d.ts +1 -1
  325. package/dist/index.d.ts.map +1 -1
  326. package/dist/infrastructure/bare-metal-plan.d.ts +208 -0
  327. package/dist/infrastructure/bare-metal-plan.d.ts.map +1 -0
  328. package/dist/infrastructure/bare-metal-plan.js +363 -0
  329. package/dist/infrastructure/bare-metal-plan.js.map +1 -0
  330. package/dist/infrastructure/bare-metal-runner.d.ts +65 -0
  331. package/dist/infrastructure/bare-metal-runner.d.ts.map +1 -0
  332. package/dist/infrastructure/bare-metal-runner.js +354 -0
  333. package/dist/infrastructure/bare-metal-runner.js.map +1 -0
  334. package/dist/infrastructure/config-payload.d.ts +149 -0
  335. package/dist/infrastructure/config-payload.d.ts.map +1 -0
  336. package/dist/infrastructure/config-payload.js +235 -0
  337. package/dist/infrastructure/config-payload.js.map +1 -0
  338. package/dist/infrastructure/container-runtime.d.ts +179 -1
  339. package/dist/infrastructure/container-runtime.d.ts.map +1 -1
  340. package/dist/infrastructure/container-runtime.js +612 -45
  341. package/dist/infrastructure/container-runtime.js.map +1 -1
  342. package/dist/infrastructure/gateway-reachability.d.ts +85 -0
  343. package/dist/infrastructure/gateway-reachability.d.ts.map +1 -0
  344. package/dist/infrastructure/gateway-reachability.js +116 -0
  345. package/dist/infrastructure/gateway-reachability.js.map +1 -0
  346. package/dist/infrastructure/gateway-redis-db.d.ts +29 -0
  347. package/dist/infrastructure/gateway-redis-db.d.ts.map +1 -0
  348. package/dist/infrastructure/gateway-redis-db.js +32 -0
  349. package/dist/infrastructure/gateway-redis-db.js.map +1 -0
  350. package/dist/infrastructure/host-inspection.d.ts +223 -0
  351. package/dist/infrastructure/host-inspection.d.ts.map +1 -0
  352. package/dist/infrastructure/host-inspection.js +501 -0
  353. package/dist/infrastructure/host-inspection.js.map +1 -0
  354. package/dist/infrastructure/infrastructure-gate.d.ts.map +1 -1
  355. package/dist/infrastructure/infrastructure.service.d.ts +247 -6
  356. package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
  357. package/dist/infrastructure/infrastructure.service.js +571 -96
  358. package/dist/infrastructure/infrastructure.service.js.map +1 -1
  359. package/dist/infrastructure/known-machines.d.ts +76 -0
  360. package/dist/infrastructure/known-machines.d.ts.map +1 -0
  361. package/dist/infrastructure/known-machines.js +94 -0
  362. package/dist/infrastructure/known-machines.js.map +1 -0
  363. package/dist/infrastructure/node-credentials.d.ts +45 -0
  364. package/dist/infrastructure/node-credentials.d.ts.map +1 -0
  365. package/dist/infrastructure/node-credentials.js +80 -0
  366. package/dist/infrastructure/node-credentials.js.map +1 -0
  367. package/dist/infrastructure/orphan-containers.d.ts +99 -0
  368. package/dist/infrastructure/orphan-containers.d.ts.map +1 -0
  369. package/dist/infrastructure/orphan-containers.js +106 -0
  370. package/dist/infrastructure/orphan-containers.js.map +1 -0
  371. package/dist/infrastructure/phantom-endpoint-janitor.d.ts +92 -0
  372. package/dist/infrastructure/phantom-endpoint-janitor.d.ts.map +1 -0
  373. package/dist/infrastructure/phantom-endpoint-janitor.js +199 -0
  374. package/dist/infrastructure/phantom-endpoint-janitor.js.map +1 -0
  375. package/dist/infrastructure/presets/gateway-openresty.d.ts.map +1 -1
  376. package/dist/infrastructure/presets/gateway-openresty.js +29 -3
  377. package/dist/infrastructure/presets/gateway-openresty.js.map +1 -1
  378. package/dist/infrastructure/presets/index.d.ts.map +1 -1
  379. package/dist/infrastructure/presets/index.js +2 -0
  380. package/dist/infrastructure/presets/index.js.map +1 -1
  381. package/dist/infrastructure/presets/minio.d.ts.map +1 -1
  382. package/dist/infrastructure/presets/minio.js +36 -3
  383. package/dist/infrastructure/presets/minio.js.map +1 -1
  384. package/dist/infrastructure/presets/postgres.d.ts.map +1 -1
  385. package/dist/infrastructure/presets/postgres.js +78 -5
  386. package/dist/infrastructure/presets/postgres.js.map +1 -1
  387. package/dist/infrastructure/presets/redis.d.ts.map +1 -1
  388. package/dist/infrastructure/presets/redis.js +23 -4
  389. package/dist/infrastructure/presets/redis.js.map +1 -1
  390. package/dist/infrastructure/presets/registry.d.ts.map +1 -1
  391. package/dist/infrastructure/presets/registry.js +85 -4
  392. package/dist/infrastructure/presets/registry.js.map +1 -1
  393. package/dist/infrastructure/presets/tor.d.ts +112 -0
  394. package/dist/infrastructure/presets/tor.d.ts.map +1 -0
  395. package/dist/infrastructure/presets/tor.js +185 -0
  396. package/dist/infrastructure/presets/tor.js.map +1 -0
  397. package/dist/infrastructure/presets/torrc.d.ts +54 -0
  398. package/dist/infrastructure/presets/torrc.d.ts.map +1 -0
  399. package/dist/infrastructure/presets/torrc.js +153 -0
  400. package/dist/infrastructure/presets/torrc.js.map +1 -0
  401. package/dist/infrastructure/presets/types.d.ts +17 -0
  402. package/dist/infrastructure/presets/types.d.ts.map +1 -1
  403. package/dist/infrastructure/provisioning-outcome.d.ts +85 -0
  404. package/dist/infrastructure/provisioning-outcome.d.ts.map +1 -0
  405. package/dist/infrastructure/provisioning-outcome.js +122 -0
  406. package/dist/infrastructure/provisioning-outcome.js.map +1 -0
  407. package/dist/infrastructure/server-registry.d.ts +27 -0
  408. package/dist/infrastructure/server-registry.d.ts.map +1 -1
  409. package/dist/infrastructure/server-registry.js +60 -5
  410. package/dist/infrastructure/server-registry.js.map +1 -1
  411. package/dist/infrastructure/service-binding.d.ts +60 -0
  412. package/dist/infrastructure/service-binding.d.ts.map +1 -0
  413. package/dist/infrastructure/service-binding.js +90 -0
  414. package/dist/infrastructure/service-binding.js.map +1 -0
  415. package/dist/infrastructure/service-credentials.d.ts +105 -0
  416. package/dist/infrastructure/service-credentials.d.ts.map +1 -0
  417. package/dist/infrastructure/service-credentials.js +141 -0
  418. package/dist/infrastructure/service-credentials.js.map +1 -0
  419. package/dist/infrastructure/service-resolver.d.ts +191 -12
  420. package/dist/infrastructure/service-resolver.d.ts.map +1 -1
  421. package/dist/infrastructure/service-resolver.js +521 -60
  422. package/dist/infrastructure/service-resolver.js.map +1 -1
  423. package/dist/infrastructure/shipped-config.d.ts +43 -0
  424. package/dist/infrastructure/shipped-config.d.ts.map +1 -0
  425. package/dist/infrastructure/shipped-config.js +159 -0
  426. package/dist/infrastructure/shipped-config.js.map +1 -0
  427. package/dist/infrastructure/stack-infra-manager.d.ts +7 -1
  428. package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
  429. package/dist/infrastructure/stack-infra-manager.js +32 -14
  430. package/dist/infrastructure/stack-infra-manager.js.map +1 -1
  431. package/dist/infrastructure/types.d.ts +278 -6
  432. package/dist/infrastructure/types.d.ts.map +1 -1
  433. package/dist/mcp/mcp-bridge.d.ts.map +1 -1
  434. package/dist/mcp/mcp-bridge.js.map +1 -1
  435. package/dist/mcp/tool-groups/apps.tools.d.ts +2 -1
  436. package/dist/mcp/tool-groups/apps.tools.d.ts.map +1 -1
  437. package/dist/mcp/tool-groups/apps.tools.js +2 -2
  438. package/dist/mcp/tool-groups/apps.tools.js.map +1 -1
  439. package/dist/mcp/tool-groups/infra.tools.d.ts +16 -2
  440. package/dist/mcp/tool-groups/infra.tools.d.ts.map +1 -1
  441. package/dist/mcp/tool-groups/infra.tools.js +51 -41
  442. package/dist/mcp/tool-groups/infra.tools.js.map +1 -1
  443. package/dist/mcp/tool-groups/kb.tools.d.ts.map +1 -1
  444. package/dist/mcp/tool-groups/kb.tools.js +4 -6
  445. package/dist/mcp/tool-groups/kb.tools.js.map +1 -1
  446. package/dist/mcp/tool-groups/management.tools.d.ts +20 -3
  447. package/dist/mcp/tool-groups/management.tools.d.ts.map +1 -1
  448. package/dist/mcp/tool-groups/management.tools.js +158 -125
  449. package/dist/mcp/tool-groups/management.tools.js.map +1 -1
  450. package/dist/mcp/tool-groups/monitoring.tools.d.ts +10 -1
  451. package/dist/mcp/tool-groups/monitoring.tools.d.ts.map +1 -1
  452. package/dist/mcp/tool-groups/monitoring.tools.js +65 -18
  453. package/dist/mcp/tool-groups/monitoring.tools.js.map +1 -1
  454. package/dist/mcp/unavailable-tools.d.ts +35 -0
  455. package/dist/mcp/unavailable-tools.d.ts.map +1 -0
  456. package/dist/mcp/unavailable-tools.js +59 -0
  457. package/dist/mcp/unavailable-tools.js.map +1 -0
  458. package/dist/monitoring/app-health.indicator.d.ts.map +1 -1
  459. package/dist/monitoring/app-health.indicator.js +12 -0
  460. package/dist/monitoring/app-health.indicator.js.map +1 -1
  461. package/dist/monitoring/desktop-notifier.d.ts +26 -0
  462. package/dist/monitoring/desktop-notifier.d.ts.map +1 -0
  463. package/dist/monitoring/desktop-notifier.js +49 -0
  464. package/dist/monitoring/desktop-notifier.js.map +1 -0
  465. package/dist/monitoring/docker-health.indicator.d.ts +17 -1
  466. package/dist/monitoring/docker-health.indicator.d.ts.map +1 -1
  467. package/dist/monitoring/docker-health.indicator.js +38 -8
  468. package/dist/monitoring/docker-health.indicator.js.map +1 -1
  469. package/dist/monitoring/event-loop-stall.indicator.d.ts +26 -0
  470. package/dist/monitoring/event-loop-stall.indicator.d.ts.map +1 -0
  471. package/dist/monitoring/event-loop-stall.indicator.js +47 -0
  472. package/dist/monitoring/event-loop-stall.indicator.js.map +1 -0
  473. package/dist/monitoring/event-loop-watch.d.ts +124 -0
  474. package/dist/monitoring/event-loop-watch.d.ts.map +1 -0
  475. package/dist/monitoring/event-loop-watch.js +179 -0
  476. package/dist/monitoring/event-loop-watch.js.map +1 -0
  477. package/dist/monitoring/host-disk.d.ts +6 -0
  478. package/dist/monitoring/host-disk.d.ts.map +1 -0
  479. package/dist/monitoring/host-disk.js +26 -0
  480. package/dist/monitoring/host-disk.js.map +1 -0
  481. package/dist/monitoring/log-line.d.ts +188 -0
  482. package/dist/monitoring/log-line.d.ts.map +1 -0
  483. package/dist/monitoring/log-line.js +458 -0
  484. package/dist/monitoring/log-line.js.map +1 -0
  485. package/dist/monitoring/log-manager.d.ts +89 -8
  486. package/dist/monitoring/log-manager.d.ts.map +1 -1
  487. package/dist/monitoring/log-manager.js +246 -77
  488. package/dist/monitoring/log-manager.js.map +1 -1
  489. package/dist/observability/index.d.ts +4 -0
  490. package/dist/observability/index.d.ts.map +1 -0
  491. package/dist/observability/index.js +3 -0
  492. package/dist/observability/index.js.map +1 -0
  493. package/dist/observability/metrics-bridge.d.ts +87 -0
  494. package/dist/observability/metrics-bridge.d.ts.map +1 -0
  495. package/dist/observability/metrics-bridge.js +295 -0
  496. package/dist/observability/metrics-bridge.js.map +1 -0
  497. package/dist/observability/metrics-server.d.ts +68 -0
  498. package/dist/observability/metrics-server.d.ts.map +1 -0
  499. package/dist/observability/metrics-server.js +146 -0
  500. package/dist/observability/metrics-server.js.map +1 -0
  501. package/dist/orchestrator/app-handle.d.ts +170 -3
  502. package/dist/orchestrator/app-handle.d.ts.map +1 -1
  503. package/dist/orchestrator/app-handle.js +177 -3
  504. package/dist/orchestrator/app-handle.js.map +1 -1
  505. package/dist/orchestrator/app-health.d.ts +33 -0
  506. package/dist/orchestrator/app-health.d.ts.map +1 -0
  507. package/dist/orchestrator/app-health.js +74 -0
  508. package/dist/orchestrator/app-health.js.map +1 -0
  509. package/dist/orchestrator/app-traffic.d.ts +14 -0
  510. package/dist/orchestrator/app-traffic.d.ts.map +1 -0
  511. package/dist/orchestrator/app-traffic.js +33 -0
  512. package/dist/orchestrator/app-traffic.js.map +1 -0
  513. package/dist/orchestrator/bootstrap-loader-child.d.ts +19 -0
  514. package/dist/orchestrator/bootstrap-loader-child.d.ts.map +1 -0
  515. package/dist/orchestrator/bootstrap-loader-child.js +43 -0
  516. package/dist/orchestrator/bootstrap-loader-child.js.map +1 -0
  517. package/dist/orchestrator/bootstrap-loader.d.ts +72 -0
  518. package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
  519. package/dist/orchestrator/bootstrap-loader.js +350 -5
  520. package/dist/orchestrator/bootstrap-loader.js.map +1 -1
  521. package/dist/orchestrator/bootstrap-process.d.ts +148 -4
  522. package/dist/orchestrator/bootstrap-process.d.ts.map +1 -1
  523. package/dist/orchestrator/bootstrap-process.js +496 -18
  524. package/dist/orchestrator/bootstrap-process.js.map +1 -1
  525. package/dist/orchestrator/build-service.d.ts +50 -3
  526. package/dist/orchestrator/build-service.d.ts.map +1 -1
  527. package/dist/orchestrator/build-service.js +357 -85
  528. package/dist/orchestrator/build-service.js.map +1 -1
  529. package/dist/orchestrator/call-exposed-service.d.ts +7 -0
  530. package/dist/orchestrator/call-exposed-service.d.ts.map +1 -0
  531. package/dist/orchestrator/call-exposed-service.js +50 -0
  532. package/dist/orchestrator/call-exposed-service.js.map +1 -0
  533. package/dist/orchestrator/classic-launcher.d.ts +1 -0
  534. package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
  535. package/dist/orchestrator/classic-launcher.js +34 -3
  536. package/dist/orchestrator/classic-launcher.js.map +1 -1
  537. package/dist/orchestrator/file-watcher.d.ts +78 -1
  538. package/dist/orchestrator/file-watcher.d.ts.map +1 -1
  539. package/dist/orchestrator/file-watcher.js +240 -43
  540. package/dist/orchestrator/file-watcher.js.map +1 -1
  541. package/dist/orchestrator/module-worker-process.d.ts.map +1 -1
  542. package/dist/orchestrator/module-worker-process.js +10 -0
  543. package/dist/orchestrator/module-worker-process.js.map +1 -1
  544. package/dist/orchestrator/orchestrator.service.d.ts +540 -7
  545. package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
  546. package/dist/orchestrator/orchestrator.service.js +2106 -242
  547. package/dist/orchestrator/orchestrator.service.js.map +1 -1
  548. package/dist/orchestrator/owned-pids.d.ts +83 -0
  549. package/dist/orchestrator/owned-pids.d.ts.map +1 -0
  550. package/dist/orchestrator/owned-pids.js +93 -0
  551. package/dist/orchestrator/owned-pids.js.map +1 -0
  552. package/dist/orchestrator/port-conflict.d.ts +102 -0
  553. package/dist/orchestrator/port-conflict.d.ts.map +1 -0
  554. package/dist/orchestrator/port-conflict.js +131 -0
  555. package/dist/orchestrator/port-conflict.js.map +1 -0
  556. package/dist/orchestrator/process-janitor.d.ts +227 -0
  557. package/dist/orchestrator/process-janitor.d.ts.map +1 -0
  558. package/dist/orchestrator/process-janitor.js +495 -0
  559. package/dist/orchestrator/process-janitor.js.map +1 -0
  560. package/dist/orchestrator/ps-metrics.d.ts +37 -0
  561. package/dist/orchestrator/ps-metrics.d.ts.map +1 -0
  562. package/dist/orchestrator/ps-metrics.js +60 -0
  563. package/dist/orchestrator/ps-metrics.js.map +1 -0
  564. package/dist/orchestrator/service-router.d.ts +58 -2
  565. package/dist/orchestrator/service-router.d.ts.map +1 -1
  566. package/dist/orchestrator/service-router.js +157 -25
  567. package/dist/orchestrator/service-router.js.map +1 -1
  568. package/dist/orchestrator/topology-query.d.ts +49 -0
  569. package/dist/orchestrator/topology-query.d.ts.map +1 -0
  570. package/dist/orchestrator/topology-query.js +54 -0
  571. package/dist/orchestrator/topology-query.js.map +1 -0
  572. package/dist/project/artifact-builder.d.ts +174 -3
  573. package/dist/project/artifact-builder.d.ts.map +1 -1
  574. package/dist/project/artifact-builder.js +454 -42
  575. package/dist/project/artifact-builder.js.map +1 -1
  576. package/dist/project/build-decision.d.ts +58 -0
  577. package/dist/project/build-decision.d.ts.map +1 -0
  578. package/dist/project/build-decision.js +59 -0
  579. package/dist/project/build-decision.js.map +1 -0
  580. package/dist/project/config-resolver.d.ts +6 -4
  581. package/dist/project/config-resolver.d.ts.map +1 -1
  582. package/dist/project/config-resolver.js +158 -78
  583. package/dist/project/config-resolver.js.map +1 -1
  584. package/dist/project/declared-config.d.ts +42 -0
  585. package/dist/project/declared-config.d.ts.map +1 -0
  586. package/dist/project/declared-config.js +60 -0
  587. package/dist/project/declared-config.js.map +1 -0
  588. package/dist/project/dependency-analyzer.d.ts.map +1 -1
  589. package/dist/project/dependency-analyzer.js.map +1 -1
  590. package/dist/project/deploy-phases.d.ts +35 -0
  591. package/dist/project/deploy-phases.d.ts.map +1 -0
  592. package/dist/project/deploy-phases.js +75 -0
  593. package/dist/project/deploy-phases.js.map +1 -0
  594. package/dist/project/exec-in-group.d.ts +49 -0
  595. package/dist/project/exec-in-group.d.ts.map +1 -0
  596. package/dist/project/exec-in-group.js +100 -0
  597. package/dist/project/exec-in-group.js.map +1 -0
  598. package/dist/project/node-app-config.d.ts +101 -0
  599. package/dist/project/node-app-config.d.ts.map +1 -0
  600. package/dist/project/node-app-config.js +134 -0
  601. package/dist/project/node-app-config.js.map +1 -0
  602. package/dist/project/node-app-health.d.ts +76 -0
  603. package/dist/project/node-app-health.d.ts.map +1 -0
  604. package/dist/project/node-app-health.js +119 -0
  605. package/dist/project/node-app-health.js.map +1 -0
  606. package/dist/project/operator-account.d.ts +287 -0
  607. package/dist/project/operator-account.d.ts.map +1 -0
  608. package/dist/project/operator-account.js +373 -0
  609. package/dist/project/operator-account.js.map +1 -0
  610. package/dist/project/registry.d.ts +46 -8
  611. package/dist/project/registry.d.ts.map +1 -1
  612. package/dist/project/registry.js +238 -40
  613. package/dist/project/registry.js.map +1 -1
  614. package/dist/project/requirements-scanner.d.ts +1 -0
  615. package/dist/project/requirements-scanner.d.ts.map +1 -1
  616. package/dist/project/requirements-scanner.js +28 -3
  617. package/dist/project/requirements-scanner.js.map +1 -1
  618. package/dist/project/stack-env.d.ts +37 -0
  619. package/dist/project/stack-env.d.ts.map +1 -0
  620. package/dist/project/stack-env.js +85 -0
  621. package/dist/project/stack-env.js.map +1 -0
  622. package/dist/project/stand-containers.d.ts +4 -0
  623. package/dist/project/stand-containers.d.ts.map +1 -0
  624. package/dist/project/stand-containers.js +27 -0
  625. package/dist/project/stand-containers.js.map +1 -0
  626. package/dist/project/working-tree.d.ts +62 -0
  627. package/dist/project/working-tree.d.ts.map +1 -0
  628. package/dist/project/working-tree.js +101 -0
  629. package/dist/project/working-tree.js.map +1 -0
  630. package/dist/release/artifact-store.d.ts +360 -0
  631. package/dist/release/artifact-store.d.ts.map +1 -0
  632. package/dist/release/artifact-store.js +1040 -0
  633. package/dist/release/artifact-store.js.map +1 -0
  634. package/dist/release/attest-on-node.d.ts +92 -0
  635. package/dist/release/attest-on-node.d.ts.map +1 -0
  636. package/dist/release/attest-on-node.js +207 -0
  637. package/dist/release/attest-on-node.js.map +1 -0
  638. package/dist/release/attest.d.ts +135 -0
  639. package/dist/release/attest.d.ts.map +1 -0
  640. package/dist/release/attest.js +199 -0
  641. package/dist/release/attest.js.map +1 -0
  642. package/dist/release/build-run.d.ts +151 -0
  643. package/dist/release/build-run.d.ts.map +1 -0
  644. package/dist/release/build-run.js +609 -0
  645. package/dist/release/build-run.js.map +1 -0
  646. package/dist/release/builder.d.ts +57 -0
  647. package/dist/release/builder.d.ts.map +1 -0
  648. package/dist/release/builder.js +120 -0
  649. package/dist/release/builder.js.map +1 -0
  650. package/dist/release/daemon-omni.d.ts +41 -0
  651. package/dist/release/daemon-omni.d.ts.map +1 -0
  652. package/dist/release/daemon-omni.js +59 -0
  653. package/dist/release/daemon-omni.js.map +1 -0
  654. package/dist/release/definition-inputs.d.ts +34 -0
  655. package/dist/release/definition-inputs.d.ts.map +1 -0
  656. package/dist/release/definition-inputs.js +90 -0
  657. package/dist/release/definition-inputs.js.map +1 -0
  658. package/dist/release/delivered.d.ts +53 -0
  659. package/dist/release/delivered.d.ts.map +1 -0
  660. package/dist/release/delivered.js +81 -0
  661. package/dist/release/delivered.js.map +1 -0
  662. package/dist/release/layout.d.ts +61 -0
  663. package/dist/release/layout.d.ts.map +1 -0
  664. package/dist/release/layout.js +114 -0
  665. package/dist/release/layout.js.map +1 -0
  666. package/dist/release/load.d.ts +44 -0
  667. package/dist/release/load.d.ts.map +1 -0
  668. package/dist/release/load.js +84 -0
  669. package/dist/release/load.js.map +1 -0
  670. package/dist/release/manifest.d.ts +299 -0
  671. package/dist/release/manifest.d.ts.map +1 -0
  672. package/dist/release/manifest.js +176 -0
  673. package/dist/release/manifest.js.map +1 -0
  674. package/dist/release/migrations.d.ts +32 -0
  675. package/dist/release/migrations.d.ts.map +1 -0
  676. package/dist/release/migrations.js +51 -0
  677. package/dist/release/migrations.js.map +1 -0
  678. package/dist/release/publish.d.ts +71 -0
  679. package/dist/release/publish.d.ts.map +1 -0
  680. package/dist/release/publish.js +102 -0
  681. package/dist/release/publish.js.map +1 -0
  682. package/dist/release/store.d.ts +174 -0
  683. package/dist/release/store.d.ts.map +1 -0
  684. package/dist/release/store.js +257 -0
  685. package/dist/release/store.js.map +1 -0
  686. package/dist/services/alert.rpc-service.d.ts +11 -15
  687. package/dist/services/alert.rpc-service.d.ts.map +1 -1
  688. package/dist/services/alert.rpc-service.js +29 -14
  689. package/dist/services/alert.rpc-service.js.map +1 -1
  690. package/dist/services/alert.service.d.ts +96 -59
  691. package/dist/services/alert.service.d.ts.map +1 -1
  692. package/dist/services/alert.service.js +455 -70
  693. package/dist/services/alert.service.js.map +1 -1
  694. package/dist/services/anonymous-input.d.ts +26 -0
  695. package/dist/services/anonymous-input.d.ts.map +1 -0
  696. package/dist/services/anonymous-input.js +44 -0
  697. package/dist/services/anonymous-input.js.map +1 -0
  698. package/dist/services/audit.rpc-service.d.ts +27 -0
  699. package/dist/services/audit.rpc-service.d.ts.map +1 -0
  700. package/dist/services/audit.rpc-service.js +57 -0
  701. package/dist/services/audit.rpc-service.js.map +1 -0
  702. package/dist/services/audit.service.d.ts +165 -0
  703. package/dist/services/audit.service.d.ts.map +1 -0
  704. package/dist/services/audit.service.js +242 -0
  705. package/dist/services/audit.service.js.map +1 -0
  706. package/dist/services/auth-context.d.ts +21 -1
  707. package/dist/services/auth-context.d.ts.map +1 -1
  708. package/dist/services/auth-context.js +51 -5
  709. package/dist/services/auth-context.js.map +1 -1
  710. package/dist/services/auth.rpc-service.d.ts +0 -1
  711. package/dist/services/auth.rpc-service.d.ts.map +1 -1
  712. package/dist/services/auth.rpc-service.js +23 -9
  713. package/dist/services/auth.rpc-service.js.map +1 -1
  714. package/dist/services/auth.service.d.ts +47 -33
  715. package/dist/services/auth.service.d.ts.map +1 -1
  716. package/dist/services/auth.service.js +163 -24
  717. package/dist/services/auth.service.js.map +1 -1
  718. package/dist/services/backup-pipeline.d.ts +97 -0
  719. package/dist/services/backup-pipeline.d.ts.map +1 -0
  720. package/dist/services/backup-pipeline.js +286 -0
  721. package/dist/services/backup-pipeline.js.map +1 -0
  722. package/dist/services/backup-schedule.d.ts +69 -0
  723. package/dist/services/backup-schedule.d.ts.map +1 -0
  724. package/dist/services/backup-schedule.js +132 -0
  725. package/dist/services/backup-schedule.js.map +1 -0
  726. package/dist/services/backup.rpc-service.d.ts +31 -2
  727. package/dist/services/backup.rpc-service.d.ts.map +1 -1
  728. package/dist/services/backup.rpc-service.js +53 -0
  729. package/dist/services/backup.rpc-service.js.map +1 -1
  730. package/dist/services/backup.service.d.ts +458 -14
  731. package/dist/services/backup.service.d.ts.map +1 -1
  732. package/dist/services/backup.service.js +1368 -170
  733. package/dist/services/backup.service.js.map +1 -1
  734. package/dist/services/bundle-build-worker.d.ts +19 -0
  735. package/dist/services/bundle-build-worker.d.ts.map +1 -0
  736. package/dist/services/bundle-build-worker.js +40 -0
  737. package/dist/services/bundle-build-worker.js.map +1 -0
  738. package/dist/services/bundle-builder.d.ts +487 -0
  739. package/dist/services/bundle-builder.d.ts.map +1 -0
  740. package/dist/services/bundle-builder.js +1083 -0
  741. package/dist/services/bundle-builder.js.map +1 -0
  742. package/dist/services/bundle-worker-protocol.d.ts +19 -0
  743. package/dist/services/bundle-worker-protocol.d.ts.map +1 -0
  744. package/dist/services/bundle-worker-protocol.js +32 -0
  745. package/dist/services/bundle-worker-protocol.js.map +1 -0
  746. package/dist/services/deploy.rpc-service.d.ts +4 -1
  747. package/dist/services/deploy.rpc-service.d.ts.map +1 -1
  748. package/dist/services/deploy.rpc-service.js +10 -0
  749. package/dist/services/deploy.rpc-service.js.map +1 -1
  750. package/dist/services/deploy.service.d.ts +14 -24
  751. package/dist/services/deploy.service.d.ts.map +1 -1
  752. package/dist/services/deploy.service.js +46 -5
  753. package/dist/services/deploy.service.js.map +1 -1
  754. package/dist/services/discovery.rpc-service.d.ts +2 -1
  755. package/dist/services/discovery.rpc-service.d.ts.map +1 -1
  756. package/dist/services/discovery.rpc-service.js.map +1 -1
  757. package/dist/services/discovery.service.d.ts +32 -28
  758. package/dist/services/discovery.service.d.ts.map +1 -1
  759. package/dist/services/discovery.service.js +108 -133
  760. package/dist/services/discovery.service.js.map +1 -1
  761. package/dist/services/event-broadcaster.rpc-service.d.ts +36 -23
  762. package/dist/services/event-broadcaster.rpc-service.d.ts.map +1 -1
  763. package/dist/services/event-broadcaster.rpc-service.js +70 -32
  764. package/dist/services/event-broadcaster.rpc-service.js.map +1 -1
  765. package/dist/services/event-broadcaster.service.d.ts.map +1 -1
  766. package/dist/services/event-broadcaster.service.js.map +1 -1
  767. package/dist/services/fleet.rpc-service.d.ts +2 -1
  768. package/dist/services/fleet.rpc-service.d.ts.map +1 -1
  769. package/dist/services/fleet.rpc-service.js +3 -1
  770. package/dist/services/fleet.rpc-service.js.map +1 -1
  771. package/dist/services/fleet.service.d.ts +32 -34
  772. package/dist/services/fleet.service.d.ts.map +1 -1
  773. package/dist/services/fleet.service.js +66 -10
  774. package/dist/services/fleet.service.js.map +1 -1
  775. package/dist/services/health-check.rpc-service.d.ts +2 -1
  776. package/dist/services/health-check.rpc-service.d.ts.map +1 -1
  777. package/dist/services/health-check.rpc-service.js.map +1 -1
  778. package/dist/services/health-check.service.d.ts +50 -36
  779. package/dist/services/health-check.service.d.ts.map +1 -1
  780. package/dist/services/health-check.service.js +179 -182
  781. package/dist/services/health-check.service.js.map +1 -1
  782. package/dist/services/infrastructure.rpc-service.d.ts +297 -2
  783. package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
  784. package/dist/services/infrastructure.rpc-service.js +523 -11
  785. package/dist/services/infrastructure.rpc-service.js.map +1 -1
  786. package/dist/services/kubernetes.rpc-service.d.ts +2 -1
  787. package/dist/services/kubernetes.rpc-service.d.ts.map +1 -1
  788. package/dist/services/kubernetes.rpc-service.js.map +1 -1
  789. package/dist/services/kubernetes.service.d.ts +5 -39
  790. package/dist/services/kubernetes.service.d.ts.map +1 -1
  791. package/dist/services/kubernetes.service.js +29 -5
  792. package/dist/services/kubernetes.service.js.map +1 -1
  793. package/dist/services/local-bundle.d.ts +151 -0
  794. package/dist/services/local-bundle.d.ts.map +1 -0
  795. package/dist/services/local-bundle.js +233 -0
  796. package/dist/services/local-bundle.js.map +1 -0
  797. package/dist/services/lockout.d.ts +28 -0
  798. package/dist/services/lockout.d.ts.map +1 -0
  799. package/dist/services/lockout.js +41 -0
  800. package/dist/services/lockout.js.map +1 -0
  801. package/dist/services/log-collector.rpc-service.d.ts +16 -0
  802. package/dist/services/log-collector.rpc-service.d.ts.map +1 -1
  803. package/dist/services/log-collector.rpc-service.js +16 -0
  804. package/dist/services/log-collector.rpc-service.js.map +1 -1
  805. package/dist/services/log-collector.service.d.ts +172 -54
  806. package/dist/services/log-collector.service.d.ts.map +1 -1
  807. package/dist/services/log-collector.service.js +492 -102
  808. package/dist/services/log-collector.service.js.map +1 -1
  809. package/dist/services/log-retention.d.ts +50 -0
  810. package/dist/services/log-retention.d.ts.map +1 -0
  811. package/dist/services/log-retention.js +52 -0
  812. package/dist/services/log-retention.js.map +1 -0
  813. package/dist/services/maintenance-lock.d.ts +162 -0
  814. package/dist/services/maintenance-lock.d.ts.map +1 -0
  815. package/dist/services/maintenance-lock.js +316 -0
  816. package/dist/services/maintenance-lock.js.map +1 -0
  817. package/dist/services/master-address.d.ts +107 -0
  818. package/dist/services/master-address.d.ts.map +1 -0
  819. package/dist/services/master-address.js +154 -0
  820. package/dist/services/master-address.js.map +1 -0
  821. package/dist/services/migration-output.d.ts +16 -0
  822. package/dist/services/migration-output.d.ts.map +1 -0
  823. package/dist/services/migration-output.js +22 -0
  824. package/dist/services/migration-output.js.map +1 -0
  825. package/dist/services/netron-telemetry-transport.d.ts.map +1 -1
  826. package/dist/services/netron-telemetry-transport.js +0 -4
  827. package/dist/services/netron-telemetry-transport.js.map +1 -1
  828. package/dist/services/node-deploy-lease.d.ts +154 -0
  829. package/dist/services/node-deploy-lease.d.ts.map +1 -0
  830. package/dist/services/node-deploy-lease.js +386 -0
  831. package/dist/services/node-deploy-lease.js.map +1 -0
  832. package/dist/services/node-health.repository.d.ts +63 -4
  833. package/dist/services/node-health.repository.d.ts.map +1 -1
  834. package/dist/services/node-health.repository.js +108 -67
  835. package/dist/services/node-health.repository.js.map +1 -1
  836. package/dist/services/node-manager.rpc-service.d.ts +355 -10
  837. package/dist/services/node-manager.rpc-service.d.ts.map +1 -1
  838. package/dist/services/node-manager.rpc-service.js +730 -34
  839. package/dist/services/node-manager.rpc-service.js.map +1 -1
  840. package/dist/services/node-manager.service.d.ts +267 -95
  841. package/dist/services/node-manager.service.d.ts.map +1 -1
  842. package/dist/services/node-manager.service.js +806 -104
  843. package/dist/services/node-manager.service.js.map +1 -1
  844. package/dist/services/node-upgrade.d.ts +129 -0
  845. package/dist/services/node-upgrade.d.ts.map +1 -0
  846. package/dist/services/node-upgrade.js +161 -0
  847. package/dist/services/node-upgrade.js.map +1 -0
  848. package/dist/services/node-upgrade.service.d.ts +232 -0
  849. package/dist/services/node-upgrade.service.d.ts.map +1 -0
  850. package/dist/services/node-upgrade.service.js +468 -0
  851. package/dist/services/node-upgrade.service.js.map +1 -0
  852. package/dist/services/pipeline.rpc-service.d.ts +2 -1
  853. package/dist/services/pipeline.rpc-service.d.ts.map +1 -1
  854. package/dist/services/pipeline.rpc-service.js.map +1 -1
  855. package/dist/services/pipeline.service.d.ts +30 -46
  856. package/dist/services/pipeline.service.d.ts.map +1 -1
  857. package/dist/services/pipeline.service.js +125 -11
  858. package/dist/services/pipeline.service.js.map +1 -1
  859. package/dist/services/project.rpc-service.d.ts +137 -5
  860. package/dist/services/project.rpc-service.d.ts.map +1 -1
  861. package/dist/services/project.rpc-service.js +258 -22
  862. package/dist/services/project.rpc-service.js.map +1 -1
  863. package/dist/services/project.service.d.ts +680 -9
  864. package/dist/services/project.service.d.ts.map +1 -1
  865. package/dist/services/project.service.js +2914 -293
  866. package/dist/services/project.service.js.map +1 -1
  867. package/dist/services/redeploy-decision.d.ts +176 -0
  868. package/dist/services/redeploy-decision.d.ts.map +1 -0
  869. package/dist/services/redeploy-decision.js +142 -0
  870. package/dist/services/redeploy-decision.js.map +1 -0
  871. package/dist/services/release.rpc-service.d.ts +159 -0
  872. package/dist/services/release.rpc-service.d.ts.map +1 -0
  873. package/dist/services/release.rpc-service.js +395 -0
  874. package/dist/services/release.rpc-service.js.map +1 -0
  875. package/dist/services/release.service.d.ts +114 -0
  876. package/dist/services/release.service.d.ts.map +1 -0
  877. package/dist/services/release.service.js +208 -0
  878. package/dist/services/release.service.js.map +1 -0
  879. package/dist/services/remote-deployer.service.d.ts +569 -8
  880. package/dist/services/remote-deployer.service.d.ts.map +1 -1
  881. package/dist/services/remote-deployer.service.js +1618 -169
  882. package/dist/services/remote-deployer.service.js.map +1 -1
  883. package/dist/services/remote-ops.service.d.ts +51 -9
  884. package/dist/services/remote-ops.service.d.ts.map +1 -1
  885. package/dist/services/remote-ops.service.js +235 -39
  886. package/dist/services/remote-ops.service.js.map +1 -1
  887. package/dist/services/remote-provisioner.d.ts +122 -0
  888. package/dist/services/remote-provisioner.d.ts.map +1 -0
  889. package/dist/services/remote-provisioner.js +258 -0
  890. package/dist/services/remote-provisioner.js.map +1 -0
  891. package/dist/services/secrets.rpc-service.d.ts +51 -2
  892. package/dist/services/secrets.rpc-service.d.ts.map +1 -1
  893. package/dist/services/secrets.rpc-service.js +81 -2
  894. package/dist/services/secrets.rpc-service.js.map +1 -1
  895. package/dist/services/secrets.service.d.ts +108 -9
  896. package/dist/services/secrets.service.d.ts.map +1 -1
  897. package/dist/services/secrets.service.js +313 -48
  898. package/dist/services/secrets.service.js.map +1 -1
  899. package/dist/services/slave-storage.service.d.ts +107 -0
  900. package/dist/services/slave-storage.service.d.ts.map +1 -1
  901. package/dist/services/slave-storage.service.js +171 -2
  902. package/dist/services/slave-storage.service.js.map +1 -1
  903. package/dist/services/static-carry-forward.d.ts +85 -0
  904. package/dist/services/static-carry-forward.d.ts.map +1 -0
  905. package/dist/services/static-carry-forward.js +159 -0
  906. package/dist/services/static-carry-forward.js.map +1 -0
  907. package/dist/services/sync-policy.d.ts +167 -0
  908. package/dist/services/sync-policy.d.ts.map +1 -0
  909. package/dist/services/sync-policy.js +161 -0
  910. package/dist/services/sync-policy.js.map +1 -0
  911. package/dist/services/sync-summary.d.ts +39 -0
  912. package/dist/services/sync-summary.d.ts.map +1 -0
  913. package/dist/services/sync-summary.js +62 -0
  914. package/dist/services/sync-summary.js.map +1 -0
  915. package/dist/services/sync.rpc-service.d.ts +19 -7
  916. package/dist/services/sync.rpc-service.d.ts.map +1 -1
  917. package/dist/services/sync.rpc-service.js +36 -5
  918. package/dist/services/sync.rpc-service.js.map +1 -1
  919. package/dist/services/sync.service.d.ts +350 -18
  920. package/dist/services/sync.service.d.ts.map +1 -1
  921. package/dist/services/sync.service.js +864 -116
  922. package/dist/services/sync.service.js.map +1 -1
  923. package/dist/services/system-info.rpc-service.d.ts +2 -1
  924. package/dist/services/system-info.rpc-service.d.ts.map +1 -1
  925. package/dist/services/system-info.rpc-service.js.map +1 -1
  926. package/dist/services/system-info.service.d.ts +56 -68
  927. package/dist/services/system-info.service.d.ts.map +1 -1
  928. package/dist/services/system-info.service.js +240 -127
  929. package/dist/services/system-info.service.js.map +1 -1
  930. package/dist/services/telemetry.rpc-service.d.ts.map +1 -1
  931. package/dist/services/telemetry.rpc-service.js +9 -3
  932. package/dist/services/telemetry.rpc-service.js.map +1 -1
  933. package/dist/services/telemetry.service.d.ts +4 -2
  934. package/dist/services/telemetry.service.d.ts.map +1 -1
  935. package/dist/services/telemetry.service.js +4 -2
  936. package/dist/services/telemetry.service.js.map +1 -1
  937. package/dist/services/trace-collector.rpc-service.d.ts +2 -1
  938. package/dist/services/trace-collector.rpc-service.d.ts.map +1 -1
  939. package/dist/services/trace-collector.rpc-service.js.map +1 -1
  940. package/dist/services/trace-collector.service.d.ts +2 -41
  941. package/dist/services/trace-collector.service.d.ts.map +1 -1
  942. package/dist/services/trace-collector.service.js +27 -7
  943. package/dist/services/trace-collector.service.js.map +1 -1
  944. package/dist/services/wait-for-postgres.d.ts +28 -0
  945. package/dist/services/wait-for-postgres.d.ts.map +1 -0
  946. package/dist/services/wait-for-postgres.js +77 -0
  947. package/dist/services/wait-for-postgres.js.map +1 -0
  948. package/dist/shared/alert-expression.d.ts +60 -0
  949. package/dist/shared/alert-expression.d.ts.map +1 -0
  950. package/dist/shared/alert-expression.js +124 -0
  951. package/dist/shared/alert-expression.js.map +1 -0
  952. package/dist/shared/audit-outcome.d.ts +39 -0
  953. package/dist/shared/audit-outcome.d.ts.map +1 -0
  954. package/dist/shared/audit-outcome.js +45 -0
  955. package/dist/shared/audit-outcome.js.map +1 -0
  956. package/dist/shared/build-freshness.d.ts +59 -0
  957. package/dist/shared/build-freshness.d.ts.map +1 -0
  958. package/dist/shared/build-freshness.js +113 -0
  959. package/dist/shared/build-freshness.js.map +1 -0
  960. package/dist/shared/build-stamp.d.ts +26 -0
  961. package/dist/shared/build-stamp.d.ts.map +1 -0
  962. package/dist/shared/build-stamp.js +36 -0
  963. package/dist/shared/build-stamp.js.map +1 -0
  964. package/dist/shared/describe-error.d.ts +22 -0
  965. package/dist/shared/describe-error.d.ts.map +1 -0
  966. package/dist/shared/describe-error.js +39 -0
  967. package/dist/shared/describe-error.js.map +1 -0
  968. package/dist/shared/dto/alerts.d.ts +101 -0
  969. package/dist/shared/dto/alerts.d.ts.map +1 -0
  970. package/dist/shared/dto/alerts.js +9 -0
  971. package/dist/shared/dto/alerts.js.map +1 -0
  972. package/dist/shared/dto/auth.d.ts +49 -0
  973. package/dist/shared/dto/auth.d.ts.map +1 -0
  974. package/dist/shared/dto/auth.js +14 -0
  975. package/dist/shared/dto/auth.js.map +1 -0
  976. package/dist/shared/dto/backups.d.ts +16 -0
  977. package/dist/shared/dto/backups.d.ts.map +1 -0
  978. package/dist/shared/dto/backups.js +9 -0
  979. package/dist/shared/dto/backups.js.map +1 -0
  980. package/dist/shared/dto/deploy.d.ts +28 -0
  981. package/dist/shared/dto/deploy.d.ts.map +1 -0
  982. package/dist/shared/dto/deploy.js +8 -0
  983. package/dist/shared/dto/deploy.js.map +1 -0
  984. package/dist/shared/dto/discovery.d.ts +24 -0
  985. package/dist/shared/dto/discovery.d.ts.map +1 -0
  986. package/dist/shared/dto/discovery.js +9 -0
  987. package/dist/shared/dto/discovery.js.map +1 -0
  988. package/dist/shared/dto/fleet.d.ts +35 -0
  989. package/dist/shared/dto/fleet.d.ts.map +1 -0
  990. package/dist/shared/dto/fleet.js +8 -0
  991. package/dist/shared/dto/fleet.js.map +1 -0
  992. package/dist/shared/dto/health.d.ts +26 -0
  993. package/dist/shared/dto/health.d.ts.map +1 -0
  994. package/dist/shared/dto/health.js +9 -0
  995. package/dist/shared/dto/health.js.map +1 -0
  996. package/dist/shared/dto/kubernetes.d.ts +44 -0
  997. package/dist/shared/dto/kubernetes.d.ts.map +1 -0
  998. package/dist/shared/dto/kubernetes.js +9 -0
  999. package/dist/shared/dto/kubernetes.js.map +1 -0
  1000. package/dist/shared/dto/logs.d.ts +77 -0
  1001. package/dist/shared/dto/logs.d.ts.map +1 -0
  1002. package/dist/shared/dto/logs.js +10 -0
  1003. package/dist/shared/dto/logs.js.map +1 -0
  1004. package/dist/shared/dto/metrics.d.ts +65 -0
  1005. package/dist/shared/dto/metrics.d.ts.map +1 -0
  1006. package/dist/shared/dto/metrics.js +17 -0
  1007. package/dist/shared/dto/metrics.js.map +1 -0
  1008. package/dist/shared/dto/nodes.d.ts +347 -0
  1009. package/dist/shared/dto/nodes.d.ts.map +1 -0
  1010. package/dist/shared/dto/nodes.js +8 -0
  1011. package/dist/shared/dto/nodes.js.map +1 -0
  1012. package/dist/shared/dto/pipelines.d.ts +53 -0
  1013. package/dist/shared/dto/pipelines.d.ts.map +1 -0
  1014. package/dist/shared/dto/pipelines.js +9 -0
  1015. package/dist/shared/dto/pipelines.js.map +1 -0
  1016. package/dist/shared/dto/project.d.ts +143 -9
  1017. package/dist/shared/dto/project.d.ts.map +1 -1
  1018. package/dist/shared/dto/services.d.ts +855 -7
  1019. package/dist/shared/dto/services.d.ts.map +1 -1
  1020. package/dist/shared/dto/system-info.d.ts +93 -0
  1021. package/dist/shared/dto/system-info.d.ts.map +1 -0
  1022. package/dist/shared/dto/system-info.js +9 -0
  1023. package/dist/shared/dto/system-info.js.map +1 -0
  1024. package/dist/shared/dto/traces.d.ts +49 -0
  1025. package/dist/shared/dto/traces.d.ts.map +1 -0
  1026. package/dist/shared/dto/traces.js +9 -0
  1027. package/dist/shared/dto/traces.js.map +1 -0
  1028. package/dist/shared/env-config.d.ts +74 -0
  1029. package/dist/shared/env-config.d.ts.map +1 -0
  1030. package/dist/shared/env-config.js +99 -0
  1031. package/dist/shared/env-config.js.map +1 -0
  1032. package/dist/shared/events.d.ts +45 -39
  1033. package/dist/shared/events.d.ts.map +1 -1
  1034. package/dist/shared/events.js +6 -0
  1035. package/dist/shared/events.js.map +1 -1
  1036. package/dist/shared/format.d.ts +1 -1
  1037. package/dist/shared/format.d.ts.map +1 -1
  1038. package/dist/shared/format.js +4 -0
  1039. package/dist/shared/format.js.map +1 -1
  1040. package/dist/shared/local-infra-host.d.ts +28 -0
  1041. package/dist/shared/local-infra-host.d.ts.map +1 -0
  1042. package/dist/shared/local-infra-host.js +28 -0
  1043. package/dist/shared/local-infra-host.js.map +1 -0
  1044. package/dist/shared/node-check.d.ts +37 -0
  1045. package/dist/shared/node-check.d.ts.map +1 -0
  1046. package/dist/shared/node-check.js +41 -0
  1047. package/dist/shared/node-check.js.map +1 -0
  1048. package/dist/shared/paths.d.ts +29 -0
  1049. package/dist/shared/paths.d.ts.map +1 -0
  1050. package/dist/shared/paths.js +54 -0
  1051. package/dist/shared/paths.js.map +1 -0
  1052. package/dist/shared/pnpm.d.ts +29 -0
  1053. package/dist/shared/pnpm.d.ts.map +1 -0
  1054. package/dist/shared/pnpm.js +57 -0
  1055. package/dist/shared/pnpm.js.map +1 -0
  1056. package/dist/shared/private-files.d.ts +29 -0
  1057. package/dist/shared/private-files.d.ts.map +1 -0
  1058. package/dist/shared/private-files.js +109 -0
  1059. package/dist/shared/private-files.js.map +1 -0
  1060. package/dist/shared/redact-env.d.ts +24 -0
  1061. package/dist/shared/redact-env.d.ts.map +1 -0
  1062. package/dist/shared/redact-env.js +36 -0
  1063. package/dist/shared/redact-env.js.map +1 -0
  1064. package/dist/shared/release-reading.d.ts +85 -0
  1065. package/dist/shared/release-reading.d.ts.map +1 -0
  1066. package/dist/shared/release-reading.js +81 -0
  1067. package/dist/shared/release-reading.js.map +1 -0
  1068. package/dist/shared/roles.d.ts +58 -3
  1069. package/dist/shared/roles.d.ts.map +1 -1
  1070. package/dist/shared/roles.js +55 -0
  1071. package/dist/shared/roles.js.map +1 -1
  1072. package/dist/shared/rpcauth.d.ts +13 -0
  1073. package/dist/shared/rpcauth.d.ts.map +1 -0
  1074. package/dist/shared/rpcauth.js +23 -0
  1075. package/dist/shared/rpcauth.js.map +1 -0
  1076. package/dist/shared/shell-escape.d.ts +9 -0
  1077. package/dist/shared/shell-escape.d.ts.map +1 -0
  1078. package/dist/shared/shell-escape.js +11 -0
  1079. package/dist/shared/shell-escape.js.map +1 -0
  1080. package/dist/shared/sync-reading.d.ts +45 -0
  1081. package/dist/shared/sync-reading.d.ts.map +1 -0
  1082. package/dist/shared/sync-reading.js +42 -0
  1083. package/dist/shared/sync-reading.js.map +1 -0
  1084. package/dist/shared/tokens.d.ts +19 -0
  1085. package/dist/shared/tokens.d.ts.map +1 -1
  1086. package/dist/shared/tokens.js +28 -0
  1087. package/dist/shared/tokens.js.map +1 -1
  1088. package/dist/webapp/webapp.service.d.ts +33 -2
  1089. package/dist/webapp/webapp.service.d.ts.map +1 -1
  1090. package/dist/webapp/webapp.service.js +143 -31
  1091. package/dist/webapp/webapp.service.js.map +1 -1
  1092. package/dist/workers/health-monitor-process.d.ts.map +1 -1
  1093. package/dist/workers/health-monitor-process.js +10 -0
  1094. package/dist/workers/health-monitor-process.js.map +1 -1
  1095. package/dist/workers/health-monitor.service.d.ts +45 -3
  1096. package/dist/workers/health-monitor.service.d.ts.map +1 -1
  1097. package/dist/workers/health-monitor.service.js +156 -26
  1098. package/dist/workers/health-monitor.service.js.map +1 -1
  1099. package/dist/workers/system-worker-manager.d.ts +43 -0
  1100. package/dist/workers/system-worker-manager.d.ts.map +1 -1
  1101. package/dist/workers/system-worker-manager.js +150 -3
  1102. package/dist/workers/system-worker-manager.js.map +1 -1
  1103. package/dist/workers/types.d.ts +11 -2
  1104. package/dist/workers/types.d.ts.map +1 -1
  1105. package/package.json +64 -29
  1106. package/webapp/dist/BUILD.json +5 -0
  1107. package/webapp/dist/assets/FormControlLabel-BrH2diyl.js +1 -0
  1108. package/webapp/dist/assets/Grid-BZ7xhoHQ.js +1 -0
  1109. package/webapp/dist/assets/Switch-DGSAxQPM.js +1 -0
  1110. package/webapp/dist/assets/alerts-sXqHJbsa.js +1 -0
  1111. package/webapp/dist/assets/apps-B_Ah8Flh.js +1 -0
  1112. package/webapp/dist/assets/audit-YXihwyui.js +1 -0
  1113. package/webapp/dist/assets/client-CGbBvy9k.js +1 -0
  1114. package/webapp/dist/assets/constants-veKGFkC6.js +1 -0
  1115. package/webapp/dist/assets/containers-Di4NOTLf.js +2 -0
  1116. package/webapp/dist/assets/daemon-memory-CCPZ4cV_.js +1 -0
  1117. package/webapp/dist/assets/dashboard-Cfuf95N3.js +1 -0
  1118. package/webapp/dist/assets/dashboard-builder-CowaYb-M.js +1 -0
  1119. package/webapp/dist/assets/deploy-release-dialog-D6C-XcKY.js +1 -0
  1120. package/webapp/dist/assets/deployments-ChTq5kCw.js +1 -0
  1121. package/webapp/dist/assets/detail-BsGWv7Dj.js +1 -0
  1122. package/webapp/dist/assets/detail-CyVf_cw5.js +2 -0
  1123. package/webapp/dist/assets/detail-D027j6aO.js +8 -0
  1124. package/webapp/dist/assets/formatters-BqjL73N3.js +1 -0
  1125. package/webapp/dist/assets/index-B8AHIPc-.css +1 -0
  1126. package/webapp/dist/assets/index-DiZbrwxT.js +414 -0
  1127. package/webapp/dist/assets/logs-L2b8Oqmo.js +12 -0
  1128. package/webapp/dist/assets/maplibre-gl-MlmG2kbn.js +801 -0
  1129. package/webapp/dist/assets/metrics-CQgjz2Lz.js +2 -0
  1130. package/webapp/dist/assets/nodes-Nwia2Ty6.js +7 -0
  1131. package/webapp/dist/assets/pipelines-DCu_rXOr.js +4 -0
  1132. package/webapp/dist/assets/projects-Bb4aV76h.js +1 -0
  1133. package/webapp/dist/assets/react-CwJFpaho.js +1 -0
  1134. package/webapp/dist/assets/react-apexcharts.esm-Dy0Fez42.js +1956 -0
  1135. package/webapp/dist/assets/release-bits-Dj4EOhhb.js +1 -0
  1136. package/webapp/dist/assets/release-build-panel-D6OCVlhT.js +1 -0
  1137. package/webapp/dist/assets/releases-CnAt3yO6.js +2 -0
  1138. package/webapp/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  1139. package/webapp/dist/assets/settings-DU-mjiwb.js +1 -0
  1140. package/webapp/dist/assets/settled-pair-C7SjZuns.js +1 -0
  1141. package/webapp/dist/assets/sign-in-Bcvjrr4C.js +1 -0
  1142. package/webapp/dist/assets/stacks-B-e8p50I.js +1 -0
  1143. package/webapp/dist/assets/sync-reading-C3PnJ7AX.js +1 -0
  1144. package/webapp/dist/assets/system-info-BtE3dgfw.js +1 -0
  1145. package/webapp/dist/assets/topology-Cwch-uF1.js +12 -0
  1146. package/webapp/dist/assets/topology-DLioOiRN.css +1 -0
  1147. package/webapp/dist/assets/traces-BbShR-4P.js +1 -0
  1148. package/webapp/dist/assets/use-stack-context-CZUgPq88.js +1 -0
  1149. package/webapp/dist/index.html +6 -7
  1150. package/dist/commands/dev.d.ts +0 -23
  1151. package/dist/commands/dev.d.ts.map +0 -1
  1152. package/dist/commands/dev.js +0 -76
  1153. package/dist/commands/dev.js.map +0 -1
  1154. package/dist/config/define-app.d.ts +0 -6
  1155. package/dist/config/define-app.d.ts.map +0 -1
  1156. package/dist/config/define-app.js +0 -38
  1157. package/dist/config/define-app.js.map +0 -1
  1158. package/dist/infrastructure/gateway-generator.d.ts +0 -63
  1159. package/dist/infrastructure/gateway-generator.d.ts.map +0 -1
  1160. package/dist/infrastructure/gateway-generator.js +0 -253
  1161. package/dist/infrastructure/gateway-generator.js.map +0 -1
  1162. package/dist/monitoring/health.service.d.ts +0 -14
  1163. package/dist/monitoring/health.service.d.ts.map +0 -1
  1164. package/dist/monitoring/health.service.js +0 -50
  1165. package/dist/monitoring/health.service.js.map +0 -1
  1166. package/dist/monitoring/metrics.service.d.ts +0 -14
  1167. package/dist/monitoring/metrics.service.d.ts.map +0 -1
  1168. package/dist/monitoring/metrics.service.js +0 -40
  1169. package/dist/monitoring/metrics.service.js.map +0 -1
  1170. package/dist/orchestrator/bootstrap-runner.d.ts +0 -15
  1171. package/dist/orchestrator/bootstrap-runner.d.ts.map +0 -1
  1172. package/dist/orchestrator/bootstrap-runner.js +0 -152
  1173. package/dist/orchestrator/bootstrap-runner.js.map +0 -1
  1174. package/dist/orchestrator/topology-proxy.d.ts +0 -65
  1175. package/dist/orchestrator/topology-proxy.d.ts.map +0 -1
  1176. package/dist/orchestrator/topology-proxy.js +0 -209
  1177. package/dist/orchestrator/topology-proxy.js.map +0 -1
  1178. package/dist/orchestrator/topology-relay.d.ts +0 -111
  1179. package/dist/orchestrator/topology-relay.d.ts.map +0 -1
  1180. package/dist/orchestrator/topology-relay.js +0 -255
  1181. package/dist/orchestrator/topology-relay.js.map +0 -1
  1182. package/dist/services/auth-middleware.d.ts +0 -47
  1183. package/dist/services/auth-middleware.d.ts.map +0 -1
  1184. package/dist/services/auth-middleware.js +0 -158
  1185. package/dist/services/auth-middleware.js.map +0 -1
  1186. package/dist/services/environment.rpc-service.d.ts +0 -22
  1187. package/dist/services/environment.rpc-service.d.ts.map +0 -1
  1188. package/dist/services/environment.rpc-service.js +0 -65
  1189. package/dist/services/environment.rpc-service.js.map +0 -1
  1190. package/dist/services/environment.service.d.ts +0 -87
  1191. package/dist/services/environment.service.d.ts.map +0 -1
  1192. package/dist/services/environment.service.js +0 -220
  1193. package/dist/services/environment.service.js.map +0 -1
  1194. package/dist/services/metrics-collector.service.d.ts +0 -103
  1195. package/dist/services/metrics-collector.service.d.ts.map +0 -1
  1196. package/dist/services/metrics-collector.service.js +0 -235
  1197. package/dist/services/metrics-collector.service.js.map +0 -1
  1198. package/dist/services/metrics.rpc-service.d.ts +0 -41
  1199. package/dist/services/metrics.rpc-service.d.ts.map +0 -1
  1200. package/dist/services/metrics.rpc-service.js +0 -90
  1201. package/dist/services/metrics.rpc-service.js.map +0 -1
  1202. package/dist/services/metrics.service.d.ts +0 -72
  1203. package/dist/services/metrics.service.d.ts.map +0 -1
  1204. package/dist/services/metrics.service.js +0 -185
  1205. package/dist/services/metrics.service.js.map +0 -1
  1206. package/dist/supervisor/omnitron-supervisor.d.ts +0 -21
  1207. package/dist/supervisor/omnitron-supervisor.d.ts.map +0 -1
  1208. package/dist/supervisor/omnitron-supervisor.js +0 -43
  1209. package/dist/supervisor/omnitron-supervisor.js.map +0 -1
  1210. package/dist/webapp/static-server.d.ts +0 -24
  1211. package/dist/webapp/static-server.d.ts.map +0 -1
  1212. package/dist/webapp/static-server.js +0 -152
  1213. package/dist/webapp/static-server.js.map +0 -1
  1214. package/webapp/dist/assets/Chip-BySxTjyN.js +0 -1
  1215. package/webapp/dist/assets/DialogContentText-YQWiAVrU.js +0 -1
  1216. package/webapp/dist/assets/DialogTitle-CDkgUIwp.js +0 -1
  1217. package/webapp/dist/assets/FormControlLabel-Bu3CCsBM.js +0 -1
  1218. package/webapp/dist/assets/Grid-BFY8hAzL.js +0 -1
  1219. package/webapp/dist/assets/InputAdornment-_Dayk-3y.js +0 -3
  1220. package/webapp/dist/assets/Skeleton-BGj6Sw8m.js +0 -37
  1221. package/webapp/dist/assets/Switch-CA6682vv.js +0 -1
  1222. package/webapp/dist/assets/TableContainer-4epq10KJ.js +0 -1
  1223. package/webapp/dist/assets/TextField-C73KHMVN.js +0 -1
  1224. package/webapp/dist/assets/alerts-JEQALS7Y.js +0 -1
  1225. package/webapp/dist/assets/apps-DMPysEKB.js +0 -1
  1226. package/webapp/dist/assets/client-DlGPter5.js +0 -110
  1227. package/webapp/dist/assets/constants-DqFQJ27U.js +0 -1
  1228. package/webapp/dist/assets/containers-CvkC7do8.js +0 -1
  1229. package/webapp/dist/assets/dashboard-C0Fre9pC.js +0 -1
  1230. package/webapp/dist/assets/dashboard-builder-DAghqdpE.js +0 -1
  1231. package/webapp/dist/assets/deployments-Cg1lI1uT.js +0 -1
  1232. package/webapp/dist/assets/detail-CyFtEson.js +0 -1
  1233. package/webapp/dist/assets/detail-DqoSGKT1.js +0 -8
  1234. package/webapp/dist/assets/formatters-J3Xqn6BQ.js +0 -1
  1235. package/webapp/dist/assets/icons-ynIWYfBP.js +0 -3
  1236. package/webapp/dist/assets/index-CHVXOsTS.css +0 -1
  1237. package/webapp/dist/assets/index-vKrDmEA_.js +0 -259
  1238. package/webapp/dist/assets/isMuiElement-BRL3YuCT.js +0 -1
  1239. package/webapp/dist/assets/logs-IovIij1C.js +0 -11
  1240. package/webapp/dist/assets/metrics-Dufwtwjq.js +0 -1
  1241. package/webapp/dist/assets/nodes-Brvu8W3j.js +0 -7
  1242. package/webapp/dist/assets/pipelines-t40X4MuC.js +0 -4
  1243. package/webapp/dist/assets/prop-types-CvVtHxp4.js +0 -1
  1244. package/webapp/dist/assets/react-apexcharts.esm-CLO6gala.js +0 -954
  1245. package/webapp/dist/assets/settings-DTAIST3v.js +0 -1
  1246. package/webapp/dist/assets/sign-in-DQEAkJum.js +0 -1
  1247. package/webapp/dist/assets/stacks-BjM1f45N.js +0 -1
  1248. package/webapp/dist/assets/system-info-BQSbSEK-.js +0 -1
  1249. package/webapp/dist/assets/topology-6N8-Az_Z.js +0 -12
  1250. package/webapp/dist/assets/topology-CHpVij2M.css +0 -1
  1251. package/webapp/dist/assets/traces-DVXyb-4m.js +0 -1
  1252. package/webapp/dist/assets/use-stack-context-CbE8fxBA.js +0 -1
  1253. /package/webapp/dist/assets/{fs-uP8eiZAR.js → fs-Ccl3sAcp.js} +0 -0
@@ -43,36 +43,211 @@ import { FileWatcher } from '../orchestrator/file-watcher.js';
43
43
  import { InfrastructureService } from '../infrastructure/infrastructure.service.js';
44
44
  import { DockerHealthIndicator } from '../monitoring/docker-health.indicator.js';
45
45
  import { AppHealthIndicator } from '../monitoring/app-health.indicator.js';
46
+ import { EventLoopWatch } from '../monitoring/event-loop-watch.js';
47
+ import { EventLoopStallIndicator } from '../monitoring/event-loop-stall.indicator.js';
46
48
  import { registerDaemonJobs } from './daemon-scheduler.js';
47
49
  import { METRICS_SERVICE_TOKEN as TITAN_METRICS_TOKEN } from '@omnitron-dev/titan-metrics';
48
- import { ORCHESTRATOR_TOKEN, LOG_MANAGER_TOKEN, STATE_STORE_TOKEN, OMNITRON_DB_TOKEN, AUTH_SERVICE_TOKEN, LOG_COLLECTOR_TOKEN, FLEET_SERVICE_TOKEN, ALERT_SERVICE_TOKEN, HEALTH_CHECK_SERVICE_TOKEN, DISCOVERY_SERVICE_TOKEN, KUBERNETES_SERVICE_TOKEN, PIPELINE_SERVICE_TOKEN, TRACE_COLLECTOR_TOKEN, BACKUP_SERVICE_TOKEN, SECRETS_SERVICE_TOKEN, TELEMETRY_RELAY_TOKEN, DEPLOY_SERVICE_TOKEN, INFRASTRUCTURE_GATE_TOKEN, PROJECT_SERVICE_TOKEN, SLAVE_STORAGE_TOKEN, } from '../shared/tokens.js';
50
+ import { ORCHESTRATOR_TOKEN, LOG_MANAGER_TOKEN, STATE_STORE_TOKEN, OMNITRON_DB_TOKEN, AUTH_SERVICE_TOKEN, LOG_COLLECTOR_TOKEN, FLEET_SERVICE_TOKEN, ALERT_SERVICE_TOKEN, HEALTH_CHECK_SERVICE_TOKEN, DISCOVERY_SERVICE_TOKEN, KUBERNETES_SERVICE_TOKEN, PIPELINE_SERVICE_TOKEN, TRACE_COLLECTOR_TOKEN, BACKUP_SERVICE_TOKEN, SECRETS_SERVICE_TOKEN, AUDIT_SERVICE_TOKEN, DAEMON_STATE_STORE_TOKEN, TELEMETRY_RELAY_TOKEN, DEPLOY_SERVICE_TOKEN, INFRASTRUCTURE_GATE_TOKEN, PROJECT_SERVICE_TOKEN, SLAVE_STORAGE_TOKEN, } from '../shared/tokens.js';
49
51
  import { APP_EVENTS, INFRA_EVENTS, DAEMON_EVENTS, PROJECT_EVENTS, STACK_EVENTS, NODE_EVENTS, } from '../shared/events.js';
50
52
  import { JWT_SERVICE_TOKEN } from '@omnitron-dev/titan-auth';
51
- import { AuthenticationManager } from '@omnitron-dev/titan/netron/auth';
53
+ import { AuthenticationManager, AuthorizationManager, } from '@omnitron-dev/titan/netron/auth';
52
54
  import { createAuthContextWrapper } from '../services/auth-context.js';
55
+ import { ROLES } from '../shared/roles.js';
56
+ import { expandPath } from '../shared/paths.js';
57
+ /**
58
+ * Deadline for a request on the daemon's unix socket.
59
+ *
60
+ * Same value as `CLI_REQUEST_TIMEOUT` in `daemon-client.ts`: one number for
61
+ * one socket, stated by both ends rather than inherited from a default meant
62
+ * for wire requests.
63
+ */
64
+ const DAEMON_SOCKET_REQUEST_TIMEOUT = 60_000;
65
+ /**
66
+ * Where the daemon listens. The default is loopback; anything the operator
67
+ * writes is passed through literally, `0.0.0.0` included.
68
+ *
69
+ * Exported so the two call sites below and the tests agree on one
70
+ * definition. They previously each carried their own copy of
71
+ * `host !== '0.0.0.0' ? host : '127.0.0.1'`, which is how the two of them
72
+ * plus the config default managed to disagree about what the default was.
73
+ */
74
+ export function resolveBindHost(configured) {
75
+ return configured ?? '127.0.0.1';
76
+ }
77
+ /**
78
+ * Where a daemon binds its fleet TCP transport.
79
+ *
80
+ * A master follows `daemon.host`, whose default is loopback — the safe answer
81
+ * for the machine an operator is sitting at.
82
+ *
83
+ * A slave cannot use that default, because being reachable is the whole of
84
+ * what a slave is for: the master dials it to check its health and to pull
85
+ * from it. Bound to loopback it is a daemon nobody can see, and the console
86
+ * shows it as offline for ever with no way to tell it from a machine that is
87
+ * genuinely down.
88
+ *
89
+ * It was exactly that, and by a longer route than a wrong default. The
90
+ * generated slave config carried `daemon: { host: '0.0.0.0', … }` — and
91
+ * `IEcosystemConfig`, the schema of the file it was written into, has no
92
+ * `daemon` key; the daemon boots from `~/.omnitron/config.json`, which until
93
+ * now could not carry one either. So the setting was written, ignored, and
94
+ * every provisioned slave would have bound 127.0.0.1.
95
+ *
96
+ * An explicit `host` still wins, for an operator who wants one interface.
97
+ */
98
+ export function fleetBindHostFor(dc) {
99
+ if (dc.role === 'slave')
100
+ return dc.host ?? '0.0.0.0';
101
+ return resolveBindHost(dc.host);
102
+ }
103
+ /**
104
+ * The bind address a daemon should END UP with, from what was actually
105
+ * configured.
106
+ *
107
+ * `fleetBindHostFor` above takes the daemon's RUNNING config, and by the time
108
+ * anything holds one, `host` has been filled in from `DEFAULT_DAEMON_CONFIG`
109
+ * — so `?? '0.0.0.0'` can never fire and every slave binds loopback. The
110
+ * information it needs is whether an operator SET a host, and only the saved
111
+ * file knows that: absent means "no opinion", `127.0.0.1` means "loopback,
112
+ * deliberately".
113
+ *
114
+ * Measured 2026-09-14 on a provisioned slave, after the rule was written and
115
+ * before this existed:
116
+ *
117
+ * LISTEN 127.0.0.1:9700 ← the fleet port no master can dial
118
+ * LISTEN 127.0.0.1:9801
119
+ * LISTEN 127.0.0.1:9802
120
+ *
121
+ * The rule was right, the value it was given was already decided. This is the
122
+ * same rule applied where the answer is still open.
123
+ */
124
+ export function effectiveBindHost(saved) {
125
+ if (saved?.host)
126
+ return saved.host;
127
+ if (saved?.role === 'slave')
128
+ return '0.0.0.0';
129
+ return DEFAULT_DAEMON_CONFIG.host;
130
+ }
131
+ /**
132
+ * Where a daemon binds the surfaces that exist to serve a console: the Netron
133
+ * HTTP RPC and the WebSocket.
134
+ *
135
+ * A master follows `daemon.host`, like everything else. A slave does not,
136
+ * because on a slave that knob is published for a different reason: a fleet
137
+ * node has to bind its TCP transport somewhere the master can reach, and a
138
+ * generated slave config therefore says `0.0.0.0`. The same value then
139
+ * published two more surfaces — an authentication endpoint and a WebSocket
140
+ * one that has no rate limit available to it — and a slave serves no console,
141
+ * so nothing dials either. They were cost without benefit, on a machine whose
142
+ * whole point is being somewhere else.
143
+ */
144
+ export function consoleBindHostFor(dc) {
145
+ return dc.role === 'slave' ? '127.0.0.1' : resolveBindHost(dc.host);
146
+ }
147
+ /** Addresses that reach this host and nowhere else. */
148
+ export function isLoopbackHost(host) {
149
+ if (!host)
150
+ return true; // absent means the loopback default above
151
+ const h = host.trim().toLowerCase();
152
+ return h === '127.0.0.1' || h === 'localhost' || h === '::1' || h === '[::1]' || h.startsWith('127.');
153
+ }
154
+ /**
155
+ * Is the daemon still accepting the password its own migration seeded?
156
+ *
157
+ * Resolves the auth service lazily and answers `false` on any failure — a
158
+ * daemon that cannot reach its database has a louder problem than this, and
159
+ * a warning that fails the boot would be a worse trade than the risk it
160
+ * describes.
161
+ */
162
+ async function isSeededAdminPasswordInUse(app, logger) {
163
+ try {
164
+ const auth = await app.container.resolveAsync(AUTH_SERVICE_TOKEN);
165
+ return await auth.isUsingSeededAdminPassword();
166
+ }
167
+ catch (err) {
168
+ logger.warn({ err: err.message }, 'Could not check whether the seeded admin password is still in use');
169
+ return false;
170
+ }
171
+ }
172
+ /**
173
+ * The address other fleet nodes should dial to reach this one.
174
+ *
175
+ * `peer.address` is not decoration: `leader-election.ts` calls `requestVote`
176
+ * and `leaderHeartbeat` against it. A node bound to `0.0.0.0` therefore may
177
+ * not advertise `0.0.0.0` (not routable) — and must not advertise
178
+ * `127.0.0.1` either, which is what it did: every other node in the fleet
179
+ * resolves that to itself, so votes and heartbeats meant for the leader go
180
+ * to the sender's own loopback. A wrong answer of the right shape.
181
+ *
182
+ * When the bind address says nothing about reachability, take the first
183
+ * non-internal IPv4 the host has. Loopback remains the honest answer only
184
+ * when there is genuinely nothing else — a single-host deployment, where
185
+ * no other node can reach this one regardless.
186
+ */
187
+ export function advertisedAddress(bindHost) {
188
+ if (bindHost && bindHost !== '0.0.0.0' && bindHost !== '::')
189
+ return bindHost;
190
+ for (const addrs of Object.values(os.networkInterfaces())) {
191
+ for (const a of addrs ?? []) {
192
+ if (a.family === 'IPv4' && !a.internal)
193
+ return a.address;
194
+ }
195
+ }
196
+ return '127.0.0.1';
197
+ }
53
198
  export class OmnitronDaemon {
54
199
  app = null;
55
200
  pidManager = null;
201
+ /** Port this daemon serves `/netron/invoke` on; advertised to fleet peers. */
202
+ rpcHttpPort = null;
56
203
  fileWatcher = null;
57
204
  infraService = null;
205
+ eventLoopWatch = null;
58
206
  eventBroadcaster = null;
59
207
  syncService = null;
60
208
  nodeManagerService = null;
209
+ slaveConnector = null;
210
+ meshHandle = null;
61
211
  nodeManagerRpcService = null;
212
+ /**
213
+ * The live record of release builds this daemon is running.
214
+ *
215
+ * Held on the daemon rather than in the container because it is the state
216
+ * of work in progress: a build that a restart kills leaves a release
217
+ * directory with no manifest, and that — not this — is the durable trace.
218
+ */
219
+ releaseService = null;
62
220
  systemWorkerManager = null;
221
+ /** Everything the health-monitor worker needs to be spawned again. */
222
+ healthWorkerSpawn = null;
223
+ healthWorkerRestartTimer = null;
224
+ healthWorkerRestartAttempts = 0;
225
+ /** The startup budget the worker is given, so a failure can name it. */
226
+ healthWorkerStartupTimeout = 0;
227
+ /** In-daemon check timer, armed only while the worker is down. */
228
+ fallbackNodeCheckTimer = null;
229
+ /** Set by the shutdown task so nothing schedules work on the way out. */
230
+ isShuttingDown = false;
63
231
  leaderElection = null;
64
232
  configSyncService = null;
233
+ metricsServer = null;
234
+ metricsBridge = null;
65
235
  dc = DEFAULT_DAEMON_CONFIG;
66
236
  /** Daemon config accessor for external use (e.g., DaemonRpcService) */
67
237
  get daemonConfig() { return this.dc; }
68
238
  async start(config, options, daemonConfig) {
69
239
  this.dc = daemonConfig ?? DEFAULT_DAEMON_CONFIG;
70
- const pidFile = this.dc.pidFile.replace('~', process.env['HOME'] ?? '');
240
+ const pidFile = expandPath(this.dc.pidFile);
71
241
  this.pidManager = new PidManager(pidFile);
72
242
  if (this.pidManager.isRunning()) {
73
243
  const existingPid = this.pidManager.getPid();
74
244
  throw new Error(`Omnitron daemon already running (PID: ${existingPid})`);
75
245
  }
246
+ // Stale file from a previous crash is normal — clean it up so the
247
+ // atomic `O_EXCL` write below succeeds. P1-L: doing this AFTER
248
+ // isRunning() returned false means we only delete known-dead
249
+ // markers; live daemons get the conflict error above.
250
+ this.pidManager.remove();
76
251
  this.pidManager.write();
77
252
  // 1. Create Titan Application (the daemon itself)
78
253
  const DaemonModule = createDaemonModule(config, this.dc);
@@ -80,6 +255,25 @@ export class OmnitronDaemon {
80
255
  name: 'omnitron',
81
256
  version: CLI_VERSION,
82
257
  });
258
+ // P1-M — install our own SIGHUP handler BEFORE Application's
259
+ // default shutdown handlers attach, so `kill -HUP <daemon>`
260
+ // triggers a config reload (the Unix convention for long-running
261
+ // daemons) instead of terminating the supervisor. We don't bind
262
+ // this through the Application's signal manager because that
263
+ // path is wired to `runShutdownTasks()`; we install at the
264
+ // process level and the listener runs first.
265
+ process.on('SIGHUP', () => {
266
+ this.reloadConfigOnHup().catch((err) => {
267
+ // The message already carries which steps applied — printing only
268
+ // `.message` was fine for the load failure and misleading for the
269
+ // apply failure, which is the one that leaves work half-done.
270
+ console.warn(`[omnitron] SIGHUP config reload failed: ${err.message}`);
271
+ const cause = err.cause;
272
+ if (cause) {
273
+ console.warn(`[omnitron] caused by: ${cause.stack ?? String(cause)}`);
274
+ }
275
+ });
276
+ });
83
277
  // 2. Register transports (Unix, TCP, HTTP, WebSocket)
84
278
  await this.registerTransports(this.dc);
85
279
  // 3. Resolve and expose RPC services via Netron
@@ -88,6 +282,12 @@ export class OmnitronDaemon {
88
282
  await this.app.start();
89
283
  const loggerModule = await this.app.container.resolveAsync(LOGGER_SERVICE_TOKEN);
90
284
  const logger = loggerModule.logger;
285
+ // As soon as there is somewhere to say it, so a stall in the boot's own
286
+ // steps — infrastructure, migrations — is seen too. A node whose loop
287
+ // stands still answers nothing, its heartbeat included, and until this
288
+ // nothing on the node could say so. See `monitoring/event-loop-watch.ts`.
289
+ this.eventLoopWatch = new EventLoopWatch(logger);
290
+ this.eventLoopWatch.start();
91
291
  logger.info({
92
292
  socketPath: this.dc.socketPath,
93
293
  port: this.dc.port,
@@ -111,9 +311,33 @@ export class OmnitronDaemon {
111
311
  {
112
312
  const orchestrator = await this.app.container.resolveAsync(ORCHESTRATOR_TOKEN);
113
313
  if (this.app.netron) {
114
- const socketPath = this.dc.socketPath.replace('~', process.env['HOME'] ?? '');
314
+ const socketPath = expandPath(this.dc.socketPath);
115
315
  orchestrator.setDaemonNetron(this.app.netron, socketPath);
116
316
  }
317
+ // 8.6. Wire metrics bridge + start /metrics HTTP endpoint.
318
+ // Bridge subscribes to orchestrator events (restart/crash/escalation),
319
+ // janitor sweeps, and esbuild reset/build hooks. The HTTP server
320
+ // refreshes per-app gauges on each scrape via the bridge before
321
+ // formatting Prometheus text.
322
+ try {
323
+ const { MetricsBridge, MetricsServer } = await import('../observability/index.js');
324
+ const metricsService = await this.app.container.resolveAsync(TITAN_METRICS_TOKEN);
325
+ const bridge = new MetricsBridge(metricsService, logger);
326
+ orchestrator.setMetricsBridge(bridge);
327
+ this.metricsBridge = bridge;
328
+ const metricsPort = (this.dc.httpPort ?? 9800) + 3;
329
+ const metricsServer = new MetricsServer({
330
+ port: metricsPort,
331
+ metrics: metricsService,
332
+ bridge,
333
+ logger,
334
+ });
335
+ await metricsServer.start();
336
+ this.metricsServer = metricsServer;
337
+ }
338
+ catch (err) {
339
+ logger.warn({ error: err.message }, 'metrics endpoint failed to start — /metrics will be unavailable');
340
+ }
117
341
  }
118
342
  // 8.8. Start webapp (nginx container serving Console UI) if enabled
119
343
  {
@@ -122,7 +346,24 @@ export class OmnitronDaemon {
122
346
  if (savedConfig?.webapp) {
123
347
  try {
124
348
  const { WebappService } = await import('../webapp/webapp.service.js');
125
- const webapp = new WebappService(logger, process.cwd(), (this.dc.httpPort ?? 9800) + 1, this.dc.httpPort ?? 9800);
349
+ // Publishing the console while the seeded password is still in
350
+ // place hands the whole control plane — start, stop, restart, logs,
351
+ // config — to anyone who can reach the port and guess a word that
352
+ // is written in the migration. Migration 001 says the password
353
+ // "MUST be changed on first login"; nothing enforced it, and a
354
+ // comment is not a control.
355
+ //
356
+ // So the console falls back to loopback rather than refusing to
357
+ // start: the operator needs it to change the password, and a
358
+ // control plane that will not come up is its own outage. The log
359
+ // line says what to do and that this is not the configured bind.
360
+ let consoleHost = this.dc.consoleBindHost ?? '127.0.0.1';
361
+ if (!isLoopbackHost(consoleHost) && (await isSeededAdminPasswordInUse(this.app, logger))) {
362
+ logger.error({ configured: consoleHost, bound: '127.0.0.1' }, 'REFUSING to publish the Console while the seeded admin password is unchanged — ' +
363
+ 'binding loopback instead. Sign in at http://127.0.0.1 and change it, then restart.');
364
+ consoleHost = '127.0.0.1';
365
+ }
366
+ const webapp = new WebappService(logger, process.cwd(), (this.dc.httpPort ?? 9800) + 1, this.dc.httpPort ?? 9800, consoleHost);
126
367
  await webapp.start();
127
368
  }
128
369
  catch (err) {
@@ -130,14 +371,34 @@ export class OmnitronDaemon {
130
371
  }
131
372
  }
132
373
  }
374
+ // 8.9. Fleet monitoring. Started BEFORE managed apps and not awaited.
375
+ //
376
+ // It used to be step 11.5, after `startApps`, `startScheduledTasks` and
377
+ // `startBackgroundServices`. Fleet monitoring is about MACHINES; it has
378
+ // nothing to do with whether this daemon's managed applications start, and
379
+ // gating it behind them means one application that fails its startup
380
+ // leaves the whole fleet unwatched for as long as the supervisor keeps
381
+ // retrying it. Measured on this host: ten minutes after a daemon start,
382
+ // with two applications still cycling through failed starts, not a single
383
+ // node had been checked and nothing had said so.
384
+ //
385
+ // Not awaited, for the same reason in the other direction: spawning the
386
+ // worker has a 30-second startup timeout, and the applications should not
387
+ // wait behind it. The function handles and logs its own failures, and arms
388
+ // the in-daemon fallback checks when the worker cannot run.
389
+ void this.startHealthMonitorWorker(logger).catch((err) => {
390
+ // It handles its own failures; this is the last resort, because an
391
+ // unhandled rejection is a crash of the daemon rather than a lost
392
+ // subsystem.
393
+ logger.error({ error: err.message }, 'Health monitor startup threw');
394
+ });
133
395
  // 9. Start managed apps (waits for infra gate if apps have `requires`)
134
396
  await this.startApps(config, logger);
135
397
  // 10. Start background tasks via titan-scheduler (replaces bare setInterval)
136
398
  await this.startScheduledTasks(config, logger);
137
399
  // 11. Start remaining background services (telemetry, traces, leader election)
138
400
  await this.startBackgroundServices(config, logger);
139
- // 11.5. Start health monitor system worker (master only)
140
- await this.startHealthMonitorWorker(logger);
401
+ // (Fleet monitoring is started at 8.5, above — before managed apps.)
141
402
  // 12. Wire event broadcasting (orchestrator events → WebSocket push)
142
403
  await this.wireEventBroadcasting(logger);
143
404
  // 13. Register shutdown tasks — Application handles SIGTERM/SIGINT
@@ -160,18 +421,87 @@ export class OmnitronDaemon {
160
421
  this.app.netron.registerTransportServer('unix', {
161
422
  name: 'daemon-local',
162
423
  options: {
163
- path: dc.socketPath.replace('~', process.env['HOME'] ?? ''),
424
+ path: expandPath(dc.socketPath),
164
425
  force: true,
165
426
  mode: 0o600, // Owner-only access for security
427
+ // Every other party on this socket states a deadline and this one
428
+ // did not, so `netron.ts:797` — `serverRequestTimeout ??
429
+ // transportOpts.requestTimeout` — found both undefined and built its
430
+ // accepted-connection peers with netron's 5 s default, which is a
431
+ // deadline for a wire request rather than for management-plane work.
432
+ // The reading of the server option was added deliberately («a
433
+ // `requestTimeout` set on the server was silently ignored»); the
434
+ // value was never supplied. Same number as `CLI_REQUEST_TIMEOUT`,
435
+ // which the daemon's own client uses over this same socket.
436
+ requestTimeout: DAEMON_SOCKET_REQUEST_TIMEOUT,
437
+ // The same bridge the HTTP and WebSocket servers below have: it puts
438
+ // the context the guard admitted a call with — here the implicit
439
+ // `omnitron-local` set just after this — where the services read
440
+ // who is calling. This server had none, so for every CLI call the
441
+ // guard saw `omnitron-local` and the services saw nobody: the audit
442
+ // trail recorded `system` for 171 of the master's 178 rows, and an
443
+ // operator's deployment read exactly like the daemon's own boot
444
+ // autostart. No proxy stands in front of a unix socket, so the
445
+ // wrapper trusts no forwarded address — its default.
446
+ invocationWrapper: createAuthContextWrapper(),
166
447
  },
167
448
  });
168
- // TCP — remote fleet communication (cross-server)
449
+ // Local CLI trust. The Unix socket above is created mode 0600, so any
450
+ // peer that can open it is provably the same OS user that owns this
451
+ // daemon — the OS has already authenticated them. Grant those
452
+ // connections an implicit admin context so the local CLI satisfies the
453
+ // `@Public({ auth: { roles } })` guards on the daemon RPC services
454
+ // without a redundant JWT handshake (the CLI talks over this socket
455
+ // only). Remote transports — TCP (fleet), HTTP/WS (webapp) — are NOT
456
+ // registered here and keep requiring a validated token, so this widens
457
+ // nothing beyond same-user local access that the socket mode already
458
+ // grants.
459
+ this.app.netron.setTransportAuthContext('unix', {
460
+ userId: 'omnitron-local',
461
+ roles: [ROLES.ADMIN],
462
+ permissions: [],
463
+ metadata: { source: 'local-unix-socket' },
464
+ });
465
+ // TCP — remote fleet communication (cross-server). The default is
466
+ // 127.0.0.1 so a single-host deployment never exposes daemon control
467
+ // to the LAN; multi-host fleet operators opt in via `daemon.host`.
468
+ //
469
+ // The default belongs in DEFAULT_DAEMON_CONFIG, not here. It used to be
470
+ // enforced here instead, as `dc.host !== '0.0.0.0' ? dc.host :
471
+ // '127.0.0.1'` over a config default of `'0.0.0.0'` — absence encoded as
472
+ // a value from the domain of valid answers. `0.0.0.0` is not a marker for
473
+ // "unset", it is a bind address with a meaning, and it was the exact
474
+ // value the comment above it told fleet operators to set. Following the
475
+ // documentation produced loopback and no fleet, silently. It also
476
+ // contradicted every neighbouring knob: `consoleBindHost: '0.0.0.0'`
477
+ // publishes, and so does an app's transport host.
478
+ //
479
+ // (The original hazard — pre-fix this defaulted to all-interfaces while
480
+ // the AuthorizationManager below was missing, giving any LAN host an
481
+ // unauthenticated `stopAll({force:true})` — is answered by the default
482
+ // being loopback and by that manager now being wired.)
483
+ const tcpHost = fleetBindHostFor(dc);
484
+ if (!isLoopbackHost(tcpHost)) {
485
+ // Said, not enforced, and the difference is deliberate: silently
486
+ // rebinding a fleet transport to loopback would take a cluster down,
487
+ // and a node that cannot be reached is an outage rather than a
488
+ // hardening. The console above IS rebound, because its whole purpose
489
+ // is a password prompt and loopback still serves it.
490
+ void (async () => {
491
+ const loggerModule = await this.app.container.resolveAsync(LOGGER_SERVICE_TOKEN);
492
+ const log = loggerModule.logger;
493
+ if (await isSeededAdminPasswordInUse(this.app, log)) {
494
+ log.error({ host: tcpHost, port: dc.port }, 'The daemon fleet transport is published and the seeded admin password is unchanged — ' +
495
+ 'anyone who can reach this port can sign in and stop every app. Change it now.');
496
+ }
497
+ })().catch(() => undefined);
498
+ }
169
499
  this.app.netron.registerTransport('tcp', () => new TcpTransport());
170
500
  this.app.netron.registerTransportServer('tcp', {
171
501
  name: 'daemon-fleet',
172
502
  options: {
173
503
  port: dc.port,
174
- host: dc.host,
504
+ host: tcpHost,
175
505
  },
176
506
  });
177
507
  // Auth — JWT validation + session check + role extraction
@@ -220,18 +550,79 @@ export class OmnitronDaemon {
220
550
  },
221
551
  tokenCache: { enabled: true, ttl: 10_000 },
222
552
  });
223
- this.app.netron.configureAuth(authManager);
224
- const authContextWrapper = createAuthContextWrapper();
553
+ // Wire the AuthorizationManager so the `@Public({ auth: { roles } })`
554
+ // decorators on DaemonRpcService are actually enforced on the wire.
555
+ // Without an authzManager, `remote-peer.ts:enforceMethodAccess`
556
+ // early-returns and every method is reachable by anyone who can
557
+ // mint a valid JWT, regardless of `roles`. The WS origin policy
558
+ // (`allowedOrigins: true` → same-host) closes the cross-origin
559
+ // upgrade attack against cookie-mode auth.
560
+ const authzManager = new AuthorizationManager(authLogger);
561
+ this.app.netron.configureAuth(authManager, authzManager, {
562
+ allowedOrigins: true,
563
+ });
564
+ // `trustProxy` is shared with the rate limiter deliberately: both answer
565
+ // the same question — is there a proxy in front whose forwarded headers
566
+ // we may believe? Two separate switches would eventually disagree.
567
+ const authContextWrapper = createAuthContextWrapper({
568
+ trustProxy: dc.httpRateLimit?.trustProxy === true,
569
+ });
570
+ // HTTP / WS bind also defaults to 127.0.0.1. The webapp talks to
571
+ // these over the same loopback; nginx fronts the public surface.
572
+ // Operators who proxy in from another host opt in via `daemon.host`
573
+ // (same knob that controls TCP, with the same literal meaning).
574
+ //
575
+ // Except on a slave, where that knob has to be published for a different
576
+ // reason and publishing these with it is pure cost. A fleet node must
577
+ // bind its TCP transport somewhere the master can reach — that is what
578
+ // `daemon.host: '0.0.0.0'` in a generated slave config is for — and the
579
+ // same value then published the Netron HTTP RPC and the WebSocket
580
+ // alongside it. A slave serves no console: the operator uses the
581
+ // master's. So the two surfaces it gained are an authentication endpoint
582
+ // it does not need and a WebSocket one that, as the note below records,
583
+ // has no rate limit available to it.
584
+ //
585
+ // Rebound rather than warned about, which is the opposite of the choice
586
+ // made for the TCP transport twenty lines up — and for the opposite
587
+ // reason. Rebinding the fleet port would take a cluster down, because
588
+ // being reachable is that port's whole job. Rebinding these costs a slave
589
+ // nothing, because nothing dials them.
590
+ const localHost = consoleBindHostFor(dc);
225
591
  // HTTP — Netron RPC API (internal port, nginx proxies from public port)
592
+ // The public `httpPort` (9800) is nginx in front of the console; the
593
+ // daemon's own Netron HTTP listener sits one above it. Remembered here
594
+ // because the fleet registration below must advertise the port peers can
595
+ // actually POST `/netron/invoke` to — not the TCP port, and not the
596
+ // console's.
226
597
  const internalHttpPort = (dc.httpPort ?? 9800) + 1;
598
+ this.rpcHttpPort = internalHttpPort;
227
599
  this.app.netron.registerTransport('http', () => new HttpTransport());
600
+ const rl = dc.httpRateLimit ?? {};
228
601
  this.app.netron.registerTransportServer('http', {
229
602
  name: 'daemon-http',
230
603
  options: {
231
604
  port: internalHttpPort,
232
- host: '0.0.0.0',
605
+ host: localHost,
233
606
  cors: true,
234
607
  invocationWrapper: authContextWrapper,
608
+ // Volume cap on the HTTP RPC surface, and only that one. Password
609
+ // guessing is bounded by the per-account lockout in AuthService.
610
+ //
611
+ // The WebSocket surface below is NOT covered: netron's WebSocket
612
+ // transport has no rate-limit option — `maxPayload` is the only
613
+ // bound it accepts — and the console talks over WebSocket. This
614
+ // comment used to say the limit bounded "everything else, including
615
+ // attempts to flood the daemon into unresponsiveness", which
616
+ // describes a protection that covers one of the two transports an
617
+ // attacker can reach.
618
+ rateLimit: {
619
+ enabled: rl.enabled ?? true,
620
+ windowMs: rl.windowMs ?? 60_000,
621
+ maxRequests: rl.maxRequests ?? 3_000,
622
+ globalMaxRequests: rl.globalMaxRequests ?? 6_000,
623
+ trustProxy: rl.trustProxy ?? false,
624
+ ...(rl.whitelist ? { whitelist: rl.whitelist } : {}),
625
+ },
235
626
  },
236
627
  });
237
628
  // WebSocket — real-time event push to webapp clients
@@ -241,8 +632,18 @@ export class OmnitronDaemon {
241
632
  name: 'daemon-ws',
242
633
  options: {
243
634
  port: wsPort,
244
- host: '0.0.0.0',
635
+ host: localHost,
636
+ // Applied per invocation by `remote-peer.ts`, the dispatcher every
637
+ // socket transport shares — so WS calls get the same request context
638
+ // HTTP calls do. Until recently only the HTTP server read this
639
+ // option and this line did nothing.
245
640
  invocationWrapper: authContextWrapper,
641
+ // The only bound this transport accepts. Left unset it inherits
642
+ // `ws`'s 100 MB default, which is a lot of memory to hand a single
643
+ // frame on a control-plane socket that carries RPC arguments and
644
+ // event pushes. 8 MB is well above anything the console sends and
645
+ // well below anything worth allocating for a stranger.
646
+ maxPayload: dc.wsMaxPayload ?? 8 * 1024 * 1024,
246
647
  },
247
648
  });
248
649
  }
@@ -260,11 +661,20 @@ export class OmnitronDaemon {
260
661
  const logManager = await container.resolveAsync(LOG_MANAGER_TOKEN);
261
662
  const logCollector = await container.resolveAsync(LOG_COLLECTOR_TOKEN);
262
663
  const loggerModule = await container.resolveAsync(LOGGER_SERVICE_TOKEN);
664
+ // Table retention. `logging.maxSize` and `maxFiles` bound the rotated
665
+ // files on disk and say nothing about the `logs` table, which had no
666
+ // bound at all — 13 GB on the development host. Off unless configured,
667
+ // and the number comes from the config rather than a default chosen
668
+ // inside the collector, because a wrong default there deletes history.
669
+ logCollector.setRetentionDays(config.logging?.databaseRetentionDays ?? 0, loggerModule.logger.child({ component: 'log-retention' }));
263
670
  const rpcService = new DaemonRpcService(orchestrator, titanHealth, logManager, config, this);
264
671
  await this.app.netron.peer.exposeService(rpcService);
265
672
  // Logs RPC service (works on both master and slave — uses PG or SQLite)
266
673
  const logsRpcService = new LogsRpcService(logCollector);
267
674
  await this.app.netron.peer.exposeService(logsRpcService);
675
+ // `getLogs` answers an app this daemon does not run from the stored rows —
676
+ // where a node's synced logs are.
677
+ rpcService.setLogStore(logCollector);
268
678
  // Metrics RPC service — provided by TitanMetricsModule
269
679
  const metricsService = await container.resolveAsync(TITAN_METRICS_TOKEN);
270
680
  const { MetricsRpcService: MetricsRpc } = await import('@omnitron-dev/titan-metrics');
@@ -275,10 +685,29 @@ export class OmnitronDaemon {
275
685
  const healthCheckService = await container.resolveAsync(HEALTH_CHECK_SERVICE_TOKEN);
276
686
  const healthCheckRpcService = new HealthCheckRpc(healthCheckService);
277
687
  await this.app.netron.peer.exposeService(healthCheckRpcService);
688
+ // The audit trail. Master-only — its table lives in the omnitron
689
+ // database — and every service that records into it takes it as an
690
+ // optional dependency, so a slave serves the same methods and records
691
+ // nothing rather than refusing them.
692
+ let audit;
693
+ if (!isSlave) {
694
+ try {
695
+ audit = await container.resolveAsync(AUDIT_SERVICE_TOKEN);
696
+ // Revealing an app's secrets is recorded (`revealEnv`).
697
+ rpcService.setAudit(audit);
698
+ const { AuditRpcService } = await import('../services/audit.rpc-service.js');
699
+ await this.app.netron.peer.exposeService(new AuditRpcService(audit));
700
+ }
701
+ catch (err) {
702
+ // A daemon whose database did not come up still runs; it simply
703
+ // records nothing, and says so once rather than at every action.
704
+ loggerModule.logger.warn({ error: err.message }, 'No audit trail on this daemon — actions will not be recorded');
705
+ }
706
+ }
278
707
  // Secrets RPC service (file-based, no DB dependency)
279
708
  const { SecretsRpcService: SecretsRpc } = await import('../services/secrets.rpc-service.js');
280
709
  const secretsService = await container.resolveAsync(SECRETS_SERVICE_TOKEN);
281
- const secretsRpcService = new SecretsRpc(secretsService);
710
+ const secretsRpcService = new SecretsRpc(secretsService, audit);
282
711
  await this.app.netron.peer.exposeService(secretsRpcService);
283
712
  // Kubernetes RPC service (no DB dependency)
284
713
  const { KubernetesRpcService: K8sRpc } = await import('../services/kubernetes.rpc-service.js');
@@ -290,14 +719,60 @@ export class OmnitronDaemon {
290
719
  const backupService = await container.resolveAsync(BACKUP_SERVICE_TOKEN);
291
720
  const backupRpcService = new BackupRpc(backupService);
292
721
  await this.app.netron.peer.exposeService(backupRpcService);
722
+ // Re-arm any persisted backup schedules so they survive daemon restarts.
723
+ await backupService.restoreSchedules().catch((err) => loggerModule.logger.warn({ err: err.message }, 'Failed to restore backup schedules'));
293
724
  // Infrastructure RPC service (reports infra state — slave has no infra)
294
725
  const { InfrastructureRpcService: InfraRpc } = await import('../services/infrastructure.rpc-service.js');
295
- const infraRpcService = new InfraRpc(() => this.infraService);
726
+ const infraRpcService = new InfraRpc(() => this.infraService,
727
+ // Only a node hosts a stack's infrastructure on demand. A master
728
+ // provisions its own from its own config at boot, and handing it a
729
+ // second one over RPC would give one machine two reconcilers over one
730
+ // set of containers.
731
+ isSlave
732
+ ? (infraConfig, declaredServices, presetRegistry, serviceOverrides) => {
733
+ const service = new InfrastructureService(loggerModule.logger.child({ component: 'infra' }), infraConfig, declaredServices, presetRegistry, serviceOverrides,
734
+ // A node keeps its own state in SQLite. Provisioning a
735
+ // control-plane Postgres for it gives the host a database
736
+ // nobody queries, on default credentials.
737
+ false);
738
+ this.infraService = service;
739
+ return service;
740
+ }
741
+ : undefined,
742
+ // The node's own vault. Generated service credentials are written
743
+ // here and read back on every later provision, which is what makes a
744
+ // generated password usable: a data directory keeps the one it was
745
+ // initialised with.
746
+ // Resolved per call, not captured here: a resolution that fails during
747
+ // startup would otherwise leave credential generation off for the life
748
+ // of this daemon, and the absence reads as "this node needs none".
749
+ isSlave
750
+ ? () => {
751
+ try {
752
+ return container.resolve(SECRETS_SERVICE_TOKEN);
753
+ }
754
+ catch {
755
+ return undefined;
756
+ }
757
+ }
758
+ : undefined,
759
+ // The logger, because the one thing this service says on its own — that
760
+ // a deployment is running on a default credential — is said at error
761
+ // level and reaches nobody without it.
762
+ loggerModule.logger.child({ component: 'infra' }));
296
763
  await this.app.netron.peer.exposeService(infraRpcService);
297
764
  // Project + Stack management RPC service
298
765
  const projectService = await container.resolveAsync(PROJECT_SERVICE_TOKEN);
299
- const projectRpcService = new ProjectRpcService(projectService);
766
+ const projectRpcService = new ProjectRpcService(projectService, audit);
300
767
  await this.app.netron.peer.exposeService(projectRpcService);
768
+ // Releases. Master-only, like the builds themselves: a release is built
769
+ // from clean clones of BOTH repositories, and only a master has both.
770
+ if (!isSlave) {
771
+ const { ReleaseService } = await import('../services/release.service.js');
772
+ const { ReleaseRpcService } = await import('../services/release.rpc-service.js');
773
+ this.releaseService = new ReleaseService(loggerModule.logger.child({ component: 'release' }));
774
+ await this.app.netron.peer.exposeService(new ReleaseRpcService(this.releaseService, audit, projectService));
775
+ }
301
776
  // Sync service (slave→master data replication)
302
777
  const syncNodeId = `${os.hostname()}-${dc.port}`;
303
778
  let syncDb;
@@ -309,6 +784,53 @@ export class OmnitronDaemon {
309
784
  syncDb = await container.resolveAsync(OMNITRON_DB_TOKEN);
310
785
  }
311
786
  this.syncService = new SyncService(syncDb, loggerModule.logger.child({ component: 'sync' }), syncNodeId, dc.role, dc.sync);
787
+ // Remote metrics into the store the console actually reads.
788
+ //
789
+ // `ingestMetric` wrote them to `metrics_raw`, and nothing in this
790
+ // repository reads that table — the metrics page queries titan-metrics'
791
+ // own storage. So once the pipeline carried anything, it would have
792
+ // carried it somewhere invisible. Recording through the same service the
793
+ // master uses for its own readings makes a remote node a LABEL on the
794
+ // existing series instead of a second store of the same concept.
795
+ //
796
+ // `node` is a distinct argument in the sink's signature rather than a
797
+ // key a caller may forget to put in `labels`: a sample recorded without
798
+ // it merges with the master's own, and the chart then shows two machines
799
+ // summed under one name.
800
+ if (!isSlave) {
801
+ const syncMetrics = await container.resolveAsync(TITAN_METRICS_TOKEN);
802
+ this.syncService.setMetricsSink((sample) => {
803
+ try {
804
+ syncMetrics.recordTyped('gauge', sample.name, { ...sample.labels, app: sample.app, node: sample.node }, sample.value);
805
+ }
806
+ catch (err) {
807
+ loggerModule.logger.warn({ node: sample.node, name: sample.name, error: err.message }, 'Failed to record a replicated metric');
808
+ }
809
+ });
810
+ }
811
+ // The producer the replication pipeline never had.
812
+ //
813
+ // `SyncService` buffers, batches, retries with backoff, evicts when over
814
+ // budget, and the master keeps a dedup ledger for what it has taken —
815
+ // all of it built, and `buffer`/`bufferBatch` had ZERO callers. A slave
816
+ // collected its logs into its own SQLite and replicated none of them,
817
+ // and nothing said so: an empty buffer drains successfully every cycle.
818
+ //
819
+ // Wired only for a slave. `bufferBatch` is a no-op for any other role,
820
+ // so this is belt and braces rather than the guard itself — but a master
821
+ // has no master to ship to, and doing the work would be pure cost.
822
+ if (isSlave) {
823
+ const syncService = this.syncService;
824
+ logCollector.setSyncSink((entries) => {
825
+ void syncService
826
+ .bufferBatch(entries.map((e) => ({ category: 'logs', payload: e })))
827
+ .catch(() => {
828
+ // `bufferBatch` logs its own failure. Swallowed here because the
829
+ // lines are already in the local table: losing the replica copy
830
+ // must not cost the flush that succeeded.
831
+ });
832
+ });
833
+ }
312
834
  const syncRpcService = new SyncRpcService(this.syncService);
313
835
  await this.app.netron.peer.exposeService(syncRpcService);
314
836
  // System info service (real-time OS/hardware metrics)
@@ -317,19 +839,120 @@ export class OmnitronDaemon {
317
839
  await this.app.netron.peer.exposeService(systemInfoRpcService);
318
840
  // Event Broadcaster RPC service (WebSocket subscriptions)
319
841
  this.eventBroadcaster = new EventBroadcasterService(loggerModule.logger);
320
- const eventBroadcasterRpcService = new EventBroadcasterRpcService(this.eventBroadcaster);
842
+ const eventBroadcasterRpcService = new EventBroadcasterRpcService(this.eventBroadcaster, this.app.netron);
321
843
  await this.app.netron.peer.exposeService(eventBroadcasterRpcService);
322
844
  // Node manager — file-based, no PG (master only — manages infrastructure nodes)
323
845
  // Health checks delegated to dedicated system worker (started later after infra is ready)
324
846
  if (!isSlave) {
325
847
  const { NodeManagerService } = await import('../services/node-manager.service.js');
326
848
  const { NodeManagerRpcService } = await import('../services/node-manager.rpc-service.js');
327
- const secretsService = await container.resolveAsync(SECRETS_SERVICE_TOKEN);
328
- const nodeManager = new NodeManagerService(loggerModule.logger.child({ component: 'nodes' }), secretsService);
849
+ const { DaemonStateStore: DaemonStateStoreCls } = await import('./daemon-state-store.service.js');
850
+ const daemonStateStore = await container.resolveAsync(DAEMON_STATE_STORE_TOKEN);
851
+ const nodeManager = new NodeManagerService(loggerModule.logger.child({ component: 'nodes' }), daemonStateStore, secretsService);
329
852
  this.nodeManagerService = nodeManager;
330
- const nodeManagerRpcService = new NodeManagerRpcService(nodeManager);
853
+ const nodeManagerRpcService = new NodeManagerRpcService(nodeManager, audit);
854
+ // The deployer reaches nodes through the same SSH implementation as the
855
+ // health checks — the only one that can present a stored password or a
856
+ // key passphrase.
857
+ {
858
+ const { RemoteDeployer } = await import('../services/remote-deployer.service.js');
859
+ const { ExecutionService } = await import('../execution/execution.service.js');
860
+ const deployLogger = loggerModule.logger.child({ component: 'deploy' });
861
+ const remoteDeployer = new RemoteDeployer(deployLogger, new ExecutionService(deployLogger));
862
+ nodeManagerRpcService.setRemoteDeployer(remoteDeployer);
863
+ // Upgrading a node from the console: the same build the CLI makes,
864
+ // made here, then the same two calls. Without this the console could
865
+ // see every node and update none of them.
866
+ const { NodeUpgradeService } = await import('../services/node-upgrade.service.js');
867
+ nodeManagerRpcService.setUpgradeService(new NodeUpgradeService(loggerModule.logger.child({ component: 'node-upgrade' }), {
868
+ getNode: (id) => nodeManager.getNode(id),
869
+ // The same projection the CLI builds for `fleet upgrade`, made
870
+ // here so the console's plan and the terminal's dry run decide
871
+ // from identical inputs. Two places computing «what is this
872
+ // node running» is how they come to disagree.
873
+ listCandidates: () => nodeManager.listNodes().map((n) => ({
874
+ nodeId: n.id,
875
+ name: n.name,
876
+ currentVersion: n.status?.omnitronVersion ?? null,
877
+ isLocal: n.isLocal,
878
+ // SSH, not the daemon port: an upgrade travels over SSH,
879
+ // and a node whose daemon is unreachable is often exactly
880
+ // the one worth upgrading.
881
+ sshReachable: n.status?.sshConnected ?? null,
882
+ // The machine, for telling two registry names for one box
883
+ // apart before both get the bundle.
884
+ address: `${n.host}:${n.sshPort}`,
885
+ })),
886
+ }, (id) => nodeManager.nodeToDeployTarget(id), () => remoteDeployer, audit));
887
+ }
331
888
  this.nodeManagerRpcService = nodeManagerRpcService;
889
+ // The local node's own answers, for the three readers that ask every
890
+ // node about itself. A daemon has no mesh connection to itself, so
891
+ // `askNode` needs a local source for each.
892
+ //
893
+ // On the INSTANCE, after it is assigned, and not on `this.…?.` from
894
+ // wherever each dependency happens to be constructed: `setSyncService`
895
+ // was first written next to `new SyncService(...)`, ninety lines above
896
+ // this assignment and inside the same function, where `?.` swallowed it
897
+ // silently and the local node would have reported "not wired" forever.
898
+ // An optional call is only safe where the object is known to exist.
899
+ nodeManagerRpcService.setSyncService(this.syncService);
332
900
  await this.app.netron.peer.exposeService(nodeManagerRpcService);
901
+ // The mesh: every registered node, connected and replicating.
902
+ //
903
+ // `SlaveConnector` maintains the connections and pulls each node's
904
+ // write-ahead buffer on connect and on every heartbeat, and all of it
905
+ // worked — but the only caller of `addSlave` was a remote or cluster
906
+ // stack starting. So the master connected to the nodes something had
907
+ // been deployed onto, and to no others. A node added through the
908
+ // console, provisioned, and collecting its own metrics and logs was
909
+ // never dialled, and buffered locally forever: measured at 47,407
910
+ // undelivered entries over eleven hours on the first such node, which
911
+ // reported healthy the whole time.
912
+ //
913
+ // Built here rather than inside ProjectService because reaching a node
914
+ // needs what only this side has — the registry's SSH credentials, for
915
+ // the token the node's daemon demands and for the tunnel a firewalled
916
+ // daemon port needs. ProjectService adopts the same instance: one per
917
+ // master, or two connectors pull the same buffer and race to ack it.
918
+ try {
919
+ const { SlaveConnector } = await import('../cluster/slave-connector.js');
920
+ const { createMeshDialer } = await import('../cluster/mesh-link.js');
921
+ const { startMesh } = await import('../cluster/mesh.js');
922
+ const { ExecutionService: Exec } = await import('../execution/execution.service.js');
923
+ const meshLogger = loggerModule.logger.child({ component: 'mesh' });
924
+ const connector = new SlaveConnector(meshLogger, this.syncService ?? null, {
925
+ dial: createMeshDialer({
926
+ logger: meshLogger,
927
+ execution: new Exec(meshLogger),
928
+ subject: `mesh:${os.hostname()}`,
929
+ sshTargetFor: async ({ host }) => {
930
+ const node = nodeManager.listNodes().find((n) => n.host === host && !n.isLocal);
931
+ return node ? nodeManager.nodeToSshTarget(node) : null;
932
+ },
933
+ }),
934
+ });
935
+ this.slaveConnector = connector;
936
+ projectService.setSlaveConnector(connector);
937
+ // So the console can answer "is this node replicating", which is a
938
+ // different question from "can this master reach it".
939
+ nodeManagerRpcService.setSlaveConnector(connector);
940
+ // A stack names a host; the registry holds the credential for it.
941
+ // Wired here because this is the side that has both — ProjectService
942
+ // reads config files, the node registry keeps its secrets in the
943
+ // daemon's vault.
944
+ projectService.setNodeCredentialResolver(async (host) => {
945
+ const node = nodeManager.listNodes().find((n) => n.host === host && !n.isLocal);
946
+ return node ? nodeManager.nodeToSshTarget(node) : null;
947
+ });
948
+ this.meshHandle = startMesh({ registry: nodeManager, connector, logger: meshLogger });
949
+ }
950
+ catch (err) {
951
+ // A master with no mesh still supervises its own apps, so this does
952
+ // not stop the daemon — but nothing else reports it, and a silent
953
+ // failure here looks exactly like a fleet that has nothing to say.
954
+ loggerModule.logger.error({ error: err.message }, 'Mesh not started — registered nodes will buffer their data locally and replicate nothing');
955
+ }
333
956
  }
334
957
  // =====================================================================
335
958
  // Master-only RPC services (require PostgreSQL)
@@ -348,6 +971,9 @@ export class OmnitronDaemon {
348
971
  const { TelemetryRpcService: TelemetryRpc } = await import('../services/telemetry.rpc-service.js');
349
972
  const telemetryRelay = await container.resolveAsync(TELEMETRY_RELAY_TOKEN);
350
973
  const telemetryRpcService = new TelemetryRpc(telemetryRelay);
974
+ // The console asks every node for its relay, and a daemon has no mesh
975
+ // connection to itself, so the local node's answer comes from here.
976
+ this.nodeManagerRpcService?.setTelemetryRelay(telemetryRelay);
351
977
  await this.app.netron.peer.exposeService(telemetryRpcService);
352
978
  // Fleet management RPC service
353
979
  const { FleetRpcService: FleetRpc } = await import('../services/fleet.rpc-service.js');
@@ -387,6 +1013,8 @@ export class OmnitronDaemon {
387
1013
  ...(dc.cluster.heartbeatInterval != null && { heartbeatInterval: dc.cluster.heartbeatInterval }),
388
1014
  });
389
1015
  const clusterRpcService = new ClusterRpc(this.leaderElection);
1016
+ // The local node's own election state, for the cross-node reader.
1017
+ this.nodeManagerRpcService?.setLeaderElection(this.leaderElection);
390
1018
  await this.app.netron.peer.exposeService(clusterRpcService);
391
1019
  }
392
1020
  }
@@ -422,6 +1050,10 @@ export class OmnitronDaemon {
422
1050
  if (!this.app)
423
1051
  return;
424
1052
  const stateStore = await this.app.container.resolveAsync(STATE_STORE_TOKEN);
1053
+ // First-touch init reads from SQLite (and migrates the legacy
1054
+ // JSON snapshot in-place if one exists). Subsequent reads via
1055
+ // `load()` are synchronous from the in-memory cache.
1056
+ await stateStore.init();
425
1057
  const previousState = stateStore.load();
426
1058
  if (previousState) {
427
1059
  const runningApps = previousState.apps.filter((a) => a.status === 'online' || a.status === 'starting');
@@ -481,8 +1113,37 @@ export class OmnitronDaemon {
481
1113
  logger.warn('Continuing without internal database — some features will be unavailable');
482
1114
  }
483
1115
  }
484
- // Step 2: Provision app infrastructure (postgres, redis, minio) if declared
485
- // Slave daemons never provision Docker infrastructure
1116
+ // Step 2: Provision app infrastructure (postgres, redis, minio) if declared.
1117
+ //
1118
+ // Two ownership models share this slot historically:
1119
+ //
1120
+ // (a) Top-level apps declared directly in the daemon config — the
1121
+ // daemon owns their infra here, marks the gate ready, and apps
1122
+ // in the top-level `apps: []` proceed.
1123
+ //
1124
+ // (b) Project/stack mode — infra is owned by `StackInfrastructureManager`
1125
+ // per `(project, stack)` and provisioned later by
1126
+ // `projectService.startStack`. Running both passes can collide
1127
+ // on the SAME service name with different prefixes (D14): the
1128
+ // daemon path uses prefix `omnitron-` while the stack path
1129
+ // sets `${project}-${stack}-`. With a non-trivial top-level
1130
+ // `infrastructure.services` map the daemon would create a
1131
+ // parallel set of containers that nobody owns and the stack's
1132
+ // provision later races them on ports.
1133
+ //
1134
+ // Resolution: if any project has enabled stacks, defer infra entirely
1135
+ // to stack provision and mark the gate ready immediately. Top-level
1136
+ // `infrastructure.services` is informational in this mode (used by
1137
+ // stack auto-detection upstream); the daemon does not provision it
1138
+ // again here.
1139
+ const { ProjectRegistry } = await import('../project/registry.js');
1140
+ const projects = ProjectRegistry.open().list();
1141
+ const projectModeActive = projects.some((p) => Array.isArray(p.enabledStacks) && p.enabledStacks.length > 0);
1142
+ if (projectModeActive) {
1143
+ logger.info({ projects: projects.length }, 'project mode active — daemon-level infrastructure deferred to stack provision');
1144
+ gate.markReady({ services: {}, ready: true });
1145
+ return;
1146
+ }
486
1147
  if (config.infrastructure && !isSlave) {
487
1148
  try {
488
1149
  this.infraService = new InfrastructureService(logger, config.infrastructure);
@@ -502,7 +1163,21 @@ export class OmnitronDaemon {
502
1163
  error: reason,
503
1164
  });
504
1165
  logger.error({ error: reason }, 'Failed to provision app infrastructure');
505
- logger.warn('Apps with infrastructure requirements may fail to start');
1166
+ // Fail-fast unless explicitly opted out via `--no-infra` (handled
1167
+ // earlier by skipping this branch). Re-throw so daemon startup
1168
+ // surfaces the actual cause to the operator instead of leaving the
1169
+ // daemon "running" with a half-broken state.
1170
+ const allowDegraded = config.allowDegradedInfra === true;
1171
+ if (!allowDegraded) {
1172
+ const throwable = new Error(`Daemon infrastructure provisioning failed: ${reason}\n` +
1173
+ `Stack apps will not start until this is resolved.\n` +
1174
+ `Fix the underlying issue (Docker socket, image pull, port, …) and run 'omnitron up' again.\n` +
1175
+ `To deliberately run with no managed infra (external services), pass --no-infra to 'omnitron up'.`);
1176
+ throwable.code = 'DAEMON_INFRA_PROVISIONING_FAILED';
1177
+ throwable.cause = err;
1178
+ throw throwable;
1179
+ }
1180
+ logger.warn('allowDegradedInfra=true — apps with infrastructure requirements may fail to start');
506
1181
  }
507
1182
  }
508
1183
  else {
@@ -521,27 +1196,70 @@ export class OmnitronDaemon {
521
1196
  */
522
1197
  async reconcileOrphanContainers(logger, projects, projectService) {
523
1198
  const { listManagedContainers, stopContainer, removeContainer } = await import('../infrastructure/container-runtime.js');
1199
+ const { decideOrphans } = await import('../infrastructure/orphan-containers.js');
524
1200
  const managed = await listManagedContainers();
525
1201
  if (managed.length === 0)
526
1202
  return;
527
1203
  // Build set of expected container name prefixes from registered projects/stacks
528
1204
  const expectedPrefixes = new Set();
529
1205
  const internalNames = new Set(['omnitron-pg', 'omnitron-nginx']);
1206
+ // Load each project's config FIRST. `listStacks` reads the loaded-config
1207
+ // cache and returns [] for a project whose config has not been read —
1208
+ // and this runs before the per-project load loop further down, whose own
1209
+ // comment says loading "populates internal cache used by listStacks".
1210
+ //
1211
+ // So on every boot where projects come from the registry rather than CWD
1212
+ // auto-detection — the normal case — `expectedPrefixes` was EMPTY and
1213
+ // every managed container looked like an orphan. Measured from this
1214
+ // daemon's own log, 2026-09-12 12:46, ten RUNNING containers removed in
1215
+ // fourteen seconds: postgres, redis, bitcoin, monero (daemon and wallet),
1216
+ // nominatim, tor, tiles, minio, gateway — each logged as "not part of any
1217
+ // registered stack" while being part of the registered stack.
1218
+ //
1219
+ // Named volumes survived (`docker rm -f`, no `-v`), so this cost a cold
1220
+ // restart of every service rather than the chain data. That is luck, not
1221
+ // design.
1222
+ let expectationsComplete = true;
530
1223
  for (const project of projects) {
1224
+ try {
1225
+ await projectService.loadProjectConfig(project.name);
1226
+ }
1227
+ catch (err) {
1228
+ expectationsComplete = false;
1229
+ logger.warn({ project: project.name, error: err.message }, 'Could not read this project’s stacks — skipping orphan reconciliation rather than guessing');
1230
+ continue;
1231
+ }
531
1232
  const stacks = projectService.listStacks(project.name);
1233
+ if (stacks.length === 0)
1234
+ expectationsComplete = false;
532
1235
  for (const stack of stacks) {
533
1236
  expectedPrefixes.add(`${project.name}-${stack.name}-`);
534
1237
  }
535
1238
  }
1239
+ // Whether anything may be removed is one decision with several ways of
1240
+ // being wrong, and every way it has been wrong so far was a mistake about
1241
+ // what the inputs MEANT. It lives in `decideOrphans`, where it can be
1242
+ // pinned in a test rather than discovered on a host.
1243
+ const decision = decideOrphans({
1244
+ isSlave: this.dc?.role === 'slave',
1245
+ managed: managed.map((c) => ({
1246
+ name: c.name,
1247
+ status: c.status,
1248
+ project: c.project,
1249
+ stack: c.stack,
1250
+ })),
1251
+ projects: projects.map((p) => p.name),
1252
+ expectedPrefixes: [...expectedPrefixes],
1253
+ expectationsComplete,
1254
+ internalNames: [...internalNames],
1255
+ });
1256
+ if (decision.action === 'skip') {
1257
+ logger.warn({ managed: managed.length, expectedPrefixes: expectedPrefixes.size, because: decision.because }, 'Orphan reconciliation skipped');
1258
+ return;
1259
+ }
536
1260
  let orphanCount = 0;
537
- for (const container of managed) {
538
- // Always keep internal omnitron containers
539
- if (internalNames.has(container.name))
540
- continue;
541
- // Keep containers that match a known project/stack prefix
542
- if ([...expectedPrefixes].some((prefix) => container.name.startsWith(prefix)))
543
- continue;
544
- // This container is an orphan — stop and remove it
1261
+ for (const name of decision.containers) {
1262
+ const container = managed.find((c) => c.name === name);
545
1263
  logger.info({ container: container.name, status: container.status }, 'Removing orphan container (not part of any registered stack)');
546
1264
  try {
547
1265
  if (container.status === 'running')
@@ -557,49 +1275,23 @@ export class OmnitronDaemon {
557
1275
  logger.info({ removed: orphanCount }, 'Orphan container cleanup complete');
558
1276
  }
559
1277
  }
1278
+ /**
1279
+ * Apply the internal-database schema.
1280
+ *
1281
+ * Delegates to the shared runner (`database/migration-runner.ts`) so this
1282
+ * path, the infrastructure service and the `migrate` CLI all execute the
1283
+ * same registry through the same engine. A failure here is logged as an
1284
+ * ERROR — the daemon keeps running, but every feature backed by
1285
+ * omnitron-pg (alerts, sessions, logs, metrics, deployments) is degraded
1286
+ * and the operator has to see it.
1287
+ */
560
1288
  async runOmnitronMigrations(logger) {
1289
+ const { runOmnitronMigrations } = await import('../database/migration-runner.js');
561
1290
  try {
562
- const { Kysely, PostgresDialect, Migrator } = await import('kysely');
563
- const pg = await import('pg');
564
- const m001 = await import('../database/migrations/001_initial_schema.js');
565
- const m002 = await import('../database/migrations/002_metrics_raw.js');
566
- const m003 = await import('../database/migrations/003_pipelines_traces.js');
567
- const m004 = await import('../database/migrations/004_sync_buffer.js');
568
- const m005 = await import('../database/migrations/005_node_health_checks.js');
569
- const pool = new pg.default.Pool({
570
- host: 'localhost', port: 5480,
571
- database: 'omnitron', user: 'omnitron', password: 'omnitron',
572
- });
573
- const db = new Kysely({ dialect: new PostgresDialect({ pool }) });
574
- try {
575
- const migrator = new Migrator({
576
- db,
577
- provider: {
578
- async getMigrations() {
579
- return {
580
- '001_initial_schema': { up: m001.up, down: m001.down },
581
- '002_metrics_raw': { up: m002.up, down: m002.down },
582
- '003_pipelines_traces': { up: m003.up, down: m003.down },
583
- '004_sync_buffer': { up: m004.up, down: m004.down },
584
- '005_node_health_checks': { up: m005.up, down: m005.down },
585
- };
586
- },
587
- },
588
- });
589
- const { results, error } = await migrator.migrateToLatest();
590
- const applied = results?.filter((r) => r.status === 'Success') ?? [];
591
- if (applied.length > 0) {
592
- logger.info({ migrations: applied.map((r) => r.migrationName) }, `Applied ${applied.length} migration(s)`);
593
- }
594
- if (error)
595
- logger.warn({ error: String(error) }, 'Migration warning');
596
- }
597
- finally {
598
- await db.destroy();
599
- }
1291
+ await runOmnitronMigrations(logger);
600
1292
  }
601
1293
  catch (err) {
602
- logger.warn({ error: err.message }, 'Could not run migrations — will retry on next start');
1294
+ logger.error({ error: err.message }, 'Omnitron database migrations FAILED — alerts, sessions, logs and metrics will not work until this is resolved');
603
1295
  }
604
1296
  }
605
1297
  // ============================================================================
@@ -615,6 +1307,15 @@ export class OmnitronDaemon {
615
1307
  titanHealth.registerIndicator(new DockerHealthIndicator(() => this.infraService));
616
1308
  // Managed apps health indicator
617
1309
  titanHealth.registerIndicator(new AppHealthIndicator(orchestrator));
1310
+ // How long the loop has stood still, and when — the number the console
1311
+ // reads for a node that went quiet.
1312
+ if (this.eventLoopWatch)
1313
+ titanHealth.registerIndicator(new EventLoopStallIndicator(this.eventLoopWatch));
1314
+ // The console asks every node for its own indicators, and a daemon has
1315
+ // no mesh connection to itself — so the local node's answer comes from
1316
+ // here. Registered beside the indicators rather than in the wiring
1317
+ // above, because this is the object that has just been given them.
1318
+ this.nodeManagerRpcService?.setTitanHealth(titanHealth);
618
1319
  }
619
1320
  catch {
620
1321
  // Health module may not be available — non-critical
@@ -674,6 +1375,31 @@ export class OmnitronDaemon {
674
1375
  logger.info('No projects registered. Use `omnitron project add <name> <path>` or run from a directory with omnitron.config.ts');
675
1376
  return;
676
1377
  }
1378
+ // When there are enabled stacks to resume, wait for Docker first. Under
1379
+ // OS-service supervision the daemon boots at login, seconds before
1380
+ // OrbStack/dockerd finishes starting — on 2026-07-11 the one-shot resume
1381
+ // fired into "Docker is not available" and the platform stayed appless
1382
+ // for days. Bounded wait; on timeout we proceed and the enabled-stacks
1383
+ // reconciler keeps retrying with backoff.
1384
+ const hasEnabledStacks = allProjects.some((p) => (p.enabledStacks?.length ?? 0) > 0);
1385
+ if (hasEnabledStacks) {
1386
+ const { isDockerAvailable } = await import('../infrastructure/container-runtime.js');
1387
+ const deadline = Date.now() + 120_000;
1388
+ let waited = false;
1389
+ while (!(await isDockerAvailable())) {
1390
+ if (Date.now() > deadline) {
1391
+ logger.warn('Docker still unavailable after 120s — proceeding; reconciler will retry enabled stacks');
1392
+ break;
1393
+ }
1394
+ if (!waited) {
1395
+ logger.info('Docker not available yet (host still booting?) — waiting before stack resume');
1396
+ waited = true;
1397
+ }
1398
+ await new Promise((r) => setTimeout(r, 3_000));
1399
+ }
1400
+ if (waited)
1401
+ logger.info('Docker is available — continuing stack resume');
1402
+ }
677
1403
  // Reconcile orphan containers: stop/remove any omnitron.managed containers
678
1404
  // that don't belong to current registered projects/stacks (e.g. leftovers from
679
1405
  // a previous session or auto-restarted by Docker after a system reboot).
@@ -708,7 +1434,7 @@ export class OmnitronDaemon {
708
1434
  // Start each enabled stack
709
1435
  for (const stackName of enabledStacks) {
710
1436
  try {
711
- await projectService.startStack(project.name, stackName);
1437
+ await projectService.startStack(project.name, stackName, { source: 'boot' });
712
1438
  totalStacks++;
713
1439
  logger.info({ project: project.name, stack: stackName }, 'Stack started');
714
1440
  }
@@ -718,6 +1444,10 @@ export class OmnitronDaemon {
718
1444
  }
719
1445
  }
720
1446
  logger.info({ projects: allProjects.length, stacks: totalStacks }, 'All projects and stacks initialized');
1447
+ // Keep converging after boot: retry failed resumes (with backoff) and
1448
+ // re-start enabled stacks that later fall over. One-shot boot resume is
1449
+ // not enough — see the 2026-07-11 Docker-race incident.
1450
+ projectService.startEnabledStacksReconciler();
721
1451
  }
722
1452
  // ============================================================================
723
1453
  // Scheduled Tasks (Phase 2: titan-scheduler replaces bare setInterval)
@@ -734,7 +1464,8 @@ export class OmnitronDaemon {
734
1464
  const logManager = await container.resolveAsync(LOG_MANAGER_TOKEN);
735
1465
  // Master-only services (require PG)
736
1466
  const authService = !isSlave ? await container.resolveAsync(AUTH_SERVICE_TOKEN) : null;
737
- const alertService = !isSlave ? await container.resolveAsync(ALERT_SERVICE_TOKEN) : null;
1467
+ // Both roles: a node evaluates its own alarms on its SQLite (daemon.module.ts).
1468
+ const alertService = await container.resolveAsync(ALERT_SERVICE_TOKEN);
738
1469
  const fleetService = !isSlave ? await container.resolveAsync(FLEET_SERVICE_TOKEN) : null;
739
1470
  registerDaemonJobs(scheduler, {
740
1471
  logger,
@@ -745,6 +1476,10 @@ export class OmnitronDaemon {
745
1476
  fleetService,
746
1477
  logManager,
747
1478
  infraService: this.infraService,
1479
+ // Only a slave replicates. `bufferBatch` is itself a no-op for any
1480
+ // other role, but passing null keeps the job from registering at all
1481
+ // and makes the scheduler's own "skipped, because" line say so.
1482
+ syncService: isSlave ? this.syncService : null,
748
1483
  metricsInterval: config.monitoring.metrics.interval,
749
1484
  healthCheckInterval: config.monitoring.healthCheck.interval,
750
1485
  });
@@ -775,16 +1510,18 @@ export class OmnitronDaemon {
775
1510
  logger.warn({ error: err.message }, 'Telemetry relay failed to start');
776
1511
  }
777
1512
  }
778
- // Register self in fleet (master only — requires PG)
1513
+ // Register self in fleet (master only — requires PG). `registerSelf`, so
1514
+ // the service remembers which row is this daemon's: the fleet-heartbeat
1515
+ // job beats that row, and had only the literal 'self' before.
779
1516
  if (!isSlave) {
780
1517
  try {
781
1518
  const fleetService = await container.resolveAsync(FLEET_SERVICE_TOKEN);
782
- await fleetService.registerNode({
1519
+ await fleetService.registerSelf({
783
1520
  hostname: os.hostname(),
784
- address: this.dc.host === '0.0.0.0' ? '127.0.0.1' : this.dc.host,
1521
+ address: advertisedAddress(this.dc.host),
785
1522
  port: this.dc.port,
786
1523
  role: 'leader',
787
- metadata: { pid: process.pid, version: CLI_VERSION, httpPort: this.dc.httpPort },
1524
+ metadata: { pid: process.pid, version: CLI_VERSION, httpPort: this.dc.httpPort, rpcPort: this.rpcHttpPort },
788
1525
  });
789
1526
  }
790
1527
  catch {
@@ -829,77 +1566,167 @@ export class OmnitronDaemon {
829
1566
  // ============================================================================
830
1567
  // Health Monitor System Worker
831
1568
  // ============================================================================
1569
+ /**
1570
+ * Start the health-monitor system worker, and keep it started.
1571
+ *
1572
+ * Three things were wrong with the previous version, and they compounded
1573
+ * into one outage:
1574
+ *
1575
+ * - It returned SILENTLY when a precondition failed, so a daemon that
1576
+ * never checked a single node looked exactly like one that checked them
1577
+ * all. Each branch now says which precondition it was.
1578
+ * - Nothing watched the worker's process. When it died, the master kept
1579
+ * the proxy, and every fleet check — the console's Refresh, the Check
1580
+ * button, `omnitron node check` — answered
1581
+ * `TitanError: Service with id HealthMonitor@1.0.0 not found`. Observed
1582
+ * live: a fleet frozen at the worker's last report for two days, with
1583
+ * the console rendering it as current.
1584
+ * - The fallback timer existed only inside the catch for a FAILED SPAWN,
1585
+ * so a worker that started and later crashed had no fallback at all.
1586
+ *
1587
+ * The worker is now respawned with backoff, the proxy is dropped the moment
1588
+ * its process ends, and the daemon runs the checks itself in the meantime.
1589
+ */
832
1590
  async startHealthMonitorWorker(logger) {
833
- if (!this.app || !this.nodeManagerService || this.dc.role === 'slave')
1591
+ if (this.dc.role === 'slave')
1592
+ return; // By design: slaves do not manage a fleet.
1593
+ if (!this.app) {
1594
+ logger.warn({}, 'Health monitor not started — application is not available');
834
1595
  return;
1596
+ }
1597
+ if (!this.nodeManagerService) {
1598
+ logger.warn({}, 'Health monitor not started — node manager was not constructed');
1599
+ return;
1600
+ }
1601
+ const nodeManager = this.nodeManagerService;
835
1602
  try {
836
1603
  const titanPm = await import('@omnitron-dev/titan-pm');
837
1604
  const { SystemWorkerManager } = await import('../workers/system-worker-manager.js');
838
1605
  const { fileURLToPath } = await import('node:url');
839
1606
  const nodePath = await import('node:path');
1607
+ const { omnitronPgConnectionString } = await import('../database/connection.js');
840
1608
  // Resolve PM from DI
841
1609
  const pm = await this.app.container.resolveAsync(titanPm.PM_MANAGER_TOKEN);
842
1610
  const loggerModule = await this.app.container.resolveAsync(LOGGER_SERVICE_TOKEN);
843
- this.systemWorkerManager = new SystemWorkerManager(pm, loggerModule.logger.child({ component: 'system-workers' }));
844
- // Build health monitor config
845
- const hmDefaults = this.dc.healthMonitor ?? {};
846
- const healthConfig = {
847
- intervalMs: hmDefaults.intervalMs ?? 30_000,
848
- concurrency: hmDefaults.concurrency ?? 20,
849
- offlineTimeoutMs: hmDefaults.offlineTimeoutMs ?? 90_000,
850
- pingTimeout: hmDefaults.pingTimeout ?? 5_000,
851
- sshTimeout: hmDefaults.sshTimeout ?? 10_000,
852
- omnitronCheckTimeout: hmDefaults.omnitronCheckTimeout ?? 15_000,
853
- pingEnabled: hmDefaults.pingEnabled ?? true,
854
- retentionDays: hmDefaults.retentionDays ?? 7,
855
- dbUrl: 'postgresql://omnitron:omnitron@localhost:5480/omnitron',
856
- };
857
- // Get worker process path — .ts when running via tsx, .js when compiled
1611
+ const workerManager = new SystemWorkerManager(pm, loggerModule.logger.child({ component: 'system-workers' }));
1612
+ this.systemWorkerManager = workerManager;
1613
+ // Get worker process path. The naive heuristic of "tsx in execArgv → .ts"
1614
+ // is wrong: the daemon binary itself is loaded with `--import tsx/esm`
1615
+ // (so transpilation works for any .ts in the tree), but the *daemon
1616
+ // entry* may still be the compiled .js. We must check what physically
1617
+ // exists alongside the running daemon, not what's in execArgv.
858
1618
  const thisDir = nodePath.dirname(fileURLToPath(import.meta.url));
859
- const isTsx = process.execArgv.some((a) => a.includes('tsx'));
860
- const ext = isTsx ? '.ts' : '.js';
861
- const workerPath = nodePath.resolve(thisDir, '..', 'workers', `health-monitor-process${ext}`);
862
- // Convert nodes to check targets
863
- const nodesJson = JSON.stringify(await this.nodeManagerService.getNodeCheckTargets());
864
- const configJson = JSON.stringify(healthConfig);
1619
+ const workersDir = nodePath.resolve(thisDir, '..', 'workers');
1620
+ const tsPath = nodePath.join(workersDir, 'health-monitor-process.ts');
1621
+ const jsPath = nodePath.join(workersDir, 'health-monitor-process.js');
1622
+ const fsMod = await import('node:fs');
1623
+ const workerPath = fsMod.existsSync(jsPath)
1624
+ ? jsPath
1625
+ : fsMod.existsSync(tsPath)
1626
+ ? tsPath
1627
+ : jsPath; // fall through to .js — error will be clear if missing
865
1628
  const spawnOpts = {};
866
1629
  if (process.execArgv.some((a) => a.includes('tsx'))) {
867
1630
  spawnOpts.execArgv = ['--import', 'tsx/esm'];
868
1631
  }
869
- spawnOpts.startupTimeout = 30_000;
870
- const proxy = await this.systemWorkerManager.spawn('health-monitor', workerPath, { configJson, nodesJson }, spawnOpts);
871
- // Wire IPC: worker → master status cache updates via PM's public API
872
- const nodeManager = this.nodeManagerService;
873
- const eventBroadcaster = this.eventBroadcaster;
874
- this.systemWorkerManager.onMessage('health-monitor', (msg) => {
875
- if (msg?.type === 'health:status_batch' && Array.isArray(msg.summaries)) {
876
- nodeManager.updateStatusCacheFromWorker(msg.summaries);
877
- // Broadcast node status updates via WebSocket
878
- if (eventBroadcaster) {
879
- const online = msg.summaries.filter((s) => s.status === 'online').length;
880
- const degraded = msg.summaries.filter((s) => s.status === 'degraded').length;
881
- const offline = msg.summaries.filter((s) => s.status === 'offline').length;
882
- eventBroadcaster.broadcast(NODE_EVENTS.CHECK_COMPLETED, {
883
- nodeCount: msg.summaries.length,
884
- onlineCount: online,
885
- degradedCount: degraded,
886
- offlineCount: offline,
887
- });
1632
+ // What the worker has to do before it can report ready is create a
1633
+ // Titan application — a DI container, a module graph and a PG pool.
1634
+ // Managed applications get two to five minutes for that; this was
1635
+ // thirty seconds, and on a loaded host it expired on every attempt.
1636
+ // A deadline shorter than the work turns a healthy subsystem into one
1637
+ // that is started and killed for ever, which is what was observed:
1638
+ // fifty minutes of restart attempts, none of which could have finished.
1639
+ spawnOpts.startupTimeout =
1640
+ this.dc.healthMonitor?.startupTimeoutMs
1641
+ ?? DEFAULT_DAEMON_CONFIG.healthMonitor?.startupTimeoutMs
1642
+ ?? 120_000;
1643
+ this.healthWorkerStartupTimeout = spawnOpts.startupTimeout;
1644
+ /**
1645
+ * The worker's configuration.
1646
+ *
1647
+ * Read at every spawn, not once: the operator's check settings live in
1648
+ * the node manager and change at runtime. The fallbacks come from
1649
+ * `DEFAULT_DAEMON_CONFIG` rather than being written out again here —
1650
+ * the inline copies said 30 s and 7 days while the declared defaults
1651
+ * said 60 s and 90 days, so which numbers a deployment got depended on
1652
+ * whether its config file happened to mention `healthMonitor` at all.
1653
+ */
1654
+ const buildConfig = () => {
1655
+ const declared = DEFAULT_DAEMON_CONFIG.healthMonitor ?? {};
1656
+ const configured = this.dc.healthMonitor ?? {};
1657
+ const operator = nodeManager.getCheckConfig();
1658
+ return {
1659
+ intervalMs: configured.intervalMs ?? declared.intervalMs ?? 60_000,
1660
+ concurrency: operator.concurrency,
1661
+ offlineTimeoutMs: configured.offlineTimeoutMs ?? declared.offlineTimeoutMs ?? 90_000,
1662
+ pingTimeout: operator.pingTimeout,
1663
+ sshTimeout: operator.sshTimeout,
1664
+ omnitronCheckTimeout: operator.omnitronCheckTimeout,
1665
+ pingEnabled: operator.pingEnabled,
1666
+ retentionDays: configured.retentionDays ?? declared.retentionDays ?? 90,
1667
+ // Was a hard-coded DSN, password included, in this file — the sixth
1668
+ // copy of literals `database/connection.ts` exists to be the only
1669
+ // copy of. A daemon pointed elsewhere by OMNITRON_DATABASE_URL wrote
1670
+ // its check history to a database nothing else used.
1671
+ dbUrl: omnitronPgConnectionString(),
1672
+ };
1673
+ };
1674
+ this.healthWorkerSpawn = async () => {
1675
+ const healthConfig = buildConfig();
1676
+ const nodesJson = JSON.stringify(await nodeManager.getNodeCheckTargets());
1677
+ const proxy = await workerManager.spawn('health-monitor', workerPath, { configJson: JSON.stringify(healthConfig), nodesJson }, spawnOpts);
1678
+ // Wire IPC: worker → master status cache updates via PM's public API
1679
+ workerManager.onMessage('health-monitor', (msg) => {
1680
+ if (msg?.type === 'health:status_batch' && Array.isArray(msg.summaries)) {
1681
+ nodeManager.updateStatusCacheFromWorker(msg.summaries);
1682
+ this.broadcastNodeCheckCompleted(msg.summaries);
888
1683
  }
1684
+ });
1685
+ if (this.nodeManagerRpcService) {
1686
+ this.nodeManagerRpcService.setHealthWorkerProxy(proxy);
889
1687
  }
1688
+ this.healthWorkerRestartAttempts = 0;
1689
+ this.stopFallbackNodeChecks();
1690
+ logger.info({ intervalMs: healthConfig.intervalMs }, 'Health monitor system worker started');
1691
+ };
1692
+ // A worker that ends — crash or deliberate stop — must take its proxy
1693
+ // with it. `expected` is true only while the daemon is shutting down,
1694
+ // which is also when a respawn would be wrong.
1695
+ workerManager.onExit('health-monitor', (exit) => {
1696
+ this.nodeManagerRpcService?.setHealthWorkerProxy(null);
1697
+ if (exit.expected || this.isShuttingDown)
1698
+ return;
1699
+ this.startFallbackNodeChecks(logger);
1700
+ this.scheduleHealthMonitorRestart(logger);
1701
+ });
1702
+ // Operator changes to the check settings have to reach the process that
1703
+ // performs the checks. They reached a field on the node manager that
1704
+ // only the daemon's own fallback path read, so turning ping off in the
1705
+ // console turned nothing off.
1706
+ nodeManager.on('checkConfig:changed', () => {
1707
+ void callWorkerMethod(workerManager, 'updateConfig', JSON.stringify(buildConfig())).catch((err) => {
1708
+ logger.warn({ error: err.message }, 'Failed to push check config to health monitor');
1709
+ });
890
1710
  });
891
1711
  // Wire node CRUD events → sync to worker
892
1712
  const syncNodesToWorker = async () => {
1713
+ if (!workerManager.get('health-monitor'))
1714
+ return;
893
1715
  const targets = await nodeManager.getNodeCheckTargets();
894
- proxy.updateNodes(JSON.stringify(targets)).catch(() => { });
1716
+ await callWorkerMethod(workerManager, 'updateNodes', JSON.stringify(targets)).catch((err) => {
1717
+ logger.warn({ error: err.message }, 'Failed to sync node list to health monitor');
1718
+ });
895
1719
  };
896
1720
  nodeManager.on('node:added', syncNodesToWorker);
897
1721
  nodeManager.on('node:updated', syncNodesToWorker);
898
1722
  nodeManager.on('node:removed', syncNodesToWorker);
899
- // Wire RPC service to use worker proxy + PG repository for direct reads
1723
+ // A node changing state is the event the console subscribes to. Four of
1724
+ // the five NODE_EVENTS channels were declared and never emitted.
1725
+ nodeManager.on('node:health', (t) => {
1726
+ this.broadcastNodeTransition(t);
1727
+ });
1728
+ // Wire PG repository for direct history reads (bypasses worker)
900
1729
  if (this.nodeManagerRpcService) {
901
- this.nodeManagerRpcService.setHealthWorkerProxy(proxy);
902
- // Wire PG repository for direct history reads (bypasses worker)
903
1730
  try {
904
1731
  const db = await this.app.container.resolveAsync(OMNITRON_DB_TOKEN);
905
1732
  const { NodeHealthRepository } = await import('../services/node-health.repository.js');
@@ -909,20 +1736,102 @@ export class OmnitronDaemon {
909
1736
  logger.warn({}, 'Could not wire health repository — PG may not be ready');
910
1737
  }
911
1738
  }
912
- logger.info({ intervalMs: healthConfig.intervalMs }, 'Health monitor system worker started');
1739
+ // The console needs these to size its uptime bars against history that
1740
+ // actually exists.
1741
+ const bootConfig = buildConfig();
1742
+ nodeManager.setHistoryConfig({
1743
+ uptimeIntervalMs: this.dc.healthMonitor?.uptimeIntervalMs
1744
+ ?? DEFAULT_DAEMON_CONFIG.healthMonitor?.uptimeIntervalMs
1745
+ ?? 86_400_000,
1746
+ retentionDays: bootConfig.retentionDays,
1747
+ });
1748
+ await this.healthWorkerSpawn();
913
1749
  }
914
1750
  catch (err) {
915
1751
  logger.warn({ error: err.message }, 'Health monitor worker failed to start — falling back to direct checks');
916
- // Fallback: start legacy periodic checks if worker fails
917
- // This ensures node status still works even without the worker
918
- if (this.nodeManagerService) {
919
- const intervalMs = this.dc.healthMonitor?.intervalMs ?? 30_000;
920
- const timer = setInterval(() => void this.nodeManagerService?.checkAllNodes(), intervalMs);
921
- timer.unref();
922
- void this.nodeManagerService.checkAllNodes();
923
- }
1752
+ this.startFallbackNodeChecks(logger);
1753
+ if (this.healthWorkerSpawn)
1754
+ this.scheduleHealthMonitorRestart(logger);
924
1755
  }
925
1756
  }
1757
+ /**
1758
+ * Try the health-monitor worker again, backing off.
1759
+ *
1760
+ * Capped at a minute: the worker is cheap to start and the fleet view is
1761
+ * blind without it, but a worker that cannot start (a missing file, a bad
1762
+ * runtime) must not be respawned in a tight loop for the life of the daemon.
1763
+ */
1764
+ scheduleHealthMonitorRestart(logger) {
1765
+ if (this.healthWorkerRestartTimer || this.isShuttingDown || !this.healthWorkerSpawn)
1766
+ return;
1767
+ const attempt = ++this.healthWorkerRestartAttempts;
1768
+ const delay = Math.min(60_000, 2_000 * 2 ** Math.min(attempt - 1, 5));
1769
+ logger.warn({ attempt, delayMs: delay }, 'Health monitor worker is down — restarting');
1770
+ this.healthWorkerRestartTimer = setTimeout(() => {
1771
+ this.healthWorkerRestartTimer = null;
1772
+ if (this.isShuttingDown || !this.healthWorkerSpawn)
1773
+ return;
1774
+ this.healthWorkerSpawn().catch((err) => {
1775
+ logger.warn({ attempt, startupTimeoutMs: this.healthWorkerStartupTimeout, error: err.message }, 'Health monitor worker restart failed');
1776
+ this.scheduleHealthMonitorRestart(logger);
1777
+ });
1778
+ }, delay);
1779
+ this.healthWorkerRestartTimer.unref();
1780
+ }
1781
+ /**
1782
+ * Run node checks in the daemon while the worker is unavailable.
1783
+ *
1784
+ * Idempotent, and stopped as soon as the worker is back — two check loops
1785
+ * against the same fleet would double every remote host's load and write
1786
+ * two rows per interval.
1787
+ */
1788
+ startFallbackNodeChecks(logger) {
1789
+ if (this.fallbackNodeCheckTimer || !this.nodeManagerService)
1790
+ return;
1791
+ const declared = DEFAULT_DAEMON_CONFIG.healthMonitor ?? {};
1792
+ const intervalMs = this.dc.healthMonitor?.intervalMs ?? declared.intervalMs ?? 60_000;
1793
+ logger.warn({ intervalMs }, 'Checking nodes from the daemon until the health monitor returns');
1794
+ this.fallbackNodeCheckTimer = setInterval(() => {
1795
+ void this.nodeManagerService?.checkAllNodes();
1796
+ }, intervalMs);
1797
+ this.fallbackNodeCheckTimer.unref();
1798
+ void this.nodeManagerService.checkAllNodes();
1799
+ }
1800
+ stopFallbackNodeChecks() {
1801
+ if (!this.fallbackNodeCheckTimer)
1802
+ return;
1803
+ clearInterval(this.fallbackNodeCheckTimer);
1804
+ this.fallbackNodeCheckTimer = null;
1805
+ }
1806
+ /** Aggregate counts from a worker batch → one console event. */
1807
+ broadcastNodeCheckCompleted(summaries) {
1808
+ if (!this.eventBroadcaster)
1809
+ return;
1810
+ const count = (s) => summaries.filter((x) => x.status === s).length;
1811
+ this.eventBroadcaster.broadcast(NODE_EVENTS.CHECK_COMPLETED, {
1812
+ nodeCount: summaries.length,
1813
+ onlineCount: count('online'),
1814
+ degradedCount: count('degraded'),
1815
+ offlineCount: count('offline'),
1816
+ });
1817
+ }
1818
+ /** One node's health changing → the channel named after the new state. */
1819
+ broadcastNodeTransition(transition) {
1820
+ if (!this.eventBroadcaster)
1821
+ return;
1822
+ const payload = {
1823
+ nodeId: transition.nodeId,
1824
+ status: transition.status,
1825
+ ...(transition.previousStatus ? { previousStatus: transition.previousStatus } : {}),
1826
+ };
1827
+ this.eventBroadcaster.broadcast(NODE_EVENTS.STATUS_UPDATED, payload);
1828
+ const channel = transition.status === 'online' ? NODE_EVENTS.WENT_ONLINE
1829
+ : transition.status === 'offline' ? NODE_EVENTS.WENT_OFFLINE
1830
+ : transition.status === 'degraded' ? NODE_EVENTS.WENT_DEGRADED
1831
+ : null;
1832
+ if (channel)
1833
+ this.eventBroadcaster.broadcast(channel, payload);
1834
+ }
926
1835
  // ============================================================================
927
1836
  // Event Broadcasting (Phase 3: orchestrator events → WebSocket push)
928
1837
  // ============================================================================
@@ -1018,16 +1927,53 @@ export class OmnitronDaemon {
1018
1927
  this.fileWatcher.start(apps);
1019
1928
  return this.fileWatcher.getWatchedApps();
1020
1929
  }
1930
+ /**
1931
+ * Point the file watcher at a reloaded config, if one is running.
1932
+ *
1933
+ * Public because `DaemonRpcService.reloadConfig` is the other half of the
1934
+ * same reload — the RPC and the SIGHUP handler must not diverge on what a
1935
+ * reload updates, which is exactly how the watcher came to be left out of
1936
+ * both.
1937
+ */
1938
+ applyWatchConfig(config) {
1939
+ this.fileWatcher?.applyConfig(config);
1940
+ }
1021
1941
  disableWatch() {
1022
1942
  if (this.fileWatcher) {
1023
1943
  this.fileWatcher.stop();
1024
1944
  this.fileWatcher = null;
1025
1945
  }
1026
1946
  }
1947
+ /**
1948
+ * Whether file watching is running, and over what.
1949
+ *
1950
+ * `enabled` said only that a `FileWatcher` OBJECT existed. Observed on this
1951
+ * host: `{ enabled: true, apps: [] }` — a watcher running over nothing,
1952
+ * reported to an operator as "watching". Everyone downstream read the
1953
+ * boolean and concluded their edits were being picked up.
1954
+ *
1955
+ * The watch set is built from `config.apps` — the daemon's OWN ecosystem
1956
+ * config. Apps that arrive through the project registry are not in it, so a
1957
+ * daemon serving projects watches none of them however the flag reads.
1958
+ * `watching` is the fact an operator actually wants; `enabled` keeps its
1959
+ * old meaning so existing callers do not silently change behaviour, and
1960
+ * `reason` says why the two can disagree.
1961
+ */
1027
1962
  getWatchStatus() {
1028
- if (!this.fileWatcher)
1029
- return { enabled: false, apps: [] };
1030
- return { enabled: true, apps: this.fileWatcher.getWatchedApps() };
1963
+ if (!this.fileWatcher) {
1964
+ return { enabled: false, watching: false, reason: 'the file watcher is not running', apps: [] };
1965
+ }
1966
+ const apps = this.fileWatcher.getWatchedApps();
1967
+ if (apps.length === 0) {
1968
+ return {
1969
+ enabled: true,
1970
+ watching: false,
1971
+ reason: 'the watcher is running but no app matched: the watch set comes from this daemon\'s own ' +
1972
+ 'ecosystem config, and apps registered through a project are not in it',
1973
+ apps,
1974
+ };
1975
+ }
1976
+ return { enabled: true, watching: true, apps };
1031
1977
  }
1032
1978
  async getConfig() {
1033
1979
  const { loadEcosystemConfig } = await import('../config/loader.js');
@@ -1038,7 +1984,7 @@ export class OmnitronDaemon {
1038
1984
  // ============================================================================
1039
1985
  /**
1040
1986
  * Trigger graceful shutdown via Application's lifecycle system.
1041
- * Called by DaemonRpcService.shutdown() for the `omnitron shutdown` CLI command.
1987
+ * Called by DaemonRpcService.shutdown() for the `omnitron down` CLI command.
1042
1988
  */
1043
1989
  async stop() {
1044
1990
  if (!this.app)
@@ -1047,6 +1993,67 @@ export class OmnitronDaemon {
1047
1993
  // Application doesn't process.exit() for Manual reason — do it explicitly
1048
1994
  process.exit(0);
1049
1995
  }
1996
+ /**
1997
+ * SIGHUP handler — reload the ecosystem config without taking the
1998
+ * daemon down. Follows the Unix convention (`man 7 signal`: "many
1999
+ * daemons interpret SIGHUP as a request to reread their configs").
2000
+ * Reloads via the same code path the `reloadConfig` RPC uses so
2001
+ * orchestrator + RPC service both pick up the new entries.
2002
+ */
2003
+ async reloadConfigOnHup() {
2004
+ if (!this.app)
2005
+ return;
2006
+ // Loading first, on its own: a malformed file must be rejected before
2007
+ // anything is touched, and that failure genuinely means "the daemon is as
2008
+ // it was".
2009
+ const { loadEcosystemConfig } = await import('../config/loader.js');
2010
+ const newConfig = await loadEcosystemConfig();
2011
+ // Applying is the other half, and it has no such guarantee — each
2012
+ // destination takes the config in turn, so a failure in the middle leaves
2013
+ // the daemon split. `applyReloadedConfig` names what applied and what did
2014
+ // not, because "reload failed" reads as "nothing changed" and that is the
2015
+ // wrong reading exactly when it matters.
2016
+ //
2017
+ // The handler used to end in `catch (err) { throw err; }`, which did
2018
+ // nothing whatever. Removing it is trivial; what it was standing in front
2019
+ // of is not.
2020
+ const { applyReloadedConfig } = await import('./config-reload.js');
2021
+ await applyReloadedConfig(newConfig, [
2022
+ {
2023
+ name: 'orchestrator',
2024
+ apply: async (cfg) => {
2025
+ const orchestrator = await this.app.container.resolveAsync(ORCHESTRATOR_TOKEN);
2026
+ orchestrator.setConfig(cfg);
2027
+ },
2028
+ },
2029
+ {
2030
+ name: 'file watcher',
2031
+ // The watcher holds its own copy of the app list. Left out, a reload
2032
+ // updated who gets restarted but not who is watched: an app added to
2033
+ // the config was never watched, and one removed from it kept its
2034
+ // watchers and went on triggering restarts.
2035
+ apply: (cfg) => {
2036
+ this.applyWatchConfig(cfg);
2037
+ },
2038
+ },
2039
+ {
2040
+ name: 'RPC snapshot',
2041
+ // Exposed under DAEMON_SERVICE_ID; the reloadConfig RPC path mutates
2042
+ // `this.config` directly, so we mirror the same write. Unresolvable is
2043
+ // not a failure here — the service may not be exposed yet.
2044
+ apply: async (cfg) => {
2045
+ const { DAEMON_SERVICE_ID } = await import('../config/defaults.js');
2046
+ const daemonRpc = await this.app.container
2047
+ .resolveAsync(DAEMON_SERVICE_ID)
2048
+ .catch(() => null);
2049
+ if (daemonRpc) {
2050
+ daemonRpc.config = cfg;
2051
+ }
2052
+ },
2053
+ },
2054
+ ]);
2055
+ console.info('[omnitron] SIGHUP — config reloaded');
2056
+ }
1050
2057
  /**
1051
2058
  * Register all daemon shutdown tasks with Application's lifecycle system.
1052
2059
  *
@@ -1071,6 +2078,32 @@ export class OmnitronDaemon {
1071
2078
  signal: details?.signal,
1072
2079
  });
1073
2080
  });
2081
+ // D13: Application now drives shutdown via LifecycleController, which
2082
+ // emits per-phase events via ApplicationEvent.LifecyclePhaseEvent.
2083
+ // Forward `phase-finish` durations into the metrics bridge so the
2084
+ // `lifecycle_shutdown_phase_duration_seconds` histogram and the
2085
+ // `lifecycle_shutdown_phase_timeouts_total` counter populate.
2086
+ app.on(ApplicationEvent.LifecyclePhaseEvent, (event) => {
2087
+ const bridge = this.metricsBridge;
2088
+ if (!bridge)
2089
+ return;
2090
+ const phase = event?.phase;
2091
+ const kind = event?.kind;
2092
+ const durationMs = typeof event?.durationMs === 'number' ? event.durationMs : 0;
2093
+ if (!phase)
2094
+ return;
2095
+ try {
2096
+ if (kind === 'phase-finish') {
2097
+ bridge.recordLifecyclePhase(String(phase), durationMs, 'finish');
2098
+ }
2099
+ else if (kind === 'phase-timeout') {
2100
+ bridge.recordLifecyclePhase(String(phase), durationMs, 'timeout');
2101
+ }
2102
+ }
2103
+ catch {
2104
+ // Metrics are best-effort; never fail shutdown over telemetry.
2105
+ }
2106
+ });
1074
2107
  // --- Priority: First (0) — Immediate cleanup ---
1075
2108
  app.registerShutdownTask('stop-file-watcher-and-webapp', async () => {
1076
2109
  if (this.fileWatcher) {
@@ -1081,6 +2114,13 @@ export class OmnitronDaemon {
1081
2114
  this.eventBroadcaster.clear();
1082
2115
  this.eventBroadcaster = null;
1083
2116
  }
2117
+ if (this.metricsServer) {
2118
+ try {
2119
+ await this.metricsServer.stop();
2120
+ }
2121
+ catch { /* non-critical */ }
2122
+ this.metricsServer = null;
2123
+ }
1084
2124
  // Stop webapp nginx container (non-blocking — don't wait if slow)
1085
2125
  try {
1086
2126
  const { removeContainer } = await import('../infrastructure/container-runtime.js');
@@ -1088,6 +2128,20 @@ export class OmnitronDaemon {
1088
2128
  }
1089
2129
  catch { /* non-critical */ }
1090
2130
  }, ShutdownPriority.First);
2131
+ // A release build outlives its daemon otherwise.
2132
+ //
2133
+ // Every step runs in its own process group, detached, so `pnpm install`
2134
+ // and the gate suite carry on after the daemon that started them is
2135
+ // gone — holding ports, writing into a release directory nothing will
2136
+ // ever finish, and for a quarter of an hour. Stopped first, with the
2137
+ // file watcher, because ending them is instant and everything after
2138
+ // this is slower.
2139
+ app.registerShutdownTask('stop-release-builds', () => {
2140
+ // It says what it stopped through its own logger; a shutdown task that
2141
+ // resolved the logger module to say one line would be one more thing
2142
+ // that can fail while the daemon is going down.
2143
+ this.releaseService?.stopAll();
2144
+ }, ShutdownPriority.First);
1091
2145
  // --- Priority: VeryHigh (10) — Stop managed apps (fast — 5s per app max) ---
1092
2146
  app.registerShutdownTask('stop-managed-apps', async () => {
1093
2147
  const orchestrator = await container.resolveAsync(ORCHESTRATOR_TOKEN);
@@ -1105,11 +2159,6 @@ export class OmnitronDaemon {
1105
2159
  await mc.stop();
1106
2160
  }
1107
2161
  catch { /* non-critical */ }
1108
- try {
1109
- const alert = await container.resolveAsync(ALERT_SERVICE_TOKEN);
1110
- alert.stop();
1111
- }
1112
- catch { /* non-critical */ }
1113
2162
  try {
1114
2163
  const fleet = await container.resolveAsync(FLEET_SERVICE_TOKEN);
1115
2164
  fleet.stopHeartbeatSweep();
@@ -1123,6 +2172,22 @@ export class OmnitronDaemon {
1123
2172
  }, ShutdownPriority.High);
1124
2173
  // --- Priority: Normal (50) — System workers, node manager, sync, cluster ---
1125
2174
  app.registerShutdownTask('stop-system-services', async () => {
2175
+ // The mesh first: stop following the registry, then drop the
2176
+ // connections. Each one may hold an SSH tunnel, and a tunnel that
2177
+ // outlives its daemon holds a socket and a remote session open with
2178
+ // nothing left to notice.
2179
+ try {
2180
+ this.meshHandle?.stop();
2181
+ }
2182
+ catch { /* non-critical */ }
2183
+ this.meshHandle = null;
2184
+ if (this.slaveConnector) {
2185
+ try {
2186
+ await this.slaveConnector.dispose();
2187
+ }
2188
+ catch { /* non-critical */ }
2189
+ this.slaveConnector = null;
2190
+ }
1126
2191
  if (this.syncService) {
1127
2192
  try {
1128
2193
  await this.syncService.stop();
@@ -1140,6 +2205,15 @@ export class OmnitronDaemon {
1140
2205
  this.configSyncService.stopPeriodicSync();
1141
2206
  }
1142
2207
  catch { /* non-critical */ }
2208
+ // Before stopping the workers: a worker exiting on the way out must not
2209
+ // be read as a crash and rescheduled.
2210
+ this.isShuttingDown = true;
2211
+ this.healthWorkerSpawn = null;
2212
+ if (this.healthWorkerRestartTimer) {
2213
+ clearTimeout(this.healthWorkerRestartTimer);
2214
+ this.healthWorkerRestartTimer = null;
2215
+ }
2216
+ this.stopFallbackNodeChecks();
1143
2217
  if (this.systemWorkerManager) {
1144
2218
  try {
1145
2219
  await this.systemWorkerManager.stopAll();
@@ -1186,10 +2260,29 @@ export class OmnitronDaemon {
1186
2260
  }
1187
2261
  catch { /* non-critical */ }
1188
2262
  if (this.infraService) {
1189
- try {
1190
- await this.infraService.teardown();
2263
+ // A node's infrastructure outlives its daemon.
2264
+ //
2265
+ // `teardown()` is `docker rm`, not `docker stop`, and this task runs
2266
+ // on every shutdown — so on a node, restarting the daemon would
2267
+ // remove the stack's Postgres, Redis and MinIO. `omnitron fleet
2268
+ // upgrade` restarts every node's daemon, which would have made a
2269
+ // routine upgrade take the test environment down with it.
2270
+ //
2271
+ // The daemon is the supervisor, not the owner of the data plane's
2272
+ // lifetime. Locally it still tears down — `omnitron down` there means
2273
+ // "I am done for the day" — and on either, `omnitron infra down` is
2274
+ // the command that removes containers on purpose.
2275
+ if (this.dc.role === 'slave') {
2276
+ // Said on the way out, because the absence of a teardown is
2277
+ // otherwise indistinguishable from a teardown that failed.
2278
+ console.info('[omnitron] leaving this node\'s infrastructure running — the daemon does not own its lifetime');
2279
+ }
2280
+ else {
2281
+ try {
2282
+ await this.infraService.teardown();
2283
+ }
2284
+ catch { /* best-effort */ }
1191
2285
  }
1192
- catch { /* best-effort */ }
1193
2286
  this.infraService = null;
1194
2287
  }
1195
2288
  }, ShutdownPriority.VeryLow);
@@ -1199,4 +2292,22 @@ export class OmnitronDaemon {
1199
2292
  }, ShutdownPriority.Last);
1200
2293
  }
1201
2294
  }
2295
+ /**
2296
+ * Call a method on a system worker's proxy.
2297
+ *
2298
+ * `ServiceProxy` is typed with an index signature whose values may be an
2299
+ * async iterable as well as a function, so a direct `worker.updateConfig(…)`
2300
+ * does not type-check. Resolving through one place keeps the cast in one
2301
+ * place, and returns without throwing when the worker is not running — the
2302
+ * only correct answer for "tell the health monitor" when there is none.
2303
+ */
2304
+ async function callWorkerMethod(manager, method, ...args) {
2305
+ const worker = manager.get('health-monitor');
2306
+ if (!worker)
2307
+ return undefined;
2308
+ const fn = worker[method];
2309
+ if (typeof fn !== 'function')
2310
+ return undefined;
2311
+ return fn.apply(worker, args);
2312
+ }
1202
2313
  //# sourceMappingURL=daemon.js.map