Resolve the constraints a release ships instead of tagging whatever exists - #71040
Merged
Conversation
…xists The constraints published with a version were whatever the constraints-X-Y branch happened to hold when the release ran - a resolution made by the last CI build, for the sources at that moment, rather than for the version being released. The candidate tagged that tip and the final release retagged the candidate, so no release ever resolved constraints of its own. A candidate now resolves them allowing pre-releases: the providers of the wave being voted on exist on PyPI only as rc versions, so constraints that refuse pre-releases cannot describe what a tester is asked to install. They land on a branch of the candidate's own, leaving the branch every other build reads where it was. The final release cannot promote those by retagging - a released version must never pin an rc - so it resolves the same set again, without pre-releases, and commits onto constraints-X-Y, which is what makes the released constraints the baseline everything downstream reads. Which of the two happens is derived from the version, so the stage cannot be set inconsistently with it. The work runs on CI runners rather than on the release manager's machine, which would otherwise need a CI image for every supported Python before it could cut a release, and is reachable on its own through `breeze workflow-run release-constraints` for redoing a candidate's constraints or producing them for a release cut before this existed.
potiuk
requested review from
amoghrajesh,
ashb,
bugraoz93,
choo121600,
ephraimbuddy,
gopidesupavan,
jason810496,
jedcunningham,
jscheffl,
shahar1,
vatsrahul1001 and
vincbeck
as code owners
August 4, 2026 00:13
`--pre` applies to the whole resolution, so a candidate's constraints could pin a pre-release of any dependency - a beta of some third-party library would end up in what a release ships, which is not what allowing rc providers was meant to permit. uv considers a pre-release for a package only when a requirement for it mentions one, so naming the providers with a pre-release lower bound confines the allowance to them and leaves every other package on the default policy.
The rc lower bounds already confined pre-releases to the providers, but only because uv's default strategy happens to permit them for explicitly marked packages. Naming `explicit` says that in the command instead of leaving it to a default that could change, and drops the `if-necessary` half of that default - the part that would let a package nobody marked resolve to a pre-release when no final version satisfies it.
The candidate half was undocumented - the release notes described only what the final release does, leaving a release manager to infer why a candidate suddenly grows a branch and a tag of its own, and why its constraints pin rc providers when nothing else in them is a pre-release. Both stages and the scope of the pre-release allowance are stated where each is reached.
eladkal
approved these changes
Aug 4, 2026
A command has to be grouped in its `*_commands_config.py` and embedded in one of the breeze docs, or the static checks reject it - `--allow-pre-releases` had no group and `workflow-run release-constraints` had a generated screenshot that nothing referenced. The two `setup` screenshots move because the command list they render is exactly what gained the new entry.
Contributor
Backport failed to create: v3-3-test. View the failure log Run detailsNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
You can attempt to backport this manually by running: cherry_picker 26d53bd v3-3-testThis should apply the commit to the v3-3-test branch and leave the commit in conflict state marking After you have resolved the conflicts, you can continue the backport process by running: cherry_picker --continueIf you don't have cherry-picker installed, see the installation guide. |
1 task
potiuk
added a commit
that referenced
this pull request
Aug 4, 2026
#71081) * [v3-3-test] Resolve the constraints a release ships instead of tagging whatever exists (#71040) * Resolve the constraints a release ships instead of tagging whatever exists The constraints published with a version were whatever the constraints-X-Y branch happened to hold when the release ran - a resolution made by the last CI build, for the sources at that moment, rather than for the version being released. The candidate tagged that tip and the final release retagged the candidate, so no release ever resolved constraints of its own. A candidate now resolves them allowing pre-releases: the providers of the wave being voted on exist on PyPI only as rc versions, so constraints that refuse pre-releases cannot describe what a tester is asked to install. They land on a branch of the candidate's own, leaving the branch every other build reads where it was. The final release cannot promote those by retagging - a released version must never pin an rc - so it resolves the same set again, without pre-releases, and commits onto constraints-X-Y, which is what makes the released constraints the baseline everything downstream reads. Which of the two happens is derived from the version, so the stage cannot be set inconsistently with it. The work runs on CI runners rather than on the release manager's machine, which would otherwise need a CI image for every supported Python before it could cut a release, and is reachable on its own through `breeze workflow-run release-constraints` for redoing a candidate's constraints or producing them for a release cut before this existed. * Let only the providers answer with a pre-release `--pre` applies to the whole resolution, so a candidate's constraints could pin a pre-release of any dependency - a beta of some third-party library would end up in what a release ships, which is not what allowing rc providers was meant to permit. uv considers a pre-release for a package only when a requirement for it mentions one, so naming the providers with a pre-release lower bound confines the allowance to them and leaves every other package on the default policy. * State the pre-release scoping rather than leaning on uv's default The rc lower bounds already confined pre-releases to the providers, but only because uv's default strategy happens to permit them for explicitly marked packages. Naming `explicit` says that in the command instead of leaving it to a default that could change, and drops the `if-necessary` half of that default - the part that would let a package nobody marked resolve to a pre-release when no final version satisfies it. * Document what a release manager now sees at the constraints step The candidate half was undocumented - the release notes described only what the final release does, leaving a release manager to infer why a candidate suddenly grows a branch and a tag of its own, and why its constraints pin rc providers when nothing else in them is a pre-release. Both stages and the scope of the pre-release allowance are stated where each is reached. * Register the new constraints command where the docs checks look for it A command has to be grouped in its `*_commands_config.py` and embedded in one of the breeze docs, or the static checks reject it - `--allow-pre-releases` had no group and `workflow-run release-constraints` had a generated screenshot that nothing referenced. The two `setup` screenshots move because the command list they render is exactly what gained the new entry. (cherry picked from commit 26d53bd) Co-authored-by: Jarek Potiuk <jarek@potiuk.com> * Trim the backported constraints tests to what this branch implements The cherry-pick created the test file in full, including the cases for the provider-downgrade check, which exists only on main - this branch has no check_providers_not_downgraded for them to exercise, so seven of the nine tests fail on an attribute that is not there. What the picked commit added here is the pre-release requirement builder, and that is what the file is left holding.
Merged
1 task
vatsrahul1001
added a commit
that referenced
this pull request
Aug 5, 2026
…traints resolution Follow-up to #71040. airflow-core requires `opentelemetry-exporter-prometheus>=0.47b0`, a package that only ever ships betas (released constraints already pin e.g. `==0.65b0`). The candidate constraints resolution runs `uv pip install ... --prerelease explicit`, which permits a pre-release only for a package some requirement marks as such and drops the if-necessary fallback, so with no explicit mark it fails with 'No solution found'. Add it to `additional_constraints_for_highest_resolution` (the build-side list the failure message points at) rather than the provider-only pre-release list, so it is an always-allowed pre-release across every resolution, matching how it already ships.
potiuk
pushed a commit
that referenced
this pull request
Aug 5, 2026
…traints resolution (#71110) Follow-up to #71040. airflow-core requires `opentelemetry-exporter-prometheus>=0.47b0`, a package that only ever ships betas (released constraints already pin e.g. `==0.65b0`). The candidate constraints resolution runs `uv pip install ... --prerelease explicit`, which permits a pre-release only for a package some requirement marks as such and drops the if-necessary fallback, so with no explicit mark it fails with 'No solution found'. Add it to `additional_constraints_for_highest_resolution` (the build-side list the failure message points at) rather than the provider-only pre-release list, so it is an always-allowed pre-release across every resolution, matching how it already ships.
potiuk
pushed a commit
that referenced
this pull request
Aug 5, 2026
…ndidate constraints resolution (#71110) (#71132) Follow-up to #71040. airflow-core requires `opentelemetry-exporter-prometheus>=0.47b0`, a package that only ever ships betas (released constraints already pin e.g. `==0.65b0`). The candidate constraints resolution runs `uv pip install ... --prerelease explicit`, which permits a pre-release only for a package some requirement marks as such and drops the if-necessary fallback, so with no explicit mark it fails with 'No solution found'. Add it to `additional_constraints_for_highest_resolution` (the build-side list the failure message points at) rather than the provider-only pre-release list, so it is an always-allowed pre-release across every resolution, matching how it already ships. (cherry picked from commit b23fa6b) Co-authored-by: Rahul Vats <43964496+vatsrahul1001@users.noreply.github.com>
1 task
vatsrahul1001
added a commit
that referenced
this pull request
Aug 5, 2026
The Release constraints workflow (added in #71040) seeds its actor allowlist with the standard committer list but omitted vatsrahul1001, who is already on the allowlist of the sibling release workflows (publish-docs-to-s3, release_dockerhub_image, update-constraints-on-push). As the 3.3.1 release manager he needs to dispatch this workflow; without the entry the whole run skips (build-info gate evaluates false).
vatsrahul1001
added a commit
that referenced
this pull request
Aug 5, 2026
…owlist (#71152) (#71153) The Release constraints workflow (added in #71040) seeds its actor allowlist with the standard committer list but omitted vatsrahul1001, who is already on the allowlist of the sibling release workflows (publish-docs-to-s3, release_dockerhub_image, update-constraints-on-push). As the 3.3.1 release manager he needs to dispatch this workflow; without the entry the whole run skips (build-info gate evaluates false). (cherry picked from commit 24c0069) Co-authored-by: Rahul Vats <43964496+vatsrahul1001@users.noreply.github.com>
1 task
vatsrahul1001
pushed a commit
that referenced
this pull request
Aug 5, 2026
#71081) * [v3-3-test] Resolve the constraints a release ships instead of tagging whatever exists (#71040) * Resolve the constraints a release ships instead of tagging whatever exists The constraints published with a version were whatever the constraints-X-Y branch happened to hold when the release ran - a resolution made by the last CI build, for the sources at that moment, rather than for the version being released. The candidate tagged that tip and the final release retagged the candidate, so no release ever resolved constraints of its own. A candidate now resolves them allowing pre-releases: the providers of the wave being voted on exist on PyPI only as rc versions, so constraints that refuse pre-releases cannot describe what a tester is asked to install. They land on a branch of the candidate's own, leaving the branch every other build reads where it was. The final release cannot promote those by retagging - a released version must never pin an rc - so it resolves the same set again, without pre-releases, and commits onto constraints-X-Y, which is what makes the released constraints the baseline everything downstream reads. Which of the two happens is derived from the version, so the stage cannot be set inconsistently with it. The work runs on CI runners rather than on the release manager's machine, which would otherwise need a CI image for every supported Python before it could cut a release, and is reachable on its own through `breeze workflow-run release-constraints` for redoing a candidate's constraints or producing them for a release cut before this existed. * Let only the providers answer with a pre-release `--pre` applies to the whole resolution, so a candidate's constraints could pin a pre-release of any dependency - a beta of some third-party library would end up in what a release ships, which is not what allowing rc providers was meant to permit. uv considers a pre-release for a package only when a requirement for it mentions one, so naming the providers with a pre-release lower bound confines the allowance to them and leaves every other package on the default policy. * State the pre-release scoping rather than leaning on uv's default The rc lower bounds already confined pre-releases to the providers, but only because uv's default strategy happens to permit them for explicitly marked packages. Naming `explicit` says that in the command instead of leaving it to a default that could change, and drops the `if-necessary` half of that default - the part that would let a package nobody marked resolve to a pre-release when no final version satisfies it. * Document what a release manager now sees at the constraints step The candidate half was undocumented - the release notes described only what the final release does, leaving a release manager to infer why a candidate suddenly grows a branch and a tag of its own, and why its constraints pin rc providers when nothing else in them is a pre-release. Both stages and the scope of the pre-release allowance are stated where each is reached. * Register the new constraints command where the docs checks look for it A command has to be grouped in its `*_commands_config.py` and embedded in one of the breeze docs, or the static checks reject it - `--allow-pre-releases` had no group and `workflow-run release-constraints` had a generated screenshot that nothing referenced. The two `setup` screenshots move because the command list they render is exactly what gained the new entry. (cherry picked from commit 26d53bd) Co-authored-by: Jarek Potiuk <jarek@potiuk.com> * Trim the backported constraints tests to what this branch implements The cherry-pick created the test file in full, including the cases for the provider-downgrade check, which exists only on main - this branch has no check_providers_not_downgraded for them to exercise, so seven of the nine tests fail on an attribute that is not there. What the picked commit added here is the pre-release requirement builder, and that is what the file is left holding.
vatsrahul1001
added a commit
that referenced
this pull request
Aug 5, 2026
…ndidate constraints resolution (#71110) (#71132) Follow-up to #71040. airflow-core requires `opentelemetry-exporter-prometheus>=0.47b0`, a package that only ever ships betas (released constraints already pin e.g. `==0.65b0`). The candidate constraints resolution runs `uv pip install ... --prerelease explicit`, which permits a pre-release only for a package some requirement marks as such and drops the if-necessary fallback, so with no explicit mark it fails with 'No solution found'. Add it to `additional_constraints_for_highest_resolution` (the build-side list the failure message points at) rather than the provider-only pre-release list, so it is an always-allowed pre-release across every resolution, matching how it already ships. (cherry picked from commit b23fa6b) Co-authored-by: Rahul Vats <43964496+vatsrahul1001@users.noreply.github.com>
vatsrahul1001
added a commit
that referenced
this pull request
Aug 5, 2026
…owlist (#71152) (#71153) The Release constraints workflow (added in #71040) seeds its actor allowlist with the standard committer list but omitted vatsrahul1001, who is already on the allowlist of the sibling release workflows (publish-docs-to-s3, release_dockerhub_image, update-constraints-on-push). As the 3.3.1 release manager he needs to dispatch this workflow; without the entry the whole run skips (build-info gate evaluates false). (cherry picked from commit 24c0069) Co-authored-by: Rahul Vats <43964496+vatsrahul1001@users.noreply.github.com>
dabla
pushed a commit
to dabla/airflow
that referenced
this pull request
Aug 14, 2026
…xists (apache#71040) * Resolve the constraints a release ships instead of tagging whatever exists The constraints published with a version were whatever the constraints-X-Y branch happened to hold when the release ran - a resolution made by the last CI build, for the sources at that moment, rather than for the version being released. The candidate tagged that tip and the final release retagged the candidate, so no release ever resolved constraints of its own. A candidate now resolves them allowing pre-releases: the providers of the wave being voted on exist on PyPI only as rc versions, so constraints that refuse pre-releases cannot describe what a tester is asked to install. They land on a branch of the candidate's own, leaving the branch every other build reads where it was. The final release cannot promote those by retagging - a released version must never pin an rc - so it resolves the same set again, without pre-releases, and commits onto constraints-X-Y, which is what makes the released constraints the baseline everything downstream reads. Which of the two happens is derived from the version, so the stage cannot be set inconsistently with it. The work runs on CI runners rather than on the release manager's machine, which would otherwise need a CI image for every supported Python before it could cut a release, and is reachable on its own through `breeze workflow-run release-constraints` for redoing a candidate's constraints or producing them for a release cut before this existed. * Let only the providers answer with a pre-release `--pre` applies to the whole resolution, so a candidate's constraints could pin a pre-release of any dependency - a beta of some third-party library would end up in what a release ships, which is not what allowing rc providers was meant to permit. uv considers a pre-release for a package only when a requirement for it mentions one, so naming the providers with a pre-release lower bound confines the allowance to them and leaves every other package on the default policy. * State the pre-release scoping rather than leaning on uv's default The rc lower bounds already confined pre-releases to the providers, but only because uv's default strategy happens to permit them for explicitly marked packages. Naming `explicit` says that in the command instead of leaving it to a default that could change, and drops the `if-necessary` half of that default - the part that would let a package nobody marked resolve to a pre-release when no final version satisfies it. * Document what a release manager now sees at the constraints step The candidate half was undocumented - the release notes described only what the final release does, leaving a release manager to infer why a candidate suddenly grows a branch and a tag of its own, and why its constraints pin rc providers when nothing else in them is a pre-release. Both stages and the scope of the pre-release allowance are stated where each is reached. * Register the new constraints command where the docs checks look for it A command has to be grouped in its `*_commands_config.py` and embedded in one of the breeze docs, or the static checks reject it - `--allow-pre-releases` had no group and `workflow-run release-constraints` had a generated screenshot that nothing referenced. The two `setup` screenshots move because the command list they render is exactly what gained the new entry.
dabla
pushed a commit
to dabla/airflow
that referenced
this pull request
Aug 14, 2026
…traints resolution (apache#71110) Follow-up to apache#71040. airflow-core requires `opentelemetry-exporter-prometheus>=0.47b0`, a package that only ever ships betas (released constraints already pin e.g. `==0.65b0`). The candidate constraints resolution runs `uv pip install ... --prerelease explicit`, which permits a pre-release only for a package some requirement marks as such and drops the if-necessary fallback, so with no explicit mark it fails with 'No solution found'. Add it to `additional_constraints_for_highest_resolution` (the build-side list the failure message points at) rather than the provider-only pre-release list, so it is an always-allowed pre-release across every resolution, matching how it already ships.
dabla
pushed a commit
to dabla/airflow
that referenced
this pull request
Aug 14, 2026
…he#71152) The Release constraints workflow (added in apache#71040) seeds its actor allowlist with the standard committer list but omitted vatsrahul1001, who is already on the allowlist of the sibling release workflows (publish-docs-to-s3, release_dockerhub_image, update-constraints-on-push). As the 3.3.1 release manager he needs to dispatch this workflow; without the entry the whole run skips (build-info gate evaluates false).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The constraints published with a version were whatever the
constraints-X-Ybranch happened to hold when the release ran — a resolution made by the last CI build, for the sources at that moment, rather than for the version being released. The candidate tagged that tip and the final release retagged the candidate, so no release ever resolved constraints of its own.What happens now
The stage is derived from the version, so it cannot be set inconsistently with it:
3.1.3rc1constraints-3.1.3rc1, branched offconstraints-3-1constraints-3.1.3rc13.1.3constraints-3-1(commit)constraints-3.1.3A candidate resolves allowing pre-releases: the providers of the wave being voted on exist on PyPI only as
rcNversions, so constraints that refuse pre-releases cannot describe what a tester is asked to install. It lands on a branch of its own, leaving the branch every other build reads where it was.A final cannot promote those by retagging — a released version must never pin an rc — so it resolves the same set again without pre-releases, against the providers now published as finals, and commits onto
constraints-X-Y. That commit is what makes the released constraints the baseline everything downstream reads, which also removes the need to refresh the branch beforehand.How it runs
A new
release-constraints.ymlworkflow, so the work happens on CI runners rather than on the release manager's machine, which would otherwise need a CI image for every supported Python before it could cut a release.start-releaseand the RC command trigger it; it is also reachable on its own for redoing a candidate's constraints or producing them for a release cut before this existed:Only the PyPI constraints are regenerated — the other modes serve CI, and moving them here would change them for reasons unrelated to the release.
Notes for reviewers
--allow-pre-releasesis threaded throughgenerate-constraintsto--preon the uv resolve, matching the convention already ininstall_airflow_and_providers.py.ALLOW_PRE_RELEASESas its env var name — that is the breeze option's own envvar, and having both set it would obscure which is in force.get_constraints_branch_for_versionand its test are removed: the workflow derives the branch now, so it was dead.main, so the candidate path in particular is unexercised.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5) following the guidelines