diff --git a/scripts/cleanup-fwazb-pr90.py b/scripts/cleanup-fwazb-pr90.py new file mode 100644 index 0000000..e44f1ff --- /dev/null +++ b/scripts/cleanup-fwazb-pr90.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""One-off cleanup for FwazB GitHub PR #90 (the cherry-pick artifact case). + +Phase 3 of the external contributor merge flow rollout. + +Background: + PR #90 was opened by @FwazB on Apr 27 with a single claim about Arcium + confidential computing. The pipeline merged it via cherry-pick (the path + that has now been replaced for gh-pr-* branches by _merge_no_ff_external, + Phase 2). Cherry-pick rewrote the contributor SHA, so the GitHub PR shows + state=open, merged=false, merge_commit_sha=null forever — looks abandoned. + + The claim IS on main: + domains/internet-finance/confidential-computing-reshapes-defi-mechanism-design.md + via two commits: + f6a59d7d claim: confidential computing reshapes DeFi mechanism design + d7916d65 auto-fix: strip 2 broken wiki links + +This script posts an explanatory comment on PR #90 and closes it. The PR +will show "closed" without the merged badge — accepting the artifact for +this single historical case rather than running a backfilled --no-ff +re-merge (which would rewrite main's history just to fix the badge). + +Idempotent: checks current state, no-ops if already closed. + +Usage: + python3 cleanup-fwazb-pr90.py --dry-run + python3 cleanup-fwazb-pr90.py +""" +import argparse +import json +import os +import sys +import urllib.request + +GITHUB_PAT_FILE = "/opt/teleo-eval/secrets/github-pat" +GITHUB_REPO = "living-ip/teleo-codex" +PR_NUMBER = 90 + +CLAIM_PATH = "domains/internet-finance/confidential-computing-reshapes-defi-mechanism-design.md" +CLAIM_COMMIT = "f6a59d7d" # the original add commit from cherry-pick +FIX_COMMIT = "d7916d65" # the auto-fixer wiki-link strip commit + +CLOSE_COMMENT = ( + "This contribution is **merged into the knowledge base** — but you'll " + "see this PR shows \"open\" with no diff. Two commits explain why:\n" + "\n" + f"- [`{CLAIM_COMMIT}`](https://github.com/{GITHUB_REPO}/commit/{CLAIM_COMMIT}) — your claim, added on `main`\n" + f"- [`{FIX_COMMIT}`](https://github.com/{GITHUB_REPO}/commit/{FIX_COMMIT}) — pipeline auto-fixer (stripped 2 broken wiki-link brackets)\n" + "\n" + f"Claim is live at [`{CLAIM_PATH}`](https://github.com/{GITHUB_REPO}/blob/main/{CLAIM_PATH}).\n" + "\n" + "Why the PR doesn't show as merged: the pipeline previously used " + "`git cherry-pick` to land contributions, which rewrote the commit SHA. " + "GitHub's \"merged\" badge fires when the PR head SHA is in main's history " + "— after a cherry-pick, the original SHA isn't there.\n" + "\n" + "**This has been fixed.** Future external GitHub PRs go through " + "`git merge --no-ff`, which preserves the contributor SHA so the merged " + "badge fires correctly. Closing this one as the lone historical artifact.\n" + "\n" + "Thank you for the Arcium claim — it's running through the pipeline as " + "intended.\n" + "\n" + "_— Epimetheus, on behalf of the LivingIP pipeline._" +) + + +def gh_api(method: str, path: str, token: str, body: dict | None = None) -> dict | None: + url = f"https://api.github.com{path}" + data = json.dumps(body).encode() if body else None + req = urllib.request.Request(url, data=data, method=method) + req.add_header("Authorization", f"token {token}") + req.add_header("Accept", "application/vnd.github+json") + if data: + req.add_header("Content-Type", "application/json") + try: + with urllib.request.urlopen(req, timeout=15) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as e: + body_text = e.read().decode()[:500] + print(f"ERROR: GitHub API {method} {path} returned {e.code}: {body_text}", file=sys.stderr) + return None + except Exception as e: + print(f"ERROR: GitHub API {method} {path}: {e}", file=sys.stderr) + return None + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + + if not os.path.exists(GITHUB_PAT_FILE): + print(f"ERROR: GitHub PAT not found at {GITHUB_PAT_FILE}", file=sys.stderr) + sys.exit(1) + token = open(GITHUB_PAT_FILE).read().strip() + + print(f"=== cleanup FwazB GitHub PR #{PR_NUMBER} ===") + print(f" repo: {GITHUB_REPO}") + print(f" dry_run: {args.dry_run}") + + pr = gh_api("GET", f"/repos/{GITHUB_REPO}/pulls/{PR_NUMBER}", token) + if pr is None: + print("ERROR: PR fetch failed", file=sys.stderr) + sys.exit(2) + + print(f" state: {pr['state']}") + print(f" merged: {pr['merged']}") + print(f" user: {pr['user']['login']}") + + if pr["state"] == "closed": + print("\nNO-OP: PR already closed.") + sys.exit(0) + + if pr["user"]["login"].lower() != "fwazb": + print(f"\nABORT: PR author is {pr['user']['login']!r}, expected 'FwazB'. " + "Refusing to act on the wrong PR.", file=sys.stderr) + sys.exit(3) + + if args.dry_run: + print("\nWould post comment + close (skipped: --dry-run).") + print(f"\nComment preview:\n{'-'*70}\n{CLOSE_COMMENT}\n{'-'*70}") + sys.exit(0) + + # Step 1: post comment + print("\nPosting comment...") + comment_resp = gh_api( + "POST", + f"/repos/{GITHUB_REPO}/issues/{PR_NUMBER}/comments", + token, + {"body": CLOSE_COMMENT}, + ) + if comment_resp is None: + print("ERROR: comment post failed", file=sys.stderr) + sys.exit(4) + print(f" comment posted: {comment_resp.get('html_url', '')}") + + # Step 2: close PR + print("Closing PR...") + close_resp = gh_api( + "PATCH", + f"/repos/{GITHUB_REPO}/pulls/{PR_NUMBER}", + token, + {"state": "closed"}, + ) + if close_resp is None: + print("ERROR: PR close failed", file=sys.stderr) + sys.exit(5) + print(f" PR state: {close_resp['state']}") + + print("\nDone.") + + +if __name__ == "__main__": + main()