Skip to content

ci: guard release provenance + manifest; drop rolling 'latest' tag - #7

Merged
JPHutchins merged 1 commit into
mainfrom
ci/release-provenance-guard
Jun 3, 2026
Merged

ci: guard release provenance + manifest; drop rolling 'latest' tag#7
JPHutchins merged 1 commit into
mainfrom
ci/release-provenance-guard

Conversation

@JPHutchins

Copy link
Copy Markdown
Collaborator

Closes #4.

Problem

A separate release on a tag literally named latest had diverged from GitHub's own "Latest release" pointer: it accumulated assets from four build SHAs (9ccdd4d2, a2497820, cbc3cd94, f5968188) and shipped a stale, mps2-less manifest.json. So gh release download latest silently got stale, multi-SHA, manifest-less assets, while releases/latest/download/... got the current build.

Fix

  • .github/scripts/validate_release.py (typed, doctested, mypy --strict, unit-tested): before publishing, assert every shipped asset carries this build's SHA, every primary image has a manifest entry, every entry's artifact and every run/qemu_cmd-referenced file exists, every .signed.bin belongs to a fixture, and there are no stray or duplicate artifacts.
  • .github/workflows/build.yaml: runs the guard in the release job before the gh-release upload (a bad release never publishes), and lints/type-checks/tests it in the check-script gate.
  • Retired the rolling latest tag/release (deleted). The only "latest" is now GitHub's newest-release pointer + the self-consistent per-SHA releases consumers can pin.
  • README.md: documents release self-containment + per-SHA pinning.

Verification

  • Run against real releases: PASSES the good 2ad0d6bf release (0 problems); REJECTS the stale latest (54 problems — 27 foreign-SHA + orphan images/payloads).
  • End-to-end CLI exits 0 on a sound release, 1 on an injected foreign-SHA asset.
  • Script suite green: ruff, ruff format --check, mypy --strict, pytest (72, incl. doctests).

A separate release on a tag literally named `latest` had diverged from
GitHub's own "Latest release" pointer: it accumulated assets from four
build SHAs and shipped a stale, mps2-less manifest.json. So
`gh release download latest` (and anything resolving the tag) silently got
stale, multi-SHA, manifest-less assets, while `releases/latest/download/...`
got the current build.

Add validate_release.py (typed, doctested, mypy --strict, unit-tested):
before publishing, assert every shipped asset carries this build's SHA,
every primary image has a manifest entry, every entry's artifact and
launch-command file exists, every .signed.bin belongs to a fixture, and
there are no stray or duplicate artifacts. Wire it into the release job
(it runs before the gh-release upload, so a bad release never publishes)
and into the check-script gate (ruff + mypy --strict + pytest).

The rolling `latest` tag/release is retired (deleted); the only "latest"
is now GitHub's newest-release pointer plus the self-consistent per-SHA
releases consumers can pin.

Closes #4

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 3, 2026 22:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the release pipeline by adding a CI guard that validates release self-consistency (provenance-by-SHA and manifest↔assets agreement) and by removing reliance on a rolling tag literally named latest, aligning behavior with GitHub’s “Latest release” pointer.

Changes:

  • Add a typed, doctested, unit-tested release validator script that enforces provenance + manifest invariants before publishing.
  • Run the validator in the release job prior to uploading assets; extend the existing script quality gate (ruff/mypy/pytest) to include it.
  • Update documentation to explain self-contained releases and per-SHA pinning (no rolling latest tag).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Documents release self-containment, GitHub “latest” pointer usage, and per-SHA pinning.
.github/workflows/build.yaml Adds validator checks in CI gate and runs validation before publishing a release.
.github/scripts/validate_release.py New release validation CLI + core checks for SHA provenance and manifest↔asset consistency.
.github/scripts/test_validate_release.py New unit + end-to-end tests defining validator behavior.
.github/scripts/pyproject.toml Extends ruff/isort + mypy file targets to include the new validator and tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +269 to +291
def main(argv: Sequence[str] | None = None) -> int:
args = parse_args(argv)
entries = parse_entries(json.loads(args.manifest.read_text()))
asset_names = list_assets(args.assets_dir)
problems = validate(asset_names, entries, args.git_sha)
if problems:
print(f"Release validation FAILED ({len(problems)} problem(s)):", file=sys.stderr)
for problem in problems:
print(f" - {problem}", file=sys.stderr)
return 1
print(
f"Release validation OK: {len(entries)} manifest entries, "
f"{sum(is_asset(name) for name in asset_names)} assets, all SHA {short_sha(args.git_sha)}."
)
return 0


if __name__ == "__main__":
try:
sys.exit(main())
except ValidationError as err:
print(f"error: {err}", file=sys.stderr)
sys.exit(1)
@JPHutchins
JPHutchins merged commit 0eae053 into main Jun 3, 2026
5 checks passed
@JPHutchins
JPHutchins deleted the ci/release-provenance-guard branch June 3, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rolling latest tag diverges from the GitHub latest release (stale manifest + multi-SHA assets)

2 participants