Repository navigation
ci: gate create-release.yml's publishers on a dev-release version - #2452
Conversation
|
Verified directly: the |
|
Cross-review (different model, adversarial) came back NEEDS CHANGES. The
Also should-fix: the "no human in the path" framing overstates it (reviewer approval already gates Dispatching a fix to this branch now. |
A scheduled Vendor Update can trigger create-release.yml with no human starting the run. version_check already computed PCAPKIT_PRERELEASE (is_prerelease), but nothing read it to stop a publish, so a version carrying a PEP 440 dev-release suffix (X.Y.Z.devN, the owner's work-in-progress marker during active development) could be tagged and published by that automatic trigger alone. - version_check's "Get Version" step adds PCAPKIT_DEV_RELEASE, reading is_devrelease rather than is_prerelease: the latter is also true for a deliberately-tagged alpha/beta/rc this project has shipped for real (e.g. 1.5.0b14), and gating on it would silently block one. - github, tag, pypi and conda each add needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true' as a top-level && conjunct, unconditionally, and unit-tests (the ~20-minute release test gate) now adds the same conjunct -- a dev version can never publish, so running that gate for one is wasted time (#1052). - util/release_status.py: a dev release with every publisher skipped for it now reports a quiet "Nothing to release" notice instead of falling into the #888 stranded-partial-release error; wired via a new PCAPKIT_DEV_RELEASE entry in release_status's env. - Reworded four "no human in the path" overstatements (create-release.yml x2, releasing.rst, a test docstring) to acknowledge github-release's reviewer approval -- still worth the gate, since a reviewer can miss a stray .devN, and a re-run that skips github asks for no fresh approval. - releasing.rst: util/bump_version.py has no option to drop the dev suffix -- on a dev version it only advances the devN counter -- so corrected the claim that it bumps to the final version string, and noted that running it on a dev version just produces another one. - tests/project/test_release_gates.py: new rows in gate_rows(), GITHUB_ROWS and UNIT_TESTS_ROWS covering PCAPKIT_DEV_RELEASE, a character-for-character wiring check between version_check's outputs and every consumer, a check that no job's gate reads PCAPKIT_PRERELEASE by mistake, and a fixed test comment whose reasoning had the risk of an unset gate output backwards for a publisher job. - tests/project/test_release_status_scripts_1967.py: new cases covering the dev-release notice and the regression check that the same skip pattern with PCAPKIT_DEV_RELEASE=false still raises #888. Build: coverage run -m pytest tests/project/ -k "release or workflow or conda_dist" (641 passed, 1 pre-existing unrelated skip); isort, pylint (10.00/10 on util/release_status.py) and actionlint 1.7.12 all clean.
12889d6 to
a842da0
Compare
|
Fixed and verified directly (new head |
#2458) Mirror create-release.yml's #2452 dev-release gate in cron-conda.yml, which has its own independent, schedule/workflow_run-reachable route to publishing with no check against __version__ carrying a PEP 440 .devN suffix. - conda-update's Get Version step computes PCAPKIT_DEV_RELEASE from packaging.version.Version(...).is_devrelease, deliberately not is_prerelease, which is also true for a real alpha/beta/rc this project has shipped and must not block. - conda-tag's `if:` adds the clause as a top-level conjunct; conda-dist's adds it inside its publishing branch only, so a build-only manual run still builds and smoke-tests a dev version without uploading it. - discussion's "Post the report to Discussions" step now tells "nothing pushed" apart from "a dev-release build was pushed but Tag/Upload were withheld on purpose", instead of silently dropping both lines; the stale "Both skip unless a build was pushed" comment is corrected. - Comments describing #2452's create-release.yml gate as already merged are reworded to "proposed ... (not yet merged)". - test_conda_dev_release_gate_2458.py gains two inputs (1.6.0rc1.dev2, 1.6.0.post1.dev0) that close two narrowed-predicate mutations a cross-review found surviving; test_cron_reports_2030.py gains coverage for the new report line. actionlint 1.7.12 (shellcheck 0.10.0): zero findings. Both test files: 55 passed, 118 subtests passed, including the new report and mutation-closing cases; pylint/mypy clean on the touched files (`pcapkit` itself is untouched, so neither applies per lint.yml's own invocation).
#2458) Mirror create-release.yml's #2452 dev-release gate in cron-conda.yml, which has its own independent, schedule/workflow_run-reachable route to publishing with no check against __version__ carrying a PEP 440 .devN suffix. - conda-update's Get Version step computes PCAPKIT_DEV_RELEASE from packaging.version.Version(...).is_devrelease, deliberately not is_prerelease, which is also true for a real alpha/beta/rc this project has shipped and must not block. - conda-tag's `if:` adds the clause as a top-level conjunct; conda-dist's adds it inside its publishing branch only, so a build-only manual run still builds and smoke-tests a dev version without uploading it. - discussion's "Post the report to Discussions" step now tells "nothing pushed" apart from "a dev-release build was pushed but Tag/Upload were withheld on purpose", instead of silently dropping both lines; the stale "Both skip unless a build was pushed" comment is corrected. - Comments describing #2452's create-release.yml gate as already merged are reworded to "proposed ... (not yet merged)". - test_conda_dev_release_gate_2458.py gains two inputs (1.6.0rc1.dev2, 1.6.0.post1.dev0) that close two narrowed-predicate mutations a cross-review found surviving; test_cron_reports_2030.py gains coverage for the new report line, plus three rows closing a fourth review round's remaining gaps -- CONDA_CHANGED=false with DEV_RELEASE=true, DEV_RELEASE= false, and DEV_RELEASE='' on a pushed build -- each confirmed to kill its own narrowed-predicate mutant of the explanation line's elif guard. actionlint 1.7.12 (shellcheck 0.10.0): zero findings. Both test files: 43 passed, 105 subtests passed, including the new report and mutation-closing cases; pylint/mypy clean on the touched files (`pcapkit` itself is untouched, so neither applies per lint.yml's own invocation).
|
Coverage: 94.44% (unit tier, Python 3.15,
Per-file detail: the |
#2458) Mirror create-release.yml's #2452 dev-release gate in cron-conda.yml, which has its own independent, schedule/workflow_run-reachable route to publishing with no check against __version__ carrying a PEP 440 .devN suffix. - conda-update's Get Version step computes PCAPKIT_DEV_RELEASE from packaging.version.Version(...).is_devrelease, deliberately not is_prerelease, which is also true for a real alpha/beta/rc this project has shipped and must not block. - conda-tag's `if:` adds the clause as a top-level conjunct; conda-dist's adds it inside its publishing branch only, so a build-only manual run still builds and smoke-tests a dev version without uploading it. - discussion's "Post the report to Discussions" step now tells "nothing pushed" apart from "a dev-release build was pushed but Tag/Upload were withheld on purpose", instead of silently dropping both lines; the stale "Both skip unless a build was pushed" comment is corrected. - Comments describing #2452's create-release.yml gate as already merged are reworded to "proposed ... (not yet merged)". - test_conda_dev_release_gate_2458.py gains two inputs (1.6.0rc1.dev2, 1.6.0.post1.dev0) that close two narrowed-predicate mutations a cross-review found surviving; test_cron_reports_2030.py gains coverage for the new report line, plus three rows closing round 3's remaining gaps -- CONDA_CHANGED=false with DEV_RELEASE=true, DEV_RELEASE=false, and DEV_RELEASE='' on a pushed build -- each confirmed to kill its own widened-predicate mutant of the explanation line's elif guard. actionlint 1.7.12 (shellcheck 0.10.0): zero findings. Both test files: 43 passed, 105 subtests passed, including the new report and mutation-closing cases; pylint clean on both files; mypy clean on test_cron_reports_2030.py (the 2458 file carries the same pre-existing attr-defined/unused-ignore pattern as sibling tests, not CI-gated).
|
NEEDS CHANGES. Cross-review on Sonnet at head The dev-version gate itself works as claimed. Every PEP 440 dev spelling is flagged and
Not blocking: the publishers' |
…dev-release guard (#2452) Against a842da0: - release_status.py reconciles with all([...]) again, as before this PR: the generator short-circuited at the first unreconciled publisher and skipped reading later evidence, so an unset one exited 0 where the bash reference's set -u exits 1. A NOTE says why the list is deliberate. - The dev-release notice says "this run's version", not "main's version", which was wrong on a v* tag push. - New status cases kill three surviving mutants: a failure followed by a skipped publisher with unset evidence (generator), a dev flag with pypi failed (dropping the every-publisher-skipped check), and run 41098750345's 1.5.1 shape with the flag empty and unset (== 'true' to != 'false'). sampled_status_cases() now draws PCAPKIT_DEV_RELEASE.
|
Verified directly (new head |
|
GOOD TO GO (Sonnet, Merge only after 1.5.1 publishes: this edits |
Please follow the guide below
make pylint,make mypy,make isort) -- nopcapkit/file is touched, so these targets are no-ops for this change; confirmed by running them (pre-existingpcapkit/findings only).make testpasses, and a test case covers the change -- see test results below.docs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible -- N/A, centralised in chore(docs): centralize 1.6.0 changelog updates in one place, not per-feature-PR #2449: this is a CI/workflow-only change with nopcapkit/library behaviour.What is the purpose of your pull request?
ci— workflows or build toolingDescription of your pull request and other information
Risk. Owner ruling, 2026-10-30: during active development on a not-yet-released
version (the current wave-2 effort targeting 1.6.0),
pcapkit.__version__should carrya PEP 440 dev-release suffix (
X.Y.Z.devN) as a work-in-progress signal. But.github/workflows/create-release.ymlhad no gate on it, and it can be triggeredwithout a human pushing a
v*tag: theworkflow_runtrigger fires on every "VendorUpdate" completion (a scheduled weekly cron that bumps vendor-registry constants).
version_checkalready computedPCAPKIT_PRERELEASE(is_prerelease), but that outputwas only ever read for display (the GitHub Release's
prerelease:flag) and the condalabel — never to skip a publish step. So a dev version left in
__version__by anin-progress feature branch could be tagged, released on GitHub, and published to PyPI
and conda by the next scheduled Vendor Update, with only
github-release's single reviewer approval in the path.Fix.
version_check's "Get Version" step now also computesPCAPKIT_DEV_RELEASEfrom
packaging.version.Version(pcapkit.__version__).is_devrelease, exposed as a joboutput. Each of the four jobs that actually tags or publishes something --
github(the GitHub Release and
v*tag),tag(theconda-*tag and a commit tomain),pypi, andconda-- gets a new top-level&&conjunct:needs.version_check.outputs.PCAPKIT_DEV_RELEASE != 'true'. It is unconditional,carrying none of the other clauses'
startsWith(github.ref_name, 'v')exemption, so italso blocks a human hand-pushing a
v*tag that happens to name a dev-release version.Why
is_devrelease, notis_prerelease. This project has shipped real,intentionally-tagged pre-1.x betas before (e.g.
1.5.0b14), andis_prereleaseisTruefor those too -- a blanketis_prereleasegate would silently block a legitimate,deliberately-tagged beta release, which is not what this ruling is about.
is_devreleaseisTrueonly for a PEP 440 dev release (X.Y.Z.devN) specifically, andstays
Falsefor alpha/beta/rc and for a normal/final version, so a real pre-release canstill ship while a dev-in-progress version cannot.
version_checkitself keeps running unconditionally (it is read-only, as its owncomment already documents) -- only the four publish-side jobs needed the new gate.
Tests.
tests/project/test_release_gates.pygets: new rows ingate_rows()(fortag/pypi/conda) andGITHUB_ROWS(forgithub) coveringPCAPKIT_DEV_RELEASE=trueon bothmainand av*ref, and=null(an unset outputlets the publishers through: their
!= 'true'fails open, matching the existingPCAPKIT_RELEASED_ELSEWHEREconvention; a fail-closed== 'false'was deliberately leftout); a new text-layer assertion,
test_all_four_publishers_gate_on_dev_release, checked directly against all four realconditions; a script-source assertion pinning
is_devreleaseoveris_prerelease(
test_dev_release_evidence_reads_is_devrelease_not_is_prerelease); and aMUTATIONSrow for the dropped conjunct. I verified these actually catch a regression by
temporarily removing the new conjunct from the
pypijob alone: both the evaluatorlayer (
gate_rows) and the text layer (test_all_four_publishers_gate_on_dev_release)independently failed, naming
pypi.docs/source/contributing/releasing.rstgets a short paragraph documenting theconvention in "The One Manual Step" section, where the
devNbump counter is alreadydiscussed.
Test plan
actionlint1.7.12 (the version this repo's ownlint.ymlpins) overcreate-release.yml-- cleanpython -c "import yaml; yaml.safe_load(...)"overcreate-release.yml-- parses, and the four jobs' resolvedif:text matches what was intendedpypi) makes two independent tests fail, naming that jobunit-testsandrelease_status.py. Theunit-testsrelease gate takes the same unconditionalPCAPKIT_DEV_RELEASE != 'true'conjunct, so a dev version doesn't spend a test run it can't publish from.util/release_status.pygains one case: when the flag is'true'and every publisher skipped, it exits 0 with a quiet "Nothing to release" notice saying this run's version carries a PEP 440 dev-release suffix. A publisher that ran, or a flag that is empty or unset, still reaches normal reconciliation and the #888 error. Reconciliation keeps its eagerall([...]), so every evidence variable is read, matching the bash reference intest_release_status_scripts_1967.py.tests/projectfiles that readcreate-release.yml,release_statusorreleasing.rst): 706 passed, 4 skipped, 5303 subtests.all(...), adev_release_skipped()without its every-publisher-skipped check, and== 'true'→!= 'false'.