#!/usr/bin/env python3 """Preflight and run ProofFrame's final live sponsor proof gate.""" from __future__ import annotations import argparse import importlib.util import json from pathlib import Path import subprocess import sys from typing import Any, Callable from proofframe.config import Settings ROOT = Path(__file__).resolve().parents[1] API_SMOKE = ROOT / "scripts" / "api_smoke.py" def package_available(module: str) -> bool: return importlib.util.find_spec(module) is not None def add_requirement( checks: list[dict[str, Any]], name: str, ok: bool, remediation: str ) -> None: checks.append({"name": name, "ok": ok, "remediation": "" if ok else remediation}) def build_preflight_report( settings: Settings, *, require_storage_backend: str, require_generation_backend: str, available: Callable[[str], bool] = package_available, ) -> dict[str, Any]: checks: list[dict[str, Any]] = [] add_requirement( checks, "storage backend mode", settings.storage_backend == require_storage_backend, f"Set PROOFFRAME_STORAGE_BACKEND={require_storage_backend}.", ) add_requirement( checks, "generation backend mode", settings.generation_backend == require_generation_backend, f"Set PROOFFRAME_GENERATION_BACKEND={require_generation_backend}.", ) if require_storage_backend == "b2": add_requirement(checks, "B2 endpoint", bool(settings.b2_endpoint_url), "Set B2_ENDPOINT_URL.") add_requirement(checks, "B2 bucket", bool(settings.b2_bucket), "Set B2_BUCKET.") add_requirement(checks, "B2 key id", bool(settings.b2_key_id), "Set B2_KEY_ID.") add_requirement( checks, "B2 application key", bool(settings.b2_application_key), "Set B2_APPLICATION_KEY or B2_APP_KEY.", ) add_requirement( checks, "boto3 package", available("boto3"), "Install integrations with `pip install -e '.[integrations]'`.", ) if require_generation_backend == "genblaze": add_requirement( checks, "B2 region for Genblaze sink", bool(settings.b2_region_for_backblaze()), ( "Set B2_REGION or use a Backblaze S3 endpoint like " "https://s3.us-west-004.backblazeb2.com." ), ) if require_generation_backend == "genblaze": add_requirement( checks, "Genblaze provider", settings.genblaze_provider_supported(), "Set GENBLAZE_PROVIDER=gmicloud, openai, or local.", ) add_requirement( checks, "Genblaze provider API key", (not settings.genblaze_provider_requires_key()) or bool(settings.genblaze_provider_key()), settings.genblaze_key_remediation(), ) add_requirement( checks, "Genblaze image model", bool(settings.genblaze_image_model), "Set GENBLAZE_IMAGE_MODEL.", ) modules = ["genblaze_core", *settings.genblaze_provider_modules()] if require_storage_backend == "b2": modules.append("genblaze_s3") for module in modules: add_requirement( checks, f"{module} package", available(module), "Install integrations with `pip install -e '.[integrations]'`.", ) missing = [check for check in checks if not check["ok"]] return { "ok": not missing, "storage_backend": settings.storage_backend, "generation_backend": settings.generation_backend, "genblaze_provider": settings.genblaze_provider, "required_storage_backend": require_storage_backend, "required_generation_backend": require_generation_backend, "checks": checks, "missing": missing, "secret_policy": ( "No credential values are printed. Evidence output is delegated to api_smoke.py, " "which refuses secret-like keys, bearer tokens, signed URL parameters, and GMI-style keys." ), } def build_api_smoke_command( *, base_url: str, evidence_out: Path, require_storage_backend: str, require_generation_backend: str, ) -> list[str]: return [ sys.executable, str(API_SMOKE), "--base-url", base_url, "--require-storage-backend", require_storage_backend, "--require-generation-backend", require_generation_backend, "--evidence-out", str(evidence_out), ] def run_live_proof(args: argparse.Namespace) -> int: settings = Settings.from_env() report = build_preflight_report( settings, require_storage_backend=args.require_storage_backend, require_generation_backend=args.require_generation_backend, ) print(json.dumps(report, indent=2)) if not report["ok"]: return 2 if args.preflight_only: return 0 command = build_api_smoke_command( base_url=args.base_url, evidence_out=args.evidence_out, require_storage_backend=args.require_storage_backend, require_generation_backend=args.require_generation_backend, ) completed = subprocess.run(command, check=False) return completed.returncode def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Preflight and run the final B2/Genblaze live proof gate." ) parser.add_argument("--base-url", default="http://127.0.0.1:8088") parser.add_argument( "--require-storage-backend", choices=["b2", "local"], default="b2", help="Expected storage backend reported by the running app.", ) parser.add_argument( "--require-generation-backend", choices=["genblaze", "mock"], default="genblaze", help="Expected generation backend reported by the running app.", ) parser.add_argument( "--evidence-out", type=Path, default=Path("docs/assets/final-live-proof-evidence.json"), help="Safe evidence JSON path written by api_smoke.py.", ) parser.add_argument( "--preflight-only", action="store_true", help="Only check required env/packages; do not call api_smoke.py.", ) return parser def main() -> None: raise SystemExit(run_live_proof(build_parser().parse_args())) if __name__ == "__main__": main()