Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """Build a no-secret operator checklist for the final Devpost web submission.""" | |
| 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] | |
| SCHEMA = "proofframe.devpost_submission_checklist.v1" | |
| DEFAULT_JSON = ROOT / "docs" / "assets" / "devpost-submission-checklist.json" | |
| DEFAULT_MD = ROOT / "docs" / "assets" / "devpost-submission-checklist.md" | |
| FINAL_PREREQUISITE_TASKS = ["T020", "T021", "T040", "T041A"] | |
| SUBMISSION_TASKS = ["T041", "T042"] | |
| FIELD_ORDER = [ | |
| "project_name", | |
| "tagline", | |
| "one_liner", | |
| "short_description", | |
| "repository_url", | |
| "demo_url", | |
| "video_url", | |
| "built_with", | |
| "tags", | |
| "inspiration", | |
| "what_it_does", | |
| "how_we_built_it", | |
| "backblaze_b2_usage", | |
| "genblaze_usage", | |
| "challenges", | |
| "accomplishments", | |
| "what_we_learned", | |
| "whats_next", | |
| "judging_note", | |
| ] | |
| 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]: | |
| tasks = load_json(root / "tasks.json") | |
| return { | |
| str(task.get("id", "")).upper(): str(task.get("status", "missing")) | |
| for task in tasks.get("tasks", []) | |
| } | |
| def form_field_map(form_kit: dict[str, Any]) -> dict[str, dict[str, Any]]: | |
| return { | |
| str(field.get("id")): field | |
| for field in form_kit.get("fields", []) | |
| if field.get("id") | |
| } | |
| def ordered_fields(form_kit: dict[str, Any]) -> list[dict[str, Any]]: | |
| fields_by_id = form_field_map(form_kit) | |
| ordered = [fields_by_id[field_id] for field_id in FIELD_ORDER if field_id in fields_by_id] | |
| known = {field.get("id") for field in ordered} | |
| extras = [ | |
| field | |
| for field in form_kit.get("fields", []) | |
| if field.get("id") not in known | |
| ] | |
| return ordered + extras | |
| def field_item(field: dict[str, Any]) -> dict[str, Any]: | |
| value = str(field.get("value", "")) | |
| return { | |
| "id": field.get("id"), | |
| "label": field.get("label"), | |
| "source": field.get("source"), | |
| "chars": field.get("chars"), | |
| "max_chars": field.get("max_chars"), | |
| "ok_for_mock": bool(field.get("ok_for_mock")), | |
| "ok_for_final": bool(field.get("ok_for_final")), | |
| "copy_value": value, | |
| "copy_instruction": f"Paste into Devpost field: {field.get('label')}", | |
| "verification": [ | |
| "Confirm the pasted text matches this checklist exactly.", | |
| "Confirm Devpost does not show a length or required-field error.", | |
| ], | |
| } | |
| def build_field_items(form_kit: dict[str, Any]) -> list[dict[str, Any]]: | |
| return [field_item(field) for field in ordered_fields(form_kit)] | |
| def all_required_tasks_done(statuses: dict[str, str]) -> bool: | |
| return all(statuses.get(task_id) == "done" for task_id in FINAL_PREREQUISITE_TASKS) | |
| def build_preflight( | |
| *, | |
| form_kit: dict[str, Any], | |
| packet: dict[str, Any], | |
| final_control: dict[str, Any], | |
| submission_audit: dict[str, Any], | |
| receipt: dict[str, Any], | |
| statuses: dict[str, str], | |
| ) -> list[dict[str, Any]]: | |
| return [ | |
| { | |
| "id": "final_form_ready", | |
| "ok": form_kit.get("final_form_ready") is True, | |
| "detail": f"Devpost form kit mode is {form_kit.get('mode')}.", | |
| "evidence": "docs/assets/devpost-form-kit.json", | |
| }, | |
| { | |
| "id": "packet_post_live_verified", | |
| "ok": packet.get("mode") == "post_live_verified", | |
| "detail": f"Devpost packet mode is {packet.get('mode')}.", | |
| "evidence": "docs/assets/devpost-submission-packet.json", | |
| }, | |
| { | |
| "id": "prerequisite_tasks_done", | |
| "ok": all_required_tasks_done(statuses), | |
| "detail": ", ".join(f"{task}={statuses.get(task, 'missing')}" for task in FINAL_PREREQUISITE_TASKS), | |
| "evidence": "tasks.json", | |
| }, | |
| { | |
| "id": "submission_audit_report_present", | |
| "ok": submission_audit.get("schema") == "proofframe.submission_audit.v1", | |
| "detail": f"Submission audit mode is {submission_audit.get('mode')}.", | |
| "evidence": "docs/assets/submission-audit-report.json", | |
| }, | |
| { | |
| "id": "final_control_report_present", | |
| "ok": final_control.get("schema") == "proofframe.final_submission_control.v1", | |
| "detail": f"Final control mode is {final_control.get('mode')}; safe_to_submit={final_control.get('safe_to_submit')}.", | |
| "evidence": "docs/assets/final-submission-control.json", | |
| }, | |
| { | |
| "id": "receipt_not_already_done", | |
| "ok": receipt.get("ok") is not True and statuses.get("T042") != "done", | |
| "detail": f"T042={statuses.get('T042', 'missing')}; receipt mode={receipt.get('mode')}.", | |
| "evidence": "docs/assets/devpost-submission-receipt.json", | |
| }, | |
| ] | |
| def build_stop_rules() -> list[str]: | |
| return [ | |
| "Do not paste Backblaze keys, Genblaze provider keys, Devpost cookies, authorization headers, or signed URLs into Devpost.", | |
| "Do not press Submit unless every preflight item is OK and final_submission_control.py --strict-final passes.", | |
| "If Devpost changes field labels or requirements, stop and update this checklist before submitting.", | |
| "After Devpost accepts the project, record only the public project URL and submitted timestamp; never capture cookies or private browser state.", | |
| ] | |
| def build_post_submit_commands() -> list[str]: | |
| return [ | |
| '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."', | |
| 'python3 scripts/task.py done T042 --note "Devpost project submitted and public receipt captured."', | |
| "PYTHONPATH=src python scripts/final_submission_control.py --strict-final", | |
| "PYTHONPATH=src python scripts/submission_bundle.py", | |
| ] | |
| def build_checklist(root: Path = ROOT) -> dict[str, Any]: | |
| root = root.resolve() | |
| form_kit = load_json(root / "docs" / "assets" / "devpost-form-kit.json") | |
| packet = load_json(root / "docs" / "assets" / "devpost-submission-packet.json") | |
| final_control = load_json(root / "docs" / "assets" / "final-submission-control.json") | |
| submission_audit = load_json(root / "docs" / "assets" / "submission-audit-report.json") | |
| receipt = load_json(root / "docs" / "assets" / "devpost-submission-receipt.json") | |
| statuses = task_statuses(root) | |
| fields = build_field_items(form_kit) | |
| field_gate_ok = bool(fields) and all(field["ok_for_final"] for field in fields) | |
| preflight = build_preflight( | |
| form_kit=form_kit, | |
| packet=packet, | |
| final_control=final_control, | |
| submission_audit=submission_audit, | |
| receipt=receipt, | |
| statuses=statuses, | |
| ) | |
| ready = field_gate_ok and all(item["ok"] for item in preflight) | |
| return { | |
| "schema": SCHEMA, | |
| "created_at": utc_now(), | |
| "mode": "ready_to_submit_devpost" if ready else "pre_submit_blocked", | |
| "ok": ready, | |
| "safe_to_submit": ready, | |
| "task_statuses": { | |
| task_id: statuses.get(task_id, "missing") | |
| for task_id in FINAL_PREREQUISITE_TASKS + SUBMISSION_TASKS | |
| }, | |
| "preflight": preflight, | |
| "field_gate": { | |
| "ok": field_gate_ok, | |
| "field_count": len(fields), | |
| "final_pending_fields": [ | |
| field["id"] for field in fields if not field["ok_for_final"] | |
| ], | |
| }, | |
| "fields": fields, | |
| "stop_rules": build_stop_rules(), | |
| "post_submit_commands": build_post_submit_commands(), | |
| } | |
| def render_markdown(checklist: dict[str, Any]) -> str: | |
| lines = [ | |
| "# ProofFrame Devpost Submission Checklist", | |
| "", | |
| f"Mode: `{checklist['mode']}`", | |
| f"OK: `{str(checklist['ok']).lower()}`", | |
| f"Safe to submit: `{str(checklist['safe_to_submit']).lower()}`", | |
| "", | |
| "## Preflight", | |
| "", | |
| ] | |
| for item in checklist["preflight"]: | |
| marker = "OK" if item["ok"] else "BLOCKED" | |
| lines.append(f"- {marker} `{item['id']}`: {item['detail']} Evidence: `{item['evidence']}`") | |
| lines.extend(["", "## Copy Order", ""]) | |
| for index, field in enumerate(checklist["fields"], start=1): | |
| final_status = "FINAL OK" if field["ok_for_final"] else "FINAL PENDING" | |
| lines.extend( | |
| [ | |
| f"### {index}. {field['label']}", | |
| "", | |
| f"- Field id: `{field['id']}`", | |
| f"- Source: `{field['source']}`", | |
| f"- Status: `{final_status}`", | |
| f"- Length: `{field['chars']} / {field['max_chars']}`", | |
| "", | |
| "```text", | |
| field["copy_value"], | |
| "```", | |
| "", | |
| ] | |
| ) | |
| lines.extend(["## Stop Rules", ""]) | |
| lines.extend(f"- {rule}" for rule in checklist["stop_rules"]) | |
| lines.extend(["", "## Post-submit Commands", "", "```bash"]) | |
| lines.extend(checklist["post_submit_commands"]) | |
| lines.extend(["```", ""]) | |
| lines.append("This checklist contains Devpost copy only, never credentials or browser session data.") | |
| return "\n".join(lines).rstrip() + "\n" | |
| def write_outputs(checklist: 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(checklist, indent=2) + "\n", encoding="utf-8") | |
| markdown_path.write_text(render_markdown(checklist), encoding="utf-8") | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Build a final Devpost web submission 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-final", action="store_true", help="Fail unless Devpost is ready to submit.") | |
| return parser | |
| def main() -> None: | |
| args = build_parser().parse_args() | |
| checklist = build_checklist(args.root) | |
| write_outputs(checklist, args.json_out, args.markdown_out) | |
| print( | |
| json.dumps( | |
| { | |
| "ok": checklist["ok"], | |
| "mode": checklist["mode"], | |
| "json": str(args.json_out), | |
| "markdown": str(args.markdown_out), | |
| "safe_to_submit": checklist["safe_to_submit"], | |
| "pending_fields": checklist["field_gate"]["final_pending_fields"], | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| if args.strict_final and not checklist["ok"]: | |
| raise SystemExit(2) | |
| if __name__ == "__main__": | |
| main() | |