Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """Build a public-safe recording asset report for the final demo workflow.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from typing import Any, Callable | |
| from urllib.error import HTTPError, URLError | |
| from urllib.request import Request, urlopen | |
| ROOT = Path(__file__).resolve().parents[1] | |
| DEFAULT_JSON = ROOT / "docs" / "assets" / "recording-assets.json" | |
| DEFAULT_MD = ROOT / "docs" / "assets" / "recording-assets.md" | |
| PUBLIC_BASE_URL = "https://adjcjh-backblaze-proofframe.hf.space" | |
| PUBLIC_JUDGE_PATH = "/?judge=1" | |
| TIMEOUT_SECONDS = 20 | |
| REQUIRED_RECORDING_ASSETS = [ | |
| "README.md", | |
| "docs/demo_script.md", | |
| "docs/assets/demo-storyboard.json", | |
| "docs/assets/demo-storyboard.md", | |
| "docs/assets/demo-video-draft.json", | |
| "docs/assets/demo-video-draft.md", | |
| "docs/assets/proofframe-demo-draft.mp4", | |
| "docs/assets/final-video-publish-kit.json", | |
| "docs/assets/final-video-publish-kit.md", | |
| "scripts/final_video_publish_kit.py", | |
| "docs/assets/public-video-check.json", | |
| "docs/assets/public-video-check.md", | |
| "scripts/public_demo_screenshot.py", | |
| "docs/assets/public-demo-screenshot-report.json", | |
| "docs/assets/public-demo-screenshot-report.md", | |
| "docs/assets/demo-readiness-report.json", | |
| "docs/assets/demo-readiness-report.md", | |
| "docs/assets/devpost-form-kit.json", | |
| "docs/assets/devpost-form-kit.md", | |
| "docs/assets/final-submission-control.json", | |
| "docs/assets/final-submission-control.md", | |
| "docs/assets/proofframe-local-ui-smoke.png", | |
| "docs/assets/proofframe-review-console-smoke.png", | |
| "docs/assets/proofframe-hf-public-smoke.png", | |
| "docs/assets/proofframe-sponsor-model-smoke.png", | |
| "docs/assets/proofframe-sponsor-model-mobile-smoke.png", | |
| ] | |
| SHOT_PLAN = [ | |
| { | |
| "id": "judge_slate", | |
| "source": "Public judge URL", | |
| "screen": "First viewport with judge recording slate and claim boundary.", | |
| }, | |
| { | |
| "id": "sponsor_model", | |
| "source": "Public judge URL", | |
| "screen": "Sponsor Evidence Model showing Genblaze step, B2 route, manifest, and claim mode.", | |
| }, | |
| { | |
| "id": "creative_brief", | |
| "source": "Local or public mock demo", | |
| "screen": "Campaign brief with audience, tone, and prompt context.", | |
| }, | |
| { | |
| "id": "asset_ledger", | |
| "source": "Local or public mock demo", | |
| "screen": "Generated asset ledger with provider/model/checksum/storage fields.", | |
| }, | |
| { | |
| "id": "review_console", | |
| "source": "Committed screenshot or live UI", | |
| "screen": "Review console filters, approval state, and copy-safe summary.", | |
| }, | |
| { | |
| "id": "final_gate", | |
| "source": "Final submission control report", | |
| "screen": "Fail-closed final gate with remaining B2/Genblaze/video blockers.", | |
| }, | |
| ] | |
| RECORDING_COMMANDS = [ | |
| "uvicorn proofframe.app:app --host 127.0.0.1 --port 8088", | |
| "python scripts/recording_assets.py --verify-public", | |
| "python scripts/api_smoke.py --base-url https://adjcjh-backblaze-proofframe.hf.space", | |
| "python scripts/run_b2_live_proof.py --env-file .env.final.local --evidence-out docs/assets/b2-live-proof-evidence.json", | |
| "python scripts/run_final_live_proof.py --env-file .env.final.local --evidence-out docs/assets/final-live-proof-evidence.json", | |
| "python scripts/demo_storyboard.py --strict-final", | |
| "python scripts/demo_video_draft.py --build-video", | |
| 'python scripts/public_video_check.py --video-url "$PROOFFRAME_PUBLIC_VIDEO_URL" --verify-url --strict-final', | |
| "python scripts/demo_readiness.py --strict-final", | |
| "python scripts/final_submission_control.py --strict-final", | |
| ] | |
| FetchResult = dict[str, Any] | |
| Fetcher = Callable[[str, int], FetchResult] | |
| def load_json(path: Path) -> dict[str, Any] | None: | |
| try: | |
| return json.loads(path.read_text(encoding="utf-8")) | |
| except (FileNotFoundError, json.JSONDecodeError): | |
| return None | |
| def fetch_text(url: str, timeout: int = TIMEOUT_SECONDS) -> FetchResult: | |
| request = Request(url, headers={"User-Agent": "ProofFrame recording asset verifier"}) | |
| try: | |
| with urlopen(request, timeout=timeout) as response: | |
| body = response.read().decode("utf-8", errors="replace") | |
| return {"ok": True, "status": response.status, "body": body, "error": None} | |
| except HTTPError as error: | |
| body = error.read().decode("utf-8", errors="replace") | |
| return {"ok": False, "status": error.code, "body": body, "error": str(error)} | |
| except URLError as error: | |
| return {"ok": False, "status": None, "body": "", "error": str(error.reason)} | |
| except TimeoutError as error: | |
| return {"ok": False, "status": None, "body": "", "error": str(error)} | |
| def public_url(base_url: str, path: str) -> str: | |
| return f"{base_url.rstrip('/')}{path}" | |
| def file_record(root: Path, relative_path: str) -> dict[str, Any]: | |
| path = root / relative_path | |
| return { | |
| "path": relative_path, | |
| "present": path.exists(), | |
| "bytes": path.stat().st_size if path.exists() else 0, | |
| } | |
| def report_summary( | |
| root: Path, | |
| relative_path: str, | |
| expected_schema: str, | |
| fields: list[str], | |
| ) -> dict[str, Any]: | |
| data = load_json(root / relative_path) | |
| if data is None: | |
| return {"present": False, "path": relative_path, "schema_ok": False, "schema": None} | |
| summary = { | |
| "present": True, | |
| "path": relative_path, | |
| "schema_ok": data.get("schema") == expected_schema, | |
| "schema": data.get("schema"), | |
| "mode": data.get("mode"), | |
| } | |
| summary.update({field: data.get(field) for field in fields}) | |
| return summary | |
| def parse_json_body(result: FetchResult) -> dict[str, Any] | None: | |
| try: | |
| return json.loads(str(result.get("body") or "")) | |
| except json.JSONDecodeError: | |
| return None | |
| def verify_public_demo(base_url: str, fetcher: Fetcher = fetch_text) -> dict[str, Any]: | |
| judge_url = public_url(base_url, PUBLIC_JUDGE_PATH) | |
| health_url = public_url(base_url, "/api/health") | |
| gate_url = public_url(base_url, "/api/submission/gate") | |
| html = fetcher(judge_url, TIMEOUT_SECONDS) | |
| health = fetcher(health_url, TIMEOUT_SECONDS) | |
| gate = fetcher(gate_url, TIMEOUT_SECONDS) | |
| html_body = str(html.get("body") or "") | |
| health_json = parse_json_body(health) | |
| gate_json = parse_json_body(gate) | |
| html_markers = { | |
| "judge_recording_slate": "Judge recording slate" in html_body, | |
| "sponsor_evidence_model": "Sponsor Evidence Model" in html_body, | |
| "recording_runbook": "Recording Runbook" in html_body, | |
| "auto_load_judge_demo": "shouldAutoLoadJudgeDemo" in html_body, | |
| "final_report_gate_copy": "Final reports pending" in html_body, | |
| } | |
| health_ok = bool( | |
| health_json | |
| and health_json.get("ready") is True | |
| and health_json.get("storage_backend") == "local" | |
| and health_json.get("generation_backend") == "mock" | |
| ) | |
| gate_paths = [ | |
| gate_json.get("tasks_path") if gate_json else None, | |
| gate_json.get("evidence_gate", {}).get("path") if gate_json else None, | |
| gate_json.get("packet_gate", {}).get("path") if gate_json else None, | |
| ] | |
| gate_paths_relative = bool( | |
| gate_json | |
| and all(isinstance(path, str) and path and not path.startswith("/") for path in gate_paths) | |
| ) | |
| report_gate_present = bool(gate_json and isinstance(gate_json.get("report_gate"), dict)) | |
| gate_ok = bool( | |
| gate_json | |
| and gate_json.get("mode") in {"pre_live_safe", "final_ready"} | |
| and report_gate_present | |
| and gate_paths_relative | |
| ) | |
| ok = bool( | |
| html.get("ok") | |
| and health.get("ok") | |
| and gate.get("ok") | |
| and all(html_markers.values()) | |
| and health_ok | |
| and gate_ok | |
| ) | |
| return { | |
| "checked": True, | |
| "ok": ok, | |
| "judge_url": judge_url, | |
| "health_url": health_url, | |
| "gate_url": gate_url, | |
| "html_status": html.get("status"), | |
| "health_status": health.get("status"), | |
| "gate_status": gate.get("status"), | |
| "html_markers": html_markers, | |
| "health": { | |
| "ok": health_ok, | |
| "storage_backend": health_json.get("storage_backend") if health_json else None, | |
| "generation_backend": health_json.get("generation_backend") if health_json else None, | |
| "ready": health_json.get("ready") if health_json else None, | |
| }, | |
| "submission_gate": { | |
| "ok": gate_ok, | |
| "mode": gate_json.get("mode") if gate_json else None, | |
| "summary": gate_json.get("summary") if gate_json else None, | |
| "report_gate_present": report_gate_present, | |
| "report_gate_status": ( | |
| gate_json.get("report_gate", {}).get("status") if gate_json else None | |
| ), | |
| "paths_relative": gate_paths_relative, | |
| }, | |
| "errors": [ | |
| str(item.get("error")) | |
| for item in [html, health, gate] | |
| if item.get("error") | |
| ], | |
| } | |
| def unchecked_public_demo(base_url: str) -> dict[str, Any]: | |
| return { | |
| "checked": False, | |
| "ok": None, | |
| "judge_url": public_url(base_url, PUBLIC_JUDGE_PATH), | |
| "health_url": public_url(base_url, "/api/health"), | |
| "gate_url": public_url(base_url, "/api/submission/gate"), | |
| } | |
| def build_next_actions( | |
| *, | |
| missing_assets: list[str], | |
| source_reports_ok: bool, | |
| public_verification: dict[str, Any], | |
| final_video_ready: bool, | |
| ) -> list[str]: | |
| actions: list[str] = [] | |
| if missing_assets: | |
| actions.append("Restore missing recording assets before recording the demo.") | |
| if not source_reports_ok: | |
| actions.append("Regenerate storyboard, readiness, Devpost form, and final control reports.") | |
| if not public_verification.get("checked"): | |
| actions.append("Run python scripts/recording_assets.py --verify-public before recording.") | |
| elif not public_verification.get("ok"): | |
| actions.append("Refresh or redeploy the public mock demo before recording.") | |
| if not final_video_ready: | |
| actions.append("After live B2/Genblaze proof, record and upload the final public video.") | |
| actions.append("Run python scripts/final_submission_control.py --strict-final before Devpost submit.") | |
| return actions[:6] | |
| def build_report( | |
| root: Path = ROOT, | |
| *, | |
| verify_public: bool = False, | |
| public_base_url: str = PUBLIC_BASE_URL, | |
| fetcher: Fetcher = fetch_text, | |
| ) -> dict[str, Any]: | |
| root = root.resolve() | |
| assets = [file_record(root, path) for path in REQUIRED_RECORDING_ASSETS] | |
| missing_assets = [asset["path"] for asset in assets if not asset["present"]] | |
| source_reports = { | |
| "storyboard": report_summary( | |
| root, | |
| "docs/assets/demo-storyboard.json", | |
| "proofframe.demo_storyboard.v1", | |
| ["mock_storyboard_ready", "final_video_ready", "public_video_ready"], | |
| ), | |
| "readiness": report_summary( | |
| root, | |
| "docs/assets/demo-readiness-report.json", | |
| "proofframe.demo_readiness.v1", | |
| ["mock_recording_ready", "final_recording_ready"], | |
| ), | |
| "public_video_check": report_summary( | |
| root, | |
| "docs/assets/public-video-check.json", | |
| "proofframe.public_video_check.v1", | |
| ["safe_to_submit"], | |
| ), | |
| "demo_video_draft": report_summary( | |
| root, | |
| "docs/assets/demo-video-draft.json", | |
| "proofframe.demo_video_draft.v1", | |
| ["safe_to_submit", "final_video_ready", "public_video_draft_url"], | |
| ), | |
| "final_video_publish_kit": report_summary( | |
| root, | |
| "docs/assets/final-video-publish-kit.json", | |
| "proofframe.final_video_publish_kit.v1", | |
| ["ok", "safe_to_submit", "final_video_ready"], | |
| ), | |
| "devpost_form": report_summary( | |
| root, | |
| "docs/assets/devpost-form-kit.json", | |
| "proofframe.devpost_form_kit.v1", | |
| ["final_form_ready", "public_video_ready"], | |
| ), | |
| "final_control": report_summary( | |
| root, | |
| "docs/assets/final-submission-control.json", | |
| "proofframe.final_submission_control.v1", | |
| ["safe_to_submit"], | |
| ), | |
| } | |
| source_reports_ok = all(report.get("present") and report.get("schema_ok") for report in source_reports.values()) | |
| public_verification = ( | |
| verify_public_demo(public_base_url, fetcher) if verify_public else unchecked_public_demo(public_base_url) | |
| ) | |
| mock_recording_ready = bool( | |
| not missing_assets | |
| and source_reports_ok | |
| and source_reports["storyboard"].get("mock_storyboard_ready") | |
| and source_reports["readiness"].get("mock_recording_ready") | |
| and (public_verification.get("ok") if verify_public else True) | |
| ) | |
| final_video_ready = bool( | |
| mock_recording_ready | |
| and source_reports["storyboard"].get("final_video_ready") | |
| and source_reports["public_video_check"].get("safe_to_submit") | |
| and source_reports["readiness"].get("final_recording_ready") | |
| and source_reports["devpost_form"].get("public_video_ready") | |
| ) | |
| mode = "missing_recording_assets" | |
| if final_video_ready: | |
| mode = "final_video_ready" | |
| elif mock_recording_ready and public_verification.get("ok"): | |
| mode = "public_mock_verified" | |
| elif mock_recording_ready: | |
| mode = "mock_recording_ready" | |
| return { | |
| "schema": "proofframe.recording_assets.v1", | |
| "mode": mode, | |
| "mock_recording_ready": mock_recording_ready, | |
| "public_mock_verified": bool(public_verification.get("ok")), | |
| "final_video_ready": final_video_ready, | |
| "public_base_url": public_base_url, | |
| "required_assets": assets, | |
| "missing_assets": missing_assets, | |
| "source_reports": source_reports, | |
| "public_verification": public_verification, | |
| "shot_plan": SHOT_PLAN, | |
| "recording_commands": RECORDING_COMMANDS, | |
| "next_actions": build_next_actions( | |
| missing_assets=missing_assets, | |
| source_reports_ok=source_reports_ok, | |
| public_verification=public_verification, | |
| final_video_ready=final_video_ready, | |
| ), | |
| } | |
| def render_markdown(report: dict[str, Any]) -> str: | |
| lines = [ | |
| "# ProofFrame Recording Assets", | |
| "", | |
| f"Mode: `{report['mode']}`", | |
| f"Mock recording ready: `{str(report['mock_recording_ready']).lower()}`", | |
| f"Public mock verified: `{str(report['public_mock_verified']).lower()}`", | |
| f"Final video ready: `{str(report['final_video_ready']).lower()}`", | |
| f"Public judge URL: {report['public_verification']['judge_url']}", | |
| "", | |
| "## Source Reports", | |
| "", | |
| ] | |
| for name, source in report["source_reports"].items(): | |
| marker = "OK" if source.get("present") and source.get("schema_ok") else "MISSING" | |
| lines.append(f"- {marker} `{name}`: `{source.get('path')}` mode `{source.get('mode')}`") | |
| lines.extend(["", "## Required Assets", ""]) | |
| for asset in report["required_assets"]: | |
| marker = "OK" if asset["present"] else "MISSING" | |
| lines.append(f"- {marker} `{asset['path']}`") | |
| public = report["public_verification"] | |
| lines.extend(["", "## Public GET-only Verification", ""]) | |
| if public.get("checked"): | |
| lines.extend( | |
| [ | |
| f"- Overall: `{str(public.get('ok')).lower()}`", | |
| f"- HTML status: `{public.get('html_status')}`", | |
| f"- Health: `{public.get('health', {}).get('storage_backend')}` / `{public.get('health', {}).get('generation_backend')}`", | |
| f"- Submission gate: `{public.get('submission_gate', {}).get('mode')}`", | |
| ] | |
| ) | |
| else: | |
| lines.append("- Not checked in this run. Use `python scripts/recording_assets.py --verify-public`.") | |
| lines.extend(["", "## Shot Plan", ""]) | |
| for shot in report["shot_plan"]: | |
| lines.append(f"- `{shot['id']}` - {shot['source']}: {shot['screen']}") | |
| lines.extend(["", "## Commands", "", "```bash"]) | |
| lines.extend(report["recording_commands"]) | |
| lines.append("```") | |
| lines.extend(["", "## Next Actions", ""]) | |
| if report["next_actions"]: | |
| lines.extend(f"- {action}" for action in report["next_actions"]) | |
| else: | |
| lines.append("- Recording assets are complete.") | |
| return "\n".join(lines) + "\n" | |
| def write_outputs(report: dict[str, Any], json_path: Path, markdown_path: Path) -> None: | |
| json_path.parent.mkdir(parents=True, exist_ok=True) | |
| markdown_path.parent.mkdir(parents=True, exist_ok=True) | |
| json_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") | |
| markdown_path.write_text(render_markdown(report), encoding="utf-8") | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Build a ProofFrame recording asset report.") | |
| parser.add_argument("--root", type=Path, default=ROOT) | |
| parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON) | |
| parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD) | |
| parser.add_argument("--public-base-url", default=PUBLIC_BASE_URL) | |
| parser.add_argument("--verify-public", action="store_true", help="Run GET-only checks against the public demo.") | |
| parser.add_argument("--strict-final", action="store_true", help="Fail unless final video assets are ready.") | |
| return parser | |
| def main() -> None: | |
| args = build_parser().parse_args() | |
| report = build_report( | |
| args.root, | |
| verify_public=args.verify_public, | |
| public_base_url=args.public_base_url, | |
| ) | |
| write_outputs(report, args.json_out, args.markdown_out) | |
| ok = report["final_video_ready"] if args.strict_final else report["mock_recording_ready"] | |
| print( | |
| json.dumps( | |
| { | |
| "ok": ok, | |
| "mode": report["mode"], | |
| "json": str(args.json_out), | |
| "markdown": str(args.markdown_out), | |
| "next_actions": report["next_actions"], | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| raise SystemExit(0 if ok else 2) | |
| if __name__ == "__main__": | |
| main() | |