Pete Dunn
Add release preflight, baseline gates, and review-queue observability
94f8da0
Raw
History Blame Contribute Delete
5.1 kB
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List
REPO_ROOT = Path(__file__).resolve().parents[2]
BACKEND_DIR = REPO_ROOT / "backend"
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
from app.auth import load_auth_settings # noqa: E402
def _is_placeholder_key(value: str) -> bool:
v = str(value or "").strip()
if not v:
return True
vu = v.upper()
return vu.startswith("YOUR_KEY") or vu in {"<YOUR_OPENAI_API_KEY>", "YOUR_OPENAI_API_KEY", "REPLACE_ME"}
def _resolve_router_rag_paths() -> Dict[str, Path]:
data_dir = Path(str(os.getenv("ROUTER_RAG_DATA_DIR", "") or "").strip() or (REPO_ROOT / "_RAG_Ready_KB_Organized"))
chunks = Path(str(os.getenv("ROUTER_RAG_CHUNKS_PATH", "") or "").strip() or (data_dir / "04_ingestion" / "rag_ingestion_chunks.jsonl"))
manifest = Path(str(os.getenv("ROUTER_RAG_MANIFEST_PATH", "") or "").strip() or (data_dir / "03_manifests" / "rag_manifest_organized.csv"))
return {"data_dir": data_dir, "chunks": chunks, "manifest": manifest}
def _check_path(path: Path, *, file_required: bool = True) -> Dict[str, Any]:
exists = path.exists()
ok = exists and (path.is_file() if file_required else path.is_dir())
return {"path": str(path), "exists": exists, "ok": ok}
def _build_report(args: argparse.Namespace) -> Dict[str, Any]:
checks: Dict[str, Any] = {}
failures: List[str] = []
warnings: List[str] = []
openai_key = str(os.getenv("OPENAI_API_KEY", "") or "").strip()
openai_ok = bool(openai_key) and not _is_placeholder_key(openai_key)
checks["openai"] = {
"required": bool(args.require_openai),
"present": bool(openai_key),
"placeholder": _is_placeholder_key(openai_key) if openai_key else True,
"ok": openai_ok or (not args.require_openai),
}
if args.require_openai and not openai_ok:
failures.append("OPENAI_API_KEY is missing or placeholder.")
auth = load_auth_settings()
checks["auth"] = {
"required": bool(auth.required),
"enabled": bool(auth.enabled),
"config_error": auth.config_error,
"config_details": list(auth.config_details),
"config_warnings": list(auth.config_warnings),
"ok": (not auth.required) or bool(auth.enabled),
}
if auth.required and not auth.enabled:
failures.append(auth.config_error or "Auth is required but Auth0 config is invalid.")
if auth.config_warnings:
warnings.extend(list(auth.config_warnings))
repo_assets = {
"routers_eos_eol_by_sku.csv": REPO_ROOT / "routers_eos_eol_by_sku.csv",
"feb2026routers.csv": REPO_ROOT / "feb2026routers.csv",
"FAQ_master_updated.csv": REPO_ROOT / "docs" / "faq" / "FAQ_master_updated.csv",
}
checks["required_assets"] = {name: _check_path(path, file_required=True) for name, path in repo_assets.items()}
for name, data in checks["required_assets"].items():
if not bool(data.get("ok")):
failures.append(f"Missing required asset: {name}")
rag = _resolve_router_rag_paths()
checks["router_rag"] = {
"data_dir": _check_path(rag["data_dir"], file_required=False),
"chunks": _check_path(rag["chunks"], file_required=True),
"manifest": _check_path(rag["manifest"], file_required=True),
"strict": bool(args.fail_on_missing_router_rag),
}
if args.fail_on_missing_router_rag:
if not checks["router_rag"]["chunks"]["ok"]:
failures.append("Router RAG chunks file is missing.")
if not checks["router_rag"]["manifest"]["ok"]:
failures.append("Router RAG manifest file is missing.")
else:
if not checks["router_rag"]["chunks"]["ok"] or not checks["router_rag"]["manifest"]["ok"]:
warnings.append("Router RAG ingestion artifacts are missing; router-doc retrieval may be degraded.")
return {
"ok": not failures,
"failures": failures,
"warnings": warnings,
"checks": checks,
}
def main() -> int:
parser = argparse.ArgumentParser(description="Preflight environment/configuration checks for CI and release gates.")
parser.add_argument("--require-openai", action="store_true", help="Fail if OPENAI_API_KEY is missing/placeholder.")
parser.add_argument(
"--fail-on-missing-router-rag",
action="store_true",
help="Fail if router RAG manifest/chunks files are missing.",
)
parser.add_argument("--out", default="", help="Optional output JSON path.")
args = parser.parse_args()
report = _build_report(args)
payload = json.dumps(report, indent=2)
if args.out:
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(payload + "\n", encoding="utf-8")
print(f"Wrote preflight report: {out_path}")
print(payload)
return 0 if report.get("ok") else 2
if __name__ == "__main__":
raise SystemExit(main())