#!/usr/bin/env python3 """gen_release_notes_truth.py — freeze oracle truth for the FULL Go port of src/rune/release_notes.py (dichotomy wave #2479). Every public function, every branch, run against the LIVE Python oracle (RUNE_*_CORE=python pinned per the poisoned-truth law). The Gitea HTTP surface is exercised through a scripted fake urllib transport that records (method, url, headers, body) for each call — the Go replay rebuilds the same requests byte-for-byte and replays the same scripted responses. Usage: uv run python scripts/gen_release_notes_truth.py [--out PATH] """ from __future__ import annotations import argparse import io import json import os import sys import tempfile import urllib.error import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) os.environ.setdefault("RUNE_CAPABILITY_CORE", "python") from rune.release_notes import ( # noqa: E402 ReleaseNote, _api, auto_initial_note, build_from_deployment, build_note, convergence_refresh, converged_deployed_sha, extract_evidence_facts, fetch_release, publish_release, render_markdown, sha_from_body, summary_from_body, ) ROWS: list[dict] = [] def row(name: str, **kw) -> None: kw["name"] = name ROWS.append(kw) # ── scripted fake urllib transport ─────────────────────────────────────────── class FakeResp(io.BytesIO): status = 200 def __init__(self, status: int, body: bytes): super().__init__(body) self.status = status def __enter__(self): return self def __exit__(self, *a): return False class FakeHTTPError(urllib.error.HTTPError): def __init__(self, code: int, body: bytes): super().__init__("http://fake", code, "error", None, io.BytesIO(body)) def script_transport(calls: list, script: list[dict]): """Monkeypatch urllib.request.urlopen with a recorder + scripted replies.""" real = urllib.request.urlopen def fake(req, timeout=None): spec = script.pop(0) calls.append( { "method": req.get_method(), "url": req.full_url, "headers": dict(req.headers), "body": req.data.decode() if req.data is not None else None, } ) kind = spec["kind"] if kind == "ok": return FakeResp(spec["status"], spec["body"].encode()) if kind == "http": raise FakeHTTPError(spec["status"], spec["body"].encode()) raise OSError(spec["os_error"]) urllib.request.urlopen = fake return real def run_api(script: list[dict], fn) -> dict: calls: list = [] real = script_transport(calls, list(script)) try: out = fn() finally: urllib.request.urlopen = real return {"result": out, "calls": calls} def want_note(n: ReleaseNote) -> str: return json.dumps(n.to_dict()) def resp_ok(status: int, body) -> dict: return {"kind": "ok", "status": status, "body": body if isinstance(body, str) else json.dumps(body)} def resp_http(status: int, body) -> dict: return {"kind": "http", "status": status, "body": body if isinstance(body, str) else json.dumps(body)} def resp_os(msg: str) -> dict: return {"kind": "os", "os_error": msg} # ── fixture helpers ────────────────────────────────────────────────────────── SHA_A = "a" * 40 SHA_B = "b" * 40 def entity(kind: str, ident: dict, status: str | None = None, ext: dict | None = None) -> dict: e = {"id": f"{kind}-1", "kind": kind, "identities": ident} if status is not None: e["status"] = status if ext is not None: e["ext"] = ext return e def contract(need: str, quality: dict | None = None, rel_evidence=None, cid: str = "contract-1") -> dict: ext: dict = {} if quality is not None: ext["contract_quality"] = quality if rel_evidence is not None: ext["release_evidence"] = rel_evidence return {"id": cid, "kind": "construction_contract", "status": "closed", "identities": {"need": need}, "ext": ext} def note_dict(**over) -> dict: d = { "version": "0.142.0", "summary": "evidence-derived release", "improved": ["faster recall"], "added": ["new gate"], "fixed": ["the drift bug"], "proven": ["parity 25/25"], "limitations": ["known: x"], "references": ["issue #2"], "release_sha": SHA_A, "deployed_sha": SHA_A, "provenance": ["contract B-1"], "backfill": False, } d.update(over) return d # ── ReleaseNote record: __init__ sorting/defaults, to_dict, from_dict ──────── row( "init-unsorted-dedup", op="init", args={"version": "1.2.3", "summary": "s", "improved": ["z", "a", "z", "m"], "added": None, "fixed": ["f"], "proven": [], "limitations": ["l2", "l1"], "references": [], "release_sha": "abc", "deployed_sha": ""}, ) row("init-all-defaults", op="init", args={"version": "0.0.1", "summary": "minimal"}) row("init-unicode-sort", op="init", args={"version": "2.0.0", "summary": "s", "improved": ["émigré", "apple", "Zebra", "zebra", "Änderung"]}) row( "roundtrip-full", op="roundtrip", args=note_dict(backfill=True, provenance=["p2", "p1"]), ) row("roundtrip-empty", op="roundtrip", args=note_dict(version="9.9.9", summary="", improved=[], added=[], fixed=[], proven=[], limitations=[], references=[], release_sha="", deployed_sha="", provenance=[])) # freeze init/roundtrip wants for r in ROWS: if r["op"] == "init": kw = dict(r["args"]) r["want"] = want_note(ReleaseNote(**kw)) elif r["op"] == "roundtrip": d = dict(r["args"]) n = ReleaseNote.from_dict(d) r["want"] = json.dumps(n.to_dict()) # ── render_markdown ────────────────────────────────────────────────────────── for name, nd in [ ("render-both-same", note_dict()), ("render-both-diff", note_dict(release_sha=SHA_A, deployed_sha=SHA_B, backfill=True, provenance=["p1", "p2"])), ("render-deployed-only", note_dict(release_sha="", deployed_sha=SHA_B)), ("render-no-shas", note_dict(release_sha="", deployed_sha="")), ("render-empty-sections", note_dict(improved=[], added=[], fixed=[], proven=[], limitations=[], references=[], provenance=[])), ("render-short-shas", note_dict(release_sha="abc", deployed_sha="abd", improved=["x"], added=[], fixed=[], proven=[], limitations=[], references=[])), ("render-short-equal-shas", note_dict(release_sha="abc", deployed_sha="abc")), ("render-unicode", note_dict(summary="Über — concluído ✓", improved=["café ☕ — faster"], added=[], fixed=[], proven=[], limitations=[], references=[])), ]: n = ReleaseNote(**dict(nd)) row(name, op="render", args=nd, want=render_markdown(n)) # ── extract_evidence_facts ─────────────────────────────────────────────────── doc1 = { "entities": [ contract("close the loop on recall gating " + "detail " * 20, {"correctly_predicted": ["e1", "e2"]}), entity("maintenance_item", {"status": "resolved", "issue": "drift on promote " + "more " * 20}), entity("maintenance_item", {"status": "open", "issue": "known flake in CI"}), entity("maintenance_item", {"status": "wontfix", "issue": "ignored"}), entity("tui_projection_decision", {"feature": "build panel", "projection": "live gates"}), contract("not closed yet", cid="contract-2"), entity("mystery_kind", {"x": "y"}), ] } row("extract-mixed", op="extract", args={"doc": doc1}) doc2 = { "entities": [ contract("dup need", {"correctly_predicted": []}, cid="c-a"), contract("dup need", cid="c-b"), {"id": "c-c", "kind": "construction_contract", "status": "closed", "identities": {"need": "dup need"}, "ext": {"release_evidence": {"final_deployed": SHA_A}}}, {"id": "c-d", "kind": "construction_contract", "status": "closed", "identities": {"need": "identity only"}, "ext": {"release_evidence": {"deployed_identity": "v9-build-77"}}}, {"id": "c-e", "kind": "construction_contract", "status": "closed", "identities": {"need": "both keys"}, "ext": {"release_evidence": {"final_deployed": SHA_B, "deployed_identity": "ignored-when-final-present"}}}, {"id": "c-f", "kind": "construction_contract", "status": "closed", "identities": {"need": "neither key"}, "ext": {"release_evidence": {"other": 1}}}, {"id": "c-g", "kind": "construction_contract", "status": "closed", "identities": {"need": "empty evidence dict"}, "ext": {"release_evidence": {}}}, ] } row("extract-release-evidence-variants", op="extract", args={"doc": doc2}) row("extract-empty", op="extract", args={"doc": {"entities": []}}) row("extract-no-entities-key", op="extract", args={"doc": {}}) row( "extract-unicode-truncation", op="extract", args={"doc": {"entities": [ contract("é" * 95), entity("maintenance_item", {"status": "resolved", "issue": "ü" * 95}), entity("tui_projection_decision", {"feature": "féature", "projection": "projęction — " + "z" * 200}), ]}}, ) for r in ROWS: if r["op"] == "extract": r["want"] = json.dumps(extract_evidence_facts(r["args"]["doc"])) # ── build_note ─────────────────────────────────────────────────────────────── bn_doc = { "entities": [ contract("recall gating closed"), entity("maintenance_item", {"status": "resolved", "issue": "drift bug"}), entity("maintenance_item", {"status": "open", "issue": "legacy parser"}), ] } row( "build-note-merge", op="build_note", args={"version": "0.150.0", "summary": "converged release", "scaffold_doc": bn_doc, "release_sha": SHA_A, "deployed_sha": SHA_B, "extra": {"improved": ["faster recall", "manual extra"], "fixed": ["manual fix"]}, "provenance": ["deploy workflow"], "backfill": False}, ) row( "build-note-exclude", op="build_note", args={"version": "0.151.0", "summary": "filtered", "scaffold_doc": bn_doc, "exclude_unsupported": ["RECALL", "manual"], "extra": {"improved": ["recall gating — RETRACTED", "safe item"]}}, ) row( "build-note-no-extras", op="build_note", args={"version": "0.152.0", "summary": "plain", "scaffold_doc": {"entities": []}}, ) for r in ROWS: if r["op"] == "build_note": a = r["args"] n = build_note( version=a["version"], summary=a["summary"], scaffold_doc=a["scaffold_doc"], release_sha=a.get("release_sha", ""), deployed_sha=a.get("deployed_sha", ""), extra=a.get("extra"), provenance=a.get("provenance"), backfill=a.get("backfill", False), exclude_unsupported=a.get("exclude_unsupported"), ) r["want"] = want_note(n) # ── summary_from_body ──────────────────────────────────────────────────────── for name, body in [ ("summary-rendered", render_markdown(ReleaseNote(**note_dict()))), ("summary-then-heading", "# Rune v1\n\nThe good stuff.\n\n## Improved\n- x"), ("summary-break-on-item", "# Rune v1\n\nFirst line.\nSecond line.\n- item breaks"), ("summary-break-on-italic", "# Rune v1\n\nWords here.\n_italic breaks_"), ("summary-blank-gap-stops", "# Rune v1\n\nPara one.\n\n## Improved\n- x"), ("summary-leading-blanks", "# Rune v1\n\n\n\nafter blanks"), ("summary-title-only", "# Rune v1\n"), ("summary-empty-body", ""), ("summary-crlf", "# Rune v1\r\n\r\nCRLF body line.\r\n"), ("summary-ws-strip", "# Rune v1\n\n padded line \n"), ("summary-vertical-tab", "# Rune v1\n\ntitle\x0bvertical sep"), ("summary-hash-inside", "# Rune v1\n\nnot heading # inside"), ]: row(name, op="summary", args={"body": body}, want=summary_from_body(body)) # ── sha_from_body ──────────────────────────────────────────────────────────── body_same = render_markdown(ReleaseNote(**note_dict())) body_diff = render_markdown(ReleaseNote(**note_dict(release_sha=SHA_A, deployed_sha=SHA_B))) for name, body, which in [ ("sha-tag", body_same, "tag"), ("sha-deployed-same", body_same, "deployed"), ("sha-tag-diff", body_diff, "tag"), ("sha-deployed-diff", body_diff, "deployed"), ("sha-missing", "no shas here", "tag"), ("sha-missing-deployed", "no shas here", "deployed"), ("sha-uppercase-hex", "_Tag commit: `ABCDEF1234567890ABCDEF1234567890ABCDEF12`_", "tag"), ("sha-lowercase-keyword", "_tag COMMIT: `" + SHA_A[:12] + "` (deployed: `" + SHA_A[:12] + "`)_", "tag"), ("sha-only-deployed-markup", "**final deployed: `" + SHA_B[:12] + "`**", "deployed"), ("sha-both-present-first-wins", "_Tag commit: `aaaaaaaaaaaa` · **final deployed: `bbbbbbbbbbbb`**", "tag"), ]: row(name, op="sha", args={"body": body, "which": which}, want=sha_from_body(body, which)) # ── _api / fetch_release / publish_release ─────────────────────────────────── server, repo, token = "https://gitea.example", "nick/rune", "tok-123" api_cases = [ ("api-get-json", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_ok(200, '{"id": 5, "html_url": "https://gitea.example/nick/rune/releases/tag/v1"}')]), ("api-get-empty-body", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_ok(204, "")]), ("api-post-payload", {"url": f"{server}/api/v1/x", "token": token, "method": "POST", "payload": [["tag_name", "v0.1.0"], ["name", "Rune — ünïcode ✓"], ["draft", False]]}, [resp_ok(201, '{"id": 9}')]), ("api-http-json-error", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_http(404, '{"message": "release not found"}')]), ("api-http-plain-text", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_http(404, "page not found")]), ("api-http-plain-long", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_http(500, "E" * 500)]), ("api-http-empty-body", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_http(502, "")]), ("api-os-error", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_os("dial tcp: connection refused")]), ("api-os-error-long", {"url": f"{server}/api/v1/x", "token": token, "method": "GET", "payload": None}, [resp_os("O" * 300)]), ] for name, args, script in api_cases: out = run_api(script, lambda a=args: _api(a["url"], a["token"], a["method"], dict(a["payload"]) if a["payload"] else None)) row(name, op="api", args=args, script=script, result=json.dumps(out["result"]), calls=out["calls"]) fr_cases = [ ("fetch-200", [resp_ok(200, '{"id": 5, "tag_name": "v0.142.0", "body": "stored"}')]), ("fetch-404-plain", [resp_http(404, "page not found")]), ("fetch-500", [resp_http(500, '{"message": "boom"}')]), ] for name, script in fr_cases: out = run_api(script, lambda: fetch_release(server, repo, token, "v0.142.0")) row(name, op="fetch_release", args={"server": server, "repo": repo, "token": token, "tag": "v0.142.0"}, script=script, result=json.dumps(out["result"]), calls=out["calls"]) pub_note = ReleaseNote(**note_dict(version="0.160.0")) pub_cases = [ ("publish-created", [resp_http(404, "page not found"), resp_ok(201, '{"id": 7, "html_url": "https://gitea.example/nick/rune/releases/tag/v0.160.0"}')], {}), ("publish-created-failure-json", [resp_http(404, "page not found"), resp_http(422, '{"message": "tag already exists"}')], {}), ("publish-created-failure-plain", [resp_http(404, "page not found"), resp_http(422, "validation failed, no json")], {}), ("publish-updated", [resp_ok(200, '{"id": 3}'), resp_ok(200, '{"id": 3, "html_url": "https://gitea.example/nick/rune/releases/tag/v0.160.0"}')], {}), ("publish-update-failure-plain", [resp_ok(200, '{"id": 3}'), resp_http(500, "internal server error — " + "x" * 250)], {}), ("publish-target-commitish", [resp_http(404, "page not found"), resp_ok(201, '{"id": 11}')], {"target_commitish": "main"}), ("publish-update-no-html-url", [resp_ok(200, '{"id": 4}'), resp_ok(200, '{"id": 4}')], {}), ("publish-fetch-os-error-creates", [resp_os("getaddrinfo failed"), resp_ok(201, '{"id": 12}')], {}), ] for name, script, kw in pub_cases: out = run_api(script, lambda kw=kw: publish_release(server, repo, token, pub_note, **kw)) row(name, op="publish_release", args={"server": server, "repo": repo, "token": token, "note": pub_note.to_dict(), "target_commitish": kw.get("target_commitish", "master")}, script=script, result=json.dumps(out["result"]), calls=out["calls"]) # ── converged_deployed_sha (real filesystem via temp roots) ───────────────── with tempfile.TemporaryDirectory() as td: root = Path(td) # case 1: healthy link dep1 = root / "dep1" tgt1 = root / "rel1" (tgt1 / "src" / "rune").mkdir(parents=True) (tgt1 / "src" / "rune" / "_build_identity.json").write_text(json.dumps({"commit": SHA_A, "version": "0.1.0"})) dep1.mkdir() (dep1 / "current").symlink_to(tgt1) row("converged-ok", op="converged", deploy_root="dep1", setup={"files": {"rel1/src/rune/_build_identity.json": json.dumps({"commit": SHA_A, "version": "0.1.0"})}, "links": {"dep1/current": "{ROOT}/rel1"}}, want=converged_deployed_sha(str(dep1))) # case 2: missing current symlink dep2 = root / "dep2" dep2.mkdir() row("converged-no-link", op="converged", deploy_root="dep2", setup={"files": {}, "links": {}}, want=converged_deployed_sha(str(dep2))) # case 3: identity file missing dep3 = root / "dep3" tgt3 = root / "rel3" (tgt3 / "src" / "rune").mkdir(parents=True) dep3.mkdir() (dep3 / "current").symlink_to(tgt3) row("converged-no-identity", op="converged", deploy_root="dep3", setup={"files": {}, "links": {"dep3/current": "{ROOT}/rel3"}}, want=converged_deployed_sha(str(dep3))) # case 4: bad JSON identity dep4 = root / "dep4" tgt4 = root / "rel4" (tgt4 / "src" / "rune").mkdir(parents=True) (tgt4 / "src" / "rune" / "_build_identity.json").write_text("{not json") dep4.mkdir() (dep4 / "current").symlink_to(tgt4) row("converged-bad-json", op="converged", deploy_root="dep4", setup={"files": {"rel4/src/rune/_build_identity.json": "{not json"}, "links": {"dep4/current": "{ROOT}/rel4"}}, want=converged_deployed_sha(str(dep4))) # case 5: commit key missing → "" dep5 = root / "dep5" tgt5 = root / "rel5" (tgt5 / "src" / "rune").mkdir(parents=True) (tgt5 / "src" / "rune" / "_build_identity.json").write_text(json.dumps({"version": "0.2.0"})) dep5.mkdir() (dep5 / "current").symlink_to(tgt5) row("converged-no-commit-key", op="converged", deploy_root="dep5", setup={"files": {"rel5/src/rune/_build_identity.json": json.dumps({"version": "0.2.0"})}, "links": {"dep5/current": "{ROOT}/rel5"}}, want=converged_deployed_sha(str(dep5))) # case 6: non-string commit → str() dep6 = root / "dep6" tgt6 = root / "rel6" (tgt6 / "src" / "rune").mkdir(parents=True) (tgt6 / "src" / "rune" / "_build_identity.json").write_text(json.dumps({"commit": 424242})) dep6.mkdir() (dep6 / "current").symlink_to(tgt6) row("converged-int-commit", op="converged", deploy_root="dep6", setup={"files": {"rel6/src/rune/_build_identity.json": json.dumps({"commit": 424242})}, "links": {"dep6/current": "{ROOT}/rel6"}}, want=converged_deployed_sha(str(dep6))) # ── auto_initial_note ──────────────────────────────────────────────────────── store_doc = json.dumps({"entities": [ contract("contracts closed at init", {"correctly_predicted": ["e1"]}, cid="c-init"), entity("maintenance_item", {"status": "resolved", "issue": "init bug"}), entity("maintenance_item", {"status": "open", "issue": "open item"}), ]}) row( "auto-init-store", op="auto_initial", args={"version": "0.170.0", "summary": "init summary", "store": store_doc, "release_sha": SHA_A, "deployed_sha": SHA_B, "extra": {"improved": ["init improvement"]}}, ) row( "auto-init-store-missing", op="auto_initial", args={"version": "0.170.1", "summary": "no store", "store": None, "release_sha": SHA_A}, ) row( "auto-init-store-bad-json", op="auto_initial", args={"version": "0.170.2", "summary": "bad store", "store": "{oops", "release_sha": SHA_A}, ) row( "auto-init-custom-provenance", op="auto_initial", args={"version": "0.170.3", "summary": "prov", "store": store_doc, "release_sha": SHA_A, "provenance": ["explicit provenance"], "extra": {"added": ["new thing"], "fixed": ["old thing"]}}, ) for r in ROWS: if r["op"] == "auto_initial": a = r["args"] tmp = None path = "missing-store.json" if a["store"] is not None: tmp = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) tmp.write(a["store"]) tmp.close() path = tmp.name try: n = auto_initial_note( version=a["version"], summary=a["summary"], scaffold_store=path, release_sha=a["release_sha"], deployed_sha=a.get("deployed_sha", ""), provenance=a.get("provenance"), extra=a.get("extra"), ) finally: if tmp is not None: os.unlink(path) r["want"] = want_note(n) # ── convergence_refresh ────────────────────────────────────────────────────── refresh_store = json.dumps({"entities": [ entity("tui_projection_decision", {"feature": "gates", "projection": "panel"}), contract("newly closed after tag", cid="c-late"), ]}) base = ReleaseNote(**note_dict(version="0.180.0", release_sha=SHA_A, deployed_sha=SHA_A, provenance=["init prov"], backfill=True)) row( "refresh-merge", op="refresh", args={"note": base.to_dict(), "deployed_sha": SHA_B, "store": refresh_store, "extra_provenance": "convergence pass"}, ) row( "refresh-dup-provenance", op="refresh", args={"note": base.to_dict(), "deployed_sha": SHA_B, "store": None, "extra_provenance": "init prov"}, ) row( "refresh-bad-store", op="refresh", args={"note": base.to_dict(), "deployed_sha": SHA_B, "store": "{{{", "extra_provenance": ""}, ) for r in ROWS: if r["op"] == "refresh": a = r["args"] tmp = None path = "missing-store.json" if a["store"] is not None: tmp = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) tmp.write(a["store"]) tmp.close() path = tmp.name try: n = convergence_refresh(ReleaseNote.from_dict(a["note"]), deployed_sha=a["deployed_sha"], scaffold_store=path, extra_provenance=a["extra_provenance"]) finally: if tmp is not None: os.unlink(path) r["want"] = want_note(n) # ── build_from_deployment ──────────────────────────────────────────────────── row( "bfd-default-summary", op="bfd", args={"version": "0.190.0", "deployed_sha": SHA_B, "store": store_doc, "release_sha": SHA_A}, ) row( "bfd-explicit-summary", op="bfd", args={"version": "0.190.1", "deployed_sha": SHA_B, "store": None, "release_sha": "", "summary": "custom summary"}, ) for r in ROWS: if r["op"] == "bfd": a = r["args"] tmp = None path = "missing-store.json" if a["store"] is not None: tmp = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) tmp.write(a["store"]) tmp.close() path = tmp.name try: n = build_from_deployment(version=a["version"], deployed_sha=a["deployed_sha"], scaffold_store=path, release_sha=a.get("release_sha", ""), summary=a.get("summary", "")) finally: if tmp is not None: os.unlink(path) r["want"] = want_note(n) def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--out", default=str(ROOT / "src" / "go" / "release-notes" / "truth.json")) a = ap.parse_args() Path(a.out).write_text(json.dumps(ROWS, indent=1) + "\n", encoding="utf-8") ops: dict[str, int] = {} for r in ROWS: ops[r["op"]] = ops.get(r["op"], 0) + 1 print(f"frozen {len(ROWS)} rows → {a.out}") for k in sorted(ops): print(f" {k}: {ops[k]}") if __name__ == "__main__": main()