@askexenow/exe-os 0.9.372 → 0.9.374

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 (470) hide show
  1. package/deploy/compose/.env.customer.example +45 -10
  2. package/deploy/compose/.env.example +46 -9
  3. package/deploy/compose/docker-compose.yml +136 -18
  4. package/deploy/compose/generate-env.ts +142 -34
  5. package/deploy/compose/sso-edge/default.conf.template +78 -7
  6. package/deploy/compose/sso-edge/sso-redirect.conf +109 -13
  7. package/deploy/stack-manifests/v0.9.30-apps.json +1 -1
  8. package/deploy/stack-manifests/v0.9.json +4 -4
  9. package/dist/.dist-verified.json +3 -3
  10. package/dist/{active-agent-GRT5MKD5.js → active-agent-ONPPEWQV.js} +7 -6
  11. package/dist/{active-agent-L2ZSSXDK.js → active-agent-UJKMG7LJ.js} +7 -6
  12. package/dist/{agent-defs-generator-M4ZNYIVX.js → agent-defs-generator-ZWI6DXOE.js} +6 -5
  13. package/dist/{agent-loop-FOK2IRZQ.js → agent-loop-OYHJKNYH.js} +1 -1
  14. package/dist/{agentic-ontology-DMPRDEUX.js → agentic-ontology-WHFJ3QFS.js} +1 -1
  15. package/dist/{artifact-backup-767STHSW.js → artifact-backup-WXZNB7KE.js} +2 -1
  16. package/dist/{backfill-gate-VX2RZTQV.js → backfill-gate-KWJQ35YX.js} +3 -2
  17. package/dist/{backfill-metadata-ZMEHJQ5M.js → backfill-metadata-3OUSQD5V.js} +8 -7
  18. package/dist/{background-jobs-LTPWCMA3.js → background-jobs-6VMD55PK.js} +3 -2
  19. package/dist/{backup-verify-EX2K732J.js → backup-verify-ZKTUYLTA.js} +4 -3
  20. package/dist/{behavior-injection-22AGMS25.js → behavior-injection-M6UYZCMW.js} +17 -15
  21. package/dist/{behaviors-DVFXAWF5.js → behaviors-ZJ45TFBJ.js} +21 -13
  22. package/dist/bin/age-ontology-load.js +3 -2
  23. package/dist/bin/agentic-ontology-backfill.js +10 -9
  24. package/dist/bin/agentic-reflection-backfill.js +11 -10
  25. package/dist/bin/agentic-semantic-label.js +10 -9
  26. package/dist/bin/backfill-conversations.js +13 -11
  27. package/dist/bin/backfill-responses.js +13 -11
  28. package/dist/bin/backfill-vectors.js +17 -15
  29. package/dist/bin/bulk-sync-postgres.js +22 -20
  30. package/dist/bin/cc-doctor.js +10 -9
  31. package/dist/bin/cleanup-stale-review-tasks.js +29 -26
  32. package/dist/bin/cli.js +36 -26
  33. package/dist/bin/deferred-daemon-restart.js +114 -110
  34. package/dist/bin/exe-agent-config.js +6 -5
  35. package/dist/bin/exe-agent.js +127 -33
  36. package/dist/bin/exe-assign.js +15 -13
  37. package/dist/bin/exe-boot.js +57 -54
  38. package/dist/bin/exe-call.js +8 -7
  39. package/dist/bin/exe-cloud.js +24 -22
  40. package/dist/bin/exe-config-dump.js +6 -5
  41. package/dist/bin/exe-dispatch.js +30 -27
  42. package/dist/bin/exe-doctor.js +14 -10
  43. package/dist/bin/exe-export-behaviors.js +19 -17
  44. package/dist/bin/exe-forget.js +10 -9
  45. package/dist/bin/exe-gateway.js +199 -19
  46. package/dist/bin/exe-grant.js +5 -5
  47. package/dist/bin/exe-healthcheck.js +10 -9
  48. package/dist/bin/exe-heartbeat.js +35 -32
  49. package/dist/bin/exe-kill.js +34 -31
  50. package/dist/bin/exe-launch-agent.js +126 -54
  51. package/dist/bin/exe-mcp.js +6 -5
  52. package/dist/bin/exe-memory-reads.js +8 -7
  53. package/dist/bin/exe-new-employee.js +12 -11
  54. package/dist/bin/exe-os-mcp-proxy.js +6 -5
  55. package/dist/bin/exe-pending-messages.js +30 -27
  56. package/dist/bin/exe-pending-notifications.js +29 -26
  57. package/dist/bin/exe-pending-reviews.js +30 -27
  58. package/dist/bin/exe-rename.js +8 -7
  59. package/dist/bin/exe-repo-truth.js +1 -1
  60. package/dist/bin/exe-review.js +33 -30
  61. package/dist/bin/exe-search.js +9 -8
  62. package/dist/bin/exe-session-cleanup.js +35 -32
  63. package/dist/bin/exe-settings.js +24 -22
  64. package/dist/bin/exe-start-codex.js +137 -55
  65. package/dist/bin/exe-start-opencode.js +121 -23
  66. package/dist/bin/exe-status.js +31 -28
  67. package/dist/bin/exe-support.js +5 -4
  68. package/dist/bin/exe-team.js +7 -6
  69. package/dist/bin/exe-watchdog.js +31 -11
  70. package/dist/bin/generate-hook-manifest.js +44 -7
  71. package/dist/bin/git-sweep.js +31 -28
  72. package/dist/bin/graph-backfill.js +89 -35
  73. package/dist/bin/graph-export.js +9 -8
  74. package/dist/bin/import-history.js +14 -13
  75. package/dist/bin/install.js +137 -113
  76. package/dist/bin/intercom-check.js +5 -5
  77. package/dist/bin/mcp-sessions.js +2 -2
  78. package/dist/bin/orchestration-metrics.js +7 -6
  79. package/dist/bin/postgres-agentic-reflection-backfill.js +5 -4
  80. package/dist/bin/postgres-agentic-semantic-backfill.js +4 -3
  81. package/dist/bin/pre-publish.js +1 -1
  82. package/dist/bin/reconcile-backlog.js +2 -2
  83. package/dist/bin/registry-proxy.js +1 -1
  84. package/dist/bin/scan-tasks.js +29 -26
  85. package/dist/bin/setup.js +5 -4
  86. package/dist/bin/shard-migrate.js +9 -8
  87. package/dist/bin/stack-update.js +40 -9
  88. package/dist/bin/verify-stack.js +2 -2
  89. package/dist/bin/vps-health-gate.js +1 -1
  90. package/dist/{boot-generation-OHXH4YC5.js → boot-generation-JKVZ4ZAT.js} +6 -5
  91. package/dist/{boot-review-sections-N5IXIGSS.js → boot-review-sections-2M6BTG73.js} +28 -25
  92. package/dist/{branch-hygiene-KCGXRAWP.js → branch-hygiene-U5GFRSTP.js} +31 -7
  93. package/dist/{branding-GH43W3PU.js → branding-I6KIMB7F.js} +3 -2
  94. package/dist/budget-fallback-XJRPMP2D.js +11 -0
  95. package/dist/build-manifest.json +1318 -1233
  96. package/dist/{capability-cards-4PXVLIVS.js → capability-cards-5IMJT6J7.js} +5 -4
  97. package/dist/{capacity-monitor-FKMQXORX.js → capacity-monitor-SAXCQK7O.js} +30 -27
  98. package/dist/{catchup-brief-SN5EKO4N.js → catchup-brief-U2TXBLHD.js} +34 -31
  99. package/dist/{chunk-CNC57JCS.js → chunk-23MVDXLY.js} +4 -4
  100. package/dist/{chunk-V4XAKXXF.js → chunk-2AJGV5UL.js} +1 -1
  101. package/dist/{chunk-FBYAXTDM.js → chunk-2ZOEC4KC.js} +10 -2
  102. package/dist/{chunk-ELXFSIIM.js → chunk-32YV32VA.js} +2 -2
  103. package/dist/{chunk-44C7QNZS.js → chunk-3KDCORDO.js} +1 -1
  104. package/dist/{chunk-JTB5V6BV.js → chunk-3SXHEQ7X.js} +1 -1
  105. package/dist/{chunk-HDW4XPDV.js → chunk-3UUX5OY4.js} +1 -1
  106. package/dist/{chunk-7PSLK36P.js → chunk-44TLJZJ5.js} +1 -1
  107. package/dist/{chunk-JTWTBX2M.js → chunk-4EN3IZ3Q.js} +2 -1
  108. package/dist/{chunk-KPMC7PY7.js → chunk-4HKTO2SV.js} +1 -1
  109. package/dist/{chunk-CDUB5Q6J.js → chunk-4IK2K3B3.js} +1 -1
  110. package/dist/{chunk-GH6YJOOC.js → chunk-4SJ4LK4V.js} +6 -6
  111. package/dist/{chunk-PVEM537A.js → chunk-4YLBKEL5.js} +2 -2
  112. package/dist/chunk-4YNA3CM6.js +872 -0
  113. package/dist/{chunk-KYFFVTGO.js → chunk-55BEFF4V.js} +3 -3
  114. package/dist/{chunk-SEP5ITFQ.js → chunk-636LKCKE.js} +2 -2
  115. package/dist/{chunk-QTI4QAWA.js → chunk-6EZQUNE6.js} +338 -5
  116. package/dist/{chunk-WAEKYKTK.js → chunk-6RKKVPYF.js} +1 -1
  117. package/dist/{chunk-VX2F4ZUY.js → chunk-6SCGLHJ7.js} +3 -3
  118. package/dist/{chunk-MEGRQ53A.js → chunk-6VHOQ3IM.js} +51 -6
  119. package/dist/{chunk-HIIXCAEE.js → chunk-6YVUR3OU.js} +3 -3
  120. package/dist/{chunk-L6ZNURWN.js → chunk-7GE2EQ23.js} +1 -1
  121. package/dist/{chunk-2CWS374F.js → chunk-7RI2CNXJ.js} +1 -1
  122. package/dist/{chunk-OL3QP3AU.js → chunk-7TSBV3SD.js} +1 -1
  123. package/dist/{chunk-DSOQUQTV.js → chunk-A27Q27CT.js} +27 -0
  124. package/dist/{chunk-5AHRZI3R.js → chunk-AETLDRZB.js} +1 -1
  125. package/dist/{chunk-RNLF3CBB.js → chunk-AKLJ4JH3.js} +1 -1
  126. package/dist/{chunk-XHYHX6F7.js → chunk-AKLQSC6W.js} +329 -164
  127. package/dist/chunk-ANLFH23R.js +261 -0
  128. package/dist/{chunk-QLA3RTH4.js → chunk-AWWKRZMB.js} +5 -5
  129. package/dist/{chunk-ERVQTR7P.js → chunk-BBSIEYF5.js} +17 -3
  130. package/dist/{chunk-IPMUWYRQ.js → chunk-BJ7AVSY6.js} +2 -2
  131. package/dist/{chunk-HV6Y6F4X.js → chunk-C2L622QJ.js} +89 -3
  132. package/dist/{chunk-TX3Q2GIL.js → chunk-C5HUMAA7.js} +24 -1
  133. package/dist/{chunk-QTYJPWJN.js → chunk-CBCGDEJA.js} +7 -7
  134. package/dist/{chunk-XXSNLM6R.js → chunk-DB55JZHU.js} +1 -1
  135. package/dist/{chunk-LAZKEJMS.js → chunk-DIXXEE3S.js} +840 -607
  136. package/dist/{chunk-FB343XQX.js → chunk-DJ7YXOTU.js} +2 -2
  137. package/dist/{chunk-YFCNWQAM.js → chunk-DJOIMFKV.js} +2 -2
  138. package/dist/{chunk-UTDMTQNH.js → chunk-DSJI76XL.js} +92 -14
  139. package/dist/{chunk-EEXX4UMQ.js → chunk-DZKPOSFD.js} +1 -1
  140. package/dist/{chunk-WSOA3WMC.js → chunk-E426DOSY.js} +161 -4
  141. package/dist/{chunk-ZISQOOER.js → chunk-EKE37MO4.js} +3 -3
  142. package/dist/{chunk-RIECV7SR.js → chunk-EKYYMIVT.js} +1 -1
  143. package/dist/{chunk-HIVZ53YB.js → chunk-EXTZSNWL.js} +2 -2
  144. package/dist/{chunk-YYA5LXCT.js → chunk-FNEZ4BWT.js} +1 -1
  145. package/dist/chunk-FTZET5YX.js +313 -0
  146. package/dist/chunk-GHR4ZPJ7.js +70 -0
  147. package/dist/{chunk-MINXPXAB.js → chunk-GJI77VDO.js} +3 -3
  148. package/dist/{chunk-AAMGHW5W.js → chunk-GLQEJOXE.js} +1 -0
  149. package/dist/{chunk-ADNBOKYZ.js → chunk-GM7TLYOK.js} +60 -18
  150. package/dist/{chunk-RUWWES2K.js → chunk-GNUAL43X.js} +2 -2
  151. package/dist/{chunk-QZL6E4UB.js → chunk-GOMQ7PVQ.js} +2 -2
  152. package/dist/{chunk-GB3AHV5F.js → chunk-GQOEWNVM.js} +1 -1
  153. package/dist/{chunk-NQZUSEAR.js → chunk-GS7LIT34.js} +246 -17
  154. package/dist/{chunk-XTWHNLGD.js → chunk-GZOG2MAX.js} +1 -1
  155. package/dist/{chunk-E5HAY3DQ.js → chunk-HDLAB7W3.js} +1 -1
  156. package/dist/{chunk-E5QQZDQN.js → chunk-HNWS7YHN.js} +358 -136
  157. package/dist/{chunk-VV5EZZNT.js → chunk-HTBEVCO2.js} +1 -1
  158. package/dist/{chunk-OZBII5E3.js → chunk-I4DNJFDR.js} +4 -4
  159. package/dist/{chunk-SI5SZAJA.js → chunk-IBS2FAI4.js} +21 -4
  160. package/dist/{chunk-J5EYOBFF.js → chunk-IHXW4VUI.js} +1 -1
  161. package/dist/{chunk-RUVO3NJC.js → chunk-IN6CAM4F.js} +9 -3
  162. package/dist/{chunk-53PYNYTW.js → chunk-IRVOXUSK.js} +5 -5
  163. package/dist/{chunk-THM7J6UI.js → chunk-JHI5WMK5.js} +5 -5
  164. package/dist/{chunk-3KTQWMHI.js → chunk-JSMDRKPJ.js} +12 -4
  165. package/dist/chunk-JTVIANUB.js +131 -0
  166. package/dist/{chunk-2ADYTOHQ.js → chunk-K4ESASPL.js} +1 -1
  167. package/dist/{chunk-YWUQVHDL.js → chunk-KTCXJWJG.js} +1 -1
  168. package/dist/{chunk-RGUDGC6A.js → chunk-KTU3EAWK.js} +5 -5
  169. package/dist/{chunk-DKBPCOU4.js → chunk-L6RXG36J.js} +2 -2
  170. package/dist/{chunk-T4EK56WI.js → chunk-LDNYSMPZ.js} +1 -1
  171. package/dist/{chunk-RIK5GRWI.js → chunk-LGS2V2XD.js} +3 -3
  172. package/dist/{chunk-JKXZGRWR.js → chunk-LP4GB656.js} +2 -2
  173. package/dist/{chunk-H74466TM.js → chunk-M7O7TGGU.js} +1 -1
  174. package/dist/chunk-MJCSEIFI.js +54 -0
  175. package/dist/{chunk-IDZPQG2D.js → chunk-MOGA4Q7F.js} +2 -2
  176. package/dist/{chunk-OYQNNKNS.js → chunk-MXPNVSXI.js} +37 -14
  177. package/dist/{chunk-UVW7YFAF.js → chunk-N7VEP5B3.js} +4 -4
  178. package/dist/{chunk-HHE2HV3V.js → chunk-NBXDXKLC.js} +1 -1
  179. package/dist/{chunk-LDL3S7AC.js → chunk-NKETJCB5.js} +7 -3
  180. package/dist/{chunk-ECHLVLA7.js → chunk-NMH5ZQPU.js} +1 -1
  181. package/dist/{chunk-U3XLRMJD.js → chunk-NT7W4RXD.js} +1 -1
  182. package/dist/{chunk-UQUUNGTP.js → chunk-NUK7EEN5.js} +11 -9
  183. package/dist/{chunk-GBNB4EWD.js → chunk-ORM7ZFKC.js} +16 -0
  184. package/dist/{chunk-O4ZFRK6W.js → chunk-OVEQE6YS.js} +244 -49
  185. package/dist/{chunk-4ZJ5BPY6.js → chunk-PB5S7YOF.js} +2 -2
  186. package/dist/{chunk-JR23BL74.js → chunk-PDVXWXTL.js} +4 -4
  187. package/dist/{chunk-3CIMDFXG.js → chunk-PFQ3VEEM.js} +1 -1
  188. package/dist/{chunk-SBJV2PCU.js → chunk-PHBU4LGO.js} +1 -1
  189. package/dist/{chunk-W25RLOQ2.js → chunk-PLZSZOZN.js} +3 -3
  190. package/dist/{chunk-THIJIOE2.js → chunk-PY3FOMRN.js} +18 -6
  191. package/dist/{chunk-AAUWFXOX.js → chunk-Q3AXXKDF.js} +3 -3
  192. package/dist/{chunk-VKXS2YGN.js → chunk-QHJR7IMX.js} +2 -2
  193. package/dist/chunk-QLZ46RCK.js +45 -0
  194. package/dist/{chunk-UHPMK6QZ.js → chunk-R3CZWFFC.js} +10 -2
  195. package/dist/{chunk-6XEYELCA.js → chunk-R4CBHKZ2.js} +1 -1
  196. package/dist/{chunk-5APCDDJZ.js → chunk-R522MWAR.js} +9 -9
  197. package/dist/chunk-RH4AH3YB.js +80 -0
  198. package/dist/{chunk-AMKSDKMK.js → chunk-S2WBS6BG.js} +22 -35
  199. package/dist/{chunk-WUKQVNLP.js → chunk-SEQIPGLU.js} +1 -1
  200. package/dist/{chunk-7WQRFSZI.js → chunk-SL6KQYDU.js} +26 -6
  201. package/dist/chunk-SLLLOOEI.js +103 -0
  202. package/dist/{chunk-5N4SG5NB.js → chunk-T35RCQFE.js} +3 -3
  203. package/dist/{chunk-7V6M3QUS.js → chunk-T3QUKNLI.js} +1 -1
  204. package/dist/{chunk-CW2UCKHS.js → chunk-TADAO3HV.js} +2 -2
  205. package/dist/{chunk-5DCCKHDY.js → chunk-TAMDAOCU.js} +109 -13
  206. package/dist/{chunk-ZXB56YR2.js → chunk-TFKCERAK.js} +101 -23
  207. package/dist/{chunk-RBYHJDAT.js → chunk-TQAZTRQ3.js} +3 -3
  208. package/dist/{chunk-OKGIOWOB.js → chunk-U6SUKSZR.js} +2 -2
  209. package/dist/{chunk-23QKEUF6.js → chunk-U73AC5AN.js} +34 -12
  210. package/dist/{chunk-PM6WSWVU.js → chunk-UBK7HK3J.js} +3 -3
  211. package/dist/{chunk-H37FSTF2.js → chunk-UBMZ3YQU.js} +1 -1
  212. package/dist/{chunk-JJUGI2YL.js → chunk-UCFC2AEI.js} +1 -1
  213. package/dist/{chunk-6IWLAQ7O.js → chunk-UCUOGVDX.js} +4 -0
  214. package/dist/{chunk-E7UCDSUE.js → chunk-UKLN57Q5.js} +2 -2
  215. package/dist/{chunk-ZYCLSEZT.js → chunk-ULIBLJ6W.js} +33 -11
  216. package/dist/{chunk-RLR6VVKV.js → chunk-UM3N6VND.js} +4 -4
  217. package/dist/{chunk-ZNUJFX7L.js → chunk-UQGSBM3U.js} +1 -1
  218. package/dist/{chunk-ZZXMB5F2.js → chunk-UZDF6FLJ.js} +1 -1
  219. package/dist/{chunk-LNOS24GV.js → chunk-V3WLC5TJ.js} +2 -95
  220. package/dist/{chunk-UXLO7VHX.js → chunk-VI4ATC6I.js} +1 -1
  221. package/dist/{chunk-77JP3LK4.js → chunk-VKJGHAZT.js} +92 -21
  222. package/dist/{chunk-7R73IAHU.js → chunk-VMTZ4RUW.js} +67 -150
  223. package/dist/{chunk-TGPQMMQP.js → chunk-VYQ7B7DI.js} +30 -17
  224. package/dist/{chunk-E2WEFZOR.js → chunk-W75GTHR3.js} +1 -1
  225. package/dist/{chunk-UKLF7F3Z.js → chunk-WJG344NC.js} +9 -9
  226. package/dist/{chunk-MBNIJXKI.js → chunk-WMEYJT4R.js} +18 -4
  227. package/dist/{chunk-R6FLELV6.js → chunk-XFTEX77S.js} +3 -3
  228. package/dist/{chunk-MDGYXUOV.js → chunk-XH7YL7JM.js} +1 -1
  229. package/dist/{chunk-JOO35RHR.js → chunk-XL5MFGMR.js} +3 -3
  230. package/dist/{chunk-OW5RZIDW.js → chunk-XNNECNDL.js} +95 -33
  231. package/dist/{chunk-5AHDQCTB.js → chunk-XXFKSJYA.js} +6 -6
  232. package/dist/{chunk-TGEPLJ66.js → chunk-XXOPG4BW.js} +3 -3
  233. package/dist/{chunk-PGLPUP37.js → chunk-XZ65ST2I.js} +1 -1
  234. package/dist/{chunk-3TTKAT6V.js → chunk-YKC7QDZH.js} +9 -4
  235. package/dist/{chunk-KXYFBBZR.js → chunk-YMP2QSOP.js} +20 -3
  236. package/dist/{chunk-TK4MI6F6.js → chunk-YSLGFPWX.js} +6 -6
  237. package/dist/{chunk-X2P43TB2.js → chunk-YTSXPJUQ.js} +1 -1
  238. package/dist/{chunk-HECXZGGZ.js → chunk-ZCERWHY2.js} +33 -35
  239. package/dist/{chunk-IVU3WN7S.js → chunk-ZG62LIK3.js} +74 -4
  240. package/dist/{chunk-GU62SNDH.js → chunk-ZGYNKA3O.js} +744 -111
  241. package/dist/{chunk-QMSEL5YS.js → chunk-ZIG7VPJV.js} +28 -76
  242. package/dist/{chunk-WZJVGA5I.js → chunk-ZL4AGWAH.js} +258 -13
  243. package/dist/{chunk-QR624DER.js → chunk-ZSODLMFV.js} +2 -2
  244. package/dist/{cleanup-graph-garbage-IPYDS5OX.js → cleanup-graph-garbage-DPJGO52H.js} +11 -10
  245. package/dist/{cloud-sync-command-JLNVG7H4.js → cloud-sync-command-JPNTT4OQ.js} +2 -2
  246. package/dist/{cloud-sync-db-init-JXUF6GI3.js → cloud-sync-db-init-V2LWIUQU.js} +1 -1
  247. package/dist/{cloud-sync-shutdown-Z4SBP6PL.js → cloud-sync-shutdown-BNALPWGX.js} +5 -4
  248. package/dist/{co-activation-74RDFTKS.js → co-activation-CMNSWS6B.js} +5 -4
  249. package/dist/{co-occurrence-CN3TBYVR.js → co-occurrence-WRI2NAN6.js} +9 -8
  250. package/dist/{code-context-index-D4MJ4VFX.js → code-context-index-NOJSHLPN.js} +12 -6
  251. package/dist/code-context-watcher-VZNZU3BO.js +118 -0
  252. package/dist/{codex-consolidation-PPXNBXCV.js → codex-consolidation-SH4UKFBQ.js} +2 -1
  253. package/dist/{conversation-entity-extractor-QGTLH4BP.js → conversation-entity-extractor-74252CAO.js} +4 -4
  254. package/dist/{conversation-wiki-populator-EBUZXYEU.js → conversation-wiki-populator-OS4C6W54.js} +1 -1
  255. package/dist/conversations-retention-C4BLKFEL.js +54 -0
  256. package/dist/coordinator-notice-MHUY7AQB.js +55 -0
  257. package/dist/{crdt-sync-7T4WCOKD.js → crdt-sync-B3JN6AIV.js} +1 -1
  258. package/dist/{crm-webhook-FTW3XZTO.js → crm-webhook-GYWQRFET.js} +2 -2
  259. package/dist/{cto-delegation-gate-SZCE34LB.js → cto-delegation-gate-LQHVLEMW.js} +29 -26
  260. package/dist/{daemon-auth-SSPY5ODS.js → daemon-auth-QZHEO7IQ.js} +3 -2
  261. package/dist/{daemon-orchestration-IGPHVNFR.js → daemon-orchestration-5HIQALZH.js} +36 -34
  262. package/dist/{daemon-ready-OT2VQLS7.js → daemon-ready-P2BR2XP5.js} +1 -1
  263. package/dist/{db-integrity-events-5DTASXJF.js → db-integrity-events-2DTFO37N.js} +3 -2
  264. package/dist/{db-restore-events-2FI4ZMKA.js → db-restore-events-G6NOQOVE.js} +2 -1
  265. package/dist/{destructive-data-guard-6I5MDFVD.js → destructive-data-guard-BVFWPPIS.js} +1 -1
  266. package/dist/doc-graph-extractor-PQHDHQPQ.js +367 -0
  267. package/dist/{documents-URULIXGM.js → documents-C4SSPJMC.js} +15 -13
  268. package/dist/{dreaming-VIZO3QDO.js → dreaming-XAO3FCBV.js} +33 -30
  269. package/dist/{employee-boot-brief-XLOG4ULP.js → employee-boot-brief-W4OHUMRQ.js} +37 -34
  270. package/dist/{exe-drift-TLQM3IHD.js → exe-drift-AG2KMKD5.js} +6 -5
  271. package/dist/{exe-export-7YWYWJHY.js → exe-export-IC5BT335.js} +17 -15
  272. package/dist/{exe-import-DW6VAUKW.js → exe-import-A35ZLDPG.js} +17 -15
  273. package/dist/{exe-key-TLJP2B4Y.js → exe-key-TCMJNYLA.js} +116 -112
  274. package/dist/{exe-org-LU46N36E.js → exe-org-JXCR3JYZ.js} +24 -23
  275. package/dist/{exe-snapshot-QHUZRS62.js → exe-snapshot-SV6SRIG6.js} +36 -33
  276. package/dist/{fast-db-init-SCTOOIUT.js → fast-db-init-L2RGKJWH.js} +1 -1
  277. package/dist/{founder-context-ZFZC4UHO.js → founder-context-D4HFIESJ.js} +4 -3
  278. package/dist/{founder-monitor-envelope-5KTKNL5G.js → founder-monitor-envelope-TFKRGCFE.js} +5 -4
  279. package/dist/{fts-integrity-LKAZEZJE.js → fts-integrity-4YB34KWX.js} +3 -2
  280. package/dist/gateway/index.js +19 -17
  281. package/dist/{git-main-guard-TN27QBAI.js → git-main-guard-VTW4N3CM.js} +7 -1
  282. package/dist/{git-staleness-GSGP344N.js → git-staleness-TN7CYGFG.js} +5 -4
  283. package/dist/{global-procedures-YAR7G4C6.js → global-procedures-YFDEIOG7.js} +7 -6
  284. package/dist/{graph-auto-extract-GNQS64F5.js → graph-auto-extract-3YR3TYRW.js} +9 -8
  285. package/dist/{graph-rag-RMSFLUOB.js → graph-rag-Z2KCJTPK.js} +6 -4
  286. package/dist/{hook-integrity-CF567MVN.js → hook-integrity-KGAV6ZC2.js} +14 -4
  287. package/dist/{hook-prefetch-36BK4F7U.js → hook-prefetch-K5DN3TOQ.js} +29 -26
  288. package/dist/hooks/bug-report-worker.js +34 -30
  289. package/dist/hooks/codex-stop-task-finalizer.js +36 -32
  290. package/dist/hooks/commit-complete.js +33 -30
  291. package/dist/hooks/error-recall.js +12 -11
  292. package/dist/hooks/exe-heartbeat-hook.js +8 -7
  293. package/dist/hooks/ingest-worker.js +23 -11
  294. package/dist/hooks/ingest.js +18 -17
  295. package/dist/hooks/instructions-loaded.js +10 -9
  296. package/dist/hooks/manifest.json +20 -20
  297. package/dist/hooks/notification.js +9 -8
  298. package/dist/hooks/post-compact.js +8 -7
  299. package/dist/hooks/post-tool-combined.js +26 -15
  300. package/dist/hooks/pre-compact.js +33 -30
  301. package/dist/hooks/pre-tool-use.js +404 -46
  302. package/dist/hooks/prompt-submit.js +57 -47
  303. package/dist/hooks/session-end.js +40 -41
  304. package/dist/hooks/session-start.js +21 -20
  305. package/dist/hooks/stop.js +40 -37
  306. package/dist/hooks/subagent-stop.js +38 -35
  307. package/dist/hooks/summary-worker.js +39 -36
  308. package/dist/hyde-MP4QNMU5.js +80 -0
  309. package/dist/index.js +44 -39
  310. package/dist/{installer-LUN4HQIC.js → installer-54HTN5RE.js} +8 -7
  311. package/dist/{installer-OC5VXNC7.js → installer-LK35OX6R.js} +8 -7
  312. package/dist/{installer-MPRFUQJE.js → installer-LLWKZI2S.js} +8 -7
  313. package/dist/intercom-ack-VCW755TN.js +13 -0
  314. package/dist/{intercom-queue-6DZ7BANV.js → intercom-queue-5NOT2RKI.js} +28 -25
  315. package/dist/{key-backup-status-6L2ARCXW.js → key-backup-status-LQPINQ3W.js} +2 -1
  316. package/dist/lib/agent-config.js +3 -2
  317. package/dist/lib/cloud-sync.js +26 -20
  318. package/dist/lib/config.js +2 -1
  319. package/dist/lib/consolidation.js +17 -10
  320. package/dist/lib/database.js +7 -4
  321. package/dist/lib/db-backup.js +17 -2
  322. package/dist/lib/db-daemon-client.js +8 -6
  323. package/dist/lib/db-write-worker-client.js +3 -1
  324. package/dist/lib/db-write-worker.js +20 -3
  325. package/dist/lib/db.js +7 -4
  326. package/dist/lib/device-registry.js +3 -2
  327. package/dist/lib/embed-worker.js +2 -1
  328. package/dist/lib/embedder.js +8 -6
  329. package/dist/lib/employee-templates.js +8 -7
  330. package/dist/lib/employees.js +5 -4
  331. package/dist/lib/exe-daemon-client.js +7 -5
  332. package/dist/lib/exe-daemon.js +467 -221
  333. package/dist/lib/hybrid-search.js +11 -8
  334. package/dist/lib/identity.js +5 -4
  335. package/dist/lib/license.js +4 -3
  336. package/dist/lib/messaging.js +29 -26
  337. package/dist/lib/post-tool-memory.js +16 -2
  338. package/dist/lib/registry-proxy.js +3 -1
  339. package/dist/lib/reminders.js +6 -5
  340. package/dist/lib/schedules.js +9 -8
  341. package/dist/lib/session-registry.js +7 -6
  342. package/dist/lib/skill-learning.js +16 -14
  343. package/dist/lib/stack-invariants.js +17 -1
  344. package/dist/lib/status-brief.js +5 -4
  345. package/dist/lib/store.js +10 -7
  346. package/dist/lib/task-router.js +6 -5
  347. package/dist/lib/tasks.js +31 -27
  348. package/dist/lib/tmux-routing.js +40 -25
  349. package/dist/lib/tmux-status.js +3 -1
  350. package/dist/lib/tmux-transport.js +2 -1
  351. package/dist/lib/token-spend.js +6 -5
  352. package/dist/lib/transport.js +3 -2
  353. package/dist/{license-gate-ESER24PJ.js → license-gate-JVMB6OFD.js} +5 -4
  354. package/dist/{license-sync-JVG5KNIY.js → license-sync-5VYS2F2W.js} +4 -3
  355. package/dist/mcp/register-tools.js +113 -109
  356. package/dist/mcp/server.js +118 -114
  357. package/dist/mcp/tools/complete-reminder.js +7 -6
  358. package/dist/mcp/tools/create-reminder.js +7 -6
  359. package/dist/mcp/tools/create-task.js +34 -30
  360. package/dist/mcp/tools/deactivate-behavior.js +18 -16
  361. package/dist/mcp/tools/list-reminders.js +7 -6
  362. package/dist/mcp/tools/list-tasks.js +34 -30
  363. package/dist/mcp/tools/send-message.js +32 -29
  364. package/dist/mcp/tools/update-task.js +32 -28
  365. package/dist/mcp-child-registry-OTG4MWO7.js +25 -0
  366. package/dist/{mcp-http-config-6K6IKH5F.js → mcp-http-config-DQWL7U2Z.js} +6 -5
  367. package/dist/{mcp-session-identity-LRC6WJSD.js → mcp-session-identity-277GASTQ.js} +28 -25
  368. package/dist/{memory-cards-V3OZHIYP.js → memory-cards-3S2ZQPR7.js} +17 -8
  369. package/dist/{memory-graph-extractor-JC2Z64L4.js → memory-graph-extractor-A6DFEFIN.js} +10 -9
  370. package/dist/{memory-poisoning-defense-YM4QJHCX.js → memory-poisoning-defense-ZVP4H6DY.js} +5 -4
  371. package/dist/{memory-pressure-watchdog-6F6KEQHN.js → memory-pressure-watchdog-6W5F6R7H.js} +29 -26
  372. package/dist/{memory-queue-QJNWKWKG.js → memory-queue-YF7IDSFY.js} +3 -2
  373. package/dist/{memory-queue-client-COA4ABEV.js → memory-queue-client-ADSDP6OX.js} +9 -7
  374. package/dist/{memory-reflection-QXHJPYPC.js → memory-reflection-BKB2YOE6.js} +52 -6
  375. package/dist/{monitor-grants-BW2WL4IU.js → monitor-grants-EV747HMW.js} +4 -3
  376. package/dist/{notification-ledger-6QRLQW4Q.js → notification-ledger-CLGM7ODF.js} +30 -27
  377. package/dist/{notifications-7B6D7Y5C.js → notifications-MACUZPYP.js} +28 -25
  378. package/dist/{oauth-server-TZFNQAFS.js → oauth-server-AYM37SQ4.js} +25 -4
  379. package/dist/{orchestration-events-GY5VP5LU.js → orchestration-events-N7LYMUGA.js} +6 -5
  380. package/dist/{orchestration-phase-UDS24CVQ.js → orchestration-phase-T2WD45UA.js} +3 -2
  381. package/dist/{orchestrator-JVPVOYWA.js → orchestrator-4X24EWHV.js} +30 -27
  382. package/dist/{org-brief-session-GRV5R5E3.js → org-brief-session-FZOFMO5U.js} +13 -12
  383. package/dist/{org-consent-policy-3FHHSYXV.js → org-consent-policy-O5E6HXBL.js} +1 -1
  384. package/dist/{org-join-BY4PV6UO.js → org-join-POWUG25F.js} +8 -7
  385. package/dist/{org-key-admin-WSCPT2CZ.js → org-key-admin-E7EMDOZZ.js} +9 -8
  386. package/dist/{org-key-pin-5WUSPGVN.js → org-key-pin-WHWHVWUO.js} +4 -3
  387. package/dist/{org-membership-ESALTLLN.js → org-membership-WRT2IHJK.js} +5 -4
  388. package/dist/{org-partner-config-YKHPDPZR.js → org-partner-config-B7T4CL2T.js} +7 -6
  389. package/dist/{org-partner-runtime-UJDGECZ4.js → org-partner-runtime-FODONVTM.js} +13 -12
  390. package/dist/{org-row-visibility-V5DXIHTE.js → org-row-visibility-VX4AVV5Q.js} +1 -1
  391. package/dist/{pipeline-router-YY5FQKTY.js → pipeline-router-6GGT2ZW3.js} +6 -5
  392. package/dist/{plan-limits-OVX6SN6B.js → plan-limits-FUN76LYY.js} +8 -7
  393. package/dist/{platform-procedures-I57ITMDC.js → platform-procedures-7YSCLF4Z.js} +1 -1
  394. package/dist/{postinstall-failure-B2JHNFPM.js → postinstall-failure-O6TSSNJB.js} +3 -2
  395. package/dist/{project-boot-LXLU2J32.js → project-boot-3Q65LCIP.js} +2 -1
  396. package/dist/project-name-LJVFMYTQ.js +30 -0
  397. package/dist/{projection-worker-D2472IOD.js → projection-worker-4OANGP5K.js} +5 -4
  398. package/dist/{prospective-memory-JXO2A6PL.js → prospective-memory-KR3P4T6N.js} +5 -4
  399. package/dist/{push-notifications-HR3FZRDI.js → push-notifications-NCZLH6DI.js} +29 -26
  400. package/dist/ref-provenance-gate-M4AQO4OT.js +269 -0
  401. package/dist/{reproject-worktree-memories-OSS3PGRF.js → reproject-worktree-memories-PWYKPB2U.js} +106 -23
  402. package/dist/{reranker-IMZRPYJ2.js → reranker-6V3C2AO4.js} +3 -2
  403. package/dist/resolve-image-labels-ZI6UBW5W.js +15 -0
  404. package/dist/{retrieval-health-W7OD44FE.js → retrieval-health-5D7O6VH6.js} +4 -3
  405. package/dist/{review-polling-2INP7H6M.js → review-polling-QW3LJ4KN.js} +29 -26
  406. package/dist/runtime/index.js +38 -34
  407. package/dist/{session-events-G4XUPVWV.js → session-events-T45SSTMF.js} +31 -26
  408. package/dist/{session-kill-telemetry-W7MHWJTQ.js → session-kill-telemetry-QSPVJGBF.js} +29 -26
  409. package/dist/{session-mcp-config-F7KATWOF.js → session-mcp-config-SC5BLWZG.js} +3 -2
  410. package/dist/{session-scope-LG2J4EMQ.js → session-scope-KRWE54C4.js} +28 -25
  411. package/dist/{setup-wizard-YQJI3SVM.js → setup-wizard-GP3TBTPF.js} +5 -4
  412. package/dist/{shard-manager-UROHZE46.js → shard-manager-OE36A4MY.js} +3 -2
  413. package/dist/{skill-refinement-RA37U756.js → skill-refinement-C7JTMLJG.js} +6 -5
  414. package/dist/stack-generate-manifest-MFMDWRUP.js +810 -0
  415. package/dist/{stack-promote-IYBRXHTA.js → stack-promote-EKSCYY63.js} +8 -6
  416. package/dist/{stack-release-OXWLVVG6.js → stack-release-2GZAAFO4.js} +8 -6
  417. package/dist/{stack-update-2CZEROPK.js → stack-update-J6JXSTAZ.js} +51 -5
  418. package/dist/{steward-gate-LTNFB3FY.js → steward-gate-BFCCMCSW.js} +6 -5
  419. package/dist/{subagent-brief-injection-OHLWLECQ.js → subagent-brief-injection-ED36PLYY.js} +44 -36
  420. package/dist/{subagent-spawn-ledger-USYZXU3M.js → subagent-spawn-ledger-E6U3OY6Z.js} +3 -2
  421. package/dist/{supervisor-handoff-CSJQMI53.js → supervisor-handoff-SGLEPOCY.js} +12 -3
  422. package/dist/{support-inbox-4KCEPAZQ.js → support-inbox-YW47SYJF.js} +20 -5
  423. package/dist/{task-enforcement-LGXWXGVZ.js → task-enforcement-CECTQX4U.js} +29 -26
  424. package/dist/{task-request-inbox-WHCOSUCP.js → task-request-inbox-Q5T4N5K7.js} +1 -1
  425. package/dist/{task-scope-KXF7Y6KA.js → task-scope-ERVEUEYU.js} +28 -25
  426. package/dist/{tasks-crud-5EM55WRZ.js → tasks-crud-ZRQNJXQ2.js} +28 -25
  427. package/dist/{tasks-notify-VFMAURTN.js → tasks-notify-MIZH4C2O.js} +29 -26
  428. package/dist/{tasks-review-MHGMBHUZ.js → tasks-review-Z7J2P6DG.js} +28 -25
  429. package/dist/{telemetry-upload-6NRTN7QJ.js → telemetry-upload-LVQUQYFP.js} +10 -9
  430. package/dist/{token-budget-LZPOYTRP.js → token-budget-BU4NOVZF.js} +6 -5
  431. package/dist/{tool-capability-index-ED5OAPMW.js → tool-capability-index-7F42MOJT.js} +1 -1
  432. package/dist/{tool-gates-7PLIOLAL.js → tool-gates-JWVSAYTW.js} +1 -1
  433. package/dist/{tool-gates-2OZY3GZM.js → tool-gates-VXITVRFQ.js} +1 -1
  434. package/dist/{tool-telemetry-ZX7VSAXK.js → tool-telemetry-O27EVMBR.js} +1 -1
  435. package/dist/tui/App.js +43 -40
  436. package/dist/{tui-data-CC5POSUV.js → tui-data-TPWWBIUQ.js} +28 -25
  437. package/dist/{update-support-KH6NSQON.js → update-support-F2F5537B.js} +22 -20
  438. package/dist/{whatsapp-QW3X7OCN.js → whatsapp-YM4A2CSK.js} +3 -1
  439. package/dist/{work-claims-MNK7PEPW.js → work-claims-B73X5NE7.js} +6 -5
  440. package/dist/{work-state-heartbeat-YEWT5NMZ.js → work-state-heartbeat-P4YX6E2R.js} +1 -1
  441. package/dist/{worker-gate-BOQSIU2Z.js → worker-gate-BEEIIVWA.js} +3 -2
  442. package/dist/{workflow-engine-MUUD7ZEX.js → workflow-engine-5X46PFCW.js} +4 -2
  443. package/dist/{worktree-GOGT2LZP.js → worktree-W47CIMJ6.js} +9 -6
  444. package/dist/worktree-ownership-guard-OUWJROW2.js +88 -0
  445. package/dist/{worktree-sweep-46VWPVQT.js → worktree-sweep-BNBD4RFX.js} +8 -7
  446. package/package.json +14 -5
  447. package/release-notes.json +5 -5
  448. package/src/commands/exe/call.md +1 -1
  449. package/stack.release.json +6 -0
  450. package/dist/chunk-KZXMP6JG.js +0 -319
  451. package/dist/chunk-TCTED52S.js +0 -120
  452. package/dist/chunk-W2IRQJ6R.js +0 -67
  453. package/dist/doc-graph-extractor-VPTRIEKN.js +0 -149
  454. package/dist/project-name-ILJTETUE.js +0 -14
  455. /package/dist/{chunk-3Y5THOU6.js → chunk-6WIFPD2P.js} +0 -0
  456. /package/dist/{chunk-7XGXRD22.js → chunk-AISULNGL.js} +0 -0
  457. /package/dist/{chunk-3F2UB4IQ.js → chunk-DN4YVV4Z.js} +0 -0
  458. /package/dist/{chunk-WV3TOOL7.js → chunk-KQ6RJWRW.js} +0 -0
  459. /package/dist/{chunk-HR2R2QER.js → chunk-M4IRPXSZ.js} +0 -0
  460. /package/dist/{chunk-ORA5BWCV.js → chunk-NRG7KZIP.js} +0 -0
  461. /package/dist/{chunk-7UTJA6HF.js → chunk-VZAFWPAY.js} +0 -0
  462. /package/dist/{chunk-VJSDGDNS.js → chunk-WSQASFLP.js} +0 -0
  463. /package/dist/{chunk-FZQWVQJG.js → chunk-YO4BO2CL.js} +0 -0
  464. /package/dist/{core-memory-6WBNT2HL.js → core-memory-7I7RE2SJ.js} +0 -0
  465. /package/dist/{entity-boost-MT336PI3.js → entity-boost-3UWGXG5O.js} +0 -0
  466. /package/dist/{memory-isolation-EJ45IXBH.js → memory-isolation-2TUZ7Z4K.js} +0 -0
  467. /package/dist/{message-queue-client-MVBTYCSV.js → message-queue-client-XQN7QABL.js} +0 -0
  468. /package/dist/{webhook-pipe-UQSEHFPO.js → webhook-pipe-W2FVXY7M.js} +0 -0
  469. /package/dist/{wiki-acl-WXRJGZ5Q.js → wiki-acl-IBQLLBTS.js} +0 -0
  470. /package/dist/{wiki-client-AXNM5FWY.js → wiki-client-W65IOSWF.js} +0 -0
@@ -1,8 +1,12 @@
1
+ import {
2
+ readImageLabel
3
+ } from "./chunk-GHR4ZPJ7.js";
1
4
  import {
2
5
  clearDeployStalenessAlert,
6
+ deployStalenessAlertPath,
3
7
  evaluateDeployStaleness,
4
8
  writeDeployStalenessAlert
5
- } from "./chunk-TX3Q2GIL.js";
9
+ } from "./chunk-C5HUMAA7.js";
6
10
  import {
7
11
  REVISION_LABEL,
8
12
  VERSION_LABEL,
@@ -11,77 +15,17 @@ import {
11
15
  } from "./chunk-L25OAFOU.js";
12
16
  import {
13
17
  loadLicense
14
- } from "./chunk-DKBPCOU4.js";
18
+ } from "./chunk-L6RXG36J.js";
15
19
 
16
20
  // src/lib/stack-update.ts
17
- import { execFileSync, spawnSync as spawnSync2 } from "child_process";
21
+ import { execFileSync, spawnSync } from "child_process";
18
22
  import { createVerify, randomBytes, verify as verifySignature } from "crypto";
19
23
  import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
20
24
  import http from "http";
21
25
  import https from "https";
26
+ import net from "net";
22
27
  import path from "path";
23
28
  import { fileURLToPath } from "url";
24
-
25
- // scripts/lib/resolve-image-labels.mjs
26
- import { spawnSync } from "child_process";
27
- var defaultRunner = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8", timeout: 6e4 });
28
- function repositoryOf(ref) {
29
- if (typeof ref !== "string") return ref;
30
- const at = ref.indexOf("@");
31
- if (at !== -1) return ref.slice(0, at);
32
- const slash = ref.lastIndexOf("/");
33
- const colon = ref.lastIndexOf(":");
34
- if (colon > slash) return ref.slice(0, colon);
35
- return ref;
36
- }
37
- function amd64ChildDigest(rawJson) {
38
- let doc;
39
- try {
40
- doc = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
41
- } catch {
42
- return void 0;
43
- }
44
- if (!doc || !Array.isArray(doc.manifests)) return void 0;
45
- const isLinuxAmd64 = (m) => m?.platform?.os === "linux" && m?.platform?.architecture === "amd64";
46
- const anyAmd64 = (m) => m?.platform?.architecture === "amd64" && m?.platform?.os !== "unknown";
47
- const pick = doc.manifests.find(isLinuxAmd64) ?? doc.manifests.find(anyAmd64);
48
- const digest = pick?.digest;
49
- return typeof digest === "string" && digest.length > 0 ? digest : void 0;
50
- }
51
- function resolvePlatformRef(imageRef, { runner } = {}) {
52
- const run = runner ?? defaultRunner;
53
- const res = run("docker", ["buildx", "imagetools", "inspect", imageRef, "--raw"]);
54
- if (!res || res.status !== 0) return imageRef;
55
- const raw = (res.stdout ?? "").toString();
56
- const childDigest = amd64ChildDigest(raw);
57
- if (!childDigest) return imageRef;
58
- return `${repositoryOf(imageRef)}@${childDigest}`;
59
- }
60
- function readLabelOff(platformRef, label, { runner } = {}) {
61
- const run = runner ?? defaultRunner;
62
- const res = run("docker", [
63
- "buildx",
64
- "imagetools",
65
- "inspect",
66
- platformRef,
67
- "--format",
68
- `{{ index .Config.Labels "${label}" }}`
69
- ]);
70
- if (!res || res.status !== 0) return void 0;
71
- const out = (res.stdout ?? "").toString().trim();
72
- return out === "" || out === "<no value>" ? void 0 : out;
73
- }
74
- function resolveImageLabels(imageRef, labels, { runner } = {}) {
75
- const platformRef = resolvePlatformRef(imageRef, { runner });
76
- const out = {};
77
- for (const label of labels) out[label] = readLabelOff(platformRef, label, { runner });
78
- return out;
79
- }
80
- function readImageLabel(imageRef, label, opts = {}) {
81
- return resolveImageLabels(imageRef, [label], opts)[label];
82
- }
83
-
84
- // src/lib/stack-update.ts
85
29
  var SLOW_SERVICE_HEALTH_DEFAULTS = {
86
30
  // Frappe `bench migrate` on every boot can take well over the 180s default.
87
31
  "exe-erp": 900
@@ -239,6 +183,69 @@ function isCloudflaredTunnelMisconfigured(composeFile, envFile) {
239
183
  }
240
184
  return true;
241
185
  }
186
+ var INGRESS_DEFAULT_PORTS = { http: 80, https: 443 };
187
+ function stripYamlScalar(raw) {
188
+ let v = raw.trim();
189
+ v = v.replace(/\s+#.*$/, "").trim();
190
+ if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
191
+ v = v.slice(1, -1);
192
+ }
193
+ return v.trim();
194
+ }
195
+ function parseCloudflaredIngressTargets(configYml) {
196
+ const lines = configYml.split(/\r?\n/);
197
+ const targets = [];
198
+ let inIngress = false;
199
+ let ingressIndent = -1;
200
+ let currentHostname;
201
+ const flushService = (serviceRaw) => {
202
+ const service = stripYamlScalar(serviceRaw);
203
+ const m = /^([a-z][a-z0-9+.\-]*):\/\/(?:\[([^\]]+)\]|([^/:\s]+))(?::(\d+))?/i.exec(service);
204
+ if (!m) return;
205
+ const scheme = (m[1] ?? "").toLowerCase();
206
+ const host = m[2] ?? m[3] ?? "";
207
+ const port = m[4] ? Number(m[4]) : INGRESS_DEFAULT_PORTS[scheme];
208
+ if (!host) return;
209
+ if (port === void 0 || !Number.isFinite(port) || port <= 0 || port > 65535) return;
210
+ targets.push({ hostname: currentHostname, service, host, port });
211
+ };
212
+ for (const raw of lines) {
213
+ if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
214
+ const indent = raw.length - raw.replace(/^\s*/, "").length;
215
+ if (!inIngress) {
216
+ if (/^ingress\s*:\s*$/.test(raw.trim())) {
217
+ inIngress = true;
218
+ ingressIndent = indent;
219
+ }
220
+ continue;
221
+ }
222
+ if (indent <= ingressIndent && !raw.trim().startsWith("-")) {
223
+ inIngress = false;
224
+ continue;
225
+ }
226
+ const t = raw.trim();
227
+ if (t.startsWith("-")) {
228
+ currentHostname = void 0;
229
+ const item = t.replace(/^-\s*/, "");
230
+ const hm = /^hostname\s*:\s*(.+)$/i.exec(item);
231
+ const sm = /^service\s*:\s*(.+)$/i.exec(item);
232
+ if (hm) currentHostname = stripYamlScalar(hm[1] ?? "");
233
+ else if (sm) flushService(sm[1] ?? "");
234
+ } else {
235
+ const hm = /^hostname\s*:\s*(.+)$/i.exec(t);
236
+ const sm = /^service\s*:\s*(.+)$/i.exec(t);
237
+ if (hm) currentHostname = stripYamlScalar(hm[1] ?? "");
238
+ else if (sm) flushService(sm[1] ?? "");
239
+ }
240
+ }
241
+ return targets;
242
+ }
243
+ async function findUnreachableIngressTargets(targets, probe) {
244
+ const results = await Promise.all(
245
+ targets.map(async (t) => ({ t, reachable: await probe(t.host, t.port) }))
246
+ );
247
+ return results.filter((r) => !r.reachable).map((r) => r.t);
248
+ }
242
249
  function resolveTransitiveDeps(selected, depsMap) {
243
250
  const resolved = new Set(selected);
244
251
  const queue = [...selected];
@@ -448,9 +455,10 @@ async function rollbackStackUpdate(options) {
448
455
  const rollbackEnvRaw = readFileSync(rollbackEnv, "utf8");
449
456
  const rollbackEnvMap = parseEnv(rollbackEnvRaw);
450
457
  const preUpdateImageDefaults = lockData?.preUpdateImageDefaults ?? {};
458
+ let processSyncKeys = [];
451
459
  if (scopedServiceNames.length > 0 && lockData?.services) {
452
460
  const scopedEnvKeys = scopedServiceNames.map((serviceName) => lockData.services[serviceName].env).filter((key) => Boolean(key));
453
- const mutatedEnvKeys = lockData.mutatedEnvKeys ?? [];
461
+ const mutatedEnvKeys = options.isolateEnvScope ? [] : lockData.mutatedEnvKeys ?? [];
454
462
  const restoreEnvKeys = [.../* @__PURE__ */ new Set([...scopedEnvKeys, ...mutatedEnvKeys])];
455
463
  const scopedUpdates = {};
456
464
  const scopedRemovals = [];
@@ -478,6 +486,7 @@ async function rollbackStackUpdate(options) {
478
486
  }
479
487
  const patched = patchEnv(readFileSync(options.envFile, "utf8"), scopedUpdates);
480
488
  writeSecretFile(options.envFile, removeEnvKeys(patched, scopedRemovals));
489
+ processSyncKeys = restoreEnvKeys;
481
490
  } else {
482
491
  console.log(`[stack-update] Restoring env from ${rollbackEnv}`);
483
492
  writeSecretFile(options.envFile, rollbackEnvRaw);
@@ -500,6 +509,50 @@ async function rollbackStackUpdate(options) {
500
509
  writeSecretFile(options.envFile, patchEnv(readFileSync(options.envFile, "utf8"), repins));
501
510
  }
502
511
  }
512
+ processSyncKeys = Object.values(lockData?.services ?? {}).map((svc) => svc.env).filter((key) => Boolean(key));
513
+ }
514
+ {
515
+ const imagePinKeys = new Set(
516
+ Object.values(lockData?.services ?? {}).map((svc) => svc.env).filter((key) => Boolean(key))
517
+ );
518
+ const restored = syncImagePinsWithProcessEnv(
519
+ readFileSync(options.envFile, "utf8"),
520
+ processSyncKeys.filter((key) => imagePinKeys.has(key))
521
+ );
522
+ if (restored.pinned.length > 0 || restored.cleared.length > 0) {
523
+ console.log(
524
+ `[stack-update] ROLLBACK: re-synced image ref(s) in the deploy environment (restored: ${restored.pinned.join(", ") || "none"}; cleared: ${restored.cleared.join(", ") || "none"})`
525
+ );
526
+ }
527
+ }
528
+ {
529
+ const restoredEnvRaw = readFileSync(options.envFile, "utf8");
530
+ const restoredEnvMap = parseEnv(restoredEnvRaw);
531
+ const binUpdates = {};
532
+ const binRemovals = [];
533
+ for (const serviceKey of ["monitorHub", "monitorAgent"]) {
534
+ const binKey = MONITOR_HEALTH_BIN_ENV[serviceKey];
535
+ const imageEnvKey = MONITOR_IMAGE_TAG_ENV[serviceKey];
536
+ const rollbackImage = restoredEnvMap.get(imageEnvKey)?.trim();
537
+ if (rollbackImage) {
538
+ binUpdates[binKey] = monitorHealthBinForImage(serviceKey, rollbackImage);
539
+ } else if (restoredEnvMap.has(binKey)) {
540
+ binRemovals.push(binKey);
541
+ }
542
+ }
543
+ if (Object.keys(binUpdates).length > 0 || binRemovals.length > 0) {
544
+ const patched = patchEnv(restoredEnvRaw, binUpdates);
545
+ writeSecretFile(options.envFile, removeEnvKeys(patched, binRemovals));
546
+ }
547
+ const restoredBins = syncImagePinsWithProcessEnv(
548
+ readFileSync(options.envFile, "utf8"),
549
+ Object.values(MONITOR_HEALTH_BIN_ENV)
550
+ );
551
+ if (restoredBins.pinned.length > 0 || restoredBins.cleared.length > 0) {
552
+ console.log(
553
+ `[stack-update] ROLLBACK: re-synced monitor healthcheck bin path(s) in the deploy environment (restored: ${restoredBins.pinned.join(", ") || "none"}; cleared: ${restoredBins.cleared.join(", ") || "none"})`
554
+ );
555
+ }
503
556
  }
504
557
  const composeArgs = composeArgsForStack(options.composeFile, options.envFile);
505
558
  if (lockData?.services) {
@@ -519,8 +572,10 @@ async function rollbackStackUpdate(options) {
519
572
  }
520
573
  console.log("[stack-update] Restarting services with previous images\u2026");
521
574
  if (scopedServiceNames.length > 0 && lockData?.services) {
522
- for (const serviceName of scopedServiceNames) {
523
- const composeServiceName = lockData.services[serviceName]?.composeService ?? serviceName;
575
+ const scopedComposeServices = expandComposeServicesWithSidecars(
576
+ scopedServiceNames.map((serviceName) => lockData.services[serviceName]?.composeService ?? serviceName)
577
+ );
578
+ for (const composeServiceName of scopedComposeServices) {
524
579
  exec("docker", [...composeArgs, "up", "-d", "--remove-orphans", "--no-deps", composeServiceName]);
525
580
  }
526
581
  } else {
@@ -552,6 +607,43 @@ async function rollbackStackUpdate(options) {
552
607
  );
553
608
  }
554
609
  }
610
+ const rolledBackComposeServices = scopedServiceNames.length > 0 && lockData?.services ? [...new Set(expandComposeServicesWithSidecars(scopedServiceNames.map((name) => lockData.services[name]?.composeService ?? name)))] : [...new Set(Object.entries(lockData?.services ?? {}).map(([name, svc]) => svc.composeService ?? name))];
611
+ try {
612
+ await verifyComposeServicesRunning(
613
+ composeArgs,
614
+ rolledBackComposeServices,
615
+ options.healthTimeoutSecs ?? 180,
616
+ options.composePsCapture
617
+ );
618
+ if (rolledBackComposeServices.length > 0) {
619
+ console.log(`[stack-update] \u2713 Post-rollback health verified: ${rolledBackComposeServices.length} service(s) running & healthy.`);
620
+ }
621
+ } catch (healthErr) {
622
+ const healthReason = healthErr instanceof Error ? healthErr.message : String(healthErr);
623
+ const runningVersion = lockData?.previousVersion ?? lockData?.stackVersion ?? "unknown";
624
+ const attemptedVersion = lockData?.targetVersion ?? lockData?.stackVersion ?? "unknown";
625
+ writeDeployStalenessAlert({
626
+ detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
627
+ runningVersion,
628
+ targetVersion: attemptedVersion,
629
+ attemptedAt: lockData?.updatedAt,
630
+ failedServices: rolledBackComposeServices,
631
+ totalServices: rolledBackComposeServices.length,
632
+ sampleError: healthReason.slice(0, 240),
633
+ reason: `ROLLBACK LANDED UNHEALTHY: the stack was rolled back toward ${runningVersion} but the rolled-back service(s) did not come back running & healthy. The rollback did NOT succeed \u2014 production is likely degraded and needs manual attention (bug 879aad12).`,
634
+ recovery: {
635
+ inspect: "docker compose ps --all \u2014 inspect the rolled-back service(s) that failed to become healthy.",
636
+ retry: "Fix the underlying cause, then re-run the deploy or rollback; a healthy landing clears this alert.",
637
+ acknowledge: `Delete ${deployStalenessAlertPath()} to silence it deliberately.`
638
+ }
639
+ });
640
+ await postDeployAudit(options, "rollback_unhealthy", attemptedVersion, runningVersion, healthReason, {
641
+ failedServices: rolledBackComposeServices
642
+ });
643
+ throw new Error(
644
+ `Rollback restarted services but they did not become healthy \u2014 rollback FAILED (bug 879aad12): ${healthReason}`
645
+ );
646
+ }
555
647
  if (!options.serviceName && lockData) {
556
648
  writeStackUpdateLock(lockFilePath, {
557
649
  ...lockData,
@@ -606,7 +698,7 @@ function isExecFormMigration(migrationCommand) {
606
698
  }
607
699
  function isComposeServiceRunning(composeArgs, composeServiceName) {
608
700
  try {
609
- const result = spawnSync2("docker", [...composeArgs, "ps", "--quiet", composeServiceName], {
701
+ const result = spawnSync("docker", [...composeArgs, "ps", "--quiet", composeServiceName], {
610
702
  encoding: "utf8",
611
703
  stdio: ["pipe", "pipe", "pipe"],
612
704
  timeout: 1e4
@@ -638,6 +730,20 @@ function expandComposeServicesWithSidecars(primaryComposeServices) {
638
730
  }
639
731
  return expanded;
640
732
  }
733
+ function resolveOwningManifestService(composeService, services) {
734
+ const direct = Object.entries(services).find(
735
+ ([name, svc]) => name === composeService || (svc.composeService ?? name) === composeService
736
+ );
737
+ if (direct) return direct[0];
738
+ const primary = Object.entries(COMPOSE_SIDECARS).find(([, sidecars]) => sidecars.includes(composeService))?.[0];
739
+ if (primary) {
740
+ const owner = Object.entries(services).find(
741
+ ([name, svc]) => name === primary || (svc.composeService ?? name) === primary
742
+ );
743
+ if (owner) return owner[0];
744
+ }
745
+ return composeService;
746
+ }
641
747
  function cloneReleaseWithServices(release, serviceNames) {
642
748
  const wanted = new Set(serviceNames);
643
749
  const services = {};
@@ -896,7 +1002,18 @@ function parseRequiredEnvRefs(composeRaw) {
896
1002
  }
897
1003
  return [...refs.entries()].map(([key, message]) => ({ key, message }));
898
1004
  }
899
- function deriveRequiredEnvValue(key, env) {
1005
+ function resolveManifestImagePin(key, release) {
1006
+ if (!release) return void 0;
1007
+ for (const service of Object.values(release.services ?? {})) {
1008
+ if (service?.env !== key) continue;
1009
+ const image = service.image?.trim();
1010
+ if (image) return image;
1011
+ }
1012
+ return void 0;
1013
+ }
1014
+ function deriveRequiredEnvValue(key, env, release) {
1015
+ const manifestPin = resolveManifestImagePin(key, release);
1016
+ if (manifestPin) return manifestPin;
900
1017
  const nonEmpty = (k) => {
901
1018
  const v = env.get(k);
902
1019
  return v && v.trim() !== "" ? v.trim() : void 0;
@@ -935,14 +1052,34 @@ function deriveRequiredEnvValue(key, env) {
935
1052
  return void 0;
936
1053
  }
937
1054
  }
938
- function detectMissingRequiredEnvVars(composeRaw, envRaw) {
1055
+ function requiredImagePinKeys(composeRaw, release) {
1056
+ return parseRequiredEnvRefs(composeRaw).map((ref) => ref.key).filter((key) => resolveManifestImagePin(key, release) !== void 0);
1057
+ }
1058
+ function syncImagePinsWithProcessEnv(envRaw, keys, processEnv = process.env) {
1059
+ const env = parseEnv(envRaw);
1060
+ const pinned = [];
1061
+ const cleared = [];
1062
+ for (const key of new Set(keys)) {
1063
+ const value = env.get(key)?.trim();
1064
+ if (value) {
1065
+ if (processEnv[key] === value) continue;
1066
+ processEnv[key] = value;
1067
+ pinned.push(key);
1068
+ } else if (processEnv[key] !== void 0) {
1069
+ delete processEnv[key];
1070
+ cleared.push(key);
1071
+ }
1072
+ }
1073
+ return { pinned, cleared };
1074
+ }
1075
+ function detectMissingRequiredEnvVars(composeRaw, envRaw, release) {
939
1076
  const env = parseEnv(envRaw);
940
1077
  const derived = {};
941
1078
  const unresolvable = [];
942
1079
  for (const ref of parseRequiredEnvRefs(composeRaw)) {
943
1080
  const current = env.get(ref.key);
944
1081
  if (current !== void 0 && current.trim() !== "") continue;
945
- const value = deriveRequiredEnvValue(ref.key, env);
1082
+ const value = deriveRequiredEnvValue(ref.key, env, release);
946
1083
  if (value !== void 0 && value.trim() !== "") {
947
1084
  derived[ref.key] = value;
948
1085
  } else {
@@ -951,6 +1088,32 @@ function detectMissingRequiredEnvVars(composeRaw, envRaw) {
951
1088
  }
952
1089
  return { derived, unresolvable };
953
1090
  }
1091
+ var MONITOR_HEALTH_BIN_ENV = {
1092
+ monitorHub: "MONITOR_HUB_HEALTH_BIN",
1093
+ monitorAgent: "MONITOR_AGENT_HEALTH_BIN"
1094
+ };
1095
+ var MONITOR_IMAGE_TAG_ENV = {
1096
+ monitorHub: "MONITOR_HUB_IMAGE_TAG",
1097
+ monitorAgent: "MONITOR_AGENT_IMAGE_TAG"
1098
+ };
1099
+ var STOCK_BESZEL_MONITOR_VERSIONS = /* @__PURE__ */ new Set(["0.9.5"]);
1100
+ function monitorImageVersion(imageRef) {
1101
+ const noDigest = imageRef.split("@")[0] ?? imageRef;
1102
+ const lastSlash = noDigest.lastIndexOf("/");
1103
+ const lastColon = noDigest.lastIndexOf(":");
1104
+ const tag = lastColon > lastSlash ? noDigest.slice(lastColon + 1) : "";
1105
+ const match = /^v?(\d+\.\d+\.\d+)/.exec(tag);
1106
+ return match ? match[1] : void 0;
1107
+ }
1108
+ function isStockBeszelMonitorImage(imageRef) {
1109
+ const version = monitorImageVersion(imageRef);
1110
+ return version !== void 0 && STOCK_BESZEL_MONITOR_VERSIONS.has(version);
1111
+ }
1112
+ function monitorHealthBinForImage(serviceKey, imageRef) {
1113
+ const stock = isStockBeszelMonitorImage(imageRef);
1114
+ if (serviceKey === "monitorHub") return stock ? "/beszel" : "/app/beszel";
1115
+ return stock ? "/agent" : "/app/beszel-agent";
1116
+ }
954
1117
  function createStackUpdatePlan(manifest, envRaw, targetVersion, serviceNames, selection, composeRaw) {
955
1118
  const version = targetVersion ?? manifest.latest;
956
1119
  const manifestRelease = manifest.stacks[version];
@@ -967,6 +1130,16 @@ function createStackUpdatePlan(manifest, envRaw, targetVersion, serviceNames, se
967
1130
  changes.push({ key: service.env, before, after, service: serviceName });
968
1131
  }
969
1132
  }
1133
+ for (const serviceKey of ["monitorHub", "monitorAgent"]) {
1134
+ const monitorService = release.services[serviceKey];
1135
+ if (!monitorService?.image) continue;
1136
+ const binKey = MONITOR_HEALTH_BIN_ENV[serviceKey];
1137
+ const binBefore = env.get(binKey);
1138
+ const binAfter = monitorHealthBinForImage(serviceKey, monitorService.image);
1139
+ if (binBefore !== binAfter) {
1140
+ changes.push({ key: binKey, before: binBefore, after: binAfter, service: serviceKey });
1141
+ }
1142
+ }
970
1143
  return {
971
1144
  manifest,
972
1145
  release,
@@ -1021,6 +1194,18 @@ function stripImageDigest(image) {
1021
1194
  function imagePullRef(image) {
1022
1195
  return isThirdPartyImage(image) ? stripImageDigest(image) : image;
1023
1196
  }
1197
+ function manifestInspectRef(image) {
1198
+ const trimmed = image.trim();
1199
+ const digest = imageDigest(trimmed);
1200
+ if (!digest) return trimmed;
1201
+ const name = stripImageDigest(trimmed);
1202
+ const lastSlash = name.lastIndexOf("/");
1203
+ const ref = lastSlash >= 0 ? name.slice(lastSlash + 1) : name;
1204
+ const colon = ref.indexOf(":");
1205
+ if (colon < 0) return trimmed;
1206
+ const tagless = lastSlash >= 0 ? name.slice(0, lastSlash + 1) + ref.slice(0, colon) : ref.slice(0, colon);
1207
+ return `${tagless}@${digest}`;
1208
+ }
1024
1209
  function isMutableTag(tag) {
1025
1210
  if (!tag) return true;
1026
1211
  return /^(latest|stable|main|master|edge|nightly|dev|develop|rolling|current)$/i.test(tag);
@@ -1173,7 +1358,7 @@ function collectImageAvailabilityIssues(plan) {
1173
1358
  let inspectOutput = "";
1174
1359
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1175
1360
  try {
1176
- const result = spawnSync2("docker", ["manifest", "inspect", image, "--verbose"], {
1361
+ const result = spawnSync("docker", ["manifest", "inspect", manifestInspectRef(image), "--verbose"], {
1177
1362
  stdio: ["ignore", "pipe", "pipe"],
1178
1363
  timeout: 3e4
1179
1364
  });
@@ -1188,7 +1373,7 @@ function collectImageAvailabilityIssues(plan) {
1188
1373
  console.log(
1189
1374
  `[stack-update] Image ${image} not yet available on proxy (attempt ${attempt}/${maxAttempts}), retrying in ${delayMs / 1e3}s\u2026 (bug d11c9809)`
1190
1375
  );
1191
- spawnSync2("sleep", [String(delayMs / 1e3)], { stdio: "ignore" });
1376
+ spawnSync("sleep", [String(delayMs / 1e3)], { stdio: "ignore" });
1192
1377
  }
1193
1378
  } catch {
1194
1379
  inspected = true;
@@ -1253,6 +1438,165 @@ ${details}
1253
1438
  Emergency override requires --break-glass <reason> and writes an audit file.`
1254
1439
  );
1255
1440
  }
1441
+ function parseComposeServiceImages(composeRaw) {
1442
+ const images = /* @__PURE__ */ new Map();
1443
+ let inServices = false;
1444
+ let current = null;
1445
+ for (const line of composeRaw.split(/\r?\n/)) {
1446
+ if (!line.trim() || line.trim().startsWith("#")) continue;
1447
+ const indent = line.length - line.trimStart().length;
1448
+ const trimmed = line.trim();
1449
+ if (indent === 0) {
1450
+ inServices = /^services:\s*$/.test(trimmed);
1451
+ current = null;
1452
+ continue;
1453
+ }
1454
+ if (!inServices) continue;
1455
+ if (indent === 2 && /^[a-zA-Z0-9_.-]+:\s*$/.test(trimmed)) {
1456
+ current = trimmed.replace(/:\s*$/, "");
1457
+ continue;
1458
+ }
1459
+ if (!current) continue;
1460
+ const match = trimmed.match(/^image:\s*(.+?)\s*(?:#.*)?$/);
1461
+ if (match) images.set(current, match[1].trim().replace(/^['"]|['"]$/g, ""));
1462
+ }
1463
+ return images;
1464
+ }
1465
+ function imageDigest(image) {
1466
+ return image.match(/@(sha256:[a-f0-9]{64})$/i)?.[1]?.toLowerCase();
1467
+ }
1468
+ function envRefsInImageExpression(expr) {
1469
+ return [...expr.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1]);
1470
+ }
1471
+ function manifestCoveredComposeServices(release, composeImages, env) {
1472
+ const covered = /* @__PURE__ */ new Set();
1473
+ const pinVars = /* @__PURE__ */ new Map();
1474
+ const manifestRefs = /* @__PURE__ */ new Set();
1475
+ const manifestDigests = /* @__PURE__ */ new Set();
1476
+ for (const [name, service] of Object.entries(release.services)) {
1477
+ const composeName = service.composeService ?? name;
1478
+ covered.add(name);
1479
+ covered.add(composeName);
1480
+ for (const sidecar of COMPOSE_SIDECARS[composeName] ?? []) covered.add(sidecar);
1481
+ if (service.env) pinVars.set(service.env, composeName);
1482
+ const image = service.image.trim();
1483
+ manifestRefs.add(image);
1484
+ manifestRefs.add(stripImageDigest(image));
1485
+ const digest = imageDigest(image);
1486
+ if (digest) manifestDigests.add(digest);
1487
+ }
1488
+ for (const [composeService, expr] of composeImages) {
1489
+ if (covered.has(composeService)) continue;
1490
+ if (envRefsInImageExpression(expr).some((key) => pinVars.has(key))) {
1491
+ covered.add(composeService);
1492
+ continue;
1493
+ }
1494
+ const resolved = resolveComposeImageExpression(expr, env);
1495
+ if (!resolved) continue;
1496
+ const digest = imageDigest(resolved);
1497
+ if (digest && manifestDigests.has(digest)) covered.add(composeService);
1498
+ else if (manifestRefs.has(resolved)) covered.add(composeService);
1499
+ }
1500
+ return covered;
1501
+ }
1502
+ function collectManifestDeliveryIssues(input) {
1503
+ const issues = [];
1504
+ const env = parseEnv(input.envRaw);
1505
+ const composeImages = parseComposeServiceImages(input.composeRaw);
1506
+ const covered = manifestCoveredComposeServices(input.release, composeImages, env);
1507
+ const report = (service, origin, detail) => {
1508
+ issues.push({
1509
+ service,
1510
+ origin,
1511
+ reason: "absent-from-manifest",
1512
+ message: `${origin === "host" ? "Running service" : "Compose service"} "${service}" is NOT in signed stack manifest ${input.targetVersion}. ${detail} An image the signed manifest does not pin is not deployable. Publish it to the manifest (exe-os stack-release) and re-cut the manifest, or remove the service from the host.`
1513
+ });
1514
+ };
1515
+ for (const [composeService, expr] of composeImages) {
1516
+ if (covered.has(composeService)) continue;
1517
+ const resolved = resolveComposeImageExpression(expr, env);
1518
+ if (!resolved) continue;
1519
+ report(composeService, "compose", `It would deploy ${resolved}, which no manifest entry pins.`);
1520
+ }
1521
+ for (const hostService of input.hostServices ?? []) {
1522
+ if (covered.has(hostService)) continue;
1523
+ report(hostService, "host", "It is running on this host with no manifest entry behind it.");
1524
+ }
1525
+ for (const [name, service] of Object.entries(input.release.services)) {
1526
+ const image = service.image.trim();
1527
+ if (imageDigest(image)) continue;
1528
+ if (isThirdPartyImage(image)) continue;
1529
+ issues.push({
1530
+ service: service.composeService ?? name,
1531
+ origin: "compose",
1532
+ reason: "unpinned-image",
1533
+ message: `Manifest ${input.targetVersion} entry "${name}" pins image ${image} with no @sha256: digest. A tag can be repointed at different bytes after review, and a tag whose release failed does not resolve at all. Re-cut the manifest with a resolved digest for ${name}.`
1534
+ });
1535
+ }
1536
+ return issues;
1537
+ }
1538
+ function manifestDeliveryBreakGlassServices(explicit) {
1539
+ const raw = explicit ?? process.env.EXE_STACK_ALLOW_UNMANIFESTED_SERVICES ?? "";
1540
+ return new Set(raw.split(",").map((s) => s.trim()).filter(Boolean));
1541
+ }
1542
+ function manifestDeliveryBreakGlassBanner(bypassed) {
1543
+ const border = "=".repeat(72);
1544
+ return [
1545
+ "",
1546
+ border,
1547
+ " !!! SIGNED-MANIFEST DELIVERY GATE BYPASSED !!!",
1548
+ border,
1549
+ " These services are being deployed WITHOUT being pinned by the signed",
1550
+ " stack manifest. Nobody vouched for the bytes they will run:",
1551
+ ...bypassed.map((issue) => ` - ${issue.service} (${issue.origin}): ${issue.reason}`),
1552
+ "",
1553
+ " Authorised by --allow-unmanifested-services / EXE_STACK_ALLOW_UNMANIFESTED_SERVICES.",
1554
+ " Break-glass only. Publish these to the signed manifest and remove the override.",
1555
+ border,
1556
+ ""
1557
+ ].join("\n");
1558
+ }
1559
+ function assertSignedManifestDeliveryGate(input) {
1560
+ const warn = input.warn ?? ((message) => console.warn(message));
1561
+ if (!input.release || typeof input.release.services !== "object" || input.release.services === null) {
1562
+ throw new Error(
1563
+ `Signed-manifest delivery gate: stack release ${input.targetVersion} has no services block. Refusing to deploy \u2014 a manifest that cannot be read cannot pin anything.`
1564
+ );
1565
+ }
1566
+ if (Object.keys(input.release.services).length === 0) {
1567
+ throw new Error(
1568
+ `Signed-manifest delivery gate: stack release ${input.targetVersion} pins ZERO services. Refusing to deploy \u2014 an empty manifest release vouches for nothing.`
1569
+ );
1570
+ }
1571
+ if (input.hostServicesUnavailable) {
1572
+ warn(
1573
+ `[stack-update] \u26A0\uFE0F Signed-manifest delivery gate: could NOT enumerate running containers on this host. Services running outside compose cannot be checked against manifest ${input.targetVersion} on this run. The compose-declared half of the gate still applies.`
1574
+ );
1575
+ }
1576
+ const issues = collectManifestDeliveryIssues({
1577
+ release: input.release,
1578
+ targetVersion: input.targetVersion,
1579
+ composeRaw: input.composeRaw,
1580
+ envRaw: input.envRaw,
1581
+ hostServices: input.hostServices
1582
+ });
1583
+ if (issues.length === 0) return;
1584
+ const allowed = manifestDeliveryBreakGlassServices(input.allowUnmanifestedServices);
1585
+ const bypassed = issues.filter((i) => i.reason === "absent-from-manifest" && allowed.has(i.service));
1586
+ const blocking = issues.filter((i) => !bypassed.includes(i));
1587
+ if (bypassed.length > 0) warn(manifestDeliveryBreakGlassBanner(bypassed));
1588
+ if (blocking.length === 0) return;
1589
+ const details = blocking.map((i) => `- [${i.reason}] ${i.message}`).join("\n");
1590
+ const absent = blocking.filter((i) => i.reason === "absent-from-manifest").map((i) => i.service);
1591
+ throw new Error(
1592
+ `Signed-manifest delivery gate REFUSED this deploy (stack ${input.targetVersion}).
1593
+ Every update goes to the signed stack manifest FIRST, then deploys. An image the manifest does not pin by digest is not deployable.
1594
+ ${details}
1595
+ ` + (absent.length > 0 ? `Emergency override for the ${absent.length} unmanifested service(s), which must be named explicitly:
1596
+ exe-os stack-update --allow-unmanifested-services ${absent.join(",")} --yes
1597
+ (or EXE_STACK_ALLOW_UNMANIFESTED_SERVICES=${absent.join(",")}). It is logged loudly at the point of use.` : `Re-cut the signed manifest with resolved digests, then re-run.`)
1598
+ );
1599
+ }
1256
1600
  function writeBreakGlassAudit(plan, issues, options) {
1257
1601
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
1258
1602
  const stamp = now().toISOString().replace(/[:.]/g, "-");
@@ -1352,7 +1696,7 @@ async function verifyManifestImagesAvailable(plan, options) {
1352
1696
  let ok = false;
1353
1697
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1354
1698
  try {
1355
- exec("docker", ["manifest", "inspect", image]);
1699
+ exec("docker", ["manifest", "inspect", manifestInspectRef(image)]);
1356
1700
  ok = true;
1357
1701
  break;
1358
1702
  } catch (err) {
@@ -1539,7 +1883,7 @@ A first-party image whose version label does not match its tag cannot be traced
1539
1883
  }
1540
1884
  var PROBE_TIMEOUT_MS = 1e4;
1541
1885
  function commandSucceeds(cmd, args = []) {
1542
- const res = spawnSync2(cmd, args, {
1886
+ const res = spawnSync(cmd, args, {
1543
1887
  stdio: "ignore",
1544
1888
  timeout: PROBE_TIMEOUT_MS,
1545
1889
  killSignal: "SIGKILL"
@@ -1547,7 +1891,7 @@ function commandSucceeds(cmd, args = []) {
1547
1891
  return res.status === 0;
1548
1892
  }
1549
1893
  function shellSucceeds(command) {
1550
- const res = spawnSync2("sh", ["-lc", command], {
1894
+ const res = spawnSync("sh", ["-lc", command], {
1551
1895
  stdio: "ignore",
1552
1896
  timeout: PROBE_TIMEOUT_MS,
1553
1897
  killSignal: "SIGKILL"
@@ -1953,7 +2297,7 @@ function hardenHost(exec) {
1953
2297
  return result;
1954
2298
  }
1955
2299
  function hardenUfw(exec, result) {
1956
- const whichUfw = spawnSync2("which", ["ufw"], { stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 });
2300
+ const whichUfw = spawnSync("which", ["ufw"], { stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 });
1957
2301
  const ufwInstalled = whichUfw.status === 0;
1958
2302
  if (!ufwInstalled) {
1959
2303
  if (!existsSync("/etc/os-release")) {
@@ -1978,7 +2322,7 @@ function hardenUfw(exec, result) {
1978
2322
  return;
1979
2323
  }
1980
2324
  }
1981
- const statusResult = spawnSync2("ufw", ["status", "verbose"], {
2325
+ const statusResult = spawnSync("ufw", ["status", "verbose"], {
1982
2326
  encoding: "utf8",
1983
2327
  stdio: ["pipe", "pipe", "pipe"],
1984
2328
  timeout: 1e4
@@ -2130,7 +2474,7 @@ function hardenSsh(exec, result) {
2130
2474
  }
2131
2475
  function areStackContainersRunning(composeFile, envFile) {
2132
2476
  try {
2133
- const result = spawnSync2("docker", [...composeArgsForStack(composeFile, envFile), "ps", "-q"], {
2477
+ const result = spawnSync("docker", [...composeArgsForStack(composeFile, envFile), "ps", "-q"], {
2134
2478
  stdio: ["pipe", "pipe", "pipe"],
2135
2479
  timeout: 15e3
2136
2480
  });
@@ -2174,7 +2518,7 @@ function assertBindMountsExist(stackDir) {
2174
2518
  function assertVolumesIntact() {
2175
2519
  const missing = [];
2176
2520
  for (const vol of CRITICAL_VOLUMES) {
2177
- const result = spawnSync2("docker", ["volume", "inspect", `exe-os_${vol}`], {
2521
+ const result = spawnSync("docker", ["volume", "inspect", `exe-os_${vol}`], {
2178
2522
  stdio: ["pipe", "pipe", "pipe"],
2179
2523
  timeout: 5e3,
2180
2524
  killSignal: "SIGKILL"
@@ -2190,13 +2534,13 @@ function assertVolumesIntact() {
2190
2534
  }
2191
2535
  }
2192
2536
  function warnLegacyDaemonVolume() {
2193
- const legacy = spawnSync2("docker", ["volume", "inspect", "exe-os_exed_data"], {
2537
+ const legacy = spawnSync("docker", ["volume", "inspect", "exe-os_exed_data"], {
2194
2538
  stdio: ["pipe", "pipe", "pipe"],
2195
2539
  timeout: 5e3,
2196
2540
  killSignal: "SIGKILL"
2197
2541
  // SIGTERM'd docker CLIs on a hung daemon linger as orphans
2198
2542
  });
2199
- const current = spawnSync2("docker", ["volume", "inspect", "exe-os_exe_os_data"], {
2543
+ const current = spawnSync("docker", ["volume", "inspect", "exe-os_exe_os_data"], {
2200
2544
  stdio: ["pipe", "pipe", "pipe"],
2201
2545
  timeout: 5e3,
2202
2546
  killSignal: "SIGKILL"
@@ -2378,7 +2722,7 @@ async function runStackUpdate(options) {
2378
2722
  });
2379
2723
  assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
2380
2724
  assertDeploymentScopeAllowed(plan, options.deploymentPersona ?? "customer");
2381
- const requiredEnvValidation = options.dryRun ? { derived: {}, unresolvable: [] } : detectMissingRequiredEnvVars(stagedCompose.raw, envRaw);
2725
+ const requiredEnvValidation = options.dryRun ? { derived: {}, unresolvable: [] } : detectMissingRequiredEnvVars(stagedCompose.raw, envRaw, targetRelease);
2382
2726
  const migratedEnvVars = requiredEnvValidation.derived;
2383
2727
  if (requiredEnvValidation.unresolvable.length > 0) {
2384
2728
  const lines = requiredEnvValidation.unresolvable.map((r) => ` - ${r.key}${r.message ? ` (${r.message})` : ""}`);
@@ -2392,6 +2736,18 @@ ${lines.join("\n")}`
2392
2736
  ...migratedEnvVars,
2393
2737
  ...Object.fromEntries(plan.changes.map((c) => [c.key, c.after]))
2394
2738
  });
2739
+ const processPinned = syncImagePinsWithProcessEnv(plannedEnvRaw, requiredImagePinKeys(stagedCompose.raw, targetRelease));
2740
+ if (processPinned.pinned.length > 0) {
2741
+ console.log(
2742
+ `[stack-update] Pinned validated image ref(s) into the deploy environment: ${processPinned.pinned.join(", ")}`
2743
+ );
2744
+ }
2745
+ const processHealthBins = syncImagePinsWithProcessEnv(plannedEnvRaw, Object.values(MONITOR_HEALTH_BIN_ENV));
2746
+ if (processHealthBins.pinned.length > 0 || processHealthBins.cleared.length > 0) {
2747
+ console.log(
2748
+ `[stack-update] Synced monitor healthcheck bin path(s) into the deploy environment: ${[...processHealthBins.pinned, ...processHealthBins.cleared].join(", ")}`
2749
+ );
2750
+ }
2395
2751
  assertProductionDeployGate(plan, plannedEnvRaw, composeRaw, {
2396
2752
  breakGlassReason: options.breakGlassReason,
2397
2753
  breakGlassAuditFile: options.breakGlassAuditFile,
@@ -2399,6 +2755,21 @@ ${lines.join("\n")}`
2399
2755
  now,
2400
2756
  envFile: options.envFile
2401
2757
  });
2758
+ {
2759
+ const hostState = listHostComposeServices(
2760
+ composeArgsForStack(options.composeFile, options.envFile),
2761
+ options.composePsCapture ?? defaultComposePsCapture
2762
+ );
2763
+ assertSignedManifestDeliveryGate({
2764
+ release: manifest.stacks[plan.targetVersion],
2765
+ targetVersion: plan.targetVersion,
2766
+ composeRaw,
2767
+ envRaw: plannedEnvRaw,
2768
+ hostServices: hostState.services,
2769
+ hostServicesUnavailable: hostState.unavailable,
2770
+ allowUnmanifestedServices: options.allowUnmanifestedServices
2771
+ });
2772
+ }
2402
2773
  const previousVersion = readCurrentStackVersion(lockFile);
2403
2774
  if (options.verifyImages) {
2404
2775
  const endPhase = startPhase("verify images");
@@ -2754,9 +3125,14 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2754
3125
  ...RESTART_ORDER.filter((service) => scopedComposeServices.includes(service)),
2755
3126
  ...scopedComposeServices.filter((service) => !RESTART_ORDER.includes(service))
2756
3127
  ];
2757
- const preSnapshot = spawnSync2("docker", ["ps", "--format", "json"], { encoding: "utf8", timeout: 1e4 });
3128
+ const preSnapshot = spawnSync("docker", ["ps", "--format", "json"], { encoding: "utf8", timeout: 1e4 });
2758
3129
  const preContainerCount = preSnapshot.stdout?.split("\n").filter(Boolean).length ?? 0;
2759
3130
  console.log(`[stack-update] Pre-update snapshot: ${preContainerCount} containers`);
3131
+ const bundledIsolation = Object.keys(plan.release.services).length > 1;
3132
+ const failedManifestServices = /* @__PURE__ */ new Set();
3133
+ const failedComposeServices = /* @__PURE__ */ new Set();
3134
+ const isolationRollbackErrors = [];
3135
+ const partialHealthWarnings = [];
2760
3136
  let restartedAnyService = false;
2761
3137
  progress.resetCounter(restartOrder.length);
2762
3138
  progress.startPhase("restart");
@@ -2765,15 +3141,18 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2765
3141
  restartIndex++;
2766
3142
  const endRestart = startPhase(`restart ${service}`);
2767
3143
  const manifestServiceName = Object.entries(plan.release.services).find(([name, svc]) => (svc.composeService ?? name) === service)?.[0] ?? service;
2768
- const psResult = spawnSync2("docker", [...composeArgs, "ps", "--quiet", service], {
3144
+ if (bundledIsolation && (failedComposeServices.has(service) || failedManifestServices.has(resolveOwningManifestService(service, plan.release.services)))) {
3145
+ continue;
3146
+ }
3147
+ const serviceDefined = options.composeServiceExists ? options.composeServiceExists(service) : spawnSync("docker", [...composeArgs, "ps", "--quiet", service], {
2769
3148
  encoding: "utf8",
2770
3149
  stdio: ["pipe", "pipe", "pipe"],
2771
3150
  timeout: 1e4
2772
- });
2773
- if (psResult.status !== 0) continue;
3151
+ }).status === 0;
3152
+ if (!serviceDefined) continue;
2774
3153
  progress.logServiceStep(restartIndex, service, "Restarting");
2775
3154
  if (service === "exe-wiki") {
2776
- const guard = spawnSync2(
3155
+ const guard = spawnSync(
2777
3156
  "docker",
2778
3157
  [...composeArgs, "run", "--rm", "--no-deps", "exe-wiki-schema-guard"],
2779
3158
  { encoding: "utf8", stdio: ["pipe", "inherit", "inherit"], timeout: 12e4 }
@@ -2788,19 +3167,19 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2788
3167
  restartedAnyService = true;
2789
3168
  updateServiceStatus(lockFile, manifestServiceName, "restarted", now);
2790
3169
  const maxWait = resolveServiceHealthTimeout(service, plan.release, options.healthTimeoutSecs);
2791
- let healthy = false;
2792
- for (let i = 0; i < maxWait; i++) {
2793
- const inspectResult = spawnSync2(
3170
+ const probeHealth = options.inspectContainerHealth ?? ((svc) => {
3171
+ const inspectResult = spawnSync(
2794
3172
  "docker",
2795
- ["inspect", "--format", "{{.State.Health.Status}}", service],
3173
+ ["inspect", "--format", "{{.State.Health.Status}}", svc],
2796
3174
  { encoding: "utf8", timeout: 5e3 }
2797
3175
  );
2798
- const status = inspectResult.stdout?.trim();
2799
- if (status === "healthy") {
2800
- healthy = true;
2801
- break;
2802
- }
2803
- if (status === "" || inspectResult.status !== 0) {
3176
+ if (inspectResult.status !== 0) return null;
3177
+ return inspectResult.stdout?.trim() ?? "";
3178
+ });
3179
+ let healthy = false;
3180
+ for (let i = 0; i < maxWait; i++) {
3181
+ const status = probeHealth(service);
3182
+ if (status === null || status === "" || status === "healthy") {
2804
3183
  healthy = true;
2805
3184
  break;
2806
3185
  }
@@ -2810,7 +3189,27 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2810
3189
  const failMsg = `Service ${service} failed health check after ${maxWait}s`;
2811
3190
  progress.logServiceFailure(service, failMsg, options.composeFile);
2812
3191
  updateServiceStatus(lockFile, manifestServiceName, "failed", now, failMsg);
2813
- throw new Error(`${failMsg} \u2014 aborting update`);
3192
+ if (!bundledIsolation) {
3193
+ throw new Error(`${failMsg} \u2014 aborting update`);
3194
+ }
3195
+ const owner = resolveOwningManifestService(service, plan.release.services);
3196
+ console.error(
3197
+ `[stack-update] ISOLATED ROLLBACK (bug a5e29168): ${service} failed its health gate \u2014 reverting only ${owner} and its sidecars; the rest of the batch stays deployed.`
3198
+ );
3199
+ try {
3200
+ await rollbackStackUpdate({ ...options, lockFile, serviceName: owner, isolateEnvScope: true });
3201
+ console.error(`[stack-update] Isolated rollback of ${owner} complete; batch continues.`);
3202
+ } catch (isoErr) {
3203
+ const isoReason = isoErr instanceof Error ? isoErr.message : String(isoErr);
3204
+ console.error(`[stack-update] Isolated rollback of ${owner} did not land healthy: ${isoReason}`);
3205
+ isolationRollbackErrors.push(`${owner}: ${isoReason}`);
3206
+ }
3207
+ failedManifestServices.add(owner);
3208
+ for (const cs of expandComposeServicesWithSidecars([plan.release.services[owner]?.composeService ?? owner])) {
3209
+ failedComposeServices.add(cs);
3210
+ }
3211
+ updateServiceStatus(lockFile, owner, "failed", now, failMsg);
3212
+ continue;
2814
3213
  }
2815
3214
  if (service === "exe-db") {
2816
3215
  applyPostgresInitSql(composeArgs, options.envFile, options.composeFile, exec);
@@ -2823,7 +3222,7 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2823
3222
  if (!restartedAnyService) {
2824
3223
  exec("docker", [...composeArgs, "up", "-d", "--remove-orphans"]);
2825
3224
  }
2826
- const postSnapshot = spawnSync2("docker", ["ps", "--format", "json"], { encoding: "utf8", timeout: 1e4 });
3225
+ const postSnapshot = spawnSync("docker", ["ps", "--format", "json"], { encoding: "utf8", timeout: 1e4 });
2827
3226
  const postContainerCount = postSnapshot.stdout?.split("\n").filter(Boolean).length ?? 0;
2828
3227
  console.log(`[stack-update] Post-update snapshot: ${postContainerCount} containers (was ${preContainerCount})`);
2829
3228
  if (postContainerCount < preContainerCount) {
@@ -2834,7 +3233,7 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2834
3233
  try {
2835
3234
  await verifyComposeServicesRunning(
2836
3235
  composeArgs,
2837
- restartOrder,
3236
+ restartOrder.filter((s) => !failedComposeServices.has(s)),
2838
3237
  options.healthTimeoutSecs ?? 180,
2839
3238
  options.composePsCapture
2840
3239
  );
@@ -2842,9 +3241,38 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2842
3241
  endPhase3();
2843
3242
  }
2844
3243
  }
3244
+ {
3245
+ const cfIngressConfigPath = path.join(path.dirname(options.composeFile), "cloudflared", "config.yml");
3246
+ const cfIngressConfigYml = existsSync(cfIngressConfigPath) ? readFileSync(cfIngressConfigPath, "utf8") : null;
3247
+ await verifyIngressTargetsReachable(
3248
+ cfIngressConfigYml,
3249
+ defaultIngressReachabilityProbe,
3250
+ makeDefaultComposeServiceProbe(composeArgs)
3251
+ );
3252
+ }
2845
3253
  const stackEnv = existsSync(options.envFile) ? parseEnv(readFileSync(options.envFile, "utf8")) : void 0;
2846
- await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3, stackEnv);
2847
- const releasePostMigrations = plan.release.migrations?.post;
3254
+ const partialRun = failedManifestServices.size > 0;
3255
+ if (partialRun) {
3256
+ console.error(
3257
+ `[stack-update] SKIPPING RELEASE-LEVEL POST-DEPLOY WORK (bug a5e29168): ${failedManifestServices.size} service(s) were rolled back in isolation (${[...failedManifestServices].join(", ")}), so the stack is mixed-version and has NOT fully reached ${plan.targetVersion}.
3258
+ Skipped: release post-migrations, post-deploy health gate, volume-integrity assert, post-deploy verification, host hardening. The release health-URL probe still runs for the services that stayed deployed, but only to report \u2014 it can no longer abort the batch.
3259
+ Re-run (or --resume) once the rolled-back service(s) are fixed so these run against a uniform ${plan.targetVersion} stack.`
3260
+ );
3261
+ }
3262
+ if (partialRun) {
3263
+ try {
3264
+ await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3, stackEnv, failedManifestServices);
3265
+ } catch (healthErr) {
3266
+ const reason = healthErr instanceof Error ? healthErr.message : String(healthErr);
3267
+ console.error(
3268
+ `[stack-update] Release health probe failed on the partial stack (NOT rolling back the batch \u2014 bug a5e29168): ${reason}`
3269
+ );
3270
+ partialHealthWarnings.push(reason);
3271
+ }
3272
+ } else {
3273
+ await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3, stackEnv, failedManifestServices);
3274
+ }
3275
+ const releasePostMigrations = partialRun ? void 0 : plan.release.migrations?.post;
2848
3276
  if (releasePostMigrations && releasePostMigrations.length > 0) {
2849
3277
  const endReleasePost = startPhase("release post-migrations");
2850
3278
  try {
@@ -2864,8 +3292,49 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
2864
3292
  console.warn(`[stack-update] \u26A0 Overlay re-apply step failed (non-fatal): ${reason}`);
2865
3293
  }
2866
3294
  for (const serviceName of Object.keys(plan.release.services)) {
3295
+ if (failedManifestServices.has(serviceName)) continue;
2867
3296
  updateServiceStatus(lockFile, serviceName, "completed", now);
2868
3297
  }
3298
+ if (partialRun) {
3299
+ progress.logOverallSummary();
3300
+ printPhaseSummary();
3301
+ const failedList = [...failedManifestServices];
3302
+ const appliedList = Object.keys(plan.release.services).filter((s) => !failedManifestServices.has(s));
3303
+ console.error(
3304
+ `[stack-update] PARTIAL UPDATE (bug a5e29168): ${appliedList.length} service(s) deployed to ${plan.targetVersion}; ${failedList.length} rolled back in isolation and left on the previous image.
3305
+ Applied: ${appliedList.join(", ") || "(none)"}
3306
+ Rolled back: ${failedList.join(", ")}` + (isolationRollbackErrors.length > 0 ? `
3307
+ Rollback warnings:
3308
+ - ${isolationRollbackErrors.join("\n - ")}` : ""),
3309
+ partialHealthWarnings.length > 0 ? `
3310
+ Health-probe warnings (services left deployed but NOT answering their health URL):
3311
+ - ${partialHealthWarnings.join("\n - ")}` : ""
3312
+ );
3313
+ const partialLock = readStackUpdateLock(lockFile) ?? {};
3314
+ writeStackUpdateLock(lockFile, {
3315
+ ...partialLock,
3316
+ previousVersion,
3317
+ updatedAt: now().toISOString(),
3318
+ backupEnvFile: partialLock.backupEnvFile ?? backupEnvFile,
3319
+ backupComposeFile: partialLock.backupComposeFile ?? backupComposeFile,
3320
+ backupComposeOverrideFile: partialLock.backupComposeOverrideFile ?? backupComposeOverrideFile,
3321
+ hadComposeOverrideFile: partialLock.hadComposeOverrideFile ?? hadComposeOverrideFile,
3322
+ mutatedEnvKeys: partialLock.mutatedEnvKeys ?? mutatedEnvKeys,
3323
+ preUpdateImageDefaults: partialLock.preUpdateImageDefaults ?? configBackup.preUpdateImageDefaults,
3324
+ services: { ...fullTargetPlan.release.services, ...partialLock.services ?? {}, ...plan.release.services }
3325
+ });
3326
+ const partialStaleness = evaluateDeployStaleness(readStackUpdateLock(lockFile), now);
3327
+ if (partialStaleness) writeDeployStalenessAlert(partialStaleness);
3328
+ await postDeployAudit(
3329
+ options,
3330
+ "partial",
3331
+ plan.targetVersion,
3332
+ previousVersion,
3333
+ `Isolated rollback of: ${failedList.join(", ")}`,
3334
+ { applied: appliedList.length, rolledBack: failedList.length }
3335
+ );
3336
+ return { status: "partial", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile, serviceStatus: readStackUpdateLock(lockFile)?.serviceStatus };
3337
+ }
2869
3338
  try {
2870
3339
  if (!options.healthGate) {
2871
3340
  throw new Error("healthGate hook not provided");
@@ -3097,9 +3566,10 @@ function resolveHealthHost(serviceName, service, env) {
3097
3566
  }
3098
3567
  return expandEnvRefs(service.healthHost, env ?? /* @__PURE__ */ new Map());
3099
3568
  }
3100
- async function verifyReleaseHealth(release, retries, delayMs, env) {
3569
+ async function verifyReleaseHealth(release, retries, delayMs, env, skipServices) {
3101
3570
  for (const [serviceName, service] of Object.entries(release.services)) {
3102
3571
  if (!service.healthUrl) continue;
3572
+ if (skipServices?.has(serviceName)) continue;
3103
3573
  const hostHeader = resolveHealthHost(serviceName, service, env);
3104
3574
  await waitForHttpOk(service.healthUrl, retries, delayMs, serviceName, hostHeader);
3105
3575
  }
@@ -3107,7 +3577,7 @@ async function verifyReleaseHealth(release, retries, delayMs, env) {
3107
3577
  var defaultComposePsCapture = (composeArgs) => {
3108
3578
  let result;
3109
3579
  try {
3110
- result = spawnSync2("docker", [...composeArgs, "ps", "--all", "--format", "json"], {
3580
+ result = spawnSync("docker", [...composeArgs, "ps", "--all", "--format", "json"], {
3111
3581
  encoding: "utf8",
3112
3582
  stdio: ["pipe", "pipe", "pipe"],
3113
3583
  timeout: 2e4
@@ -3118,6 +3588,37 @@ var defaultComposePsCapture = (composeArgs) => {
3118
3588
  if (result.error || typeof result.status !== "number" || result.status !== 0) return null;
3119
3589
  return result.stdout?.toString() ?? "";
3120
3590
  };
3591
+ function listHostComposeServices(composeArgs, capture = defaultComposePsCapture) {
3592
+ const stdout = capture(composeArgs);
3593
+ if (stdout === null) return { services: [], unavailable: true };
3594
+ const trimmed = stdout.trim().replace(/^/, "");
3595
+ if (!trimmed) return { services: [], unavailable: false };
3596
+ const records = [];
3597
+ if (trimmed.startsWith("[")) {
3598
+ try {
3599
+ const parsed = JSON.parse(trimmed);
3600
+ if (Array.isArray(parsed)) records.push(...parsed);
3601
+ } catch {
3602
+ }
3603
+ }
3604
+ if (records.length === 0) {
3605
+ for (const line of trimmed.split("\n")) {
3606
+ const l = line.trim();
3607
+ if (!l) continue;
3608
+ try {
3609
+ records.push(JSON.parse(l));
3610
+ } catch {
3611
+ }
3612
+ }
3613
+ }
3614
+ if (records.length === 0) return { services: [], unavailable: true };
3615
+ const services = /* @__PURE__ */ new Set();
3616
+ for (const obj of records) {
3617
+ const service = String(obj.Service ?? "").trim();
3618
+ if (service) services.add(service);
3619
+ }
3620
+ return { services: [...services], unavailable: false };
3621
+ }
3121
3622
  function snapshotComposeServiceStates(composeArgs, wantedServices, capture) {
3122
3623
  const stdout = capture(composeArgs);
3123
3624
  if (stdout === null) return null;
@@ -3215,6 +3716,116 @@ Inspect with: docker compose ps --all`
3215
3716
  await new Promise((r) => setTimeout(r, 3e3));
3216
3717
  }
3217
3718
  }
3719
+ var INGRESS_PROBE_TIMEOUT_MS = 4e3;
3720
+ var defaultDockerCapture = (args) => {
3721
+ let result;
3722
+ try {
3723
+ result = spawnSync("docker", args, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 2e4 });
3724
+ } catch (err) {
3725
+ return { unavailable: true, status: null, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
3726
+ }
3727
+ if (result.error) {
3728
+ return { unavailable: true, status: null, stdout: "", stderr: result.error.message };
3729
+ }
3730
+ return {
3731
+ unavailable: false,
3732
+ status: typeof result.status === "number" ? result.status : null,
3733
+ stdout: result.stdout?.toString() ?? "",
3734
+ stderr: result.stderr?.toString() ?? ""
3735
+ };
3736
+ };
3737
+ function isDockerDaemonUnavailable(stderr) {
3738
+ const s = stderr.toLowerCase();
3739
+ return s.includes("cannot connect to the docker daemon") || s.includes("is the docker daemon running") || s.includes("docker daemon is not running") || s.includes("error during connect") || s.includes("permission denied while trying to connect to the docker daemon");
3740
+ }
3741
+ function isComposeNetworkHost(host) {
3742
+ const h = host.trim().toLowerCase();
3743
+ if (h === "" || h === "localhost") return false;
3744
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(h)) return false;
3745
+ if (h.includes(":")) return false;
3746
+ if (h.includes(".")) return false;
3747
+ return true;
3748
+ }
3749
+ function isCloudflaredLoopbackHost(host) {
3750
+ let h = host.trim().toLowerCase();
3751
+ if (h === "") return false;
3752
+ if (h === "localhost") return true;
3753
+ if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
3754
+ if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
3755
+ h = h.replace(/^::ffff:/, "");
3756
+ if (/^127(?:\.\d{1,3}){3}$/.test(h)) return true;
3757
+ return false;
3758
+ }
3759
+ function makeDefaultComposeServiceProbe(composeArgs, capture = defaultDockerCapture, hostProbe = defaultIngressReachabilityProbe) {
3760
+ return async (service, port) => {
3761
+ const ps = capture([...composeArgs, "ps", "-q", service]);
3762
+ if (ps.unavailable || ps.status !== 0 && isDockerDaemonUnavailable(ps.stderr)) {
3763
+ console.warn(`[stack-update] cloudflared ingress: cannot query docker for compose service '${service}'; treating as reachable (indeterminate)`);
3764
+ return true;
3765
+ }
3766
+ const containerId = ps.stdout.trim().split(/\r?\n/)[0]?.trim() ?? "";
3767
+ if (!containerId) return false;
3768
+ const ins = capture(["inspect", "-f", "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}", containerId]);
3769
+ if (ins.unavailable || ins.status !== 0 && isDockerDaemonUnavailable(ins.stderr)) {
3770
+ console.warn(`[stack-update] cloudflared ingress: cannot inspect container for compose service '${service}'; treating as reachable (indeterminate)`);
3771
+ return true;
3772
+ }
3773
+ const ip = ins.stdout.trim().split(/\s+/).find((x) => x !== "") ?? "";
3774
+ if (!ip) return false;
3775
+ return hostProbe(ip, port);
3776
+ };
3777
+ }
3778
+ function defaultIngressReachabilityProbe(host, port) {
3779
+ return new Promise((resolve) => {
3780
+ let settled = false;
3781
+ const socket = net.connect({ host, port });
3782
+ const done = (reachable) => {
3783
+ if (settled) return;
3784
+ settled = true;
3785
+ socket.destroy();
3786
+ resolve(reachable);
3787
+ };
3788
+ socket.setTimeout(INGRESS_PROBE_TIMEOUT_MS);
3789
+ socket.once("connect", () => done(true));
3790
+ socket.once("timeout", () => done(false));
3791
+ socket.once("error", (err) => {
3792
+ if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) done(true);
3793
+ else done(false);
3794
+ });
3795
+ });
3796
+ }
3797
+ async function verifyIngressTargetsReachable(configYml, probe = defaultIngressReachabilityProbe, composeProbe) {
3798
+ if (!configYml || configYml.trim() === "") return;
3799
+ const targets = parseCloudflaredIngressTargets(configYml);
3800
+ if (targets.length === 0) return;
3801
+ const loopbackTargets = targets.filter((t) => isCloudflaredLoopbackHost(t.host));
3802
+ const probeTargets = targets.filter((t) => !isCloudflaredLoopbackHost(t.host));
3803
+ const effectiveProbe = composeProbe ? (host, port) => isComposeNetworkHost(host) ? composeProbe(host, port) : probe(host, port) : probe;
3804
+ const probedUnreachable = await findUnreachableIngressTargets(probeTargets, effectiveProbe);
3805
+ if (loopbackTargets.length === 0 && probedUnreachable.length === 0) {
3806
+ console.log(`[stack-update] \u2713 cloudflared ingress: all ${targets.length} target(s) reachable`);
3807
+ return;
3808
+ }
3809
+ const fmt = (t) => `${t.host}:${t.port}${t.hostname ? ` (hostname ${t.hostname})` : ""} [service ${t.service}]`;
3810
+ const sections = [];
3811
+ if (probedUnreachable.length > 0) {
3812
+ sections.push(
3813
+ `target(s) not accepting TCP connections:
3814
+ - ${probedUnreachable.map(fmt).join("\n - ")}
3815
+ \`cloudflared tunnel ingress validate\` only checks rule syntax; a rule pointing at a dead port validates cleanly and then 502s every request. Fix the target service/port (or remove the rule) and re-deploy.`
3816
+ );
3817
+ }
3818
+ if (loopbackTargets.length > 0) {
3819
+ sections.push(
3820
+ `loopback target(s) unreachable from the cloudflared container:
3821
+ - ${loopbackTargets.map(fmt).join("\n - ")}
3822
+ cloudflared runs as a bridge-network container, so 127.0.0.1/localhost resolves to the tunnel container itself \u2014 not the deploy host where the app port is published \u2014 and every request 502s. Address the service by its docker-compose service name (e.g. \`http://exe-dashboard:PORT\`) and re-deploy.`
3823
+ );
3824
+ }
3825
+ throw new Error(
3826
+ `Cloudflared ingress reachability check failed (bug 57fcb5bf) \u2014 ${sections.join("\n\n")}`
3827
+ );
3828
+ }
3218
3829
  async function waitForHttpOk(url, retries, delayMs, label, hostHeader) {
3219
3830
  let last = "";
3220
3831
  for (let i = 0; i < retries; i++) {
@@ -3329,7 +3940,7 @@ function parseComposeVolumeNames(composeRaw) {
3329
3940
  return names;
3330
3941
  }
3331
3942
  function volumeHasComposeProjectLabel(volumeName, projectName) {
3332
- const res = spawnSync2(
3943
+ const res = spawnSync(
3333
3944
  "docker",
3334
3945
  ["volume", "inspect", volumeName, "--format", '{{ index .Labels "com.docker.compose.project" }}'],
3335
3946
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
@@ -3358,7 +3969,7 @@ function detectDanglingVolumes(scope) {
3358
3969
  byName.set(name, { name, driver: driver ?? "local", size: "" });
3359
3970
  }
3360
3971
  };
3361
- const labeled = spawnSync2(
3972
+ const labeled = spawnSync(
3362
3973
  "docker",
3363
3974
  [
3364
3975
  "volume",
@@ -3373,7 +3984,7 @@ function detectDanglingVolumes(scope) {
3373
3984
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
3374
3985
  );
3375
3986
  if (labeled.status === 0 && labeled.stdout?.trim()) ingest(labeled.stdout, false);
3376
- const all = spawnSync2(
3987
+ const all = spawnSync(
3377
3988
  "docker",
3378
3989
  ["volume", "ls", "--filter", "dangling=true", "--format", "{{.Name}} {{.Driver}}"],
3379
3990
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
@@ -3388,14 +3999,14 @@ function detectDanglingVolumes(scope) {
3388
3999
  }
3389
4000
  function getVolumeSize(volumeName) {
3390
4001
  try {
3391
- const inspectResult = spawnSync2(
4002
+ const inspectResult = spawnSync(
3392
4003
  "docker",
3393
4004
  ["volume", "inspect", volumeName, "--format", "{{.Mountpoint}}"],
3394
4005
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
3395
4006
  );
3396
4007
  const mountpoint = inspectResult.stdout?.trim();
3397
4008
  if (!mountpoint || inspectResult.status !== 0) return "unknown";
3398
- const duResult = spawnSync2(
4009
+ const duResult = spawnSync(
3399
4010
  "du",
3400
4011
  ["-sh", mountpoint],
3401
4012
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
@@ -3424,7 +4035,7 @@ function removeDanglingVolumes(volumes, scope) {
3424
4035
  );
3425
4036
  continue;
3426
4037
  }
3427
- const checkResult = spawnSync2(
4038
+ const checkResult = spawnSync(
3428
4039
  "docker",
3429
4040
  ["volume", "ls", "--filter", "dangling=true", "--filter", `name=^${vol.name}$`, "--format", "{{.Name}}"],
3430
4041
  { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
@@ -3512,6 +4123,8 @@ export {
3512
4123
  parseComposeServiceNames,
3513
4124
  isUsableTunnelToken,
3514
4125
  isCloudflaredTunnelMisconfigured,
4126
+ parseCloudflaredIngressTargets,
4127
+ findUnreachableIngressTargets,
3515
4128
  resolveTransitiveDeps,
3516
4129
  filterServicesBySelection,
3517
4130
  buildDefaultServiceSelection,
@@ -3525,6 +4138,7 @@ export {
3525
4138
  runReleaseMigrations,
3526
4139
  isExecFormMigration,
3527
4140
  expandComposeServicesWithSidecars,
4141
+ resolveOwningManifestService,
3528
4142
  composeArgsForStack,
3529
4143
  parseStackManifest,
3530
4144
  loadStackManifest,
@@ -3538,7 +4152,13 @@ export {
3538
4152
  resolveServerImageOverride,
3539
4153
  applyServerImageOverrideResolution,
3540
4154
  parseRequiredEnvRefs,
4155
+ resolveManifestImagePin,
4156
+ requiredImagePinKeys,
4157
+ syncImagePinsWithProcessEnv,
3541
4158
  detectMissingRequiredEnvVars,
4159
+ MONITOR_HEALTH_BIN_ENV,
4160
+ MONITOR_IMAGE_TAG_ENV,
4161
+ monitorHealthBinForImage,
3542
4162
  createStackUpdatePlan,
3543
4163
  isUnpinnedImageBreakGlassEnabled,
3544
4164
  isStrictDigestEnabled,
@@ -3546,10 +4166,17 @@ export {
3546
4166
  isThirdPartyImage,
3547
4167
  stripImageDigest,
3548
4168
  imagePullRef,
4169
+ manifestInspectRef,
3549
4170
  collectProductionDeployGateIssues,
3550
4171
  collectImageAvailabilityIssues,
3551
4172
  assertDeploymentScopeAllowed,
3552
4173
  assertProductionDeployGate,
4174
+ parseComposeServiceImages,
4175
+ manifestCoveredComposeServices,
4176
+ collectManifestDeliveryIssues,
4177
+ manifestDeliveryBreakGlassServices,
4178
+ manifestDeliveryBreakGlassBanner,
4179
+ assertSignedManifestDeliveryGate,
3553
4180
  assertRolloutStageAllowed,
3554
4181
  assertBreakingChangesAllowed,
3555
4182
  verifyManifestImagesAvailable,
@@ -3569,7 +4196,13 @@ export {
3569
4196
  expandEnvRefs,
3570
4197
  resolveHealthHost,
3571
4198
  verifyReleaseHealth,
4199
+ listHostComposeServices,
3572
4200
  verifyComposeServicesRunning,
4201
+ isComposeNetworkHost,
4202
+ isCloudflaredLoopbackHost,
4203
+ makeDefaultComposeServiceProbe,
4204
+ defaultIngressReachabilityProbe,
4205
+ verifyIngressTargetsReachable,
3573
4206
  waitForHttpOk,
3574
4207
  httpStatus,
3575
4208
  defaultStackPaths,