from __future__ import annotations import argparse import json import sys from dataclasses import asdict, dataclass from pathlib import Path from typing import Any ROOT_DIR = Path(__file__).resolve().parent.parent if str(ROOT_DIR) not in sys.path: sys.path.insert(0, str(ROOT_DIR)) from scripts.next_deployment_step import ( LIVE_PROOF, LOCAL_REPORT, SITE_REPORT, WORKER_REPORT, choose_next_step, deployment_urls_look_real, load_json, local_ready, ) @dataclass class ReportStatus: path: str exists: bool valid_json: bool summary: str @dataclass class DeploymentStatus: ready_for_live_urls: bool live_urls_look_real: bool live_complete: bool reports_are_real_live_evidence: bool local_report: ReportStatus site_report: ReportStatus worker_report: ReportStatus live_proof: ReportStatus missing_site_checks: list[str] missing_worker_checks: list[str] next_status: str next_title: str next_detail: str next_command: str def _report_status(path: Path) -> ReportStatus: payload = load_json(path) if payload is None: return ReportStatus(str(path), path.exists(), False, "missing or invalid JSON") return ReportStatus(str(path), True, True, summarize_payload(path, payload)) def _list_checks_ok(payload: Any, names: set[str]) -> bool: if not isinstance(payload, list): return False checks = {str(item.get("name", "")): bool(item.get("ok")) for item in payload if isinstance(item, dict)} return all(checks.get(name) is True for name in names) def _missing_checks(payload: Any, names: set[str]) -> list[str]: if not isinstance(payload, list): return sorted(names) checks = {str(item.get("name", "")): bool(item.get("ok")) for item in payload if isinstance(item, dict)} return sorted(name for name in names if checks.get(name) is not True) def _looks_like_test_artifact(payload: Any) -> bool: if not isinstance(payload, dict): return False text = json.dumps(payload, ensure_ascii=False).lower() return "pytest-" in text or "your-space.hf.space" in text or "your-vercel-app.vercel.app" in text def _url_evidence_matches(live_payload: Any, worker_url: str | None, vercel_origin: str | None) -> bool: if not isinstance(live_payload, dict): return False proof_worker = str(live_payload.get("workerUrl") or "").rstrip("/") proof_origin = str(live_payload.get("origin") or "").rstrip("/") expected_worker = (worker_url or proof_worker).rstrip("/") expected_origin = (vercel_origin or proof_origin).rstrip("/") if not deployment_urls_look_real(expected_worker, expected_origin): return False return proof_worker == expected_worker and proof_origin == expected_origin def summarize_payload(path: Path, payload: Any) -> str: if isinstance(payload, dict): if "ready" in payload or "complete" in payload: return f"ready={payload.get('ready')} complete={payload.get('complete')}" if "counts" in payload: return f"counts={payload.get('counts')}" return f"object keys={','.join(sorted(str(key) for key in payload.keys())[:6])}" if isinstance(payload, list): passed = sum(1 for item in payload if isinstance(item, dict) and item.get("ok") is True) failed = sum(1 for item in payload if isinstance(item, dict) and item.get("ok") is False) return f"checks={len(payload)} pass={passed} fail={failed}" return type(payload).__name__ def build_status( worker_url: str | None = None, vercel_origin: str | None = None, code: str = "1234", local_report: Path = LOCAL_REPORT, site_report: Path = SITE_REPORT, worker_report: Path = WORKER_REPORT, live_proof: Path = LIVE_PROOF, ) -> DeploymentStatus: site_required = { "site login", "site platform vercel", "site worker configured", "site large PDF ready", "site production worker ready", "site hosted limits documented", "site recommended stack documented", "site direct cloud fallback disabled", "site worker diagnostics endpoint", "site worker reachable from vercel", "site worker CORS ready", } worker_required = { "recommended stack documented", "smoke upload accepted", "smoke job complete", "smoke usable text", "smoke audio url", "smoke download url", "smoke audio bytes", "smoke audio file signature", "smoke download bytes", "smoke download file signature", "scanned smoke upload accepted", "scanned smoke job complete", "scanned smoke usable text", "scanned smoke OCR extraction", "scanned smoke audio url", "scanned smoke download url", "scanned smoke audio bytes", "scanned smoke audio file signature", "scanned smoke download bytes", "scanned smoke download file signature", } live_payload = load_json(live_proof) site_payload = load_json(site_report) worker_payload = load_json(worker_report) missing_site_checks = _missing_checks(site_payload, site_required) missing_worker_checks = _missing_checks(worker_payload, worker_required) live_complete = isinstance(live_payload, dict) and bool(live_payload.get("complete")) live_urls_look_real = deployment_urls_look_real(worker_url, vercel_origin) reports_are_real_live_evidence = bool( live_complete and not missing_site_checks and not missing_worker_checks and not _looks_like_test_artifact(live_payload) and _url_evidence_matches(live_payload, worker_url, vercel_origin) ) next_step = choose_next_step( worker_url=worker_url, vercel_origin=vercel_origin, code=code, local_report=local_report, site_report=site_report, worker_report=worker_report, live_proof=live_proof, ) if live_complete and not reports_are_real_live_evidence: next_status = "live-proof" next_title = "Re-run live proof against real deployed URLs" next_detail = ( "A complete live proof file exists, but it does not look like trustworthy live evidence. " "Re-run the proof after replacing placeholder/test URLs with the real Hugging Face worker and Vercel site." ) next_command = ( f"python scripts\\prove_live_deployment.py {worker_url or 'https://your-space.hf.space'} " f"--origin {vercel_origin or 'https://your-vercel-app.vercel.app'} --code {code} " "--smoke-ocr-engine arabic " "--check-hf-metadata --hf-metadata-report outputs\\hf-model-metadata.md " "--proof-out outputs\\live-deployment-proof.json" ) else: next_status = next_step.status next_title = next_step.title next_detail = next_step.detail next_command = next_step.command return DeploymentStatus( ready_for_live_urls=local_ready(local_report) and live_urls_look_real, live_urls_look_real=live_urls_look_real, live_complete=live_complete, reports_are_real_live_evidence=reports_are_real_live_evidence, local_report=_report_status(local_report), site_report=_report_status(site_report), worker_report=_report_status(worker_report), live_proof=_report_status(live_proof), missing_site_checks=missing_site_checks, missing_worker_checks=missing_worker_checks, next_status=next_status, next_title=next_title, next_detail=next_detail, next_command=next_command, ) def print_status(status: DeploymentStatus) -> None: print(f"Ready for live URLs: {status.ready_for_live_urls}") print(f"Live URLs look real: {status.live_urls_look_real}") print(f"Live proof complete: {status.live_complete}") print(f"Reports are real live evidence: {status.reports_are_real_live_evidence}") print() for label, report in [ ("Local", status.local_report), ("Site", status.site_report), ("Worker", status.worker_report), ("Live proof", status.live_proof), ]: print(f"{label}: {report.summary} ({report.path})") if status.missing_site_checks: print() print("Missing site proof checks:") for name in status.missing_site_checks: print(f"- {name}") if status.missing_worker_checks: print() print("Missing worker proof checks:") for name in status.missing_worker_checks: print(f"- {name}") print() print(status.next_title) print(status.next_detail) print() print(status.next_command) def main() -> None: parser = argparse.ArgumentParser(description="Show the current deployment proof status and next command.") parser.add_argument("--worker-url", help="Live worker URL, for example https://your-space.hf.space") parser.add_argument("--origin", help="Live Vercel origin, for example https://your-app.vercel.app") parser.add_argument("--code", default="1234", help="Access code for live verification.") parser.add_argument("--json", action="store_true", help="Print JSON.") args = parser.parse_args() status = build_status(worker_url=args.worker_url, vercel_origin=args.origin, code=args.code) if args.json: print(json.dumps(asdict(status), ensure_ascii=False, indent=2)) else: print_status(status) if __name__ == "__main__": main()