@askexenow/exe-os 0.9.362 → 0.9.365

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 (450) hide show
  1. package/deploy/compose/.env.customer.example +46 -2
  2. package/deploy/compose/.env.example +49 -2
  3. package/deploy/compose/cloudflared/config.yml.example +15 -8
  4. package/deploy/compose/docker-compose.yml +240 -34
  5. package/deploy/compose/generate-env.ts +88 -2
  6. package/deploy/compose/status.sh +30 -0
  7. package/deploy/compose/uptime-check.sh +39 -2
  8. package/deploy/compose/wiki-schema-guard.sh +302 -0
  9. package/deploy/stack-manifests/v0.9.json +232 -2
  10. package/dist/.dist-verified.json +3 -3
  11. package/dist/{active-agent-BPNEBKHI.js → active-agent-C6DPH43L.js} +10 -5
  12. package/dist/{active-agent-3FV5V7SS.js → active-agent-MGF7J3HB.js} +10 -5
  13. package/dist/agent-defs-generator-X7TVMPVB.js +240 -0
  14. package/dist/{agentic-ontology-RCVCJ6WB.js → agentic-ontology-CARDCG37.js} +1 -1
  15. package/dist/artifact-backup-767STHSW.js +209 -0
  16. package/dist/artifact-provenance-C7HQ267L.js +13 -0
  17. package/dist/{backfill-gate-LRX77SUD.js → backfill-gate-VX2RZTQV.js} +2 -2
  18. package/dist/{backfill-metadata-Y3DGVCK5.js → backfill-metadata-ANCJA6LA.js} +7 -7
  19. package/dist/{background-jobs-Z6KHCWES.js → background-jobs-LTPWCMA3.js} +2 -2
  20. package/dist/{backup-verify-TZPPUCLU.js → backup-verify-F7BHVEF7.js} +27 -14
  21. package/dist/{behavior-injection-JMGGWCWG.js → behavior-injection-XQ4B7DCF.js} +13 -12
  22. package/dist/{behaviors-TT6CMEKC.js → behaviors-R2IE3PEN.js} +12 -11
  23. package/dist/bin/age-ontology-load.js +2 -2
  24. package/dist/bin/agentic-ontology-backfill.js +9 -9
  25. package/dist/bin/agentic-reflection-backfill.js +10 -10
  26. package/dist/bin/agentic-semantic-label.js +9 -9
  27. package/dist/bin/backfill-conversations.js +10 -9
  28. package/dist/bin/backfill-responses.js +10 -9
  29. package/dist/bin/backfill-vectors.js +14 -13
  30. package/dist/bin/bulk-sync-postgres.js +23 -19
  31. package/dist/bin/cc-doctor.js +11 -8
  32. package/dist/bin/cleanup-stale-review-tasks.js +26 -24
  33. package/dist/bin/cli.js +29 -102
  34. package/dist/bin/customer-readiness.js +3 -1
  35. package/dist/bin/deferred-daemon-restart.js +115 -103
  36. package/dist/bin/exe-agent-config.js +5 -5
  37. package/dist/bin/exe-agent.js +14 -14
  38. package/dist/bin/exe-assign.js +12 -11
  39. package/dist/bin/exe-boot.js +60 -51
  40. package/dist/bin/exe-call.js +7 -7
  41. package/dist/bin/exe-cloud.js +26 -22
  42. package/dist/bin/exe-config-dump.js +5 -5
  43. package/dist/bin/exe-dispatch.js +30 -24
  44. package/dist/bin/exe-doctor.js +10 -9
  45. package/dist/bin/exe-export-behaviors.js +14 -13
  46. package/dist/bin/exe-forget.js +9 -9
  47. package/dist/bin/exe-gateway.js +9 -9
  48. package/dist/bin/exe-grant.js +5 -5
  49. package/dist/bin/exe-healthcheck.js +13 -8
  50. package/dist/bin/exe-heartbeat.js +36 -29
  51. package/dist/bin/exe-kill.js +30 -28
  52. package/dist/bin/exe-launch-agent.js +41 -37
  53. package/dist/bin/exe-mcp.js +5 -5
  54. package/dist/bin/exe-memory-reads.js +7 -7
  55. package/dist/bin/exe-new-employee.js +12 -11
  56. package/dist/bin/exe-os-mcp-proxy.js +5 -5
  57. package/dist/bin/exe-pending-messages.js +27 -25
  58. package/dist/bin/exe-pending-notifications.js +26 -24
  59. package/dist/bin/exe-pending-reviews.js +27 -25
  60. package/dist/bin/exe-rename.js +7 -7
  61. package/dist/bin/exe-review.js +29 -27
  62. package/dist/bin/exe-search.js +8 -8
  63. package/dist/bin/exe-session-cleanup.js +31 -29
  64. package/dist/bin/exe-settings.js +24 -20
  65. package/dist/bin/exe-start-codex.js +36 -21
  66. package/dist/bin/exe-start-opencode.js +15 -14
  67. package/dist/bin/exe-status.js +28 -25
  68. package/dist/bin/exe-support.js +5 -4
  69. package/dist/bin/exe-team.js +12 -6
  70. package/dist/bin/exe-verify-running-images.js +145 -0
  71. package/dist/bin/exe-watchdog.js +16 -6
  72. package/dist/bin/git-sweep.js +27 -25
  73. package/dist/bin/graph-backfill.js +10 -9
  74. package/dist/bin/graph-export.js +8 -8
  75. package/dist/bin/import-history.js +13 -11
  76. package/dist/bin/install.js +119 -107
  77. package/dist/bin/intercom-check.js +5 -5
  78. package/dist/bin/mcp-sessions.js +2 -2
  79. package/dist/bin/orchestration-metrics.js +6 -6
  80. package/dist/bin/postgres-agentic-reflection-backfill.js +4 -4
  81. package/dist/bin/postgres-agentic-semantic-backfill.js +3 -3
  82. package/dist/bin/pre-publish.js +1 -1
  83. package/dist/bin/reconcile-backlog.js +2 -2
  84. package/dist/bin/registry-proxy.js +1 -1
  85. package/dist/bin/scan-tasks.js +26 -24
  86. package/dist/bin/setup.js +4 -3
  87. package/dist/bin/shard-migrate.js +8 -8
  88. package/dist/bin/stack-update.js +87 -38
  89. package/dist/bin/vps-health-gate.js +1 -1
  90. package/dist/{boot-generation-XABYKF5P.js → boot-generation-X45BSVZY.js} +5 -5
  91. package/dist/{boot-review-sections-2X5TFIGU.js → boot-review-sections-OBVRXXSF.js} +25 -23
  92. package/dist/{branch-hygiene-Z4IBWAOX.js → branch-hygiene-HFJ3HA62.js} +17 -10
  93. package/dist/{branding-7MZDHTKN.js → branding-GH43W3PU.js} +2 -2
  94. package/dist/build-manifest.json +1189 -1049
  95. package/dist/{capability-cards-O5UXDOOA.js → capability-cards-57FURUMR.js} +4 -4
  96. package/dist/{capacity-monitor-IPXM726A.js → capacity-monitor-QEL62JDO.js} +27 -25
  97. package/dist/{catchup-brief-EBIGDXOK.js → catchup-brief-PMXTCASA.js} +30 -28
  98. package/dist/{chunk-UO5QJ2RX.js → chunk-22YQBVMO.js} +4 -4
  99. package/dist/chunk-2ADYTOHQ.js +189 -0
  100. package/dist/{chunk-NO5KHKKB.js → chunk-2CWS374F.js} +1 -1
  101. package/dist/{chunk-NAHCSQWT.js → chunk-2ESB5SDP.js} +19 -7
  102. package/dist/{chunk-CY5NVDTH.js → chunk-2FYIMTQE.js} +9 -5
  103. package/dist/{chunk-SC6WDF3Q.js → chunk-2RQT7TED.js} +2 -2
  104. package/dist/{chunk-UWEFIIEG.js → chunk-3CIMDFXG.js} +1 -1
  105. package/dist/{chunk-E7PL3T2U.js → chunk-3JNVB5LG.js} +1 -1
  106. package/dist/{chunk-7SY2NOMQ.js → chunk-3THNS2T7.js} +1 -1
  107. package/dist/{chunk-XJIRUTGJ.js → chunk-45EQTS7B.js} +1 -1
  108. package/dist/{chunk-YZUHSV24.js → chunk-4LWBC7CM.js} +1 -1
  109. package/dist/{chunk-DKRDU2AX.js → chunk-4QKKLK3C.js} +14 -14
  110. package/dist/{chunk-WP2TGH5R.js → chunk-52TIQJEQ.js} +2 -2
  111. package/dist/{chunk-LG37UBBE.js → chunk-5CBGH6RA.js} +5 -5
  112. package/dist/{chunk-W3LKIQJ2.js → chunk-6GQYPJPG.js} +22 -16
  113. package/dist/{chunk-I3SF6QGV.js → chunk-6Q263OZE.js} +1 -1
  114. package/dist/{chunk-XSYQ5EPE.js → chunk-7C4FUNVO.js} +11 -11
  115. package/dist/{chunk-P7CV4SHQ.js → chunk-7Y6AQWVT.js} +21 -10
  116. package/dist/{chunk-NGDXBN3M.js → chunk-A6PDF4BO.js} +2 -2
  117. package/dist/chunk-A7XLR3K7.js +45 -0
  118. package/dist/{chunk-7H3AZNQ7.js → chunk-AGQAGOQN.js} +9 -9
  119. package/dist/{chunk-VDMFVUSY.js → chunk-ANPE3TT3.js} +3 -3
  120. package/dist/{chunk-NE4QI2GX.js → chunk-BATQKSLI.js} +2 -2
  121. package/dist/{chunk-AOMFYUPT.js → chunk-BEHLIRRJ.js} +2 -2
  122. package/dist/{chunk-6EM6USV7.js → chunk-BNHAZ65R.js} +42 -50
  123. package/dist/{chunk-HLU52KTO.js → chunk-BRLS5OLZ.js} +257 -36
  124. package/dist/{chunk-QCJT265O.js → chunk-BY7WWPRM.js} +1 -1
  125. package/dist/{chunk-Z67DIJKT.js → chunk-C3UAQ3JG.js} +1 -1
  126. package/dist/{chunk-MZYNW5OY.js → chunk-CDUB5Q6J.js} +1 -1
  127. package/dist/{chunk-5H3RHWRE.js → chunk-CKG6CVKI.js} +1 -1
  128. package/dist/{chunk-WH2VVEGD.js → chunk-CMBF5WCL.js} +56 -2
  129. package/dist/{chunk-JYKZVAS6.js → chunk-CPILYY2K.js} +1 -1
  130. package/dist/{chunk-XTGR3LMF.js → chunk-CW2UCKHS.js} +2 -2
  131. package/dist/{chunk-32FEF5BW.js → chunk-CWBFOAGM.js} +2 -2
  132. package/dist/{chunk-V5I5N7NC.js → chunk-DEFSN2FQ.js} +3 -3
  133. package/dist/{chunk-HAXCUCJC.js → chunk-DJK45U7B.js} +1 -1
  134. package/dist/{chunk-MZZN5GFC.js → chunk-DQ5KHWFD.js} +1 -1
  135. package/dist/{chunk-SLBOKYYQ.js → chunk-DWAQOEXL.js} +2 -2
  136. package/dist/{chunk-T2RZU7JA.js → chunk-DYWGIPVP.js} +7 -7
  137. package/dist/{chunk-6DTALXOA.js → chunk-E5HAY3DQ.js} +1 -1
  138. package/dist/{chunk-D5CY4E7I.js → chunk-EHHMNKOA.js} +1 -1
  139. package/dist/{chunk-54USDN5Q.js → chunk-EJ4522YM.js} +1 -1
  140. package/dist/{chunk-URNVIZXU.js → chunk-EKVTRI3N.js} +52 -6
  141. package/dist/{chunk-KID6T2A4.js → chunk-EMZHB767.js} +44 -35
  142. package/dist/{chunk-KBI4FVBZ.js → chunk-EPDNWY5P.js} +111 -22
  143. package/dist/{chunk-AQO3BT3I.js → chunk-EPNWEOOE.js} +13 -13
  144. package/dist/{chunk-5C6ZIJ7J.js → chunk-ESTR7QUP.js} +1 -1
  145. package/dist/{chunk-Q2VQNAPU.js → chunk-EVPBYX2Z.js} +2 -2
  146. package/dist/{chunk-CVYDJ2TW.js → chunk-EWCEUO65.js} +1 -1
  147. package/dist/{chunk-RNSAVQEU.js → chunk-EWZYFG5Q.js} +8 -3
  148. package/dist/{chunk-R7F6HKTH.js → chunk-EXDW3E2R.js} +3 -1
  149. package/dist/{chunk-TY4JLBFO.js → chunk-F2S3M5MB.js} +3 -3
  150. package/dist/{chunk-BT2PUK3S.js → chunk-FXPAUDCZ.js} +1 -1
  151. package/dist/{chunk-3C7NE4JE.js → chunk-G2EAYH4U.js} +1 -1
  152. package/dist/{chunk-WDA2KAV7.js → chunk-GSJLB6JI.js} +1 -1
  153. package/dist/{chunk-M67ARGXY.js → chunk-H37FSTF2.js} +1 -1
  154. package/dist/{chunk-ZR4CQBAP.js → chunk-H74466TM.js} +1 -1
  155. package/dist/{chunk-3BAGFZFN.js → chunk-HHGOCBVQ.js} +9 -9
  156. package/dist/{working-memory-BACVOAIV.js → chunk-HSFBNCNC.js} +3 -3
  157. package/dist/{chunk-PF7EQSE5.js → chunk-IIZITKBV.js} +6 -6
  158. package/dist/{chunk-2FA4PYYX.js → chunk-IM2I7OOS.js} +1 -1
  159. package/dist/{chunk-DSKEYEL2.js → chunk-IXEFO6HD.js} +3 -3
  160. package/dist/{chunk-TN2E6ZCM.js → chunk-J4U5Z7VX.js} +214 -58
  161. package/dist/{chunk-PU7UYQRC.js → chunk-J5EYOBFF.js} +1 -1
  162. package/dist/{chunk-RWKX2TK5.js → chunk-J6I7X272.js} +1 -1
  163. package/dist/{chunk-BDLRTLXY.js → chunk-JITNFE4W.js} +6 -2
  164. package/dist/{chunk-PUMH7JSB.js → chunk-JNVQGLPX.js} +3 -3
  165. package/dist/{chunk-VAOUB2TK.js → chunk-JTB5V6BV.js} +1 -1
  166. package/dist/{chunk-QYRI25IP.js → chunk-JUOZLXLH.js} +2 -2
  167. package/dist/{chunk-RYRNY6IF.js → chunk-JYFYIGYK.js} +2 -2
  168. package/dist/{chunk-2PRAXZWM.js → chunk-KOYGRP2L.js} +10 -5
  169. package/dist/{chunk-4AFD4R35.js → chunk-KRUYQFSH.js} +5 -5
  170. package/dist/chunk-L25OAFOU.js +93 -0
  171. package/dist/{chunk-FUQ2OSDV.js → chunk-L2VZBPJJ.js} +25 -8
  172. package/dist/{chunk-LCDW2HUL.js → chunk-LHOKWZKL.js} +1 -1
  173. package/dist/{chunk-BN26LSME.js → chunk-LKYGSJ2E.js} +13 -2
  174. package/dist/chunk-LNOS24GV.js +214 -0
  175. package/dist/{chunk-O32HTNRX.js → chunk-LZKYVMBY.js} +13 -4
  176. package/dist/{chunk-35FD67ZB.js → chunk-M7EES2KM.js} +3 -3
  177. package/dist/chunk-MBBFL2SP.js +45 -0
  178. package/dist/{chunk-4R2WD6T3.js → chunk-MPQXQ5IE.js} +2 -2
  179. package/dist/{chunk-QQMYD4M7.js → chunk-MXEIZUXG.js} +1 -1
  180. package/dist/{chunk-6QJXFLNN.js → chunk-MZJBUAC3.js} +5 -5
  181. package/dist/chunk-NEHOQ3P2.js +19 -0
  182. package/dist/{chunk-BRZR76TS.js → chunk-NJFJWQNN.js} +49 -14
  183. package/dist/{chunk-Q7J2SQJ6.js → chunk-NO3M2AFM.js} +100 -32
  184. package/dist/{gotrue-session-2IT7MDGW.js → chunk-NUTZJZKW.js} +7 -8
  185. package/dist/{chunk-6UZDGEGQ.js → chunk-NV6A5HDN.js} +71 -1
  186. package/dist/{chunk-ZI3HA3XW.js → chunk-O2MEBF23.js} +3 -3
  187. package/dist/{chunk-V6YLIDLK.js → chunk-ODBRU6BF.js} +577 -230
  188. package/dist/{chunk-6N3HUOV3.js → chunk-OFGFRT5J.js} +2 -2
  189. package/dist/{chunk-HKQ7UTSE.js → chunk-OPNROU5B.js} +1 -1
  190. package/dist/{chunk-SQM4ZTL2.js → chunk-ORNCMRIX.js} +60 -33
  191. package/dist/{chunk-RUUX2ZST.js → chunk-P47MPWNU.js} +1 -1
  192. package/dist/{org-key-admin-4W6LZ5KZ.js → chunk-P5XTVIEC.js} +62 -17
  193. package/dist/{chunk-OZNCAMS3.js → chunk-POPRJKAY.js} +122 -20
  194. package/dist/{chunk-SRFRUN66.js → chunk-PPGPCWRC.js} +5 -5
  195. package/dist/{chunk-B6PFGUYJ.js → chunk-PVEM537A.js} +46 -5
  196. package/dist/{chunk-QJBX3MBV.js → chunk-Q7BU5Q6I.js} +3 -3
  197. package/dist/{chunk-SXKIGBNF.js → chunk-QFIVK3G5.js} +1 -1
  198. package/dist/{chunk-BRIZ6OZ3.js → chunk-QMSEL5YS.js} +81 -23
  199. package/dist/{chunk-BJEQBKGH.js → chunk-QYJANQWC.js} +8 -8
  200. package/dist/{chunk-4U56FZ7B.js → chunk-R7RM766N.js} +3 -3
  201. package/dist/{chunk-QI25QRUP.js → chunk-RD4E5G3B.js} +2 -2
  202. package/dist/{chunk-QP53XLC5.js → chunk-RHX6MNWY.js} +1 -1
  203. package/dist/chunk-RIECV7SR.js +234 -0
  204. package/dist/{chunk-3YBJ36UX.js → chunk-RLDMUSRM.js} +2 -2
  205. package/dist/{chunk-CXQ724WH.js → chunk-RVWALVDT.js} +2 -2
  206. package/dist/{chunk-VWTUKKUF.js → chunk-RZIKSE6N.js} +10 -4
  207. package/dist/chunk-SBAU735L.js +328 -0
  208. package/dist/{chunk-XRCX6R5S.js → chunk-SBJV2PCU.js} +1 -1
  209. package/dist/chunk-SO7KTH3Q.js +27 -0
  210. package/dist/{chunk-327Q53AS.js → chunk-SP37DONS.js} +17 -6
  211. package/dist/{chunk-KA4WVDLX.js → chunk-T6S45OLV.js} +4 -4
  212. package/dist/chunk-TCTED52S.js +120 -0
  213. package/dist/{chunk-HLQ4MS42.js → chunk-TO6TB75E.js} +4 -4
  214. package/dist/{chunk-465RCKVI.js → chunk-U373MEF5.js} +6 -6
  215. package/dist/{chunk-AL4HXKDJ.js → chunk-VGE6IHZB.js} +10 -10
  216. package/dist/{chunk-WBG7XYVR.js → chunk-VHJVD3MZ.js} +8 -0
  217. package/dist/{chunk-ZZEKOI2M.js → chunk-VMJ2UH4S.js} +1 -1
  218. package/dist/{chunk-3ICF4IAZ.js → chunk-VS4LPG66.js} +1 -1
  219. package/dist/{chunk-E3YI6FVJ.js → chunk-VV5EZZNT.js} +1 -1
  220. package/dist/{chunk-OPU3NYOO.js → chunk-W2IRQJ6R.js} +12 -0
  221. package/dist/{chunk-PV5BIXT6.js → chunk-WR75WUEP.js} +1 -1
  222. package/dist/{chunk-XUB4GFPN.js → chunk-WYLFDSIE.js} +144 -99
  223. package/dist/{chunk-4L33OFU4.js → chunk-X2P43TB2.js} +6 -3
  224. package/dist/chunk-X2X3IGZU.js +215 -0
  225. package/dist/{chunk-KIKB6FY3.js → chunk-X6A2VWVJ.js} +2 -2
  226. package/dist/chunk-XMVSLLED.js +38 -0
  227. package/dist/{chunk-KDMVVHZH.js → chunk-XTWHNLGD.js} +95 -23
  228. package/dist/{chunk-QY3TM2T3.js → chunk-YMJOLEPE.js} +4 -4
  229. package/dist/{chunk-RFKRCVGW.js → chunk-YYA5LXCT.js} +1 -1
  230. package/dist/{chunk-HSKABJE6.js → chunk-ZDUO6QBH.js} +5 -10
  231. package/dist/{chunk-PDJWHL63.js → chunk-ZEKCHV43.js} +4 -4
  232. package/dist/{chunk-23KWUA7Z.js → chunk-ZNEBZCZR.js} +53 -2
  233. package/dist/chunk-ZNUJFX7L.js +135 -0
  234. package/dist/{chunk-TP7RXGMP.js → chunk-ZRKEUCUZ.js} +315 -67
  235. package/dist/{chunk-6Y3KUO7Q.js → chunk-ZSZPMUNX.js} +244 -21
  236. package/dist/{chunk-4P2BFNSB.js → chunk-ZVTKWWLV.js} +2 -2
  237. package/dist/{chunk-RCOIKTHM.js → chunk-ZZXMB5F2.js} +1 -1
  238. package/dist/{cleanup-graph-garbage-6F22Y5SD.js → cleanup-graph-garbage-CAA625RJ.js} +10 -9
  239. package/dist/cloud-sync-command-DBB7WRHL.js +96 -0
  240. package/dist/cloud-sync-db-init-V7IB23W5.js +46 -0
  241. package/dist/{cloud-sync-shutdown-FWERMZB5.js → cloud-sync-shutdown-2D3B73CJ.js} +10 -4
  242. package/dist/{cloud-sync-state-DJRZORFI.js → cloud-sync-state-5DFKLWNB.js} +1 -1
  243. package/dist/{co-activation-UEEDCM5U.js → co-activation-JVN7EBS6.js} +4 -4
  244. package/dist/{co-occurrence-W6OPFRSY.js → co-occurrence-526PTFGT.js} +8 -7
  245. package/dist/{code-context-index-DWR6HS35.js → code-context-index-XOP6TLPZ.js} +5 -4
  246. package/dist/{conversation-entity-extractor-LWUIH3UB.js → conversation-entity-extractor-JO56DB2E.js} +9 -4
  247. package/dist/{conversation-wiki-populator-5EGRW2UZ.js → conversation-wiki-populator-EBUZXYEU.js} +1 -1
  248. package/dist/{crdt-sync-NHJX3ZJD.js → crdt-sync-LIJLZKFE.js} +2 -2
  249. package/dist/{crm-webhook-OY467ATC.js → crm-webhook-55F7Y6D2.js} +2 -2
  250. package/dist/{cto-delegation-gate-XH4YSQVC.js → cto-delegation-gate-QIYV4KFO.js} +41 -24
  251. package/dist/daemon-auth-SSPY5ODS.js +46 -0
  252. package/dist/{daemon-orchestration-7425ZVNH.js → daemon-orchestration-Q3GJA2JJ.js} +46 -30
  253. package/dist/{daemon-ready-VGPSRI3Z.js → daemon-ready-MKBH4QWD.js} +1 -1
  254. package/dist/{daemon-restart-report-HZU5LWXN.js → daemon-restart-report-EIVD6WDJ.js} +1 -1
  255. package/dist/db-integrity-events-5DTASXJF.js +22 -0
  256. package/dist/{db-restore-events-TFCZ4CPM.js → db-restore-events-2FI4ZMKA.js} +1 -1
  257. package/dist/{doc-graph-extractor-RR2VSO4W.js → doc-graph-extractor-P46R4QME.js} +13 -8
  258. package/dist/{documents-TRZ4R2ZW.js → documents-7KFEREWO.js} +12 -11
  259. package/dist/{dreaming-MAMZAPYW.js → dreaming-JMUXBAPU.js} +29 -27
  260. package/dist/employee-boot-brief-HN63D2BY.js +71 -0
  261. package/dist/{exe-drift-VBWB5E6S.js → exe-drift-7J7GX7DP.js} +5 -5
  262. package/dist/{exe-export-XJJTVVT7.js → exe-export-KJ7B2OBU.js} +14 -13
  263. package/dist/{exe-import-G6O7JFNZ.js → exe-import-HPZDWKGE.js} +14 -13
  264. package/dist/{exe-key-A2WJLLTM.js → exe-key-2PREP464.js} +117 -105
  265. package/dist/{exe-org-HXF4XBB3.js → exe-org-VEIISATF.js} +88 -26
  266. package/dist/{exe-snapshot-TL3TLKLR.js → exe-snapshot-JXQFLUKA.js} +33 -40
  267. package/dist/{fast-db-init-SO2XUQ7T.js → fast-db-init-SCXJXOYX.js} +1 -1
  268. package/dist/{founder-context-IOCADC66.js → founder-context-U2SZPLYV.js} +4 -3
  269. package/dist/{founder-monitor-envelope-SMILO4BT.js → founder-monitor-envelope-5KTKNL5G.js} +4 -4
  270. package/dist/fts-integrity-LKAZEZJE.js +96 -0
  271. package/dist/gateway/index.js +19 -19
  272. package/dist/{git-staleness-I6WT7EXV.js → git-staleness-S4NDR7NX.js} +4 -4
  273. package/dist/{global-procedures-3RMO6C3C.js → global-procedures-ZOFTOXIR.js} +6 -6
  274. package/dist/gotrue-session-ODZ2ERJX.js +25 -0
  275. package/dist/{graph-auto-extract-NKTYPD2I.js → graph-auto-extract-CLVTSUSB.js} +8 -7
  276. package/dist/{graph-rag-EOGPZOYI.js → graph-rag-LSJLWGXV.js} +4 -3
  277. package/dist/{hook-integrity-RTBF2BL2.js → hook-integrity-SGTKBL6W.js} +1 -1
  278. package/dist/{hook-prefetch-V5KCGW2N.js → hook-prefetch-BDSLB6F4.js} +26 -24
  279. package/dist/hooks/bug-report-worker.js +30 -28
  280. package/dist/hooks/codex-stop-task-finalizer.js +32 -30
  281. package/dist/hooks/commit-complete.js +29 -27
  282. package/dist/hooks/error-recall.js +11 -10
  283. package/dist/hooks/exe-heartbeat-hook.js +7 -6
  284. package/dist/hooks/ingest-worker.js +10 -9
  285. package/dist/hooks/ingest.js +18 -16
  286. package/dist/hooks/instructions-loaded.js +12 -8
  287. package/dist/hooks/manifest.json +20 -20
  288. package/dist/hooks/notification.js +8 -7
  289. package/dist/hooks/post-compact.js +7 -6
  290. package/dist/hooks/post-tool-combined.js +33 -14
  291. package/dist/hooks/pre-compact.js +31 -28
  292. package/dist/hooks/pre-tool-use.js +61 -34
  293. package/dist/hooks/prompt-submit.js +45 -42
  294. package/dist/hooks/session-end.js +40 -35
  295. package/dist/hooks/session-start.js +29 -71
  296. package/dist/hooks/stop.js +42 -36
  297. package/dist/hooks/subagent-stop.js +49 -33
  298. package/dist/hooks/summary-worker.js +35 -33
  299. package/dist/index.js +40 -38
  300. package/dist/{installer-G67M3DME.js → installer-EA7T3PBD.js} +7 -7
  301. package/dist/{installer-L3ZKMOPU.js → installer-KCEM4RCG.js} +7 -7
  302. package/dist/{installer-QBWZSYDG.js → installer-RC2JCKSE.js} +9 -7
  303. package/dist/{intercom-queue-R5LSXD3V.js → intercom-queue-6DZPHHQ2.js} +25 -23
  304. package/dist/{key-backup-status-3RC6XTUX.js → key-backup-status-6L2ARCXW.js} +1 -1
  305. package/dist/lib/agent-config.js +2 -2
  306. package/dist/lib/cloud-sync.js +25 -19
  307. package/dist/lib/config.js +1 -1
  308. package/dist/lib/consolidation.js +9 -9
  309. package/dist/lib/crypto.js +3 -1
  310. package/dist/lib/database.js +18 -4
  311. package/dist/lib/db-backup.js +4 -2
  312. package/dist/lib/db-daemon-client.js +5 -4
  313. package/dist/lib/db-write-worker.js +3 -3
  314. package/dist/lib/db.js +18 -4
  315. package/dist/lib/device-registry.js +2 -2
  316. package/dist/lib/embed-worker.js +1 -1
  317. package/dist/lib/embedder.js +5 -4
  318. package/dist/lib/employee-templates.js +7 -7
  319. package/dist/lib/employees.js +4 -4
  320. package/dist/lib/exe-daemon-client.js +4 -3
  321. package/dist/lib/exe-daemon.js +473 -206
  322. package/dist/lib/hybrid-search.js +8 -8
  323. package/dist/lib/identity-templates.js +1 -1
  324. package/dist/lib/identity.js +4 -4
  325. package/dist/lib/license.js +6 -4
  326. package/dist/lib/messaging.js +26 -24
  327. package/dist/lib/post-tool-memory.js +2 -2
  328. package/dist/lib/registry-proxy.js +3 -1
  329. package/dist/lib/reminders.js +5 -5
  330. package/dist/lib/schedules.js +8 -8
  331. package/dist/lib/session-registry.js +6 -6
  332. package/dist/lib/skill-learning.js +14 -12
  333. package/dist/lib/stack-invariants.js +21 -0
  334. package/dist/lib/status-brief.js +3 -3
  335. package/dist/lib/store.js +7 -7
  336. package/dist/lib/task-router.js +5 -5
  337. package/dist/lib/tasks.js +27 -25
  338. package/dist/lib/tmux-routing.js +25 -23
  339. package/dist/lib/token-spend.js +5 -5
  340. package/dist/{license-gate-6R3F3NQR.js → license-gate-HMSMUIAU.js} +5 -4
  341. package/dist/{license-sync-VBJCXBAP.js → license-sync-FBCMPC4J.js} +4 -3
  342. package/dist/{mailbox-inbox-CP2VRBXZ.js → mailbox-inbox-4MEUOL3J.js} +2 -2
  343. package/dist/{mailbox-outbox-HXI7DYNC.js → mailbox-outbox-J6WIF3UY.js} +1 -1
  344. package/dist/mcp/register-tools.js +114 -102
  345. package/dist/mcp/server.js +120 -108
  346. package/dist/mcp/tools/complete-reminder.js +6 -6
  347. package/dist/mcp/tools/create-reminder.js +6 -6
  348. package/dist/mcp/tools/create-task.js +30 -27
  349. package/dist/mcp/tools/deactivate-behavior.js +15 -13
  350. package/dist/mcp/tools/list-reminders.js +6 -6
  351. package/dist/mcp/tools/list-tasks.js +30 -27
  352. package/dist/mcp/tools/send-message.js +29 -26
  353. package/dist/mcp/tools/update-task.js +28 -26
  354. package/dist/{mcp-http-config-BWX7AMAZ.js → mcp-http-config-YKJN52HG.js} +5 -5
  355. package/dist/{mcp-session-identity-GOBGICLI.js → mcp-session-identity-QLUVYWPR.js} +25 -23
  356. package/dist/{memory-cards-4EXX3BQS.js → memory-cards-K2OSX4AW.js} +7 -7
  357. package/dist/{memory-graph-extractor-OYVZIJ3R.js → memory-graph-extractor-YLTSKLSY.js} +9 -8
  358. package/dist/{memory-poisoning-defense-AHOD2EBX.js → memory-poisoning-defense-IPO4ADRL.js} +4 -4
  359. package/dist/{memory-pressure-watchdog-COJC3OYS.js → memory-pressure-watchdog-ZI3TH2OF.js} +26 -24
  360. package/dist/{memory-queue-4VN3DTOE.js → memory-queue-QJNWKWKG.js} +2 -2
  361. package/dist/{memory-queue-client-OI4LDT4W.js → memory-queue-client-HIHCA3W4.js} +6 -5
  362. package/dist/{memory-reflection-V2SNAUIB.js → memory-reflection-ZKVRU3NT.js} +4 -4
  363. package/dist/{monitor-grants-W3ISOLMC.js → monitor-grants-BW2WL4IU.js} +3 -3
  364. package/dist/{notification-ledger-J6Q2GAYU.js → notification-ledger-4REL6M3T.js} +27 -25
  365. package/dist/{notifications-56TP36IK.js → notifications-UXJUAP3C.js} +25 -23
  366. package/dist/{orchestration-events-CKQ756NA.js → orchestration-events-GMRGGBMN.js} +5 -5
  367. package/dist/{orchestration-phase-ZJ4XVNFO.js → orchestration-phase-UDS24CVQ.js} +2 -2
  368. package/dist/{orchestrator-ERSQ3KNT.js → orchestrator-5TV75HDJ.js} +27 -25
  369. package/dist/{org-brief-session-FSZ3QJDN.js → org-brief-session-7AZZEO3F.js} +13 -13
  370. package/dist/{org-consent-policy-BN7MKCZ2.js → org-consent-policy-XT6UZENB.js} +2 -2
  371. package/dist/{org-join-HTQ57JYH.js → org-join-BMBQDMVF.js} +10 -7
  372. package/dist/org-key-admin-SCCVVE2H.js +38 -0
  373. package/dist/{org-key-pin-OTOKAELD.js → org-key-pin-5WUSPGVN.js} +3 -3
  374. package/dist/{org-membership-SML6GCHP.js → org-membership-ESALTLLN.js} +4 -4
  375. package/dist/{org-partner-config-XQGOB7EJ.js → org-partner-config-YKHPDPZR.js} +6 -6
  376. package/dist/{org-partner-runtime-RNNUL5IN.js → org-partner-runtime-WLZAZJIY.js} +14 -14
  377. package/dist/{org-row-visibility-JZFJ3YRJ.js → org-row-visibility-A5XM4MVO.js} +1 -1
  378. package/dist/{pipeline-router-IUENNTAY.js → pipeline-router-UEVXDUHR.js} +5 -5
  379. package/dist/{plan-limits-ZUII366B.js → plan-limits-QEP32X4W.js} +8 -7
  380. package/dist/{platform-procedures-2VQGBJU7.js → platform-procedures-44NDFTB6.js} +1 -1
  381. package/dist/{postinstall-failure-A7WTAXD6.js → postinstall-failure-B2JHNFPM.js} +2 -2
  382. package/dist/{project-boot-3DFY5F35.js → project-boot-LEZSK3W7.js} +9 -1
  383. package/dist/project-name-ILJTETUE.js +14 -0
  384. package/dist/{projection-worker-MBWUPB5O.js → projection-worker-GMRC6SBH.js} +4 -4
  385. package/dist/{prospective-memory-GHE5ORN4.js → prospective-memory-QLHSAPSG.js} +4 -4
  386. package/dist/{push-notifications-PTQ5DHCD.js → push-notifications-GKRPXOGI.js} +26 -24
  387. package/dist/reproject-worktree-memories-IXYGHTGA.js +224 -0
  388. package/dist/{reranker-NUIGAPA3.js → reranker-GB4VEMUV.js} +2 -2
  389. package/dist/{retrieval-health-XHCERQPG.js → retrieval-health-QIBPBGN6.js} +5 -1
  390. package/dist/{review-polling-CYBCBHZA.js → review-polling-MU47NQ2N.js} +26 -24
  391. package/dist/runtime/index.js +37 -35
  392. package/dist/seat-benefit-VDQJYJOB.js +17 -0
  393. package/dist/{session-events-X33W4JNA.js → session-events-YMQ3VGBJ.js} +26 -24
  394. package/dist/{session-kill-telemetry-I2F5PUIW.js → session-kill-telemetry-VGPKE57M.js} +26 -24
  395. package/dist/{session-mcp-config-YKNXG5DW.js → session-mcp-config-F7KATWOF.js} +10 -1
  396. package/dist/{session-scope-WXP4F7OF.js → session-scope-P46KAHXQ.js} +26 -24
  397. package/dist/{setup-wizard-2CTVITIX.js → setup-wizard-BRNHSJ4U.js} +4 -3
  398. package/dist/{shard-manager-YEOHEOHL.js → shard-manager-UROHZE46.js} +2 -2
  399. package/dist/{skill-refinement-VDR5OT4V.js → skill-refinement-NLYDSFEH.js} +5 -5
  400. package/dist/{stack-promote-XVC7FRJW.js → stack-promote-NHFTNIM2.js} +7 -5
  401. package/dist/{stack-release-6PWAACRM.js → stack-release-AQVKZUS7.js} +7 -5
  402. package/dist/{stack-update-24VRBQ3U.js → stack-update-LLX5KT7G.js} +38 -6
  403. package/dist/{steward-gate-2ZNYDVFO.js → steward-gate-JKHYFAHA.js} +5 -5
  404. package/dist/subagent-brief-injection-VDVOHDOU.js +97 -0
  405. package/dist/subagent-spawn-ledger-USYZXU3M.js +22 -0
  406. package/dist/{support-inbox-ZHKLDETT.js → support-inbox-S6YO5YZU.js} +5 -4
  407. package/dist/{support-outbox-DSWU3IXH.js → support-outbox-HFJWG52M.js} +1 -1
  408. package/dist/{task-enforcement-GQDUTIFY.js → task-enforcement-LXW45H5Z.js} +26 -24
  409. package/dist/{task-request-inbox-BFXNUU44.js → task-request-inbox-UCSKDC3R.js} +2 -2
  410. package/dist/{task-scope-ERHPHLUM.js → task-scope-GDLFE26E.js} +25 -23
  411. package/dist/{tasks-crud-NVNF3NRA.js → tasks-crud-W7VGWRUV.js} +25 -23
  412. package/dist/{tasks-notify-DXLU5VCT.js → tasks-notify-CT7NWCS4.js} +26 -24
  413. package/dist/{tasks-review-BST6N34P.js → tasks-review-WCSKOUFW.js} +25 -23
  414. package/dist/{telemetry-upload-NY3EKOGY.js → telemetry-upload-FK5WKXVB.js} +10 -9
  415. package/dist/{token-budget-CLB3GUIX.js → token-budget-GBNGSIU3.js} +5 -5
  416. package/dist/{tool-capability-index-WZWVBI2R.js → tool-capability-index-BT2GGLLI.js} +1 -1
  417. package/dist/{tool-telemetry-PALAZUXT.js → tool-telemetry-VKMRS6MP.js} +1 -1
  418. package/dist/tui/App.js +39 -37
  419. package/dist/{tui-data-CNFSRZP7.js → tui-data-FTPV2XBQ.js} +25 -23
  420. package/dist/{update-support-ZVB3CNGY.js → update-support-J6UWPUOC.js} +18 -15
  421. package/dist/{work-state-heartbeat-O7UMB6SE.js → work-state-heartbeat-4LDF6DSM.js} +1 -1
  422. package/dist/{worker-gate-6DQQWDNB.js → worker-gate-35VZINZG.js} +2 -2
  423. package/dist/{workflow-engine-5ZULFH7O.js → workflow-engine-4DA6RSHI.js} +2 -2
  424. package/dist/working-memory-DPFRSKH3.js +18 -0
  425. package/dist/{worktree-ZWA35PKW.js → worktree-H3YFJ3NU.js} +6 -6
  426. package/dist/{worktree-sweep-5LCVT46V.js → worktree-sweep-4EDLLQ2E.js} +9 -7
  427. package/package.json +11 -5
  428. package/release-notes.json +86 -34
  429. package/src/commands/exe/call.md +6 -4
  430. package/src/commands/exe-reference.md +11 -7
  431. package/src/commands/exe.md +16 -20
  432. package/dist/chunk-HISFWAAK.js +0 -90
  433. package/dist/daemon-auth-OCHW2U6B.js +0 -20
  434. package/dist/project-name-EWG5KAZP.js +0 -10
  435. package/dist/session-summary-5EYI4XW4.js +0 -128
  436. /package/dist/{chunk-YAZ5ECEG.js → chunk-3NBWARKC.js} +0 -0
  437. /package/dist/{chunk-6C4UAKMS.js → chunk-6CAX7YH4.js} +0 -0
  438. /package/dist/{chunk-FIPUVCKC.js → chunk-7DMCAGSC.js} +0 -0
  439. /package/dist/{chunk-X7LY3IOB.js → chunk-JRPYLUXH.js} +0 -0
  440. /package/dist/{chunk-L4LPJMPV.js → chunk-QYECRPGD.js} +0 -0
  441. /package/dist/{chunk-UELGUVNL.js → chunk-UY6HRDB2.js} +0 -0
  442. /package/dist/{chunk-YWN3XMPU.js → chunk-YSDIJ3VI.js} +0 -0
  443. /package/dist/{core-memory-BZFPVS43.js → core-memory-CMPPHJN5.js} +0 -0
  444. /package/dist/{entity-boost-VXXED26D.js → entity-boost-CDFAXVYL.js} +0 -0
  445. /package/dist/{memory-isolation-3PIOWLYP.js → memory-isolation-ZB5S5HCO.js} +0 -0
  446. /package/dist/{message-queue-client-5XW7ZOK2.js → message-queue-client-PL2POQOV.js} +0 -0
  447. /package/dist/{oauth-server-KW5M437C.js → oauth-server-NP7KGF7N.js} +0 -0
  448. /package/dist/{webhook-pipe-QJRFZVKA.js → webhook-pipe-UQSEHFPO.js} +0 -0
  449. /package/dist/{wiki-acl-RXFV5OEU.js → wiki-acl-5DKOWOIH.js} +0 -0
  450. /package/dist/{wiki-client-57QWZIHQ.js → wiki-client-AXNM5FWY.js} +0 -0
@@ -18,7 +18,7 @@ const EXE_BUILD_RELEASE_TAG = "v0.1.0";
18
18
  // Bug 58840873: pin exe-auth to the manifest-pinned tag so a generated .env never
19
19
  // lets compose fall back to its (stale) hardcoded default image. MUST equal the
20
20
  // AUTH_IMAGE_TAG tag in deploy/stack-manifests/v0.9.json and the compose default.
21
- const AUTH_RELEASE_TAG = "v0.2.0";
21
+ const AUTH_RELEASE_TAG = "v0.5.0";
22
22
 
23
23
  const POSTGRES_USER = "exe";
24
24
  const POSTGRES_DB = "exedb";
@@ -27,7 +27,11 @@ const CLICKHOUSE_USER = "exe";
27
27
  export const CRM_IMAGE_TAG = `${REGISTRY}/exe-crm:${CRM_RELEASE_TAG}`;
28
28
  const CRM_HOST_PORT = "3000";
29
29
  export const WIKI_IMAGE_TAG = `${REGISTRY}/exe-wiki:${WIKI_RELEASE_TAG}`;
30
- const WIKI_DB_SCHEMA = "wiki";
30
+ // Bug 2a8d3003: the wiki's data lives in `public` and generated .env files must say
31
+ // so. This constant used to be "wiki", which meant every freshly generated stack
32
+ // pinned the wiki to an empty schema — Prisma bootstraps it silently rather than
33
+ // erroring, so the wiki simply comes up with no users and no documents.
34
+ const WIKI_DB_SCHEMA = "public";
31
35
  const WIKI_VECTOR_DB = "exeOsMcp";
32
36
  const WIKI_HOST_PORT = "3001";
33
37
  export const EXE_OS_IMAGE_TAG = `${REGISTRY}/exe-os:${EXED_RELEASE_TAG}`;
@@ -42,6 +46,47 @@ const GATEWAY_WS_HOST_PORT = "3101";
42
46
  const RANDOM_SECRET_16 = 16;
43
47
  const RANDOM_SECRET_32 = 32;
44
48
  const RANDOM_SECRET_48 = 48;
49
+ // Centralized role administration (e51ca54c §10.6b). Emitted verbatim into both
50
+ // the generated customer .env and the checked-in .env.example so a fresh install
51
+ // STATES its permissions posture instead of inheriting one silently.
52
+ //
53
+ // exe-crm#102 / exe-wiki#45 make managed permissions REQUIRED by default in the
54
+ // apps, so an unmanaged first login can no longer mint its own admin. Those
55
+ // defaults are kept. What a fresh install needs in addition is a legitimate way
56
+ // to seat its FIRST admin, and for the CRM that way is EXE_CRM_ADMIN_EMAIL: the
57
+ // boot-time WorkspaceBootstrapService seeds that user, so the intended owner is
58
+ // an EXISTING user by the time they sign in and the first-login refusal never
59
+ // applies to them. Leave it empty and the seed silently throws (onModuleInit
60
+ // swallows it), after which nobody can ever get in.
61
+ const MANAGED_PERMS_ENV_BLOCK = [
62
+ "# --- Centralized role administration (permissions) ---",
63
+ "# The apps refuse to let an unmanaged first login mint its own admin",
64
+ "# (exe-crm#102 / exe-wiki#45). Both flags below are therefore left at their",
65
+ "# SECURE default of true, stated explicitly rather than inherited silently.",
66
+ "#",
67
+ "# Because of that, a fresh CRM gets its first admin from EXE_CRM_ADMIN_EMAIL",
68
+ "# in the CRM section below — NOT from whoever signs in first. That user is",
69
+ "# seeded at boot, so their sign-in is an existing-user login and is allowed;",
70
+ "# every OTHER unmanaged first login is still refused. If you leave",
71
+ "# EXE_CRM_ADMIN_EMAIL empty, the seed silently does not happen and the CRM",
72
+ "# cannot be bootstrapped at all. The wiki does not need this: its first admin",
73
+ "# comes from POST /system/enable-multi-user, authorised by WIKI_AUTH_TOKEN.",
74
+ "CRM_REQUIRE_MANAGED_PERMS=true",
75
+ "WIKI_REQUIRE_MANAGED_PERMS=true",
76
+ "#",
77
+ "# MANAGED MODE — roles set ONLY in the Exe dashboard. Empty means unmanaged:",
78
+ "# no token can carry permissions for an org that was never named. To adopt it:",
79
+ "# EXE_ORG_ID=<org id from the Exe dashboard>",
80
+ "# EXE_ORG_WORKSPACE_ID=<the org's canonical ACTIVE CRM workspace>",
81
+ "# EXE_ORG_WORKSPACE_ID must ALREADY EXIST and have a legitimate admin, because",
82
+ "# managed users join THAT workspace instead of creating their own. That is why",
83
+ "# managed mode cannot be the shipped default: on a fresh stack the workspace",
84
+ "# does not exist yet, so its id is unknowable at install time. Bootstrap the",
85
+ "# stack first, then set these and redeploy. The wiki needs only EXE_ORG_ID.",
86
+ "EXE_ORG_ID=",
87
+ "EXE_ORG_WORKSPACE_ID=",
88
+ ];
89
+
45
90
  const LICENSE_KEY_COMMENT = "# injected by deploy_client";
46
91
  const MANUAL_VALUE_COMMENT = "# SET_MANUALLY";
47
92
  const EXAMPLE_LICENSE_KEY = "CHANGEME_EXE_LICENSE_KEY";
@@ -165,15 +210,34 @@ export function generateEnv(options: GenerateEnvOptions): string {
165
210
  "# resolved GOTRUE_DISABLE_SIGNUP value above (bug d6f980c1).",
166
211
  `AUTH_DISABLE_SIGNUP=${disableSignup}`,
167
212
  "",
213
+ ...MANAGED_PERMS_ENV_BLOCK,
214
+ "",
168
215
  "# --- CRM ---",
169
216
  `CRM_IMAGE_TAG=${CRM_IMAGE_TAG}`,
170
217
  `CRM_SERVER_URL=https://crm.${normalizedDomain}`,
171
218
  `CRM_APP_SECRET=${randomSecret(RANDOM_SECRET_48)}`,
172
219
  `EXE_CRM_ADMIN_TOKEN=${randomSecret(RANDOM_SECRET_48)}`,
220
+ // The CRM's first admin. Seeded into core."user" at boot by
221
+ // WorkspaceBootstrapService, which is what makes that person an EXISTING
222
+ // user whose sign-in is allowed while every other unmanaged first login is
223
+ // refused (e51ca54c §10.6b). It cannot be generated — it must be a real
224
+ // mailbox that also exists in GoTrue — so it ships as SET_MANUALLY rather
225
+ // than as a random secret or a guessed admin@<domain>.
226
+ MANUAL_VALUE_COMMENT,
227
+ "EXE_CRM_ADMIN_EMAIL=CHANGEME_ADMIN_EMAIL",
173
228
  `CRM_HOST_PORT=${CRM_HOST_PORT}`,
174
229
  "",
175
230
  "# --- Wiki ---",
176
231
  `WIKI_IMAGE_TAG=${WIKI_IMAGE_TAG}`,
232
+ "# Bug 2a8d3003 — DO NOT change this without migrating the data first.",
233
+ "# The wiki's users / workspaces / workspace_documents live in `public`. Point it at a",
234
+ "# different schema and Prisma will NOT error: it finds no tables, assumes a fresh",
235
+ "# database, creates empty ones, and the wiki comes up with zero documents and the",
236
+ "# admin locked out. It looks exactly like catastrophic data loss while every row is",
237
+ "# still sitting safely in `public`. That misread has a very expensive next step —",
238
+ "# restoring a backup over good data. This value used to default to `wiki`, and the",
239
+ "# only thing holding production together was a host-local compose override.",
240
+ "# exe-wiki-schema-guard blocks the boot if you get this wrong.",
177
241
  `WIKI_DB_SCHEMA=${WIKI_DB_SCHEMA}`,
178
242
  `WIKI_VECTOR_DB=${WIKI_VECTOR_DB}`,
179
243
  `WIKI_AUTH_TOKEN=${randomSecret(RANDOM_SECRET_48)}`,
@@ -415,19 +479,41 @@ export function generateExampleEnv(): string {
415
479
  "AUTH_SERVER_NAME=auth.CHANGEME_DOMAIN",
416
480
  "AUTH_CORS_ORIGIN=https://CHANGEME_DOMAIN",
417
481
  "AUTH_DEFAULT_PRODUCT=AUTH",
482
+ '# Brand word after the gold "EXE" in the login logo (exe-auth v0.5.0+). One',
483
+ "# identity for the whole front door — ?product= never rebrands the page. Leave",
484
+ '# unset for the stack default "EXE INTELLIGENCE"; white-label deploys set their',
485
+ "# own word here.",
486
+ "#AUTH_BRAND_NAME=INTELLIGENCE",
418
487
  "# Bug 5960ebb1: invite-only — mirror GOTRUE_DISABLE_SIGNUP so the auth SPA hides",
419
488
  "# the sign-up UI/route instead of showing a form that always fails.",
420
489
  "AUTH_DISABLE_SIGNUP=true",
421
490
  "",
491
+ ...MANAGED_PERMS_ENV_BLOCK,
492
+ "",
422
493
  "# --- CRM ---",
423
494
  `CRM_IMAGE_TAG=${CRM_IMAGE_TAG}`,
424
495
  "CRM_SERVER_URL=https://crm.CHANGEME_DOMAIN",
425
496
  "CRM_APP_SECRET=CHANGEME_CRM_APP_SECRET",
426
497
  "EXE_CRM_ADMIN_TOKEN=CHANGEME_EXE_CRM_ADMIN_TOKEN",
498
+ "# The CRM's first admin — seeded at boot by WorkspaceBootstrapService, which",
499
+ "# is what makes that person an EXISTING user whose sign-in is allowed while",
500
+ "# every other unmanaged first login is refused (e51ca54c §10.6b). Must be a",
501
+ "# real mailbox that also exists in GoTrue, so it cannot be generated.",
502
+ MANUAL_VALUE_COMMENT,
503
+ "EXE_CRM_ADMIN_EMAIL=CHANGEME_ADMIN_EMAIL",
427
504
  `CRM_HOST_PORT=${CRM_HOST_PORT}`,
428
505
  "",
429
506
  "# --- Wiki ---",
430
507
  `WIKI_IMAGE_TAG=${WIKI_IMAGE_TAG}`,
508
+ "# Bug 2a8d3003 — DO NOT change this without migrating the data first.",
509
+ "# The wiki's users / workspaces / workspace_documents live in `public`. Point it at a",
510
+ "# different schema and Prisma will NOT error: it finds no tables, assumes a fresh",
511
+ "# database, creates empty ones, and the wiki comes up with zero documents and the",
512
+ "# admin locked out. It looks exactly like catastrophic data loss while every row is",
513
+ "# still sitting safely in `public`. That misread has a very expensive next step —",
514
+ "# restoring a backup over good data. This value used to default to `wiki`, and the",
515
+ "# only thing holding production together was a host-local compose override.",
516
+ "# exe-wiki-schema-guard blocks the boot if you get this wrong.",
431
517
  `WIKI_DB_SCHEMA=${WIKI_DB_SCHEMA}`,
432
518
  `WIKI_VECTOR_DB=${WIKI_VECTOR_DB}`,
433
519
  "WIKI_AUTH_TOKEN=CHANGEME_WIKI_AUTH_TOKEN",
@@ -47,6 +47,36 @@ docker exec exe-db psql -U exe -d exedb -c "SELECT schemaname, COUNT(*) as table
47
47
 
48
48
  echo ""
49
49
 
50
+ # Bug 2a8d3003: which schema is the wiki ACTUALLY running against?
51
+ # exe-wiki-schema-guard gates `docker compose up`, but compose dependencies are not
52
+ # evaluated by `docker start exe-wiki` or by restart:unless-stopped after a reboot —
53
+ # so a wiki can still come back on the wrong schema without the guard ever running.
54
+ # This makes that visible on demand. Read from the LIVE container, never from .env:
55
+ # a docker-compose.override.yml can rewrite DATABASE_URL and .env would not show it.
56
+ echo "Wiki schema (bug 2a8d3003):"
57
+ WIKI_URL=$(docker inspect exe-wiki --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | sed -n 's/^DATABASE_URL=//p')
58
+ if [[ -z "$WIKI_URL" ]]; then
59
+ echo " ⚠️ exe-wiki not running — cannot determine schema"
60
+ else
61
+ WIKI_SCHEMA=$(printf '%s' "$WIKI_URL" | sed -n 's/.*[?&]schema=\([^&]*\).*/\1/p')
62
+ [[ -z "$WIKI_SCHEMA" ]] && WIKI_SCHEMA="public"
63
+ COUNTS=$(docker exec exe-db psql -U exe -d exedb -tA -F' ' -c \
64
+ "SELECT COALESCE((SELECT count(*) FROM ${WIKI_SCHEMA}.users), -1),
65
+ COALESCE((SELECT count(*) FROM ${WIKI_SCHEMA}.workspace_documents), -1)" 2>/dev/null)
66
+ W_USERS=$(echo "$COUNTS" | awk '{print $1}')
67
+ W_DOCS=$(echo "$COUNTS" | awk '{print $2}')
68
+ if [[ "${W_USERS:-0}" -gt 0 || "${W_DOCS:-0}" -gt 0 ]]; then
69
+ echo -e " ${GREEN}✅${NC} schema '$WIKI_SCHEMA' — users=$W_USERS documents=$W_DOCS"
70
+ else
71
+ echo -e " ${RED}❌${NC} schema '$WIKI_SCHEMA' has NO users and NO documents — the wiki is"
72
+ echo -e " serving an EMPTY schema. This is a config fault, not data loss."
73
+ echo -e " ${YELLOW}DO NOT RESTORE FROM BACKUP.${NC} Check WIKI_DB_SCHEMA in .env and any"
74
+ echo -e " docker-compose.override.yml, then: docker compose up -d exe-wiki"
75
+ fi
76
+ fi
77
+
78
+ echo ""
79
+
50
80
  # Disk/Memory
51
81
  echo "Resources:"
52
82
  echo " RAM: $(free -h 2>/dev/null | awk '/^Mem:/ {print $3 "/" $2}' || echo 'N/A')"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # exe-os uptime check — runs every 5 min via cron.
3
- # Checks: CRM, Gateway, WhatsApp state, Postgres.
3
+ # Checks: CRM, Gateway, WhatsApp state, Postgres, wiki schema (bug 2a8d3003).
4
4
  # Writes JSONL to /opt/exe-stack/uptime.jsonl.
5
5
  # Logs ALERT to stderr (captured by cron/syslog) on any failure.
6
6
  #
@@ -92,10 +92,40 @@ check_whatsapp() {
92
92
  # --- Run checks ---
93
93
  NOW=$(ts)
94
94
 
95
+ # Bug 2a8d3003: is the wiki serving the schema its data is actually in?
96
+ # exe-wiki-schema-guard gates `docker compose up`, but nothing evaluates compose
97
+ # dependencies on `docker start exe-wiki` or on restart:unless-stopped after a
98
+ # reboot. Those paths can bring the wiki back on an empty schema with no guard run
99
+ # and no error anywhere — silent-empty, which is the whole failure mode. Checking it
100
+ # here bounds how long that can go unnoticed to one cron interval.
101
+ # Reads the LIVE container env, not .env: a compose override can rewrite DATABASE_URL.
102
+ check_wiki_schema() {
103
+ local url schema counts users docs
104
+ url=$(docker inspect exe-wiki --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null \
105
+ | sed -n 's/^DATABASE_URL=//p')
106
+ [[ -z "$url" ]] && { echo "skip"; return; }
107
+ schema=$(printf '%s' "$url" | sed -n 's/.*[?&]schema=\([^&]*\).*/\1/p')
108
+ [[ -z "$schema" ]] && schema="public"
109
+ # Only ever interpolate a bare identifier into SQL.
110
+ [[ ! "$schema" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] && { echo "fail:bad-schema-name"; return; }
111
+ counts=$(docker exec exe-db psql -U "${POSTGRES_USER:-exe}" -d "${POSTGRES_DB:-exedb}" -tA -F' ' -c \
112
+ "SELECT COALESCE((SELECT count(*) FROM ${schema}.users), 0),
113
+ COALESCE((SELECT count(*) FROM ${schema}.workspace_documents), 0)" 2>/dev/null)
114
+ [[ -z "$counts" ]] && { echo "skip"; return; }
115
+ users=$(echo "$counts" | awk '{print $1}')
116
+ docs=$(echo "$counts" | awk '{print $2}')
117
+ if [[ "${users:-0}" -gt 0 || "${docs:-0}" -gt 0 ]]; then
118
+ echo "ok:$schema"
119
+ else
120
+ echo "fail:empty-schema-$schema"
121
+ fi
122
+ }
123
+
95
124
  crm_status=$(check_http "CRM" "http://127.0.0.1:3000/healthz")
96
125
  gateway_status=$(check_http "Gateway" "http://127.0.0.1:3100/health")
97
126
  whatsapp_status=$(check_whatsapp)
98
127
  postgres_status=$(check_postgres)
128
+ wiki_schema_status=$(check_wiki_schema)
99
129
 
100
130
  # --- Determine overall health ---
101
131
  overall="ok"
@@ -117,11 +147,18 @@ if [[ "$postgres_status" != "ok" ]]; then
117
147
  overall="critical"
118
148
  alerts="${alerts}Postgres($postgres_status) "
119
149
  fi
150
+ # A wiki serving an empty schema is critical: it looks like total data loss to
151
+ # whoever opens it, and the wrong reflex (restore from backup) destroys the real
152
+ # rows still sitting in the other schema. Say so in the alert itself.
153
+ if [[ "$wiki_schema_status" == fail:* ]]; then
154
+ overall="critical"
155
+ alerts="${alerts}WikiSchema($wiki_schema_status — CONFIG FAULT, DO NOT RESTORE FROM BACKUP, see bug 2a8d3003) "
156
+ fi
120
157
 
121
158
  # --- Write JSONL ---
122
159
  # Single-line JSON — no jq dependency required
123
160
  ENTRY=$(cat <<JSONEOF
124
- {"ts":"${NOW}","overall":"${overall}","crm":"${crm_status}","gateway":"${gateway_status}","whatsapp":"${whatsapp_status}","postgres":"${postgres_status}"}
161
+ {"ts":"${NOW}","overall":"${overall}","crm":"${crm_status}","gateway":"${gateway_status}","whatsapp":"${whatsapp_status}","postgres":"${postgres_status}","wiki_schema":"${wiki_schema_status}"}
125
162
  JSONEOF
126
163
  )
127
164
 
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env bash
2
+ # exe-wiki schema guard — bug 2a8d3003.
3
+ #
4
+ # THE FAILURE THIS EXISTS TO PREVENT
5
+ # ----------------------------------
6
+ # exe-wiki reaches Postgres through a DATABASE_URL whose `?schema=` param decides
7
+ # which schema it lives in. Production's real content (users, workspaces,
8
+ # workspace_documents) is in `public`. A stale default of `?schema=wiki` — or a
9
+ # WIKI_DB_SCHEMA typo, or a dropped docker-compose.override.yml — points the wiki at
10
+ # a DIFFERENT schema. Prisma does not complain: it sees no tables, treats it as a
11
+ # fresh database, bootstraps empty ones, and the wiki comes up looking completely
12
+ # wiped. Admin locked out, zero documents. The real rows are fine, sitting in the
13
+ # other schema, but anyone debugging that at speed reaches for a restore and turns a
14
+ # config mistake into actual data loss.
15
+ #
16
+ # Silent-empty is the failure mode. This script makes it loud.
17
+ #
18
+ # WHAT IT DOES
19
+ # ------------
20
+ # Runs as a one-shot container BEFORE exe-wiki starts (compose service
21
+ # exe-wiki-schema-guard, gated by service_completed_successfully). It:
22
+ # 1. Parses the RESOLVED schema out of the same DATABASE_URL exe-wiki will use.
23
+ # 2. Logs that schema plus live user / workspace / document counts.
24
+ # 3. Compares them against the last passing boot, recorded in
25
+ # `meta.wiki_schema_guard` — deliberately OUTSIDE the wiki's own schema, so
26
+ # flipping schemas cannot also flip the evidence.
27
+ # 4. Exits non-zero, with the remediation spelled out, if the wiki is about to
28
+ # boot into a schema with zero workspaces when the last boot recorded more.
29
+ #
30
+ # It never writes to wiki tables and never drops anything. Worst case it refuses to
31
+ # let the wiki start, which is strictly better than presenting an empty wiki.
32
+ #
33
+ # ESCAPE HATCH
34
+ # ------------
35
+ # A genuine, intentional schema migration will trip the guard — that is the point.
36
+ # Move the data across first and the guard passes on its own (the new schema is no
37
+ # longer empty). If you really do mean to start empty, WIKI_SCHEMA_GUARD=warn for a
38
+ # single boot logs the warning, proceeds, and adopts the new counts as the baseline.
39
+ # WIKI_SCHEMA_GUARD=off disables the check entirely and is not recommended.
40
+ #
41
+ # Required env: DATABASE_URL (same value exe-wiki receives), PGPASSWORD.
42
+ # Optional env: WIKI_SCHEMA_GUARD=enforce|warn|off (default: enforce)
43
+
44
+ set -uo pipefail
45
+
46
+ MODE="${WIKI_SCHEMA_GUARD:-enforce}"
47
+ LOG_PREFIX="[wiki-schema-guard]"
48
+
49
+ log() { echo "$LOG_PREFIX $*"; }
50
+
51
+ banner() {
52
+ echo "" >&2
53
+ echo "$LOG_PREFIX ============================================================" >&2
54
+ echo "$LOG_PREFIX $1" >&2
55
+ echo "$LOG_PREFIX ============================================================" >&2
56
+ while IFS= read -r line; do echo "$LOG_PREFIX $line" >&2; done <<< "$2"
57
+ echo "$LOG_PREFIX ============================================================" >&2
58
+ echo "" >&2
59
+ }
60
+
61
+ # Hard stop: the guard cannot do its job at all (bad config, no database). Never
62
+ # downgraded by warn mode — a guard that cannot verify must not vouch.
63
+ die() {
64
+ banner "REFUSING TO START exe-wiki" "$1"
65
+ exit 1
66
+ }
67
+
68
+ # The silent-empty tripwire. warn mode logs and lets the boot proceed so an operator
69
+ # can deliberately accept a new state; enforce mode (the default) stops the boot.
70
+ trip() {
71
+ if [[ "$MODE" == "enforce" ]]; then
72
+ banner "REFUSING TO START exe-wiki — WIKI WOULD COME UP EMPTY" "$1"
73
+ exit 1
74
+ fi
75
+ banner "WARNING: WIKI WILL COME UP EMPTY" "$1"
76
+ log "WIKI_SCHEMA_GUARD=$MODE — proceeding anyway and accepting the new counts as the baseline."
77
+ }
78
+
79
+ if [[ "$MODE" == "off" ]]; then
80
+ log "WIKI_SCHEMA_GUARD=off — skipping. The wiki may boot against an empty schema undetected."
81
+ exit 0
82
+ fi
83
+
84
+ if [[ -z "${DATABASE_URL:-}" ]]; then
85
+ die "DATABASE_URL is empty. The guard cannot determine which schema exe-wiki
86
+ would use, so it cannot prove the wiki will find its data."
87
+ fi
88
+
89
+ # --- Resolve the schema exactly the way the wiki's client will ---
90
+ # postgres://user:pass@host:port/db?schema=NAME&sslmode=disable
91
+ SCHEMA="$(printf '%s' "$DATABASE_URL" | sed -n 's/.*[?&]schema=\([^&]*\).*/\1/p')"
92
+ if [[ -z "$SCHEMA" ]]; then
93
+ # No explicit param: Postgres/Prisma fall back to the role search_path, which in
94
+ # practice resolves to `public`. Record that we INFERRED it — an inferred schema is
95
+ # exactly the ambiguity bug 2a8d3003 was about.
96
+ SCHEMA="public"
97
+ RESOLUTION="inferred (no ?schema= param in DATABASE_URL — resolution depends on the role search_path)"
98
+ else
99
+ RESOLUTION="explicit (?schema=$SCHEMA)"
100
+ fi
101
+
102
+ # Reject anything that is not a plain identifier before it reaches SQL.
103
+ if [[ ! "$SCHEMA" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
104
+ die "Resolved schema '$SCHEMA' is not a valid bare identifier.
105
+ Refusing to interpolate it into SQL. Check WIKI_DB_SCHEMA in .env."
106
+ fi
107
+
108
+ log "resolved schema: $SCHEMA [$RESOLUTION]"
109
+
110
+ # libpq does NOT understand `schema=` as a URI parameter — Prisma consumes it before
111
+ # connecting, but psql errors out with "invalid URI query parameter". Strip it, then
112
+ # tidy up the leftover separators so the remaining query string stays well-formed.
113
+ PSQL_URL="$(printf '%s' "$DATABASE_URL" \
114
+ | sed -e 's/\([?&]\)schema=[^&]*&/\1/' -e 's/[?&]schema=[^&]*$//')"
115
+
116
+ psql_q() {
117
+ psql "$PSQL_URL" -X -q -t -A -v ON_ERROR_STOP=1 -c "$1" 2>/dev/null
118
+ }
119
+
120
+ # Compose already gates this service on exe-db being healthy, but "healthy" and
121
+ # "accepting this connection right now" are not the same thing. This container runs
122
+ # with restart: "no", so a single transient refusal would leave the wiki down until
123
+ # someone noticed. Retry before giving up.
124
+ CONNECTED=0
125
+ for attempt in 1 2 3 4 5; do
126
+ if psql_q "SELECT 1" >/dev/null; then CONNECTED=1; break; fi
127
+ log "postgres not answering yet (attempt $attempt/5) — retrying in 3s"
128
+ sleep 3
129
+ done
130
+ if [[ "$CONNECTED" -ne 1 ]]; then
131
+ die "Cannot reach Postgres with the wiki's DATABASE_URL after 5 attempts. The guard
132
+ cannot verify the schema, so it will not vouch for the wiki's data."
133
+ fi
134
+
135
+ # --- Counting ----------------------------------------------------------------
136
+ # Count a table, or emit "-" when the table does not exist yet. "-" and 0 are both
137
+ # "empty" for the tripwire, but worth distinguishing in the log: "-" means Prisma
138
+ # has not bootstrapped this schema at all yet.
139
+ show() { if [[ "$1" == "-" ]]; then printf 'no-such-table'; else printf '%s' "$1"; fi; }
140
+ n_of() { if [[ "$1" == "-" ]]; then printf '0'; else printf '%s' "$1"; fi; }
141
+
142
+ count_in() {
143
+ local schema="$1" table="$2" out
144
+ out="$(psql_q "SELECT CASE WHEN to_regclass('${schema}.${table}') IS NULL THEN '-'
145
+ ELSE (SELECT count(*)::text FROM ${schema}.${table}) END")"
146
+ printf '%s' "${out:--}"
147
+ }
148
+
149
+ USERS="$(count_in "$SCHEMA" users)"
150
+ WORKSPACES="$(count_in "$SCHEMA" workspaces)"
151
+ DOCUMENTS="$(count_in "$SCHEMA" workspace_documents)"
152
+
153
+ log "live counts in '$SCHEMA': users=$(show "$USERS") workspaces=$(show "$WORKSPACES") workspace_documents=$(show "$DOCUMENTS")"
154
+
155
+ # --- What counts as "the wiki really lives here" -------------------------------
156
+ # Deliberately NOT `workspaces`. deploy/compose/init-db.sql creates its OWN
157
+ # wiki.workspaces / wiki.workspace_documents projection tables for exe-os's
158
+ # projection-worker, and the worker writes rows into them. So the vestigial `wiki`
159
+ # schema on production reports workspaces=1 while containing none of the Prisma
160
+ # wiki's actual content. A tripwire keyed on `workspaces` would wave that through —
161
+ # the exact case this guard exists to catch.
162
+ #
163
+ # `users` is the honest signal: it holds the admin account, exe-os never writes it,
164
+ # and zero users means nobody can log in. `workspace_documents` corroborates.
165
+ OCCUPIED=0
166
+ if [[ "$(n_of "$USERS")" -gt 0 || "$(n_of "$DOCUMENTS")" -gt 0 ]]; then OCCUPIED=1; fi
167
+
168
+ # --- Stateless cross-schema check ----------------------------------------------
169
+ # The strongest evidence needs no history: if THIS schema is empty but another
170
+ # schema in the same database holds a populated wiki, we are pointed at the wrong
171
+ # one right now. This fires on the very first run, before any watermark exists —
172
+ # which matters, because a guard whose first boot lands on the empty schema would
173
+ # otherwise record that as its baseline and bless it forever.
174
+ RIVAL=""; RIVAL_USERS=0; RIVAL_DOCS=0
175
+ if [[ "$OCCUPIED" -eq 0 ]]; then
176
+ # Candidate = any schema carrying BOTH wiki tables. Counting rows in a
177
+ # dynamically-named schema needs one query per candidate, so enumerate first.
178
+ CANDIDATES="$(psql_q "SELECT table_schema
179
+ FROM information_schema.tables
180
+ WHERE table_name IN ('users', 'workspace_documents')
181
+ AND table_schema NOT IN ('pg_catalog', 'information_schema', '${SCHEMA}')
182
+ AND table_schema NOT LIKE 'pg\\_%'
183
+ GROUP BY table_schema
184
+ HAVING count(DISTINCT table_name) = 2
185
+ ORDER BY table_schema")"
186
+ while IFS= read -r cand; do
187
+ [[ -z "$cand" ]] && continue
188
+ [[ ! "$cand" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] && continue
189
+ c_users="$(n_of "$(count_in "$cand" users)")"
190
+ c_docs="$(n_of "$(count_in "$cand" workspace_documents)")"
191
+ if [[ "$c_users" -gt 0 || "$c_docs" -gt 0 ]]; then
192
+ RIVAL="$cand"; RIVAL_USERS="$c_users"; RIVAL_DOCS="$c_docs"
193
+ break
194
+ fi
195
+ done <<< "$CANDIDATES"
196
+ fi
197
+
198
+ # --- Watermark, stored outside the wiki's schema -------------------------------
199
+ # State inside the wiki's own schema would vanish along with the data the moment
200
+ # someone flips the schema, leaving nothing to compare against.
201
+ #
202
+ # Every bookkeeping statement is checked. Under `set -uo pipefail` (no -e) an
203
+ # unchecked psql failure would sail past and the guard would print OK having
204
+ # verified nothing — a guard that fails open silently is worse than no guard.
205
+ run_sql() {
206
+ local sql="$1" err
207
+ if ! err="$(psql "$PSQL_URL" -X -q -t -A -v ON_ERROR_STOP=1 -c "$sql" 2>&1)"; then
208
+ log "WARNING: guard bookkeeping failed — $err"
209
+ return 1
210
+ fi
211
+ return 0
212
+ }
213
+
214
+ WATERMARK_OK=1
215
+ run_sql "CREATE SCHEMA IF NOT EXISTS meta" || WATERMARK_OK=0
216
+ run_sql "CREATE TABLE IF NOT EXISTS meta.wiki_schema_guard (
217
+ id boolean PRIMARY KEY DEFAULT true CHECK (id),
218
+ schema_name text NOT NULL,
219
+ users bigint NOT NULL,
220
+ workspaces bigint NOT NULL,
221
+ documents bigint NOT NULL,
222
+ observed_at timestamptz NOT NULL DEFAULT now()
223
+ )" || WATERMARK_OK=0
224
+
225
+ PREV=""; PREV_SCHEMA=""; PREV_USERS=0; PREV_WORKSPACES=0; PREV_DOCUMENTS=0
226
+ if [[ "$WATERMARK_OK" -eq 1 ]]; then
227
+ PREV="$(psql_q "SELECT schema_name || '|' || users || '|' || workspaces || '|' || documents
228
+ FROM meta.wiki_schema_guard WHERE id")"
229
+ fi
230
+ if [[ -n "$PREV" ]]; then
231
+ PREV_SCHEMA="${PREV%%|*}"; REST="${PREV#*|}"
232
+ PREV_USERS="${REST%%|*}"; REST="${REST#*|}"
233
+ PREV_WORKSPACES="${REST%%|*}"
234
+ PREV_DOCUMENTS="${REST##*|}"
235
+ log "last recorded: schema='$PREV_SCHEMA' users=$PREV_USERS workspaces=$PREV_WORKSPACES workspace_documents=$PREV_DOCUMENTS"
236
+ elif [[ "$WATERMARK_OK" -eq 1 ]]; then
237
+ log "no previous record — first guard run against this database."
238
+ fi
239
+
240
+ # --- The tripwire ---------------------------------------------------------------
241
+ EVIDENCE=""; SUGGEST=""
242
+ if [[ "$OCCUPIED" -eq 0 && -n "$RIVAL" ]]; then
243
+ EVIDENCE="Schema '$RIVAL' in this SAME database does hold a wiki: users=$RIVAL_USERS,
244
+ workspace_documents=$RIVAL_DOCS. That is almost certainly where your data is."
245
+ SUGGEST="$RIVAL"
246
+ elif [[ "$OCCUPIED" -eq 0 && -n "$PREV_SCHEMA" ]] \
247
+ && [[ "$PREV_USERS" -gt 0 || "$PREV_DOCUMENTS" -gt 0 ]]; then
248
+ EVIDENCE="A previous boot saw users=$PREV_USERS and workspace_documents=$PREV_DOCUMENTS
249
+ in schema '$PREV_SCHEMA'."
250
+ SUGGEST="$PREV_SCHEMA"
251
+ fi
252
+
253
+ if [[ -n "$EVIDENCE" ]]; then
254
+ trip "exe-wiki is about to start against schema '$SCHEMA', which holds NO wiki
255
+ account and NO documents (users=$(show "$USERS"), workspace_documents=$(show "$DOCUMENTS")).
256
+ The wiki would come up looking completely wiped, with the admin locked out.
257
+
258
+ $EVIDENCE
259
+
260
+ This is a CONFIGURATION problem, NOT data loss. DO NOT RESTORE FROM BACKUP — the
261
+ rows are still there, and restoring over them is how this becomes real data loss.
262
+
263
+ Fix the pointer instead:
264
+ 1. Set WIKI_DB_SCHEMA=$SUGGEST in /opt/exe-stack/.env
265
+ 2. Check for a docker-compose.override.yml that rewrites exe-wiki's DATABASE_URL.
266
+ 3. Verify: docker compose config | grep -A2 'exe-wiki' | grep DATABASE_URL
267
+ 4. Re-run. The guard clears as soon as the wiki points at its data again.
268
+
269
+ Note: 'workspaces' is NOT proof of life here — exe-os keeps its own projection
270
+ tables in the 'wiki' schema (deploy/compose/init-db.sql), so a schema can report
271
+ workspaces>0 while holding none of the wiki's real content.
272
+
273
+ If you are INTENTIONALLY migrating to '$SCHEMA', move the rows first and the guard
274
+ clears itself. To accept an empty wiki for exactly one boot:
275
+ WIKI_SCHEMA_GUARD=warn docker compose up -d exe-wiki"
276
+ fi
277
+
278
+ if [[ -n "$PREV_SCHEMA" && "$PREV_SCHEMA" != "$SCHEMA" && "$OCCUPIED" -eq 1 ]]; then
279
+ log "NOTE: schema changed '$PREV_SCHEMA' -> '$SCHEMA' and the new schema is populated. Accepting."
280
+ fi
281
+
282
+ # --- Record what we saw ---------------------------------------------------------
283
+ # Last-observed, NOT a ratchet. A ratchet would wedge a wiki whose content was
284
+ # legitimately all deleted: it would trip forever with no way back. Recording the
285
+ # real counts means one WIKI_SCHEMA_GUARD=warn boot clears a deliberate emptying,
286
+ # while an accidental schema flip still trips on the very next start.
287
+ if [[ "$WATERMARK_OK" -eq 1 ]]; then
288
+ run_sql "INSERT INTO meta.wiki_schema_guard (id, schema_name, users, workspaces, documents, observed_at)
289
+ VALUES (true, '${SCHEMA}', $(n_of "$USERS"), $(n_of "$WORKSPACES"), $(n_of "$DOCUMENTS"), now())
290
+ ON CONFLICT (id) DO UPDATE SET
291
+ schema_name = EXCLUDED.schema_name,
292
+ users = EXCLUDED.users,
293
+ workspaces = EXCLUDED.workspaces,
294
+ documents = EXCLUDED.documents,
295
+ observed_at = now()" \
296
+ || log "WARNING: watermark not recorded — the cross-schema check still applies next boot."
297
+ else
298
+ log "WARNING: watermark table unavailable — relying on the cross-schema check alone."
299
+ fi
300
+
301
+ log "OK — exe-wiki cleared to start against schema '$SCHEMA'."
302
+ exit 0