Files
rune/scripts/gen_release_notes_truth.py
T
Nick 38497a61d0 feat(release_notes): port to FULL Go — dichotomy wave
Full name-preserving port of src/rune/release_notes.py to
src/go/release-notes (GO verdict, #2479 dichotomy tranche — 16/16 fns,
dichotomy audit now FULL):
- ReleaseNote record: __init__ sort-never-dedup semantics, to_dict
  insertion-order, from_dict; unicode codepoint sorts byte-parity
- render_markdown: short-SHA [:12] clamping (rune-safe pySlice), empty
  section omission, backfill/provenance footers, unicode rstrip
- extract_evidence_facts: closed-contract references + correctly_predicted
  proven, maintenance resolved/open, TUI projection decisions,
  release_evidence final_deployed/deployed_identity variants, sorted-set
  dedup; nested _clean echo for the §24F unsupported-claim filter
- build_note: extras merged per key through the SAME exclude filter
- summary_from_body: Python splitlines() full separator set (\v \f
  \x1c-\x1e \x85 \u2028 \u2029), H1-skip, heading/italic/item breaks,
  blank-gap stop, em-dash fallback
- sha_from_body: (?i) tag/deployed markup regexes
- Gitea surface: _api (HTTPError-before-OSError order, JSON/"_raw"/"_error"
  degrades with [:200] truncation), fetch_release, publish_release
  (create/update/idempotent, ordered POST payload, failed-dict shape) —
  HTTP behind the injectable Transport seam with request capture
- converged_deployed_sha + os_readlink behind the FileSystem seam
  (RUNE_DEPLOY_ROOT default preserved)
- auto_initial_note / convergence_refresh / build_from_deployment:
  store-missing/corrupt degrades, provenance defaults, tag-SHA-never-
  changes convergence truth
- truth.json: 78 frozen rows from the live oracle (scripts/
  gen_release_notes_truth.py) covering EVERY public function and branch —
  incl. scripted-transport request capture (method/url/headers/body
  byte-parity), 200+ char truncation, non-ascii escapes; replay 78/78
  exact-equal, live-regenerated and re-verified; go test green; gofmt+vet
  clean
- CI: release-notes gate upgraded to the HARD FULL-port parity gate
  (replay + TestTruthParity)
- release_notes.py untouched (Python oracle remains the parity authority)

Contract: BUILDER-2026-10-11T15:00:35Z-e67406-factory-wave (draft; phase
walk + closure with both SHAs in the Phase B commit). Dichotomy audit:
release_notes 16/16 → FULL.
2026-10-11 17:09:07 +01:00

599 lines
25 KiB
Python

#!/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()