File size: 3,699 Bytes
587a933 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | #!/usr/bin/env python3
"""
Preflight β verify every Path B dependency before flipping KanBot to live.
Probes the real services (no mocks). Prints a checklist and exits non-zero if
anything the live cycle needs is missing, so you never flip DRY_RUN=0 into a
half-ready stack.
β Postgres reachable on DATABASE_URL, schema present
β Ollama reachable on OLLAMA_HOST
β both configured models pulled
β dry-run status reported (informational)
Run: python cli/preflight.py
"""
from __future__ import annotations
import asyncio
import os
import sys
sys.path.insert(0, ".")
GREEN, RED, YEL, DIM, RST = "\033[32m", "\033[31m", "\033[33m", "\033[2m", "\033[0m"
OK, BAD, WARN = f"{GREEN}β{RST}", f"{RED}β{RST}", f"{YEL}!{RST}"
DSN = os.environ.get("DATABASE_URL", "postgresql://kanbot:kanbot@localhost:5432/kanbot")
OLLAMA = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
MODEL_FAST = os.environ.get("KANBOT_MODEL_FAST", "qwen2.5:8b-instruct-q4_K_M")
MODEL_DAILY = os.environ.get("KANBOT_MODEL_DAILY", "qwen2.5:14b-instruct-q4_K_M")
async def check_postgres() -> tuple[bool, str]:
try:
import asyncpg
except ImportError:
return False, "asyncpg not installed (pip install -r requirements.txt)"
try:
conn = await asyncpg.connect(DSN, timeout=4)
except Exception as e:
return False, f"cannot connect to {DSN}: {e}"
try:
has = await conn.fetchval(
"SELECT to_regclass('public.cards') IS NOT NULL")
return (bool(has),
"connected, schema present" if has
else "connected but 'cards' table missing (apply sql/001_schema.sql)")
finally:
await conn.close()
async def check_ollama() -> tuple[bool, str, set[str]]:
try:
import httpx
except ImportError:
return False, "httpx not installed", set()
try:
async with httpx.AsyncClient(timeout=4) as c:
r = await c.get(f"{OLLAMA}/api/tags")
r.raise_for_status()
names = {m["name"] for m in r.json().get("models", [])}
return True, f"reachable at {OLLAMA}", names
except Exception as e:
return False, f"unreachable at {OLLAMA}: {e}", set()
async def main() -> int:
print(f"\n KanBot preflight β Path B readiness\n")
fail = 0
ok, msg = await check_postgres()
print(f" {OK if ok else BAD} postgres {DIM}{msg}{RST}")
fail += not ok
ok, msg, models = await check_ollama()
print(f" {OK if ok else BAD} ollama {DIM}{msg}{RST}")
fail += not ok
if ok:
for label, m in (("daily/critic", MODEL_DAILY), ("fast head", MODEL_FAST)):
have = any(m.split(":")[0] in n or m == n for n in models)
print(f" {OK if have else BAD} model {DIM}{label}: {m} "
f"{'pulled' if have else 'MISSING β ollama pull ' + m}{RST}")
fail += not have
else:
print(f" {WARN} model {DIM}skipped (ollama unreachable){RST}")
dry = os.environ.get("KANBOT_DRY_RUN", "1") == "1"
print(f" {WARN} dry-run {DIM}KANBOT_DRY_RUN={'1 (proposals only)' if dry else '0 (LIVE β commands execute)'}{RST}")
print()
if fail:
print(f" {RED}NOT READY: {fail} dependency check(s) failed. See GOLIVE.md.{RST}\n")
return 1
print(f" {GREEN}READY: all Path B dependencies satisfied.{RST}")
if dry:
print(f" {DIM}Still in dry-run. Flip KANBOT_DRY_RUN=0 when you want live execution.{RST}\n")
else:
print(f" {YEL}LIVE MODE ON β approved cards will execute through the sandbox.{RST}\n")
return 0
if __name__ == "__main__":
raise SystemExit(asyncio.run(main()))
|