Part of #421. Closes #363 when done.
Context
#363, filed earlier and standing alone, is a fails-green defect and belongs in this epic:
Release version can drift from the git tag with no guard (same gap views-faoapi hit at go-live)
Nothing enforces that [project].version in pyproject.toml equals the tag it was released under. The failure is silent in the worst way: PyPI would carry a package whose metadata says one version while the git history says another, and the mismatch is only discoverable by someone comparing them by hand.
The publish workflow has a version guard, but it only checks that the version beats what is on PyPI — not that it matches the tag being released.
Today this holds by discipline. It has held so far because the release ritual is followed carefully, which is exactly the kind of assurance this epic exists to replace.
Work
Two layers, because they catch it at different moments:
1. A test — tests/test_falsification_deploy_v160.py already has TestF1VersionBumped. Add a sibling: when HEAD is exactly on a v* tag, [project].version must equal that tag minus the v. When HEAD is not on a tag, skip with a reason (per this file's C-320 idiom — do not guess).
2. A workflow check — in publish_package.yml, before the build: assert github.ref_name (the tag that triggered the release) matches [project].version. Fail loudly with both values in the message. This is the last moment before an immutable artifact is published, and unlike the test it cannot be skipped.
WET. Two checks, two places, deliberately. They answer the same question at different times — one during development, one at the point of no return. Do not factor them into a shared helper.
Acceptance criteria
Validation — drill both
Record all three in the PR.
Files
tests/test_falsification_deploy_v160.py · .github/workflows/publish_package.yml · docs/guides/publishing_to_pypi.md (note the guard in §C)
Dependencies
None. Independent of Stories 1–3.
Note
The tag ruleset makes v* tags immutable — no deletion, no update, no bypass. That is precisely why the workflow check matters more than the test: after the tag exists, a mismatch cannot be corrected, only superseded by a new version.
Part of #421. Closes #363 when done.
Context
#363, filed earlier and standing alone, is a fails-green defect and belongs in this epic:
Nothing enforces that
[project].versioninpyproject.tomlequals the tag it was released under. The failure is silent in the worst way: PyPI would carry a package whose metadata says one version while the git history says another, and the mismatch is only discoverable by someone comparing them by hand.The publish workflow has a version guard, but it only checks that the version beats what is on PyPI — not that it matches the tag being released.
Today this holds by discipline. It has held so far because the release ritual is followed carefully, which is exactly the kind of assurance this epic exists to replace.
Work
Two layers, because they catch it at different moments:
1. A test —
tests/test_falsification_deploy_v160.pyalready hasTestF1VersionBumped. Add a sibling: whenHEADis exactly on av*tag,[project].versionmust equal that tag minus thev. WhenHEADis not on a tag, skip with a reason (per this file's C-320 idiom — do not guess).2. A workflow check — in
publish_package.yml, before the build: assertgithub.ref_name(the tag that triggered the release) matches[project].version. Fail loudly with both values in the message. This is the last moment before an immutable artifact is published, and unlike the test it cannot be skipped.WET. Two checks, two places, deliberately. They answer the same question at different times — one during development, one at the point of no return. Do not factor them into a shared helper.
Acceptance criteria
publish_package.ymlfails before build on mismatch, printing both valuesValidation — drill both
HEADonv1.11.0andpyproject.tomltemporarily set to1.11.1→ test fails. Revert.HEADnot on a tag → test skips, with a readable reasongithub.ref_name=v9.9.9against the real version → fails, printing bothRecord all three in the PR.
Files
tests/test_falsification_deploy_v160.py·.github/workflows/publish_package.yml·docs/guides/publishing_to_pypi.md(note the guard in §C)Dependencies
None. Independent of Stories 1–3.
Note
The tag ruleset makes
v*tags immutable — no deletion, no update, no bypass. That is precisely why the workflow check matters more than the test: after the tag exists, a mismatch cannot be corrected, only superseded by a new version.