Files
hermes-agent/tests/scripts/test_render_builds_table_commit.py
ethernet 1c4093fdc9 feat(release): stage commit builds with verified receipts
Commit builds use their own immutable namespace and the shared signed
transport. Publish each receipt after its files, and fetch shared files
once only when their receipt records agree.

Summary links retain the full object key. A completed row requires its
own validated receipt and listed object. Missing, corrupt and ambiguous
results remain distinct. Include both universal Windows bundles.

Verified: 85 tests passed across transfer, rendering, candidate and
promotion paths. The subprocess test fetches the rendered download URL
from loopback HTTP. Ruff and added-comment checks passed.

No live R2 writes, workflow dispatch or native package build ran.
The commit-build CLI and workflow changes remain separate drafts.
2026-09-09 21:29:34 -04:00

224 lines
9.7 KiB
Python

"""Commit-mode summary renderer: the receipt-bound expected-binary matrix.
The commit summary is a SEPARATE sink from the release-body tables: it
never reads or edits a GitHub release, and every expected binary gets a
row — Built (receipt + object both present, with a link) or Not built —
so a skipped or interrupted leg can never be a silent omission or a
forged success.
"""
from __future__ import annotations
import importlib.util
import json
from pathlib import Path
import pytest
_SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "render-builds-table.py"
_SPEC = importlib.util.spec_from_file_location("render_builds_table", _SCRIPT)
assert _SPEC and _SPEC.loader
rbt = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(rbt)
COMMIT = "a" * 40
BASE = "https://cdn.example.com"
def _leg_for(basename: str) -> str:
"""The receipt name whose regex matches this artifact basename."""
import re
for _label, leg, pattern in rbt._COMMIT_EXPECTED:
if re.match(pattern, basename):
return leg
raise AssertionError(f"no expected row matches {basename!r}")
# Every produced binary of a fully green commit run (Termux's .deb is a
# nested deb/ path — the workflow stages it under deb/pool/).
_ALL_BASENAMES = [
"HermesBundled-0.28.0-win-x64.msix",
"HermesBundled-0.28.0-win-arm64.msix",
"Store-HermesBundled-0.28.0-win-x64.msix",
"Store-HermesBundled-0.28.0-win-arm64.msix",
"HermesBundled-0.28.0-win.msixbundle",
"Store-HermesBundled-0.28.0.0-win.msixbundle",
"HermesBundled-0.28.0-mac-arm64.dmg",
"HermesBundled-0.28.0-mac-x64.dmg",
"HermesBundled-0.28.0-mac-arm64.zip",
"HermesBundled-0.28.0-mac-x64.zip",
"deb/pool/hermes-agent_0.28.0_aarch64.deb",
]
def _receipts_all_built() -> dict[str, dict | None]:
"""Validated receipts: each leg lists exactly the artifact basenames
the matrix expects from it (the real staged shape)."""
receipts: dict[str, dict | None] = {name: None for name in rbt.COMMIT_RECEIPT_NAMES}
for basename in _ALL_BASENAMES:
leg = _leg_for(basename.rsplit("/", 1)[-1])
receipt = receipts[leg] or {"schema": 2, "commit": COMMIT, "name": leg, "files": []}
receipt["files"].append({"path": basename, "size": 1, "sha256": "0" * 64})
receipts[leg] = receipt
return receipts
def _names(*basenames: str) -> list[str]:
return [f"releases/commit/{COMMIT}/{name}" for name in basenames]
def _all_built_names() -> list[str]:
return _names(*_ALL_BASENAMES)
def test_every_expected_binary_gets_a_row_built_or_not():
receipts = _receipts_all_built()
rows = rbt.commit_expected_rows(_all_built_names(), receipts)
assert len(rows) == len(rbt._COMMIT_EXPECTED)
for row in rows:
assert row["state"] == "built", row
summary = rbt.render_commit_summary(_all_built_names(), BASE, COMMIT, receipts)
assert summary.count("✅ Built") == len(rbt._COMMIT_EXPECTED)
# zip/Termux/Store rows exist in the COMMIT summary (the release-body
# table hides zips on purpose; the commit summary shows every binary).
assert any("ZIP" in row["label"] for row in rows)
assert any("Termux" in row["label"] for row in rows)
assert any("Store MSIX" in row["label"] for row in rows)
assert any("MSIXBUNDLE" in row["label"] for row in rows)
# The universal Store MSIXBUNDLE is commit-native now and links correctly.
bundle = next(row for row in rows if "MSIXBUNDLE" in row["label"])
assert bundle["key"].endswith(".msixbundle")
def test_missing_binaries_are_explicit_rows_never_links():
receipts = _receipts_all_built()
names = _names("HermesBundled-0.28.0-win-x64.msix")
summary = rbt.render_commit_summary(names, BASE, COMMIT, receipts)
built = sum(1 for line in summary.splitlines() if "✅ Built" in line)
missing = sum(1 for line in summary.splitlines()
if "❌ Not built" in line and "release leg disabled" not in line)
assert built == 1
assert missing == len(rbt._COMMIT_EXPECTED) - built
# No link for an object that does not exist.
assert "win-arm64.msix)" not in summary
# Failed legs are blamed by name on the RECEIPT-missing rows; without
# blame the row says incomplete.
darwin_down = dict(receipts)
darwin_down["darwin-arm64"] = None
blamed = rbt.render_commit_summary(names, BASE, COMMIT, darwin_down, failed_legs=["build-darwin"])
assert "failed: build-darwin" in blamed
assert "Not built (leg incomplete or upload interrupted)" in rbt.render_commit_summary(names, BASE, COMMIT, darwin_down)
def test_object_without_completion_receipt_never_renders_built():
"""The old draft trusted raw object names: an interrupted upload that
left artifacts but no receipt looked ✅ Built. Now the receipt is the
completion marker."""
receipts = _receipts_all_built()
names = _all_built_names() # every object present...
for leg in ("win32-x64", "termux"):
receipts[leg] = None # ...two legs' receipts absent
summary = rbt.render_commit_summary(names, BASE, COMMIT, receipts)
for row in rbt.commit_expected_rows(names, receipts):
if row["leg"] in ("win32-x64", "termux"):
assert row["state"] != "built"
assert f"| {row['label']} | ✅ Built |" not in summary
else:
assert row["state"] == "built"
def test_orphan_object_with_unrelated_valid_receipt_never_renders_built():
"""An object matching a row's shape, staged under a leg whose VALID
receipt covers only unrelated files, is NOT evidence that the row's
binary was built. The old draft accepted ANY receipt object for the
leg; rows now bind to the receipt's own file paths."""
receipts = _receipts_all_built()
# win32-x64's receipt is valid but lists a DIFFERENT file.
receipts["win32-x64"] = {"schema": 2, "commit": COMMIT, "name": "win32-x64",
"files": [{"path": "metadata-windows-x64.json",
"size": 1, "sha256": "0" * 64}]}
names = _all_built_names() # the orphan win-x64 msix object exists
rows = rbt.commit_expected_rows(names, receipts)
x64 = next(row for row in rows if row["label"] == "Windows x64 (MSIX)")
assert x64["state"] != "built"
assert x64["key"] is None
summary = rbt.render_commit_summary(names, BASE, COMMIT, receipts)
assert "| Windows x64 (MSIX) | ✅ Built |" not in summary
def test_receipt_without_object_is_reported_not_built():
receipts = _receipts_all_built()
summary = rbt.render_commit_summary([], BASE, COMMIT, receipts)
assert "Not built (receipt present but object missing)" in summary
assert "✅ Built" not in summary
def test_ambiguous_or_extra_objects_never_render_a_link():
"""Two objects that BOTH match a row's shape AND are listed by the
leg's receipt are genuinely ambiguous — no link is rendered. An extra
object the receipt does NOT list cannot poison a row whose own
receipt-listed object is unique (it is foreign to the binding)."""
receipts = _receipts_all_built()
# List both versions in win32-x64's receipt (a torn staging would
# leave exactly this shape behind two shape-matching objects).
receipts["win32-x64"]["files"].append(
{"path": "HermesBundled-0.28.1-win-x64.msix", "size": 1, "sha256": "0" * 64})
names = _names(
"HermesBundled-0.28.0-win-x64.msix",
"HermesBundled-0.28.1-win-x64.msix",
)
rows = rbt.commit_expected_rows(names, receipts)
x64 = next(row for row in rows if row["label"] == "Windows x64 (MSIX)")
assert x64["state"] == "ambiguous" and x64["key"] is None
# The extra 0.28.1 object without a receipt listing is NOT ambiguous:
# the receipt's own 0.28.0 binding stays built.
plain = _receipts_all_built()
rows = rbt.commit_expected_rows(
_names("HermesBundled-0.28.0-win-x64.msix",
"HermesBundled-0.28.1-win-x64.msix"), plain)
x64 = next(row for row in rows if row["label"] == "Windows x64 (MSIX)")
assert x64["state"] == "built"
assert x64["key"].endswith("HermesBundled-0.28.0-win-x64.msix")
def test_failed_legs_from_release_needs_seam():
needs = json.dumps({
"build-win32": {"result": "success"},
"build-darwin": {"result": "failure"},
"termux-deb": {"result": "skipped"},
"build-linux": {"result": "failure"},
})
assert rbt.failed_legs_from_release_needs(needs) == ["build-darwin", "build-linux"]
assert rbt.failed_legs_from_release_needs(None) == []
assert rbt.failed_legs_from_release_needs("") == []
assert rbt.failed_legs_from_release_needs("not json") == []
def test_summary_uses_exact_nested_keys_and_lists_both_universal_bundles():
from scripts.releases import handoff
paths = {
"windows-universal": [
"HermesBundled-0.28.0.0-win.msixbundle",
"Store-HermesBundled-0.28.0.0-win.msixbundle",
],
"termux": ["deb/pool build/hermes-agent_0.28.0_aarch64.deb"],
}
receipts = {
leg: {"schema": 2, "commit": COMMIT, "name": leg,
"files": [{"path": path, "size": 1, "sha256": "0" * 64} for path in files]}
for leg, files in paths.items()
}
for leg, receipt in receipts.items():
handoff.validate_commit_receipt(receipt, COMMIT, leg)
names = _names(*(path for files in paths.values() for path in files))
summary = rbt.render_commit_summary(names, BASE + "/downloads", COMMIT, receipts)
from urllib.parse import quote
for key in names:
assert f"]({BASE}/downloads/{quote(key, safe='/')})" in summary
built = [line for line in summary.splitlines() if "✅ Built" in line]
assert len(built) == len(names)
assert any("Store" in line and "MSIXBUNDLE" in line for line in built)
assert "Linux x64" in summary and "Linux ARM64" in summary