Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 60 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,20 @@ jobs:
with:
python-version: "3.14"

- name: Rewrite README repo slug for the public PyPI long_description
run: |
# PyPI renders README.md (pyproject `readme = "README.md"`) as the project description and
# fetches it anonymously. The source README points at the PRIVATE source repo
# (MEFORORG/MessageFoundry) so the team's GitHub view shows the private repo's own CI
# badges — but anonymous fetches of a private repo's Actions badge SVG 404 (broken badge
# images), and every body link 404s on click. The public mirror (MEFORORG/MessageFoundry)
# runs the same workflows and is anonymously reachable, so rewrite the slug here (the same
# rewrite the retired scripts/publish/publish.ps1 used to apply) before `python -m build` embeds
# the README. Ephemeral runner edit only — never committed.
sed -i 's#MEFORORG/MessageFoundry#MEFORORG/MessageFoundry#g' README.md
if grep -q 'MEFORORG/MessageFoundry' README.md; then
echo '::error::README slug rewrite left private-repo links'; exit 1
fi
# REMOVED at the MEFORORG cutover: "Rewrite README repo slug for the public PyPI
# long_description". It rewrote the private source slug to the public mirror's before `build`
# embedded README.md, because PyPI fetches the long_description anonymously and a private repo's
# badge SVGs and body links 404 for anonymous readers.
#
# There is one repo now, and README.md already names it — so there is nothing to rewrite. Worse,
# the step could no longer even fail safely: publish.ps1's slug rewrite had been applied to this
# workflow itself, collapsing BOTH sides of the substitution to the same string. The sed became a
# no-op that replaced the public slug with itself, and the guard immediately after it failed if
# that slug appeared in the README — which it always does, 19 times. So the step failed on EVERY
# tag push. That is exactly what happened: the v0.3.0 tag (2026-07-13) died here, and the repo has
# zero published releases as a result. Same root cause as the release job's inverted `if:` guard —
# mirror-era logic that the slug rewrite mangled and the cutover left behind.
# (tests/test_release_pipeline.py rejects both the step and any self-substituting sed returning.)

- name: Build sdist + wheel
run: |
Expand Down Expand Up @@ -118,9 +118,32 @@ jobs:
built=$(/tmp/relsmoke/bin/python -c "import messagefoundry; print(messagefoundry.__version__)")
echo "built version: $built"
# On a tag push, the built version MUST equal the tag (single-source check).
#
# Compared as PEP 440 VERSIONS, not as strings. A pre-release tag has to be spelled with a
# hyphen to match the `v[0-9]+.[0-9]+.[0-9]+-*` trigger above (`v0.3.0rc1` fires nothing), so
# a string compare forced __version__ to carry the non-canonical "0.3.0-rc1" purely to satisfy
# the gate — while hatchling/PyPI normalise it to 0.3.0rc1 everywhere else, meaning the tag,
# the module attribute and the wheel filename could not all be canonical at once. Version()
# normalises both sides, so canonical "0.3.0rc1" in __init__.py matches tag v0.3.0-rc1 and the
# check still fails loudly on a genuine mismatch.
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
want="${GITHUB_REF_NAME#v}"
[ "$built" = "$want" ] || { echo "::error::built version $built != tag $want"; exit 1; }
/tmp/relsmoke/bin/pip install --quiet packaging
/tmp/relsmoke/bin/python - "$built" "$want" <<'PYVER'
import sys
from packaging.version import InvalidVersion, Version

built, want = sys.argv[1], sys.argv[2]
try:
b, w = Version(built), Version(want)
except InvalidVersion as exc:
raise SystemExit(f"::error::unparseable version ({exc}) — built={built!r} tag={want!r}")
if b != w:
raise SystemExit(
f"::error::built version {built} != tag {want} (normalised {b} != {w})"
)
print(f"version matches tag: {b}")
PYVER
# A released wheel MUST ship the PEP 561 marker so an external consumer's mypy sees the
# typed surface. Enforced ONLY on a tag, so the workflow_dispatch dry-run is never blocked
# before py.typed (WS-3) merges. This is the CI enforcement of the cross-workstream rule
Expand Down Expand Up @@ -306,9 +329,30 @@ jobs:
run: |
built=$(python -c "import glob,re; print(re.search(r'messagefoundry_harness-([^-]+)-', glob.glob('harness-dist/*.whl')[0]).group(1))")
echo "harness wheel version: $built"
# PEP 440 comparison, for a STRONGER reason than the engine's copy of this check: `built` is
# read out of the WHEEL FILENAME, which hatchling has already normalised. On tag v0.3.0-rc1
# the file is messagefoundry_harness-0.3.0rc1-...whl, so built=0.3.0rc1 while want=0.3.0-rc1
# and a string compare could NEVER match — this job failed on every pre-release tag by
# construction, whatever __version__ said. With PUBLISH_HARNESS=true it also runs after the
# engine has already uploaded, so the failure would land half-published.
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
want="${GITHUB_REF_NAME#v}"
[ "$built" = "$want" ] || { echo "::error::harness wheel version $built != tag $want"; exit 1; }
python -m pip install --quiet packaging
python - "$built" "$want" <<'PYVER'
import sys
from packaging.version import InvalidVersion, Version

built, want = sys.argv[1], sys.argv[2]
try:
b, w = Version(built), Version(want)
except InvalidVersion as exc:
raise SystemExit(f"::error::unparseable version ({exc}) — built={built!r} tag={want!r}")
if b != w:
raise SystemExit(
f"::error::harness wheel version {built} != tag {want} (normalised {b} != {w})"
)
print(f"harness version matches tag: {b}")
PYVER
fi

- name: Attach the harness wheel to the GitHub release
Expand Down
43 changes: 42 additions & 1 deletion tests/test_release_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ def test_release_load_bearing_canaries_present() -> None:
"leak gate exits nonzero": "exit 1",
# version single-sourced from the package == the tag
"version==tag single-source": 'want="${GITHUB_REF_NAME#v}"',
"version==tag comparison": '[ "$built" = "$want" ]',
# The comparison itself. Was the raw string test `[ "$built" = "$want" ]`; that could not
# accept a canonical pre-release (0.3.0rc1 vs tag v0.3.0-rc1), so it is now a PEP 440
# Version() compare. The canary tracks the CHECK existing, not how it is spelled.
"version==tag comparison": "from packaging.version import InvalidVersion, Version",
# py.typed (WS-3) enforced on a tag push
"py.typed enforced on tag": "unzip -l dist/*.whl | grep -q 'messagefoundry/py.typed'",
"py.typed only on a tag": 'GITHUB_REF_TYPE:-}" = "tag"',
Expand Down Expand Up @@ -265,3 +268,41 @@ def test_release_jobs_are_gated_ON_the_source_repo() -> None:
)
# The private vault must never be a release target either.
assert "wshallwshall" not in rel, "release.yml must not reference the retired private vault"


def test_no_self_referential_slug_rewrite_survives() -> None:
"""The README slug rewrite is GONE, and no `sed s#X#X#` may come back.

publish.ps1 rewrote the private slug to the public one across *.yml — including this workflow —
so at the cutover both sides of the substitution collapsed to the same string, leaving a no-op
sed followed by a guard that failed if that string was present. The README names it 19 times, so
the step failed on EVERY tag push: the v0.3.0 tag died there and the repo has no releases.
"""
rel = _release()
assert "- name: Rewrite README repo slug" not in rel, (
"the mirror-era README slug rewrite is back — there is one repo now, so it rewrites nothing, "
"and its 'left private-repo links' guard then fails on every tag"
)
assert not re.search(r"sed[^\n]*s([#/|])([^\n#/|]+)\1\2\1", rel), (
"a self-referential sed (s#X#X#) is present — it cannot transform anything, and paired with "
"a grep guard it fails unconditionally"
)


def test_both_wheel_smokes_compare_versions_not_strings() -> None:
"""Tag-vs-built comparison must normalise (PEP 440), in BOTH the engine and harness jobs.

The trigger only fires on `vX.Y.Z` / `vX.Y.Z-*`, so a pre-release tag must carry a hyphen, while
hatchling and PyPI normalise `0.3.0-rc1` to `0.3.0rc1`. A raw string compare therefore cannot be
satisfied by a canonical version — and in the HARNESS job it can never be satisfied at all, since
its `built` is parsed out of the already-normalised wheel FILENAME.
"""
rel = _release()
assert '[ "$built" = "$want" ]' not in rel, (
"a raw string compare of tag vs built version is back; it rejects canonical pre-release "
"versions (0.3.0rc1 != 0.3.0-rc1) and blocks every rc tag"
)
assert rel.count("from packaging.version import") == 2, (
"both the engine and harness wheel smokes must compare PEP 440 versions — fixing only one "
"moves the failure rather than removing it"
)
Loading