Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """Build a no-secret final-submission rehearsal checklist.""" | |
| 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-rehearsal-checklist.json" | |
| DEFAULT_MD = ROOT / "docs" / "assets" / "final-rehearsal-checklist.md" | |
| SCHEMA = "proofframe.final_rehearsal.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, expected_schema: str) -> dict[str, Any]: | |
| payload = load_json(root / relative_path) | |
| return { | |
| "path": relative_path, | |
| "present": bool(payload), | |
| "schema_ok": payload.get("schema") == expected_schema, | |
| "mode": payload.get("mode"), | |
| "ok": payload.get("ok"), | |
| "safe_to_submit": payload.get("safe_to_submit"), | |
| "ready_for_secret_entry": payload.get("ready_for_secret_entry"), | |
| "current_phase": payload.get("current_phase"), | |
| "next_command": payload.get("next_command"), | |
| "mock_form_ready": payload.get("mock_form_ready"), | |
| "final_form_ready": payload.get("final_form_ready"), | |
| "mock_recording_ready": payload.get("mock_recording_ready"), | |
| "final_video_ready": payload.get("final_video_ready"), | |
| "runtime_sha": (payload.get("observed") or {}).get("runtime_sha"), | |
| "failed_checks": payload.get("failed_checks", []), | |
| "missing_ids": payload.get("missing_ids") | |
| or (payload.get("credential_handoff") or {}).get("missing_ids", []), | |
| "blocking_items": payload.get("blocking_items", []), | |
| } | |
| def current_reports(root: Path) -> dict[str, dict[str, Any]]: | |
| return { | |
| "operator_brief": report( | |
| root, | |
| "docs/assets/final-operator-brief.json", | |
| "proofframe.final_operator_brief.v1", | |
| ), | |
| "launch_plan": report( | |
| root, | |
| "docs/assets/final-launch-plan.json", | |
| "proofframe.final_launch_plan.v1", | |
| ), | |
| "final_control": report( | |
| root, | |
| "docs/assets/final-submission-control.json", | |
| "proofframe.final_submission_control.v1", | |
| ), | |
| "devpost_form": report( | |
| root, | |
| "docs/assets/devpost-form-kit.json", | |
| "proofframe.devpost_form_kit.v1", | |
| ), | |
| "public_space_sync": report( | |
| root, | |
| "docs/assets/public-space-sync-report.json", | |
| "proofframe.public_space_sync.v1", | |
| ), | |
| "recording_assets": report( | |
| root, | |
| "docs/assets/recording-assets.json", | |
| "proofframe.recording_assets.v1", | |
| ), | |
| "secret_scan": report( | |
| root, | |
| "docs/assets/secret-scan-report.json", | |
| "proofframe.secret_scan.v1", | |
| ), | |
| "submission_audit": report( | |
| root, | |
| "docs/assets/submission-audit-report.json", | |
| "proofframe.submission_audit.v1", | |
| ), | |
| "devpost_receipt": report( | |
| root, | |
| "docs/assets/devpost-submission-receipt.json", | |
| "proofframe.devpost_submission_receipt.v1", | |
| ), | |
| } | |
| def public_space_ready(report_data: dict[str, Any]) -> bool: | |
| return bool(report_data.get("schema_ok") and report_data.get("ok") is True and not report_data.get("failed_checks")) | |
| def build_preconditions(reports: dict[str, dict[str, Any]], statuses: dict[str, str]) -> list[dict[str, Any]]: | |
| missing_ids = sorted(str(item) for item in reports["operator_brief"].get("missing_ids", [])) | |
| return [ | |
| { | |
| "id": "operator_ready", | |
| "ok": reports["operator_brief"].get("ready_for_secret_entry") is True, | |
| "detail": f"Operator brief mode is {reports['operator_brief'].get('mode')}.", | |
| "evidence": reports["operator_brief"]["path"], | |
| }, | |
| { | |
| "id": "only_expected_secrets_missing", | |
| "ok": missing_ids == sorted(EXPECTED_SECRET_MISSING_IDS), | |
| "detail": f"Missing ids: {', '.join(missing_ids) or 'none'}.", | |
| "evidence": reports["operator_brief"]["path"], | |
| }, | |
| { | |
| "id": "launch_plan_at_credential_entry", | |
| "ok": reports["launch_plan"].get("mode") == "ready_for_credential_entry", | |
| "detail": f"Current phase is {reports['launch_plan'].get('current_phase')}.", | |
| "evidence": reports["launch_plan"]["path"], | |
| }, | |
| { | |
| "id": "public_space_synced", | |
| "ok": public_space_ready(reports["public_space_sync"]), | |
| "detail": f"Runtime sha: {reports['public_space_sync'].get('runtime_sha')}.", | |
| "evidence": reports["public_space_sync"]["path"], | |
| }, | |
| { | |
| "id": "mock_form_ready", | |
| "ok": reports["devpost_form"].get("mock_form_ready") is True, | |
| "detail": f"Devpost form mode is {reports['devpost_form'].get('mode')}.", | |
| "evidence": reports["devpost_form"]["path"], | |
| }, | |
| { | |
| "id": "mock_recording_ready", | |
| "ok": reports["recording_assets"].get("mock_recording_ready") is True, | |
| "detail": f"Recording assets mode is {reports['recording_assets'].get('mode')}.", | |
| "evidence": reports["recording_assets"]["path"], | |
| }, | |
| { | |
| "id": "secret_scan_currently_clear", | |
| "ok": reports["secret_scan"].get("ok") is True and reports["secret_scan"].get("mode") == "clear", | |
| "detail": f"Secret scan mode is {reports['secret_scan'].get('mode')}.", | |
| "evidence": reports["secret_scan"]["path"], | |
| }, | |
| { | |
| "id": "final_gate_fail_closed", | |
| "ok": reports["final_control"].get("safe_to_submit") is False, | |
| "detail": f"Final control mode is {reports['final_control'].get('mode')}.", | |
| "evidence": reports["final_control"]["path"], | |
| }, | |
| { | |
| "id": "live_tasks_not_overclaimed", | |
| "ok": statuses.get("T020") != "done" and statuses.get("T021") != "done", | |
| "detail": f"T020={statuses.get('T020')}; T021={statuses.get('T021')}.", | |
| "evidence": "tasks.json", | |
| }, | |
| ] | |
| def rehearsal_steps() -> list[dict[str, Any]]: | |
| return [ | |
| { | |
| "id": "enter_credentials", | |
| "owner": "operator", | |
| "command": "python scripts/final_env_wizard.py --output .env.final.local --missing-only --force", | |
| "success_signal": "docs/assets/live-credential-handoff.json reports no missing ids after live_env_handoff.py --strict.", | |
| "safe_to_commit": [], | |
| }, | |
| { | |
| "id": "b2_live_proof", | |
| "owner": "codex", | |
| "command": "python scripts/run_b2_live_proof.py --env-file .env.final.local --evidence-out docs/assets/b2-live-proof-evidence.json", | |
| "success_signal": "B2 evidence JSON has ok=true, storage_backend=b2, asset and manifest checksums, and sanitized object keys.", | |
| "safe_to_commit": ["docs/assets/b2-live-proof-evidence.json"], | |
| "task_update": 'python3 scripts/task.py done T020 --note "B2 live proof evidence captured in docs/assets/b2-live-proof-evidence.json."', | |
| }, | |
| { | |
| "id": "final_live_proof", | |
| "owner": "codex", | |
| "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" | |
| ), | |
| "success_signal": "Final evidence JSON has storage_backend=b2, generation_backend=genblaze, provider/model metadata, checksums, and no raw provider URLs.", | |
| "safe_to_commit": ["docs/assets/final-live-proof-evidence.json"], | |
| "task_update": 'python3 scripts/task.py done T021 --note "Final B2 plus Genblaze live proof evidence captured in docs/assets/final-live-proof-evidence.json."', | |
| }, | |
| { | |
| "id": "seed_public_video_packet", | |
| "owner": "codex", | |
| "command": 'python scripts/devpost_packet.py --post-live --video-url "$PROOFFRAME_PUBLIC_VIDEO_URL" && python scripts/public_video_check.py --video-url "$PROOFFRAME_PUBLIC_VIDEO_URL" --verify-url --strict-final', | |
| "success_signal": "Devpost packet includes the public video URL and public-video-check reports safe_to_submit=true.", | |
| "safe_to_commit": [ | |
| "docs/assets/devpost-submission-packet.json", | |
| "docs/assets/public-video-check.json", | |
| ], | |
| }, | |
| { | |
| "id": "final_secret_scan", | |
| "owner": "codex", | |
| "command": "python scripts/secret_scan.py", | |
| "success_signal": "Secret scan is clear after live proof and public video URL are present.", | |
| "safe_to_commit": [ | |
| "docs/assets/secret-scan-report.json", | |
| ], | |
| "task_update": 'python3 scripts/task.py done T041A --note "Final secret scan clear after live proof and public video."', | |
| }, | |
| { | |
| "id": "final_video_reports", | |
| "owner": "codex", | |
| "command": "python scripts/devpost_form_kit.py --strict-final && python scripts/demo_storyboard.py --strict-final && python scripts/demo_readiness.py --strict-final && python scripts/recording_assets.py --verify-public --strict-final", | |
| "success_signal": "Devpost form kit, storyboard, demo readiness, and recording assets all report final video readiness.", | |
| "safe_to_commit": [ | |
| "docs/assets/devpost-form-kit.json", | |
| "docs/assets/demo-storyboard.json", | |
| "docs/assets/demo-readiness-report.json", | |
| "docs/assets/recording-assets.json", | |
| ], | |
| }, | |
| { | |
| "id": "devpost_submission_checklist", | |
| "owner": "codex", | |
| "command": "python scripts/devpost_submission_checklist.py --strict-final", | |
| "success_signal": "Devpost submission checklist is ready_to_submit_devpost.", | |
| "safe_to_commit": [ | |
| "docs/assets/devpost-submission-checklist.json", | |
| ], | |
| }, | |
| { | |
| "id": "devpost_submission_preview", | |
| "owner": "codex", | |
| "command": "python scripts/devpost_submission_preview.py", | |
| "success_signal": "Devpost submission preview is regenerated with current copy, evidence links, and remaining final blockers.", | |
| "safe_to_commit": [ | |
| "docs/assets/devpost-submission-preview.json", | |
| ], | |
| }, | |
| { | |
| "id": "final_submission_audit", | |
| "owner": "codex", | |
| "command": "python scripts/submission_audit.py --strict-final", | |
| "success_signal": "Submission audit is pre_submit_audit_ready after live proof, final video, final scan, and Devpost checklist.", | |
| "safe_to_commit": [ | |
| "docs/assets/submission-audit-report.json", | |
| ], | |
| "task_update": 'python3 scripts/task.py done T041 --note "Final submission audit passed after live proof, public video, secret scan, and Devpost checklist."', | |
| }, | |
| { | |
| "id": "devpost_receipt", | |
| "owner": "operator", | |
| "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."', | |
| "success_signal": "Devpost receipt JSON is ok=true, uses a devpost.com project URL, and contains no cookies or session data.", | |
| "safe_to_commit": ["docs/assets/devpost-submission-receipt.json"], | |
| "task_update": 'python3 scripts/task.py done T042 --note "Devpost project submitted and public receipt captured."', | |
| }, | |
| { | |
| "id": "final_green_gate", | |
| "owner": "codex", | |
| "command": "python scripts/secret_scan.py && python scripts/final_submission_control.py --strict-final && python scripts/final_launch_plan.py --strict-final && python scripts/devpost_submission_preview.py --strict-final && python scripts/submission_bundle.py --strict-final", | |
| "success_signal": "Final scan is clear; final control, launch plan, Devpost preview, and submission bundle all report final submit readiness.", | |
| "safe_to_commit": [ | |
| "docs/assets/secret-scan-report.json", | |
| "docs/assets/final-submission-control.json", | |
| "docs/assets/final-launch-plan.json", | |
| "docs/assets/devpost-submission-preview.json", | |
| "docs/assets/submission-bundle-manifest.json", | |
| ], | |
| }, | |
| ] | |
| def stop_rules() -> list[str]: | |
| return [ | |
| "Stop immediately if a command prints or writes a value that looks like a B2 key, Genblaze provider key, browser cookie, authorization header, or signed URL.", | |
| "Do not mark T020 or T021 done unless the corresponding sanitized evidence JSON exists and passes the expected backend/provider checks.", | |
| "Do not run Devpost submission until final_submission_control.py --strict-final passes after live proof, public video, final scan, and final audit.", | |
| "Do not update public copy from pre-live to completed sponsor proof until both B2 and Genblaze evidence are committed after a clean secret scan.", | |
| ] | |
| def build_report(root: Path = ROOT) -> dict[str, Any]: | |
| root = root.resolve() | |
| statuses = task_statuses(root) | |
| reports = current_reports(root) | |
| preconditions = build_preconditions(reports, statuses) | |
| ready = all(item["ok"] for item in preconditions) | |
| return { | |
| "schema": SCHEMA, | |
| "created_at": utc_now(), | |
| "mode": "ready_for_credential_rehearsal" if ready else "needs_rehearsal_setup", | |
| "ok": ready, | |
| "safe_to_submit": False, | |
| "current_phase": reports["launch_plan"].get("current_phase"), | |
| "next_command": reports["launch_plan"].get("next_command"), | |
| "task_statuses": { | |
| task: statuses.get(task, "missing") | |
| for task in ["T020", "T021", "T040", "T041", "T041A", "T042"] | |
| }, | |
| "preconditions": preconditions, | |
| "required_secret_ids": EXPECTED_SECRET_MISSING_IDS, | |
| "steps": rehearsal_steps(), | |
| "stop_rules": stop_rules(), | |
| "reports": reports, | |
| } | |
| def render_markdown(report_data: dict[str, Any]) -> str: | |
| lines = [ | |
| "# ProofFrame Final Rehearsal Checklist", | |
| "", | |
| f"Mode: `{report_data['mode']}`", | |
| f"OK: `{str(report_data['ok']).lower()}`", | |
| f"Safe to submit: `{str(report_data['safe_to_submit']).lower()}`", | |
| f"Current phase: `{report_data.get('current_phase')}`", | |
| f"Next command: `{report_data.get('next_command')}`", | |
| "", | |
| "## Preconditions", | |
| "", | |
| ] | |
| for item in report_data["preconditions"]: | |
| marker = "OK" if item["ok"] else "TODO" | |
| lines.append(f"- {marker} `{item['id']}`: {item['detail']} Evidence: `{item['evidence']}`") | |
| lines.extend(["", "## Required Secret IDs", ""]) | |
| lines.extend(f"- `{secret_id}`" for secret_id in report_data["required_secret_ids"]) | |
| lines.extend(["", "## Rehearsal Steps", ""]) | |
| for index, step in enumerate(report_data["steps"], start=1): | |
| lines.append(f"### {index}. {step['id']} ({step['owner']})") | |
| lines.extend(["```bash", step["command"], "```"]) | |
| lines.append(f"- Success signal: {step['success_signal']}") | |
| if step["safe_to_commit"]: | |
| lines.append("- Safe to commit after a clean secret scan:") | |
| lines.extend(f" - `{path}`" for path in step["safe_to_commit"]) | |
| if step.get("task_update"): | |
| lines.append(f"- Task update after success: `{step['task_update']}`") | |
| lines.append("") | |
| lines.extend(["## Stop Rules", ""]) | |
| lines.extend(f"- {rule}" for rule in report_data["stop_rules"]) | |
| lines.append("") | |
| lines.append("This checklist contains secret names only, never secret values.") | |
| return "\n".join(lines).rstrip() + "\n" | |
| def write_outputs(report_data: 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_data, indent=2) + "\n", encoding="utf-8") | |
| markdown_path.write_text(render_markdown(report_data), encoding="utf-8") | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Build a no-secret final submission rehearsal checklist.") | |
| 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-ready", action="store_true", help="Fail unless ready for credential rehearsal.") | |
| return parser | |
| def main() -> None: | |
| args = build_parser().parse_args() | |
| report_data = build_report(args.root) | |
| write_outputs(report_data, args.json_out, args.markdown_out) | |
| print( | |
| json.dumps( | |
| { | |
| "ok": report_data["ok"], | |
| "mode": report_data["mode"], | |
| "json": str(args.json_out), | |
| "markdown": str(args.markdown_out), | |
| "safe_to_submit": report_data["safe_to_submit"], | |
| "next_command": report_data["next_command"], | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| if args.strict_ready and not report_data["ok"]: | |
| raise SystemExit(2) | |
| if __name__ == "__main__": | |
| main() | |