docs(contributing): correct releasing.rst's cascade guard (#958) - #959
Conversation
The page generalised the sibling workflows' guard to the three
publishing jobs: "The three jobs here do the same for each of their own
direct dependencies: `!cancelled() && needs.<job>.result !=
'failure'`". No job in create-release.yml uses that spelling. The only
two occurrences in the file are comments explaining why it is not used
(:119, :124-128).
What the three actually use, for `github` and -- for `conda` -- `tag`:
!cancelled() && (needs.<job>.result == 'success' ||
needs.<job>.result == 'skipped')
The two differ only when a predecessor's own result is `cancelled`:
`!= 'failure'` admits it, the equality pair does not. With `tag`
cancelled and the run itself not, `!= 'failure'` would send `conda`
into `actions/checkout` with `ref: conda-<version>+0` for a tag never
pushed, trading a clean skip for a checkout failure. So the documented
guard would have reintroduced what the real one prevents.
`version_check` is now named as the exception rather than folded into
"each of their own direct dependencies", which it is: all three require
it to have strictly succeeded, with no `|| 'skipped'`, because it
produces the evidence the gates read.
The first half of the paragraph is untouched and correct -- the three
sibling workflows do use `!= 'failure'` for `needs.unit-tests.result`.
tests/project/test_release_gates.py:624 already asserts `!= 'failure'`
must not appear in these conditions, so the behaviour was pinned and
only the prose disagreed. 34 tests, 23 subtests, re-derived as 34 under
plain unittest. No test pins this page's prose.
|
GOOD TO GO at It did the two things I most wanted and neither was in the diff's own claims. It enumerated rather than accepted "differ only when cancelled". It checked the half of the paragraph this change does not touch, which nobody had: all three siblings do open with exactly It also derived the Three things it raised that I am acting on separately rather than here:
One more non-blocking point worth keeping: the Unverified by anyone: no Labelled |
… predecessors (#960) The comment above `jobs:` said `tag`, `pypi` and `conda` spell the guard as the success-or-skipped equality pair "for each of their own direct dependencies". That is false for `version_check`, a direct dependency of all three (:443, :521, :620) which each require to have strictly succeeded, with no `|| 'skipped'` (:445, :523, :622). The equality pair applies only to `github`, and for `conda` also `tag`. The comment is where the documentation got the claim from -- #958 was the same error in releasing.rst, corrected in #959 -- so leaving it would have had the docs describe this file more accurately than the file describes itself. Comment text only; no executable YAML changed. Verified by parsing both revisions with `yaml.safe_load` and comparing the loaded structures, which is stronger than the `grep -vE '^[+-]#'` it replaces -- that pattern only matches `#` at column zero and says nothing about indented YAML. Also adds test_version_check_must_strictly_succeed, which nothing in the module covered. It asserts the set of comparisons against `version_check.result` is exactly `{('==', 'success')}` rather than checking substrings, because the substring form cannot catch `(!= 'failure' || == 'success')` -- logically just `!= 'failure'`, so it admits `skipped` and `cancelled` while keeping the `== 'success'` literal -- nor a bare `|| == 'cancelled'`. Falsifiability confirmed against mutated copies of the workflow: head OK, and failures on `|| == 'skipped'`, `|| == 'cancelled'`, `(!= 'failure' || == 'success')` and the clause deleted outright. tests/project/test_release_gates.py: 35 passed, 26 subtests, re-derived as 35 under plain unittest.
… predecessors (#960) The comment above `jobs:` said `tag`, `pypi` and `conda` spell the guard as the success-or-skipped equality pair "for each of their own direct dependencies". That is false for `version_check`, a direct dependency of all three (:443, :521, :620) which each require to have strictly succeeded, with no `|| 'skipped'` (:445, :523, :622). The equality pair applies only to `github`, and for `conda` also `tag`. The comment is where the documentation got the claim from -- #958 was the same error in releasing.rst, corrected in #959 -- so leaving it would have had the docs describe this file more accurately than the file describes itself. Comment text only; no executable YAML changed. Verified by parsing both revisions with `yaml.safe_load` and comparing the loaded structures, which is stronger than a `grep -vE '^[+-]#'` -- that pattern only matches `#` at column zero and says nothing about indented YAML. Also adds test_version_check_must_strictly_succeed, which nothing in the module covered. Substring assertions cannot pin this clause: a weakening can keep the `== 'success'` literal while meaning the opposite, since `(!= 'failure' || == 'success')` is just `!= 'failure'`. So the test strips whitespace, then asserts the set of comparisons against `version_check.result` is exactly `{('==', 'success')}` *and* that the property is referenced exactly once. The count is the exhaustiveness half: anything added alongside the real clause raises it however spelled, including the mirror-image `'skipped' == needs.version_check.result` and forms using no `==` at all, such as `contains(fromJSON('["skipped"]'), ...)`. Falsifiability confirmed against mutated copies of the workflow, each checked to be valid YAML first. Head passes; all seven of `|| == 'skipped'`, `|| == 'cancelled'`, `(!= 'failure' || == 'success')`, reversed operands, `contains(fromJSON(...))`, whitespace-padded property dots, and the clause deleted outright fail. tests/project/test_release_gates.py: 35 passed, 26 subtests, re-derived as 35 under plain unittest.
… predecessors (#960) The comment above `jobs:` said `tag`, `pypi` and `conda` spell the guard as the success-or-skipped equality pair "for each of their own direct dependencies". That is false for `version_check`, a direct dependency of all three (:443, :521, :620) which each require to have strictly succeeded, with no `|| 'skipped'` (:445, :523, :622). The equality pair applies only to `github`, and for `conda` also `tag`. The comment is where the documentation got the claim from -- #958 was the same error in releasing.rst, corrected in #959 -- so leaving it would have had the docs describe this file more accurately than the file describes itself. Comment text only; no executable YAML changed. Verified by parsing both revisions with `yaml.safe_load` and comparing the loaded structures, which is stronger than a `grep -vE '^[+-]#'` -- that pattern only matches `#` at column zero and says nothing about indented YAML. Also adds test_version_check_must_strictly_succeed, which nothing in the module covered. Substring assertions cannot pin this clause on their own, so it makes three, over a whitespace-stripped condition: - the clause appears as a top-level `&&` conjunct, because vocabulary is not structure -- a one-character `&&`-to-`||` slip, or a well-meant `|| github.event_name == 'push'`, keeps every token while nullifying the gate; - the set of comparisons against `version_check.result` is exactly `{('==', 'success')}`, since `(!= 'failure' || == 'success')` keeps the literal while meaning the opposite; - `version_check.result` is referenced exactly once, which is the exhaustiveness half: anything added raises the count however spelled, including `'skipped' == needs.version_check.result` and forms using no `==` at all. Falsifiability confirmed against mutated copies, each valid YAML first. Head passes; `|| == 'skipped'`, `|| == 'cancelled'`, `(!= 'failure' || == 'success')`, reversed operands, `contains(fromJSON(...))`, whitespace-padded dots, `|| event_name`, a negation, `&&`-to-`||`, `|| outputs != ''`, and the clause deleted all fail. tests/project/test_release_gates.py: 35 passed, 26 subtests, re-derived as 35 under plain unittest.
… predecessors (#960) The comment above `jobs:` said `tag`, `pypi` and `conda` spell the guard as the success-or-skipped equality pair "for each of their own direct dependencies". That is false for `version_check`, a direct dependency of all three (:443, :521, :620) which each require to have strictly succeeded, with no `|| 'skipped'` (:445, :523, :622). The equality pair applies only to `github`, and for `conda` also `tag`. The comment is where the documentation got the claim from -- #958 was the same error in releasing.rst, corrected in #959 -- so leaving it would have had the docs describe this file more accurately than the file describes itself. Comment text only; no executable YAML changed. Verified by parsing both revisions with `yaml.safe_load` and comparing the loaded structures, which is stronger than a `grep -vE '^[+-]#'` -- that pattern only matches `#` at column zero and says nothing about indented YAML. Also adds test_version_check_must_strictly_succeed, which nothing in the module covered. An `assertNotIn` over known-bad spellings cannot pin this clause, since `(!= 'failure' || == 'success')` keeps the literal while meaning the opposite. Two assertions over a whitespace-stripped condition instead: - the clause appears as a top-level `&&` conjunct, because vocabulary is not structure -- a one-character `&&`-to-`||` slip keeps every token while nullifying the gate; - `version_check.result` is referenced exactly once, which closes the rest: anything added alongside the real clause raises the count however spelled. A comparison-set assertion was carried for two rounds and is dropped as provably redundant: the conjunct substring guarantees one occurrence followed by `== 'success'`, the count guarantees it is the only one, so the set is necessarily `{('==', 'success')}`. Brute-forced over 173,088 synthetic conditions -- zero cases where it fires alone. Knowingly not closed, and now recorded in the docstring: a top-level `||` outside the conjunction chain. Conjunction is monotone, so adding `&&` conjuncts can only strengthen the gate; disjunction is the only remaining weakening and catching it needs the expression evaluated rather than matched. Tracked as #962. Falsifiability confirmed against mutated copies, each valid YAML first. Head passes, a legitimate extra `&&` conjunct still passes, and `|| == 'skipped'`, `(!= 'failure' || == 'success')`, reversed operands, `contains(fromJSON(...))`, `|| event_name`, a negation, `&&`-to-`||` and the clause deleted all fail. tests/project/test_release_gates.py: 35 passed, 26 subtests, re-derived as 35 under plain unittest.
… predecessors (#960) The comment above `jobs:` said `tag`, `pypi` and `conda` spell the guard as the success-or-skipped equality pair "for each of their own direct dependencies". That is false for `version_check`, a direct dependency of all three (:443, :521, :620) which each require to have strictly succeeded, with no `|| 'skipped'` (:445, :523, :622). The equality pair applies only to `github`, and for `conda` also `tag`. The comment is where the documentation got the claim from -- #958 was the same error in releasing.rst, corrected in #959 -- so leaving it would have had the docs describe this file more accurately than the file describes itself. Comment text only; no executable YAML changed. Verified by parsing both revisions with `yaml.safe_load` and comparing the loaded structures, which is stronger than a `grep -vE '^[+-]#'` -- that pattern only matches `#` at column zero and says nothing about indented YAML. Also adds test_version_check_must_strictly_succeed, which nothing in the module covered. An `assertNotIn` over known-bad spellings cannot pin this clause, since `(!= 'failure' || == 'success')` keeps the literal while meaning the opposite. Two assertions over a whitespace-stripped condition instead: - the clause appears as a top-level `&&` conjunct, because vocabulary is not structure -- a one-character `&&`-to-`||` slip keeps every token while nullifying the gate; - `version_check.result` is referenced exactly once, which closes the rest: anything added alongside the real clause raises the count however spelled. A comparison-set assertion was carried for two rounds and is dropped as provably redundant: the conjunct substring guarantees one occurrence followed by `== 'success'`, the count guarantees it is the only one, so the set is necessarily `{('==', 'success')}`. Brute-forced over 173,088 synthetic conditions -- zero cases where it fires alone. Knowingly not closed, and now recorded in the docstring: a top-level `||` outside the conjunction chain. Conjunction is monotone, so adding `&&` conjuncts can only strengthen the gate; disjunction is the only remaining weakening and catching it needs the expression evaluated rather than matched. Tracked as #962. Falsifiability confirmed against mutated copies, each valid YAML first. Head passes, a legitimate extra `&&` conjunct still passes, and `|| == 'skipped'`, `(!= 'failure' || == 'success')`, reversed operands, `contains(fromJSON(...))`, `|| event_name`, a negation, `&&`-to-`||` and the clause deleted all fail. tests/project/test_release_gates.py: 35 passed, 26 subtests, re-derived as 35 under plain unittest.
Please follow the guide below
make pylint,make mypy,make isort)make testpasses, and a test case covers the changedocs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — documentation prose only, no library behaviour changeWhat is the purpose of your pull request?
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingchore— anything elseDescription of your pull request and other information
Closes #958. The companion to #957. On
maintoday this is the only page that states the cascade guard at all, and it states it wrongly —pep.rstdoes not mention it, and the corrected version of that page lives only in unmerged #957.What was wrong.
releasing.rstgeneralised the sibling workflows' guard to the three publishing jobs:No job in
create-release.ymluses that spelling. The only two occurrences in the file are comments explaining why it is not used (:119,:124-128). Whattag,pypiandcondaactually use, forgithuband — forconda—tag:Why it matters rather than being a typo. The two spellings differ only when a predecessor's own result is
cancelled, and that is the case the real spelling exists for. Withtagcancelled and the run itself not cancelled,!= 'failure'evaluates true andcondaruns, intoactions/checkoutwithref: conda-<version>+0for a tagtagnever pushed — trading a clean skip for a checkout failure. So the documented guard would have reintroduced exactly what the real one prevents.A second over-generalisation, which the original sentence also carried. "each of their own direct dependencies" is false for
version_check, which is a direct dependency of all three and is required to have strictly succeeded, with no|| 'skipped'(:445,:523,:622). It now has its own sentence, with the reason: it produces the evidence the gates read, so a skipped or cancelledversion_checkleaves them nothing to decide on.Untouched and correct: the first half of the paragraph.
cron-vendor.yml,deploy-pages.ymlandcron-conda.ymlgenuinely do use!= 'failure'forneeds.unit-tests.result. Only the sentence generalising that to the publishing jobs was wrong.Verification.
tests/project/test_release_gates.py:624already asserts!= 'failure'must not appear in these conditions — so the behaviour was pinned and only the prose disagreed. 34 tests, 23 subtests, re-derived as 34 tests, OK under plainunittest.grep -rln "releasing.rst" tests/is empty, so no test pins this page's prose. The edited block parses underdocutils.A grep trap for anyone re-checking this:
grep "!= 'failure'" docs/source/contributing/releasing.rstonmainreturns only:171, which is the correct occurrence. The wrong one wrapped across a line break, so a single-line grep missed it entirely.Not verified: no
sphinx-buildwas run. No workflow file was changed.