kanbot-backend / cli /preflight.py
LoveLogicAI's picture
Upload folder using huggingface_hub
587a933 verified
Raw History Blame Contribute Delete
3.7 kB
#!/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()))