backblaze-proofframe / scripts /devpost_submission_checklist.py
ADJCJH's picture
Sync ProofFrame public Space with provider fallback
347a4d6 verified
Raw
History Blame Contribute Delete
11.3 kB
#!/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()