Skip to content

Commit ad6a7d5

Browse files
committed
ci(release): verify published artifacts end to end
1 parent cbbc710 commit ad6a7d5

8 files changed

Lines changed: 106 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
run: |
4040
python scripts/check_rating_integrity.py
4141
python scripts/check_migration_coverage.py
42+
python scripts/check_release_metadata.py
4243
pip-audit -r requirements.txt
4344
for file in scripts/*.sh scripts/lib/*.sh; do bash -n "$file"; done
4445
shellcheck scripts/*.sh scripts/lib/*.sh
@@ -97,7 +98,9 @@ jobs:
9798
run: |
9899
python -m venv /tmp/rewardharness-wheel
99100
/tmp/rewardharness-wheel/bin/pip install dist/*.whl
101+
/tmp/rewardharness-wheel/bin/rewardharness --version
100102
/tmp/rewardharness-wheel/bin/rewardharness inspect
103+
/tmp/rewardharness-wheel/bin/rewardharness release-status
101104
102105
- name: Upload distributions
103106
uses: actions/upload-artifact@v4

.github/workflows/release.yml

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ on:
77
permissions:
88
contents: read
99

10+
concurrency:
11+
group: release-${{ github.ref }}
12+
cancel-in-progress: false
13+
1014
jobs:
1115
build:
1216
runs-on: ubuntu-latest
@@ -17,10 +21,7 @@ jobs:
1721
python-version: "3.10"
1822
- run: python -m pip install -r requirements-dev.txt
1923
- name: Verify tag and package version
20-
run: |
21-
package_version=$(python -c 'import rewardharness; print(rewardharness.__version__)')
22-
tag_version=${GITHUB_REF_NAME#v}
23-
test "${tag_version//-/}" = "$package_version"
24+
run: python -m rewardharness.release --check-tag "$GITHUB_REF_NAME"
2425
- run: make release-check
2526
- uses: actions/upload-artifact@v4
2627
with:
@@ -31,7 +32,9 @@ jobs:
3132
pypi:
3233
needs: build
3334
runs-on: ubuntu-latest
34-
environment: pypi
35+
environment:
36+
name: pypi
37+
url: https://pypi.org/project/rewardharness/
3538
permissions:
3639
id-token: write
3740
steps:
@@ -44,7 +47,7 @@ jobs:
4447
attestations: true
4548

4649
github:
47-
needs: build
50+
needs: [build, pypi]
4851
runs-on: ubuntu-latest
4952
permissions:
5053
contents: write
@@ -60,3 +63,36 @@ jobs:
6063
prerelease=""
6164
[[ "$GITHUB_REF_NAME" == *-* ]] && prerelease="--prerelease"
6265
gh release create "$GITHUB_REF_NAME" dist/* --generate-notes $prerelease
66+
67+
verify:
68+
needs: [pypi, github]
69+
runs-on: ubuntu-latest
70+
permissions:
71+
contents: read
72+
steps:
73+
- uses: actions/setup-python@v6
74+
with:
75+
python-version: "3.10"
76+
- name: Wait for PyPI metadata
77+
run: |
78+
package_version=${GITHUB_REF_NAME#v}
79+
package_version=${package_version//-/}
80+
for attempt in {1..18}; do
81+
curl --fail --silent --output /dev/null \
82+
"https://pypi.org/pypi/rewardharness/$package_version/json" && exit 0
83+
echo "PyPI metadata not visible yet (attempt $attempt/18)"
84+
sleep 10
85+
done
86+
exit 1
87+
- name: Install and smoke-test the published artifact
88+
run: |
89+
package_version=${GITHUB_REF_NAME#v}
90+
package_version=${package_version//-/}
91+
python -m venv /tmp/rewardharness-pypi
92+
/tmp/rewardharness-pypi/bin/pip install --no-deps "rewardharness==$package_version"
93+
/tmp/rewardharness-pypi/bin/rewardharness --version
94+
/tmp/rewardharness-pypi/bin/rewardharness release-status
95+
- name: Verify GitHub release
96+
env:
97+
GH_TOKEN: ${{ github.token }}
98+
run: gh release view "$GITHUB_REF_NAME" --json tagName,isPrerelease,url

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ Large-scale architecture and release-hardening candidate. The supported API
1212
now uses the `rewardharness` namespace, Library assets use schema v2, and the
1313
deprecated `src` namespace remains as a compatibility layer for v0.2.
1414

15+
- Package metadata imports are now lazy, so version and release checks do not
16+
initialize model, dataset, or image-processing dependencies.
17+
- Added canonical version/tag conversion, `rewardharness release-status`,
18+
cross-file release metadata validation, and post-publish PyPI/GitHub smoke
19+
verification.
20+
1521
### Added
1622

1723
- Active GitHub Actions CI now runs the test suite on Python 3.10–3.12,

CONTRIBUTING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,7 @@ to `CHANGELOG.md`.
5858

5959
Release candidates use PEP 440 versions such as `0.2.0rc1` and matching tags
6060
such as `v0.2.0-rc1`. The tag workflow publishes the same validated artifacts
61-
to PyPI and GitHub through trusted publishing.
61+
to PyPI and GitHub through trusted publishing. Run
62+
`rewardharness release-status` and `python scripts/check_release_metadata.py`
63+
before tagging; the workflow also installs the published PyPI artifact and
64+
verifies the resulting GitHub Release before reporting success.

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ quality:
4242
mypy rewardharness
4343
python scripts/check_rating_integrity.py
4444
python scripts/check_migration_coverage.py
45+
python scripts/check_release_metadata.py
4546
pip-audit -r requirements.txt
4647
@for file in scripts/*.sh scripts/lib/*.sh; do bash -n "$$file"; done
4748
@if command -v shellcheck >/dev/null 2>&1; then shellcheck scripts/*.sh scripts/lib/*.sh; \

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ The installation exposes one supported command surface:
144144
```bash
145145
rewardharness inspect
146146
rewardharness check
147+
rewardharness release-status
147148
rewardharness score-pair --help
148149
rewardharness evolve --help
149150
rewardharness benchmark --help

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pytest-cov>=5.0
99
mypy>=1.11,<2.0
1010
ruff>=0.9,<1.0
1111
types-pyyaml>=6.0
12+
tomli>=2.0; python_version < "3.11"
1213
pip-audit>=2.7,<3.0
1314
pre-commit>=4.0,<5.0
1415

scripts/check_release_metadata.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python3
2+
"""Verify that release metadata agrees with the canonical package version."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
from pathlib import Path
8+
9+
import yaml
10+
11+
try:
12+
import tomllib
13+
except ImportError: # pragma: no cover - exercised by the Python 3.10 CI job
14+
import tomli as tomllib
15+
16+
ROOT = Path(__file__).resolve().parent.parent
17+
18+
19+
def main() -> int:
20+
sys.path.insert(0, str(ROOT))
21+
from rewardharness.release import ReleaseIdentity
22+
23+
identity = ReleaseIdentity.current()
24+
pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
25+
citation = yaml.safe_load((ROOT / "CITATION.cff").read_text(encoding="utf-8"))
26+
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
27+
28+
if pyproject["project"]["name"] != "rewardharness":
29+
raise SystemExit("pyproject project name must be rewardharness")
30+
version_source = pyproject["tool"]["setuptools"]["dynamic"]["version"]["attr"]
31+
if version_source != "rewardharness._version.__version__":
32+
raise SystemExit("pyproject must read the canonical _version module")
33+
citation_version = str(citation["version"])
34+
if citation_version != identity.tag.removeprefix("v"):
35+
raise SystemExit(
36+
f"CITATION.cff version {citation_version!r} does not match {identity.tag!r}"
37+
)
38+
if f"## [{citation_version}]" not in changelog:
39+
raise SystemExit(f"CHANGELOG.md has no release heading for {citation_version}")
40+
print(
41+
f"release metadata: package={identity.package_version} "
42+
f"tag={identity.tag} citation={citation_version}"
43+
)
44+
return 0
45+
46+
47+
if __name__ == "__main__":
48+
raise SystemExit(main())

0 commit comments

Comments
 (0)