Skip to content

Resolve the constraints a release ships instead of tagging whatever exists - #71040

Merged
potiuk merged 5 commits into
mainfrom
constraints-generated-per-release
Aug 4, 2026
Merged

Resolve the constraints a release ships instead of tagging whatever exists#71040
potiuk merged 5 commits into
mainfrom
constraints-generated-per-release

Conversation

@potiuk

@potiuk potiuk commented Aug 4, 2026

Copy link
Copy Markdown
Member

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.

What happens now

The stage is derived from the version, so it cannot be set inconsistently with it:

Version Pre-releases Lands on Tagged
3.1.3rc1 allowed constraints-3.1.3rc1, branched off constraints-3-1 constraints-3.1.3rc1
3.1.3 refused constraints-3-1 (commit) constraints-3.1.3

A candidate resolves allowing pre-releases: the providers of the wave being voted on exist on PyPI only as rcN versions, 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.yml workflow, 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-release and 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:

breeze workflow-run release-constraints --version 3.1.3rc1 --ref v3-1-stable

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-releases is threaded through generate-constraints to --pre on the uv resolve, matching the convention already in install_airflow_and_providers.py.
  • The reusable workflow's new input deliberately does not use ALLOW_PRE_RELEASES as 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_version and its test are removed: the workflow derives the branch now, so it was dead.
  • Untested end-to-end — the workflow cannot be dispatched until it is on main, so the candidate path in particular is unexercised.

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5)

Generated-by: Claude Code (Opus 5) following the guidelines

…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 added 3 commits August 4, 2026 02:39
`--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.
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.
@potiuk
potiuk merged commit 26d53bd into main Aug 4, 2026
157 checks passed
@potiuk
potiuk deleted the constraints-generated-per-release branch August 4, 2026 09:52
@potiuk potiuk added this to the Airflow 3.3.1 milestone Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Backport failed to create: v3-3-test. View the failure log Run details

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
v3-3-test Commit Link

You can attempt to backport this manually by running:

cherry_picker 26d53bd v3-3-test

This should apply the commit to the v3-3-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

If you don't have cherry-picker installed, see the installation guide.

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.
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>
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>
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants