@@ -123,6 +123,28 @@ concurrency:
123123# gate and every publisher, and ``release_status`` reports, green, whether that
124124# release is complete. A ``v*`` push still meets the #1634 check. The
125125# ``vendor_update`` job is the issue's other half.
126+ #
127+ # Owner ruling, 2026-10-30: ``__version__`` carries a PEP 440 dev-release
128+ # suffix (``X.Y.Z.devN``) while a version is still under active development, a
129+ # work-in-progress signal with no gate of its own until now -- and the
130+ # ``workflow_run`` trigger above means a scheduled Vendor Update can reach this
131+ # workflow and carry that dev version all the way to ``github``'s one reviewer
132+ # approval (#887, below). That approval is not nothing, but it is one person
133+ # approving a diff for reasons that are usually unrelated to the version
134+ # string, so a stray ``.devN`` is easy to miss there; and a re-run of a job
135+ # that already skipped ``github`` once does not ask for a fresh approval at
136+ # all. ``PCAPKIT_DEV_RELEASE`` closes that gap in code rather than leaving it
137+ # to review. ``PCAPKIT_DEV_RELEASE`` (``version_check``'s ``is_devrelease``)
138+ # is read by ``github``, ``tag``, ``pypi`` and ``conda`` alike, deliberately not
139+ # ``PCAPKIT_PRERELEASE`` (``is_prerelease``): the latter is also ``true`` for a
140+ # deliberately-tagged alpha/beta/rc this project has shipped for real (e.g.
141+ # ``1.5.0b14``), and gating on it would silently block one.
142+ # tests/project/test_release_gates.py's ``test_all_four_publishers_gate_on_dev_release``
143+ # checks this text, and ``gate_rows``/``GITHUB_ROWS`` evaluate it. ``release_status``
144+ # also reads it: with the gate skipping all four publishers, their evidence
145+ # outputs stay ``false``, which would otherwise read as #888's stranded partial
146+ # release. util/release_status.py's ``dev_release_skipped()`` reports that
147+ # combination as a quiet, deliberate no-op instead.
126148
127149jobs :
128150 # #1748: a Vendor Update raised by a Unit Tests verdict for another commit
@@ -226,6 +248,17 @@ jobs:
226248 run : |
227249 python -m pip install -U pip setuptools wheel packaging
228250
251+ # Owner ruling, 2026-10-30: during active development on a not-yet-released
252+ # version, ``__version__`` carries a PEP 440 dev-release suffix
253+ # (``X.Y.Z.devN``) as a work-in-progress signal. ``PCAPKIT_DEV_RELEASE`` is
254+ # ``is_devrelease``, not ``is_prerelease`` -- the latter is also ``true``
255+ # for a deliberately-tagged alpha/beta/rc (e.g. ``1.5.0b14``, a real
256+ # release), and gating on it would silently block one. ``github``, ``tag``,
257+ # ``pypi`` and ``conda`` all read this below so that a scheduled Vendor
258+ # Update bumping a dev version never tags or publishes it on the strength
259+ # of ``github-release``'s one reviewer alone, who can miss a stray
260+ # ``.devN`` in an otherwise-unrelated approval; see
261+ # docs/source/contributing/releasing.rst.
229262 - name : Get Version
230263 id : get_version
231264 run : |
@@ -236,6 +269,7 @@ jobs:
236269 {
237270 echo "PCAPKIT_VERSION=$(python -c 'import pcapkit; print(pcapkit.__version__)')"
238271 echo "PCAPKIT_PRERELEASE=$(python -c 'from packaging.version import Version; import pcapkit; print(str(Version(pcapkit.__version__).is_prerelease).lower())')"
272+ echo "PCAPKIT_DEV_RELEASE=$(python -c 'from packaging.version import Version; import pcapkit; print(str(Version(pcapkit.__version__).is_devrelease).lower())')"
239273 echo "PCAPKIT_CONDA_LABEL=$(python -c 'from packaging.version import Version; import pcapkit; print("dev" if Version(pcapkit.__version__).is_prerelease else "main")')"
240274 } >> "$GITHUB_OUTPUT"
241275
@@ -310,6 +344,7 @@ jobs:
310344 outputs :
311345 PCAPKIT_VERSION : ${{ steps.get_version.outputs.PCAPKIT_VERSION }}
312346 PCAPKIT_PRERELEASE : ${{ steps.get_version.outputs.PCAPKIT_PRERELEASE }}
347+ PCAPKIT_DEV_RELEASE : ${{ steps.get_version.outputs.PCAPKIT_DEV_RELEASE }}
313348 PCAPKIT_TAG_EXISTS : ${{ steps.check_tag.outputs.exists }}
314349 PCAPKIT_CONDA_TAG_EXISTS : ${{ steps.check_conda_tag.outputs.exists }}
315350 PCAPKIT_PYPI_COMPLETE : ${{ steps.check_pypi.outputs.PCAPKIT_PYPI_COMPLETE }}
@@ -324,12 +359,18 @@ jobs:
324359
325360 # No ``environment:``: no outward effect, and a job calling a reusable
326361 # workflow may not declare one. Runs exactly when a publisher could (#1052,
327- # #1748); see the note above ``jobs:``.
362+ # #1748, and, since the dev-release gate below, owner ruling 2026-10-30);
363+ # see the note above ``jobs:``. ``PCAPKIT_DEV_RELEASE`` carries none of the
364+ # other clauses' ``startsWith(github.ref_name, 'v')`` exemption, matching
365+ # ``github``, ``tag``, ``pypi`` and ``conda`` below: a dev version can never
366+ # be published regardless of how this run started, so spending the ~20
367+ # minutes here on one is the same waste #1052 added this job to avoid.
328368 unit-tests :
329369 name : Release test gate
330370 needs : [ version_check ]
331371 if : >-
332372 ${{ needs.version_check.result == 'success' &&
373+ needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true' &&
333374 (startsWith(github.ref_name, 'v') ||
334375 needs.version_check.outputs.PCAPKIT_RELEASED_ELSEWHERE != 'true') &&
335376 (startsWith(github.ref_name, 'v') ||
@@ -360,6 +401,7 @@ jobs:
360401 needs : [ unit-tests, version_check ]
361402 if : >-
362403 ${{ (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_RELEASED_ELSEWHERE != 'true') &&
404+ needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true' &&
363405 (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_TAG_EXISTS == 'false') }}
364406 steps :
365407 - name : Checkout code
@@ -494,6 +536,7 @@ jobs:
494536 if : >-
495537 ${{ !cancelled() && needs.version_check.result == 'success' &&
496538 needs.unit-tests.result == 'success' &&
539+ needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true' &&
497540 (needs.github.result == 'success' || needs.github.result == 'skipped') &&
498541 (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_RELEASED_ELSEWHERE != 'true') &&
499542 (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_CONDA_TAG_EXISTS == 'false') }}
@@ -580,6 +623,7 @@ jobs:
580623 if : >-
581624 ${{ !cancelled() && needs.version_check.result == 'success' &&
582625 needs.unit-tests.result == 'success' &&
626+ needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true' &&
583627 (needs.github.result == 'success' || needs.github.result == 'skipped') &&
584628 (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_RELEASED_ELSEWHERE != 'true') &&
585629 (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_PYPI_COMPLETE == 'false') }}
@@ -738,6 +782,7 @@ jobs:
738782 if : >-
739783 ${{ !cancelled() && needs.version_check.result == 'success' &&
740784 needs.unit-tests.result == 'success' &&
785+ needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true' &&
741786 (needs.github.result == 'success' || needs.github.result == 'skipped') &&
742787 (needs.tag.result == 'success' || needs.tag.result == 'skipped') &&
743788 (startsWith(github.ref_name, 'v') || needs.version_check.outputs.PCAPKIT_RELEASED_ELSEWHERE != 'true') &&
@@ -994,6 +1039,7 @@ jobs:
9941039 PCAPKIT_CONDA_COMPLETE : ${{ needs.version_check.outputs.PCAPKIT_CONDA_COMPLETE }}
9951040 PCAPKIT_RELEASED_AT : ${{ needs.version_check.outputs.PCAPKIT_RELEASED_AT }}
9961041 PCAPKIT_RELEASED_ELSEWHERE : ${{ needs.version_check.outputs.PCAPKIT_RELEASED_ELSEWHERE }}
1042+ PCAPKIT_DEV_RELEASE : ${{ needs.version_check.outputs.PCAPKIT_DEV_RELEASE }}
9971043 PCAPKIT_PYPI_KTLO_310 : ${{ needs.pypi.outputs.ktlo_310 }}
9981044 PCAPKIT_PYPI_KTLO_PYPY310 : ${{ needs.pypi.outputs.ktlo_pypy310 }}
9991045 run : python3 util/release_status.py
0 commit comments