#!/usr/bin/env python3 """Build a no-secret launch plan for the final ProofFrame submission pass.""" from __future__ import annotations import argparse import json from datetime import datetime, timezone from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_JSON = ROOT / "docs" / "assets" / "final-launch-plan.json" DEFAULT_MD = ROOT / "docs" / "assets" / "final-launch-plan.md" SCHEMA = "proofframe.final_launch_plan.v1" EXPECTED_SECRET_MISSING_IDS = {"b2_key_id", "b2_application_key", "genblaze_api_key"} def utc_now() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") def load_json(path: Path) -> dict[str, Any]: try: return json.loads(path.read_text(encoding="utf-8")) except (FileNotFoundError, json.JSONDecodeError): return {} def task_statuses(root: Path) -> dict[str, str]: data = load_json(root / "tasks.json") return { str(task.get("id", "")).upper(): str(task.get("status", "missing")) for task in data.get("tasks", []) } def report(root: Path, relative_path: str) -> dict[str, Any]: data = load_json(root / relative_path) data["_path"] = relative_path data["_present"] = bool(data) return data def evidence_status(root: Path, relative_path: str) -> dict[str, Any]: data = load_json(root / relative_path) ok = bool(data.get("ok") or data.get("safe_to_commit") or data.get("evidence_ok")) return { "path": relative_path, "present": bool(data), "ok": ok, "mode": data.get("mode") or data.get("status"), } def blocking_ids(final_control: dict[str, Any]) -> set[str]: ids: set[str] = set() for item in final_control.get("blocking_items", []): if isinstance(item, dict): item_id = item.get("id") else: item_id = item if item_id: ids.add(str(item_id)) return ids def make_phase( *, phase_id: str, title: str, status: str, detail: str, command: str | None, expected_artifacts: list[str], safe_to_commit_after_scan: list[str] | None = None, task_updates_after_success: list[str] | None = None, ) -> dict[str, Any]: return { "id": phase_id, "title": title, "status": status, "detail": detail, "command": command, "expected_artifacts": expected_artifacts, "safe_to_commit_after_scan": safe_to_commit_after_scan or [], "task_updates_after_success": task_updates_after_success or [], } def build_phases(root: Path, reports: dict[str, dict[str, Any]], statuses: dict[str, str]) -> list[dict[str, Any]]: handoff = reports["credential_handoff"] operator = reports["operator_brief"] final_control = reports["final_control"] recording = reports["recording_assets"] submission_audit = reports["submission_audit"] receipt = reports["devpost_receipt"] b2_evidence = reports["b2_evidence"] final_evidence = reports["final_evidence"] b2_checklist = reports["b2_key_scope_checklist"] b2_confirmation = b2_checklist.get("pre_key_creation_confirmation") or {} b2_confirmation_phrase = b2_confirmation.get("required_phrase") missing_ids = {str(item) for item in handoff.get("missing_ids", [])} blocks = blocking_ids(final_control) credential_done = bool(handoff.get("ok")) credential_ready = bool( operator.get("ready_for_secret_entry") and missing_ids == EXPECTED_SECRET_MISSING_IDS and not credential_done ) if credential_done: credential_status = "done" credential_detail = "Live credential handoff is complete; no missing ids remain." elif credential_ready: credential_status = "ready" credential_detail = "Only expected secret ids are missing; operator can enter them locally." else: credential_status = "blocked" credential_detail = "Credential handoff is missing unexpected setup values; regenerate .env.final.local prefill." b2_done = statuses.get("T020") == "done" and b2_evidence.get("ok") b2_status = "done" if b2_done else ("ready" if credential_done else "blocked") b2_detail = ( "Sanitized B2 evidence is present and T020 is done." if b2_done else "Run after live credential handoff is complete." if credential_done else "Waiting on credential entry." ) genblaze_done = statuses.get("T021") == "done" and final_evidence.get("ok") genblaze_status = "done" if genblaze_done else ("ready" if b2_done else "blocked") genblaze_detail = ( "Sanitized final B2 plus Genblaze evidence is present and T021 is done." if genblaze_done else "Run after the B2-only proof passes so final evidence has storage and generation proof." if b2_done else "Waiting on the B2-only proof." ) video_done = bool(recording.get("final_video_ready")) video_status = "done" if video_done else ("ready" if genblaze_done else "blocked") video_detail = ( "Final public video is verified by recording assets." if video_done else "Record and upload the public demo after live proof evidence exists." if genblaze_done else "Waiting on live B2 plus Genblaze evidence." ) safety_done = statuses.get("T041A") == "done" and statuses.get("T041") == "done" and bool( submission_audit.get("ok") ) safety_status = "done" if safety_done else ("ready" if video_done else "blocked") safety_detail = ( "Final secret scan and submission audit are signed off." if safety_done else "Run final scan and strict submission audit after the public video is ready." if video_done else "Waiting on final public video." ) devpost_done = statuses.get("T042") == "done" and bool(receipt.get("ok")) devpost_ready = safety_done and blocks <= {"devpost_submitted"} devpost_status = "done" if devpost_done else ("ready" if devpost_ready else "blocked") devpost_detail = ( "Devpost receipt is present and T042 is done." if devpost_done else "Submit Devpost and generate the public receipt." if devpost_ready else "Waiting on all pre-submit gates." ) return [ make_phase( phase_id="credential_entry", title="Enter final credentials locally", status=credential_status, detail=credential_detail, command="python scripts/final_env_wizard.py --output .env.final.local --missing-only --force", expected_artifacts=[ "docs/assets/b2-key-scope-checklist.md reviewed before key creation", ( f"B2 pre-key confirmation phrase recorded without secrets: {b2_confirmation_phrase}" if b2_confirmation_phrase else "B2 pre-key confirmation phrase recorded without secrets" ), ".env.final.local (git-ignored, never committed)", ], ), make_phase( phase_id="b2_live_proof", title="Capture Backblaze B2 live proof", status=b2_status, detail=b2_detail, command="python scripts/run_b2_live_proof.py --env-file .env.final.local --evidence-out docs/assets/b2-live-proof-evidence.json", expected_artifacts=["docs/assets/b2-live-proof-evidence.json"], safe_to_commit_after_scan=["docs/assets/b2-live-proof-evidence.json"], task_updates_after_success=[ 'python3 scripts/task.py done T020 --note "B2 live proof evidence captured in docs/assets/b2-live-proof-evidence.json."' ], ), make_phase( phase_id="genblaze_live_proof", title="Capture final B2 plus Genblaze proof", status=genblaze_status, detail=genblaze_detail, command=( "python scripts/run_final_live_proof.py --env-file .env.final.local " "--genblaze-provider local --genblaze-image-model local-svg-v1 " "--evidence-out docs/assets/final-live-proof-evidence.json" ), expected_artifacts=["docs/assets/final-live-proof-evidence.json"], safe_to_commit_after_scan=["docs/assets/final-live-proof-evidence.json"], task_updates_after_success=[ 'python3 scripts/task.py done T021 --note "Final B2 plus Genblaze live proof evidence captured in docs/assets/final-live-proof-evidence.json."' ], ), make_phase( phase_id="public_video", title="Record and verify public demo video", status=video_status, detail=video_detail, command='python scripts/devpost_packet.py --post-live --video-url "$PROOFFRAME_PUBLIC_VIDEO_URL"', expected_artifacts=[ "public demo video URL", "docs/assets/devpost-submission-packet.json", "docs/assets/devpost-form-kit.json", "docs/assets/demo-storyboard.json", "docs/assets/recording-assets.json", ], safe_to_commit_after_scan=[ "docs/assets/devpost-submission-packet.json", "docs/assets/devpost-form-kit.json", "docs/assets/demo-storyboard.json", "docs/assets/recording-assets.json", ], ), make_phase( phase_id="final_safety_audit", title="Run final secret scan and submission audit", status=safety_status, detail=safety_detail, command="python scripts/secret_scan.py && python scripts/submission_audit.py --strict-final", expected_artifacts=[ "docs/assets/secret-scan-report.json", "docs/assets/submission-audit-report.json", ], safe_to_commit_after_scan=[ "docs/assets/secret-scan-report.json", "docs/assets/submission-audit-report.json", ], task_updates_after_success=[ 'python3 scripts/task.py done T041A --note "Final secret scan clear after live proof and public video."', 'python3 scripts/task.py done T041 --note "Final submission audit passed after live proof and public video."', ], ), make_phase( phase_id="devpost_submit", title="Submit Devpost and capture receipt", status=devpost_status, detail=devpost_detail, command='python scripts/devpost_submission_receipt.py --project-url "$PROOFFRAME_DEVPOST_PROJECT_URL" --submitted-at "$PROOFFRAME_DEVPOST_SUBMITTED_AT" --confirmation-note "Devpost accepted/submitted the ProofFrame project."', expected_artifacts=["docs/assets/devpost-submission-receipt.json"], safe_to_commit_after_scan=["docs/assets/devpost-submission-receipt.json"], task_updates_after_success=[ 'python3 scripts/task.py done T042 --note "Devpost project submitted and public receipt captured."' ], ), ] def first_open_phase(phases: list[dict[str, Any]]) -> dict[str, Any] | None: for phase in phases: if phase["status"] != "done": return phase return None def build_report(root: Path = ROOT) -> dict[str, Any]: root = root.resolve() statuses = task_statuses(root) reports = { "credential_handoff": report(root, "docs/assets/live-credential-handoff.json"), "operator_brief": report(root, "docs/assets/final-operator-brief.json"), "b2_key_scope_checklist": report(root, "docs/assets/b2-key-scope-checklist.json"), "final_control": report(root, "docs/assets/final-submission-control.json"), "recording_assets": report(root, "docs/assets/recording-assets.json"), "submission_audit": report(root, "docs/assets/submission-audit-report.json"), "devpost_receipt": report(root, "docs/assets/devpost-submission-receipt.json"), "b2_evidence": evidence_status(root, "docs/assets/b2-live-proof-evidence.json"), "final_evidence": evidence_status(root, "docs/assets/final-live-proof-evidence.json"), } phases = build_phases(root, reports, statuses) open_phase = first_open_phase(phases) done_count = sum(1 for phase in phases if phase["status"] == "done") ready_count = sum(1 for phase in phases if phase["status"] == "ready") blocked_count = sum(1 for phase in phases if phase["status"] == "blocked") ok = open_phase is None if ok: mode = "submitted" elif open_phase["status"] == "ready": mode = f"ready_for_{open_phase['id']}" else: mode = f"blocked_at_{open_phase['id']}" return { "schema": SCHEMA, "created_at": utc_now(), "ok": ok, "mode": mode, "repo_root": str(root), "current_phase": open_phase["id"] if open_phase else "complete", "summary": { "total": len(phases), "done": done_count, "ready": ready_count, "blocked": blocked_count, }, "task_statuses": { task: statuses.get(task, "missing") for task in ["T020", "T021", "T040", "T041", "T041A", "T042"] }, "phases": phases, "next_command": open_phase.get("command") if open_phase else None, "next_detail": open_phase.get("detail") if open_phase else "Final launch is complete.", "safety_policy": { "no_secret_values_in_report": True, "never_commit": [ ".env.final.local", "Backblaze key IDs or application keys", "Genblaze provider API keys", "Devpost cookies or browser session files", "raw signed URLs or provider temporary URLs", ], "source_env_files": False, }, } def render_markdown(plan: dict[str, Any]) -> str: lines = [ "# ProofFrame Final Launch Plan", "", f"Mode: `{plan['mode']}`", f"Current phase: `{plan['current_phase']}`", f"Complete: `{str(plan['ok']).lower()}`", ( f"Progress: `{plan['summary']['done']} / {plan['summary']['total']}` done; " f"`{plan['summary']['ready']}` ready, `{plan['summary']['blocked']}` blocked." ), "", "## Next Command", "", ] if plan.get("next_command"): lines.extend(["```bash", plan["next_command"], "```"]) lines.append(plan["next_detail"]) else: lines.append("- Final launch is complete.") lines.extend(["", "## Phases", ""]) for phase in plan["phases"]: lines.append(f"### {phase['id']} - {phase['title']}") lines.append(f"- Status: `{phase['status']}`") lines.append(f"- Detail: {phase['detail']}") if phase.get("command"): lines.extend(["- Command:", "```bash", phase["command"], "```"]) lines.append("- Expected artifacts:") lines.extend(f" - `{artifact}`" for artifact in phase["expected_artifacts"]) if phase["safe_to_commit_after_scan"]: lines.append("- Safe to commit after scan:") lines.extend(f" - `{artifact}`" for artifact in phase["safe_to_commit_after_scan"]) if phase["task_updates_after_success"]: lines.append("- Task ledger updates after success:") lines.extend(f" - `{command}`" for command in phase["task_updates_after_success"]) lines.append("") lines.extend(["## Safety Policy", ""]) lines.append("- This report contains command strings and artifact paths only; no secret values.") lines.append("- Do not commit:") lines.extend(f" - {item}" for item in plan["safety_policy"]["never_commit"]) lines.append("- Do not source `.env.final.local`; use the parser-based `--env-file` commands.") return "\n".join(lines).rstrip() + "\n" def write_outputs(plan: 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(plan, indent=2) + "\n", encoding="utf-8") markdown_path.write_text(render_markdown(plan), encoding="utf-8") def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Build a no-secret final launch plan.") 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("--strict-final", action="store_true", help="Fail unless every launch phase is done.") return parser def main() -> None: args = build_parser().parse_args() plan = build_report(args.root) write_outputs(plan, args.json_out, args.markdown_out) print( json.dumps( { "ok": plan["ok"], "mode": plan["mode"], "json": str(args.json_out), "markdown": str(args.markdown_out), "current_phase": plan["current_phase"], "next_command": plan["next_command"], }, indent=2, ) ) if args.strict_final and not plan["ok"]: raise SystemExit(2) if __name__ == "__main__": main()