@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.
- package/deploy/compose/.env.customer.example +46 -2
- package/deploy/compose/.env.example +49 -2
- package/deploy/compose/cloudflared/config.yml.example +15 -8
- package/deploy/compose/docker-compose.yml +240 -34
- package/deploy/compose/generate-env.ts +88 -2
- package/deploy/compose/status.sh +30 -0
- package/deploy/compose/uptime-check.sh +39 -2
- package/deploy/compose/wiki-schema-guard.sh +302 -0
- package/deploy/stack-manifests/v0.9.json +232 -2
- package/dist/.dist-verified.json +3 -3
- package/dist/{active-agent-BPNEBKHI.js → active-agent-C6DPH43L.js} +10 -5
- package/dist/{active-agent-3FV5V7SS.js → active-agent-MGF7J3HB.js} +10 -5
- package/dist/agent-defs-generator-X7TVMPVB.js +240 -0
- package/dist/{agentic-ontology-RCVCJ6WB.js → agentic-ontology-CARDCG37.js} +1 -1
- package/dist/artifact-backup-767STHSW.js +209 -0
- package/dist/artifact-provenance-C7HQ267L.js +13 -0
- package/dist/{backfill-gate-LRX77SUD.js → backfill-gate-VX2RZTQV.js} +2 -2
- package/dist/{backfill-metadata-Y3DGVCK5.js → backfill-metadata-ANCJA6LA.js} +7 -7
- package/dist/{background-jobs-Z6KHCWES.js → background-jobs-LTPWCMA3.js} +2 -2
- package/dist/{backup-verify-TZPPUCLU.js → backup-verify-F7BHVEF7.js} +27 -14
- package/dist/{behavior-injection-JMGGWCWG.js → behavior-injection-XQ4B7DCF.js} +13 -12
- package/dist/{behaviors-TT6CMEKC.js → behaviors-R2IE3PEN.js} +12 -11
- package/dist/bin/age-ontology-load.js +2 -2
- package/dist/bin/agentic-ontology-backfill.js +9 -9
- package/dist/bin/agentic-reflection-backfill.js +10 -10
- package/dist/bin/agentic-semantic-label.js +9 -9
- package/dist/bin/backfill-conversations.js +10 -9
- package/dist/bin/backfill-responses.js +10 -9
- package/dist/bin/backfill-vectors.js +14 -13
- package/dist/bin/bulk-sync-postgres.js +23 -19
- package/dist/bin/cc-doctor.js +11 -8
- package/dist/bin/cleanup-stale-review-tasks.js +26 -24
- package/dist/bin/cli.js +29 -102
- package/dist/bin/customer-readiness.js +3 -1
- package/dist/bin/deferred-daemon-restart.js +115 -103
- package/dist/bin/exe-agent-config.js +5 -5
- package/dist/bin/exe-agent.js +14 -14
- package/dist/bin/exe-assign.js +12 -11
- package/dist/bin/exe-boot.js +60 -51
- package/dist/bin/exe-call.js +7 -7
- package/dist/bin/exe-cloud.js +26 -22
- package/dist/bin/exe-config-dump.js +5 -5
- package/dist/bin/exe-dispatch.js +30 -24
- package/dist/bin/exe-doctor.js +10 -9
- package/dist/bin/exe-export-behaviors.js +14 -13
- package/dist/bin/exe-forget.js +9 -9
- package/dist/bin/exe-gateway.js +9 -9
- package/dist/bin/exe-grant.js +5 -5
- package/dist/bin/exe-healthcheck.js +13 -8
- package/dist/bin/exe-heartbeat.js +36 -29
- package/dist/bin/exe-kill.js +30 -28
- package/dist/bin/exe-launch-agent.js +41 -37
- package/dist/bin/exe-mcp.js +5 -5
- package/dist/bin/exe-memory-reads.js +7 -7
- package/dist/bin/exe-new-employee.js +12 -11
- package/dist/bin/exe-os-mcp-proxy.js +5 -5
- package/dist/bin/exe-pending-messages.js +27 -25
- package/dist/bin/exe-pending-notifications.js +26 -24
- package/dist/bin/exe-pending-reviews.js +27 -25
- package/dist/bin/exe-rename.js +7 -7
- package/dist/bin/exe-review.js +29 -27
- package/dist/bin/exe-search.js +8 -8
- package/dist/bin/exe-session-cleanup.js +31 -29
- package/dist/bin/exe-settings.js +24 -20
- package/dist/bin/exe-start-codex.js +36 -21
- package/dist/bin/exe-start-opencode.js +15 -14
- package/dist/bin/exe-status.js +28 -25
- package/dist/bin/exe-support.js +5 -4
- package/dist/bin/exe-team.js +12 -6
- package/dist/bin/exe-verify-running-images.js +145 -0
- package/dist/bin/exe-watchdog.js +16 -6
- package/dist/bin/git-sweep.js +27 -25
- package/dist/bin/graph-backfill.js +10 -9
- package/dist/bin/graph-export.js +8 -8
- package/dist/bin/import-history.js +13 -11
- package/dist/bin/install.js +119 -107
- package/dist/bin/intercom-check.js +5 -5
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +6 -6
- package/dist/bin/postgres-agentic-reflection-backfill.js +4 -4
- package/dist/bin/postgres-agentic-semantic-backfill.js +3 -3
- package/dist/bin/pre-publish.js +1 -1
- package/dist/bin/reconcile-backlog.js +2 -2
- package/dist/bin/registry-proxy.js +1 -1
- package/dist/bin/scan-tasks.js +26 -24
- package/dist/bin/setup.js +4 -3
- package/dist/bin/shard-migrate.js +8 -8
- package/dist/bin/stack-update.js +87 -38
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{boot-generation-XABYKF5P.js → boot-generation-X45BSVZY.js} +5 -5
- package/dist/{boot-review-sections-2X5TFIGU.js → boot-review-sections-OBVRXXSF.js} +25 -23
- package/dist/{branch-hygiene-Z4IBWAOX.js → branch-hygiene-HFJ3HA62.js} +17 -10
- package/dist/{branding-7MZDHTKN.js → branding-GH43W3PU.js} +2 -2
- package/dist/build-manifest.json +1189 -1049
- package/dist/{capability-cards-O5UXDOOA.js → capability-cards-57FURUMR.js} +4 -4
- package/dist/{capacity-monitor-IPXM726A.js → capacity-monitor-QEL62JDO.js} +27 -25
- package/dist/{catchup-brief-EBIGDXOK.js → catchup-brief-PMXTCASA.js} +30 -28
- package/dist/{chunk-UO5QJ2RX.js → chunk-22YQBVMO.js} +4 -4
- package/dist/chunk-2ADYTOHQ.js +189 -0
- package/dist/{chunk-NO5KHKKB.js → chunk-2CWS374F.js} +1 -1
- package/dist/{chunk-NAHCSQWT.js → chunk-2ESB5SDP.js} +19 -7
- package/dist/{chunk-CY5NVDTH.js → chunk-2FYIMTQE.js} +9 -5
- package/dist/{chunk-SC6WDF3Q.js → chunk-2RQT7TED.js} +2 -2
- package/dist/{chunk-UWEFIIEG.js → chunk-3CIMDFXG.js} +1 -1
- package/dist/{chunk-E7PL3T2U.js → chunk-3JNVB5LG.js} +1 -1
- package/dist/{chunk-7SY2NOMQ.js → chunk-3THNS2T7.js} +1 -1
- package/dist/{chunk-XJIRUTGJ.js → chunk-45EQTS7B.js} +1 -1
- package/dist/{chunk-YZUHSV24.js → chunk-4LWBC7CM.js} +1 -1
- package/dist/{chunk-DKRDU2AX.js → chunk-4QKKLK3C.js} +14 -14
- package/dist/{chunk-WP2TGH5R.js → chunk-52TIQJEQ.js} +2 -2
- package/dist/{chunk-LG37UBBE.js → chunk-5CBGH6RA.js} +5 -5
- package/dist/{chunk-W3LKIQJ2.js → chunk-6GQYPJPG.js} +22 -16
- package/dist/{chunk-I3SF6QGV.js → chunk-6Q263OZE.js} +1 -1
- package/dist/{chunk-XSYQ5EPE.js → chunk-7C4FUNVO.js} +11 -11
- package/dist/{chunk-P7CV4SHQ.js → chunk-7Y6AQWVT.js} +21 -10
- package/dist/{chunk-NGDXBN3M.js → chunk-A6PDF4BO.js} +2 -2
- package/dist/chunk-A7XLR3K7.js +45 -0
- package/dist/{chunk-7H3AZNQ7.js → chunk-AGQAGOQN.js} +9 -9
- package/dist/{chunk-VDMFVUSY.js → chunk-ANPE3TT3.js} +3 -3
- package/dist/{chunk-NE4QI2GX.js → chunk-BATQKSLI.js} +2 -2
- package/dist/{chunk-AOMFYUPT.js → chunk-BEHLIRRJ.js} +2 -2
- package/dist/{chunk-6EM6USV7.js → chunk-BNHAZ65R.js} +42 -50
- package/dist/{chunk-HLU52KTO.js → chunk-BRLS5OLZ.js} +257 -36
- package/dist/{chunk-QCJT265O.js → chunk-BY7WWPRM.js} +1 -1
- package/dist/{chunk-Z67DIJKT.js → chunk-C3UAQ3JG.js} +1 -1
- package/dist/{chunk-MZYNW5OY.js → chunk-CDUB5Q6J.js} +1 -1
- package/dist/{chunk-5H3RHWRE.js → chunk-CKG6CVKI.js} +1 -1
- package/dist/{chunk-WH2VVEGD.js → chunk-CMBF5WCL.js} +56 -2
- package/dist/{chunk-JYKZVAS6.js → chunk-CPILYY2K.js} +1 -1
- package/dist/{chunk-XTGR3LMF.js → chunk-CW2UCKHS.js} +2 -2
- package/dist/{chunk-32FEF5BW.js → chunk-CWBFOAGM.js} +2 -2
- package/dist/{chunk-V5I5N7NC.js → chunk-DEFSN2FQ.js} +3 -3
- package/dist/{chunk-HAXCUCJC.js → chunk-DJK45U7B.js} +1 -1
- package/dist/{chunk-MZZN5GFC.js → chunk-DQ5KHWFD.js} +1 -1
- package/dist/{chunk-SLBOKYYQ.js → chunk-DWAQOEXL.js} +2 -2
- package/dist/{chunk-T2RZU7JA.js → chunk-DYWGIPVP.js} +7 -7
- package/dist/{chunk-6DTALXOA.js → chunk-E5HAY3DQ.js} +1 -1
- package/dist/{chunk-D5CY4E7I.js → chunk-EHHMNKOA.js} +1 -1
- package/dist/{chunk-54USDN5Q.js → chunk-EJ4522YM.js} +1 -1
- package/dist/{chunk-URNVIZXU.js → chunk-EKVTRI3N.js} +52 -6
- package/dist/{chunk-KID6T2A4.js → chunk-EMZHB767.js} +44 -35
- package/dist/{chunk-KBI4FVBZ.js → chunk-EPDNWY5P.js} +111 -22
- package/dist/{chunk-AQO3BT3I.js → chunk-EPNWEOOE.js} +13 -13
- package/dist/{chunk-5C6ZIJ7J.js → chunk-ESTR7QUP.js} +1 -1
- package/dist/{chunk-Q2VQNAPU.js → chunk-EVPBYX2Z.js} +2 -2
- package/dist/{chunk-CVYDJ2TW.js → chunk-EWCEUO65.js} +1 -1
- package/dist/{chunk-RNSAVQEU.js → chunk-EWZYFG5Q.js} +8 -3
- package/dist/{chunk-R7F6HKTH.js → chunk-EXDW3E2R.js} +3 -1
- package/dist/{chunk-TY4JLBFO.js → chunk-F2S3M5MB.js} +3 -3
- package/dist/{chunk-BT2PUK3S.js → chunk-FXPAUDCZ.js} +1 -1
- package/dist/{chunk-3C7NE4JE.js → chunk-G2EAYH4U.js} +1 -1
- package/dist/{chunk-WDA2KAV7.js → chunk-GSJLB6JI.js} +1 -1
- package/dist/{chunk-M67ARGXY.js → chunk-H37FSTF2.js} +1 -1
- package/dist/{chunk-ZR4CQBAP.js → chunk-H74466TM.js} +1 -1
- package/dist/{chunk-3BAGFZFN.js → chunk-HHGOCBVQ.js} +9 -9
- package/dist/{working-memory-BACVOAIV.js → chunk-HSFBNCNC.js} +3 -3
- package/dist/{chunk-PF7EQSE5.js → chunk-IIZITKBV.js} +6 -6
- package/dist/{chunk-2FA4PYYX.js → chunk-IM2I7OOS.js} +1 -1
- package/dist/{chunk-DSKEYEL2.js → chunk-IXEFO6HD.js} +3 -3
- package/dist/{chunk-TN2E6ZCM.js → chunk-J4U5Z7VX.js} +214 -58
- package/dist/{chunk-PU7UYQRC.js → chunk-J5EYOBFF.js} +1 -1
- package/dist/{chunk-RWKX2TK5.js → chunk-J6I7X272.js} +1 -1
- package/dist/{chunk-BDLRTLXY.js → chunk-JITNFE4W.js} +6 -2
- package/dist/{chunk-PUMH7JSB.js → chunk-JNVQGLPX.js} +3 -3
- package/dist/{chunk-VAOUB2TK.js → chunk-JTB5V6BV.js} +1 -1
- package/dist/{chunk-QYRI25IP.js → chunk-JUOZLXLH.js} +2 -2
- package/dist/{chunk-RYRNY6IF.js → chunk-JYFYIGYK.js} +2 -2
- package/dist/{chunk-2PRAXZWM.js → chunk-KOYGRP2L.js} +10 -5
- package/dist/{chunk-4AFD4R35.js → chunk-KRUYQFSH.js} +5 -5
- package/dist/chunk-L25OAFOU.js +93 -0
- package/dist/{chunk-FUQ2OSDV.js → chunk-L2VZBPJJ.js} +25 -8
- package/dist/{chunk-LCDW2HUL.js → chunk-LHOKWZKL.js} +1 -1
- package/dist/{chunk-BN26LSME.js → chunk-LKYGSJ2E.js} +13 -2
- package/dist/chunk-LNOS24GV.js +214 -0
- package/dist/{chunk-O32HTNRX.js → chunk-LZKYVMBY.js} +13 -4
- package/dist/{chunk-35FD67ZB.js → chunk-M7EES2KM.js} +3 -3
- package/dist/chunk-MBBFL2SP.js +45 -0
- package/dist/{chunk-4R2WD6T3.js → chunk-MPQXQ5IE.js} +2 -2
- package/dist/{chunk-QQMYD4M7.js → chunk-MXEIZUXG.js} +1 -1
- package/dist/{chunk-6QJXFLNN.js → chunk-MZJBUAC3.js} +5 -5
- package/dist/chunk-NEHOQ3P2.js +19 -0
- package/dist/{chunk-BRZR76TS.js → chunk-NJFJWQNN.js} +49 -14
- package/dist/{chunk-Q7J2SQJ6.js → chunk-NO3M2AFM.js} +100 -32
- package/dist/{gotrue-session-2IT7MDGW.js → chunk-NUTZJZKW.js} +7 -8
- package/dist/{chunk-6UZDGEGQ.js → chunk-NV6A5HDN.js} +71 -1
- package/dist/{chunk-ZI3HA3XW.js → chunk-O2MEBF23.js} +3 -3
- package/dist/{chunk-V6YLIDLK.js → chunk-ODBRU6BF.js} +577 -230
- package/dist/{chunk-6N3HUOV3.js → chunk-OFGFRT5J.js} +2 -2
- package/dist/{chunk-HKQ7UTSE.js → chunk-OPNROU5B.js} +1 -1
- package/dist/{chunk-SQM4ZTL2.js → chunk-ORNCMRIX.js} +60 -33
- package/dist/{chunk-RUUX2ZST.js → chunk-P47MPWNU.js} +1 -1
- package/dist/{org-key-admin-4W6LZ5KZ.js → chunk-P5XTVIEC.js} +62 -17
- package/dist/{chunk-OZNCAMS3.js → chunk-POPRJKAY.js} +122 -20
- package/dist/{chunk-SRFRUN66.js → chunk-PPGPCWRC.js} +5 -5
- package/dist/{chunk-B6PFGUYJ.js → chunk-PVEM537A.js} +46 -5
- package/dist/{chunk-QJBX3MBV.js → chunk-Q7BU5Q6I.js} +3 -3
- package/dist/{chunk-SXKIGBNF.js → chunk-QFIVK3G5.js} +1 -1
- package/dist/{chunk-BRIZ6OZ3.js → chunk-QMSEL5YS.js} +81 -23
- package/dist/{chunk-BJEQBKGH.js → chunk-QYJANQWC.js} +8 -8
- package/dist/{chunk-4U56FZ7B.js → chunk-R7RM766N.js} +3 -3
- package/dist/{chunk-QI25QRUP.js → chunk-RD4E5G3B.js} +2 -2
- package/dist/{chunk-QP53XLC5.js → chunk-RHX6MNWY.js} +1 -1
- package/dist/chunk-RIECV7SR.js +234 -0
- package/dist/{chunk-3YBJ36UX.js → chunk-RLDMUSRM.js} +2 -2
- package/dist/{chunk-CXQ724WH.js → chunk-RVWALVDT.js} +2 -2
- package/dist/{chunk-VWTUKKUF.js → chunk-RZIKSE6N.js} +10 -4
- package/dist/chunk-SBAU735L.js +328 -0
- package/dist/{chunk-XRCX6R5S.js → chunk-SBJV2PCU.js} +1 -1
- package/dist/chunk-SO7KTH3Q.js +27 -0
- package/dist/{chunk-327Q53AS.js → chunk-SP37DONS.js} +17 -6
- package/dist/{chunk-KA4WVDLX.js → chunk-T6S45OLV.js} +4 -4
- package/dist/chunk-TCTED52S.js +120 -0
- package/dist/{chunk-HLQ4MS42.js → chunk-TO6TB75E.js} +4 -4
- package/dist/{chunk-465RCKVI.js → chunk-U373MEF5.js} +6 -6
- package/dist/{chunk-AL4HXKDJ.js → chunk-VGE6IHZB.js} +10 -10
- package/dist/{chunk-WBG7XYVR.js → chunk-VHJVD3MZ.js} +8 -0
- package/dist/{chunk-ZZEKOI2M.js → chunk-VMJ2UH4S.js} +1 -1
- package/dist/{chunk-3ICF4IAZ.js → chunk-VS4LPG66.js} +1 -1
- package/dist/{chunk-E3YI6FVJ.js → chunk-VV5EZZNT.js} +1 -1
- package/dist/{chunk-OPU3NYOO.js → chunk-W2IRQJ6R.js} +12 -0
- package/dist/{chunk-PV5BIXT6.js → chunk-WR75WUEP.js} +1 -1
- package/dist/{chunk-XUB4GFPN.js → chunk-WYLFDSIE.js} +144 -99
- package/dist/{chunk-4L33OFU4.js → chunk-X2P43TB2.js} +6 -3
- package/dist/chunk-X2X3IGZU.js +215 -0
- package/dist/{chunk-KIKB6FY3.js → chunk-X6A2VWVJ.js} +2 -2
- package/dist/chunk-XMVSLLED.js +38 -0
- package/dist/{chunk-KDMVVHZH.js → chunk-XTWHNLGD.js} +95 -23
- package/dist/{chunk-QY3TM2T3.js → chunk-YMJOLEPE.js} +4 -4
- package/dist/{chunk-RFKRCVGW.js → chunk-YYA5LXCT.js} +1 -1
- package/dist/{chunk-HSKABJE6.js → chunk-ZDUO6QBH.js} +5 -10
- package/dist/{chunk-PDJWHL63.js → chunk-ZEKCHV43.js} +4 -4
- package/dist/{chunk-23KWUA7Z.js → chunk-ZNEBZCZR.js} +53 -2
- package/dist/chunk-ZNUJFX7L.js +135 -0
- package/dist/{chunk-TP7RXGMP.js → chunk-ZRKEUCUZ.js} +315 -67
- package/dist/{chunk-6Y3KUO7Q.js → chunk-ZSZPMUNX.js} +244 -21
- package/dist/{chunk-4P2BFNSB.js → chunk-ZVTKWWLV.js} +2 -2
- package/dist/{chunk-RCOIKTHM.js → chunk-ZZXMB5F2.js} +1 -1
- package/dist/{cleanup-graph-garbage-6F22Y5SD.js → cleanup-graph-garbage-CAA625RJ.js} +10 -9
- package/dist/cloud-sync-command-DBB7WRHL.js +96 -0
- package/dist/cloud-sync-db-init-V7IB23W5.js +46 -0
- package/dist/{cloud-sync-shutdown-FWERMZB5.js → cloud-sync-shutdown-2D3B73CJ.js} +10 -4
- package/dist/{cloud-sync-state-DJRZORFI.js → cloud-sync-state-5DFKLWNB.js} +1 -1
- package/dist/{co-activation-UEEDCM5U.js → co-activation-JVN7EBS6.js} +4 -4
- package/dist/{co-occurrence-W6OPFRSY.js → co-occurrence-526PTFGT.js} +8 -7
- package/dist/{code-context-index-DWR6HS35.js → code-context-index-XOP6TLPZ.js} +5 -4
- package/dist/{conversation-entity-extractor-LWUIH3UB.js → conversation-entity-extractor-JO56DB2E.js} +9 -4
- package/dist/{conversation-wiki-populator-5EGRW2UZ.js → conversation-wiki-populator-EBUZXYEU.js} +1 -1
- package/dist/{crdt-sync-NHJX3ZJD.js → crdt-sync-LIJLZKFE.js} +2 -2
- package/dist/{crm-webhook-OY467ATC.js → crm-webhook-55F7Y6D2.js} +2 -2
- package/dist/{cto-delegation-gate-XH4YSQVC.js → cto-delegation-gate-QIYV4KFO.js} +41 -24
- package/dist/daemon-auth-SSPY5ODS.js +46 -0
- package/dist/{daemon-orchestration-7425ZVNH.js → daemon-orchestration-Q3GJA2JJ.js} +46 -30
- package/dist/{daemon-ready-VGPSRI3Z.js → daemon-ready-MKBH4QWD.js} +1 -1
- package/dist/{daemon-restart-report-HZU5LWXN.js → daemon-restart-report-EIVD6WDJ.js} +1 -1
- package/dist/db-integrity-events-5DTASXJF.js +22 -0
- package/dist/{db-restore-events-TFCZ4CPM.js → db-restore-events-2FI4ZMKA.js} +1 -1
- package/dist/{doc-graph-extractor-RR2VSO4W.js → doc-graph-extractor-P46R4QME.js} +13 -8
- package/dist/{documents-TRZ4R2ZW.js → documents-7KFEREWO.js} +12 -11
- package/dist/{dreaming-MAMZAPYW.js → dreaming-JMUXBAPU.js} +29 -27
- package/dist/employee-boot-brief-HN63D2BY.js +71 -0
- package/dist/{exe-drift-VBWB5E6S.js → exe-drift-7J7GX7DP.js} +5 -5
- package/dist/{exe-export-XJJTVVT7.js → exe-export-KJ7B2OBU.js} +14 -13
- package/dist/{exe-import-G6O7JFNZ.js → exe-import-HPZDWKGE.js} +14 -13
- package/dist/{exe-key-A2WJLLTM.js → exe-key-2PREP464.js} +117 -105
- package/dist/{exe-org-HXF4XBB3.js → exe-org-VEIISATF.js} +88 -26
- package/dist/{exe-snapshot-TL3TLKLR.js → exe-snapshot-JXQFLUKA.js} +33 -40
- package/dist/{fast-db-init-SO2XUQ7T.js → fast-db-init-SCXJXOYX.js} +1 -1
- package/dist/{founder-context-IOCADC66.js → founder-context-U2SZPLYV.js} +4 -3
- package/dist/{founder-monitor-envelope-SMILO4BT.js → founder-monitor-envelope-5KTKNL5G.js} +4 -4
- package/dist/fts-integrity-LKAZEZJE.js +96 -0
- package/dist/gateway/index.js +19 -19
- package/dist/{git-staleness-I6WT7EXV.js → git-staleness-S4NDR7NX.js} +4 -4
- package/dist/{global-procedures-3RMO6C3C.js → global-procedures-ZOFTOXIR.js} +6 -6
- package/dist/gotrue-session-ODZ2ERJX.js +25 -0
- package/dist/{graph-auto-extract-NKTYPD2I.js → graph-auto-extract-CLVTSUSB.js} +8 -7
- package/dist/{graph-rag-EOGPZOYI.js → graph-rag-LSJLWGXV.js} +4 -3
- package/dist/{hook-integrity-RTBF2BL2.js → hook-integrity-SGTKBL6W.js} +1 -1
- package/dist/{hook-prefetch-V5KCGW2N.js → hook-prefetch-BDSLB6F4.js} +26 -24
- package/dist/hooks/bug-report-worker.js +30 -28
- package/dist/hooks/codex-stop-task-finalizer.js +32 -30
- package/dist/hooks/commit-complete.js +29 -27
- package/dist/hooks/error-recall.js +11 -10
- package/dist/hooks/exe-heartbeat-hook.js +7 -6
- package/dist/hooks/ingest-worker.js +10 -9
- package/dist/hooks/ingest.js +18 -16
- package/dist/hooks/instructions-loaded.js +12 -8
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +8 -7
- package/dist/hooks/post-compact.js +7 -6
- package/dist/hooks/post-tool-combined.js +33 -14
- package/dist/hooks/pre-compact.js +31 -28
- package/dist/hooks/pre-tool-use.js +61 -34
- package/dist/hooks/prompt-submit.js +45 -42
- package/dist/hooks/session-end.js +40 -35
- package/dist/hooks/session-start.js +29 -71
- package/dist/hooks/stop.js +42 -36
- package/dist/hooks/subagent-stop.js +49 -33
- package/dist/hooks/summary-worker.js +35 -33
- package/dist/index.js +40 -38
- package/dist/{installer-G67M3DME.js → installer-EA7T3PBD.js} +7 -7
- package/dist/{installer-L3ZKMOPU.js → installer-KCEM4RCG.js} +7 -7
- package/dist/{installer-QBWZSYDG.js → installer-RC2JCKSE.js} +9 -7
- package/dist/{intercom-queue-R5LSXD3V.js → intercom-queue-6DZPHHQ2.js} +25 -23
- package/dist/{key-backup-status-3RC6XTUX.js → key-backup-status-6L2ARCXW.js} +1 -1
- package/dist/lib/agent-config.js +2 -2
- package/dist/lib/cloud-sync.js +25 -19
- package/dist/lib/config.js +1 -1
- package/dist/lib/consolidation.js +9 -9
- package/dist/lib/crypto.js +3 -1
- package/dist/lib/database.js +18 -4
- package/dist/lib/db-backup.js +4 -2
- package/dist/lib/db-daemon-client.js +5 -4
- package/dist/lib/db-write-worker.js +3 -3
- package/dist/lib/db.js +18 -4
- package/dist/lib/device-registry.js +2 -2
- package/dist/lib/embed-worker.js +1 -1
- package/dist/lib/embedder.js +5 -4
- package/dist/lib/employee-templates.js +7 -7
- package/dist/lib/employees.js +4 -4
- package/dist/lib/exe-daemon-client.js +4 -3
- package/dist/lib/exe-daemon.js +473 -206
- package/dist/lib/hybrid-search.js +8 -8
- package/dist/lib/identity-templates.js +1 -1
- package/dist/lib/identity.js +4 -4
- package/dist/lib/license.js +6 -4
- package/dist/lib/messaging.js +26 -24
- package/dist/lib/post-tool-memory.js +2 -2
- package/dist/lib/registry-proxy.js +3 -1
- package/dist/lib/reminders.js +5 -5
- package/dist/lib/schedules.js +8 -8
- package/dist/lib/session-registry.js +6 -6
- package/dist/lib/skill-learning.js +14 -12
- package/dist/lib/stack-invariants.js +21 -0
- package/dist/lib/status-brief.js +3 -3
- package/dist/lib/store.js +7 -7
- package/dist/lib/task-router.js +5 -5
- package/dist/lib/tasks.js +27 -25
- package/dist/lib/tmux-routing.js +25 -23
- package/dist/lib/token-spend.js +5 -5
- package/dist/{license-gate-6R3F3NQR.js → license-gate-HMSMUIAU.js} +5 -4
- package/dist/{license-sync-VBJCXBAP.js → license-sync-FBCMPC4J.js} +4 -3
- package/dist/{mailbox-inbox-CP2VRBXZ.js → mailbox-inbox-4MEUOL3J.js} +2 -2
- package/dist/{mailbox-outbox-HXI7DYNC.js → mailbox-outbox-J6WIF3UY.js} +1 -1
- package/dist/mcp/register-tools.js +114 -102
- package/dist/mcp/server.js +120 -108
- package/dist/mcp/tools/complete-reminder.js +6 -6
- package/dist/mcp/tools/create-reminder.js +6 -6
- package/dist/mcp/tools/create-task.js +30 -27
- package/dist/mcp/tools/deactivate-behavior.js +15 -13
- package/dist/mcp/tools/list-reminders.js +6 -6
- package/dist/mcp/tools/list-tasks.js +30 -27
- package/dist/mcp/tools/send-message.js +29 -26
- package/dist/mcp/tools/update-task.js +28 -26
- package/dist/{mcp-http-config-BWX7AMAZ.js → mcp-http-config-YKJN52HG.js} +5 -5
- package/dist/{mcp-session-identity-GOBGICLI.js → mcp-session-identity-QLUVYWPR.js} +25 -23
- package/dist/{memory-cards-4EXX3BQS.js → memory-cards-K2OSX4AW.js} +7 -7
- package/dist/{memory-graph-extractor-OYVZIJ3R.js → memory-graph-extractor-YLTSKLSY.js} +9 -8
- package/dist/{memory-poisoning-defense-AHOD2EBX.js → memory-poisoning-defense-IPO4ADRL.js} +4 -4
- package/dist/{memory-pressure-watchdog-COJC3OYS.js → memory-pressure-watchdog-ZI3TH2OF.js} +26 -24
- package/dist/{memory-queue-4VN3DTOE.js → memory-queue-QJNWKWKG.js} +2 -2
- package/dist/{memory-queue-client-OI4LDT4W.js → memory-queue-client-HIHCA3W4.js} +6 -5
- package/dist/{memory-reflection-V2SNAUIB.js → memory-reflection-ZKVRU3NT.js} +4 -4
- package/dist/{monitor-grants-W3ISOLMC.js → monitor-grants-BW2WL4IU.js} +3 -3
- package/dist/{notification-ledger-J6Q2GAYU.js → notification-ledger-4REL6M3T.js} +27 -25
- package/dist/{notifications-56TP36IK.js → notifications-UXJUAP3C.js} +25 -23
- package/dist/{orchestration-events-CKQ756NA.js → orchestration-events-GMRGGBMN.js} +5 -5
- package/dist/{orchestration-phase-ZJ4XVNFO.js → orchestration-phase-UDS24CVQ.js} +2 -2
- package/dist/{orchestrator-ERSQ3KNT.js → orchestrator-5TV75HDJ.js} +27 -25
- package/dist/{org-brief-session-FSZ3QJDN.js → org-brief-session-7AZZEO3F.js} +13 -13
- package/dist/{org-consent-policy-BN7MKCZ2.js → org-consent-policy-XT6UZENB.js} +2 -2
- package/dist/{org-join-HTQ57JYH.js → org-join-BMBQDMVF.js} +10 -7
- package/dist/org-key-admin-SCCVVE2H.js +38 -0
- package/dist/{org-key-pin-OTOKAELD.js → org-key-pin-5WUSPGVN.js} +3 -3
- package/dist/{org-membership-SML6GCHP.js → org-membership-ESALTLLN.js} +4 -4
- package/dist/{org-partner-config-XQGOB7EJ.js → org-partner-config-YKHPDPZR.js} +6 -6
- package/dist/{org-partner-runtime-RNNUL5IN.js → org-partner-runtime-WLZAZJIY.js} +14 -14
- package/dist/{org-row-visibility-JZFJ3YRJ.js → org-row-visibility-A5XM4MVO.js} +1 -1
- package/dist/{pipeline-router-IUENNTAY.js → pipeline-router-UEVXDUHR.js} +5 -5
- package/dist/{plan-limits-ZUII366B.js → plan-limits-QEP32X4W.js} +8 -7
- package/dist/{platform-procedures-2VQGBJU7.js → platform-procedures-44NDFTB6.js} +1 -1
- package/dist/{postinstall-failure-A7WTAXD6.js → postinstall-failure-B2JHNFPM.js} +2 -2
- package/dist/{project-boot-3DFY5F35.js → project-boot-LEZSK3W7.js} +9 -1
- package/dist/project-name-ILJTETUE.js +14 -0
- package/dist/{projection-worker-MBWUPB5O.js → projection-worker-GMRC6SBH.js} +4 -4
- package/dist/{prospective-memory-GHE5ORN4.js → prospective-memory-QLHSAPSG.js} +4 -4
- package/dist/{push-notifications-PTQ5DHCD.js → push-notifications-GKRPXOGI.js} +26 -24
- package/dist/reproject-worktree-memories-IXYGHTGA.js +224 -0
- package/dist/{reranker-NUIGAPA3.js → reranker-GB4VEMUV.js} +2 -2
- package/dist/{retrieval-health-XHCERQPG.js → retrieval-health-QIBPBGN6.js} +5 -1
- package/dist/{review-polling-CYBCBHZA.js → review-polling-MU47NQ2N.js} +26 -24
- package/dist/runtime/index.js +37 -35
- package/dist/seat-benefit-VDQJYJOB.js +17 -0
- package/dist/{session-events-X33W4JNA.js → session-events-YMQ3VGBJ.js} +26 -24
- package/dist/{session-kill-telemetry-I2F5PUIW.js → session-kill-telemetry-VGPKE57M.js} +26 -24
- package/dist/{session-mcp-config-YKNXG5DW.js → session-mcp-config-F7KATWOF.js} +10 -1
- package/dist/{session-scope-WXP4F7OF.js → session-scope-P46KAHXQ.js} +26 -24
- package/dist/{setup-wizard-2CTVITIX.js → setup-wizard-BRNHSJ4U.js} +4 -3
- package/dist/{shard-manager-YEOHEOHL.js → shard-manager-UROHZE46.js} +2 -2
- package/dist/{skill-refinement-VDR5OT4V.js → skill-refinement-NLYDSFEH.js} +5 -5
- package/dist/{stack-promote-XVC7FRJW.js → stack-promote-NHFTNIM2.js} +7 -5
- package/dist/{stack-release-6PWAACRM.js → stack-release-AQVKZUS7.js} +7 -5
- package/dist/{stack-update-24VRBQ3U.js → stack-update-LLX5KT7G.js} +38 -6
- package/dist/{steward-gate-2ZNYDVFO.js → steward-gate-JKHYFAHA.js} +5 -5
- package/dist/subagent-brief-injection-VDVOHDOU.js +97 -0
- package/dist/subagent-spawn-ledger-USYZXU3M.js +22 -0
- package/dist/{support-inbox-ZHKLDETT.js → support-inbox-S6YO5YZU.js} +5 -4
- package/dist/{support-outbox-DSWU3IXH.js → support-outbox-HFJWG52M.js} +1 -1
- package/dist/{task-enforcement-GQDUTIFY.js → task-enforcement-LXW45H5Z.js} +26 -24
- package/dist/{task-request-inbox-BFXNUU44.js → task-request-inbox-UCSKDC3R.js} +2 -2
- package/dist/{task-scope-ERHPHLUM.js → task-scope-GDLFE26E.js} +25 -23
- package/dist/{tasks-crud-NVNF3NRA.js → tasks-crud-W7VGWRUV.js} +25 -23
- package/dist/{tasks-notify-DXLU5VCT.js → tasks-notify-CT7NWCS4.js} +26 -24
- package/dist/{tasks-review-BST6N34P.js → tasks-review-WCSKOUFW.js} +25 -23
- package/dist/{telemetry-upload-NY3EKOGY.js → telemetry-upload-FK5WKXVB.js} +10 -9
- package/dist/{token-budget-CLB3GUIX.js → token-budget-GBNGSIU3.js} +5 -5
- package/dist/{tool-capability-index-WZWVBI2R.js → tool-capability-index-BT2GGLLI.js} +1 -1
- package/dist/{tool-telemetry-PALAZUXT.js → tool-telemetry-VKMRS6MP.js} +1 -1
- package/dist/tui/App.js +39 -37
- package/dist/{tui-data-CNFSRZP7.js → tui-data-FTPV2XBQ.js} +25 -23
- package/dist/{update-support-ZVB3CNGY.js → update-support-J6UWPUOC.js} +18 -15
- package/dist/{work-state-heartbeat-O7UMB6SE.js → work-state-heartbeat-4LDF6DSM.js} +1 -1
- package/dist/{worker-gate-6DQQWDNB.js → worker-gate-35VZINZG.js} +2 -2
- package/dist/{workflow-engine-5ZULFH7O.js → workflow-engine-4DA6RSHI.js} +2 -2
- package/dist/working-memory-DPFRSKH3.js +18 -0
- package/dist/{worktree-ZWA35PKW.js → worktree-H3YFJ3NU.js} +6 -6
- package/dist/{worktree-sweep-5LCVT46V.js → worktree-sweep-4EDLLQ2E.js} +9 -7
- package/package.json +11 -5
- package/release-notes.json +86 -34
- package/src/commands/exe/call.md +6 -4
- package/src/commands/exe-reference.md +11 -7
- package/src/commands/exe.md +16 -20
- package/dist/chunk-HISFWAAK.js +0 -90
- package/dist/daemon-auth-OCHW2U6B.js +0 -20
- package/dist/project-name-EWG5KAZP.js +0 -10
- package/dist/session-summary-5EYI4XW4.js +0 -128
- /package/dist/{chunk-YAZ5ECEG.js → chunk-3NBWARKC.js} +0 -0
- /package/dist/{chunk-6C4UAKMS.js → chunk-6CAX7YH4.js} +0 -0
- /package/dist/{chunk-FIPUVCKC.js → chunk-7DMCAGSC.js} +0 -0
- /package/dist/{chunk-X7LY3IOB.js → chunk-JRPYLUXH.js} +0 -0
- /package/dist/{chunk-L4LPJMPV.js → chunk-QYECRPGD.js} +0 -0
- /package/dist/{chunk-UELGUVNL.js → chunk-UY6HRDB2.js} +0 -0
- /package/dist/{chunk-YWN3XMPU.js → chunk-YSDIJ3VI.js} +0 -0
- /package/dist/{core-memory-BZFPVS43.js → core-memory-CMPPHJN5.js} +0 -0
- /package/dist/{entity-boost-VXXED26D.js → entity-boost-CDFAXVYL.js} +0 -0
- /package/dist/{memory-isolation-3PIOWLYP.js → memory-isolation-ZB5S5HCO.js} +0 -0
- /package/dist/{message-queue-client-5XW7ZOK2.js → message-queue-client-PL2POQOV.js} +0 -0
- /package/dist/{oauth-server-KW5M437C.js → oauth-server-NP7KGF7N.js} +0 -0
- /package/dist/{webhook-pipe-QJRFZVKA.js → webhook-pipe-UQSEHFPO.js} +0 -0
- /package/dist/{wiki-acl-RXFV5OEU.js → wiki-acl-5DKOWOIH.js} +0 -0
- /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.
|
|
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
|
-
|
|
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",
|
package/deploy/compose/status.sh
CHANGED
|
@@ -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
|